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.

486 lines
15 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-2022 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 <wx/font.h>
  27. #include <string_utils.h>
  28. #include <gal/graphics_abstraction_layer.h>
  29. #include <font/stroke_font.h>
  30. #include <font/outline_font.h>
  31. #include <trigo.h>
  32. #include <markup_parser.h>
  33. // The "official" name of the building Kicad stroke font (always existing)
  34. #include <font/kicad_font_name.h>
  35. #include "macros.h"
  36. // markup_parser.h includes pegtl.hpp which includes windows.h... which leaks #define DrawText
  37. #undef DrawText
  38. using namespace KIFONT;
  39. FONT* FONT::s_defaultFont = nullptr;
  40. std::map< std::tuple<wxString, bool, bool>, FONT*> FONT::s_fontMap;
  41. FONT::FONT()
  42. {
  43. }
  44. const wxString& FONT::Name() const
  45. {
  46. return m_fontName;
  47. }
  48. FONT* FONT::getDefaultFont()
  49. {
  50. if( !s_defaultFont )
  51. s_defaultFont = STROKE_FONT::LoadFont( wxEmptyString );
  52. return s_defaultFont;
  53. }
  54. FONT* FONT::GetFont( const wxString& aFontName, bool aBold, bool aItalic )
  55. {
  56. if( aFontName.empty() || aFontName.StartsWith( KICAD_FONT_NAME ) )
  57. return getDefaultFont();
  58. std::tuple<wxString, bool, bool> key = { aFontName, aBold, aItalic };
  59. FONT* font = s_fontMap[key];
  60. if( !font )
  61. font = OUTLINE_FONT::LoadFont( aFontName, aBold, aItalic );
  62. if( !font )
  63. font = getDefaultFont();
  64. s_fontMap[key] = font;
  65. return font;
  66. }
  67. bool FONT::IsStroke( const wxString& aFontName )
  68. {
  69. // This would need a more complex implementation if we ever support more stroke fonts
  70. // than the KiCad Font.
  71. return aFontName == _( "Default Font" ) || aFontName == KICAD_FONT_NAME;
  72. }
  73. void FONT::getLinePositions( const wxString& aText, const VECTOR2I& aPosition,
  74. wxArrayString& aTextLines, std::vector<VECTOR2I>& aPositions,
  75. std::vector<VECTOR2I>& aExtents, const TEXT_ATTRIBUTES& aAttrs ) const
  76. {
  77. wxStringSplit( aText, aTextLines, '\n' );
  78. int lineCount = aTextLines.Count();
  79. aPositions.reserve( lineCount );
  80. int interline = GetInterline( aAttrs.m_Size.y, aAttrs.m_LineSpacing );
  81. int height = 0;
  82. for( int i = 0; i < lineCount; i++ )
  83. {
  84. VECTOR2I pos( aPosition.x, aPosition.y + i * interline );
  85. VECTOR2I end = boundingBoxSingleLine( nullptr, aTextLines[i], pos, aAttrs.m_Size,
  86. aAttrs.m_Italic );
  87. VECTOR2I bBox( end - pos );
  88. aExtents.push_back( bBox );
  89. if( i == 0 )
  90. height += ( aAttrs.m_Size.y * 1.17 ); // 1.17 is a fudge to match 6.0 positioning
  91. else
  92. height += interline;
  93. }
  94. VECTOR2I offset( 0, 0 );
  95. offset.y += aAttrs.m_Size.y;
  96. switch( aAttrs.m_Valign )
  97. {
  98. case GR_TEXT_V_ALIGN_TOP: break;
  99. case GR_TEXT_V_ALIGN_CENTER: offset.y -= height / 2; break;
  100. case GR_TEXT_V_ALIGN_BOTTOM: offset.y -= height; break;
  101. }
  102. for( int i = 0; i < lineCount; i++ )
  103. {
  104. VECTOR2I lineSize = aExtents.at( i );
  105. VECTOR2I lineOffset( offset );
  106. lineOffset.y += i * interline;
  107. switch( aAttrs.m_Halign )
  108. {
  109. case GR_TEXT_H_ALIGN_LEFT: break;
  110. case GR_TEXT_H_ALIGN_CENTER: lineOffset.x = -lineSize.x / 2; break;
  111. case GR_TEXT_H_ALIGN_RIGHT: lineOffset.x = -lineSize.x; break;
  112. }
  113. aPositions.push_back( aPosition + lineOffset );
  114. }
  115. }
  116. /**
  117. * Draw a string.
  118. *
  119. * @param aGal
  120. * @param aText is the text to be drawn.
  121. * @param aPosition is the text object position in world coordinates.
  122. * @param aCursor is the current text position (for multiple text blocks within a single text
  123. * object, such as a run of superscript characters)
  124. * @param aAttrs are the styling attributes of the text, including its rotation
  125. */
  126. void FONT::Draw( KIGFX::GAL* aGal, const wxString& aText, const VECTOR2I& aPosition,
  127. const VECTOR2I& aCursor, const TEXT_ATTRIBUTES& aAttrs ) const
  128. {
  129. if( !aGal || aText.empty() )
  130. return;
  131. VECTOR2I position( aPosition - aCursor );
  132. // Split multiline strings into separate ones and draw them line by line
  133. wxArrayString strings_list;
  134. std::vector<VECTOR2I> positions;
  135. std::vector<VECTOR2I> extents;
  136. getLinePositions( aText, position, strings_list, positions, extents, aAttrs );
  137. aGal->SetLineWidth( aAttrs.m_StrokeWidth );
  138. for( size_t i = 0; i < strings_list.GetCount(); i++ )
  139. {
  140. drawSingleLineText( aGal, nullptr, strings_list[i], positions[i], aAttrs.m_Size,
  141. aAttrs.m_Angle, aAttrs.m_Mirrored, aPosition, aAttrs.m_Italic );
  142. }
  143. }
  144. /**
  145. * @return position of cursor for drawing next substring
  146. */
  147. VECTOR2I drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
  148. const std::unique_ptr<MARKUP::NODE>& aNode, const VECTOR2I& aPosition,
  149. const KIFONT::FONT* aFont, const VECTOR2I& aSize, const EDA_ANGLE& aAngle,
  150. bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle )
  151. {
  152. VECTOR2I nextPosition = aPosition;
  153. if( aNode )
  154. {
  155. TEXT_STYLE_FLAGS textStyle = aTextStyle;
  156. if( !aNode->is_root() )
  157. {
  158. if( aNode->isSubscript() )
  159. textStyle = TEXT_STYLE::SUBSCRIPT;
  160. else if( aNode->isSuperscript() )
  161. textStyle = TEXT_STYLE::SUPERSCRIPT;
  162. if( aNode->isOverbar() )
  163. textStyle |= TEXT_STYLE::OVERBAR;
  164. if( aNode->has_content() )
  165. {
  166. BOX2I bbox;
  167. nextPosition = aFont->GetTextAsGlyphs( &bbox, aGlyphs, aNode->asWxString(), aSize,
  168. aPosition, aAngle, aMirror, aOrigin,
  169. textStyle );
  170. if( aBoundingBox )
  171. aBoundingBox->Merge( bbox );
  172. }
  173. }
  174. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  175. {
  176. nextPosition = drawMarkup( aBoundingBox, aGlyphs, child, nextPosition, aFont, aSize,
  177. aAngle, aMirror, aOrigin, textStyle );
  178. }
  179. }
  180. return nextPosition;
  181. }
  182. VECTOR2I FONT::drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
  183. const wxString& aText, const VECTOR2I& aPosition, const VECTOR2I& aSize,
  184. const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
  185. TEXT_STYLE_FLAGS aTextStyle ) const
  186. {
  187. MARKUP::MARKUP_PARSER markupParser( TO_UTF8( aText ) );
  188. std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
  189. return ::drawMarkup( aBoundingBox, aGlyphs, root, aPosition, this, aSize, aAngle, aMirror,
  190. aOrigin, aTextStyle );
  191. }
  192. void FONT::drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const wxString& aText,
  193. const VECTOR2I& aPosition, const VECTOR2I& aSize,
  194. const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
  195. bool aItalic ) const
  196. {
  197. if( !aGal )
  198. return;
  199. TEXT_STYLE_FLAGS textStyle = 0;
  200. if( aItalic )
  201. textStyle |= TEXT_STYLE::ITALIC;
  202. std::vector<std::unique_ptr<GLYPH>> glyphs;
  203. (void) drawMarkup( aBoundingBox, &glyphs, aText, aPosition, aSize, aAngle, aMirror, aOrigin,
  204. textStyle );
  205. for( const std::unique_ptr<GLYPH>& glyph : glyphs )
  206. aGal->DrawGlyph( *glyph.get() );
  207. }
  208. VECTOR2I FONT::StringBoundaryLimits( const wxString& aText, const VECTOR2I& aSize, int aThickness,
  209. bool aBold, bool aItalic ) const
  210. {
  211. // TODO do we need to parse every time - have we already parsed?
  212. BOX2I boundingBox;
  213. TEXT_STYLE_FLAGS textStyle = 0;
  214. if( aBold )
  215. textStyle |= TEXT_STYLE::BOLD;
  216. if( aItalic )
  217. textStyle |= TEXT_STYLE::ITALIC;
  218. (void) drawMarkup( &boundingBox, nullptr, aText, VECTOR2I(), aSize, ANGLE_0, false,
  219. VECTOR2I(), textStyle );
  220. if( IsStroke() )
  221. {
  222. // Inflate by a bit more than thickness/2 to catch diacriticals, descenders, etc.
  223. boundingBox.Inflate( KiROUND( aThickness * 0.75 ) );
  224. }
  225. else if( IsOutline() )
  226. {
  227. // Outline fonts have thickness built in
  228. }
  229. return boundingBox.GetSize();
  230. }
  231. VECTOR2I FONT::boundingBoxSingleLine( BOX2I* aBBox, const wxString& aText,
  232. const VECTOR2I& aPosition, const VECTOR2I& aSize,
  233. bool aItalic ) const
  234. {
  235. TEXT_STYLE_FLAGS textStyle = 0;
  236. if( aItalic )
  237. textStyle |= TEXT_STYLE::ITALIC;
  238. VECTOR2I extents = drawMarkup( aBBox, nullptr, aText, aPosition, aSize, ANGLE_0, false,
  239. VECTOR2I(), textStyle );
  240. return extents;
  241. }
  242. /*
  243. * Break marked-up text into "words". In this context, a "word" is EITHER a run of marked-up
  244. * text (subscript, superscript or overbar), OR a run of non-marked-up text separated by spaces.
  245. */
  246. void wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords,
  247. const std::unique_ptr<MARKUP::NODE>& aNode, const KIFONT::FONT* aFont,
  248. const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle )
  249. {
  250. TEXT_STYLE_FLAGS textStyle = aTextStyle;
  251. if( !aNode->is_root() )
  252. {
  253. wxChar escapeChar = 0;
  254. if( aNode->isSubscript() )
  255. {
  256. escapeChar = '_';
  257. textStyle = TEXT_STYLE::SUBSCRIPT;
  258. }
  259. else if( aNode->isSuperscript() )
  260. {
  261. escapeChar = '^';
  262. textStyle = TEXT_STYLE::SUPERSCRIPT;
  263. }
  264. if( aNode->isOverbar() )
  265. {
  266. escapeChar = '~';
  267. textStyle |= TEXT_STYLE::OVERBAR;
  268. }
  269. if( escapeChar )
  270. {
  271. wxString word = wxString::Format( wxT( "%c{" ), escapeChar );
  272. int width = 0;
  273. if( aNode->has_content() )
  274. {
  275. VECTOR2I next = aFont->GetTextAsGlyphs( nullptr, nullptr, aNode->asWxString(),
  276. aSize, { 0, 0 }, ANGLE_0, false, { 0, 0 },
  277. textStyle );
  278. word += aNode->asWxString();
  279. width += next.x;
  280. }
  281. std::vector<std::pair<wxString, int>> childWords;
  282. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  283. wordbreakMarkup( &childWords, child, aFont, aSize, textStyle );
  284. for( const std::pair<wxString, int>& childWord : childWords )
  285. {
  286. word += childWord.first;
  287. width += childWord.second;
  288. }
  289. word += wxT( "}" );
  290. aWords->emplace_back( std::make_pair( word, width ) );
  291. return;
  292. }
  293. else
  294. {
  295. wxString space( wxS( " " ) );
  296. wxString textRun = aNode->asWxString();
  297. wxArrayString words;
  298. wxStringSplit( textRun, words, ' ' );
  299. if( textRun.EndsWith( wxS( " " ) ) )
  300. words.Add( wxS( " " ) );
  301. for( size_t ii = 0; ii < words.size(); ++ii )
  302. {
  303. int w = aFont->GetTextAsGlyphs( nullptr, nullptr, words[ii], aSize, { 0, 0 },
  304. ANGLE_0, false, { 0, 0 }, textStyle ).x;
  305. aWords->emplace_back( std::make_pair( words[ii], w ) );
  306. }
  307. }
  308. }
  309. for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
  310. wordbreakMarkup( aWords, child, aFont, aSize, textStyle );
  311. }
  312. void FONT::wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords, const wxString& aText,
  313. const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle ) const
  314. {
  315. MARKUP::MARKUP_PARSER markupParser( TO_UTF8( aText ) );
  316. std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
  317. ::wordbreakMarkup( aWords, root, this, aSize, aTextStyle );
  318. }
  319. /*
  320. * This is a highly simplified line-breaker. KiCad is an EDA tool, not a word processor.
  321. *
  322. * 1) It breaks only on spaces. If you type a word wider than the column width then you get
  323. * overflow.
  324. * 2) It treats runs of formatted text (superscript, subscript, overbar) as single words.
  325. * 3) It does not perform justification.
  326. *
  327. * The results of the linebreaking are the addition of \n in the text. It is presumed that this
  328. * function is called on m_shownText (or equivalent) rather than the original source text.
  329. */
  330. void FONT::LinebreakText( wxString& aText, int aColumnWidth, const VECTOR2I& aSize, int aThickness,
  331. bool aBold, bool aItalic ) const
  332. {
  333. TEXT_STYLE_FLAGS textStyle = 0;
  334. if( aBold )
  335. textStyle |= TEXT_STYLE::BOLD;
  336. if( aItalic )
  337. textStyle |= TEXT_STYLE::ITALIC;
  338. int spaceWidth = GetTextAsGlyphs( nullptr, nullptr, wxS( " " ), aSize, VECTOR2I(), ANGLE_0,
  339. false, VECTOR2I(), textStyle ).x;
  340. wxArrayString textLines;
  341. wxStringSplit( aText, textLines, '\n' );
  342. aText = wxEmptyString;
  343. for( size_t ii = 0; ii < textLines.Count(); ++ii )
  344. {
  345. bool virginLine = true;
  346. int lineWidth = 0;
  347. std::vector<std::pair<wxString, int>> words;
  348. wordbreakMarkup( &words, textLines[ii], aSize, textStyle );
  349. for( size_t jj = 0; jj < words.size(); /* advance in loop */ )
  350. {
  351. if( virginLine )
  352. {
  353. // First word is always placed, even when wider than columnWidth.
  354. aText += words[jj].first;
  355. lineWidth += words[jj].second;
  356. jj++;
  357. virginLine = false;
  358. }
  359. else if( lineWidth + spaceWidth + words[jj].second < aColumnWidth - aThickness )
  360. {
  361. aText += " " + words[jj].first;
  362. lineWidth += spaceWidth + words[jj].second;
  363. jj++;
  364. }
  365. else
  366. {
  367. aText += '\n';
  368. lineWidth = 0;
  369. virginLine = true;
  370. }
  371. }
  372. // Add the newlines back onto the string
  373. if( ii != ( textLines.Count() - 1 ) )
  374. aText += '\n';
  375. }
  376. }