Skip to content

Commit

Permalink
Merge branch 'master' of github.com:skramm/homog2d
Browse files Browse the repository at this point in the history
  • Loading branch information
skramm committed Apr 13, 2024
2 parents f55cb79 + f29b647 commit df007e2
Show file tree
Hide file tree
Showing 5 changed files with 2,374 additions and 2,161 deletions.
20 changes: 17 additions & 3 deletions docs/homog2d_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -2158,6 +2158,20 @@ It is enabled only if symbol `HOMOG2D_ENABLE_RTP` is defined

### 10.3 - Technical details on svg file import

You can fetch the size of image in the SVG file (as `double`):
```C++
std::pair<double,double> imSize(500.,500.); // default size
try
{
imSize = svg::getImgSize( doc );
std::cout << "size: " << imSize.first << " x " << imSize.second << '\n';
}
catch( const std::exception& error )
{
std::cout << "input file has no size, size set to 500x500\n -msg=" << error.what() << '\n';
}
```
When importing a SVG file, the following points must be considered:
* All the color, style,etc. Svg attributes present in file are lost, as this library does not store them.
Expand All @@ -2171,9 +2185,9 @@ either with the dedicated keywords
([`polyline`](https://www.w3.org/TR/SVG2/shapes.html#PolylineElement)
or [`polygon`](https://www.w3.org/TR/SVG2/shapes.html#PolygonElement)), or by using the
[`path`](https://www.w3.org/TR/SVG2/paths.html#PathElement) element, that is much more general.
This import subsystem handles both the `polyline` , `polygon` and `path` elements.
However, for the latter, the "curve" elements (SVG path commands C, S, Q, T) are not handled,
the import code will throw if such a command is encoutered while importing a SVG path object.
This import subsystem handles all three of these.
However, for the "path", the "curve" elements (SVG path commands C, S, Q, T) are not handled,
the import code will just ignore thoses commands, if encoutered while importing a SVG path object.
* When importing a SVG "path", it will be automatically converted to a `CPolyline` or a `OPolyline`, depending on the fact
that it holds a `z` at the end of the SVG path "d" string.
* If you have trouble with some SVG file, a helper function `printFileAttrib()` is provided that will output all the SVG attributes of a file on `stdout`.
Expand Down
164 changes: 127 additions & 37 deletions homog2d.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ See https://github.com/skramm/homog2d
static_assert( (std::is_arithmetic<T>::value && !std::is_same<T, bool>::value), "Type of value must be numerical" )
#endif

#ifndef HOMOG2D_NOWARNINGS
#define HOMOG2D_LOG_WARNING( a ) \
std::cerr << "homog2d warning (" << ++err::warningCount() << "), line " << __LINE__ << "\n msg=" << a << "\n";
#else
#define HOMOG2D_LOG_WARNING
#endif

/*
\todo 20230212 ttmath support: this definition does not work, I don't know why !!! see namespace \ref trait
\verbatim
Expand Down Expand Up @@ -244,6 +251,14 @@ inline size_t& errorCount()
static size_t c;
return c;
}

/// Used in macro HOMOG2D_LOG_WARNING
inline size_t& warningCount()
{
static size_t c;
return c;
}

} //namespace err

/// Holds the types needed for policy based design
Expand Down Expand Up @@ -11527,6 +11542,8 @@ numberValues()

enum class PathMode { Absolute, Relative };

/// Holds the current SVG "path" command, and the number of required numerical values
/// \sa SvgValuesBuffer
struct SvgPathCommand
{
PathMode _absRel = PathMode::Absolute;
Expand All @@ -11541,8 +11558,15 @@ struct SvgPathCommand
_command = c;
_nbValues = numberValues().at(c);
}
bool isNewPolyline() const
{
if( _command == 'm' || _command == 'M' || _command == 'z' || _command == 'Z' )
return true;
return false;
}
};


/// Generate new point from current mode and previous point, handles absolute/relative coordinates
inline
Point2d_<double>
Expand Down Expand Up @@ -11707,14 +11731,14 @@ m 261.68497,138.79393 2.57,3.15 -0.72,1.27 2.18,1.94 -0.7,4.93 1.88,0.9
The return value holds as 'second' a bool value, will be true if closed polyline
*/
inline
std::pair<std::vector<Point2d>,bool>
auto
parsePath( const char* s )
{
SvgPathCommand mode;
std::vector<Point2d> out;
std::vector<std::vector<Point2d>> vout(1);
SvgValuesBuffer values;
// std::vector<double> values;
std::string str(s);
size_t idx = 0;
HOMOG2D_LOG( "parsing string -" << str << "- #=" << str.size() );
if( str.size() == 0 )
HOMOG2D_THROW_ERROR_1( "SVG path string is empty" );
Expand All @@ -11724,34 +11748,48 @@ parsePath( const char* s )
do
{
auto e = getNextElem( str, it );
HOMOG2D_LOG( "parsing element -" << e << "- #=" << e.size() );
// HOMOG2D_LOG( "parsing element -" << e << "- #=" << e.size() );

if( e.size() == 1 && !isDigit(e[0]) ) // we have a command !
{
if( values.size() != 0 ) // if we have some values stored,
values.storeValues( out, mode ); // first process them an add new point
values.storeValues( vout[idx], mode ); // first process them and add new point

mode = getCommand( e[0] );
HOMOG2D_LOG( "command=" << e[0] );
if( !svgPathCommandIsAllowed(mode._command) )
HOMOG2D_THROW_ERROR_1( "SVG path command -" << mode._command << "- not handled" );

if( mode.isNewPolyline() && !vout[idx].empty() )
{
vout.push_back( std::vector<Point2d>() );
idx++;
HOMOG2D_LOG( "NEW vector idx=" << idx );
}
}
else // not a command, but a value
{
HOMOG2D_LOG( "process value, values size=" << values.size() );
// HOMOG2D_LOG( "process value, values size=" << values.size() );
if( values.size() == (size_t)mode._nbValues ) // already got enough values
values.storeValues( out, mode );
values.storeValues( vout[idx], mode );
values.addValue( e );
}
}
while( it < str.cend() );
if( values.size() )
values.storeValues( out, mode );

//priv::printVector( out, "point set", true );
if( values.size() ) // process remaining values that have been stored
values.storeValues( vout[idx], mode );

HOMOG2D_LOG( "Nb vectors=" << vout.size() );
for( auto& v: vout )
if( v.size() )
v = purgeSetDupes( v );
if( vout.back().empty() )
vout.pop_back();

HOMOG2D_LOG( "RETURN" );
return std::make_pair(
purgeSetDupes( out ),
vout,
mode._command == 'Z' ? true : false
);
}
Expand All @@ -11768,9 +11806,8 @@ class Visitor: public tinyxml2::XMLVisitor
/// This type is used to provide a type that can be used in a switch (see VisitExit() ),
/// as this cannot be done with a string |-(
enum SvgType {
T_circle, T_rect, T_line, T_polygon, T_polyline, T_ellipse
,T_path ///< preliminar
,T_other ///< for other elements (\c <svg>) or illegal ones, that will just be ignored
T_circle, T_rect, T_line, T_polygon, T_polyline, T_ellipse, T_path,
T_other ///< for other elements (\c <svg>) or illegal ones, that will just be ignored
};

/// A map holding correspondences between type as a string and type as a SvgType.
Expand Down Expand Up @@ -11809,8 +11846,10 @@ class Visitor: public tinyxml2::XMLVisitor
bool VisitExit( const tinyxml2::XMLElement& ) override;
};

namespace svgp {
//------------------------------------------------------------------
/// Fetch attribute from XML element. Tag \c e_name is there just in case of trouble.
inline
double
getAttribValue( const tinyxml2::XMLElement& e, const char* str, std::string e_name )
{
Expand All @@ -11824,6 +11863,7 @@ getAttribValue( const tinyxml2::XMLElement& e, const char* str, std::string e_na
/**
\todo Who owns the data? Should we return a string and/or release the memory?
*/
inline
const char*
getAttribString( const char* attribName, const tinyxml2::XMLElement& e )
{
Expand All @@ -11833,6 +11873,8 @@ getAttribString( const char* attribName, const tinyxml2::XMLElement& e )
return pts;
}

} // namespace svgp

/// This is the place where actual SVG data is converted and stored into vector
/**
(see manual, section "SVG import")
Expand All @@ -11849,17 +11891,23 @@ bool Visitor::VisitExit( const tinyxml2::XMLElement& e )
{
case T_circle:
{
std::unique_ptr<rtp::Root> c( new Circle( getAttribValue( e, "cx", n ), getAttribValue( e, "cy", n ), getAttribValue( e, "r", n ) ) );
std::unique_ptr<rtp::Root> c(
new Circle(
svgp::getAttribValue( e, "cx", n ),
svgp::getAttribValue( e, "cy", n ),
svgp::getAttribValue( e, "r", n )
)
);
_vec.push_back( std::move(c) );
}
break;

case T_rect:
{
auto x1 = getAttribValue( e, "x", n );
auto y1 = getAttribValue( e, "y", n );
auto w = getAttribValue( e, "width", n );
auto h = getAttribValue( e, "height", n );
auto x1 = svgp::getAttribValue( e, "x", n );
auto y1 = svgp::getAttribValue( e, "y", n );
auto w = svgp::getAttribValue( e, "width", n );
auto h = svgp::getAttribValue( e, "height", n );
std::unique_ptr<rtp::Root> r( new FRect( x1, y1, x1+w, y1+h ) );
_vec.push_back( std::move(r) );
}
Expand All @@ -11868,15 +11916,20 @@ bool Visitor::VisitExit( const tinyxml2::XMLElement& e )
case T_line:
{
std::unique_ptr<rtp::Root> s(
new Segment( getAttribValue( e, "x1", n ), getAttribValue( e, "y1", n ), getAttribValue( e, "x2", n ), getAttribValue( e, "y2", n ) )
new Segment(
svgp::getAttribValue( e, "x1", n ),
svgp::getAttribValue( e, "y1", n ),
svgp::getAttribValue( e, "x2", n ),
svgp::getAttribValue( e, "y2", n )
)
);
_vec.push_back( std::move(s) );
}
break;

case T_polygon:
{
auto pts_str = getAttribString( "points", e );
auto pts_str = svgp::getAttribString( "points", e );
auto vec_pts = svgp::parsePoints( pts_str );
std::unique_ptr<rtp::Root> p( new CPolyline(vec_pts) );
_vec.push_back( std::move(p) );
Expand All @@ -11885,37 +11938,51 @@ bool Visitor::VisitExit( const tinyxml2::XMLElement& e )

case T_polyline:
{
auto pts_str = getAttribString( "points", e );
auto pts_str = svgp::getAttribString( "points", e );
auto vec_pts = svgp::parsePoints( pts_str );
std::unique_ptr<rtp::Root> p( new OPolyline(vec_pts) );
_vec.push_back( std::move(p) );
}
break;

case T_path:
case T_path: // a path can hold multiple polygons (because of the 'L' command)
{
auto pts_str = getAttribString( "d", e );
auto parse_res = svgp::parsePath( pts_str );
if( parse_res.second == true )
auto pts_str = svgp::getAttribString( "d", e );
try
{
std::unique_ptr<rtp::Root> p( new CPolyline(parse_res.first) );
_vec.push_back( std::move(p) );
}
else
auto parse_res = svgp::parsePath( pts_str );
const auto& vec_vec_pts = parse_res.first; //
for( const auto& vec_pts: vec_vec_pts )
if( parse_res.second == true )
{
// _vecVar.emplace_back( CPolyline(vec_pts) );
std::unique_ptr<rtp::Root> p( new CPolyline(vec_pts) );
_vec.push_back( std::move(p) );

}
else
{
// _vecVar.emplace_back( OPolyline(vec_pts) );
std::unique_ptr<rtp::Root> p( new OPolyline(vec_pts) );
_vec.push_back( std::move(p) );
}
}
catch( std::exception& err ) // an unhandled path command will just get the whole path command ignored
{
std::unique_ptr<rtp::Root> p( new OPolyline(parse_res.first) );
_vec.push_back( std::move(p) );
HOMOG2D_LOG_WARNING( "Unable to import SVG path command\n -msg="
<< err.what() << "\n -input string=" << pts_str
);
}
}
break;

case T_ellipse:
{
auto x = getAttribValue( e, "cx", n );
auto y = getAttribValue( e, "cy", n );
auto rx = getAttribValue( e, "rx", n );
auto ry = getAttribValue( e, "ry", n );
auto rot = svgp::getEllipseRotateAttr( getAttribString( "transform", e ) );
auto x = svgp::getAttribValue( e, "cx", n );
auto y = svgp::getAttribValue( e, "cy", n );
auto rx = svgp::getAttribValue( e, "rx", n );
auto ry = svgp::getAttribValue( e, "ry", n );
auto rot = svgp::getEllipseRotateAttr( svgp::getAttribString( "transform", e ) );
Ellipse* ell = new Ellipse( x, y, rx, ry );

auto H = Homogr().addTranslation(-x,-y).addRotation(rot.second).addTranslation(x,y);
Expand All @@ -11937,6 +12004,7 @@ bool Visitor::VisitExit( const tinyxml2::XMLElement& e )
return true;
}

inline
void
printFileAttrib( const tinyxml2::XMLDocument& doc )
{
Expand All @@ -11951,6 +12019,28 @@ printFileAttrib( const tinyxml2::XMLDocument& doc )
}
}

/// Fetch size of image in SVG file
inline
auto
getImgSize( const tinyxml2::XMLDocument& doc )
{
const tinyxml2::XMLElement* root = doc.RootElement();
const tinyxml2::XMLAttribute* pAttrib = root->FirstAttribute();
double w = -1., h = -1.;
while( pAttrib )
{
auto attr = std::string( pAttrib->Name() );
if( attr == "width" )
w = std::stod( pAttrib->Value() );
if( attr == "height" )
h = std::stod( pAttrib->Value() );
pAttrib=pAttrib->Next();
}
if( w == -1. || h == -1. )
HOMOG2D_THROW_ERROR_1( "unable to find size in SVG file" );
return std::make_pair( w, h );
}

} // namespace svg

#endif // HOMOG2D_USE_SVG_IMPORT
Expand Down
Loading

0 comments on commit df007e2

Please sign in to comment.