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