1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <gerbview_painter.h>
22 #include <gal/graphics_abstraction_layer.h>
23 #include <settings/color_settings.h>
24 #include <convert_basic_shapes_to_polygon.h>
25 #include <convert_to_biu.h>
26 #include <gerbview.h>
27 #include <trigo.h>
28 
29 #include <dcode.h>
30 #include <gerber_draw_item.h>
31 #include <gerber_file_image.h>
32 
33 using namespace KIGFX;
34 
GERBVIEW_RENDER_SETTINGS()35 GERBVIEW_RENDER_SETTINGS::GERBVIEW_RENDER_SETTINGS()
36 {
37     m_backgroundColor = COLOR4D::BLACK;
38 
39     m_spotFill          = true;
40     m_lineFill          = true;
41     m_polygonFill       = true;
42     m_showNegativeItems = false;
43     m_showCodes         = false;
44     m_diffMode          = true;
45 
46     m_componentHighlightString = "";
47     m_netHighlightString       = "";
48     m_attributeHighlightString = "";
49     m_dcodeHighlightValue      = -1;
50 
51     update();
52 }
53 
54 
LoadColors(const COLOR_SETTINGS * aSettings)55 void GERBVIEW_RENDER_SETTINGS::LoadColors( const COLOR_SETTINGS* aSettings )
56 {
57     size_t palette_size = aSettings->m_Palette.size();
58     size_t palette_idx = 0;
59 
60     // Layers to draw gerber data read from gerber files:
61     for( int i = GERBVIEW_LAYER_ID_START;
62          i < GERBVIEW_LAYER_ID_START + GERBER_DRAWLAYERS_COUNT; i++ )
63     {
64         COLOR4D baseColor = aSettings->GetColor( i );
65 
66         if( baseColor == COLOR4D::UNSPECIFIED )
67             baseColor = aSettings->m_Palette[ ( palette_idx++ ) % palette_size ];
68 
69         m_layerColors[i] = baseColor;
70         m_layerColorsHi[i] = baseColor.Brightened( 0.5 );
71         m_layerColorsSel[i] = baseColor.Brightened( 0.8 );
72         m_layerColorsDark[i] = baseColor.Darkened( 0.25 );
73     }
74 
75     // Draw layers specific to Gerbview:
76     // LAYER_DCODES, LAYER_NEGATIVE_OBJECTS, LAYER_GERBVIEW_GRID,
77     // LAYER_GERBVIEW_AXES, LAYER_GERBVIEW_BACKGROUND, LAYER_GERBVIEW_DRAWINGSHEET,
78     for( int i = LAYER_DCODES; i < GERBVIEW_LAYER_ID_END; i++ )
79         m_layerColors[i] = aSettings->GetColor( i );
80 
81     for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ )
82         m_layerColors[i] = aSettings->GetColor( i );
83 
84     update();
85 }
86 
87 
LoadDisplayOptions(const GBR_DISPLAY_OPTIONS & aOptions)88 void GERBVIEW_RENDER_SETTINGS::LoadDisplayOptions( const GBR_DISPLAY_OPTIONS& aOptions )
89 {
90     m_spotFill          = aOptions.m_DisplayFlashedItemsFill;
91     m_lineFill          = aOptions.m_DisplayLinesFill;
92     m_polygonFill       = aOptions.m_DisplayPolygonsFill;
93     m_showNegativeItems = aOptions.m_DisplayNegativeObjects;
94     m_showCodes         = aOptions.m_DisplayDCodes;
95     m_diffMode          = aOptions.m_DiffMode;
96     m_hiContrastEnabled = aOptions.m_HighContrastMode;
97     m_showPageLimits    = aOptions.m_DisplayPageLimits;
98     m_backgroundColor   = aOptions.m_BgDrawColor;
99 
100     update();
101 }
102 
103 
ClearHighlightSelections()104 void GERBVIEW_RENDER_SETTINGS::ClearHighlightSelections()
105 {
106     // Clear all highlight selections (dcode, net, component, attribute selection)
107     m_componentHighlightString.Empty();
108     m_netHighlightString.Empty();
109     m_attributeHighlightString.Empty();
110     m_dcodeHighlightValue = -1;
111 }
112 
GetColor(const VIEW_ITEM * aItem,int aLayer) const113 COLOR4D GERBVIEW_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
114 {
115     const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
116     static const COLOR4D transparent = COLOR4D( 0, 0, 0, 0 );
117     const GERBER_DRAW_ITEM* gbrItem = nullptr;
118 
119     if( item && item->Type() == GERBER_DRAW_ITEM_T )
120         gbrItem = static_cast<const GERBER_DRAW_ITEM*>( item );
121 
122     // All DCODE layers stored under a single color setting
123     if( IsDCodeLayer( aLayer ) )
124         return m_layerColors[ LAYER_DCODES ];
125 
126     if( item && item->IsSelected() )
127         return m_layerColorsSel[aLayer];
128 
129     if( gbrItem && gbrItem->GetLayerPolarity() )
130     {
131         if( m_showNegativeItems )
132             return m_layerColors[LAYER_NEGATIVE_OBJECTS];
133         else
134             return transparent;
135     }
136 
137     if( !m_netHighlightString.IsEmpty() && gbrItem &&
138         m_netHighlightString == gbrItem->GetNetAttributes().m_Netname )
139         return m_layerColorsHi[aLayer];
140 
141     if( !m_componentHighlightString.IsEmpty() && gbrItem &&
142         m_componentHighlightString == gbrItem->GetNetAttributes().m_Cmpref )
143         return m_layerColorsHi[aLayer];
144 
145     if( !m_attributeHighlightString.IsEmpty() && gbrItem && gbrItem->GetDcodeDescr() &&
146         m_attributeHighlightString == gbrItem->GetDcodeDescr()->m_AperFunction )
147         return m_layerColorsHi[aLayer];
148 
149     if( m_dcodeHighlightValue> 0 && gbrItem && gbrItem->GetDcodeDescr() &&
150         m_dcodeHighlightValue == gbrItem->GetDcodeDescr()->m_Num_Dcode )
151         return m_layerColorsHi[aLayer];
152 
153     // Return grayish color for non-highlighted layers in the high contrast mode
154     if( m_hiContrastEnabled && m_highContrastLayers.count( aLayer ) == 0)
155         return m_hiContrastColor[aLayer];
156 
157     // Catch the case when highlight and high-contraste modes are enabled
158     // and we are drawing a not highlighted track
159     if( m_highlightEnabled )
160         return m_layerColorsDark[aLayer];
161 
162     // No special modificators enabled
163     return m_layerColors[aLayer];
164 }
165 
166 
GERBVIEW_PAINTER(GAL * aGal)167 GERBVIEW_PAINTER::GERBVIEW_PAINTER( GAL* aGal ) :
168     PAINTER( aGal )
169 {
170 }
171 
172 
173 // TODO(JE): Pull up to PAINTER?
getLineThickness(int aActualThickness) const174 int GERBVIEW_PAINTER::getLineThickness( int aActualThickness ) const
175 {
176     // if items have 0 thickness, draw them with the outline
177     // width, otherwise respect the set value (which, no matter
178     // how small will produce something)
179     if( aActualThickness == 0 )
180         return m_gerbviewSettings.m_outlineWidth;
181 
182     return aActualThickness;
183 }
184 
185 
Draw(const VIEW_ITEM * aItem,int aLayer)186 bool GERBVIEW_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
187 {
188     const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
189 
190     if( !item )
191         return false;
192 
193     // the "cast" applied in here clarifies which overloaded draw() is called
194     switch( item->Type() )
195     {
196     case GERBER_DRAW_ITEM_T:
197         draw( static_cast<GERBER_DRAW_ITEM*>( const_cast<EDA_ITEM*>( item ) ), aLayer );
198         break;
199 
200     default:
201         // Painter does not know how to draw the object
202         return false;
203     }
204 
205     return true;
206 }
207 
208 
209 // TODO(JE) aItem can't be const because of GetDcodeDescr()
210 // Probably that can be refactored in GERBER_DRAW_ITEM to allow const here.
draw(GERBER_DRAW_ITEM * aItem,int aLayer)211 void GERBVIEW_PAINTER::draw( /*const*/ GERBER_DRAW_ITEM* aItem, int aLayer )
212 {
213     VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) );   // TODO(JE) Getter
214     VECTOR2D end( aItem->GetABPosition( aItem->m_End ) );       // TODO(JE) Getter
215     int      width = aItem->m_Size.x;   // TODO(JE) Getter
216     bool     isFilled = true;
217     COLOR4D  color;
218     // TODO(JE) This doesn't actually work properly for ImageNegative
219     bool     isNegative = ( aItem->GetLayerPolarity() ^ aItem->m_GerberImageFile->m_ImageNegative );
220 
221     // Draw DCODE overlay text
222     if( IsDCodeLayer( aLayer ) )
223     {
224         wxString codeText;
225         VECTOR2D textPosition;
226         double textSize;
227         double orient;
228 
229         if( !aItem->GetTextD_CodePrms( textSize, textPosition, orient ) )
230             return;
231 
232         color = m_gerbviewSettings.GetColor( aItem, aLayer );
233         codeText.Printf( "D%d", aItem->m_DCode );
234 
235         m_gal->SetIsStroke( true );
236         m_gal->SetIsFill( false );
237         m_gal->SetStrokeColor( color );
238         m_gal->SetFillColor( COLOR4D( 0, 0, 0, 0 ) );
239         m_gal->SetLineWidth( textSize/10 );
240         m_gal->SetFontBold( false );
241         m_gal->SetFontItalic( false );
242         m_gal->SetFontUnderlined( false );
243         m_gal->SetTextMirrored( false );
244         m_gal->SetGlyphSize( VECTOR2D( textSize, textSize) );
245         m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
246         m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
247         m_gal->BitmapText( codeText, textPosition, orient );
248 
249         return;
250     }
251 
252     color = m_gerbviewSettings.GetColor( aItem, aLayer );
253 
254     // TODO: Should brightened color be a preference?
255     if( aItem->IsBrightened() )
256         color = COLOR4D( 0.0, 1.0, 0.0, 0.75 );
257 
258     m_gal->SetNegativeDrawMode( isNegative && ! m_gerbviewSettings.IsShowNegativeItems() );
259     m_gal->SetStrokeColor( color );
260     m_gal->SetFillColor( color );
261     m_gal->SetIsFill( isFilled );
262     m_gal->SetIsStroke( !isFilled );
263 
264     switch( aItem->m_Shape )
265     {
266     case GBR_POLYGON:
267     {
268         isFilled = m_gerbviewSettings.m_polygonFill;
269         m_gal->SetIsFill( isFilled );
270         m_gal->SetIsStroke( !isFilled );
271 
272         if( isNegative && !isFilled )
273         {
274             m_gal->SetNegativeDrawMode( false );
275             m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) );
276         }
277 
278         if( !isFilled )
279             m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
280 
281         if( aItem->m_AbsolutePolygon.OutlineCount() == 0 )
282         {
283             std::vector<VECTOR2I> pts = aItem->m_Polygon.COutline( 0 ).CPoints();
284 
285             for( auto& pt : pts )
286                 pt = aItem->GetABPosition( pt );
287 
288             SHAPE_LINE_CHAIN chain( pts );
289             chain.SetClosed( true );
290             aItem->m_AbsolutePolygon.AddOutline( chain );
291         }
292 
293         // Degenerated polygons (having < 3 points) are drawn as lines
294         // to avoid issues in draw polygon functions
295         if( !isFilled || aItem->m_AbsolutePolygon.COutline( 0 ).PointCount() < 3 )
296             m_gal->DrawPolyline( aItem->m_AbsolutePolygon.COutline( 0 ) );
297         else
298         {
299             // On Opengl, a not convex filled polygon is usually drawn by using triangles as primitives.
300             // CacheTriangulation() can create basic triangle primitives to draw the polygon solid shape
301             // on Opengl
302             if( m_gal->IsOpenGlEngine() && !aItem->m_AbsolutePolygon.IsTriangulationUpToDate() )
303                 aItem->m_AbsolutePolygon.CacheTriangulation();
304 
305             m_gal->DrawPolygon( aItem->m_AbsolutePolygon );
306         }
307 
308         break;
309     }
310 
311     case GBR_CIRCLE:
312     {
313         isFilled = m_gerbviewSettings.m_lineFill;
314         double radius = GetLineLength( aItem->m_Start, aItem->m_End );
315         m_gal->DrawCircle( start, radius );
316         break;
317     }
318 
319     case GBR_ARC:
320     {
321         isFilled = m_gerbviewSettings.m_lineFill;
322 
323         // These are swapped because wxDC fills arcs counterclockwise and GAL
324         // fills them clockwise.
325         wxPoint arcStart = aItem->m_End;
326         wxPoint arcEnd = aItem->m_Start;
327 
328         // Gerber arcs are 3-point (start, center, end)
329         // GAL needs center, radius, start angle, end angle
330         double   radius = GetLineLength( arcStart, aItem->m_ArcCentre );
331         VECTOR2D center = aItem->GetABPosition( aItem->m_ArcCentre );
332         VECTOR2D startVec = VECTOR2D( aItem->GetABPosition( arcStart ) ) - center;
333         VECTOR2D endVec = VECTOR2D( aItem->GetABPosition( arcEnd ) ) - center;
334 
335         m_gal->SetIsFill( isFilled );
336         m_gal->SetIsStroke( !isFilled );
337         m_gal->SetLineWidth( isFilled ? width : m_gerbviewSettings.m_outlineWidth );
338 
339         double startAngle = startVec.Angle();
340         double endAngle = endVec.Angle();
341 
342         // GAL fills in direction of increasing angle, so we have to convert
343         // the angle from the -PI to PI domain of atan2() to ensure that
344         // the arc goes in the right direction
345         if( startAngle > endAngle )
346             endAngle += (2 * M_PI);
347 
348         // In Gerber, 360-degree arcs are stored in the file with start equal to end
349         if( arcStart == arcEnd )
350         {
351             endAngle =  startAngle + 2*M_PI;
352         }
353 
354         m_gal->DrawArcSegment( center, radius, startAngle, endAngle, width, ARC_HIGH_DEF );
355 
356 #if 0   // Arc Debugging only
357         m_gal->SetIsFill( false );
358         m_gal->SetIsStroke( true );
359         m_gal->SetLineWidth( 5 );
360         m_gal->SetStrokeColor( COLOR4D( 0.1, 0.5, 0.0, 0.5 ) );
361         m_gal->DrawLine( center, aItem->GetABPosition( arcStart ) );
362         m_gal->SetStrokeColor( COLOR4D( 0.6, 0.1, 0.0, 0.5 ) );
363         m_gal->DrawLine( center, aItem->GetABPosition( arcEnd ) );
364 #endif
365 
366 #if 0   // Bbox arc Debugging only
367         m_gal->SetIsFill( false );
368         m_gal->SetIsStroke( true );
369         EDA_RECT box = aItem->GetBoundingBox();
370         m_gal->SetLineWidth( 5 );
371         m_gal->SetStrokeColor( COLOR4D(0.9, 0.9, 0, 0.4) );
372         // box coordinates are already in AB position.
373         m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );
374 #endif
375         break;
376     }
377 
378     case GBR_SPOT_CIRCLE:
379     case GBR_SPOT_RECT:
380     case GBR_SPOT_OVAL:
381     case GBR_SPOT_POLY:
382     case GBR_SPOT_MACRO:
383     {
384         isFilled = m_gerbviewSettings.m_spotFill;
385         drawFlashedShape( aItem, isFilled );
386         break;
387     }
388 
389     case GBR_SEGMENT:
390     {
391         /* Plot a line from m_Start to m_End.
392          * Usually, a round pen is used, but some gerber files use a rectangular pen
393          * In fact, any aperture can be used to plot a line.
394          * currently: only a square pen is handled (I believe using a polygon gives a strange plot).
395          */
396         isFilled = m_gerbviewSettings.m_lineFill;
397         m_gal->SetIsFill( isFilled );
398         m_gal->SetIsStroke( !isFilled );
399 
400         if( isNegative && !isFilled )
401             m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) );
402 
403         // TODO(JE) Refactor this to allow const aItem
404         D_CODE* code = aItem->GetDcodeDescr();
405         if( code && code->m_Shape == APT_RECT )
406         {
407             if( aItem->m_Polygon.OutlineCount() == 0 )
408                 aItem->ConvertSegmentToPolygon();
409 
410             drawPolygon( aItem, aItem->m_Polygon, isFilled );
411         }
412         else
413         {
414             if( !isFilled )
415                 m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
416 
417             m_gal->DrawSegment( start, end, width );
418         }
419         break;
420     }
421 
422     default:
423         wxASSERT_MSG( false, "GERBER_DRAW_ITEM shape is unknown!" );
424         break;
425     }
426     m_gal->SetNegativeDrawMode( false );
427 
428     // Enable for bounding box debugging
429     #if 0
430     const BOX2I& bb = aItem->ViewBBox();
431     m_gal->SetIsStroke( true );
432     m_gal->SetIsFill( true );
433     m_gal->SetLineWidth( 3 );
434     m_gal->SetStrokeColor( COLOR4D(0.9, 0.9, 0, 0.4) );
435     m_gal->SetFillColor( COLOR4D(0.9, 0.9, 0, 0.1) );
436     m_gal->DrawRectangle( bb.GetOrigin(), bb.GetEnd() );
437     #endif
438 }
439 
440 
drawPolygon(GERBER_DRAW_ITEM * aParent,const SHAPE_POLY_SET & aPolygon,bool aFilled,bool aShift)441 void GERBVIEW_PAINTER::drawPolygon(
442         GERBER_DRAW_ITEM* aParent, const SHAPE_POLY_SET& aPolygon, bool aFilled, bool aShift )
443 {
444     wxASSERT( aPolygon.OutlineCount() == 1 );
445 
446     if( aPolygon.OutlineCount() == 0 )
447         return;
448 
449     SHAPE_POLY_SET poly;
450     poly.NewOutline();
451     const std::vector<VECTOR2I> pts = aPolygon.COutline( 0 ).CPoints();
452     VECTOR2I       offset = aShift ? VECTOR2I( aParent->m_Start ) : VECTOR2I( 0, 0 );
453 
454     for( auto& pt : pts )
455         poly.Append( aParent->GetABPosition( pt + offset ) );
456 
457     if( !m_gerbviewSettings.m_polygonFill )
458         m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
459 
460     if( !aFilled )
461     {
462         m_gal->DrawPolyline( poly.COutline( 0 ) );
463     }
464     else
465         m_gal->DrawPolygon( poly );
466 }
467 
468 
drawFlashedShape(GERBER_DRAW_ITEM * aItem,bool aFilled)469 void GERBVIEW_PAINTER::drawFlashedShape( GERBER_DRAW_ITEM* aItem, bool aFilled )
470 {
471     D_CODE* code = aItem->GetDcodeDescr();
472 
473     wxASSERT_MSG( code, "drawFlashedShape: Item has no D_CODE!" );
474 
475     if( !code )
476         return;
477 
478     m_gal->SetIsFill( aFilled );
479     m_gal->SetIsStroke( !aFilled );
480     m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
481 
482     switch( aItem->m_Shape )
483     {
484     case GBR_SPOT_CIRCLE:
485     {
486         int radius = code->m_Size.x >> 1;
487         VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) );
488 
489         if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
490         {
491             m_gal->DrawCircle( start, radius );
492         }
493         else    // rectangular hole
494         {
495             if( code->m_Polygon.OutlineCount() == 0 )
496                 code->ConvertShapeToPolygon();
497 
498             drawPolygon( aItem, code->m_Polygon, aFilled, true );
499         }
500 
501         break;
502     }
503 
504     case GBR_SPOT_RECT:
505     {
506         wxPoint codeStart;
507         wxPoint aShapePos = aItem->m_Start;
508         codeStart.x = aShapePos.x - code->m_Size.x / 2;
509         codeStart.y = aShapePos.y - code->m_Size.y / 2;
510         wxPoint codeEnd = codeStart + code->m_Size;
511         codeStart = aItem->GetABPosition( codeStart );
512         codeEnd = aItem->GetABPosition( codeEnd );
513 
514         if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE  )
515         {
516             m_gal->DrawRectangle( VECTOR2D( codeStart ), VECTOR2D( codeEnd ) );
517         }
518         else
519         {
520             if( code->m_Polygon.OutlineCount() == 0 )
521                 code->ConvertShapeToPolygon();
522 
523             drawPolygon( aItem, code->m_Polygon, aFilled, true );
524         }
525         break;
526     }
527 
528     case GBR_SPOT_OVAL:
529     {
530         int radius = 0;
531 
532         wxPoint codeStart = aItem->m_Start;
533         wxPoint codeEnd = aItem->m_Start;
534 
535         if( code->m_Size.x > code->m_Size.y )   // horizontal oval
536         {
537             int delta = (code->m_Size.x - code->m_Size.y) / 2;
538             codeStart.x -= delta;
539             codeEnd.x   += delta;
540             radius   = code->m_Size.y;
541         }
542         else   // horizontal oval
543         {
544             int delta = (code->m_Size.y - code->m_Size.x) / 2;
545             codeStart.y -= delta;
546             codeEnd.y   += delta;
547             radius   = code->m_Size.x;
548         }
549 
550         codeStart = aItem->GetABPosition( codeStart );
551         codeEnd = aItem->GetABPosition( codeEnd );
552 
553         if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
554         {
555             m_gal->DrawSegment( codeStart, codeEnd, radius );
556         }
557         else
558         {
559             if( code->m_Polygon.OutlineCount() == 0 )
560                 code->ConvertShapeToPolygon();
561 
562             drawPolygon( aItem, code->m_Polygon, aFilled, true );
563         }
564         break;
565     }
566 
567     case GBR_SPOT_POLY:
568     {
569         if( code->m_Polygon.OutlineCount() == 0 )
570             code->ConvertShapeToPolygon();
571 
572         drawPolygon( aItem, code->m_Polygon, aFilled, true );
573         break;
574     }
575 
576     case GBR_SPOT_MACRO:
577         drawApertureMacro( aItem, aFilled );
578         break;
579 
580     default:
581         wxASSERT_MSG( false, wxT( "Unknown Gerber flashed shape!" ) );
582         break;
583     }
584 }
585 
586 
drawApertureMacro(GERBER_DRAW_ITEM * aParent,bool aFilled)587 void GERBVIEW_PAINTER::drawApertureMacro( GERBER_DRAW_ITEM* aParent, bool aFilled )
588 {
589     D_CODE* code = aParent->GetDcodeDescr();
590     APERTURE_MACRO* macro = code->GetMacro();
591 
592     SHAPE_POLY_SET* macroShape = macro->GetApertureMacroShape( aParent, aParent->m_Start );
593 
594     if( !m_gerbviewSettings.m_polygonFill )
595         m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
596 
597     if( !aFilled )
598     {
599         for( int i = 0; i < macroShape->OutlineCount(); i++ )
600             m_gal->DrawPolyline( macroShape->COutline( i ) );
601     }
602     else
603         m_gal->DrawPolygon( *macroShape );
604 }
605 
606 
607 const double GERBVIEW_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 );
608