You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

607 lines
19 KiB

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