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 // Build charproc stream: first specify width and bbox (d1 operator) then stroke path.
222 double kerningFactor = ADVANCED_CFG::GetCfg().m_PDFStrokeFontKerningFactor;
223
224 if( kerningFactor <= 0.0 )
225 kerningFactor = 1.0;
226
227 std::string strokes = buildGlyphStream( glyph, m_unitsPerEm, invertY, m_isBold );
228 data.m_width = size.x * m_unitsPerEm * kerningFactor;
229 data.m_stream = fmt::format( "{:.3f} 0 {:.3f} {:.3f} {:.3f} {:.3f} d1 {}",
230 data.m_width,
231 data.m_minX, data.m_minY, data.m_maxX, data.m_maxY,
232 strokes );
233
234 m_widths[code] = data.m_width;
235
236 m_bboxMinX = std::min( m_bboxMinX, data.m_minX );
237 m_bboxMinY = std::min( m_bboxMinY, data.m_minY );
238 m_bboxMaxX = std::max( m_bboxMaxX, data.m_maxX );
239 m_bboxMaxY = std::max( m_bboxMaxY, data.m_maxY );
240
241 m_glyphs.push_back( std::move( data ) );
242 m_unicodeToCode.emplace( aCode, code );
243
244 return code;
245}
246
247
248int PDF_STROKE_FONT_SUBSET::CodeForGlyph( wxUniChar aCode ) const
249{
250 auto it = m_unicodeToCode.find( aCode );
251
252 if( it != m_unicodeToCode.end() )
253 return it->second;
254
255 return -1;
256}
257
258
260{
261 return m_nextCode >= MAX_SIMPLE_FONT_CODES;
262}
263
264
266{
267 return static_cast<int>( m_glyphs.size() );
268}
269
270
272{
273 return 0;
274}
275
276
278{
279 return std::max( 0, m_lastCode );
280}
281
282
284{
285 int first = FirstChar();
286 int last = LastChar();
287
288 fmt::memory_buffer buffer;
289 fmt::format_to( std::back_inserter( buffer ), "[ {} ", first );
290
291 for( int code = first; code <= last; ++code )
292 {
293 const GLYPH* glyph = glyphForCode( code );
294
295 if( glyph )
296 fmt::format_to( std::back_inserter( buffer ), "/{} ", glyph->m_name );
297 else
298 fmt::format_to( std::back_inserter( buffer ), "/.notdef " );
299 }
300
301 fmt::format_to( std::back_inserter( buffer ), "]" );
302 return std::string( buffer.data(), buffer.size() );
303}
304
305
307{
308 int first = FirstChar();
309 int last = LastChar();
310
311 fmt::memory_buffer buffer;
312 fmt::format_to( std::back_inserter( buffer ), "[" );
313
314 for( int code = first; code <= last; ++code )
315 fmt::format_to( std::back_inserter( buffer ), " {:g}", m_widths[code] );
316
317 fmt::format_to( std::back_inserter( buffer ), " ]" );
318 return std::string( buffer.data(), buffer.size() );
319}
320
321
323{
324 size_t mappingCount = 0;
325
326 for( const GLYPH& glyph : m_glyphs )
327 {
328 if( glyph.m_code == 0 )
329 continue;
330
331 ++mappingCount;
332 }
333
334 fmt::memory_buffer buffer;
335
336 fmt::format_to( std::back_inserter( buffer ), "/CIDInit /ProcSet findresource begin\n" );
337 fmt::format_to( std::back_inserter( buffer ), "12 dict begin\n" );
338 fmt::format_to( std::back_inserter( buffer ), "begincmap\n" );
339 fmt::format_to( std::back_inserter( buffer ), "/CIDSystemInfo << /Registry (KiCad) /Ordering (StrokeFont) /Supplement 0 >> def\n" );
340 fmt::format_to( std::back_inserter( buffer ), "/CMapName /{} def\n", m_cmapName );
341 fmt::format_to( std::back_inserter( buffer ), "/CMapType 2 def\n" );
342 fmt::format_to( std::back_inserter( buffer ), "1 begincodespacerange\n" );
343 fmt::format_to( std::back_inserter( buffer ), "<00> <FF>\n" );
344 fmt::format_to( std::back_inserter( buffer ), "endcodespacerange\n" );
345
346 fmt::format_to( std::back_inserter( buffer ), "{} beginbfchar\n", mappingCount );
347
348 for( const GLYPH& glyph : m_glyphs )
349 {
350 if( glyph.m_code == 0 )
351 continue;
352
353 fmt::format_to( std::back_inserter( buffer ), "<{:02X}> <{}>\n", glyph.m_code,
354 formatUnicodeHex( static_cast<uint32_t>( glyph.m_unicode ) ) );
355 }
356
357 fmt::format_to( std::back_inserter( buffer ), "endbfchar\n" );
358 fmt::format_to( std::back_inserter( buffer ), "endcmap\n" );
359 fmt::format_to( std::back_inserter( buffer ), "CMapName currentdict /CMap defineresource pop\n" );
360 fmt::format_to( std::back_inserter( buffer ), "end\n" );
361 fmt::format_to( std::back_inserter( buffer ), "end\n" );
362
363 return std::string( buffer.data(), buffer.size() );
364}
365
366
368{
369 int value = static_cast<int>( aCode );
370
371 if( value < ' ' )
372 return static_cast<int>( '?' ) - ' ';
373
374 int index = value - ' ';
375 int count = static_cast<int>( m_font ? m_font->GetGlyphCount() : 0 );
376
377 if( index < 0 || index >= count )
378 return static_cast<int>( '?' ) - ' ';
379
380 return index;
381}
382
383
384std::string PDF_STROKE_FONT_SUBSET::makeGlyphName( int aCode ) const
385{
386 return fmt::format( "g{:02X}", aCode );
387}
388
389
391{
392 for( const GLYPH& glyph : m_glyphs )
393 {
394 if( glyph.m_code == aCode )
395 return &glyph;
396 }
397
398 return nullptr;
399}
400
401
403 m_font( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) ),
404 m_unitsPerEm( 1000.0 ),
406{
407 Reset();
408}
409
410
412{
413 m_styleGroups.clear();
415
416 if( !m_font )
417 m_font.reset( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) );
418}
419
420
422{
423 unsigned key = styleKey( aBold, aItalic );
424 return m_styleGroups[key];
425}
426
427void PDF_STROKE_FONT_MANAGER::EncodeString( const wxString& aText,
428 std::vector<PDF_STROKE_FONT_RUN>* aRuns,
429 bool aBold, bool aItalic )
430{
431 if( !aRuns )
432 return;
433
434 aRuns->clear();
435
436 if( aText.empty() )
437 return;
438
439 PDF_STROKE_FONT_SUBSET* currentSubset = nullptr;
440 std::string currentBytes;
441
442 for( wxUniChar ch : aText )
443 {
444 PDF_STROKE_FONT_SUBSET* subset = ensureSubsetForGlyph( ch, aBold, aItalic );
445
446 if( !subset )
447 continue;
448
449 int code = subset->EnsureGlyph( ch );
450
451 if( code < 0 )
452 continue;
453
454 if( subset != currentSubset )
455 {
456 if( !currentBytes.empty() && currentSubset )
457 aRuns->push_back( { currentSubset, currentBytes, aBold, aItalic } );
458
459 currentSubset = subset;
460 currentBytes.clear();
461 }
462
463 currentBytes.push_back( static_cast<char>( code ) );
464 }
465
466 if( !currentBytes.empty() && currentSubset )
467 aRuns->push_back( { currentSubset, std::move( currentBytes ), aBold, aItalic } );
468}
469
471{
472 STYLE_GROUP& group = groupFor( aBold, aItalic );
473
474 for( const std::unique_ptr<PDF_STROKE_FONT_SUBSET>& subset : group.subsets )
475 {
476 if( subset->Contains( aCode ) )
477 return subset.get();
478 }
479
480 for( const std::unique_ptr<PDF_STROKE_FONT_SUBSET>& subset : group.subsets )
481 {
482 if( subset->IsFull() )
483 continue;
484
485 if( subset->EnsureGlyph( aCode ) >= 0 )
486 return subset.get();
487 }
488
489 unsigned subsetIndex = m_nextSubsetIndex++;
490 auto newSubset = std::make_unique<PDF_STROKE_FONT_SUBSET>( m_font.get(), m_unitsPerEm, subsetIndex, aBold, aItalic );
491 PDF_STROKE_FONT_SUBSET* subsetPtr = newSubset.get();
492 subsetPtr->EnsureGlyph( aCode );
493 group.subsets.emplace_back( std::move( newSubset ) );
494 return subsetPtr;
495}
496
497std::vector<PDF_STROKE_FONT_SUBSET*> PDF_STROKE_FONT_MANAGER::AllSubsets() const
498{
499 std::vector<PDF_STROKE_FONT_SUBSET*> out;
500
501 for( const auto& [key, group] : m_styleGroups )
502 {
503 (void) key;
504 for( const auto& up : group.subsets )
505 out.push_back( up.get() );
506 }
507 return out;
508}
509
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_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:694