KiCad PCB EDA Suite
font.cpp
Go to the documentation of this file.
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
27#include <wx/font.h>
28#include <string_utils.h>
30#include <font/stroke_font.h>
31#include <font/outline_font.h>
32#include <trigo.h>
33#include <markup_parser.h>
34
35// The "official" name of the building Kicad stroke font (always existing)
37#include "macros.h"
38
39
40// markup_parser.h includes pegtl.hpp which includes windows.h... which leaks #define DrawText
41#undef DrawText
42
43
44using namespace KIFONT;
45
46FONT* FONT::s_defaultFont = nullptr;
47
48std::map< std::tuple<wxString, bool, bool>, FONT*> FONT::s_fontMap;
49
50
51FONT::FONT()
52{
53}
54
55
57{
58 if( !s_defaultFont )
59 s_defaultFont = STROKE_FONT::LoadFont( wxEmptyString );
60
61 return s_defaultFont;
62}
63
64
65FONT* FONT::GetFont( const wxString& aFontName, bool aBold, bool aItalic )
66{
67 if( aFontName.empty() || aFontName.StartsWith( KICAD_FONT_NAME ) )
68 return getDefaultFont();
69
70 std::tuple<wxString, bool, bool> key = { aFontName, aBold, aItalic };
71
72 FONT* font = s_fontMap[key];
73
74 if( !font )
75 font = OUTLINE_FONT::LoadFont( aFontName, aBold, aItalic );
76
77 if( !font )
78 font = getDefaultFont();
79
80 s_fontMap[key] = font;
81
82 return font;
83}
84
85
86bool FONT::IsStroke( const wxString& aFontName )
87{
88 // This would need a more complex implementation if we ever support more stroke fonts
89 // than the KiCad Font.
90 return aFontName == _( "Default Font" ) || aFontName == KICAD_FONT_NAME;
91}
92
93
94void FONT::getLinePositions( const wxString& aText, const VECTOR2I& aPosition,
95 wxArrayString& aTextLines, std::vector<VECTOR2I>& aPositions,
96 std::vector<VECTOR2I>& aExtents, const TEXT_ATTRIBUTES& aAttrs ) const
97{
98 wxStringSplit( aText, aTextLines, '\n' );
99 int lineCount = aTextLines.Count();
100 aPositions.reserve( lineCount );
101
102 int interline = GetInterline( aAttrs.m_Size.y, aAttrs.m_LineSpacing );
103 int height = 0;
104
105 for( int i = 0; i < lineCount; i++ )
106 {
107 VECTOR2I pos( aPosition.x, aPosition.y + i * interline );
108 VECTOR2I end = boundingBoxSingleLine( nullptr, aTextLines[i], pos, aAttrs.m_Size,
109 aAttrs.m_Italic );
110 VECTOR2I bBox( end - pos );
111
112 aExtents.push_back( bBox );
113
114 if( i == 0 )
115 height += ( aAttrs.m_Size.y * 1.17 ); // 1.17 is a fudge to match 6.0 positioning
116 else
117 height += interline;
118 }
119
120 VECTOR2I offset( 0, 0 );
121 offset.y += aAttrs.m_Size.y;
122
123 switch( aAttrs.m_Valign )
124 {
125 case GR_TEXT_V_ALIGN_TOP: break;
126 case GR_TEXT_V_ALIGN_CENTER: offset.y -= height / 2; break;
127 case GR_TEXT_V_ALIGN_BOTTOM: offset.y -= height; break;
128 }
129
130 for( int i = 0; i < lineCount; i++ )
131 {
132 VECTOR2I lineSize = aExtents.at( i );
133 VECTOR2I lineOffset( offset );
134
135 lineOffset.y += i * interline;
136
137 switch( aAttrs.m_Halign )
138 {
139 case GR_TEXT_H_ALIGN_LEFT: break;
140 case GR_TEXT_H_ALIGN_CENTER: lineOffset.x = -lineSize.x / 2; break;
141 case GR_TEXT_H_ALIGN_RIGHT: lineOffset.x = -lineSize.x; break;
142 }
143
144 aPositions.push_back( aPosition + lineOffset );
145 }
146}
147
148
159void FONT::Draw( KIGFX::GAL* aGal, const wxString& aText, const VECTOR2I& aPosition,
160 const VECTOR2I& aCursor, const TEXT_ATTRIBUTES& aAttrs ) const
161{
162 if( !aGal || aText.empty() )
163 return;
164
165 VECTOR2I position( aPosition - aCursor );
166
167 // Split multiline strings into separate ones and draw them line by line
168 wxArrayString strings_list;
169 std::vector<VECTOR2I> positions;
170 std::vector<VECTOR2I> extents;
171
172 getLinePositions( aText, position, strings_list, positions, extents, aAttrs );
173
174 aGal->SetLineWidth( aAttrs.m_StrokeWidth );
175
176 for( size_t i = 0; i < strings_list.GetCount(); i++ )
177 {
178 drawSingleLineText( aGal, nullptr, strings_list[i], positions[i], aAttrs.m_Size,
179 aAttrs.m_Angle, aAttrs.m_Mirrored, aPosition, aAttrs.m_Italic,
180 aAttrs.m_Underlined );
181 }
182}
183
184
188VECTOR2I drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
189 const std::unique_ptr<MARKUP::NODE>& aNode, const VECTOR2I& aPosition,
190 const KIFONT::FONT* aFont, const VECTOR2I& aSize, const EDA_ANGLE& aAngle,
191 bool aMirror, const VECTOR2I& aOrigin, TEXT_STYLE_FLAGS aTextStyle )
192{
193 VECTOR2I nextPosition = aPosition;
194
195 if( aNode )
196 {
197 TEXT_STYLE_FLAGS textStyle = aTextStyle;
198
199 if( !aNode->is_root() )
200 {
201 if( aNode->isSubscript() )
202 textStyle = TEXT_STYLE::SUBSCRIPT;
203 else if( aNode->isSuperscript() )
204 textStyle = TEXT_STYLE::SUPERSCRIPT;
205
206 if( aNode->isOverbar() )
207 textStyle |= TEXT_STYLE::OVERBAR;
208
209 if( aNode->has_content() )
210 {
211 BOX2I bbox;
212
213 nextPosition = aFont->GetTextAsGlyphs( &bbox, aGlyphs, aNode->asWxString(), aSize,
214 aPosition, aAngle, aMirror, aOrigin,
215 textStyle );
216
217 if( aBoundingBox )
218 aBoundingBox->Merge( bbox );
219 }
220 }
221
222 for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
223 {
224 nextPosition = drawMarkup( aBoundingBox, aGlyphs, child, nextPosition, aFont, aSize,
225 aAngle, aMirror, aOrigin, textStyle );
226 }
227 }
228
229 return nextPosition;
230}
231
232
233VECTOR2I FONT::drawMarkup( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
234 const wxString& aText, const VECTOR2I& aPosition, const VECTOR2I& aSize,
235 const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
236 TEXT_STYLE_FLAGS aTextStyle ) const
237{
238 MARKUP::MARKUP_PARSER markupParser( TO_UTF8( aText ) );
239 std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
240
241 return ::drawMarkup( aBoundingBox, aGlyphs, root, aPosition, this, aSize, aAngle, aMirror,
242 aOrigin, aTextStyle );
243}
244
245
246void FONT::drawSingleLineText( KIGFX::GAL* aGal, BOX2I* aBoundingBox, const wxString& aText,
247 const VECTOR2I& aPosition, const VECTOR2I& aSize,
248 const EDA_ANGLE& aAngle, bool aMirror, const VECTOR2I& aOrigin,
249 bool aItalic, bool aUnderline ) const
250{
251 if( !aGal )
252 return;
253
254 TEXT_STYLE_FLAGS textStyle = 0;
255
256 if( aItalic )
257 textStyle |= TEXT_STYLE::ITALIC;
258
259 if( aUnderline )
260 textStyle |= TEXT_STYLE::UNDERLINE;
261
262 std::vector<std::unique_ptr<GLYPH>> glyphs;
263
264 (void) drawMarkup( aBoundingBox, &glyphs, aText, aPosition, aSize, aAngle, aMirror, aOrigin,
265 textStyle );
266
267 aGal->DrawGlyphs( glyphs );
268}
269
270
271VECTOR2I FONT::StringBoundaryLimits( const wxString& aText, const VECTOR2I& aSize, int aThickness,
272 bool aBold, bool aItalic ) const
273{
274 // TODO do we need to parse every time - have we already parsed?
276 TEXT_STYLE_FLAGS textStyle = 0;
277
278 if( aBold )
279 textStyle |= TEXT_STYLE::BOLD;
280
281 if( aItalic )
282 textStyle |= TEXT_STYLE::ITALIC;
283
284 (void) drawMarkup( &boundingBox, nullptr, aText, VECTOR2I(), aSize, ANGLE_0, false,
285 VECTOR2I(), textStyle );
286
287 if( IsStroke() )
288 {
289 // Inflate by a bit more than thickness/2 to catch diacriticals, descenders, etc.
290 boundingBox.Inflate( KiROUND( aThickness * 0.75 ) );
291 }
292 else if( IsOutline() )
293 {
294 // Outline fonts have thickness built in
295 }
296
297 return boundingBox.GetSize();
298}
299
300
301VECTOR2I FONT::boundingBoxSingleLine( BOX2I* aBBox, const wxString& aText,
302 const VECTOR2I& aPosition, const VECTOR2I& aSize,
303 bool aItalic ) const
304{
305 TEXT_STYLE_FLAGS textStyle = 0;
306
307 if( aItalic )
308 textStyle |= TEXT_STYLE::ITALIC;
309
310 VECTOR2I extents = drawMarkup( aBBox, nullptr, aText, aPosition, aSize, ANGLE_0, false,
311 VECTOR2I(), textStyle );
312
313 return extents;
314}
315
316
317/*
318 * Break marked-up text into "words". In this context, a "word" is EITHER a run of marked-up
319 * text (subscript, superscript or overbar), OR a run of non-marked-up text separated by spaces.
320 */
321void wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords,
322 const std::unique_ptr<MARKUP::NODE>& aNode, const KIFONT::FONT* aFont,
323 const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle )
324{
325 TEXT_STYLE_FLAGS textStyle = aTextStyle;
326
327 if( !aNode->is_root() )
328 {
329 wxChar escapeChar = 0;
330
331 if( aNode->isSubscript() )
332 {
333 escapeChar = '_';
334 textStyle = TEXT_STYLE::SUBSCRIPT;
335 }
336 else if( aNode->isSuperscript() )
337 {
338 escapeChar = '^';
339 textStyle = TEXT_STYLE::SUPERSCRIPT;
340 }
341
342 if( aNode->isOverbar() )
343 {
344 escapeChar = '~';
345 textStyle |= TEXT_STYLE::OVERBAR;
346 }
347
348 if( escapeChar )
349 {
350 wxString word = wxString::Format( wxT( "%c{" ), escapeChar );
351 int width = 0;
352
353 if( aNode->has_content() )
354 {
355 VECTOR2I next = aFont->GetTextAsGlyphs( nullptr, nullptr, aNode->asWxString(),
356 aSize, { 0, 0 }, ANGLE_0, false, { 0, 0 },
357 textStyle );
358 word += aNode->asWxString();
359 width += next.x;
360 }
361
362 std::vector<std::pair<wxString, int>> childWords;
363
364 for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
365 wordbreakMarkup( &childWords, child, aFont, aSize, textStyle );
366
367 for( const std::pair<wxString, int>& childWord : childWords )
368 {
369 word += childWord.first;
370 width += childWord.second;
371 }
372
373 word += wxT( "}" );
374 aWords->emplace_back( std::make_pair( word, width ) );
375 return;
376 }
377 else
378 {
379 wxString textRun = aNode->asWxString();
380 wxArrayString words;
381
382 wxStringSplit( textRun, words, ' ' );
383
384 if( textRun.EndsWith( wxS( " " ) ) )
385 words.Add( wxS( " " ) );
386
387 for( size_t ii = 0; ii < words.size(); ++ii )
388 {
389 int w = aFont->GetTextAsGlyphs( nullptr, nullptr, words[ii], aSize, { 0, 0 },
390 ANGLE_0, false, { 0, 0 }, textStyle ).x;
391
392 aWords->emplace_back( std::make_pair( words[ii], w ) );
393 }
394 }
395 }
396
397 for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
398 wordbreakMarkup( aWords, child, aFont, aSize, textStyle );
399}
400
401
402void FONT::wordbreakMarkup( std::vector<std::pair<wxString, int>>* aWords, const wxString& aText,
403 const VECTOR2I& aSize, TEXT_STYLE_FLAGS aTextStyle ) const
404{
405 MARKUP::MARKUP_PARSER markupParser( TO_UTF8( aText ) );
406 std::unique_ptr<MARKUP::NODE> root = markupParser.Parse();
407
408 ::wordbreakMarkup( aWords, root, this, aSize, aTextStyle );
409}
410
411
412/*
413 * This is a highly simplified line-breaker. KiCad is an EDA tool, not a word processor.
414 *
415 * 1) It breaks only on spaces. If you type a word wider than the column width then you get
416 * overflow.
417 * 2) It treats runs of formatted text (superscript, subscript, overbar) as single words.
418 * 3) It does not perform justification.
419 *
420 * The results of the linebreaking are the addition of \n in the text. It is presumed that this
421 * function is called on m_shownText (or equivalent) rather than the original source text.
422 */
423void FONT::LinebreakText( wxString& aText, int aColumnWidth, const VECTOR2I& aSize, int aThickness,
424 bool aBold, bool aItalic ) const
425{
426 TEXT_STYLE_FLAGS textStyle = 0;
427
428 if( aBold )
429 textStyle |= TEXT_STYLE::BOLD;
430
431 if( aItalic )
432 textStyle |= TEXT_STYLE::ITALIC;
433
434 int spaceWidth = GetTextAsGlyphs( nullptr, nullptr, wxS( " " ), aSize, VECTOR2I(), ANGLE_0,
435 false, VECTOR2I(), textStyle ).x;
436
437 wxArrayString textLines;
438 wxStringSplit( aText, textLines, '\n' );
439
440 aText = wxEmptyString;
441
442 for( size_t ii = 0; ii < textLines.Count(); ++ii )
443 {
444 bool virginLine = true;
445 int lineWidth = 0;
446 std::vector<std::pair<wxString, int>> words;
447
448 wordbreakMarkup( &words, textLines[ii], aSize, textStyle );
449
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
459 virginLine = false;
460 }
461 else if( lineWidth + spaceWidth + words[jj].second < aColumnWidth - aThickness )
462 {
463 aText += " " + words[jj].first;
464 lineWidth += spaceWidth + words[jj].second;
465 jj++;
466 }
467 else
468 {
469 aText += '\n';
470
471 lineWidth = 0;
472 virginLine = true;
473 }
474 }
475
476 // Add the newlines back onto the string
477 if( ii != ( textLines.Count() - 1 ) )
478 aText += '\n';
479 }
480}
481
482
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:506
const Vec & GetSize() const
Definition: box2.h:179
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:588
FONT is an abstract base class for both outline and stroke fonts.
Definition: font.h:105
virtual double GetInterline(double aGlyphHeight, double aLineSpacing=1.0) const =0
Compute the distance (interline) between 2 lines of text (for multiline texts).
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false)
Definition: font.cpp:65
void Draw(KIGFX::GAL *aGal, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aCursor, const TEXT_ATTRIBUTES &aAttrs) const
Draw a string.
Definition: font.cpp:159
void drawSingleLineText(KIGFX::GAL *aGal, BOX2I *aBoundingBox, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aAngle, bool aMirror, const VECTOR2I &aOrigin, bool aItalic, bool aUnderline) const
Draws a single line of text.
Definition: font.cpp:246
virtual bool IsStroke() const
Definition: font.h:112
static FONT * s_defaultFont
Definition: font.h:266
void getLinePositions(const wxString &aText, const VECTOR2I &aPosition, wxArrayString &aTextLines, std::vector< VECTOR2I > &aPositions, std::vector< VECTOR2I > &aExtents, const TEXT_ATTRIBUTES &aAttrs) const
Definition: font.cpp:94
VECTOR2I boundingBoxSingleLine(BOX2I *aBBox, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aSize, bool aItalic) const
Computes the bounding box for a single line of text.
Definition: font.cpp:301
void wordbreakMarkup(std::vector< std::pair< wxString, int > > *aWords, const wxString &aText, const VECTOR2I &aSize, TEXT_STYLE_FLAGS aTextStyle) const
Factor that determines the pitch between 2 lines.
Definition: font.cpp:402
virtual bool IsOutline() const
Definition: font.h:113
VECTOR2I drawMarkup(BOX2I *aBoundingBox, std::vector< std::unique_ptr< GLYPH > > *aGlyphs, const wxString &aText, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aAngle, bool aMirror, const VECTOR2I &aOrigin, TEXT_STYLE_FLAGS aTextStyle) const
Definition: font.cpp:233
static std::map< std::tuple< wxString, bool, bool >, FONT * > s_fontMap
Definition: font.h:268
VECTOR2I StringBoundaryLimits(const wxString &aText, const VECTOR2I &aSize, int aThickness, bool aBold, bool aItalic) const
Compute the boundary limits of aText (the bounding box of all shapes).
Definition: font.cpp:271
virtual VECTOR2I GetTextAsGlyphs(BOX2I *aBBox, std::vector< std::unique_ptr< GLYPH > > *aGlyphs, const wxString &aText, const VECTOR2I &aSize, const VECTOR2I &aPosition, const EDA_ANGLE &aAngle, bool aMirror, const VECTOR2I &aOrigin, TEXT_STYLE_FLAGS aTextStyle) const =0
Convert text string to an array of GLYPHs.
static FONT * getDefaultFont()
Definition: font.cpp:56
void LinebreakText(wxString &aText, int aColumnWidth, const VECTOR2I &aGlyphSize, int aThickness, bool aBold, bool aItalic) const
Insert characters into text to ensure that no lines are wider than aColumnWidth.
Definition: font.cpp:423
static OUTLINE_FONT * LoadFont(const wxString &aFontFileName, bool aBold, bool aItalic)
Load an outline font.
static STROKE_FONT * LoadFont(const wxString &aFontName)
Load a stroke font.
Definition: stroke_font.cpp:72
Abstract interface for drawing on a 2D-surface.
virtual void SetLineWidth(float aLineWidth)
Set the line width.
virtual void DrawGlyphs(const std::vector< std::unique_ptr< KIFONT::GLYPH > > &aGlyphs)
Draw polygons representing font glyphs.
std::unique_ptr< NODE > Parse()
GR_TEXT_H_ALIGN_T m_Halign
GR_TEXT_V_ALIGN_T m_Valign
#define _(s)
static constexpr EDA_ANGLE & ANGLE_0
Definition: eda_angle.h:412
VECTOR2I drawMarkup(BOX2I *aBoundingBox, std::vector< std::unique_ptr< GLYPH > > *aGlyphs, const std::unique_ptr< MARKUP::NODE > &aNode, const VECTOR2I &aPosition, const KIFONT::FONT *aFont, const VECTOR2I &aSize, const EDA_ANGLE &aAngle, bool aMirror, const VECTOR2I &aOrigin, TEXT_STYLE_FLAGS aTextStyle)
Definition: font.cpp:188
void wordbreakMarkup(std::vector< std::pair< wxString, int > > *aWords, const std::unique_ptr< MARKUP::NODE > &aNode, const KIFONT::FONT *aFont, const VECTOR2I &aSize, TEXT_STYLE_FLAGS aTextStyle)
Definition: font.cpp:321
@ BOLD
Definition: font.h:45
@ SUBSCRIPT
Definition: font.h:47
@ OVERBAR
Definition: font.h:49
@ UNDERLINE
Definition: font.h:50
@ ITALIC
Definition: font.h:46
@ SUPERSCRIPT
Definition: font.h:48
unsigned int TEXT_STYLE_FLAGS
Definition: font.h:63
#define KICAD_FONT_NAME
This file contains miscellaneous commonly used macros and functions.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
Definition: font.h:100
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
CITER next(CITER it)
Definition: ptree.cpp:126
BOX2I boundingBox(T aObject)
Used by SHAPE_INDEX to get the bounding box of a generic T object.
Definition: shape_index.h:77
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:80
VECTOR2< int > VECTOR2I
Definition: vector2d.h:618