KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_symbol_clipboard_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 along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
29
30#include <boost/test/unit_test.hpp>
31
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>
38#include <sch_painter.h>
39#include <sch_plotter.h>
40#include <locale_io.h>
42#include <wx/ffile.h>
43#include <wx/mstream.h>
44
45
47{
48public:
50 {
51 m_symbol = std::make_unique<LIB_SYMBOL>( wxT( "TestSymbol" ), nullptr );
52 }
53
55
56 void AddPin( int x, int y, const wxString& name, const wxString& number )
57 {
58 std::unique_ptr<SCH_PIN> pin = std::make_unique<SCH_PIN>( m_symbol.get() );
59 pin->SetPosition( VECTOR2I( schIUScale.MilsToIU( x ), schIUScale.MilsToIU( y ) ) );
60 pin->SetName( name );
61 pin->SetNumber( number );
62 pin->SetLength( schIUScale.MilsToIU( 100 ) );
63 m_symbol->AddDrawItem( pin.release() );
64 }
65
66 void AddRectangle( int x1, int y1, int x2, int y2 )
67 {
68 std::unique_ptr<SCH_SHAPE> rect = std::make_unique<SCH_SHAPE>( SHAPE_T::RECTANGLE, LAYER_DEVICE );
69 rect->SetPosition( VECTOR2I( schIUScale.MilsToIU( x1 ), schIUScale.MilsToIU( y1 ) ) );
70 rect->SetEnd( VECTOR2I( schIUScale.MilsToIU( x2 ), schIUScale.MilsToIU( y2 ) ) );
71 rect->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
72 m_symbol->AddDrawItem( rect.release() );
73 }
74
75 void AddCircle( int cx, int cy, int radius )
76 {
77 std::unique_ptr<SCH_SHAPE> circle = std::make_unique<SCH_SHAPE>( SHAPE_T::CIRCLE, LAYER_DEVICE );
78 circle->SetPosition( VECTOR2I( schIUScale.MilsToIU( cx ), schIUScale.MilsToIU( cy ) ) );
79 circle->SetEnd( VECTOR2I( schIUScale.MilsToIU( cx + radius ), schIUScale.MilsToIU( cy ) ) );
80 circle->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
81 m_symbol->AddDrawItem( circle.release() );
82 }
83
84 void AddPolyline( const std::vector<std::pair<int, int>>& points )
85 {
86 std::unique_ptr<SCH_SHAPE> poly = std::make_unique<SCH_SHAPE>( SHAPE_T::POLY, LAYER_DEVICE );
87 poly->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
88
89 for( const auto& pt : points )
90 poly->AddPoint( VECTOR2I( schIUScale.MilsToIU( pt.first ), schIUScale.MilsToIU( pt.second ) ) );
91
92 m_symbol->AddDrawItem( poly.release() );
93 }
94
95 void AddArc( int cx, int cy, int radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle )
96 {
97 std::unique_ptr<SCH_SHAPE> arc = std::make_unique<SCH_SHAPE>( SHAPE_T::ARC, LAYER_DEVICE );
98 arc->SetCenter( VECTOR2I( schIUScale.MilsToIU( cx ), schIUScale.MilsToIU( cy ) ) );
99 arc->SetRadius( schIUScale.MilsToIU( radius ) );
100 arc->SetArcAngleAndEnd( endAngle - startAngle );
101 arc->SetStroke( STROKE_PARAMS( schIUScale.MilsToIU( 10 ), LINE_STYLE::SOLID ) );
102 m_symbol->AddDrawItem( arc.release() );
103 }
104
105 void AddText( int x, int y, const wxString& text )
106 {
107 std::unique_ptr<SCH_TEXT> txt = std::make_unique<SCH_TEXT>( VECTOR2I( schIUScale.MilsToIU( x ),
108 schIUScale.MilsToIU( y ) ),
110 txt->SetTextSize( VECTOR2I( schIUScale.MilsToIU( 50 ), schIUScale.MilsToIU( 50 ) ) );
111 m_symbol->AddDrawItem( txt.release() );
112 }
113
114 BOX2I GetSymbolBoundingBox( int aUnit = 0, int aBodyStyle = 0 )
115 {
116 BOX2I bbox;
117
118 for( SCH_ITEM& item : m_symbol->GetDrawItems() )
119 {
120 if( item.Type() == SCH_FIELD_T )
121 continue;
122
123 if( aUnit && item.GetUnit() && item.GetUnit() != aUnit )
124 continue;
125
126 if( aBodyStyle && item.GetBodyStyle() && item.GetBodyStyle() != aBodyStyle )
127 continue;
128
129 if( bbox.GetWidth() == 0 && bbox.GetHeight() == 0 )
130 bbox = item.GetBoundingBox();
131 else
132 bbox.Merge( item.GetBoundingBox() );
133 }
134
135 return bbox;
136 }
137
138 wxString PlotToSvgString( int aUnit = 0, int aBodyStyle = 0 )
139 {
140 BOX2I bbox = GetSymbolBoundingBox( aUnit, aBodyStyle );
141
142 if( bbox.GetWidth() <= 0 || bbox.GetHeight() <= 0 )
143 return wxEmptyString;
144
145 bbox.Inflate( bbox.GetWidth() * 0.02, bbox.GetHeight() * 0.02 );
146
147 SCH_RENDER_SETTINGS renderSettings;
148 COLOR_SETTINGS colorSettings;
149 renderSettings.LoadColors( &colorSettings );
150 renderSettings.SetDefaultPenWidth( schIUScale.MilsToIU( 6 ) );
151
152 std::unique_ptr<SVG_PLOTTER> plotter = std::make_unique<SVG_PLOTTER>();
153 plotter->SetRenderSettings( &renderSettings );
154
155 PAGE_INFO pageInfo;
156 pageInfo.SetWidthMils( schIUScale.IUToMils( bbox.GetWidth() ) );
157 pageInfo.SetHeightMils( schIUScale.IUToMils( bbox.GetHeight() ) );
158
159 plotter->SetPageSettings( pageInfo );
160 plotter->SetColorMode( true );
161
162 VECTOR2I plot_offset = bbox.GetOrigin();
163 plotter->SetViewport( plot_offset, schIUScale.IU_PER_MILS / 10, 1.0, false );
164 plotter->SetCreator( wxT( "Eeschema-SVG-Test" ) );
165
166 wxFileName tempFile( wxFileName::CreateTempFileName( wxT( "kicad_test_svg" ) ) );
167
168 if( !plotter->OpenFile( tempFile.GetFullPath() ) )
169 {
170 wxRemoveFile( tempFile.GetFullPath() );
171 return wxEmptyString;
172 }
173
174 LOCALE_IO toggle;
175 SCH_PLOT_OPTS plotOpts;
176
177 plotter->StartPlot( wxT( "1" ) );
178
179 constexpr bool background = true;
180 m_symbol->Plot( plotter.get(), background, plotOpts, aUnit, aBodyStyle, VECTOR2I( 0, 0 ), false );
181 m_symbol->Plot( plotter.get(), !background, plotOpts, aUnit, aBodyStyle, VECTOR2I( 0, 0 ), false );
182 m_symbol->PlotFields( plotter.get(), !background, plotOpts, aUnit, aBodyStyle, VECTOR2I( 0, 0 ), false );
183
184 plotter->EndPlot();
185 plotter.reset();
186
187 wxFFile file( tempFile.GetFullPath(), wxT( "rb" ) );
188 wxString content;
189
190 if( file.IsOpened() )
191 file.ReadAll( &content );
192
193 wxRemoveFile( tempFile.GetFullPath() );
194 return content;
195 }
196
197 std::unique_ptr<LIB_SYMBOL> m_symbol;
198};
199
200
201BOOST_FIXTURE_TEST_SUITE( SymbolClipboardExport, SYMBOL_CLIPBOARD_FIXTURE )
202
203
204
207BOOST_AUTO_TEST_CASE( SvgExport_ContainsPins )
208{
209 AddPin( 0, 0, wxT( "VCC" ), wxT( "1" ) );
210 AddPin( 0, 100, wxT( "GND" ), wxT( "2" ) );
211 AddPin( 0, 200, wxT( "OUT" ), wxT( "3" ) );
212
213 wxString svg = PlotToSvgString();
214
215 BOOST_CHECK( !svg.IsEmpty() );
216 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
217 BOOST_CHECK( svg.Contains( wxT( "</svg>" ) ) );
218 // SVG should contain paths or lines for pin elements
219 BOOST_CHECK( svg.Contains( wxT( "<path" ) ) || svg.Contains( wxT( "<line" ) ) );
220}
221
222
226BOOST_AUTO_TEST_CASE( SvgExport_ContainsRectangle )
227{
228 AddRectangle( -100, -100, 100, 100 );
229
230 wxString svg = PlotToSvgString();
231
232 BOOST_CHECK( !svg.IsEmpty() );
233 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
234 // Rectangle should produce a path or rect element
235 BOOST_CHECK( svg.Contains( wxT( "<path" ) ) || svg.Contains( wxT( "<rect" ) ) );
236}
237
238
242BOOST_AUTO_TEST_CASE( SvgExport_ContainsCircle )
243{
244 AddCircle( 0, 0, 50 );
245
246 wxString svg = PlotToSvgString();
247
248 BOOST_CHECK( !svg.IsEmpty() );
249 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
250 // Circle should produce a circle or ellipse element
251 BOOST_CHECK( svg.Contains( wxT( "<circle" ) ) || svg.Contains( wxT( "<ellipse" ) )
252 || svg.Contains( wxT( "<path" ) ) );
253}
254
255
259BOOST_AUTO_TEST_CASE( SvgExport_ContainsPolyline )
260{
261 AddPolyline( { { 0, 0 }, { 50, 50 }, { 100, 0 }, { 100, 100 } } );
262
263 wxString svg = PlotToSvgString();
264
265 BOOST_CHECK( !svg.IsEmpty() );
266 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
267 // Polyline should produce a path or polyline element
268 BOOST_CHECK( svg.Contains( wxT( "<path" ) ) || svg.Contains( wxT( "<polyline" ) ) );
269}
270
271
275BOOST_AUTO_TEST_CASE( SvgExport_ContainsText )
276{
277 AddText( 0, 0, wxT( "Test Label" ) );
278
279 wxString svg = PlotToSvgString();
280
281 BOOST_CHECK( !svg.IsEmpty() );
282 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
283 // Text should produce a text element or paths for glyphs
284 BOOST_CHECK( svg.Contains( wxT( "<text" ) ) || svg.Contains( wxT( "<path" ) ) );
285}
286
287
291BOOST_AUTO_TEST_CASE( BoundingBox_Pins )
292{
293 AddPin( 0, 0, wxT( "PIN1" ), wxT( "1" ) );
294 AddPin( 200, 0, wxT( "PIN2" ), wxT( "2" ) );
295
296 BOX2I bbox = GetSymbolBoundingBox();
297
298 // Bounding box should span from first pin to second pin (plus pin length and decoration)
299 BOOST_CHECK( bbox.GetWidth() > 0 );
300 BOOST_CHECK( bbox.GetHeight() > 0 );
301}
302
303
307BOOST_AUTO_TEST_CASE( BoundingBox_Rectangle )
308{
309 AddRectangle( -50, -50, 50, 50 );
310
311 BOX2I bbox = GetSymbolBoundingBox();
312
313 // Bounding box should approximately match the rectangle size
314 int expectedWidth = schIUScale.MilsToIU( 100 ); // 50 - (-50) = 100 mils
315 int expectedHeight = schIUScale.MilsToIU( 100 );
316
317 BOOST_CHECK( std::abs( bbox.GetWidth() - expectedWidth ) < schIUScale.MilsToIU( 20 ) );
318 BOOST_CHECK( std::abs( bbox.GetHeight() - expectedHeight ) < schIUScale.MilsToIU( 20 ) );
319}
320
321
325BOOST_AUTO_TEST_CASE( BoundingBox_Circle )
326{
327 int radius = 100;
328 AddCircle( 0, 0, radius );
329
330 BOX2I bbox = GetSymbolBoundingBox();
331
332 // Bounding box should be approximately 2*radius in each dimension
333 int expectedSize = schIUScale.MilsToIU( 2 * radius );
334
335 BOOST_CHECK( std::abs( bbox.GetWidth() - expectedSize ) < schIUScale.MilsToIU( 20 ) );
336 BOOST_CHECK( std::abs( bbox.GetHeight() - expectedSize ) < schIUScale.MilsToIU( 20 ) );
337}
338
339
343BOOST_AUTO_TEST_CASE( SvgExport_ComplexSymbol )
344{
345 // Create a simple IC-like symbol
346 AddRectangle( -100, -150, 100, 150 );
347 AddPin( -200, -100, wxT( "A" ), wxT( "1" ) );
348 AddPin( -200, 0, wxT( "B" ), wxT( "2" ) );
349 AddPin( -200, 100, wxT( "C" ), wxT( "3" ) );
350 AddPin( 200, -100, wxT( "Y" ), wxT( "4" ) );
351 AddPin( 200, 0, wxT( "Z" ), wxT( "5" ) );
352 AddPin( 200, 100, wxT( "W" ), wxT( "6" ) );
353 AddText( 0, 0, wxT( "IC" ) );
354
355 wxString svg = PlotToSvgString();
356
357 BOOST_CHECK( !svg.IsEmpty() );
358 BOOST_CHECK( svg.Contains( wxT( "<svg" ) ) );
359 BOOST_CHECK( svg.Contains( wxT( "</svg>" ) ) );
360
361 // Should have multiple path elements for all the components
362 int pathCount = 0;
363 size_t pos = 0;
364
365 while( ( pos = svg.find( wxT( "<path" ), pos ) ) != wxString::npos )
366 {
367 pathCount++;
368 pos++;
369 }
370
371 // A complex symbol should have multiple paths
372 BOOST_CHECK( pathCount >= 1 );
373}
374
375
381BOOST_AUTO_TEST_CASE( PngExport_AlphaComputation_OpaquePixel )
382{
383 // An opaque red pixel: on white = red, on black = red
384 int rW = 255, gW = 0, bW = 0; // red on white
385 int rB = 255, gB = 0, bB = 0; // red on black
386
387 int diffR = rW - rB;
388 int diffG = gW - gB;
389 int diffB = bW - bB;
390 int avgDiff = ( diffR + diffG + diffB ) / 3;
391
392 int alpha = 255 - avgDiff;
393
394 BOOST_CHECK_EQUAL( alpha, 255 ); // Fully opaque
395}
396
397
401BOOST_AUTO_TEST_CASE( PngExport_AlphaComputation_TransparentPixel )
402{
403 // A transparent pixel: on white = white, on black = black
404 int rW = 255, gW = 255, bW = 255; // white on white
405 int rB = 0, gB = 0, bB = 0; // black on black
406
407 int diffR = rW - rB;
408 int diffG = gW - gB;
409 int diffB = bW - bB;
410 int avgDiff = ( diffR + diffG + diffB ) / 3;
411
412 int alpha = 255 - avgDiff;
413
414 BOOST_CHECK_EQUAL( alpha, 0 ); // Fully transparent
415}
416
417
421BOOST_AUTO_TEST_CASE( PngExport_AlphaComputation_SemiTransparentPixel )
422{
423 // A 50% transparent red pixel
424 // On white: blends to (255, 128, 128) approximately
425 // On black: blends to (128, 0, 0) approximately
426
427 int rW = 255, gW = 128, bW = 128;
428 int rB = 128, gB = 0, bB = 0;
429
430 int diffR = rW - rB; // 127
431 int diffG = gW - gB; // 128
432 int diffB = bW - bB; // 128
433 int avgDiff = ( diffR + diffG + diffB ) / 3; // approximately 127-128
434
435 int alpha = 255 - avgDiff;
436
437 // Alpha should be around 127-128 (50%)
438 BOOST_CHECK( alpha > 120 && alpha < 140 );
439}
440
441
445BOOST_AUTO_TEST_CASE( SvgExport_EmptySymbol )
446{
447 // Don't add any items - the symbol only has default fields
448 // After removing fields from bounding box calculation, should be empty
449
450 BOX2I bbox = GetSymbolBoundingBox();
451
452 // An empty symbol (no non-field items) should have zero-size bounding box
453 BOOST_CHECK( bbox.GetWidth() == 0 || bbox.GetHeight() == 0 );
454}
455
456
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:114
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr const Vec & GetOrigin() const
Definition box2.h:210
Color settings are a bit different than most of the settings objects in that there can be more than o...
void SetDefaultPenWidth(int aWidth)
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:41
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:79
void SetHeightMils(double aHeightInMils)
void SetWidthMils(double aWidthInMils)
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:167
void LoadColors(const COLOR_SETTINGS *aSettings) override
Simple container to manage line stroke parameters.
void AddText(int x, int y, const wxString &text)
void AddArc(int cx, int cy, int radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle)
void AddPin(int x, int y, const wxString &name, const wxString &number)
void AddPolyline(const std::vector< std::pair< int, int > > &points)
~SYMBOL_CLIPBOARD_FIXTURE()=default
wxString PlotToSvgString(int aUnit=0, int aBodyStyle=0)
void AddRectangle(int x1, int y1, int x2, int y2)
std::unique_ptr< LIB_SYMBOL > m_symbol
void AddCircle(int cx, int cy, int radius)
BOX2I GetSymbolBoundingBox(int aUnit=0, int aBodyStyle=0)
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
@ LAYER_DEVICE
Definition layer_ids.h:466
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Plotting engines similar to ps (PostScript, Gerber, svg)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE_END()
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.
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_FIELD_T
Definition typeinfo.h:154
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695