KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pdf_stroke_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 The KiCad Developers, see AUTHORS.txt for contributors.
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
25
26#include <algorithm>
27#include <cstdint>
28#include <limits>
29
30#include <fmt/format.h>
31
32#include <font/glyph.h>
33#include <advanced_config.h>
34
35namespace
36{
37static constexpr int MAX_SIMPLE_FONT_CODES = 256;
38
39// Build the stroked path for a glyph.
40// KiCad's internal stroke font glyph coordinates use an inverted Y axis relative to the
41// PDF coordinate system we are targeting here. We therefore optionally flip Y so text
42// renders upright. A slightly thicker default stroke width (4% of EM) is used to improve
43// legibility at typical plot zoom levels.
44static std::string buildGlyphStream( const KIFONT::STROKE_GLYPH* aGlyph, double aUnitsPerEm,
45 bool aInvertY, bool aBold )
46{
47 if( !aGlyph )
48 return std::string();
49
50 fmt::memory_buffer buffer;
52
53 if( aBold )
54 {
56 if( boldMul < 1.0 ) boldMul = 1.0;
57 factor *= boldMul;
58 }
59
60 if( factor <= 0.0 )
61 factor = 0.04; // fallback safety
62
63 double lw = aUnitsPerEm * factor;
64 fmt::format_to( std::back_inserter( buffer ), "{:.3f} w 1 J 1 j ", lw );
65 auto& cfg = ADVANCED_CFG::GetCfg();
66
67 for( const std::vector<VECTOR2D>& stroke : *aGlyph )
68 {
69 bool firstPoint = true;
70
71 for( const VECTOR2D& point : stroke )
72 {
73 double x = ( point.x + cfg.m_PDFStrokeFontXOffset ) * aUnitsPerEm;
74 double y = point.y * aUnitsPerEm;
75
76 if( aInvertY )
77 {
78 y = -y; // Mirror vertically about baseline (y=0)
79 y += cfg.m_PDFStrokeFontYOffset * aUnitsPerEm;
80 }
81
82 if( firstPoint )
83 {
84 fmt::format_to( std::back_inserter( buffer ), "{:.3f} {:.3f} m ", x, y );
85 firstPoint = false;
86 }
87 else
88 {
89 fmt::format_to( std::back_inserter( buffer ), "{:.3f} {:.3f} l ", x, y );
90 }
91 }
92
93 if( !stroke.empty() )
94 fmt::format_to( std::back_inserter( buffer ), "S " );
95 }
96
97 return std::string( buffer.data(), buffer.size() );
98}
99
100static std::string formatUnicodeHex( uint32_t aCodepoint )
101{
102 if( aCodepoint <= 0xFFFF )
103 return fmt::format( "{:04X}", aCodepoint );
104
105 if( aCodepoint <= 0x10FFFF )
106 {
107 uint32_t value = aCodepoint - 0x10000;
108 uint16_t high = 0xD800 + ( value >> 10 );
109 uint16_t low = 0xDC00 + ( value & 0x3FF );
110 return fmt::format( "{:04X}{:04X}", high, low );
111 }
112
113 return std::string( "003F" );
114}
115} // anonymous namespace
116
117
119 unsigned aSubsetIndex, bool aBold, bool aItalic ) :
120 m_font( aFont ),
121 m_unitsPerEm( aUnitsPerEm ),
122 m_resourceName( fmt::format( "/KiCadStroke{}", aSubsetIndex ) ),
123 m_cmapName( fmt::format( "KiCadStrokeCMap{}", aSubsetIndex ) ),
124 m_widths( MAX_SIMPLE_FONT_CODES, 0.0 ),
125 m_nextCode( 1 ),
126 m_lastCode( 0 ),
127 m_bboxMinX( std::numeric_limits<double>::max() ),
128 m_bboxMinY( std::numeric_limits<double>::max() ),
129 m_bboxMaxX( std::numeric_limits<double>::lowest() ),
130 m_bboxMaxY( std::numeric_limits<double>::lowest() ),
131 m_charProcsHandle( -1 ),
132 m_fontHandle( -1 ),
133 m_toUnicodeHandle( -1 ),
134 m_isBold( aBold ),
135 m_isItalic( aItalic )
136{
137 GLYPH notdef;
138 notdef.m_unicode = 0;
139 notdef.m_code = 0;
140 notdef.m_glyphIndex = -1;
141 notdef.m_name = ".notdef";
142 notdef.m_stream.clear();
143 notdef.m_width = 0.0;
144 notdef.m_minX = 0.0;
145 notdef.m_minY = 0.0;
146 notdef.m_maxX = 0.0;
147 notdef.m_maxY = 0.0;
148 notdef.m_charProcHandle = -1;
149
150 m_glyphs.push_back( notdef );
151 m_bboxMinX = 0.0;
152 m_bboxMinY = 0.0;
153 m_bboxMaxX = 0.0;
154 m_bboxMaxY = 0.0;
155}
156
157
158bool PDF_STROKE_FONT_SUBSET::Contains( wxUniChar aCode ) const
159{
160 return m_unicodeToCode.find( aCode ) != m_unicodeToCode.end();
161}
162
163
165{
166 auto it = m_unicodeToCode.find( aCode );
167
168 if( it != m_unicodeToCode.end() )
169 return it->second;
170
171 if( IsFull() )
172 return -1;
173
174 int glyphIndex = glyphIndexForUnicode( aCode );
175
176 int code = m_nextCode++;
177 m_lastCode = std::max( m_lastCode, code );
178
179 GLYPH data;
180 data.m_unicode = aCode;
181 data.m_code = code;
182 data.m_glyphIndex = glyphIndex;
183 data.m_name = makeGlyphName( code );
184 data.m_charProcHandle = -1;
185
186 const KIFONT::STROKE_GLYPH* glyph = nullptr;
187 BOX2D bbox;
188
189 if( m_font )
190 {
191 glyph = m_font->GetGlyph( glyphIndex );
192 bbox = m_font->GetGlyphBoundingBox( glyphIndex );
193 }
194
195 VECTOR2D origin = bbox.GetOrigin();
196 VECTOR2D size = bbox.GetSize();
197
198 data.m_width = size.x * m_unitsPerEm;
199 data.m_minX = origin.x * m_unitsPerEm;
200 data.m_minY = origin.y * m_unitsPerEm;
201 data.m_maxX = ( origin.x + size.x ) * m_unitsPerEm;
202 data.m_maxY = ( origin.y + size.y ) * m_unitsPerEm;
203
204 // Invert Y so glyphs render upright in PDF coordinate space.
205 bool invertY = true;
206
207 if( invertY )
208 {
209 // Mirror bounding box vertically about baseline.
210 double newMinY = -data.m_maxY;
211 double newMaxY = -data.m_minY;
212 data.m_minY = newMinY;
213 data.m_maxY = newMaxY;
214
215 // Apply Y offset to bounding box to match the offset applied to stroke coordinates
217 data.m_minY += yOffset;
218 data.m_maxY += yOffset;
219 }
220
221 // Apply X offset to bounding box to match the offset applied to stroke coordinates
223 data.m_minX += xOffset;
224 data.m_maxX += xOffset;
225
226 // Expand bbox by half the stroke width so the d1 clipping rect covers the full painted
227 // area, not just the stroke center lines.
229
230 if( m_isBold )
231 {
233
234 if( boldMul < 1.0 )
235 boldMul = 1.0;
236
237 widthFactor *= boldMul;
238 }
239
240 if( widthFactor <= 0.0 )
241 widthFactor = 0.04;
242
243 double halfStroke = m_unitsPerEm * widthFactor / 2.0;
244 data.m_minX -= halfStroke;
245 data.m_minY -= halfStroke;
246 data.m_maxX += halfStroke;
247 data.m_maxY += halfStroke;
248
249 // Build charproc stream: first specify width and bbox (d1 operator) then stroke path.
250 double kerningFactor = ADVANCED_CFG::GetCfg().m_PDFStrokeFontKerningFactor;
251
252 if( kerningFactor <= 0.0 )
253 kerningFactor = 1.0;
254
255 std::string strokes = buildGlyphStream( glyph, m_unitsPerEm, invertY, m_isBold );
256 data.m_width = size.x * m_unitsPerEm * kerningFactor;
257 data.m_stream = fmt::format( "{:.3f} 0 {:.3f} {:.3f} {:.3f} {:.3f} d1 {}",
258 data.m_width,
259 data.m_minX, data.m_minY, data.m_maxX, data.m_maxY,
260 strokes );
261
262 m_widths[code] = data.m_width;
263
264 m_bboxMinX = std::min( m_bboxMinX, data.m_minX );
265 m_bboxMinY = std::min( m_bboxMinY, data.m_minY );
266 m_bboxMaxX = std::max( m_bboxMaxX, data.m_maxX );
267 m_bboxMaxY = std::max( m_bboxMaxY, data.m_maxY );
268
269 m_glyphs.push_back( std::move( data ) );
270 m_unicodeToCode.emplace( aCode, code );
271
272 return code;
273}
274
275
276int PDF_STROKE_FONT_SUBSET::CodeForGlyph( wxUniChar aCode ) const
277{
278 auto it = m_unicodeToCode.find( aCode );
279
280 if( it != m_unicodeToCode.end() )
281 return it->second;
282
283 return -1;
284}
285
286
288{
289 return m_nextCode >= MAX_SIMPLE_FONT_CODES;
290}
291
292
294{
295 return static_cast<int>( m_glyphs.size() );
296}
297
298
300{
301 return 0;
302}
303
304
306{
307 return std::max( 0, m_lastCode );
308}
309
310
312{
313 int first = FirstChar();
314 int last = LastChar();
315
316 fmt::memory_buffer buffer;
317 fmt::format_to( std::back_inserter( buffer ), "[ {} ", first );
318
319 for( int code = first; code <= last; ++code )
320 {
321 const GLYPH* glyph = glyphForCode( code );
322
323 if( glyph )
324 fmt::format_to( std::back_inserter( buffer ), "/{} ", glyph->m_name );
325 else
326 fmt::format_to( std::back_inserter( buffer ), "/.notdef " );
327 }
328
329 fmt::format_to( std::back_inserter( buffer ), "]" );
330 return std::string( buffer.data(), buffer.size() );
331}
332
333
335{
336 int first = FirstChar();
337 int last = LastChar();
338
339 fmt::memory_buffer buffer;
340 fmt::format_to( std::back_inserter( buffer ), "[" );
341
342 for( int code = first; code <= last; ++code )
343 fmt::format_to( std::back_inserter( buffer ), " {:g}", m_widths[code] );
344
345 fmt::format_to( std::back_inserter( buffer ), " ]" );
346 return std::string( buffer.data(), buffer.size() );
347}
348
349
351{
352 size_t mappingCount = 0;
353
354 for( const GLYPH& glyph : m_glyphs )
355 {
356 if( glyph.m_code == 0 )
357 continue;
358
359 ++mappingCount;
360 }
361
362 fmt::memory_buffer buffer;
363
364 fmt::format_to( std::back_inserter( buffer ), "/CIDInit /ProcSet findresource begin\n" );
365 fmt::format_to( std::back_inserter( buffer ), "12 dict begin\n" );
366 fmt::format_to( std::back_inserter( buffer ), "begincmap\n" );
367 fmt::format_to( std::back_inserter( buffer ), "/CIDSystemInfo << /Registry (KiCad) /Ordering (StrokeFont) /Supplement 0 >> def\n" );
368 fmt::format_to( std::back_inserter( buffer ), "/CMapName /{} def\n", m_cmapName );
369 fmt::format_to( std::back_inserter( buffer ), "/CMapType 2 def\n" );
370 fmt::format_to( std::back_inserter( buffer ), "1 begincodespacerange\n" );
371 fmt::format_to( std::back_inserter( buffer ), "<00> <FF>\n" );
372 fmt::format_to( std::back_inserter( buffer ), "endcodespacerange\n" );
373
374 fmt::format_to( std::back_inserter( buffer ), "{} beginbfchar\n", mappingCount );
375
376 for( const GLYPH& glyph : m_glyphs )
377 {
378 if( glyph.m_code == 0 )
379 continue;
380
381 fmt::format_to( std::back_inserter( buffer ), "<{:02X}> <{}>\n", glyph.m_code,
382 formatUnicodeHex( static_cast<uint32_t>( glyph.m_unicode ) ) );
383 }
384
385 fmt::format_to( std::back_inserter( buffer ), "endbfchar\n" );
386 fmt::format_to( std::back_inserter( buffer ), "endcmap\n" );
387 fmt::format_to( std::back_inserter( buffer ), "CMapName currentdict /CMap defineresource pop\n" );
388 fmt::format_to( std::back_inserter( buffer ), "end\n" );
389 fmt::format_to( std::back_inserter( buffer ), "end\n" );
390
391 return std::string( buffer.data(), buffer.size() );
392}
393
394
396{
397 int value = static_cast<int>( aCode );
398
399 if( value < ' ' )
400 return static_cast<int>( '?' ) - ' ';
401
402 int index = value - ' ';
403 int count = static_cast<int>( m_font ? m_font->GetGlyphCount() : 0 );
404
405 if( index < 0 || index >= count )
406 return static_cast<int>( '?' ) - ' ';
407
408 return index;
409}
410
411
412std::string PDF_STROKE_FONT_SUBSET::makeGlyphName( int aCode ) const
413{
414 return fmt::format( "g{:02X}", aCode );
415}
416
417
419{
420 for( const GLYPH& glyph : m_glyphs )
421 {
422 if( glyph.m_code == aCode )
423 return &glyph;
424 }
425
426 return nullptr;
427}
428
429
431 m_font( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) ),
432 m_unitsPerEm( 1000.0 ),
434{
435 Reset();
436}
437
438
440{
441 m_styleGroups.clear();
443
444 if( !m_font )
445 m_font.reset( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) );
446}
447
448
450{
451 unsigned key = styleKey( aBold, aItalic );
452 return m_styleGroups[key];
453}
454
455void PDF_STROKE_FONT_MANAGER::EncodeString( const wxString& aText,
456 std::vector<PDF_STROKE_FONT_RUN>* aRuns,
457 bool aBold, bool aItalic )
458{
459 if( !aRuns )
460 return;
461
462 aRuns->clear();
463
464 if( aText.empty() )
465 return;
466
467 PDF_STROKE_FONT_SUBSET* currentSubset = nullptr;
468 std::string currentBytes;
469
470 for( wxUniChar ch : aText )
471 {
472 PDF_STROKE_FONT_SUBSET* subset = ensureSubsetForGlyph( ch, aBold, aItalic );
473
474 if( !subset )
475 continue;
476
477 int code = subset->EnsureGlyph( ch );
478
479 if( code < 0 )
480 continue;
481
482 if( subset != currentSubset )
483 {
484 if( !currentBytes.empty() && currentSubset )
485 aRuns->push_back( { currentSubset, currentBytes, aBold, aItalic } );
486
487 currentSubset = subset;
488 currentBytes.clear();
489 }
490
491 currentBytes.push_back( static_cast<char>( code ) );
492 }
493
494 if( !currentBytes.empty() && currentSubset )
495 aRuns->push_back( { currentSubset, std::move( currentBytes ), aBold, aItalic } );
496}
497
499{
500 STYLE_GROUP& group = groupFor( aBold, aItalic );
501
502 for( const std::unique_ptr<PDF_STROKE_FONT_SUBSET>& subset : group.subsets )
503 {
504 if( subset->Contains( aCode ) )
505 return subset.get();
506 }
507
508 for( const std::unique_ptr<PDF_STROKE_FONT_SUBSET>& subset : group.subsets )
509 {
510 if( subset->IsFull() )
511 continue;
512
513 if( subset->EnsureGlyph( aCode ) >= 0 )
514 return subset.get();
515 }
516
517 unsigned subsetIndex = m_nextSubsetIndex++;
518 auto newSubset = std::make_unique<PDF_STROKE_FONT_SUBSET>( m_font.get(), m_unitsPerEm, subsetIndex, aBold, aItalic );
519 PDF_STROKE_FONT_SUBSET* subsetPtr = newSubset.get();
520 subsetPtr->EnsureGlyph( aCode );
521 group.subsets.emplace_back( std::move( newSubset ) );
522 return subsetPtr;
523}
524
525std::vector<PDF_STROKE_FONT_SUBSET*> PDF_STROKE_FONT_MANAGER::AllSubsets() const
526{
527 std::vector<PDF_STROKE_FONT_SUBSET*> out;
528
529 for( const auto& [key, group] : m_styleGroups )
530 {
531 (void) key;
532 for( const auto& up : group.subsets )
533 out.push_back( up.get() );
534 }
535 return out;
536}
537
int index
BOX2< VECTOR2D > BOX2D
Definition box2.h:923
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
constexpr const Vec & GetOrigin() const
Definition box2.h:210
constexpr const SizeVec & GetSize() const
Definition box2.h:206
Implement a stroke font drawing.
Definition stroke_font.h:54
static STROKE_FONT * LoadFont(const wxString &aFontName)
Load a stroke font.
std::unique_ptr< KIFONT::STROKE_FONT > m_font
std::map< unsigned, STYLE_GROUP > m_styleGroups
STYLE_GROUP & groupFor(bool aBold, bool aItalic)
PDF_STROKE_FONT_SUBSET * ensureSubsetForGlyph(wxUniChar aCode, bool aBold, bool aItalic)
std::vector< PDF_STROKE_FONT_SUBSET * > AllSubsets() const
void EncodeString(const wxString &aText, std::vector< PDF_STROKE_FONT_RUN > *aRuns, bool aBold=false, bool aItalic=false)
static unsigned styleKey(bool aBold, bool aItalic)
std::string BuildDifferencesArray() const
const KIFONT::STROKE_FONT * m_font
int EnsureGlyph(wxUniChar aCode)
int CodeForGlyph(wxUniChar aCode) const
std::string BuildWidthsArray() const
std::map< wxUniChar, int > m_unicodeToCode
std::string makeGlyphName(int aCode) const
std::vector< double > m_widths
std::string BuildToUnicodeCMap() const
bool Contains(wxUniChar aCode) const
std::vector< GLYPH > m_glyphs
const GLYPH * glyphForCode(int aCode) const
PDF_STROKE_FONT_SUBSET(const KIFONT::STROKE_FONT *aFont, double aUnitsPerEm, unsigned aSubsetIndex, bool aBold, bool aItalic)
int glyphIndexForUnicode(wxUniChar aCode) const
double m_PDFStrokeFontKerningFactor
Kerning (spacing) factor applied to glyph advance (width).
double m_PDFStrokeFontXOffset
Horizontal offset factor applied to stroke font glyph coordinates (in EM units) after to compensate m...
double m_PDFStrokeFontYOffset
Vertical offset factor applied to stroke font glyph coordinates (in EM units) after Y inversion to co...
double m_PDFStrokeFontBoldMultiplier
Multiplier applied to stroke width factor when rendering bold stroke font subsets.
double m_PDFStrokeFontWidthFactor
Stroke font line width factor relative to EM size for PDF stroke fonts.
STL namespace.
VECTOR2< double > VECTOR2D
Definition vector2d.h:686