KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_symbol_svg_export.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
24
25#include "tl/expected.hpp"
26#include <boost/test/unit_test.hpp>
27
28#include <wx/ffile.h>
29#include <wx/mstream.h>
30
31#include <eda_units.h>
32#include <lib_symbol.h>
33#include <sch_pin.h>
34#include <sch_shape.h>
35#include <sch_text.h>
36#include <sch_textbox.h>
37#include <sch_painter.h>
38#include <sch_plotter.h>
40
42#include <wx/xml/xml.h>
43
44
46{
47public:
49 {
50 m_symbol = std::make_unique<LIB_SYMBOL>( wxT( "TestSymbol" ), nullptr );
51 }
52
54
55 void AddPin( int x, int y, const wxString& name, const wxString& number )
56 {
57 std::unique_ptr<SCH_PIN> pin = std::make_unique<SCH_PIN>( m_symbol.get() );
58 pin->SetPosition( VECTOR2I( schIUScale.MilsToIU( x ), schIUScale.MilsToIU( y ) ) );
59 pin->SetName( name );
60 pin->SetNumber( number );
61 pin->SetLength( schIUScale.MilsToIU( 100 ) );
62 m_symbol->AddDrawItem( pin.release() );
63 }
64
65 void AddRectangle( int x1, int y1, int x2, int y2 )
66 {
67 std::unique_ptr<SCH_SHAPE> rect = std::make_unique<SCH_SHAPE>( SHAPE_T::RECTANGLE, LAYER_DEVICE );
68 rect->SetPosition( VECTOR2I( schIUScale.MilsToIU( x1 ), schIUScale.MilsToIU( y1 ) ) );
69 rect->SetEnd( VECTOR2I( schIUScale.MilsToIU( x2 ), schIUScale.MilsToIU( y2 ) ) );
70 rect->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
71 m_symbol->AddDrawItem( rect.release() );
72 }
73
74 void AddCircle( int cx, int cy, int radius )
75 {
76 std::unique_ptr<SCH_SHAPE> circle = std::make_unique<SCH_SHAPE>( SHAPE_T::CIRCLE, LAYER_DEVICE );
77 circle->SetPosition( VECTOR2I( schIUScale.MilsToIU( cx ), schIUScale.MilsToIU( cy ) ) );
78 circle->SetEnd( VECTOR2I( schIUScale.MilsToIU( cx + radius ), schIUScale.MilsToIU( cy ) ) );
79 circle->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
80 m_symbol->AddDrawItem( circle.release() );
81 }
82
83 void AddPolyline( const std::vector<std::pair<int, int>>& points )
84 {
85 std::unique_ptr<SCH_SHAPE> poly = std::make_unique<SCH_SHAPE>( SHAPE_T::POLY, LAYER_DEVICE );
86 poly->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
87
88 for( const auto& pt : points )
89 poly->AddPoint( VECTOR2I( schIUScale.MilsToIU( pt.first ), schIUScale.MilsToIU( pt.second ) ) );
90
91 m_symbol->AddDrawItem( poly.release() );
92 }
93
94 void AddArc( int cx, int cy, int radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle )
95 {
96 std::unique_ptr<SCH_SHAPE> arc = std::make_unique<SCH_SHAPE>( SHAPE_T::ARC, LAYER_DEVICE );
97 arc->SetCenter( VECTOR2I( schIUScale.MilsToIU( cx ), schIUScale.MilsToIU( cy ) ) );
98 arc->SetRadius( schIUScale.MilsToIU( radius ) );
99 arc->SetArcAngleAndEnd( endAngle - startAngle );
100 arc->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
101 m_symbol->AddDrawItem( arc.release() );
102 }
103
104 void AddText( int x, int y, const wxString& text )
105 {
106 std::unique_ptr<SCH_TEXT> txt = std::make_unique<SCH_TEXT>( VECTOR2I( schIUScale.MilsToIU( x ),
107 schIUScale.MilsToIU( y ) ),
109 txt->SetTextSize( VECTOR2I( schIUScale.MilsToIU( 50 ), schIUScale.MilsToIU( 50 ) ) );
110 m_symbol->AddDrawItem( txt.release() );
111 }
112
113 wxString PlotToSvgString( int aUnit = 0, int aBodyStyle = 0 )
114 {
115 // Route through the production export so the tests cover the real viewport/viewBox
116 // handling rather than a reimplementation of the plot setup.
117 SCH_RENDER_SETTINGS renderSettings;
118 COLOR_SETTINGS colorSettings;
119 renderSettings.LoadColors( &colorSettings );
120 renderSettings.SetDefaultPenWidth( schIUScale.MilsToIU( 6 ) );
121
122 const BOX2I bbox = GetSymbolPlotBBox( *m_symbol, aUnit, aBodyStyle, false );
123
124 wxFileName tempFile( wxFileName::CreateTempFileName( wxT( "kicad_test_svg" ) ) );
125
126 if( !PlotSymbolToSVG( *m_symbol, *m_symbol, aUnit, aBodyStyle, bbox, renderSettings, false,
127 tempFile.GetFullPath() ) )
128 {
129 wxRemoveFile( tempFile.GetFullPath() );
130 return wxEmptyString;
131 }
132
133 wxFFile file( tempFile.GetFullPath(), wxT( "rb" ) );
134 wxString content;
135
136 if( file.IsOpened() )
137 file.ReadAll( &content );
138
139 wxRemoveFile( tempFile.GetFullPath() );
140 return content;
141 }
142
143 std::unique_ptr<LIB_SYMBOL> m_symbol;
144};
145
146
147BOOST_FIXTURE_TEST_SUITE( SymbolSvgExport, SYMBOL_SVG_EXPORT_FIXTURE )
148
149
150
153BOOST_AUTO_TEST_CASE( SvgExport_ContainsPins )
154{
155 AddPin( 0, 0, wxT( "VCC" ), wxT( "1" ) );
156 AddPin( 0, 100, wxT( "GND" ), wxT( "2" ) );
157 AddPin( 0, 200, wxT( "OUT" ), wxT( "3" ) );
158
159 wxString svg = PlotToSvgString();
160
161 BOOST_CHECK( !svg.IsEmpty() );
162 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
163 BOOST_CHECK( svg.Contains( wxT( "</svg>" ) ) );
164 // SVG should contain paths or lines for pin elements
165 BOOST_CHECK( svg.Contains( wxT( "<path" ) ) || svg.Contains( wxT( "<line" ) ) );
166}
167
168
172BOOST_AUTO_TEST_CASE( SvgExport_ContainsRectangle )
173{
174 AddRectangle( -100, -100, 100, 100 );
175
176 wxString svg = PlotToSvgString();
177
178 BOOST_CHECK( !svg.IsEmpty() );
179 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
180 // Rectangle should produce a path or rect element
181 BOOST_CHECK( svg.Contains( wxT( "<path" ) ) || svg.Contains( wxT( "<rect" ) ) );
182}
183
184
188BOOST_AUTO_TEST_CASE( SvgExport_ContainsCircle )
189{
190 AddCircle( 0, 0, 50 );
191
192 wxString svg = PlotToSvgString();
193
194 BOOST_CHECK( !svg.IsEmpty() );
195 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
196 // Circle should produce a circle or ellipse element
197 BOOST_CHECK( svg.Contains( wxT( "<circle" ) ) || svg.Contains( wxT( "<ellipse" ) )
198 || svg.Contains( wxT( "<path" ) ) );
199}
200
201
205BOOST_AUTO_TEST_CASE( SvgExport_ContainsPolyline )
206{
207 AddPolyline( { { 0, 0 }, { 50, 50 }, { 100, 0 }, { 100, 100 } } );
208
209 wxString svg = PlotToSvgString();
210
211 BOOST_CHECK( !svg.IsEmpty() );
212 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
213 // Polyline should produce a path or polyline element
214 BOOST_CHECK( svg.Contains( wxT( "<path" ) ) || svg.Contains( wxT( "<polyline" ) ) );
215}
216
217
221BOOST_AUTO_TEST_CASE( SvgExport_ContainsText )
222{
223 AddText( 0, 0, wxT( "Test Label" ) );
224
225 wxString svg = PlotToSvgString();
226
227 BOOST_CHECK( !svg.IsEmpty() );
228 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
229 // Text should produce a text element or paths for glyphs
230 BOOST_CHECK( svg.Contains( wxT( "<text" ) ) || svg.Contains( wxT( "<path" ) ) );
231}
232
233
237BOOST_AUTO_TEST_CASE( SvgExport_ComplexSymbol )
238{
239 // Create a simple IC-like symbol
240 AddRectangle( -100, -150, 100, 150 );
241 AddPin( -200, -100, wxT( "A" ), wxT( "1" ) );
242 AddPin( -200, 0, wxT( "B" ), wxT( "2" ) );
243 AddPin( -200, 100, wxT( "C" ), wxT( "3" ) );
244 AddPin( 200, -100, wxT( "Y" ), wxT( "4" ) );
245 AddPin( 200, 0, wxT( "Z" ), wxT( "5" ) );
246 AddPin( 200, 100, wxT( "W" ), wxT( "6" ) );
247 AddText( 0, 0, wxT( "IC" ) );
248
249 wxString svg = PlotToSvgString();
250
251 BOOST_CHECK( !svg.IsEmpty() );
252 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
253 BOOST_CHECK( svg.Contains( wxT( "</svg>" ) ) );
254
255 // Should have multiple path elements for all the components
256 int pathCount = 0;
257 size_t pos = 0;
258
259 while( ( pos = svg.find( wxT( "<path" ), pos ) ) != wxString::npos )
260 {
261 pathCount++;
262 pos++;
263 }
264
265 // A complex symbol should have multiple paths
266 BOOST_CHECK( pathCount >= 1 );
267}
268
269
281BOOST_AUTO_TEST_CASE( SvgExport_ViewBoxOrigin )
282{
283 // A 100 x 100 mil rectangle centred on the origin (spanning -50..+50 mil).
284 AddRectangle( -50, -50, 50, 50 );
285
286 const wxString svg = PlotToSvgString();
287
288 tl::expected<wxXmlDocument, wxString> svgDoc = KI_TEST::LoadSvg( svg );
289 BOOST_REQUIRE( svgDoc.has_value() );
290
291 tl::expected<KI_TEST::SVG_VIEWBOX, wxString> viewBox = KI_TEST::ParseViewBox( *svgDoc->GetRoot() );
292
293 BOOST_REQUIRE( viewBox.has_value() );
294
295 // The viewbox should be about 100 x 100 mils in size, plus some padding for stroke width.
296 // The SVG export function doesn't promise a specific padding, so just make sure the viewBox
297 // is larger than the rectangle and is offset appropriately.
298
299 // The left and top edges of the viewport must be negative, not (0, 0).
300 BOOST_TEST( viewBox->m_X < EDA_UNIT_UTILS::Mils2mm( -50 ) );
301 BOOST_TEST( viewBox->m_Y < EDA_UNIT_UTILS::Mils2mm( -50 ) );
302
303 // The viewport must still have a positive size
304 BOOST_TEST( viewBox->m_Width > EDA_UNIT_UTILS::Mils2mm( 100 ) );
305 BOOST_TEST( viewBox->m_Height > EDA_UNIT_UTILS::Mils2mm( 100 ) );
306}
307
308
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
Color settings are a bit different than most of the settings objects in that there can be more than o...
void SetDefaultPenWidth(int aWidth)
void LoadColors(const COLOR_SETTINGS *aSettings) override
Simple container to manage line stroke parameters.
~SYMBOL_SVG_EXPORT_FIXTURE()=default
wxString PlotToSvgString(int aUnit=0, int aBodyStyle=0)
void AddPolyline(const std::vector< std::pair< int, int > > &points)
void AddText(int x, int y, const wxString &text)
void AddCircle(int cx, int cy, int radius)
void AddPin(int x, int y, const wxString &name, const wxString &number)
std::unique_ptr< LIB_SYMBOL > m_symbol
void AddRectangle(int x1, int y1, int x2, int y2)
void AddArc(int cx, int cy, int radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle)
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ LAYER_DEVICE
Definition layer_ids.h:488
KICOMMON_API int Mils2mm(double aVal)
Convert mils to mm.
Definition eda_units.cpp:78
tl::expected< wxXmlDocument, wxString > LoadSvg(const wxString &aSvg)
tl::expected< SVG_VIEWBOX, wxString > ParseViewBox(const wxXmlNode &aRoot)
Parse the four numbers of the SVG viewBox attribute (min-x, min-y, width, height).
bool PlotSymbolToSVG(LIB_SYMBOL &aDrawSymbol, LIB_SYMBOL &aFieldsSymbol, int aUnit, int aBodyStyle, const BOX2I &aBBox, SCH_RENDER_SETTINGS &aRenderSettings, bool aBlackAndWhite, const wxString &aFileName, REPORTER *aReporter)
Plot a single symbol variant (unit and body style) to an SVG file.
BOX2I GetSymbolPlotBBox(const LIB_SYMBOL &aSymbol, int aUnit, int aBodyStyle, bool aIncludeHiddenFields)
Compute the bounding box used to size a symbol SVG plot.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
KIBIS_PIN * pin
int radius
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
BOOST_AUTO_TEST_CASE(SvgExport_ContainsPins)
Test that a symbol with pins produces SVG output.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683