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.

665 lines
20 KiB

2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KICAD, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2021 Ola Rinta-Koski
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * Font abstract base class
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, you may find one here:
  21. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. * or you may search the http://www.gnu.org website for the version 2 license,
  23. * or you may write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  25. */
  26. #include <list>
  27. #include <mutex>
  28. #include <unordered_map>
  29. #include <macros.h>
  30. #include <string_utils.h>
  31. #include <gal/graphics_abstraction_layer.h>
  32. #include <font/stroke_font.h>
  33. #include <font/outline_font.h>
  34. #include <trigo.h>
  35. #include <markup_parser.h>
  36. // The "official" name of the Kicad stroke font (always existing)
  37. #include <font/kicad_font_name.h>
  38. #include <wx/tokenzr.h>
  39. // markup_parser.h includes pegtl.hpp which includes windows.h... which leaks #define DrawText
  40. #undef DrawText
  41. using namespace KIFONT;
  42. METRICS g_defaultMetrics;
  43. const METRICS& METRICS::Default()
  44. {
  45. return g_defaultMetrics;
  46. }
  47. FONT* FONT::s_defaultFont = nullptr;
  48. std::map< std::tuple<wxString, bool, bool, bool>, FONT*> FONT::s_fontMap;
  49. class MARKUP_CACHE
  50. {
  51. public:
  52. struct ENTRY
  53. {
  54. std::string source;
  55. std::unique_ptr<MARKUP::NODE> root;
  56. };
  57. typedef std::pair<wxString, ENTRY> CACHE_ENTRY;
  58. MARKUP_CACHE( size_t aMaxSize ) :
  59. m_maxSize( aMaxSize )
  60. {
  61. }
  62. ENTRY& Put( const CACHE_ENTRY::first_type& aQuery, ENTRY&& aResult )
  63. {
  64. auto it = m_cache.find( aQuery );
  65. m_cacheMru.emplace_front( CACHE_ENTRY( aQuery, std::move( aResult ) ) );
  66. if( it != m_cache.end() )
  67. {
  68. m_cacheMru.erase( it->second );
  69. m_cache.erase( it );
  70. }
  71. m_cache[aQuery] = m_cacheMru.begin();
  72. if( m_cache.size() > m_maxSize )
  73. {
  74. auto last = m_cacheMru.end();
  75. last--;
  76. m_cache.erase( last->first );
  77. m_cacheMru.pop_back();
  78. }
  79. return m_cacheMru.begin()->second;
  80. }
  81. ENTRY* Get( const CACHE_ENTRY::first_type& aQuery )
  82. {
  83. auto it = m_cache.find( aQuery );
  84. if( it == m_cache.end() )
  85. return nullptr;
  86. m_cacheMru.splice( m_cacheMru.begin(), m_cacheMru, it->second );
  87. return &m_cacheMru.begin()->second;
  88. }
  89. void Clear()
  90. {
  91. m_cacheMru.clear();
  92. m_cache.clear();
  93. }
  94. private:
  95. size_t m_maxSize;
  96. std::list<CACHE_ENTRY> m_cacheMru;
  97. std::unordered_map<wxString, std::list<CACHE_ENTRY>::iterator> m_cache;
  98. };
  99. static MARKUP_CACHE s_markupCache( 1024 );
  100. static std::mutex s_markupCacheMutex;
  101. FONT::FONT()
  102. {
  103. }
  104. FONT* FONT::getDefaultFont()
  105. {
  106. if( !s_defaultFont )
  107. s_defaultFont = STROKE_FONT::LoadFont( wxEmptyString );
  108. return s_defaultFont;
  109. }
  110. FONT* FONT::GetFont( const wxString& aFontName, bool aBold, bool aItalic,
  111. const std::vector<wxString>* aEmbeddedFiles, bool aForDrawingSheet )
  112. {
  113. if( aFontName.empty() || aFontName.StartsWith( KICAD_FONT_NAME ) )
  114. return getDefaultFont();
  115. std::tuple<wxString, bool, bool, bool> key = { aFontName, aBold, aItalic, aForDrawingSheet };
  116. FONT* font = nullptr;
  117. if( s_fontMap.find( key ) != s_fontMap.end() )
  118. font = s_fontMap[key];
  119. if( !font )
  120. font = OUTLINE_FONT::LoadFont( aFontName, aBold, aItalic, aEmbeddedFiles,
  121. aForDrawingSheet );
  122. if( !font )
  123. font = getDefaultFont();
  124. s_fontMap[key] = font;
  125. return font;
  126. }
  127. bool FONT::IsStroke( const wxString& aFontName )
  128. {
  129. // This would need a more complex implementation if we ever support more stroke fonts
  130. // than the KiCad Font.
  131. return aFontName == _( "Default Font" ) || aFontName == KICAD_FONT_NAME;
  132. }
  133. void FONT::getLinePositions( const wxString& aText, const VECTOR2I& aPosition,
  134. wxArrayString& aTextLines, std::vector<VECTOR2I>& aPositions,
  135. std::vector<VECTOR2I>& aExtents, const TEXT_ATTRIBUTES& aAttrs,
  136. const METRICS& aFontMetrics ) const
  137. {
  138. wxStringSplit( aText, aTextLines, '\n' );
  139. int lineCount = aTextLines.Count();
  140. aPositions.reserve( lineCount );
  141. int interline = GetInterline( aAttrs.m_Size.y, aFontMetrics ) * aAttrs.m_LineSpacing;
  142. int height = 0;
  143. for( int i = 0; i < lineCount; i++ )
  144. {
  145. VECTOR2I pos( aPosition.x, aPosition.y + i * interline );
  146. VECTOR2I end = boundingBoxSingleLine( nullptr, aTextLines[i], pos, aAttrs.m_Size,
  147. aAttrs.m_Italic, aFontMetrics );
  148. VECTOR2I bBox( end - pos );
  149. aExtents.push_back( bBox );
  150. if( i == 0 )
  151. height += ( aAttrs.m_Size.y * 1.17 ); // 1.17 is a fudge to match 6.0 positioning
  152. else
  153. height += interline;
  154. }
  155. VECTOR2I offset( 0, 0 );
  156. offset.y += aAttrs.m_Size.y;
  157. if( IsStroke() )
  158. {
  159. // Fudge factors to match 6.0 positioning
  160. offset.x += aAttrs.m_StrokeWidth / 1.52;
  161. offset.y -= aAttrs.m_StrokeWidth * 0.052;
  162. }
  163. switch( aAttrs.m_Valign )
  164. {
  165. case GR_TEXT_V_ALIGN_TOP: break;
  166. case GR_TEXT_V_ALIGN_CENTER: offset.y -= height / 2; break;
  167. case GR_TEXT_V_ALIGN_BOTTOM: offset.y -= height; break;
  168. case GR_TEXT_V_ALIGN_INDETERMINATE:
  169. wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
  170. break;
  171. }
  172. for( int i = 0; i < lineCount; i++ )
  173. {
  174. VECTOR2I lineSize = aExtents.at( i );
  175. VECTOR2I lineOffset( offset );
  176. lineOffset.y += i * interline;
  177. switch( aAttrs.m_Halign )
  178. {
  179. case GR_TEXT_H_ALIGN_LEFT: break;
  180. case GR_TEXT_H_ALIGN_CENTER: lineOffset.x = -lineSize.x / 2; break;
  181. case GR_TEXT_H_ALIGN_RIGHT: lineOffset.x = -( lineSize.x + offset.x ); break;
  182. case GR_TEXT_H_ALIGN_INDETERMINATE:
  183. wxFAIL_MSG( wxT( "Indeterminate state legal only in dialogs." ) );
  184. break;
  185. }
  186. aPositions.push_back( aPosition + lineOffset );
  187. }
  188. }
  189. void FONT::Draw( KIGFX::GAL* aGal, const wxString& aText, const VECTOR2I& aPosition,
  190. const VECTOR2I& aCursor, const TEXT_ATTRIBUTES& aAttrs,
  191. const METRICS& aFontMetrics ) const
  192. {
  193. if( !aGal || aText.empty() )
  194. return;
  195. VECTOR2I position( aPosition - aCursor );
  196. // Split multiline strings into separate ones and draw them line by line
  197. wxArrayString strings_list;
  198. std::vector<VECTOR2I> positions;
  199. std::vector<VECTOR2I> extents;
  200. getLinePositions( aText, position, strings_list, positions, extents, aAttrs, aFontMetrics );
  201. aGal->SetLineWidth( aAttrs.m_StrokeWidth );
  202. for( size_t i = 0; i < strings_list.GetCount(); i++ )
  203. {
  204. drawSingleLineText( aGal, nullptr, strings_list[i], positions[i], aAttrs.m_Size,
  205. aAttrs.m_Angle, aAttrs.m_Mirrored, aPosition, aAttrs.m_Italic,
  206. aAttrs.m_Underlined, aFontMetrics );
  207. }
  208. }
  209. /**
  210. * @return position of cursor for drawing next substring.
  211. */
  212. VECTOR2I drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
  213. const MARKUP::NODE* aNode, const VECTOR2I& aPosition,
  214. const KIFONT::FONT* aFont, const VECTOR2I& aSize, const EDA_ANGLE& aAngle,
  215. bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle,
  216. const METRICS& aFontMetrics )
  217. {
  218. VECTOR2I nextPosition = aPosition;
  219. bool drawUnderline = false;
  220. bool drawOverbar = false;
  221. if( aNode )
  222. {
  223. TEXT_STYLE_FLAGS textStyle = aTextStyle;
  224. if( !aNode->is_root() )
  225. {
  226. if( aNode->isSubscript() )
  227. textStyle |= TEXT_STYLE::SUBSCRIPT;
  228. else if( aNode->isSuperscript() )
  229. textStyle |= TEXT_STYLE::SUPERSCRIPT;
  230. if( aNode->isOverbar() )
  231. drawOverbar = true;
  232. if( aNode->has_content() )
  233. {
  234. BOX2I bbox;
  235. nextPosition = aFont->GetTextAsGlyphs( &bbox, aGlyphs, aNode->asWxString(), aSize,
  236. nextPosition, aAngle, aMirror, aOrigin,
  237. textStyle );
  238. if( aBoundingBox )
  239. aBoundingBox->Merge( bbox );
  240. }
  241. }
  242. else if( aTextStyle & TEXT_STYLE::UNDERLINE )
  243. {
  244. drawUnderline = true;
  245. }
  246. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  247. {
  248. nextPosition = drawMarkup( aBoundingBox, aGlyphs, child.get(), nextPosition, aFont,
  249. aSize, aAngle, aMirror, aOrigin, textStyle, aFontMetrics );
  250. }
  251. }
  252. if( drawUnderline )
  253. {
  254. // Shorten the bar a little so its rounded ends don't make it over-long
  255. double barTrim = aSize.x * 0.1;
  256. double barOffset = aFontMetrics.GetUnderlineVerticalPosition( aSize.y );
  257. VECTOR2D barStart( aPosition.x + barTrim, aPosition.y - barOffset );
  258. VECTOR2D barEnd( nextPosition.x - barTrim, nextPosition.y - barOffset );
  259. if( aGlyphs )
  260. {
  261. STROKE_GLYPH barGlyph;
  262. barGlyph.AddPoint( barStart );
  263. barGlyph.AddPoint( barEnd );
  264. barGlyph.Finalize();
  265. aGlyphs->push_back( barGlyph.Transform( { 1.0, 1.0 }, { 0, 0 }, false, aAngle, aMirror,
  266. aOrigin ) );
  267. }
  268. }
  269. if( drawOverbar )
  270. {
  271. // Shorten the bar a little so its rounded ends don't make it over-long
  272. double barTrim = aSize.x * 0.1;
  273. double barOffset = aFontMetrics.GetOverbarVerticalPosition( aSize.y );
  274. VECTOR2D barStart( aPosition.x + barTrim, aPosition.y - barOffset );
  275. VECTOR2D barEnd( nextPosition.x - barTrim, nextPosition.y - barOffset );
  276. if( aGlyphs )
  277. {
  278. STROKE_GLYPH barGlyph;
  279. barGlyph.AddPoint( barStart );
  280. barGlyph.AddPoint( barEnd );
  281. barGlyph.Finalize();
  282. aGlyphs->push_back( barGlyph.Transform( { 1.0, 1.0 }, { 0, 0 }, false, aAngle, aMirror,
  283. aOrigin ) );
  284. }
  285. }
  286. return nextPosition;
  287. }
  288. VECTOR2I FONT::drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
  289. const wxString& aText, const VECTOR2I& aPosition, const VECTOR2I& aSize,
  290. const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
  291. TEXT_STYLE_FLAGS aTextStyle, const METRICS& aFontMetrics ) const
  292. {
  293. std::lock_guard<std::mutex> lock( s_markupCacheMutex );
  294. MARKUP_CACHE::ENTRY* markup = s_markupCache.Get( aText );
  295. if( !markup || !markup->root )
  296. {
  297. MARKUP_CACHE::ENTRY& cached = s_markupCache.Put( aText, {} );
  298. cached.source = TO_UTF8( aText );
  299. MARKUP::MARKUP_PARSER markupParser( &cached.source );
  300. cached.root = markupParser.Parse();
  301. markup = &cached;
  302. }
  303. wxASSERT( markup && markup->root );
  304. return ::drawMarkup( aBoundingBox, aGlyphs, markup->root.get(), aPosition, this, aSize, aAngle,
  305. aMirror, aOrigin, aTextStyle, aFontMetrics );
  306. }
  307. void FONT::drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const wxString& aText,
  308. const VECTOR2I& aPosition, const VECTOR2I& aSize,
  309. const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
  310. bool aItalic, bool aUnderline, const METRICS& aFontMetrics ) const
  311. {
  312. if( !aGal )
  313. return;
  314. TEXT_STYLE_FLAGS textStyle = 0;
  315. if( aItalic )
  316. textStyle |= TEXT_STYLE::ITALIC;
  317. if( aUnderline )
  318. textStyle |= TEXT_STYLE::UNDERLINE;
  319. std::vector<std::unique_ptr<GLYPH>> glyphs;
  320. (void) drawMarkup( aBoundingBox, &glyphs, aText, aPosition, aSize, aAngle, aMirror, aOrigin,
  321. textStyle, aFontMetrics );
  322. aGal->DrawGlyphs( glyphs );
  323. }
  324. VECTOR2I FONT::StringBoundaryLimits( const wxString& aText, const VECTOR2I& aSize, int aThickness,
  325. bool aBold, bool aItalic, const METRICS& aFontMetrics ) const
  326. {
  327. // TODO do we need to parse every time - have we already parsed?
  328. BOX2I boundingBox;
  329. TEXT_STYLE_FLAGS textStyle = 0;
  330. if( aBold )
  331. textStyle |= TEXT_STYLE::BOLD;
  332. if( aItalic )
  333. textStyle |= TEXT_STYLE::ITALIC;
  334. (void) drawMarkup( &boundingBox, nullptr, aText, VECTOR2I(), aSize, ANGLE_0, false, VECTOR2I(),
  335. textStyle, aFontMetrics );
  336. if( IsStroke() )
  337. {
  338. // Inflate by a bit more than thickness/2 to catch diacriticals, descenders, etc.
  339. boundingBox.Inflate( KiROUND( aThickness * 1.5 ) );
  340. }
  341. else if( IsOutline() )
  342. {
  343. // Outline fonts have thickness built in, and *usually* stay within their ascent/descent
  344. }
  345. return boundingBox.GetSize();
  346. }
  347. VECTOR2I FONT::boundingBoxSingleLine( BOX2I* aBBox, const wxString& aText,
  348. const VECTOR2I& aPosition, const VECTOR2I& aSize,
  349. bool aItalic, const METRICS& aFontMetrics ) const
  350. {
  351. TEXT_STYLE_FLAGS textStyle = 0;
  352. if( aItalic )
  353. textStyle |= TEXT_STYLE::ITALIC;
  354. VECTOR2I extents = drawMarkup( aBBox, nullptr, aText, aPosition, aSize, ANGLE_0, false,
  355. VECTOR2I(), textStyle, aFontMetrics );
  356. return extents;
  357. }
  358. /**
  359. * Break marked-up text into "words".
  360. *
  361. * In this context, a "word" is EITHER a run of marked-up text (subscript, superscript or
  362. * overbar), OR a run of non-marked-up text separated by spaces.
  363. */
  364. void wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords,
  365. const std::unique_ptr<MARKUP::NODE>& aNode, const KIFONT::FONT* aFont,
  366. const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle )
  367. {
  368. TEXT_STYLE_FLAGS textStyle = aTextStyle;
  369. if( !aNode->is_root() )
  370. {
  371. wxChar escapeChar = 0;
  372. if( aNode->isSubscript() )
  373. {
  374. escapeChar = '_';
  375. textStyle = TEXT_STYLE::SUBSCRIPT;
  376. }
  377. else if( aNode->isSuperscript() )
  378. {
  379. escapeChar = '^';
  380. textStyle = TEXT_STYLE::SUPERSCRIPT;
  381. }
  382. if( aNode->isOverbar() )
  383. {
  384. escapeChar = '~';
  385. textStyle |= TEXT_STYLE::OVERBAR;
  386. }
  387. if( escapeChar )
  388. {
  389. wxString word = wxString::Format( wxT( "%c{" ), escapeChar );
  390. int width = 0;
  391. if( aNode->has_content() )
  392. {
  393. VECTOR2I next = aFont->GetTextAsGlyphs( nullptr, nullptr, aNode->asWxString(),
  394. aSize, { 0, 0 }, ANGLE_0, false, { 0, 0 },
  395. textStyle );
  396. word += aNode->asWxString();
  397. width += next.x;
  398. }
  399. std::vector<std::pair<wxString, int>> childWords;
  400. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  401. wordbreakMarkup( &childWords, child, aFont, aSize, textStyle );
  402. for( const std::pair<wxString, int>& childWord : childWords )
  403. {
  404. word += childWord.first;
  405. width += childWord.second;
  406. }
  407. word += wxT( "}" );
  408. aWords->emplace_back( std::make_pair( word, width ) );
  409. return;
  410. }
  411. else
  412. {
  413. wxString textRun = aNode->asWxString();
  414. wxStringTokenizer tokenizer( textRun, " ", wxTOKEN_RET_DELIMS );
  415. std::vector<wxString> words;
  416. while( tokenizer.HasMoreTokens() )
  417. words.emplace_back( tokenizer.GetNextToken() );
  418. for( const wxString& word : words )
  419. {
  420. wxString chars = word;
  421. chars.Trim();
  422. int w = aFont->GetTextAsGlyphs( nullptr, nullptr, chars, aSize, { 0, 0 },
  423. ANGLE_0, false, { 0, 0 }, textStyle ).x;
  424. aWords->emplace_back( std::make_pair( word, w ) );
  425. }
  426. }
  427. }
  428. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  429. wordbreakMarkup( aWords, child, aFont, aSize, textStyle );
  430. }
  431. void FONT::wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords, const wxString& aText,
  432. const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle ) const
  433. {
  434. MARKUP::MARKUP_PARSER markupParser( TO_UTF8( aText ) );
  435. std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
  436. ::wordbreakMarkup( aWords, root, this, aSize, aTextStyle );
  437. }
  438. void FONT::LinebreakText( wxString& aText, int aColumnWidth, const VECTOR2I& aSize, int aThickness,
  439. bool aBold, bool aItalic ) const
  440. {
  441. TEXT_STYLE_FLAGS textStyle = 0;
  442. if( aBold )
  443. textStyle |= TEXT_STYLE::BOLD;
  444. if( aItalic )
  445. textStyle |= TEXT_STYLE::ITALIC;
  446. int spaceWidth = GetTextAsGlyphs( nullptr, nullptr, wxS( " " ), aSize, VECTOR2I(), ANGLE_0,
  447. false, VECTOR2I(), textStyle ).x;
  448. wxArrayString textLines;
  449. wxStringSplit( aText, textLines, '\n' );
  450. aText = wxEmptyString;
  451. for( size_t ii = 0; ii < textLines.Count(); ++ii )
  452. {
  453. std::vector<std::pair<wxString, int>> markup;
  454. std::vector<std::pair<wxString, int>> words;
  455. wordbreakMarkup( &markup, textLines[ii], aSize, textStyle );
  456. for( const auto& [ run, runWidth ] : markup )
  457. {
  458. if( !words.empty() && !words.back().first.EndsWith( ' ' ) )
  459. {
  460. words.back().first += run;
  461. words.back().second += runWidth;
  462. }
  463. else
  464. {
  465. words.emplace_back( std::make_pair( run, runWidth ) );
  466. }
  467. }
  468. bool buryMode = false;
  469. int lineWidth = 0;
  470. wxString pendingSpaces;
  471. for( const auto& [ word, wordWidth ] : words )
  472. {
  473. int pendingSpaceWidth = (int) pendingSpaces.Length() * spaceWidth;
  474. bool overflow = lineWidth + pendingSpaceWidth + wordWidth > aColumnWidth - aThickness;
  475. if( overflow && pendingSpaces.Length() > 0 )
  476. {
  477. aText += '\n';
  478. lineWidth = 0;
  479. pendingSpaces = wxEmptyString;
  480. pendingSpaceWidth = 0;
  481. buryMode = true;
  482. }
  483. if( word == wxS( " " ) )
  484. {
  485. pendingSpaces += word;
  486. }
  487. else
  488. {
  489. if( buryMode )
  490. {
  491. buryMode = false;
  492. }
  493. else
  494. {
  495. aText += pendingSpaces;
  496. lineWidth += pendingSpaceWidth;
  497. }
  498. if( word.EndsWith( ' ' ) )
  499. {
  500. aText += word.Left( word.Length() - 1 );
  501. pendingSpaces = wxS( " " );
  502. }
  503. else
  504. {
  505. aText += word;
  506. pendingSpaces = wxEmptyString;
  507. }
  508. lineWidth += wordWidth;
  509. }
  510. }
  511. // Add the newlines back onto the string
  512. if( ii != ( textLines.Count() - 1 ) )
  513. aText += '\n';
  514. }
  515. }