27#include <nanosvgrast.h>
29#include <fontconfig/fontconfig.h>
44 m_lines.push_back( { aStart, aEnd } );
48 bool aFilled,
const COLOR4D& aFillColor )
override
69 bool aFilled,
const COLOR4D& aFillColor )
override
74 void AddText(
const VECTOR2D& aOrigin,
const wxString& aText,
double aHeight,
double aWidth,
84 m_splines.push_back( { aStart, aBezierControl1, aBezierControl2, aEnd } );
87 std::vector<std::pair<VECTOR2D, VECTOR2D>>
m_lines;
89 std::vector<std::tuple<VECTOR2D, VECTOR2D, VECTOR2D, VECTOR2D>>
m_splines;
106 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
107 "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\""
108 " width=\"100mm\" height=\"100mm\">"
109 "<path d=\"M 50,80 C 80,20 20,20 50,80 Z\" fill=\"black\" stroke=\"none\" />"
113 buf.AppendData( svg, strlen( svg ) + 1 );
123 BOOST_REQUIRE_MESSAGE( !importer.
m_polygons.empty(),
124 "Single-node closed SVG path should produce a polygon" );
128 BOOST_CHECK_MESSAGE( poly.size() > 3,
129 "Polygon should have more than 3 vertices (got " << poly.size() <<
")" );
132 bool hasDistinctVertices =
false;
134 for(
size_t i = 1; i < poly.size(); ++i )
136 if( poly[i] != poly[0] )
138 hasDistinctVertices =
true;
143 BOOST_CHECK_MESSAGE( hasDistinctVertices,
144 "Polygon vertices should not all be at the same point" );
149 for(
size_t i = 0; i < poly.size(); ++i )
151 size_t j = ( i + 1 ) % poly.size();
152 area += poly[i].x * poly[j].y;
153 area -= poly[j].x * poly[i].y;
156 area = std::fabs( area ) / 2.0;
158 BOOST_CHECK_MESSAGE( area > 0.01,
159 "Polygon should have non-zero area (got " << area <<
")" );
169 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
170 "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\""
171 " width=\"100mm\" height=\"100mm\">"
172 "<path d=\"M 10,10 C 40,0 60,0 90,10 C 100,40 100,60 90,90"
173 " C 60,100 40,100 10,90 C 0,60 0,40 10,10 Z\" fill=\"black\" stroke=\"none\" />"
177 buf.AppendData( svg, strlen( svg ) + 1 );
186 BOOST_REQUIRE_MESSAGE( !importer.
m_polygons.empty(),
187 "Multi-node closed SVG path should produce a polygon" );
189 BOOST_CHECK_MESSAGE( importer.
m_polygons[0].size() > 4,
190 "Multi-node polygon should have many vertices" );
196using SVG_IMAGE = std::unique_ptr<NSVGimage,
decltype( &nsvgDelete )>;
198SVG_IMAGE parseSvgText(
const std::string& aBody )
200 using FONT_CONFIG = std::unique_ptr<FcConfig,
decltype( &FcConfigDestroy )>;
201 static FONT_CONFIG
fonts = []
203 FONT_CONFIG
config( FcConfigCreate(), FcConfigDestroy );
206 for(
const char* file : {
"NimbusSans-Regular.t1",
"NimbusSans-Bold.t1",
"NimbusSans-Italic.t1" } )
209 + wxString::FromUTF8( file ) ).utf8_str();
216 std::string svg =
"<svg width=\"300\" height=\"150\" xmlns=\"http://www.w3.org/2000/svg\">"
218 return SVG_IMAGE( nsvgParseWithFontConfig( svg.data(),
"px", 96,
fonts.get() ), nsvgDelete );
221std::vector<NSVGshape*> svgShapes(
const SVG_IMAGE& aImage )
223 std::vector<NSVGshape*>
result;
225 for( NSVGshape* shape = aImage->shapes; shape; shape = shape->next )
226 result.push_back( shape );
235 auto image = parseSvgText(
"<text x=\"10\" y=\"60\" font-family=\"sans-serif\" font-size=\"40\">"
236 "<![CDATA[UMC]]></text>" );
238 BOOST_REQUIRE_EQUAL( svgShapes(
image ).size(), 3 );
239 std::unique_ptr<NSVGrasterizer,
decltype( &nsvgDeleteRasterizer )> rasterizer(
240 nsvgCreateRasterizer(), nsvgDeleteRasterizer );
242 std::vector<unsigned char> pixels( 300 * 150 * 4 );
243 nsvgRasterize( rasterizer.get(),
image.get(), 0, 0, 1, pixels.data(), 300, 150, 300 * 4 );
246 for(
size_t i = 3; i < pixels.size(); i += 4 )
247 ink += pixels[i] != 0;
249 BOOST_CHECK_GT( ink, 200 );
255 auto encoded = parseSvgText(
"<text y=\"50\"><&Ωé</text>" );
256 auto literal = parseSvgText(
"<text y=\"50\"><![CDATA[<&Ωé]]></text>" );
257 auto unexpanded = parseSvgText(
"<text y=\"50\"><![CDATA[<]]></text>" );
261 auto a = svgShapes( encoded );
262 auto b = svgShapes( literal );
263 BOOST_REQUIRE_EQUAL( a.size(), 4 );
264 BOOST_REQUIRE_EQUAL( b.size(), a.size() );
267 for(
size_t i = 0; i < a.size(); ++i )
269 for(
int axis = 0; axis < 4; ++axis )
270 BOOST_CHECK_SMALL( a[i]->bounds[axis] - b[i]->bounds[axis], 0.001f );
277 auto image = parseSvgText(
"<text x=\"10 50 100\" y=\"40 60 80\" font-size=\"20\">"
278 "H<tspan dx=\"3\" dy=\"4\">H</tspan>H</text>" );
280 auto shapes = svgShapes(
image );
281 BOOST_REQUIRE_EQUAL( shapes.size(), 3 );
282 BOOST_CHECK_SMALL( shapes[1]->bounds[0] - shapes[0]->bounds[0] - 43, 0.001f );
283 BOOST_CHECK_SMALL( shapes[1]->bounds[1] - shapes[0]->bounds[1] - 24, 0.001f );
284 BOOST_CHECK_SMALL( shapes[2]->bounds[0] - shapes[0]->bounds[0] - 90, 0.001f );
285 BOOST_CHECK_SMALL( shapes[2]->bounds[1] - shapes[0]->bounds[1] - 40, 0.001f );
291 auto start = parseSvgText(
"<text x=\"150\" y=\"60\" font-size=\"30\">H<tspan>H</tspan>H</text>" );
292 auto middle = parseSvgText(
"<text x=\"150\" y=\"60\" font-size=\"30\" text-anchor=\"middle\">"
293 "H<tspan>H</tspan>H</text>" );
294 auto end = parseSvgText(
"<text x=\"150\" y=\"60\" font-size=\"30\" text-anchor=\"end\">"
295 "H<tspan>H</tspan>H</text>" );
299 auto a = svgShapes( start );
300 auto b = svgShapes( middle );
301 auto c = svgShapes(
end );
302 BOOST_REQUIRE_EQUAL( a.size(), 3 );
303 BOOST_REQUIRE_EQUAL( b.size(), 3 );
304 BOOST_REQUIRE_EQUAL( c.size(), 3 );
305 float shift = a[0]->bounds[0] - b[0]->bounds[0];
306 BOOST_CHECK_GT( shift, 20 );
308 for(
size_t i = 0; i < a.size(); ++i )
310 BOOST_CHECK_SMALL( a[i]->bounds[0] - b[i]->bounds[0] - shift, 0.001f );
311 BOOST_CHECK_SMALL( a[i]->bounds[0] - c[i]->bounds[0] - 2 * shift, 0.001f );
318 auto image = parseSvgText(
"<g font-family=\"sans-serif\" font-size=\"20\" fill=\"#123456\">"
319 "<text x=\"10\" y=\"40\">H</text>"
320 "<text x=\"10\" y=\"40\" transform=\"translate(100,0) rotate(90)\">H</text>"
321 "<text x=\"10\" y=\"40\" font-size=\"40\">H</text></g>" );
323 auto shapes = svgShapes(
image );
324 BOOST_REQUIRE_EQUAL( shapes.size(), 3 );
326 BOOST_CHECK_SMALL( shapes[1]->bounds[0] - ( 100 - shapes[0]->bounds[3] ), 0.001f );
327 BOOST_CHECK_SMALL( shapes[1]->bounds[1] - shapes[0]->bounds[0], 0.001f );
328 float height = shapes[0]->bounds[3] - shapes[0]->bounds[1];
329 BOOST_CHECK_SMALL( shapes[2]->bounds[3] - shapes[2]->bounds[1] - 2 * height, 0.05f );
335 auto image = parseSvgText(
"<style>.s { fill: #ff0000; }</style>"
336 "<path style=\"fill:#00ff00\" class=\"s\" d=\"M0 0 L10 0 L10 10 Z\"/>" );
341 std::string svg =
"<svg width=\"200\" viewBox=\"0 0 100 100\"><rect width=\"100\" height=\"100\"/></svg>";
342 SVG_IMAGE inferred( nsvgParse( svg.data(),
"px", 96 ), nsvgDelete );
352 auto regular = parseSvgText(
"<text y=\"50\" font-family=\"sans-serif\" font-size=\"30\"> H H </text>" );
353 auto spans = parseSvgText(
"<text y=\"50\" font-family=\"sans-serif\" font-size=\"30\">"
354 "H <tspan> H</tspan></text>" );
355 auto bold = parseSvgText(
"<text y=\"50\" font-family=\"sans-serif\" font-size=\"30\" font-weight=\"700\">H</text>" );
356 auto italic = parseSvgText(
"<text y=\"50\" font-family=\"sans-serif\" font-size=\"30\" font-style=\"italic\">H</text>" );
361 auto a = svgShapes( regular );
362 auto b = svgShapes( spans );
363 BOOST_REQUIRE_EQUAL( a.size(), 2 );
364 BOOST_REQUIRE_EQUAL( b.size(), 2 );
367 BOOST_CHECK_SMALL( a[1]->bounds[0] - b[1]->bounds[0], 0.001f );
368 float width = a[0]->bounds[2] - a[0]->bounds[0];
369 BOOST_CHECK_GT( bold->shapes->bounds[2] - bold->shapes->bounds[0], width );
370 BOOST_CHECK_GT( italic->shapes->bounds[2] - italic->shapes->bounds[0], width );
376 auto reference = parseSvgText(
"<text x=\"150\" y=\"60\" text-anchor=\"middle\" fill=\"#123456\">HH</text>"
377 "<text x=\"20\" y=\"100\">H</text>" );
378 auto definitions = parseSvgText(
"<text x=\"150\" y=\"60\" text-anchor=\"middle\" fill=\"#123456\">H"
379 "<defs><g><tspan>ignored</tspan></g><defs><tspan>nested</tspan></defs>"
380 "<tspan>also ignored</tspan></defs>H</text><text x=\"20\" y=\"100\">H</text>" );
383 auto a = svgShapes( reference );
384 auto b = svgShapes( definitions );
385 BOOST_REQUIRE_EQUAL( a.size(), 3 );
386 BOOST_REQUIRE_EQUAL( b.size(), a.size() );
388 for(
size_t i = 0; i < a.size(); ++i )
392 for(
int axis = 0; axis < 4; ++axis )
393 BOOST_CHECK_SMALL( a[i]->bounds[axis] - b[i]->bounds[axis], 0.001f );
400 auto encoded = parseSvgText(
"<text y=\"50\">&#zz;&#xzz;z;</text>" );
401 auto literal = parseSvgText(
"<text y=\"50\"><![CDATA[&#zz;&#xzz;z;]]></text>" );
404 auto a = svgShapes( encoded );
405 auto b = svgShapes( literal );
406 BOOST_REQUIRE_EQUAL( a.size(), 17 );
407 BOOST_REQUIRE_EQUAL( b.size(), a.size() );
409 for(
size_t i = 0; i < a.size(); ++i )
411 for(
int axis = 0; axis < 4; ++axis )
412 BOOST_CHECK_SMALL( a[i]->bounds[axis] - b[i]->bounds[axis], 0.001f );
constexpr double PCB_IU_PER_MM
Pcbnew IU is 1 nanometer.
double m_millimeterToIu
Factor to convert millimeters to Internal Units.
virtual void SetImporter(GRAPHICS_IMPORTER *aImporter)
Set the receiver of the imported shapes.
A clone of IMPORTED_STROKE, but with floating-point width.
A color representation with 4 components: red, green, blue, alpha.
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
bool Import() override
Actually imports the file.
bool LoadFromMemory(const wxMemoryBuffer &aMemBuffer) override
Set memory buffer with content for import.
void AddSpline(const VECTOR2D &aStart, const VECTOR2D &aBezierControl1, const VECTOR2D &aBezierControl2, const VECTOR2D &aEnd, const IMPORTED_STROKE &aStroke) override
Create an object representing an arc.
void AddText(const VECTOR2D &aOrigin, const wxString &aText, double aHeight, double aWidth, double aThickness, double aOrientation, GR_TEXT_H_ALIGN_T aHJustify, GR_TEXT_V_ALIGN_T aVJustify, const COLOR4D &aColor) override
Create an object representing a text.
SVG_IMPORT_TEST_IMPORTER()
std::vector< std::vector< VECTOR2D > > m_polygons
void AddArc(const VECTOR2D &aCenter, const VECTOR2D &aStart, const EDA_ANGLE &aAngle, const IMPORTED_STROKE &aStroke) override
Create an object representing an arc.
void AddCircle(const VECTOR2D &aCenter, double aRadius, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor) override
Create an object representing a circle.
void AddEllipseArc(const VECTOR2D &aCenter, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aEndAngle, const IMPORTED_STROKE &aStroke) override
Create an object representing an elliptical arc.
void AddLine(const VECTOR2D &aStart, const VECTOR2D &aEnd, const IMPORTED_STROKE &aStroke) override
Create an object representing a line segment.
std::vector< std::tuple< VECTOR2D, VECTOR2D, VECTOR2D, VECTOR2D > > m_splines
void AddEllipse(const VECTOR2D &aCenter, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor) override
Create an object representing a closed ellipse.
std::vector< std::pair< VECTOR2D, VECTOR2D > > m_lines
void AddPolygon(const std::vector< VECTOR2D > &aVertices, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor) override
Create an object representing a polygon.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(SingleNodeClosedPath)
Regression test for https://gitlab.com/kicad/code/kicad/-/issues/11445.
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
VECTOR2< double > VECTOR2D