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.

319 lines
12 KiB

4 years ago
4 years ago
4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2021-2023 KiCad Developers.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU 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, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <macros.h>
  24. #include <pcb_field.h>
  25. #include <pcb_text.h>
  26. #include <pcb_textbox.h>
  27. #include <drc/drc_engine.h>
  28. #include <drc/drc_item.h>
  29. #include <drc/drc_rule.h>
  30. #include <drc/drc_test_provider.h>
  31. #include <font/font.h>
  32. /*
  33. Text dimensions tests.
  34. Errors generated:
  35. - DRCE_TEXT_HEIGHT
  36. - DRCE_TEXT_THICKNESS
  37. */
  38. class DRC_TEST_PROVIDER_TEXT_DIMS : public DRC_TEST_PROVIDER
  39. {
  40. public:
  41. DRC_TEST_PROVIDER_TEXT_DIMS()
  42. {
  43. }
  44. virtual ~DRC_TEST_PROVIDER_TEXT_DIMS()
  45. {
  46. }
  47. virtual bool Run() override;
  48. virtual const wxString GetName() const override
  49. {
  50. return wxT( "text_dimensions" );
  51. };
  52. virtual const wxString GetDescription() const override
  53. {
  54. return wxT( "Tests text height and thickness" );
  55. }
  56. };
  57. bool DRC_TEST_PROVIDER_TEXT_DIMS::Run()
  58. {
  59. const int progressDelta = 250;
  60. int count = 0;
  61. int ii = 0;
  62. if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_HEIGHT )
  63. && m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_THICKNESS ) )
  64. {
  65. reportAux( wxT( "Text dimension violations ignored. Tests not run." ) );
  66. return true; // continue with other tests
  67. }
  68. if( !m_drcEngine->HasRulesForConstraintType( TEXT_HEIGHT_CONSTRAINT )
  69. && !m_drcEngine->HasRulesForConstraintType( TEXT_THICKNESS_CONSTRAINT ) )
  70. {
  71. reportAux( wxT( "No text height or text thickness constraints found. Tests not run." ) );
  72. return true; // continue with other tests
  73. }
  74. if( !reportPhase( _( "Checking text dimensions..." ) ) )
  75. return false; // DRC cancelled
  76. auto checkTextHeight =
  77. [&]( BOARD_ITEM* item, EDA_TEXT* text ) -> bool
  78. {
  79. if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_HEIGHT ) )
  80. return false;
  81. DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( TEXT_HEIGHT_CONSTRAINT, item,
  82. nullptr, item->GetLayer() );
  83. if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE )
  84. return true;
  85. int actualHeight = text->GetTextSize().y;
  86. if( constraint.Value().HasMin() && actualHeight < constraint.Value().Min() )
  87. {
  88. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_TEXT_HEIGHT );
  89. wxString msg = formatMsg( _( "(%s min height %s; actual %s)" ),
  90. constraint.GetName(),
  91. constraint.Value().Min(),
  92. actualHeight );
  93. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  94. drcItem->SetItems( item );
  95. drcItem->SetViolatingRule( constraint.GetParentRule() );
  96. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  97. }
  98. if( constraint.Value().HasMax() && actualHeight > constraint.Value().Max() )
  99. {
  100. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_TEXT_HEIGHT );
  101. wxString msg = formatMsg( _( "(%s max height %s; actual %s)" ),
  102. constraint.GetName(),
  103. constraint.Value().Max(),
  104. actualHeight );
  105. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  106. drcItem->SetItems( item );
  107. drcItem->SetViolatingRule( constraint.GetParentRule() );
  108. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  109. }
  110. return true;
  111. };
  112. auto checkTextThickness =
  113. [&]( BOARD_ITEM* item, EDA_TEXT* text ) -> bool
  114. {
  115. DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( TEXT_THICKNESS_CONSTRAINT, item,
  116. nullptr, item->GetLayer() );
  117. if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE )
  118. return true;
  119. KIFONT::FONT* font = text->GetFont();
  120. if( !font )
  121. font = KIFONT::FONT::GetFont( wxEmptyString, text->IsBold(), text->IsItalic() );
  122. if( font->IsOutline() )
  123. {
  124. if( !constraint.Value().HasMin() )
  125. return true;
  126. auto* glyphs = text->GetRenderCache( font, text->GetShownText( true ) );
  127. bool collapsedStroke = false;
  128. bool collapsedArea = false;
  129. for( const std::unique_ptr<KIFONT::GLYPH>& glyph : *glyphs )
  130. {
  131. // Ensure the glyph is a OUTLINE_GLYPH
  132. // for texts with overbar, it can be a STROKE_GLYPH
  133. // TODO: perhaps test the overbar thickness.
  134. if( !glyph->IsOutline() )
  135. continue;
  136. auto outlineGlyph = static_cast<KIFONT::OUTLINE_GLYPH*>( glyph.get() );
  137. int outlineCount = outlineGlyph->OutlineCount();
  138. int holeCount = 0;
  139. if( outlineCount == 0 )
  140. continue; // ignore spaces
  141. for( ii = 0; ii < outlineCount; ++ii )
  142. holeCount += outlineGlyph->HoleCount( ii );
  143. SHAPE_POLY_SET poly = outlineGlyph->CloneDropTriangulation();
  144. poly.Deflate( constraint.Value().Min() / 2,
  145. CORNER_STRATEGY::CHAMFER_ALL_CORNERS, ARC_LOW_DEF );
  146. poly.Simplify( SHAPE_POLY_SET::PM_FAST );
  147. int resultingOutlineCount = poly.OutlineCount();
  148. int resultingHoleCount = 0;
  149. for( ii = 0; ii < resultingOutlineCount; ++ii )
  150. resultingHoleCount += poly.HoleCount( ii );
  151. if( ( resultingOutlineCount != outlineCount )
  152. || ( resultingHoleCount != holeCount ) )
  153. {
  154. collapsedStroke = true;
  155. break;
  156. }
  157. double glyphArea = outlineGlyph->Area();
  158. if( glyphArea == 0 )
  159. continue;
  160. poly.Inflate( constraint.Value().Min() / 2,
  161. CORNER_STRATEGY::CHAMFER_ALL_CORNERS, ARC_LOW_DEF, true );
  162. double resultingGlyphArea = poly.Area();
  163. if( ( std::abs( resultingGlyphArea - glyphArea ) / glyphArea ) > 0.1 )
  164. {
  165. collapsedArea = true;
  166. break;
  167. }
  168. }
  169. if( collapsedStroke || collapsedArea )
  170. {
  171. auto drcItem = DRC_ITEM::Create( DRCE_TEXT_THICKNESS );
  172. wxString msg;
  173. msg = _( "(TrueType font characters with insufficient stroke weight)" );
  174. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  175. drcItem->SetItems( item );
  176. drcItem->SetViolatingRule( constraint.GetParentRule() );
  177. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  178. }
  179. }
  180. else
  181. {
  182. int actualThickness = text->GetEffectiveTextPenWidth();
  183. if( constraint.Value().HasMin() && actualThickness < constraint.Value().Min() )
  184. {
  185. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_TEXT_THICKNESS );
  186. wxString msg = formatMsg( _( "(%s min thickness %s; actual %s)" ),
  187. constraint.GetName(),
  188. constraint.Value().Min(),
  189. actualThickness );
  190. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  191. drcItem->SetItems( item );
  192. drcItem->SetViolatingRule( constraint.GetParentRule() );
  193. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  194. }
  195. if( constraint.Value().HasMax() && actualThickness > constraint.Value().Max() )
  196. {
  197. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_TEXT_THICKNESS );
  198. wxString msg = formatMsg( _( "(%s max thickness %s; actual %s)" ),
  199. constraint.GetName(),
  200. constraint.Value().Max(),
  201. actualThickness );
  202. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  203. drcItem->SetItems( item );
  204. drcItem->SetViolatingRule( constraint.GetParentRule() );
  205. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  206. }
  207. }
  208. return true;
  209. };
  210. static const std::vector<KICAD_T> itemTypes = { PCB_FIELD_T, PCB_TEXT_T, PCB_TEXTBOX_T };
  211. forEachGeometryItem( itemTypes, LSET::AllLayersMask(),
  212. [&]( BOARD_ITEM* item ) -> bool
  213. {
  214. ++count;
  215. return true;
  216. } );
  217. forEachGeometryItem( itemTypes, LSET::AllLayersMask(),
  218. [&]( BOARD_ITEM* item ) -> bool
  219. {
  220. if( !reportProgress( ii++, count, progressDelta ) )
  221. return false;
  222. EDA_TEXT* text = nullptr;
  223. int strikes = 0;
  224. switch( item->Type() )
  225. {
  226. case PCB_FIELD_T: text = static_cast<PCB_FIELD*>( item ); break;
  227. case PCB_TEXT_T: text = static_cast<PCB_TEXT*>( item ); break;
  228. case PCB_TEXTBOX_T: text = static_cast<PCB_TEXTBOX*>( item ); break;
  229. default: UNIMPLEMENTED_FOR( item->GetClass() ); break;
  230. }
  231. if( !text || !text->IsVisible() )
  232. return true;
  233. if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_THICKNESS ) )
  234. strikes++;
  235. else
  236. checkTextThickness( item, text );
  237. if( m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_HEIGHT ) )
  238. strikes++;
  239. else
  240. checkTextHeight( item, text );
  241. if( strikes >= 2 )
  242. return false;
  243. return true;
  244. } );
  245. reportRuleStatistics();
  246. return !m_drcEngine->IsCancelled();
  247. }
  248. namespace detail
  249. {
  250. static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_TEXT_DIMS> dummy;
  251. }