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.

618 lines
19 KiB

  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 (C) 2021-2023 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 <wx/font.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 building Kicad stroke font (always existing)
  37. #include <font/kicad_font_name.h>
  38. #include "macros.h"
  39. // markup_parser.h includes pegtl.hpp which includes windows.h... which leaks #define DrawText
  40. #undef DrawText
  41. using namespace KIFONT;
  42. FONT* FONT::s_defaultFont = nullptr;
  43. std::map< std::tuple<wxString, bool, bool>, FONT*> FONT::s_fontMap;
  44. class MARKUP_CACHE
  45. {
  46. public:
  47. struct ENTRY
  48. {
  49. std::string source;
  50. std::unique_ptr<MARKUP::NODE> root;
  51. };
  52. typedef std::pair<wxString, ENTRY> CACHE_ENTRY;
  53. MARKUP_CACHE( size_t aMaxSize ) :
  54. m_maxSize( aMaxSize )
  55. {
  56. }
  57. ENTRY& Put( const CACHE_ENTRY::first_type& aQuery, ENTRY&& aResult )
  58. {
  59. auto it = m_cache.find( aQuery );
  60. m_cacheMru.emplace_front( CACHE_ENTRY( aQuery, std::move( aResult ) ) );
  61. if( it != m_cache.end() )
  62. {
  63. m_cacheMru.erase( it->second );
  64. m_cache.erase( it );
  65. }
  66. m_cache[aQuery] = m_cacheMru.begin();
  67. if( m_cache.size() > m_maxSize )
  68. {
  69. auto last = m_cacheMru.end();
  70. last--;
  71. m_cache.erase( last->first );
  72. m_cacheMru.pop_back();
  73. }
  74. return m_cacheMru.begin()->second;
  75. }
  76. ENTRY* Get( const CACHE_ENTRY::first_type& aQuery )
  77. {
  78. auto it = m_cache.find( aQuery );
  79. if( it == m_cache.end() )
  80. return nullptr;
  81. m_cacheMru.splice( m_cacheMru.begin(), m_cacheMru, it->second );
  82. return &m_cacheMru.begin()->second;
  83. }
  84. void Clear()
  85. {
  86. m_cacheMru.clear();
  87. m_cache.clear();
  88. }
  89. private:
  90. size_t m_maxSize;
  91. std::list<CACHE_ENTRY> m_cacheMru;
  92. std::unordered_map<wxString, std::list<CACHE_ENTRY>::iterator> m_cache;
  93. };
  94. static MARKUP_CACHE s_markupCache( 1024 );
  95. static std::mutex s_markupCacheMutex;
  96. FONT::FONT()
  97. {
  98. }
  99. FONT* FONT::getDefaultFont()
  100. {
  101. if( !s_defaultFont )
  102. s_defaultFont = STROKE_FONT::LoadFont( wxEmptyString );
  103. return s_defaultFont;
  104. }
  105. FONT* FONT::GetFont( const wxString& aFontName, bool aBold, bool aItalic )
  106. {
  107. if( aFontName.empty() || aFontName.StartsWith( KICAD_FONT_NAME ) )
  108. return getDefaultFont();
  109. std::tuple<wxString, bool, bool> key = { aFontName, aBold, aItalic };
  110. FONT* font = s_fontMap[key];
  111. if( !font )
  112. font = OUTLINE_FONT::LoadFont( aFontName, aBold, aItalic );
  113. if( !font )
  114. font = getDefaultFont();
  115. s_fontMap[key] = font;
  116. return font;
  117. }
  118. bool FONT::IsStroke( const wxString& aFontName )
  119. {
  120. // This would need a more complex implementation if we ever support more stroke fonts
  121. // than the KiCad Font.
  122. return aFontName == _( "Default Font" ) || aFontName == KICAD_FONT_NAME;
  123. }
  124. void FONT::getLinePositions( const wxString& aText, const VECTOR2I& aPosition,
  125. wxArrayString& aTextLines, std::vector<VECTOR2I>& aPositions,
  126. std::vector<VECTOR2I>& aExtents, const TEXT_ATTRIBUTES& aAttrs ) const
  127. {
  128. wxStringSplit( aText, aTextLines, '\n' );
  129. int lineCount = aTextLines.Count();
  130. aPositions.reserve( lineCount );
  131. int interline = GetInterline( aAttrs.m_Size.y, aAttrs.m_LineSpacing );
  132. int height = 0;
  133. for( int i = 0; i < lineCount; i++ )
  134. {
  135. VECTOR2I pos( aPosition.x, aPosition.y + i * interline );
  136. VECTOR2I end = boundingBoxSingleLine( nullptr, aTextLines[i], pos, aAttrs.m_Size,
  137. aAttrs.m_Italic );
  138. VECTOR2I bBox( end - pos );
  139. aExtents.push_back( bBox );
  140. if( i == 0 )
  141. height += ( aAttrs.m_Size.y * 1.17 ); // 1.17 is a fudge to match 6.0 positioning
  142. else
  143. height += interline;
  144. }
  145. VECTOR2I offset( 0, 0 );
  146. offset.y += aAttrs.m_Size.y;
  147. switch( aAttrs.m_Valign )
  148. {
  149. case GR_TEXT_V_ALIGN_TOP: break;
  150. case GR_TEXT_V_ALIGN_CENTER: offset.y -= height / 2; break;
  151. case GR_TEXT_V_ALIGN_BOTTOM: offset.y -= height; break;
  152. }
  153. for( int i = 0; i < lineCount; i++ )
  154. {
  155. VECTOR2I lineSize = aExtents.at( i );
  156. VECTOR2I lineOffset( offset );
  157. lineOffset.y += i * interline;
  158. switch( aAttrs.m_Halign )
  159. {
  160. case GR_TEXT_H_ALIGN_LEFT: break;
  161. case GR_TEXT_H_ALIGN_CENTER: lineOffset.x = -lineSize.x / 2; break;
  162. case GR_TEXT_H_ALIGN_RIGHT: lineOffset.x = -lineSize.x; break;
  163. }
  164. aPositions.push_back( aPosition + lineOffset );
  165. }
  166. }
  167. /**
  168. * Draw a string.
  169. *
  170. * @param aGal
  171. * @param aText is the text to be drawn.
  172. * @param aPosition is the text object position in world coordinates.
  173. * @param aCursor is the current text position (for multiple text blocks within a single text
  174. * object, such as a run of superscript characters)
  175. * @param aAttrs are the styling attributes of the text, including its rotation
  176. */
  177. void FONT::Draw( KIGFX::GAL* aGal, const wxString& aText, const VECTOR2I& aPosition,
  178. const VECTOR2I& aCursor, const TEXT_ATTRIBUTES& aAttrs ) const
  179. {
  180. if( !aGal || aText.empty() )
  181. return;
  182. VECTOR2I position( aPosition - aCursor );
  183. // Split multiline strings into separate ones and draw them line by line
  184. wxArrayString strings_list;
  185. std::vector<VECTOR2I> positions;
  186. std::vector<VECTOR2I> extents;
  187. getLinePositions( aText, position, strings_list, positions, extents, aAttrs );
  188. aGal->SetLineWidth( aAttrs.m_StrokeWidth );
  189. for( size_t i = 0; i < strings_list.GetCount(); i++ )
  190. {
  191. drawSingleLineText( aGal, nullptr, strings_list[i], positions[i], aAttrs.m_Size,
  192. aAttrs.m_Angle, aAttrs.m_Mirrored, aPosition, aAttrs.m_Italic,
  193. aAttrs.m_Underlined );
  194. }
  195. }
  196. /**
  197. * @return position of cursor for drawing next substring
  198. */
  199. VECTOR2I drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
  200. const MARKUP::NODE* aNode, const VECTOR2I& aPosition,
  201. const KIFONT::FONT* aFont, const VECTOR2I& aSize, const EDA_ANGLE& aAngle,
  202. bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle )
  203. {
  204. VECTOR2I nextPosition = aPosition;
  205. bool drawUnderline = false;
  206. bool drawOverbar = false;
  207. if( aNode )
  208. {
  209. TEXT_STYLE_FLAGS textStyle = aTextStyle;
  210. if( !aNode->is_root() )
  211. {
  212. if( aNode->isSubscript() )
  213. textStyle |= TEXT_STYLE::SUBSCRIPT;
  214. else if( aNode->isSuperscript() )
  215. textStyle |= TEXT_STYLE::SUPERSCRIPT;
  216. if( aNode->isOverbar() )
  217. drawOverbar = true;
  218. if( aNode->has_content() )
  219. {
  220. BOX2I bbox;
  221. nextPosition = aFont->GetTextAsGlyphs( &bbox, aGlyphs, aNode->asWxString(), aSize,
  222. aPosition, aAngle, aMirror, aOrigin,
  223. textStyle );
  224. if( aBoundingBox )
  225. aBoundingBox->Merge( bbox );
  226. }
  227. }
  228. else if( aTextStyle & TEXT_STYLE::UNDERLINE )
  229. {
  230. drawUnderline = true;
  231. }
  232. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  233. {
  234. nextPosition = drawMarkup( aBoundingBox, aGlyphs, child.get(), nextPosition, aFont,
  235. aSize, aAngle, aMirror, aOrigin, textStyle );
  236. }
  237. }
  238. if( drawUnderline )
  239. {
  240. // Shorten the bar a little so its rounded ends don't make it over-long
  241. double barTrim = aSize.x * 0.1;
  242. double barOffset = aFont->ComputeUnderlineVerticalPosition( aSize.y );
  243. VECTOR2D barStart( aPosition.x + barTrim, aPosition.y - barOffset );
  244. VECTOR2D barEnd( nextPosition.x - barTrim, nextPosition.y - barOffset );
  245. if( aGlyphs )
  246. {
  247. STROKE_GLYPH barGlyph;
  248. barGlyph.AddPoint( barStart );
  249. barGlyph.AddPoint( barEnd );
  250. barGlyph.Finalize();
  251. aGlyphs->push_back( barGlyph.Transform( { 1.0, 1.0 }, { 0, 0 }, false, aAngle, aMirror,
  252. aOrigin ) );
  253. }
  254. }
  255. if( drawOverbar )
  256. {
  257. // Shorten the bar a little so its rounded ends don't make it over-long
  258. double barTrim = aSize.x * 0.1;
  259. double barOffset = aFont->ComputeOverbarVerticalPosition( aSize.y );
  260. VECTOR2D barStart( aPosition.x + barTrim, aPosition.y - barOffset );
  261. VECTOR2D barEnd( nextPosition.x - barTrim, nextPosition.y - barOffset );
  262. if( aGlyphs )
  263. {
  264. STROKE_GLYPH barGlyph;
  265. barGlyph.AddPoint( barStart );
  266. barGlyph.AddPoint( barEnd );
  267. barGlyph.Finalize();
  268. aGlyphs->push_back( barGlyph.Transform( { 1.0, 1.0 }, { 0, 0 }, false, aAngle, aMirror,
  269. aOrigin ) );
  270. }
  271. }
  272. return nextPosition;
  273. }
  274. VECTOR2I FONT::drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
  275. const wxString& aText, const VECTOR2I& aPosition, const VECTOR2I& aSize,
  276. const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
  277. TEXT_STYLE_FLAGS aTextStyle ) const
  278. {
  279. std::lock_guard<std::mutex> lock( s_markupCacheMutex );
  280. MARKUP_CACHE::ENTRY* markup = s_markupCache.Get( aText );
  281. if( !markup || !markup->root )
  282. {
  283. MARKUP_CACHE::ENTRY& cached = s_markupCache.Put( aText, {} );
  284. cached.source = TO_UTF8( aText );
  285. MARKUP::MARKUP_PARSER markupParser( &cached.source );
  286. cached.root = markupParser.Parse();
  287. markup = &cached;
  288. }
  289. wxASSERT( markup && markup->root );
  290. return ::drawMarkup( aBoundingBox, aGlyphs, markup->root.get(), aPosition, this, aSize, aAngle,
  291. aMirror, aOrigin, aTextStyle );
  292. }
  293. void FONT::drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const wxString& aText,
  294. const VECTOR2I& aPosition, const VECTOR2I& aSize,
  295. const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
  296. bool aItalic, bool aUnderline ) const
  297. {
  298. if( !aGal )
  299. return;
  300. TEXT_STYLE_FLAGS textStyle = 0;
  301. if( aItalic )
  302. textStyle |= TEXT_STYLE::ITALIC;
  303. if( aUnderline )
  304. textStyle |= TEXT_STYLE::UNDERLINE;
  305. std::vector<std::unique_ptr<GLYPH>> glyphs;
  306. (void) drawMarkup( aBoundingBox, &glyphs, aText, aPosition, aSize, aAngle, aMirror, aOrigin,
  307. textStyle );
  308. aGal->DrawGlyphs( glyphs );
  309. }
  310. VECTOR2I FONT::StringBoundaryLimits( const wxString& aText, const VECTOR2I& aSize, int aThickness,
  311. bool aBold, bool aItalic ) const
  312. {
  313. // TODO do we need to parse every time - have we already parsed?
  314. BOX2I boundingBox;
  315. TEXT_STYLE_FLAGS textStyle = 0;
  316. if( aBold )
  317. textStyle |= TEXT_STYLE::BOLD;
  318. if( aItalic )
  319. textStyle |= TEXT_STYLE::ITALIC;
  320. (void) drawMarkup( &boundingBox, nullptr, aText, VECTOR2I(), aSize, ANGLE_0, false,
  321. VECTOR2I(), textStyle );
  322. if( IsStroke() )
  323. {
  324. // Inflate by a bit more than thickness/2 to catch diacriticals, descenders, etc.
  325. boundingBox.Inflate( KiROUND( aThickness * 1.25 ) );
  326. }
  327. else if( IsOutline() )
  328. {
  329. // Outline fonts have thickness built in, and *usually* stay within their ascent/descent
  330. }
  331. return boundingBox.GetSize();
  332. }
  333. VECTOR2I FONT::boundingBoxSingleLine( BOX2I* aBBox, const wxString& aText,
  334. const VECTOR2I& aPosition, const VECTOR2I& aSize,
  335. bool aItalic ) const
  336. {
  337. TEXT_STYLE_FLAGS textStyle = 0;
  338. if( aItalic )
  339. textStyle |= TEXT_STYLE::ITALIC;
  340. VECTOR2I extents = drawMarkup( aBBox, nullptr, aText, aPosition, aSize, ANGLE_0, false,
  341. VECTOR2I(), textStyle );
  342. return extents;
  343. }
  344. /*
  345. * Break marked-up text into "words". In this context, a "word" is EITHER a run of marked-up
  346. * text (subscript, superscript or overbar), OR a run of non-marked-up text separated by spaces.
  347. */
  348. void wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords,
  349. const std::unique_ptr<MARKUP::NODE>& aNode, const KIFONT::FONT* aFont,
  350. const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle )
  351. {
  352. TEXT_STYLE_FLAGS textStyle = aTextStyle;
  353. if( !aNode->is_root() )
  354. {
  355. wxChar escapeChar = 0;
  356. if( aNode->isSubscript() )
  357. {
  358. escapeChar = '_';
  359. textStyle = TEXT_STYLE::SUBSCRIPT;
  360. }
  361. else if( aNode->isSuperscript() )
  362. {
  363. escapeChar = '^';
  364. textStyle = TEXT_STYLE::SUPERSCRIPT;
  365. }
  366. if( aNode->isOverbar() )
  367. {
  368. escapeChar = '~';
  369. textStyle |= TEXT_STYLE::OVERBAR;
  370. }
  371. if( escapeChar )
  372. {
  373. wxString word = wxString::Format( wxT( "%c{" ), escapeChar );
  374. int width = 0;
  375. if( aNode->has_content() )
  376. {
  377. VECTOR2I next = aFont->GetTextAsGlyphs( nullptr, nullptr, aNode->asWxString(),
  378. aSize, { 0, 0 }, ANGLE_0, false, { 0, 0 },
  379. textStyle );
  380. word += aNode->asWxString();
  381. width += next.x;
  382. }
  383. std::vector<std::pair<wxString, int>> childWords;
  384. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  385. wordbreakMarkup( &childWords, child, aFont, aSize, textStyle );
  386. for( const std::pair<wxString, int>& childWord : childWords )
  387. {
  388. word += childWord.first;
  389. width += childWord.second;
  390. }
  391. word += wxT( "}" );
  392. aWords->emplace_back( std::make_pair( word, width ) );
  393. return;
  394. }
  395. else
  396. {
  397. wxString textRun = aNode->asWxString();
  398. wxArrayString words;
  399. wxStringSplit( textRun, words, ' ' );
  400. if( textRun.EndsWith( wxS( " " ) ) )
  401. words.Add( wxS( " " ) );
  402. for( size_t ii = 0; ii < words.size(); ++ii )
  403. {
  404. int w = aFont->GetTextAsGlyphs( nullptr, nullptr, words[ii], aSize, { 0, 0 },
  405. ANGLE_0, false, { 0, 0 }, textStyle ).x;
  406. aWords->emplace_back( std::make_pair( words[ii], w ) );
  407. }
  408. }
  409. }
  410. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  411. wordbreakMarkup( aWords, child, aFont, aSize, textStyle );
  412. }
  413. void FONT::wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords, const wxString& aText,
  414. const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle ) const
  415. {
  416. MARKUP::MARKUP_PARSER markupParser( TO_UTF8( aText ) );
  417. std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
  418. ::wordbreakMarkup( aWords, root, this, aSize, aTextStyle );
  419. }
  420. /*
  421. * This is a highly simplified line-breaker. KiCad is an EDA tool, not a word processor.
  422. *
  423. * 1) It breaks only on spaces. If you type a word wider than the column width then you get
  424. * overflow.
  425. * 2) It treats runs of formatted text (superscript, subscript, overbar) as single words.
  426. * 3) It does not perform justification.
  427. *
  428. * The results of the linebreaking are the addition of \n in the text. It is presumed that this
  429. * function is called on m_shownText (or equivalent) rather than the original source text.
  430. */
  431. void FONT::LinebreakText( wxString& aText, int aColumnWidth, const VECTOR2I& aSize, int aThickness,
  432. bool aBold, bool aItalic ) const
  433. {
  434. TEXT_STYLE_FLAGS textStyle = 0;
  435. if( aBold )
  436. textStyle |= TEXT_STYLE::BOLD;
  437. if( aItalic )
  438. textStyle |= TEXT_STYLE::ITALIC;
  439. int spaceWidth = GetTextAsGlyphs( nullptr, nullptr, wxS( " " ), aSize, VECTOR2I(), ANGLE_0,
  440. false, VECTOR2I(), textStyle ).x;
  441. wxArrayString textLines;
  442. wxStringSplit( aText, textLines, '\n' );
  443. aText = wxEmptyString;
  444. for( size_t ii = 0; ii < textLines.Count(); ++ii )
  445. {
  446. bool virginLine = true;
  447. int lineWidth = 0;
  448. std::vector<std::pair<wxString, int>> words;
  449. wordbreakMarkup( &words, textLines[ii], aSize, textStyle );
  450. for( size_t jj = 0; jj < words.size(); /* advance in loop */ )
  451. {
  452. if( virginLine )
  453. {
  454. // First word is always placed, even when wider than columnWidth.
  455. aText += words[jj].first;
  456. lineWidth += words[jj].second;
  457. jj++;
  458. virginLine = false;
  459. }
  460. else if( lineWidth + spaceWidth + words[jj].second < aColumnWidth - aThickness )
  461. {
  462. aText += " " + words[jj].first;
  463. lineWidth += spaceWidth + words[jj].second;
  464. jj++;
  465. }
  466. else
  467. {
  468. aText += '\n';
  469. lineWidth = 0;
  470. virginLine = true;
  471. }
  472. }
  473. // Add the newlines back onto the string
  474. if( ii != ( textLines.Count() - 1 ) )
  475. aText += '\n';
  476. }
  477. }