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.

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