KiCad PCB EDA Suite
Loading...
Searching...
No Matches
PDF_plotter.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) 1992-2012 Lorenzo Marcantonio, [email protected]
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <algorithm>
22#include <iterator>
23#include <cstdio> // snprintf
24#include <stack>
25#include <ranges>
26#include <vector>
27
28#include <wx/filename.h>
29#include <wx/mstream.h>
30#include <wx/zstream.h>
31#include <wx/wfstream.h>
32#include <wx/datstrm.h>
33#include <wx/tokenzr.h>
34
35#include <advanced_config.h>
36#include <common.h> // ResolveUriByEnvVars
37#include <eda_text.h> // for IsGotoPageHref
38#include <font/font.h>
39#include <gr_text.h>
40#include <core/ignore.h>
41#include <macros.h>
42#include <trace_helpers.h>
43#include <trigo.h>
44#include <string_utils.h>
45#include <core/utf8.h>
46#include <markup_parser.h>
47#include <fmt/format.h>
48#include <fmt/chrono.h>
49#include <fmt/ranges.h>
50
54#include <geometry/shape_rect.h>
56
57#include "callback_gal.h"
58
59
61
62#define GLM_ENABLE_EXPERIMENTAL //for older glm to enable euler angles
63#include <glm/glm.hpp>
64#include <glm/gtx/euler_angles.hpp>
65
66
67std::string PDF_PLOTTER::encodeStringForPlotter( const wxString& aText )
68{
69 // returns a string compatible with PDF string convention from a unicode string.
70 // if the initial text is only ASCII7, return the text between ( and ) for a good readability
71 // if the initial text is no ASCII7, return the text between < and >
72 // and encoded using 16 bits hexa (4 digits) by wide char (unicode 16)
73 std::string result;
74
75 // Is aText only ASCII7 ?
76 bool is_ascii7 = true;
77
78 for( size_t ii = 0; ii < aText.Len(); ii++ )
79 {
80 if( aText[ii] >= 0x7F )
81 {
82 is_ascii7 = false;
83 break;
84 }
85 }
86
87 if( is_ascii7 )
88 {
89 result = '(';
90
91 for( unsigned ii = 0; ii < aText.Len(); ii++ )
92 {
93 unsigned int code = aText[ii];
94
95 // These characters must be escaped
96 switch( code )
97 {
98 case '(':
99 case ')':
100 case '\\':
101 result += '\\';
103
104 default:
105 result += code;
106 break;
107 }
108 }
109
110 result += ')';
111 }
112 else
113 {
114 result = "<FEFF";
115
116 for( size_t ii = 0; ii < aText.Len(); ii++ )
117 {
118 unsigned int code = aText[ii];
119 result += fmt::format("{:04X}", code);
120 }
121
122 result += '>';
123 }
124
125 return result;
126}
127
128
129std::string PDF_PLOTTER::encodeDoubleForPlotter( double aValue ) const
130{
131 std::string buf = fmt::format( "{:g}", aValue );
132
133 // PDF syntax does not allow exponent notation (PostScript does). fmt's {:g} can emit it and
134 // can't be configured to force non-exponent output, so fall back to fixed when needed.
135 if( buf.find( 'e' ) != std::string::npos || buf.find( 'E' ) != std::string::npos )
136 buf = fmt::format( "{:.10f}", aValue );
137
138 if( buf.find( '.' ) != std::string::npos )
139 {
140 // Trim trailing zeros from fixed output while keeping at least one digit.
141 while( buf.size() > 1 && buf.back() == '0' )
142 buf.pop_back();
143
144 // Remove a dangling decimal point if we stripped all fractional digits.
145 if( !buf.empty() && buf.back() == '.' )
146 buf.pop_back();
147 }
148
149 // Avoid emitting "-0" for tiny negative values that round to zero.
150 if( buf == "-0" )
151 buf = "0";
152
153 return buf;
154}
155
156
157std::string PDF_PLOTTER::encodeByteString( const std::string& aBytes )
158{
159 std::string result;
160 result.reserve( aBytes.size() * 4 + 2 );
161 result.push_back( '(' );
162
163 for( unsigned char byte : aBytes )
164 {
165 if( byte == '(' || byte == ')' || byte == '\\' )
166 {
167 result.push_back( '\\' );
168 result.push_back( static_cast<char>( byte ) );
169 }
170 else if( byte < 32 || byte > 126 )
171 {
172 fmt::format_to( std::back_inserter( result ), "\\{:03o}", byte );
173 }
174 else
175 {
176 result.push_back( static_cast<char>( byte ) );
177 }
178 }
179
180 result.push_back( ')' );
181 return result;
182}
183
184
185bool PDF_PLOTTER::OpenFile( const wxString& aFullFilename )
186{
187 m_filename = aFullFilename;
188
189 wxASSERT( !m_outputFile );
190
191 // Open the PDF file in binary mode
192 m_outputFile = wxFopen( m_filename, wxT( "wb" ) );
193
194 if( m_outputFile == nullptr )
195 return false ;
196
197 return true;
198}
199
200
201void PDF_PLOTTER::SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil, double aScale, bool aMirror )
202{
203 m_plotMirror = aMirror;
204 m_plotOffset = aOffset;
205 m_plotScale = aScale;
206 m_IUsPerDecimil = aIusPerDecimil;
207
208 // The CTM is set to 1 user unit per decimal
209 m_iuPerDeviceUnit = 1.0 / aIusPerDecimil;
210
211 /* The paper size in this engine is handled page by page
212 Look in the StartPage function */
213}
214
215
216void PDF_PLOTTER::SetCurrentLineWidth( int aWidth, void* aData )
217{
218 wxASSERT( m_workFile );
219
220 if( aWidth == DO_NOT_SET_LINE_WIDTH )
221 return;
222 else if( aWidth == USE_DEFAULT_LINE_WIDTH )
223 aWidth = m_renderSettings->GetDefaultPenWidth();
224
225 if( aWidth == 0 )
226 aWidth = 1;
227
228 wxASSERT_MSG( aWidth > 0, "Plotter called to set negative pen width" );
229
230 if( aWidth != m_currentPenWidth )
231 fmt::println( m_workFile, "{} w", encodeDoubleForPlotter( userToDeviceSize( aWidth ) ) );
232
233 m_currentPenWidth = aWidth;
234}
235
236
237void PDF_PLOTTER::emitSetRGBColor( double r, double g, double b, double a )
238{
239 wxASSERT( m_workFile );
240
241 // PDF treats all colors as opaque, so the best we can do with alpha is generate an
242 // appropriate blended color assuming white paper.
243 if( a < 1.0 )
244 {
245 r = ( r * a ) + ( 1 - a );
246 g = ( g * a ) + ( 1 - a );
247 b = ( b * a ) + ( 1 - a );
248 }
249
250 fmt::println( m_workFile, "{} {} {} rg {} {} {} RG",
253}
254
255
256void PDF_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle )
257{
258 wxASSERT( m_workFile );
259
260 std::vector<int> pattern;
261
262 switch( aLineStyle )
263 {
264 case LINE_STYLE::DASH:
265 pattern = { (int) GetDashMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) };
266 break;
267
268 case LINE_STYLE::DOT:
269 pattern = { (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) };
270 break;
271
273 pattern = { (int) GetDashMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ),
274 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) };
275 break;
276
278 pattern = { (int) GetDashMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ),
279 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ),
280 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) };
281 break;
282
283 default:
284 break;
285 }
286
287 // A PDF dash array whose elements sum to zero is illegal and makes strict viewers
288 // (Adobe Acrobat, Evince) abort rendering of the remaining page content. This happens
289 // when a dashed item is plotted with a zero pen width, e.g. a border-less filled shape
290 // whose stroke is dotted. Fall back to a solid line in that case.
291 bool allZero = std::all_of( pattern.begin(), pattern.end(), []( int v ) { return v == 0; } );
292
293 if( pattern.empty() || allZero )
294 {
295 fmt::println( m_workFile, "[] 0 d" );
296 return;
297 }
298
299 fmt::println( m_workFile, "[{}] 0 d", fmt::join( pattern, " " ) );
300}
301
302
303void PDF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width, int aCornerRadius )
304{
305 wxASSERT( m_workFile );
306
307 if( fill == FILL_T::NO_FILL && width == 0 )
308 return;
309
310 SetCurrentLineWidth( width );
311
312 if( aCornerRadius > 0 )
313 {
314 BOX2I box( p1, VECTOR2I( p2.x - p1.x, p2.y - p1.y ) );
315 box.Normalize();
316 SHAPE_RECT rect( box );
317 rect.SetRadius( aCornerRadius );
318 PlotPoly( rect.Outline(), fill, width, nullptr );
319 return;
320 }
321
322 VECTOR2I size = p2 - p1;
323
324 if( size.x == 0 && size.y == 0 )
325 {
326 // Can't draw zero-sized rectangles
327 MoveTo( VECTOR2I( p1.x, p1.y ) );
328 FinishTo( VECTOR2I( p1.x, p1.y ) );
329
330 return;
331 }
332
333 if( std::min( std::abs( size.x ), std::abs( size.y ) ) < width )
334 {
335 // Too thick stroked rectangles are buggy, draw as polygon
336 std::vector<VECTOR2I> cornerList;
337
338 cornerList.emplace_back( p1.x, p1.y );
339 cornerList.emplace_back( p2.x, p1.y );
340 cornerList.emplace_back( p2.x, p2.y );
341 cornerList.emplace_back( p1.x, p2.y );
342 cornerList.emplace_back( p1.x, p1.y );
343
344 PlotPoly( cornerList, fill, width, nullptr );
345
346 return;
347 }
348
349 VECTOR2D p1_dev = userToDeviceCoordinates( p1 );
350 VECTOR2D p2_dev = userToDeviceCoordinates( p2 );
351
352 char paintOp;
353
354 if( fill == FILL_T::NO_FILL )
355 paintOp = 'S';
356 else
357 paintOp = width > 0 ? 'B' : 'f';
358
359 fmt::println( m_workFile, "{} {} {} {} re {}",
360 encodeDoubleForPlotter( p1_dev.x ),
361 encodeDoubleForPlotter( p1_dev.y ),
362 encodeDoubleForPlotter( p2_dev.x - p1_dev.x ),
363 encodeDoubleForPlotter( p2_dev.y - p1_dev.y ),
364 paintOp );
365}
366
367
368void PDF_PLOTTER::Circle( const VECTOR2I& pos, int diametre, FILL_T aFill, int width )
369{
370 wxASSERT( m_workFile );
371
372 if( aFill == FILL_T::NO_FILL && width == 0 )
373 return;
374
375 SetCurrentLineWidth( width );
376
377 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
378 double radius = userToDeviceSize( diametre / 2.0 );
379
380 // If diameter is less than width, switch to filled mode
381 if( aFill == FILL_T::NO_FILL && diametre < GetCurrentLineWidth() )
382 {
383 aFill = FILL_T::FILLED_SHAPE;
384 radius = userToDeviceSize( ( diametre / 2.0 ) + ( width / 2.0 ) );
385 }
386
387 /* OK. Here's a trick. PDF doesn't support circles or circular angles, that's
388 a fact. You'll have to do with cubic beziers. These *can't* represent
389 circular arcs (NURBS can, beziers don't). But there is a widely known
390 approximation which is really good
391 */
392
393 double magic = radius * 0.551784; // You don't want to know where this come from
394
395 // This is the convex hull for the bezier approximated circle
396 fmt::println( m_workFile,
397 "{} {} m "
398 "{} {} {} {} {} {} c "
399 "{} {} {} {} {} {} c "
400 "{} {} {} {} {} {} c "
401 "{} {} {} {} {} {} c {}",
402 encodeDoubleForPlotter( pos_dev.x - radius ), encodeDoubleForPlotter( pos_dev.y ),
403
404 encodeDoubleForPlotter( pos_dev.x - radius ), encodeDoubleForPlotter( pos_dev.y + magic ),
405 encodeDoubleForPlotter( pos_dev.x - magic ), encodeDoubleForPlotter( pos_dev.y + radius ),
406 encodeDoubleForPlotter( pos_dev.x ), encodeDoubleForPlotter( pos_dev.y + radius ),
407
408 encodeDoubleForPlotter( pos_dev.x + magic ), encodeDoubleForPlotter( pos_dev.y + radius ),
409 encodeDoubleForPlotter( pos_dev.x + radius ), encodeDoubleForPlotter( pos_dev.y + magic ),
410 encodeDoubleForPlotter( pos_dev.x + radius ), encodeDoubleForPlotter( pos_dev.y ),
411
412 encodeDoubleForPlotter( pos_dev.x + radius ), encodeDoubleForPlotter( pos_dev.y - magic ),
413 encodeDoubleForPlotter( pos_dev.x + magic ), encodeDoubleForPlotter( pos_dev.y - radius ),
414 encodeDoubleForPlotter( pos_dev.x ), encodeDoubleForPlotter( pos_dev.y - radius ),
415
416 encodeDoubleForPlotter( pos_dev.x - magic ), encodeDoubleForPlotter( pos_dev.y - radius ),
417 encodeDoubleForPlotter( pos_dev.x - radius ), encodeDoubleForPlotter( pos_dev.y - magic ),
418 encodeDoubleForPlotter( pos_dev.x - radius ), encodeDoubleForPlotter( pos_dev.y ),
419
420 aFill == FILL_T::NO_FILL ? 's' : 'b' );
421}
422
423
424std::vector<VECTOR2D> PDF_PLOTTER::arcPath( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
425 const EDA_ANGLE& aAngle, double aRadius )
426{
427 std::vector<VECTOR2D> path;
428
429 /*
430 * Arcs are not so easily approximated by beziers (in the general case), so we approximate
431 * them in the old way
432 */
433 EDA_ANGLE startAngle = -aStartAngle;
434 EDA_ANGLE endAngle = startAngle - aAngle;
435 VECTOR2I start;
437 const EDA_ANGLE delta( 5, DEGREES_T ); // increment to draw circles
438
439 if( startAngle > endAngle )
440 std::swap( startAngle, endAngle );
441
442 // Usual trig arc plotting routine...
443 start.x = KiROUND( aCenter.x + aRadius * ( -startAngle ).Cos() );
444 start.y = KiROUND( aCenter.y + aRadius * ( -startAngle ).Sin() );
445 path.emplace_back( userToDeviceCoordinates( start ) );
446
447 for( EDA_ANGLE ii = startAngle + delta; ii < endAngle; ii += delta )
448 {
449 end.x = KiROUND( aCenter.x + aRadius * ( -ii ).Cos() );
450 end.y = KiROUND( aCenter.y + aRadius * ( -ii ).Sin() );
451 path.emplace_back( userToDeviceCoordinates( end ) );
452 }
453
454 end.x = KiROUND( aCenter.x + aRadius * ( -endAngle ).Cos() );
455 end.y = KiROUND( aCenter.y + aRadius * ( -endAngle ).Sin() );
456 path.emplace_back( userToDeviceCoordinates( end ) );
457
458 return path;
459}
460
461
462void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
463 const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth )
464{
465 wxASSERT( m_workFile );
466
467 SetCurrentLineWidth( aWidth );
468
469 if( aRadius <= 0 )
470 {
472 return;
473 }
474
475 std::vector<VECTOR2D> path = arcPath( aCenter, aStartAngle, aAngle, aRadius );
476
477 if( path.size() >= 2 )
478 {
479 fmt::print( m_workFile, "{} {} m ",
481
482 for( int ii = 1; ii < (int) path.size(); ++ii )
483 {
484 fmt::print( m_workFile, "{} {} l ",
486 }
487 }
488
489 // The arc is drawn... if not filled we stroke it, otherwise we finish
490 // closing the pie at the center
491 if( aFill == FILL_T::NO_FILL )
492 {
493 fmt::println( m_workFile, "S" );
494 }
495 else
496 {
497 VECTOR2D pos_dev = userToDeviceCoordinates( aCenter );
498 fmt::println( m_workFile, "{} {} l b",
499 encodeDoubleForPlotter( pos_dev.x ), encodeDoubleForPlotter( pos_dev.y ) );
500 }
501}
502
503
504void PDF_PLOTTER::PlotPoly( const std::vector<VECTOR2I>& aCornerList, FILL_T aFill, int aWidth,
505 void* aData )
506{
507 wxASSERT( m_workFile );
508
509 if( aCornerList.size() <= 1 )
510 return;
511
512 if( aFill == FILL_T::NO_FILL && aWidth == 0 )
513 return;
514
515 SetCurrentLineWidth( aWidth );
516
517 VECTOR2D pos = userToDeviceCoordinates( aCornerList[0] );
518 fmt::print( m_workFile, "{:f} {:f} m ", pos.x, pos.y );
519
520 for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
521 {
522 pos = userToDeviceCoordinates( aCornerList[ii] );
523 fmt::print( m_workFile, "{:f} {:f} l ", pos.x, pos.y );
524 }
525
526 // Close path and stroke and/or fill
527 if( aFill == FILL_T::NO_FILL )
528 fmt::println( m_workFile, "S" );
529 else if( aWidth == 0 )
530 fmt::println( m_workFile, "h f" );
531 else
532 fmt::println( m_workFile, "b" );
533}
534
535
536void PDF_PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aLineChain, FILL_T aFill, int aWidth, void* aData )
537{
538 SetCurrentLineWidth( aWidth );
539
540 std::set<size_t> handledArcs;
541 std::vector<VECTOR2D> path;
542
543 for( int ii = 0; ii < aLineChain.SegmentCount(); ++ii )
544 {
545 if( aLineChain.IsArcSegment( ii ) )
546 {
547 size_t arcIndex = aLineChain.ArcIndex( ii );
548
549 if( !handledArcs.contains( arcIndex ) )
550 {
551 handledArcs.insert( arcIndex );
552 const SHAPE_ARC& arc( aLineChain.Arc( arcIndex ) );
553 std::vector<VECTOR2D> arc_path = arcPath( arc.GetCenter(), arc.GetStartAngle(),
554 arc.GetCentralAngle(), arc.GetRadius() );
555
556 for( const VECTOR2D& pt : std::ranges::reverse_view( arc_path ) )
557 path.emplace_back( pt );
558 }
559 }
560 else
561 {
562 const SEG& seg( aLineChain.Segment( ii ) );
563 path.emplace_back( userToDeviceCoordinates( seg.A ) );
564 path.emplace_back( userToDeviceCoordinates( seg.B ) );
565 }
566 }
567
568 if( path.size() <= 1 )
569 return;
570
571 fmt::print( m_workFile, "{} {} m ",
573
574 for( int ii = 1; ii < (int) path.size(); ++ii )
575 {
576 fmt::print( m_workFile, "{} {} l ",
578 }
579
580 // Close path and stroke and/or fill
581 if( aFill == FILL_T::NO_FILL )
582 fmt::println( m_workFile, "S" );
583 else if( aWidth == 0 )
584 fmt::println( m_workFile, "h f" );
585 else
586 fmt::println( m_workFile, "b" );
587}
588
589
590void PDF_PLOTTER::PenTo( const VECTOR2I& pos, char plume )
591{
592 wxASSERT( m_workFile );
593
594 if( plume == 'Z' )
595 {
596 if( m_penState != 'Z' )
597 {
598 fmt::println( m_workFile, "S" );
599 m_penState = 'Z';
600 m_penLastpos.x = -1;
601 m_penLastpos.y = -1;
602 }
603
604 return;
605 }
606
607 if( m_penState != plume || pos != m_penLastpos )
608 {
609 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
610 fmt::println( m_workFile, "{:f} {:f} {}",
611 pos_dev.x,
612 pos_dev.y,
613 plume == 'D' ? 'l' : 'm' );
614 }
615
616 m_penState = plume;
617 m_penLastpos = pos;
618}
619
620
621void PDF_PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor )
622{
623 wxASSERT( m_workFile );
624 VECTOR2I pix_size( aImage.GetWidth(), aImage.GetHeight() );
625
626 // Requested size (in IUs)
627 VECTOR2D drawsize( aScaleFactor * pix_size.x, aScaleFactor * pix_size.y );
628
629 // calculate the bitmap start position
630 VECTOR2I start( aPos.x - drawsize.x / 2, aPos.y + drawsize.y / 2 );
631 VECTOR2D dev_start = userToDeviceCoordinates( start );
632
633 // Deduplicate images
634 auto findHandleForImage =
635 [&]( const wxImage& aCurrImage ) -> int
636 {
637 for( const auto& [imgHandle, image] : m_imageHandles )
638 {
639 if( image.IsSameAs( aCurrImage ) )
640 return imgHandle;
641
642 if( image.GetWidth() != aCurrImage.GetWidth() )
643 continue;
644
645 if( image.GetHeight() != aCurrImage.GetHeight() )
646 continue;
647
648 if( image.GetType() != aCurrImage.GetType() )
649 continue;
650
651 if( image.HasAlpha() != aCurrImage.HasAlpha() )
652 continue;
653
654 if( image.HasMask() != aCurrImage.HasMask()
655 || image.GetMaskRed() != aCurrImage.GetMaskRed()
656 || image.GetMaskGreen() != aCurrImage.GetMaskGreen()
657 || image.GetMaskBlue() != aCurrImage.GetMaskBlue() )
658 {
659 continue;
660 }
661
662 int pixCount = image.GetWidth() * image.GetHeight();
663
664 if( memcmp( image.GetData(), aCurrImage.GetData(), pixCount * 3 ) != 0 )
665 continue;
666
667 if( image.HasAlpha() && memcmp( image.GetAlpha(), aCurrImage.GetAlpha(), pixCount ) != 0 )
668 continue;
669
670 return imgHandle;
671 }
672
673 return -1;
674 };
675
676 int imgHandle = findHandleForImage( aImage );
677
678 if( imgHandle == -1 )
679 {
680 imgHandle = allocPdfObject();
681 m_imageHandles.emplace( imgHandle, aImage );
682 }
683
684 /* PDF has an uhm... simplified coordinate system handling. There is
685 *one* operator to do everything (the PS concat equivalent). At least
686 they kept the matrix stack to save restore environments. Also images
687 are always emitted at the origin with a size of 1x1 user units.
688 What we need to do is:
689 1) save the CTM end establish the new one
690 2) plot the image
691 3) restore the CTM
692 4) profit
693 */
694 fmt::println( m_workFile, "q {} 0 0 {} {} {} cm", // Step 1
697 encodeDoubleForPlotter( dev_start.x ),
698 encodeDoubleForPlotter( dev_start.y ) );
699
700 fmt::println( m_workFile, "/Im{} Do", imgHandle );
701 fmt::println( m_workFile, "Q" );
702}
703
704
706{
707 m_xrefTable.push_back( 0 );
708 return m_xrefTable.size() - 1;
709}
710
711
713{
714 wxASSERT( m_outputFile );
715 wxASSERT( !m_workFile );
716
717 if( aHandle < 0 )
718 aHandle = allocPdfObject();
719
720 m_xrefTable[aHandle] = ftell( m_outputFile );
721 fmt::println( m_outputFile, "{} 0 obj", aHandle );
722 return aHandle;
723}
724
725
727{
728 wxASSERT( m_outputFile );
729 wxASSERT( !m_workFile );
730 fmt::println( m_outputFile, "endobj" );
731}
732
733
735{
736 wxASSERT( m_outputFile );
737 wxASSERT( !m_workFile );
738 int handle = startPdfObject( aHandle );
739
740 // This is guaranteed to be handle+1 but needs to be allocated since
741 // you could allocate more object during stream preparation
743
744 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
745 {
746 fmt::print( m_outputFile, "<< /Length {} 0 R >>\nstream\n",
748 }
749 else
750 {
751 fmt::print( m_outputFile, "<< /Length {} 0 R /Filter /FlateDecode >>\nstream\n",
753 }
754
755 // Open a temporary file to accumulate the stream
756 m_workFilename = wxFileName::CreateTempFileName( "" );
757 m_workFile = wxFopen( m_workFilename, wxT( "w+b" ) );
758 wxASSERT( m_workFile );
759 return handle;
760}
761
762
764{
765 wxASSERT( m_workFile );
766
767 long stream_len = ftell( m_workFile );
768
769 if( stream_len < 0 )
770 {
771 wxASSERT( false );
772 return;
773 }
774
775 // Rewind the file, read in the page stream and DEFLATE it
776 fseek( m_workFile, 0, SEEK_SET );
777 unsigned char *inbuf = new unsigned char[stream_len];
778
779 int rc = fread( inbuf, 1, stream_len, m_workFile );
780 wxASSERT( rc == stream_len );
781 ignore_unused( rc );
782
783 // We are done with the temporary file, junk it
784 fclose( m_workFile );
785 m_workFile = nullptr;
786 ::wxRemoveFile( m_workFilename );
787
788 unsigned out_count;
789
790 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
791 {
792 out_count = stream_len;
793 fwrite( inbuf, out_count, 1, m_outputFile );
794 }
795 else
796 {
797 // NULL means memos owns the memory, but provide a hint on optimum size needed.
798 wxMemoryOutputStream memos( nullptr, std::max( 2000l, stream_len ) ) ;
799
800 {
801 /* Somewhat standard parameters to compress in DEFLATE. The PDF spec is
802 * misleading, it says it wants a DEFLATE stream but it really want a ZLIB
803 * stream! (a DEFLATE stream would be generated with -15 instead of 15)
804 * rc = deflateInit2( &zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, 15,
805 * 8, Z_DEFAULT_STRATEGY );
806 */
807
808 wxZlibOutputStream zos( memos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
809
810 zos.Write( inbuf, stream_len );
811 } // flush the zip stream using zos destructor
812
813 wxStreamBuffer* sb = memos.GetOutputStreamBuffer();
814
815 out_count = sb->Tell();
816 fwrite( sb->GetBufferStart(), 1, out_count, m_outputFile );
817 }
818
819 delete[] inbuf;
820 fmt::print( m_outputFile, "\nendstream\n" );
822
823 // Writing the deferred length as an indirect object
825 fmt::println( m_outputFile, "{}", out_count );
827}
828
829
830void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageName,
831 const wxString& aParentPageNumber, const wxString& aParentPageName )
832{
833 wxASSERT( m_outputFile );
834 wxASSERT( !m_workFile );
835
836 m_pageNumbers.push_back( aPageNumber );
837 m_pageName = aPageName.IsEmpty() ? wxString::Format( _( "Page %s" ),
838 aPageNumber )
839 : wxString::Format( _( "%s (Page %s)" ),
840 aPageName,
841 aPageNumber );
842 m_parentPageName = aParentPageName.IsEmpty() ? wxString::Format( _( "Page %s" ),
843 aParentPageNumber )
844 : wxString::Format( _( "%s (Page %s)" ),
845 aParentPageName,
846 aParentPageNumber );
847
848 // Compute the paper size in IUs
849 m_paperSize = m_pageInfo.GetSizeMils();
850 m_paperSize.x *= 10.0 / m_iuPerDeviceUnit;
851 m_paperSize.y *= 10.0 / m_iuPerDeviceUnit;
852
853 // Set m_currentPenWidth to a unused value to ensure the pen width
854 // will be initialized to a the right value in pdf file by the first item to plot
856
857 if( !m_3dExportMode )
858 {
859 // Open the content stream; the page object will go later
861
862 /* Now, until ClosePage *everything* must be wrote in workFile, to be
863 compressed later in closePdfStream */
864
865 // Default graphic settings (coordinate system, default color and line style)
866 fmt::println( m_workFile,
867 "{} 0 0 {} 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG {} w",
870 encodeDoubleForPlotter( userToDeviceSize( m_renderSettings->GetDefaultPenWidth() ) ) );
871 }
872}
873
874
875void WriteImageStream( const wxImage& aImage, wxDataOutputStream& aOut, const wxColor& bg, bool colorMode )
876{
877 int w = aImage.GetWidth();
878 int h = aImage.GetHeight();
879
880 for( int y = 0; y < h; y++ )
881 {
882 for( int x = 0; x < w; x++ )
883 {
884 unsigned char r = aImage.GetRed( x, y ) & 0xFF;
885 unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
886 unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
887
888 if( aImage.HasMask() )
889 {
890 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen() && b == aImage.GetMaskBlue() )
891 {
892 r = bg.Red();
893 g = bg.Green();
894 b = bg.Blue();
895 }
896 }
897
898 if( colorMode )
899 {
900 aOut.Write8( r );
901 aOut.Write8( g );
902 aOut.Write8( b );
903 }
904 else
905 {
906 // Greyscale conversion (CIE 1931)
907 unsigned char grey = KiROUND( r * 0.2126 + g * 0.7152 + b * 0.0722 );
908
909 aOut.Write8( grey );
910 }
911 }
912 }
913}
914
915
916void WriteImageSMaskStream( const wxImage& aImage, wxDataOutputStream& aOut )
917{
918 int w = aImage.GetWidth();
919 int h = aImage.GetHeight();
920
921 if( aImage.HasMask() )
922 {
923 for( int y = 0; y < h; y++ )
924 {
925 for( int x = 0; x < w; x++ )
926 {
927 unsigned char a = 255;
928 unsigned char r = aImage.GetRed( x, y );
929 unsigned char g = aImage.GetGreen( x, y );
930 unsigned char b = aImage.GetBlue( x, y );
931
932 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen() && b == aImage.GetMaskBlue() )
933 a = 0;
934
935 aOut.Write8( a );
936 }
937 }
938 }
939 else if( aImage.HasAlpha() )
940 {
941 int size = w * h;
942 aOut.Write8( aImage.GetAlpha(), size );
943 }
944}
945
946
948{
949 // non 3d exports need this
950 if( m_pageStreamHandle != -1 )
951 {
952 wxASSERT( m_workFile );
953
954 // Close the page stream (and compress it)
956 }
957
958 // Page size is in 1/72 of inch (default user space units). Works like the bbox in postscript
959 // but there is no need for swapping the sizes, since PDF doesn't require a portrait page.
960 // We use the MediaBox but PDF has lots of other less-used boxes that could be used.
961 const double PTsPERMIL = 0.072;
962 VECTOR2D psPaperSize = VECTOR2D( m_pageInfo.GetSizeMils() ) * PTsPERMIL;
963
964 auto iuToPdfUserSpace =
965 [&]( const VECTOR2I& aCoord ) -> VECTOR2D
966 {
967 VECTOR2D pos = VECTOR2D( aCoord ) * PTsPERMIL / ( m_IUsPerDecimil * 10 );
968
969 // PDF y=0 is at bottom of page, invert coordinate
970 VECTOR2D retval( pos.x, psPaperSize.y - pos.y );
971
972 // The pdf plot can be mirrored (from left to right). So mirror the
973 // x coordinate if m_plotMirror is set
974 if( m_plotMirror )
975 {
977 retval.x = ( psPaperSize.x - pos.x );
978 else
979 retval.y = pos.y;
980 }
981
982 return retval;
983 };
984
985 // Handle annotations (at the moment only "link" type objects)
986 std::vector<int> annotHandles;
987
988 // Allocate all hyperlink objects for the page and calculate their position in user space
989 // coordinates
990 for( const std::pair<BOX2I, wxString>& linkPair : m_hyperlinksInPage )
991 {
992 const BOX2I& box = linkPair.first;
993 const wxString& url = linkPair.second;
994
995 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
996 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
997
998 BOX2D userSpaceBox;
999 userSpaceBox.SetOrigin( bottomLeft );
1000 userSpaceBox.SetEnd( topRight );
1001
1002 annotHandles.push_back( allocPdfObject() );
1003
1004 m_hyperlinkHandles.insert( { annotHandles.back(), { userSpaceBox, url } } );
1005 }
1006
1007 for( const std::pair<BOX2I, std::vector<wxString>>& menuPair : m_hyperlinkMenusInPage )
1008 {
1009 const BOX2I& box = menuPair.first;
1010 const std::vector<wxString>& urls = menuPair.second;
1011
1012 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
1013 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
1014
1015 BOX2D userSpaceBox;
1016 userSpaceBox.SetOrigin( bottomLeft );
1017 userSpaceBox.SetEnd( topRight );
1018
1019 annotHandles.push_back( allocPdfObject() );
1020
1021 m_hyperlinkMenuHandles.insert( { annotHandles.back(), { userSpaceBox, urls } } );
1022 }
1023
1024 int annot3DHandle = -1;
1025
1026 if( m_3dExportMode )
1027 {
1028 annot3DHandle = allocPdfObject();
1029 annotHandles.push_back( annot3DHandle );
1030 }
1031
1032
1033 int annotArrayHandle = -1;
1034
1035 // If we have added any annotation links, create an array containing all the objects
1036 if( annotHandles.size() > 0 )
1037 {
1038 annotArrayHandle = startPdfObject();
1039 bool isFirst = true;
1040
1041 fmt::print( m_outputFile, "[" );
1042
1043 for( int handle : annotHandles )
1044 {
1045 if( isFirst )
1046 isFirst = false;
1047 else
1048 fmt::print( m_outputFile, " " );
1049
1050 fmt::print( m_outputFile, "{} 0 R", handle );
1051 }
1052
1053 fmt::println( m_outputFile, "]" );
1055 }
1056
1057 // Emit the page object and put it in the page list for later
1058 int pageHandle = startPdfObject();
1059 m_pageHandles.push_back( pageHandle );
1060
1061 fmt::print( m_outputFile,
1062 "<<\n"
1063 "/Type /Page\n"
1064 "/Parent {} 0 R\n"
1065 "/Resources <<\n"
1066 " /ProcSet [/PDF /Text /ImageC /ImageB]\n"
1067 " /Font {} 0 R\n"
1068 " /XObject {} 0 R >>\n"
1069 "/MediaBox [0 0 {} {}]\n",
1073 encodeDoubleForPlotter( psPaperSize.x ),
1074 encodeDoubleForPlotter( psPaperSize.y ) );
1075
1076 if( m_pageStreamHandle != -1 )
1077 fmt::print( m_outputFile, "/Contents {} 0 R\n", m_pageStreamHandle );
1078
1079 if( annotHandles.size() > 0 )
1080 fmt::print( m_outputFile, "/Annots {} 0 R", annotArrayHandle );
1081
1082 fmt::print( m_outputFile, ">>\n" );
1083
1085
1086 if( m_3dExportMode )
1087 {
1088 startPdfObject( annot3DHandle );
1089 fmt::print( m_outputFile,
1090 "<<\n"
1091 "/Type /Annot\n"
1092 "/Subtype /3D\n"
1093 "/Rect [0 0 {} {}]\n"
1094 "/NM (3D Annotation)\n"
1095 "/3DD {} 0 R\n"
1096 "/3DV 0\n"
1097 "/3DA<</A/PO/D/PC/TB true/NP true>>\n"
1098 "/3DI true\n"
1099 "/P {} 0 R\n"
1100 ">>\n",
1101 encodeDoubleForPlotter( psPaperSize.x ),
1102 encodeDoubleForPlotter( psPaperSize.y ),
1104 pageHandle );
1105
1107 }
1108
1109 // Mark the page stream as idle
1110 m_pageStreamHandle = -1;
1111
1112 int actionHandle = emitGoToAction( pageHandle );
1113 PDF_PLOTTER::OUTLINE_NODE* parent_node = m_outlineRoot.get();
1114
1115 if( !m_parentPageName.IsEmpty() )
1116 {
1117 // Search for the parent node iteratively through the entire tree
1118 std::stack<OUTLINE_NODE*> nodes;
1119 nodes.push( m_outlineRoot.get() );
1120
1121 while( !nodes.empty() )
1122 {
1123 OUTLINE_NODE* node = nodes.top();
1124 nodes.pop();
1125
1126 // Check if this node matches
1127 if( node->title == m_parentPageName )
1128 {
1129 parent_node = node;
1130 break;
1131 }
1132
1133 // Add all children to the stack
1134 for( OUTLINE_NODE* child : node->children )
1135 nodes.push( child );
1136 }
1137 }
1138
1139 OUTLINE_NODE* pageOutlineNode = addOutlineNode( parent_node, actionHandle, m_pageName );
1140
1141 // let's reorg the symbol bookmarks under a page handle
1142 // let's reorg the symbol bookmarks under a page handle
1143 for( const auto& [groupName, groupVector] : m_bookmarksInPage )
1144 {
1145 OUTLINE_NODE* groupOutlineNode = addOutlineNode( pageOutlineNode, actionHandle, groupName );
1146
1147 for( const std::pair<BOX2I, wxString>& bookmarkPair : groupVector )
1148 {
1149 const BOX2I& box = bookmarkPair.first;
1150 const wxString& ref = bookmarkPair.second;
1151
1152 VECTOR2I bottomLeft = iuToPdfUserSpace( box.GetPosition() );
1153 VECTOR2I topRight = iuToPdfUserSpace( box.GetEnd() );
1154
1155 actionHandle = emitGoToAction( pageHandle, bottomLeft, topRight );
1156
1157 addOutlineNode( groupOutlineNode, actionHandle, ref );
1158 }
1159
1160 std::sort( groupOutlineNode->children.begin(), groupOutlineNode->children.end(),
1161 []( const OUTLINE_NODE* a, const OUTLINE_NODE* b ) -> bool
1162 {
1163 return a->title < b->title;
1164 } );
1165 }
1166
1167 // Clean up
1168 m_hyperlinksInPage.clear();
1169 m_hyperlinkMenusInPage.clear();
1170 m_bookmarksInPage.clear();
1171}
1172
1173
1174bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
1175{
1176 return StartPlot( aPageNumber, wxEmptyString );
1177}
1178
1179
1180bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber, const wxString& aPageName )
1181{
1182 wxASSERT( m_outputFile );
1183
1184 // First things first: the customary null object
1185 m_xrefTable.clear();
1186 m_xrefTable.push_back( 0 );
1187 m_hyperlinksInPage.clear();
1188 m_hyperlinkMenusInPage.clear();
1189 m_hyperlinkHandles.clear();
1190 m_hyperlinkMenuHandles.clear();
1191 m_bookmarksInPage.clear();
1193 m_usedBase14Fonts = false;
1194
1195 m_outlineRoot = std::make_unique<OUTLINE_NODE>();
1196
1197 if( !m_strokeFontManager )
1198 m_strokeFontManager = std::make_unique<PDF_STROKE_FONT_MANAGER>();
1199 else
1200 m_strokeFontManager->Reset();
1201
1203 m_outlineFontManager = std::make_unique<PDF_OUTLINE_FONT_MANAGER>();
1204 else
1205 m_outlineFontManager->Reset();
1206
1207 /* The header (that's easy!). The second line is binary junk required
1208 to make the file binary from the beginning (the important thing is
1209 that they must have the bit 7 set) */
1210 fmt::print( m_outputFile, "%PDF-1.5\n%\200\201\202\203\n" );
1211
1212 /* Allocate an entry for the page tree root, it will go in every page parent entry */
1214
1215 /* In the same way, the font resource dictionary is used by every page
1216 (it *could* be inherited via the Pages tree */
1218
1220
1222
1223 /* Now, the PDF is read from the end, (more or less)... so we start
1224 with the page stream for page 1. Other more important stuff is written
1225 at the end */
1226 StartPage( aPageNumber, aPageName );
1227 return true;
1228}
1229
1230
1231int PDF_PLOTTER::emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft, const VECTOR2I& aTopRight )
1232{
1233 int actionHandle = allocPdfObject();
1234 startPdfObject( actionHandle );
1235
1236 fmt::print( m_outputFile,
1237 "<</S /GoTo /D [{} 0 R /FitR {} {} {} {}]\n"
1238 ">>\n",
1239 aPageHandle,
1240 aBottomLeft.x,
1241 aBottomLeft.y,
1242 aTopRight.x,
1243 aTopRight.y );
1244
1246
1247 return actionHandle;
1248}
1249
1250
1251int PDF_PLOTTER::emitGoToAction( int aPageHandle )
1252{
1253 int actionHandle = allocPdfObject();
1254 startPdfObject( actionHandle );
1255
1256 fmt::println( m_outputFile,
1257 "<</S /GoTo /D [{} 0 R /Fit]\n"
1258 ">>",
1259 aPageHandle );
1260
1262
1263 return actionHandle;
1264}
1265
1266
1267void PDF_PLOTTER::emitOutlineNode( OUTLINE_NODE* node, int parentHandle, int nextNode, int prevNode )
1268{
1269 int nodeHandle = node->entryHandle;
1270 int prevHandle = -1;
1271 int nextHandle = -1;
1272
1273 for( std::vector<OUTLINE_NODE*>::iterator it = node->children.begin(); it != node->children.end(); it++ )
1274 {
1275 if( it >= node->children.end() - 1 )
1276 nextHandle = -1;
1277 else
1278 nextHandle = ( *( it + 1 ) )->entryHandle;
1279
1280 emitOutlineNode( *it, nodeHandle, nextHandle, prevHandle );
1281
1282 prevHandle = ( *it )->entryHandle;
1283 }
1284
1285 // -1 for parentHandle is the outline root itself which is handed elsewhere.
1286 if( parentHandle != -1 )
1287 {
1288 startPdfObject( nodeHandle );
1289
1290 fmt::print( m_outputFile,
1291 "<<\n"
1292 "/Title {}\n"
1293 "/Parent {} 0 R\n",
1295 parentHandle);
1296
1297 if( nextNode > 0 )
1298 fmt::println( m_outputFile, "/Next {} 0 R", nextNode );
1299
1300 if( prevNode > 0 )
1301 fmt::println( m_outputFile, "/Prev {} 0 R", prevNode );
1302
1303 if( node->children.size() > 0 )
1304 {
1305 int32_t count = -1 * static_cast<int32_t>( node->children.size() );
1306 fmt::println( m_outputFile, "/Count {}", count );
1307 fmt::println( m_outputFile, "/First {} 0 R", node->children.front()->entryHandle );
1308 fmt::println( m_outputFile, "/Last {} 0 R", node->children.back()->entryHandle );
1309 }
1310
1311 if( node->actionHandle != -1 )
1312 fmt::println( m_outputFile, "/A {} 0 R", node->actionHandle );
1313
1314 fmt::println( m_outputFile, ">>" );
1316 }
1317}
1318
1319
1321 const wxString& aTitle )
1322{
1323 OUTLINE_NODE *node = aParent->AddChild( aActionHandle, aTitle, allocPdfObject() );
1325
1326 return node;
1327}
1328
1329
1331{
1332 if( m_outlineRoot->children.size() > 0 )
1333 {
1334 // declare the outline object
1335 m_outlineRoot->entryHandle = allocPdfObject();
1336
1337 emitOutlineNode( m_outlineRoot.get(), -1, -1, -1 );
1338
1339 startPdfObject( m_outlineRoot->entryHandle );
1340
1341 fmt::print( m_outputFile,
1342 "<< /Type /Outlines\n"
1343 " /Count {}\n"
1344 " /First {} 0 R\n"
1345 " /Last {} 0 R\n"
1346 ">>\n",
1348 m_outlineRoot->children.front()->entryHandle,
1349 m_outlineRoot->children.back()->entryHandle
1350 );
1351
1353
1354 return m_outlineRoot->entryHandle;
1355 }
1356
1357 return -1;
1358}
1359
1360
1362{
1363 if( !m_strokeFontManager )
1364 return;
1365
1366 for( PDF_STROKE_FONT_SUBSET* subsetPtr : m_strokeFontManager->AllSubsets() )
1367 {
1368 PDF_STROKE_FONT_SUBSET& subset = *subsetPtr;
1369
1370 if( subset.GlyphCount() <= 1 )
1371 {
1372 subset.SetCharProcsHandle( -1 );
1373 subset.SetFontHandle( -1 );
1374 subset.SetToUnicodeHandle( -1 );
1375 continue;
1376 }
1377
1378 for( PDF_STROKE_FONT_SUBSET::GLYPH& glyph : subset.Glyphs() )
1379 {
1380 int charProcHandle = startPdfStream();
1381
1382 if( !glyph.m_stream.empty() )
1383 fmt::print( m_workFile, "{}\n", glyph.m_stream );
1384
1386 glyph.m_charProcHandle = charProcHandle;
1387 }
1388
1389 int charProcDictHandle = startPdfObject();
1390 fmt::println( m_outputFile, "<<" );
1391
1392 for( const PDF_STROKE_FONT_SUBSET::GLYPH& glyph : subset.Glyphs() )
1393 fmt::println( m_outputFile, " /{} {} 0 R", glyph.m_name, glyph.m_charProcHandle );
1394
1395 fmt::println( m_outputFile, ">>" );
1397 subset.SetCharProcsHandle( charProcDictHandle );
1398
1399 int toUnicodeHandle = startPdfStream();
1400 std::string cmap = subset.BuildToUnicodeCMap();
1401
1402 if( !cmap.empty() )
1403 fmt::print( m_workFile, "{}", cmap );
1404
1406 subset.SetToUnicodeHandle( toUnicodeHandle );
1407
1408 double fontMatrixScale = 1.0 / subset.UnitsPerEm();
1409 double minX = subset.FontBBoxMinX();
1410 double minY = subset.FontBBoxMinY();
1411 double maxX = subset.FontBBoxMaxX();
1412 double maxY = subset.FontBBoxMaxY();
1413
1414 int fontHandle = startPdfObject();
1415 fmt::print( m_outputFile,
1416 "<<\n/Type /Font\n/Subtype /Type3\n/Name {}\n/FontBBox [ {} {} {} {} ]\n",
1417 subset.ResourceName(),
1418 encodeDoubleForPlotter( minX ),
1419 encodeDoubleForPlotter( minY ),
1420 encodeDoubleForPlotter( maxX ),
1421 encodeDoubleForPlotter( maxY ) );
1422 fmt::print( m_outputFile,
1423 "/FontMatrix [ {} 0 0 {} 0 0 ]\n/CharProcs {} 0 R\n",
1424 encodeDoubleForPlotter( fontMatrixScale ),
1425 encodeDoubleForPlotter( fontMatrixScale ),
1426 subset.CharProcsHandle() );
1427 fmt::print( m_outputFile,
1428 "/Encoding << /Type /Encoding /Differences {} >>\n",
1429 subset.BuildDifferencesArray() );
1430 fmt::print( m_outputFile,
1431 "/FirstChar {}\n/LastChar {}\n/Widths {}\n",
1432 subset.FirstChar(),
1433 subset.LastChar(),
1434 subset.BuildWidthsArray() );
1435 fmt::print( m_outputFile,
1436 "/ToUnicode {} 0 R\n/Resources << /ProcSet [/PDF /Text] >>\n>>\n",
1437 subset.ToUnicodeHandle() );
1439 subset.SetFontHandle( fontHandle );
1440 }
1441}
1442
1443
1445{
1447 return;
1448
1449 for( PDF_OUTLINE_FONT_SUBSET* subsetPtr : m_outlineFontManager->AllSubsets() )
1450 {
1451 if( !subsetPtr || !subsetPtr->HasGlyphs() )
1452 continue;
1453
1454 const std::vector<uint8_t>& fontData = subsetPtr->FontFileData();
1455
1456 if( fontData.empty() )
1457 continue;
1458
1459 int fontFileHandle = startPdfStream();
1460 subsetPtr->SetFontFileHandle( fontFileHandle );
1461
1462 if( !fontData.empty() )
1463 fwrite( fontData.data(), fontData.size(), 1, m_workFile );
1464
1466
1467 std::string cidMap = subsetPtr->BuildCIDToGIDStream();
1468 int cidMapHandle = startPdfStream();
1469 subsetPtr->SetCIDMapHandle( cidMapHandle );
1470
1471 if( !cidMap.empty() )
1472 fwrite( cidMap.data(), cidMap.size(), 1, m_workFile );
1473
1475
1476 std::string toUnicode = subsetPtr->BuildToUnicodeCMap();
1477 int toUnicodeHandle = startPdfStream();
1478 subsetPtr->SetToUnicodeHandle( toUnicodeHandle );
1479
1480 if( !toUnicode.empty() )
1481 fmt::print( m_workFile, "{}", toUnicode );
1482
1484
1485 int descriptorHandle = startPdfObject();
1486 subsetPtr->SetFontDescriptorHandle( descriptorHandle );
1487
1488 fmt::print( m_outputFile,
1489 "<<\n/Type /FontDescriptor\n/FontName /{}\n/Flags {}\n/ItalicAngle {}\n/Ascent {}\n/Descent {}\n"
1490 "/CapHeight {}\n/StemV {}\n/FontBBox [ {} {} {} {} ]\n/FontFile2 {} 0 R\n>>\n",
1491 subsetPtr->BaseFontName(),
1492 subsetPtr->Flags(),
1493 encodeDoubleForPlotter( subsetPtr->ItalicAngle() ),
1494 encodeDoubleForPlotter( subsetPtr->Ascent() ),
1495 encodeDoubleForPlotter( subsetPtr->Descent() ),
1496 encodeDoubleForPlotter( subsetPtr->CapHeight() ),
1497 encodeDoubleForPlotter( subsetPtr->StemV() ),
1498 encodeDoubleForPlotter( subsetPtr->BBoxMinX() ),
1499 encodeDoubleForPlotter( subsetPtr->BBoxMinY() ),
1500 encodeDoubleForPlotter( subsetPtr->BBoxMaxX() ),
1501 encodeDoubleForPlotter( subsetPtr->BBoxMaxY() ),
1502 subsetPtr->FontFileHandle() );
1504
1505 int cidFontHandle = startPdfObject();
1506 subsetPtr->SetCIDFontHandle( cidFontHandle );
1507
1508 std::string widths = subsetPtr->BuildWidthsArray();
1509
1510 fmt::print( m_outputFile,
1511 "<<\n/Type /Font\n/Subtype /CIDFontType2\n/BaseFont /{}\n"
1512 "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n"
1513 "/FontDescriptor {} 0 R\n/W {}\n/CIDToGIDMap {} 0 R\n>>\n",
1514 subsetPtr->BaseFontName(),
1515 subsetPtr->FontDescriptorHandle(),
1516 widths,
1517 subsetPtr->CIDMapHandle() );
1519
1520 int fontHandle = startPdfObject();
1521 subsetPtr->SetFontHandle( fontHandle );
1522
1523 fmt::print( m_outputFile,
1524 "<<\n/Type /Font\n/Subtype /Type0\n/BaseFont /{}\n/Encoding /Identity-H\n"
1525 "/DescendantFonts [ {} 0 R ]\n/ToUnicode {} 0 R\n>>\n",
1526 subsetPtr->BaseFontName(),
1527 subsetPtr->CIDFontHandle(),
1528 subsetPtr->ToUnicodeHandle() );
1530 }
1531}
1532
1533
1535{
1538
1539 // The non-embeddable font fallback writes Tf against these names, so they must be declared
1540 // or the invisible searchable layer is an undefined resource
1541 struct
1542 {
1543 const char* psname;
1544 const char* rsname;
1545 int handle;
1546 } base14[4] = {
1547 { "/Helvetica", "/KicadFont", 0 },
1548 { "/Helvetica-Oblique", "/KicadFontI", 0 },
1549 { "/Helvetica-Bold", "/KicadFontB", 0 },
1550 { "/Helvetica-BoldOblique", "/KicadFontBI", 0 }
1551 };
1552
1553 if( m_usedBase14Fonts )
1554 {
1555 for( auto& font : base14 )
1556 {
1557 font.handle = startPdfObject();
1558 fmt::println( m_outputFile,
1559 "<< /BaseFont {}\n"
1560 " /Type /Font\n"
1561 " /Subtype /Type1\n"
1562 " /Encoding /WinAnsiEncoding\n"
1563 ">>",
1564 font.psname );
1566 }
1567 }
1568
1570 fmt::println( m_outputFile, "<<" );
1571
1572 if( m_usedBase14Fonts )
1573 {
1574 for( const auto& font : base14 )
1575 fmt::println( m_outputFile, " {} {} 0 R", font.rsname, font.handle );
1576 }
1577
1579 {
1580 for( PDF_OUTLINE_FONT_SUBSET* subsetPtr : m_outlineFontManager->AllSubsets() )
1581 {
1582 if( subsetPtr && subsetPtr->FontHandle() >= 0 )
1583 fmt::println( m_outputFile, " {} {} 0 R", subsetPtr->ResourceName(), subsetPtr->FontHandle() );
1584 }
1585 }
1586
1588 {
1589 for( PDF_STROKE_FONT_SUBSET* subsetPtr : m_strokeFontManager->AllSubsets() )
1590 {
1591 const PDF_STROKE_FONT_SUBSET& subset = *subsetPtr;
1592
1593 if( subset.FontHandle() >= 0 )
1594 fmt::println( m_outputFile, " {} {} 0 R", subset.ResourceName(), subset.FontHandle() );
1595 }
1596 }
1597
1598 fmt::println( m_outputFile, ">>" );
1600
1601 // Named image dictionary (was allocated, now we emit it)
1603 fmt::println( m_outputFile, "<<\n" );
1604
1605 for( const auto& [imgHandle, image] : m_imageHandles )
1606 fmt::print( m_outputFile, " /Im{} {} 0 R\n", imgHandle, imgHandle );
1607
1608 fmt::println( m_outputFile, ">>" );
1610
1611 // Emit images with optional SMask for transparency
1612 for( const auto& [imgHandle, image] : m_imageHandles )
1613 {
1614 // Init wxFFile so wxFFileOutputStream won't close file in dtor.
1615 wxFFile outputFFile( m_outputFile );
1616
1617 // Image
1618 startPdfObject( imgHandle );
1619 int imgLenHandle = allocPdfObject();
1620 int smaskHandle = ( image.HasAlpha() || image.HasMask() ) ? allocPdfObject() : -1;
1621
1622 fmt::print( m_outputFile,
1623 "<<\n"
1624 "/Type /XObject\n"
1625 "/Subtype /Image\n"
1626 "/BitsPerComponent 8\n"
1627 "/ColorSpace {}\n"
1628 "/Width {}\n"
1629 "/Height {}\n"
1630 "/Filter /FlateDecode\n"
1631 "/Length {} 0 R\n", // Length is deferred
1632 m_colorMode ? "/DeviceRGB" : "/DeviceGray",
1633 image.GetWidth(),
1634 image.GetHeight(),
1635 imgLenHandle );
1636
1637 if( smaskHandle != -1 )
1638 fmt::println( m_outputFile, "/SMask {} 0 R", smaskHandle );
1639
1640 fmt::println( m_outputFile, ">>" );
1641 fmt::println( m_outputFile, "stream" );
1642
1643 long imgStreamStart = ftell( m_outputFile );
1644
1645 {
1646 wxFFileOutputStream ffos( outputFFile );
1647 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1648 wxDataOutputStream dos( zos );
1649
1650 WriteImageStream( image, dos, m_renderSettings->GetBackgroundColor().ToColour(),
1651 m_colorMode );
1652 }
1653
1654 long imgStreamSize = ftell( m_outputFile ) - imgStreamStart;
1655
1656 fmt::print( m_outputFile, "\nendstream\n" );
1658
1659 startPdfObject( imgLenHandle );
1660 fmt::println( m_outputFile, "{}", imgStreamSize );
1662
1663 if( smaskHandle != -1 )
1664 {
1665 // SMask
1666 startPdfObject( smaskHandle );
1667 int smaskLenHandle = allocPdfObject();
1668
1669 fmt::print( m_outputFile,
1670 "<<\n"
1671 "/Type /XObject\n"
1672 "/Subtype /Image\n"
1673 "/BitsPerComponent 8\n"
1674 "/ColorSpace /DeviceGray\n"
1675 "/Width {}\n"
1676 "/Height {}\n"
1677 "/Length {} 0 R\n"
1678 "/Filter /FlateDecode\n"
1679 ">>\n", // Length is deferred
1680 image.GetWidth(),
1681 image.GetHeight(),
1682 smaskLenHandle );
1683
1684 fmt::println( m_outputFile, "stream" );
1685
1686 long smaskStreamStart = ftell( m_outputFile );
1687
1688 {
1689 wxFFileOutputStream ffos( outputFFile );
1690 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1691 wxDataOutputStream dos( zos );
1692
1694 }
1695
1696 long smaskStreamSize = ftell( m_outputFile ) - smaskStreamStart;
1697
1698 fmt::print( m_outputFile, "\nendstream\n" );
1700
1701 startPdfObject( smaskLenHandle );
1702 fmt::println( m_outputFile, "{}", (unsigned) smaskStreamSize );
1704 }
1705
1706 outputFFile.Detach(); // Don't close it
1707 }
1708
1709 for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
1710 {
1711 BOX2D box = linkPair.first;
1712 wxString url = linkPair.second;
1713
1714 startPdfObject( linkHandle );
1715
1716 fmt::print( m_outputFile,
1717 "<<\n"
1718 "/Type /Annot\n"
1719 "/Subtype /Link\n"
1720 "/Rect [{} {} {} {}]\n"
1721 "/Border [16 16 0]\n",
1725 encodeDoubleForPlotter( box.GetTop() ) );
1726
1727 wxString pageNumber;
1728 bool pageFound = false;
1729
1730 if( EDA_TEXT::IsGotoPageHref( url, &pageNumber ) )
1731 {
1732 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1733 {
1734 if( m_pageNumbers[ii] == pageNumber )
1735 {
1736 fmt::print( m_outputFile,
1737 "/Dest [{} 0 R /FitB]\n"
1738 ">>\n",
1739 m_pageHandles[ii] );
1740
1741 pageFound = true;
1742 break;
1743 }
1744 }
1745
1746 if( !pageFound )
1747 {
1748 // destination page is not being plotted, assign the NOP action to the link
1749 fmt::print( m_outputFile,
1750 "/A << /Type /Action /S /NOP >>\n"
1751 ">>\n" );
1752 }
1753 }
1754 else
1755 {
1756 if( m_project )
1757 url = ResolveUriByEnvVars( url, m_project );
1758
1759 fmt::print( m_outputFile,
1760 "/A << /Type /Action /S /URI /URI {} >>\n"
1761 ">>\n",
1762 encodeStringForPlotter( url ) );
1763 }
1764
1766 }
1767
1768 for( const auto& [ menuHandle, menuPair ] : m_hyperlinkMenuHandles )
1769 {
1770 const BOX2D& box = menuPair.first;
1771 const std::vector<wxString>& urls = menuPair.second;
1772 wxString js = wxT( "ShM([\n" );
1773
1774 for( const wxString& url : urls )
1775 {
1776 if( url.StartsWith( "!" ) )
1777 {
1778 wxString property = url.AfterFirst( '!' );
1779
1780 if( property.Find( "http:" ) >= 0 )
1781 {
1782 wxString href = property.substr( property.Find( "http:" ) );
1783
1784 if( m_project )
1785 href = ResolveUriByEnvVars( href, m_project );
1786
1787 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ), EscapeString( property, CTX_JS_STR ),
1788 EscapeString( href, CTX_JS_STR ) );
1789 }
1790 else if( property.Find( "https:" ) >= 0 )
1791 {
1792 wxString href = property.substr( property.Find( "https:" ) );
1793
1794 if( m_project )
1795 href = ResolveUriByEnvVars( href, m_project );
1796
1797 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ), EscapeString( property, CTX_JS_STR ),
1798 EscapeString( href, CTX_JS_STR ) );
1799 }
1800 else if( property.Find( "file:" ) >= 0 )
1801 {
1802 wxString href = property.substr( property.Find( "file:" ) );
1803
1804 if( m_project )
1805 href = ResolveUriByEnvVars( href, m_project );
1806
1807 href = NormalizeFileUri( href );
1808 wxString displayText = property.substr( 0, property.Find( "file:" ) ) + href;
1809
1810 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ), EscapeString( displayText, CTX_JS_STR ),
1811 EscapeString( href, CTX_JS_STR ) );
1812 }
1813 else
1814 {
1815 // Legacy fallback
1816 int eqPos = property.Find( wxS( " = " ) );
1817 wxString href;
1818 bool converted = false;
1819
1820 if( eqPos != wxNOT_FOUND )
1821 {
1822 href = property.Mid( eqPos + 3 );
1823
1824 if( m_project )
1825 href = ResolveUriByEnvVars( href, m_project );
1826
1827 if( href.StartsWith( wxS( "/" ) ) || href.StartsWith( wxS( "${" ) )
1828 || ( href.Length() >= 2 && wxIsalpha( href[0] ) && href[1] == ':' )
1829 || href.StartsWith( wxS( "\\\\" ) ) )
1830 {
1831 if( !href.StartsWith( wxS( "/" ) ) )
1832 {
1833 href.Replace( wxS( "\\" ), wxS( "/" ) );
1834
1835 if( href.StartsWith( wxS( "//" ) ) )
1836 href = wxS( "file:" ) + href;
1837 else
1838 href = wxS( "file:///" ) + href;
1839 }
1840 else
1841 {
1842 href = wxS( "file://" ) + href;
1843 }
1844
1845 href = NormalizeFileUri( href );
1846 converted = true;
1847 }
1848 }
1849
1850 if( converted )
1851 {
1852 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1853 EscapeString( property, CTX_JS_STR ),
1854 EscapeString( href, CTX_JS_STR ) );
1855 }
1856 else
1857 {
1858 js += wxString::Format( wxT( "[\"%s\"],\n" ), EscapeString( property, CTX_JS_STR ) );
1859 }
1860 }
1861 }
1862 else if( url.StartsWith( "#" ) )
1863 {
1864 wxString pageNumber = url.AfterFirst( '#' );
1865
1866 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1867 {
1868 if( m_pageNumbers[ii] == pageNumber )
1869 {
1870 wxString menuText = wxString::Format( _( "Show Page %s" ), pageNumber );
1871
1872 js += wxString::Format( wxT( "[\"%s\", \"#%d\"],\n" ),
1873 EscapeString( menuText, CTX_JS_STR ),
1874 static_cast<int>( ii ) );
1875 break;
1876 }
1877 }
1878 }
1879 else
1880 {
1881 wxString href = url;
1882
1883 if( m_project )
1884 href = ResolveUriByEnvVars( href, m_project );
1885
1886 // Convert bare file paths to file:// URIs (legacy support)
1887 if( !href.StartsWith( wxS( "http:" ) )
1888 && !href.StartsWith( wxS( "https:" ) )
1889 && !href.StartsWith( wxS( "file:" ) ) )
1890 {
1891 if( href.StartsWith( wxS( "/" ) ) || href.StartsWith( wxS( "${" ) ) )
1892 {
1893 href = wxS( "file://" ) + href;
1894 }
1895 else if( href.Length() >= 2 && wxIsalpha( href[0] ) && href[1] == ':' )
1896 {
1897 href.Replace( wxS( "\\" ), wxS( "/" ) );
1898 href = wxS( "file:///" ) + href;
1899 }
1900 else if( href.StartsWith( wxS( "\\\\" ) ) )
1901 {
1902 href.Replace( wxS( "\\" ), wxS( "/" ) );
1903 href = wxS( "file:" ) + href;
1904 }
1905 }
1906
1907 if( href.StartsWith( wxS( "file:" ) ) )
1908 href = NormalizeFileUri( href );
1909
1910 if( href.StartsWith( wxS( "http:" ) )
1911 || href.StartsWith( wxS( "https:" ) )
1912 || href.StartsWith( wxS( "file:" ) ) )
1913 {
1914 wxString menuText = wxString::Format( _( "Open %s" ), href );
1915
1916 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1917 EscapeString( menuText, CTX_JS_STR ),
1918 EscapeString( href, CTX_JS_STR ) );
1919 }
1920 }
1921 }
1922
1923 js += wxT( "]);" );
1924
1925 startPdfObject( menuHandle );
1926
1927 fmt::print( m_outputFile,
1928 "<<\n"
1929 "/Type /Annot\n"
1930 "/Subtype /Link\n"
1931 "/Rect [{} {} {} {}]\n"
1932 "/Border [16 16 0]\n",
1936 encodeDoubleForPlotter( box.GetTop() ) );
1937
1938 fmt::print( m_outputFile,
1939 "/A << /Type /Action /S /JavaScript /JS {} >>\n"
1940 ">>\n",
1941 encodeStringForPlotter( js ) );
1942
1944 }
1945
1946 {
1948
1949 wxString js = R"JS(
1950function ShM(aEntries) {
1951 var aParams = [];
1952 for (var i = 0; i < aEntries.length; ++i) {
1953 aParams.push({
1954 cName: aEntries[i][0],
1955 cReturn: aEntries[i].length > 1 ? aEntries[i][1] : ''
1956 })
1957 }
1958
1959 var cChoice = app.popUpMenuEx.apply(app, aParams);
1960 if (cChoice == null || cChoice == '') return;
1961
1962 if (cChoice.substring(0, 1) == '#') {
1963 this.pageNum = parseInt(cChoice.slice(1));
1964 return;
1965 }
1966
1967 // Fallback: some viewers return cName instead of cReturn
1968 var url = cChoice;
1969 if (url.substring(0, 4) != 'http' && url.substring(0, 4) != 'file') {
1970 var idx = url.indexOf('http');
1971 if (idx < 0) idx = url.indexOf('file:');
1972 if (idx >= 0) url = url.substring(idx);
1973 else return;
1974 }
1975
1976 if (url.substring(0, 8) == 'file:///') app.openDoc(url.substring(7));
1977 else if (url.substring(0, 7) == 'file://') app.openDoc('//' + url.substring(7));
1978 else app.launchURL(url);
1979}
1980)JS";
1981
1982 fmt::print( m_outputFile,
1983 "<< /JavaScript\n"
1984 " << /Names\n"
1985 " [ (JSInit) << /Type /Action /S /JavaScript /JS {} >> ]\n"
1986 " >>\n"
1987 ">>\n",
1988 encodeStringForPlotter( js ) );
1989
1991 }
1992}
1993
1994
1996{
1997 // We can end up here if there was nothing to plot
1998 if( !m_outputFile )
1999 return false;
2000
2001 // Close the current page (often the only one)
2002 ClosePage();
2003
2004 if( !m_3dExportMode )
2006
2007 /* The page tree: it's a B-tree but luckily we only have few pages!
2008 So we use just an array... The handle was allocated at the beginning,
2009 now we instantiate the corresponding object */
2011 fmt::print( m_outputFile,
2012 "<<\n"
2013 "/Type /Pages\n"
2014 "/Kids [\n" );
2015
2016 for( unsigned i = 0; i < m_pageHandles.size(); i++ )
2017 fmt::println( m_outputFile, "{} 0 R", m_pageHandles[i] );
2018
2019 fmt::print( m_outputFile,
2020 "]\n"
2021 "/Count {}\n"
2022 ">>\n", m_pageHandles.size() );
2024
2025 int infoDictHandle = startPdfObject();
2026
2027 std::time_t time = std::time( nullptr );
2028 std::tm tm{};
2029#if defined( _WIN32 ) || defined( _MSC_VER )
2030 localtime_s( &tm, &time );
2031#else
2032 localtime_r( &time, &tm );
2033#endif
2034 std::string dt = fmt::format( "D:{:%Y:%m:%d:%H:%M:%S}", tm );
2035
2036 if( m_title.IsEmpty() )
2037 {
2038 // Windows uses '\' and other platforms use '/' as separator
2039 m_title = m_filename.AfterLast( '\\' );
2040 m_title = m_title.AfterLast( '/' );
2041 }
2042
2043 fmt::print( m_outputFile,
2044 "<<\n"
2045 "/Producer (KiCad PDF)\n"
2046 "/CreationDate ({})\n"
2047 "/Creator {}\n"
2048 "/Title {}\n"
2049 "/Author {}\n"
2050 "/Subject {}\n",
2051 dt,
2056
2057 fmt::println( m_outputFile, ">>" );
2059
2060 // Let's dump in the outline
2061 int outlineHandle = -1;
2062
2063 if( !m_3dExportMode )
2064 outlineHandle = emitOutline();
2065
2066 // The catalog, at last
2067 int catalogHandle = startPdfObject();
2068
2069 if( outlineHandle > 0 )
2070 {
2071 fmt::println( m_outputFile,
2072 "<<\n"
2073 "/Type /Catalog\n"
2074 "/Pages {} 0 R\n"
2075 "/Version /1.5\n"
2076 "/PageMode /UseOutlines\n"
2077 "/Outlines {} 0 R\n"
2078 "/Names {} 0 R\n"
2079 "/PageLayout /SinglePage\n"
2080 ">>",
2082 outlineHandle,
2084 }
2085 else
2086 {
2087 fmt::println( m_outputFile,
2088 "<<\n"
2089 "/Type /Catalog\n"
2090 "/Pages {} 0 R\n"
2091 "/Version /1.5\n"
2092 "/PageMode /UseNone\n"
2093 "/PageLayout /SinglePage\n"
2094 ">>",
2096 }
2097
2099
2100 /* Emit the xref table (format is crucial to the byte, each entry must
2101 be 20 bytes long, and object zero must be done in that way). Also
2102 the offset must be kept along for the trailer */
2103 long xref_start = ftell( m_outputFile );
2104 fmt::print( m_outputFile,
2105 "xref\n"
2106 "0 {}\n"
2107 "0000000000 65535 f \n",
2108 m_xrefTable.size() );
2109
2110 for( unsigned i = 1; i < m_xrefTable.size(); i++ )
2111 fmt::print( m_outputFile, "{:010d} 00000 n \n", m_xrefTable[i] );
2112
2113 // Done the xref, go for the trailer
2114 fmt::print( m_outputFile,
2115 "trailer\n"
2116 "<< /Size {} /Root {} 0 R /Info {} 0 R >>\n"
2117 "startxref\n"
2118 "{}\n" // The offset we saved before
2119 "%%EOF\n",
2120 m_xrefTable.size(),
2121 catalogHandle,
2122 infoDictHandle,
2123 xref_start );
2124
2125 fclose( m_outputFile );
2126 m_outputFile = nullptr;
2127
2128 return true;
2129}
2130
2131
2132void PDF_PLOTTER::Text( const VECTOR2I& aPos, const COLOR4D& aColor, const wxString& aText, const EDA_ANGLE& aOrient,
2133 const VECTOR2I& aSize, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify,
2134 int aWidth, bool aItalic, bool aBold, bool aMultilineAllowed, KIFONT::FONT* aFont,
2135 const KIFONT::METRICS& aFontMetrics, void* aData )
2136{
2137 // PDF files do not like 0 sized texts which create broken files.
2138 if( aSize.x == 0 || aSize.y == 0 )
2139 return;
2140
2141 wxString text( aText );
2142
2143 if( text.Contains( wxS( "@{" ) ) )
2144 {
2145 EXPRESSION_EVALUATOR evaluator;
2146 text = evaluator.Evaluate( text );
2147 }
2148
2149 if( !aFont )
2150 aFont = KIFONT::FONT::GetFont( m_renderSettings->GetDefaultFont() );
2151
2152 if( aFont->IsOutline() )
2153 {
2154 KIFONT::OUTLINE_FONT* outlineFont = static_cast<KIFONT::OUTLINE_FONT*>( aFont );
2156
2159 {
2160 // If we're not allowed to embed the fonts, then the PDF text plotting code won't work. In that
2161 // case we have to fall back to the standard text plotting (using a CALLBACK_GAL), and render
2162 // phantom text (which will be searchable) behind the stroke font. This is a long way from ideal,
2163 // but it is what it is.
2164 int render_mode = 3; // invisible
2165
2166 VECTOR2I pos( aPos );
2167 const char *fontname = aItalic ? ( aBold ? "/KicadFontBI" : "/KicadFontI" )
2168 : ( aBold ? "/KicadFontB" : "/KicadFont" );
2169
2170 m_usedBase14Fonts = true;
2171
2172 // Compute the copious transformation parameters of the Current Transform Matrix
2173 double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
2174 double wideningFactor, heightFactor;
2175
2176 VECTOR2I t_size( std::abs( aSize.x ), std::abs( aSize.y ) );
2177 bool textMirrored = aSize.x < 0;
2178
2179 computeTextParameters( aPos, text, aOrient, t_size, textMirrored, aH_justify, aV_justify, aWidth,
2180 aItalic, aBold, &wideningFactor, &ctm_a, &ctm_b, &ctm_c, &ctm_d, &ctm_e, &ctm_f,
2181 &heightFactor );
2182
2183 SetColor( aColor );
2184 SetCurrentLineWidth( aWidth, aData );
2185
2186 wxStringTokenizer str_tok( text, " ", wxTOKEN_RET_DELIMS );
2187
2188 VECTOR2I full_box( aFont->StringBoundaryLimits( text, t_size, aWidth, aBold, aItalic, aFontMetrics ) );
2189
2190 if( textMirrored )
2191 full_box.x *= -1;
2192
2193 VECTOR2I box_x( full_box.x, 0 );
2194 VECTOR2I box_y( 0, full_box.y );
2195
2196 RotatePoint( box_x, aOrient );
2197 RotatePoint( box_y, aOrient );
2198
2199 if( aH_justify == GR_TEXT_H_ALIGN_CENTER )
2200 pos -= box_x / 2;
2201 else if( aH_justify == GR_TEXT_H_ALIGN_RIGHT )
2202 pos -= box_x;
2203
2204 if( aV_justify == GR_TEXT_V_ALIGN_CENTER )
2205 pos += box_y / 2;
2206 else if( aV_justify == GR_TEXT_V_ALIGN_TOP )
2207 pos += box_y;
2208
2209 while( str_tok.HasMoreTokens() )
2210 {
2211 wxString word = str_tok.GetNextToken();
2212
2213 computeTextParameters( pos, word, aOrient, t_size, textMirrored, GR_TEXT_H_ALIGN_LEFT,
2214 GR_TEXT_V_ALIGN_BOTTOM, aWidth, aItalic, aBold, &wideningFactor,
2215 &ctm_a, &ctm_b, &ctm_c, &ctm_d, &ctm_e, &ctm_f, &heightFactor );
2216
2217 // Extract the changed width and rotate by the orientation to get the offset for the
2218 // next word
2219 VECTOR2I bbox( aFont->StringBoundaryLimits( word, t_size, aWidth, aBold, aItalic, aFontMetrics ).x, 0 );
2220
2221 if( textMirrored )
2222 bbox.x *= -1;
2223
2224 RotatePoint( bbox, aOrient );
2225 pos += bbox;
2226
2227 // Don't try to output a blank string
2228 if( word.Trim( false ).Trim( true ).empty() )
2229 continue;
2230
2231 /* We use the full CTM instead of the text matrix because the same
2232 coordinate system will be used for the overlining. Also the %f
2233 for the trig part of the matrix to avoid %g going in exponential
2234 format (which is not supported) */
2235 fmt::print( m_workFile, "q {:f} {:f} {:f} {:f} {:f} {:f} cm BT {} {:g} Tf {} Tr {:g} Tz ",
2236 ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f,
2237 fontname,
2238 heightFactor,
2239 render_mode,
2240 wideningFactor * 100 );
2241
2242 std::string txt_pdf = encodeStringForPlotter( word );
2243 fmt::println( m_workFile, "{} Tj ET", txt_pdf );
2244 // Restore the CTM
2245 fmt::println( m_workFile, "Q" );
2246 }
2247
2248 // Plot the text
2249 PLOTTER::Text( aPos, aColor, text, aOrient, aSize, aH_justify, aV_justify, aWidth, aItalic,
2250 aBold, aMultilineAllowed, aFont, aFontMetrics, aData );
2251
2252 return;
2253 }
2254 }
2255
2256 SetColor( aColor );
2257 SetCurrentLineWidth( aWidth, aData );
2258
2259 VECTOR2I t_size( std::abs( aSize.x ), std::abs( aSize.y ) );
2260 bool textMirrored = aSize.x < 0;
2261
2262 if( aWidth == 0 && aBold )
2263 aWidth = GetPenSizeForBold( std::min( t_size.x, t_size.y ) );
2264
2265 if( aWidth < 0 )
2266 aWidth = -aWidth;
2267
2268 if( !aFont )
2269 aFont = KIFONT::FONT::GetFont( m_renderSettings->GetDefaultFont() );
2270
2271 auto computeAlignedStartPos =
2272 [&]()
2273 {
2274 VECTOR2I startPos( aPos );
2275
2276 if( aFont->IsStroke() )
2277 {
2278 TEXT_ATTRIBUTES alignAttrs;
2279 alignAttrs.m_Size = t_size;
2280 alignAttrs.m_StrokeWidth = aWidth;
2281 alignAttrs.m_Halign = aH_justify;
2282 alignAttrs.m_Valign = aV_justify;
2283 alignAttrs.m_Bold = aBold;
2284 alignAttrs.m_Italic = aItalic;
2285
2286 // getLinePositions returns anchor + offset; use (0,0) to get the offset alone.
2287 VECTOR2I drawOffset = aFont->GetAlignedDrawPosition( text, VECTOR2I( 0, 0 ), alignAttrs,
2288 aFontMetrics );
2289
2290 // GAL mirrors about the text anchor (GetDrawPos), after placing the unmirrored
2291 // cursor. Negating the X offset before rotation makes the Type3 Tz=-100 origin
2292 // land on the mirrored start so ink sits on the correct side of the anchor.
2293 if( textMirrored )
2294 drawOffset.x = -drawOffset.x;
2295
2296 RotatePoint( drawOffset, aOrient );
2297 startPos = aPos + drawOffset;
2298 }
2299 else
2300 {
2301 VECTOR2I full_box( aFont->StringBoundaryLimits( text, t_size, aWidth, aBold, aItalic,
2302 aFontMetrics ) );
2303
2304 if( textMirrored )
2305 full_box.x *= -1;
2306
2307 VECTOR2I box_x( full_box.x, 0 );
2308 VECTOR2I box_y( 0, full_box.y );
2309
2310 RotatePoint( box_x, aOrient );
2311 RotatePoint( box_y, aOrient );
2312
2313 if( aH_justify == GR_TEXT_H_ALIGN_CENTER )
2314 startPos -= box_x / 2;
2315 else if( aH_justify == GR_TEXT_H_ALIGN_RIGHT )
2316 startPos -= box_x;
2317
2318 if( aV_justify == GR_TEXT_V_ALIGN_CENTER )
2319 startPos += box_y / 2;
2320 else if( aV_justify == GR_TEXT_V_ALIGN_TOP )
2321 startPos += box_y;
2322 }
2323
2324 return startPos;
2325 };
2326
2327 // Parse the text for markup
2328 // IMPORTANT: Use explicit UTF-8 encoding. wxString::ToStdString() is locale-dependent
2329 // and under C/POSIX locale can drop or mangle non-ASCII, leading to missing CMaps.
2330 // The markup parser expects UTF-8 bytes.
2331 UTF8 utf8Text( text );
2332 MARKUP::MARKUP_PARSER markupParser( utf8Text.substr() );
2333 std::unique_ptr<MARKUP::NODE> markupTree( markupParser.Parse() );
2334
2335 if( !markupTree )
2336 {
2337 wxLogTrace( tracePdfPlotter, "PDF_PLOTTER::Text: Markup parsing failed, falling back to plain text." );
2338 // Fallback to simple text rendering if parsing fails
2339 wxStringTokenizer str_tok( text, " ", wxTOKEN_RET_DELIMS );
2340 VECTOR2I pos = computeAlignedStartPos();
2341
2342 while( str_tok.HasMoreTokens() )
2343 {
2344 wxString word = str_tok.GetNextToken();
2345 pos = renderWord( word, pos, t_size, aOrient, textMirrored, aWidth, aBold, aItalic, aFont,
2346 aFontMetrics, aV_justify, 0 );
2347 }
2348 return;
2349 }
2350
2351 VECTOR2I pos = computeAlignedStartPos();
2352
2353 // Render markup tree
2354 std::vector<OVERBAR_INFO> overbars;
2355 renderMarkupNode( markupTree.get(), pos, t_size, aOrient, textMirrored, aWidth, aBold, aItalic, aFont,
2356 aFontMetrics, aV_justify, 0, overbars );
2357
2358 // Draw any overbars that were accumulated
2359 drawOverbars( overbars, aOrient, aFontMetrics );
2360}
2361
2362
2363VECTOR2I PDF_PLOTTER::renderWord( const wxString& aWord, const VECTOR2I& aPosition, const VECTOR2I& aSize,
2364 const EDA_ANGLE& aOrient, bool aTextMirrored, int aWidth, bool aBold, bool aItalic,
2365 KIFONT::FONT* aFont, const KIFONT::METRICS& aFontMetrics,
2366 enum GR_TEXT_V_ALIGN_T aV_justify, TEXT_STYLE_FLAGS aTextStyle )
2367{
2368 if( wxGetEnv( "KICAD_DEBUG_SYN_STYLE", nullptr ) )
2369 {
2370 int styleFlags = 0;
2371
2372 if( aFont->IsOutline() )
2373 {
2374 if( const FT_Face& face = static_cast<KIFONT::OUTLINE_FONT*>( aFont )->GetFace() )
2375 styleFlags = (int) face->style_flags;
2376 }
2377
2378 wxLogTrace( tracePdfPlotter, "renderWord enter word='%s' bold=%d italic=%d textStyle=%u styleFlags=%d",
2379 TO_UTF8( aWord ), (int) aBold, (int) aItalic, (unsigned) aTextStyle, styleFlags );
2380 }
2381
2382 // Don't try to output a blank string, but handle space characters for word separation
2383 if( aWord.empty() )
2384 return aPosition;
2385
2386 // Compute the per-word cursor advance via the font's own glyph metrics so the gap between
2387 // words matches what the PDF Tj operator further down will produce. StringBoundaryLimits
2388 // would inflate the stroke-font bbox by 3*thickness, opening spurious whitespace between
2389 // words (issue #24419).
2390 //
2391 // Only BOLD/ITALIC from the caller are forwarded; SUPERSCRIPT/SUBSCRIPT in aTextStyle have
2392 // already been baked into aSize by renderMarkupNode, and Tj renders with that reduced Tf
2393 // size, so GetTextAsGlyphs must not apply the SUPER_SUB_SIZE_MULTIPLIER a second time.
2394 TEXT_STYLE_FLAGS metricsStyle = 0;
2395
2396 if( aBold )
2397 metricsStyle |= TEXT_STYLE::BOLD;
2398
2399 if( aItalic )
2400 metricsStyle |= TEXT_STYLE::ITALIC;
2401
2402 auto cursorAdvanceX =
2403 [&]( const wxString& aText )
2404 {
2405 return aFont->GetTextAsGlyphs( nullptr, nullptr, aText, aSize, VECTOR2I(), ANGLE_0,
2406 false, VECTOR2I(), metricsStyle ).x;
2407 };
2408
2409 // If the word is just a space character, advance position by space width and continue
2410 if( aWord == wxT( " " ) )
2411 {
2412 VECTOR2I spaceBox( cursorAdvanceX( wxT( " " ) ), 0 );
2413
2414 if( aTextMirrored )
2415 spaceBox.x *= -1;
2416
2417 VECTOR2I rotatedSpaceBox = spaceBox;
2418 RotatePoint( rotatedSpaceBox, aOrient );
2419 return aPosition + rotatedSpaceBox;
2420 }
2421
2422 // Tabs are layout only. Plot visible runs at font layout positions.
2423 if( aWord.Contains( wxT( '\t' ) ) )
2424 {
2425 auto positionedAdvance =
2426 [&]( const wxString& aText )
2427 {
2428 VECTOR2I advance( cursorAdvanceX( aText ), 0 );
2429
2430 if( aTextMirrored )
2431 advance.x *= -1;
2432
2433 RotatePoint( advance, aOrient );
2434 return advance;
2435 };
2436
2437 wxString prefix;
2438 wxString segment;
2439
2440 auto flushSegment =
2441 [&]()
2442 {
2443 if( !segment.IsEmpty() )
2444 {
2445 renderWord( segment, aPosition + positionedAdvance( prefix ), aSize, aOrient, aTextMirrored,
2446 aWidth, aBold, aItalic, aFont, aFontMetrics, aV_justify, aTextStyle );
2447 prefix += segment;
2448 segment.clear();
2449 }
2450 };
2451
2452 for( wxUniChar c : aWord )
2453 {
2454 if( c == '\t' )
2455 {
2456 flushSegment();
2457 prefix += c;
2458 }
2459 else
2460 {
2461 segment += c;
2462 }
2463 }
2464
2465 flushSegment();
2466
2467 return aPosition + positionedAdvance( aWord );
2468 }
2469
2470 // Compute transformation parameters for this word
2471 double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
2472 double wideningFactor, heightFactor;
2473
2474 computeTextParameters( aPosition, aWord, aOrient, aSize, aTextMirrored, GR_TEXT_H_ALIGN_LEFT,
2475 GR_TEXT_V_ALIGN_BOTTOM, aWidth, aItalic, aBold, &wideningFactor,
2476 &ctm_a, &ctm_b, &ctm_c, &ctm_d, &ctm_e, &ctm_f, &heightFactor );
2477
2478 VECTOR2I bbox( cursorAdvanceX( aWord ), 0 );
2479
2480 if( aTextMirrored )
2481 bbox.x *= -1;
2482
2483 RotatePoint( bbox, aOrient );
2484 VECTOR2I nextPos = aPosition + bbox;
2485
2486 // Apply vertical offset for subscript/superscript
2487 // Stroke font positioning (baseline) already correct per user feedback.
2488 // Outline fonts need: superscript +1 full font height higher; subscript +1 full font height higher
2489 if( aTextStyle & TEXT_STYLE::SUPERSCRIPT )
2490 {
2491 double factor = aFont->IsOutline() ? 0.050 : 0.030; // stroke original ~0.40, outline needs +1.0
2492 VECTOR2I offset( 0, static_cast<int>( std::lround( aSize.y * factor ) ) );
2493 RotatePoint( offset, aOrient );
2494 ctm_e -= offset.x;
2495 ctm_f += offset.y; // Note: PDF Y increases upward
2496 }
2497 else if( aTextStyle & TEXT_STYLE::SUBSCRIPT )
2498 {
2499 // For outline fonts raise by one font height versus stroke (which shifts downward slightly)
2500 VECTOR2I offset( 0, 0 );
2501
2502 if( aFont->IsStroke() )
2503 offset.y = static_cast<int>( std::lround( aSize.y * 0.01 ) );
2504
2505 RotatePoint( offset, aOrient );
2506 ctm_e += offset.x;
2507 ctm_f -= offset.y; // Note: PDF Y increases upward
2508 }
2509
2510 // Render the word using existing outline font logic
2511 if( aFont->IsOutline() )
2512 {
2513 std::vector<PDF_OUTLINE_FONT_RUN> outlineRuns;
2514
2516 {
2517 m_outlineFontManager->EncodeString( aWord, static_cast<KIFONT::OUTLINE_FONT*>( aFont ),
2518 ( aItalic || ( aTextStyle & TEXT_STYLE::ITALIC ) ),
2519 ( aBold || ( aTextStyle & TEXT_STYLE::BOLD ) ),
2520 &outlineRuns );
2521 }
2522
2523 if( !outlineRuns.empty() )
2524 {
2525 // Apply baseline adjustment (keeping existing logic)
2526 double baseline_factor = 0.17;
2527 double alignment_multiplier = 1.0;
2528
2529 if( aV_justify == GR_TEXT_V_ALIGN_CENTER )
2530 alignment_multiplier = 2.0;
2531 else if( aV_justify == GR_TEXT_V_ALIGN_TOP )
2532 alignment_multiplier = 4.0;
2533
2534 VECTOR2D font_size_dev = userToDeviceSize( aSize );
2535 double baseline_adjustment = font_size_dev.y * baseline_factor * alignment_multiplier;
2536
2537 double adjusted_ctm_e = ctm_e;
2538 double adjusted_ctm_f = ctm_f;
2539
2540 double angle_rad = aOrient.AsRadians();
2541 double cos_angle = cos( angle_rad );
2542 double sin_angle = sin( angle_rad );
2543
2544 adjusted_ctm_e = ctm_e - baseline_adjustment * sin_angle;
2545 adjusted_ctm_f = ctm_f + baseline_adjustment * cos_angle;
2546
2547 double adj_c = ctm_c;
2548 double adj_d = ctm_d;
2549
2550 // Synthetic italic (shear) for outline font if requested but font not intrinsically italic
2551 bool syntheticItalicApplied = false;
2552 double appliedTilt = 0.0;
2553 double syn_c = adj_c;
2554 double syn_d = adj_d;
2555 double syn_a = ctm_a;
2556 double syn_b = ctm_b;
2557 bool wantItalic = ( aItalic || ( aTextStyle & TEXT_STYLE::ITALIC ) );
2558
2559 if( std::getenv( "KICAD_FORCE_SYN_ITALIC" ) )
2560 wantItalic = true; // debug: ensure path triggers when forcing synthetic italic
2561
2562 bool wantBold = ( aBold || ( aTextStyle & TEXT_STYLE::BOLD ) );
2563 bool fontIsItalic = aFont->IsItalic();
2564 bool fontIsBold = aFont->IsBold();
2565 bool fontIsFakeItalic = static_cast<KIFONT::OUTLINE_FONT*>( aFont )->IsFakeItalic();
2566 bool fontIsFakeBold = static_cast<KIFONT::OUTLINE_FONT*>( aFont )->IsFakeBold();
2567
2568 // Environment overrides for testing synthetic italics:
2569 // KICAD_FORCE_SYN_ITALIC=1 forces synthetic shear even if font has italic face
2570 // KICAD_SYN_ITALIC_TILT=<float degrees or tangent?>: if value contains 'deg' treat as degrees,
2571 // otherwise treat as raw tilt factor (x += tilt*y)
2572 bool forceSynItalic = false;
2573 double overrideTilt = 0.0;
2574
2575 if( const char* envForce = std::getenv( "KICAD_FORCE_SYN_ITALIC" ) )
2576 {
2577 if( *envForce != '\0' && *envForce != '0' )
2578 forceSynItalic = true;
2579 }
2580
2581 if( const char* envTilt = std::getenv( "KICAD_SYN_ITALIC_TILT" ) )
2582 {
2583 std::string tiltStr( envTilt );
2584
2585 try
2586 {
2587 if( tiltStr.find( "deg" ) != std::string::npos )
2588 {
2589 double deg = std::stod( tiltStr );
2590 overrideTilt = tan( deg * M_PI / 180.0 );
2591 }
2592 else
2593 {
2594 overrideTilt = std::stod( tiltStr );
2595 }
2596 }
2597 catch( ... )
2598 {
2599 overrideTilt = 0.0; // ignore malformed
2600 }
2601 }
2602
2603 // Trace after we know style flags
2604 wxLogTrace( tracePdfPlotter, "Outline path word='%s' runs=%zu wantItalic=%d fontIsItalic=%d "
2605 "fontIsFakeItalic=%d wantBold=%d fontIsBold=%d fontIsFakeBold=%d forceSyn=%d",
2606 TO_UTF8( aWord ), outlineRuns.size(), (int) wantItalic, (int) fontIsItalic,
2607 (int) fontIsFakeItalic, (int) wantBold, (int) fontIsBold, (int) fontIsFakeBold,
2608 (int) forceSynItalic );
2609
2610 // Apply synthetic italic if:
2611 // - Italic requested AND outline font
2612 // - And either forceSynItalic env var set OR there is no REAL italic face.
2613 // (A fake italic flag from fontconfig substitution should NOT block synthetic shear.)
2614 bool realItalicFace = fontIsItalic && !fontIsFakeItalic;
2615
2616 if( wantItalic && ( forceSynItalic || !realItalicFace ) )
2617 {
2618 // We want to apply a horizontal shear so that x' = x + tilt * y in the glyph's
2619 // local coordinate system BEFORE rotation. The existing text matrix columns are:
2620 // first column = (a, b)^T -> x-axis direction & scale
2621 // second column = (c, d)^T -> y-axis direction & scale
2622 // Prepending a shear matrix S = [[1 tilt][0 1]] (i.e. T' = T * S is WRONG here).
2623 // We need to LEFT-multiply: T' = R * S where R is the original rotation/scale.
2624 // Left multiplication keeps first column unchanged and adds (tilt * firstColumn)
2625 // to the second column: (c', d') = (c + tilt * a, d + tilt * b).
2626 // This produces a right-leaning italic for positive tilt.
2627 double tilt = ( overrideTilt != 0.0 ) ? overrideTilt : ITALIC_TILT;
2628
2629 if( wideningFactor < 0 ) // mirrored text should mirror the shear
2630 tilt = -tilt;
2631
2632 syn_c = adj_c + tilt * syn_a;
2633 syn_d = adj_d + tilt * syn_b;
2634 appliedTilt = tilt;
2635 syntheticItalicApplied = true;
2636
2637 wxLogTrace( tracePdfPlotter, "Synthetic italic shear applied: tilt=%f a=%f b=%f c->%f d->%f",
2638 tilt, syn_a, syn_b, syn_c, syn_d );
2639 }
2640
2641 if( wantBold && !fontIsBold )
2642 {
2643 // Slight horizontal widening to simulate bold (~3%)
2644 syn_a *= 1.03;
2645 syn_b *= 1.03;
2646 }
2647
2648 if( syntheticItalicApplied )
2649 {
2650 // PDF comment to allow manual inspection in the output stream
2651 fmt::print( m_workFile, "% syn-italic tilt={} a={} b={} c={} d={}\n",
2652 appliedTilt, syn_a, syn_b, syn_c, syn_d );
2653 }
2654
2655 fmt::print( m_workFile, "q {:f} {:f} {:f} {:f} {:f} {:f} cm BT {} Tr {} Tz ",
2656 syn_a, syn_b, syn_c, syn_d, adjusted_ctm_e, adjusted_ctm_f,
2657 0, // render_mode
2658 encodeDoubleForPlotter( wideningFactor * 100 ) );
2659
2660 for( const PDF_OUTLINE_FONT_RUN& run : outlineRuns )
2661 {
2662 fmt::print( m_workFile, "{} {} Tf <",
2663 run.m_subset->ResourceName(),
2664 encodeDoubleForPlotter( heightFactor ) );
2665
2666 for( const PDF_OUTLINE_FONT_GLYPH& glyph : run.m_glyphs )
2667 {
2668 fmt::print( m_workFile, "{:02X}{:02X}",
2669 static_cast<unsigned char>( ( glyph.cid >> 8 ) & 0xFF ),
2670 static_cast<unsigned char>( glyph.cid & 0xFF ) );
2671 }
2672
2673 fmt::print( m_workFile, "> Tj " );
2674 }
2675
2676 fmt::println( m_workFile, "ET" );
2677 fmt::println( m_workFile, "Q" );
2678 }
2679 }
2680 else
2681 {
2682 // Handle stroke fonts
2683 if( !m_strokeFontManager )
2684 return nextPos;
2685
2686 wxLogTrace( tracePdfPlotter, "Stroke path word='%s' wantItalic=%d aItalic=%d aBold=%d",
2687 TO_UTF8( aWord ), (int) ( aItalic || ( aTextStyle & TEXT_STYLE::ITALIC ) ),
2688 (int) aItalic, (int) aBold );
2689
2690 std::vector<PDF_STROKE_FONT_RUN> runs;
2691 m_strokeFontManager->EncodeString( aWord, &runs, aWidth, aSize.x, aSize.y, aBold, aItalic );
2692
2693 if( !runs.empty() )
2694 {
2695 VECTOR2D dev_size = userToDeviceSize( aSize );
2696 double fontSize = dev_size.y;
2697
2698 double adj_c = ctm_c;
2699 double adj_d = ctm_d;
2700
2701 if( aItalic )
2702 {
2703 double tilt = -ITALIC_TILT;
2704
2705 if( wideningFactor < 0 )
2706 tilt = -tilt;
2707
2708 adj_c -= ctm_a * tilt;
2709 adj_d -= ctm_b * tilt;
2710 }
2711
2712 // Cancel m_PDFStrokeFontXOffset / m_PDFStrokeFontYOffset baked into Type3 charprocs.
2713 // Horizontal/vertical anchors are already GAL-aligned in PDF_PLOTTER::Text().
2714 // X offset is stored in aspect-scaled glyph X units, so cancel with device width.
2715 // When Tz mirrors (wideningFactor < 0), glyph X is flipped, so cancel the other way.
2716 const double xOffsetEm = ADVANCED_CFG::GetCfg().m_PDFStrokeFontXOffset;
2717 const double yOffsetEm = ADVANCED_CFG::GetCfg().m_PDFStrokeFontYOffset;
2718 const double xCancelDev = xOffsetEm * dev_size.x;
2719 const double yCancelDev = yOffsetEm * dev_size.y;
2720 const double xSign = ( wideningFactor < 0 ) ? -1.0 : 1.0;
2721
2722 const double adj_ctm_e = ctm_e - yCancelDev * adj_c - xSign * xCancelDev * ctm_a;
2723 const double adj_ctm_f = ctm_f - yCancelDev * adj_d - xSign * xCancelDev * ctm_b;
2724
2725 // Aspect ratio is baked into the Type3 glyph charprocs; Tz only mirrors when needed.
2726 const double tzFactor = wideningFactor < 0 ? -100.0 : 100.0;
2727
2728 fmt::print( m_workFile, "q {:f} {:f} {:f} {:f} {:f} {:f} cm BT {} Tr {} Tz ",
2729 ctm_a, ctm_b, adj_c, adj_d, adj_ctm_e, adj_ctm_f,
2730 0, // render_mode
2731 encodeDoubleForPlotter( tzFactor ) );
2732
2733 for( const PDF_STROKE_FONT_RUN& run : runs )
2734 {
2735 fmt::print( m_workFile, "{} {} Tf {} Tj ",
2736 run.m_subset->ResourceName(),
2737 encodeDoubleForPlotter( fontSize ),
2738 encodeByteString( run.m_bytes ) );
2739 }
2740
2741 fmt::println( m_workFile, "ET" );
2742 fmt::println( m_workFile, "Q" );
2743 }
2744 }
2745
2746 return nextPos;
2747}
2748
2749
2751 const VECTOR2I& aBaseSize, const EDA_ANGLE& aOrient,
2752 bool aTextMirrored, int aWidth, bool aBaseBold, bool aBaseItalic,
2753 KIFONT::FONT* aFont, const KIFONT::METRICS& aFontMetrics,
2754 enum GR_TEXT_V_ALIGN_T aV_justify, TEXT_STYLE_FLAGS aTextStyle,
2755 std::vector<OVERBAR_INFO>& aOverbars )
2756{
2757 VECTOR2I nextPosition = aPosition;
2758
2759 if( !aNode )
2760 return nextPosition;
2761
2762 TEXT_STYLE_FLAGS currentStyle = aTextStyle;
2763 VECTOR2I currentSize = aBaseSize;
2764 bool drawOverbar = false;
2765
2766 // Handle markup node types
2767 if( !aNode->is_root() )
2768 {
2769 if( aNode->isSubscript() )
2770 {
2771 currentStyle |= TEXT_STYLE::SUBSCRIPT;
2772 // Subscript: smaller size and lower position
2773 currentSize = VECTOR2I( aBaseSize.x * 0.5, aBaseSize.y * 0.6 );
2774 }
2775 else if( aNode->isSuperscript() )
2776 {
2777 currentStyle |= TEXT_STYLE::SUPERSCRIPT;
2778 // Superscript: smaller size and higher position
2779 currentSize = VECTOR2I( aBaseSize.x * 0.5, aBaseSize.y * 0.6 );
2780 }
2781
2782 if( aNode->isOverbar() )
2783 {
2784 drawOverbar = true;
2785 // Overbar doesn't change font size, just adds decoration
2786 }
2787
2788 // Render content of this node if it has text
2789 if( aNode->has_content() )
2790 {
2791 wxString nodeText = aNode->asWxString();
2792
2793 // Process text content (simplified version of the main text processing)
2794 wxStringTokenizer str_tok( nodeText, " ", wxTOKEN_RET_DELIMS );
2795
2796 while( str_tok.HasMoreTokens() )
2797 {
2798 wxString word = str_tok.GetNextToken();
2799 nextPosition = renderWord( word, nextPosition, currentSize, aOrient, aTextMirrored, aWidth,
2800 aBaseBold || (currentStyle & TEXT_STYLE::BOLD),
2801 aBaseItalic || (currentStyle & TEXT_STYLE::ITALIC),
2802 aFont, aFontMetrics, aV_justify, currentStyle );
2803 }
2804 }
2805 }
2806
2807 // Process child nodes recursively
2808 for( const std::unique_ptr<MARKUP::NODE>& child : aNode->children )
2809 {
2810 VECTOR2I startPos = nextPosition;
2811
2812 nextPosition = renderMarkupNode( child.get(), nextPosition, currentSize, aOrient, aTextMirrored, aWidth,
2813 aBaseBold, aBaseItalic, aFont, aFontMetrics, aV_justify, currentStyle,
2814 aOverbars );
2815
2816 // Store overbar info for later rendering
2817 if( drawOverbar )
2818 {
2819 VECTOR2I endPos = nextPosition;
2820 aOverbars.push_back( { startPos, endPos, currentSize, aFont->IsOutline(), aV_justify } );
2821 }
2822 }
2823
2824 return nextPosition;
2825}
2826
2827
2828void PDF_PLOTTER::drawOverbars( const std::vector<OVERBAR_INFO>& aOverbars, const EDA_ANGLE& aOrient,
2829 const KIFONT::METRICS& aFontMetrics )
2830{
2831 for( const OVERBAR_INFO& overbar : aOverbars )
2832 {
2833 // Baseline direction (vector from start to end). If zero length, derive from orientation.
2834 VECTOR2D dir( overbar.endPos.x - overbar.startPos.x, overbar.endPos.y - overbar.startPos.y );
2835
2836 double len = hypot( dir.x, dir.y );
2837
2838 if( len <= 1e-6 )
2839 {
2840 // Fallback: derive direction from orientation angle
2841 double ang = aOrient.AsRadians();
2842 dir.x = cos( ang );
2843 dir.y = sin( ang );
2844 len = 1.0;
2845 }
2846
2847 dir.x /= len;
2848 dir.y /= len;
2849
2850 // Perpendicular (rotate dir 90° CCW). Upward in text space so overbar sits above baseline.
2851 VECTOR2D nrm( -dir.y, dir.x );
2852
2853 // Base vertical offset distance in device units (baseline -> default overbar position)
2854 double barOffset = aFontMetrics.GetOverbarVerticalPosition( overbar.fontSize.y );
2855
2856 // Adjust further for outline fonts
2857 if( overbar.isOutline )
2858 barOffset += overbar.fontSize.y * 0.25; // extra raise for outline font
2859
2860 // Mirror the text vertical alignment adjustments used for baseline shifting.
2861 // Earlier logic scales baseline adjustment: CENTER ~2x, TOP ~4x. We apply proportional
2862 // extra raise so that overbars track visually with perceived baseline shift.
2863 double alignMult = 1.0;
2864
2865 switch( overbar.vAlign )
2866 {
2867 case GR_TEXT_V_ALIGN_CENTER: alignMult = overbar.isOutline ? 2.0 : 1.0; break;
2868 case GR_TEXT_V_ALIGN_TOP: alignMult = overbar.isOutline ? 4.0 : 1.0; break;
2869 default: alignMult = 1.0; break; // bottom
2870 }
2871
2872 if( alignMult > 1.0 )
2873 {
2874 // Scale only the baseline component (approx 17% of height, matching earlier baseline_factor)
2875 double baseline_factor = 0.17;
2876 barOffset += ( alignMult - 1.0 ) * ( baseline_factor * overbar.fontSize.y );
2877 }
2878
2879 // Trim to avoid rounded cap extension (assumes stroke caps); proportion of font width.
2880 double barTrim = overbar.fontSize.x * 0.1;
2881
2882 // Apply trim along baseline direction and offset along normal
2883 VECTOR2D startPt( overbar.startPos.x, overbar.startPos.y );
2884 VECTOR2D endPt( overbar.endPos.x, overbar.endPos.y );
2885
2886 // Both endpoints should share identical vertical (normal) offset above baseline.
2887 // Use a single offset vector offVec = -barOffset * nrm (negative because nrm points 'up').
2888 VECTOR2D offVec( -barOffset * nrm.x, -barOffset * nrm.y );
2889
2890 startPt.x += dir.x * barTrim + offVec.x;
2891 startPt.y += dir.y * barTrim + offVec.y;
2892 endPt.x -= dir.x * barTrim - offVec.x; // subtract trim, then apply same vertical offset
2893 endPt.y -= dir.y * barTrim - offVec.y;
2894
2895 VECTOR2I iStart = KiROUND( startPt.x, startPt.y );
2896 VECTOR2I iEnd = KiROUND( endPt.x, endPt.y );
2897
2898 MoveTo( iStart );
2899 LineTo( iEnd );
2900 PenFinish();
2901 }
2902}
2903
2904
2906 const COLOR4D& aColor,
2907 const wxString& aText,
2908 const TEXT_ATTRIBUTES& aAttributes,
2909 KIFONT::FONT* aFont,
2910 const KIFONT::METRICS& aFontMetrics,
2911 void* aData )
2912{
2913 VECTOR2I size = aAttributes.m_Size;
2914
2915 // PDF files do not like 0 sized texts which create broken files.
2916 if( size.x == 0 || size.y == 0 )
2917 return;
2918
2919 if( aAttributes.m_Mirrored )
2920 size.x = -size.x;
2921
2922 PDF_PLOTTER::Text( aPos, aColor, aText, aAttributes.m_Angle, size, aAttributes.m_Halign, aAttributes.m_Valign,
2923 aAttributes.m_StrokeWidth, aAttributes.m_Italic, aAttributes.m_Bold, aAttributes.m_Multiline,
2924 aFont, aFontMetrics, aData );
2925}
2926
2927
2928void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL )
2929{
2930 m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
2931}
2932
2933
2934void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
2935{
2936 m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
2937}
2938
2939
2940void PDF_PLOTTER::Bookmark( const BOX2I& aLocation, const wxString& aSymbolReference, const wxString &aGroupName )
2941{
2942
2943 m_bookmarksInPage[aGroupName].push_back( std::make_pair( aLocation, aSymbolReference ) );
2944}
2945
2946
2947void PDF_PLOTTER::Plot3DModel( const wxString& aSourcePath, const std::vector<PDF_3D_VIEW>& a3DViews )
2948{
2949 std::map<float, int> m_fovMap;
2950 std::vector<int> m_viewHandles;
2951
2952 for( const PDF_3D_VIEW& view : a3DViews )
2953 {
2954 // this is a strict need
2955 wxASSERT( view.m_cameraMatrix.size() == 12 );
2956
2957 int fovHandle = -1;
2958 if( !m_fovMap.contains( view.m_fov ) )
2959 {
2960 fovHandle = allocPdfObject();
2961 m_fovMap[view.m_fov] = fovHandle;
2962
2963 startPdfObject( fovHandle );
2964 fmt::print( m_outputFile,
2965 "<<\n"
2966 "/FOV {}\n"
2967 "/PS /Min\n"
2968 "/Subtype /P\n"
2969 ">>\n",
2970 encodeDoubleForPlotter( view.m_fov ) );
2972 }
2973 else
2974 {
2975 fovHandle = m_fovMap[view.m_fov];
2976 }
2977
2978 int viewHandle = allocPdfObject();
2979 startPdfObject( viewHandle );
2980
2981 fmt::print( m_outputFile,
2982 "<<\n"
2983 "/Type /3DView\n"
2984 "/XN ({})\n"
2985 "/IN ({})\n"
2986 "/MS /M\n"
2987 "/C2W [{:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f}]\n"
2988 "/CO {:f}\n"
2989 "/NR false\n"
2990 "/BG<<\n"
2991 "/Type /3DBG\n"
2992 "/Subtype /SC\n"
2993 "/CS /DeviceRGB\n"
2994 "/C [1.000000 1.000000 1.000000]>>\n"
2995 "/P {} 0 R\n"
2996 "/LS<<\n"
2997 "/Type /3DLightingScheme\n"
2998 "/Subtype /CAD>>\n"
2999 ">>\n",
3000 view.m_name, view.m_name, view.m_cameraMatrix[0],
3001 view.m_cameraMatrix[1],
3002 view.m_cameraMatrix[2], view.m_cameraMatrix[3], view.m_cameraMatrix[4],
3003 view.m_cameraMatrix[5], view.m_cameraMatrix[6], view.m_cameraMatrix[7],
3004 view.m_cameraMatrix[8], view.m_cameraMatrix[9], view.m_cameraMatrix[10],
3005 view.m_cameraMatrix[11],
3006 view.m_cameraCenter,
3007 fovHandle );
3008
3010
3011 m_viewHandles.push_back( viewHandle );
3012 }
3013
3015
3016 // so we can get remotely stuff the length afterwards
3017 int modelLenHandle = allocPdfObject();
3018
3019 fmt::print( m_outputFile,
3020 "<<\n"
3021 "/Type /3D\n"
3022 "/Subtype /U3D\n"
3023 "/DV 0\n" );
3024
3025 fmt::print( m_outputFile, "/VA [" );
3026
3027 for( int viewHandle : m_viewHandles )
3028 fmt::print( m_outputFile, "{} 0 R ", viewHandle );
3029
3030 fmt::print( m_outputFile, "]\n" );
3031
3032 fmt::print( m_outputFile,
3033 "/Length {} 0 R\n"
3034 "/Filter /FlateDecode\n"
3035 ">>\n", // Length is deferred
3036 modelLenHandle );
3037
3038 fmt::println( m_outputFile, "stream" );
3039
3040 wxFFile outputFFile( m_outputFile );
3041
3042 fflush( m_outputFile );
3043 long imgStreamStart = ftell( m_outputFile );
3044
3045 size_t model_stored_size = 0;
3046
3047 {
3048 wxFFileOutputStream ffos( outputFFile );
3049 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
3050
3051 wxFFileInputStream fileStream( aSourcePath );
3052
3053 if( !fileStream.IsOk() )
3054 wxLogError( _( "Failed to open 3D model file: %s" ), aSourcePath );
3055
3056 zos.Write( fileStream );
3057 }
3058
3059 fflush( m_outputFile );
3060 model_stored_size = ftell( m_outputFile );
3061 model_stored_size -= imgStreamStart; // Get the size of the compressed stream
3062
3063 fmt::print( m_outputFile, "\nendstream\n" );
3065
3066 startPdfObject( modelLenHandle );
3067 fmt::println( m_outputFile, "{}", (unsigned) model_stored_size );
3069
3070 outputFFile.Detach(); // Don't close it
3071}
3072
3073
3074std::vector<float> PDF_PLOTTER::CreateC2WMatrixFromAngles( const VECTOR3D& aTargetPosition,
3075 float aCameraDistance,
3076 float aYawDegrees,
3077 float aPitchDegrees,
3078 float aRollDegrees )
3079{
3080 float yRadians = glm::radians( aYawDegrees );
3081 float xRadians = glm::radians( aPitchDegrees );
3082 float zRadians = glm::radians( aRollDegrees );
3083
3084 // Create rotation matrix from Euler angles
3085 glm::mat4 rotationMatrix = glm::eulerAngleYXZ( yRadians, xRadians, zRadians );
3086
3087 // Calculate camera position based on target, distance, and rotation
3088 // Start with a vector pointing backward along the z-axis
3089 glm::vec4 cameraOffset = glm::vec4( 0.0f, 0.0f, aCameraDistance, 1.0f );
3090
3091 // Apply rotation to this offset
3092 cameraOffset = rotationMatrix * cameraOffset;
3093
3094 // Camera position is target position minus the rotated offset
3095 glm::vec3 cameraPosition = glm::vec3(aTargetPosition.x, aTargetPosition.y, aTargetPosition.z)
3096 - glm::vec3( cameraOffset );
3097
3098 std::vector<float> result( 12 );
3099
3100 // Handle rotation part in column-major order (first 9 elements)
3101 int index = 0;
3102 for( int col = 0; col < 3; ++col )
3103 {
3104 for( int row = 0; row < 3; ++row )
3105 result[index++] = static_cast<float>( rotationMatrix[col][row] );
3106 }
3107
3108 // Handle translation part (last 3 elements)
3109 result[9] = static_cast<float>( cameraPosition.x );
3110 result[10] = static_cast<float>( cameraPosition.y );
3111 result[11] = static_cast<float>( cameraPosition.z );
3112
3113 return result;
3114}
int index
void WriteImageStream(const wxImage &aImage, wxDataOutputStream &aOut, const wxColor &bg, bool colorMode)
void WriteImageSMaskStream(const wxImage &aImage, wxDataOutputStream &aOut)
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
BOX2< VECTOR2D > BOX2D
Definition box2.h:928
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
constexpr const Vec & GetPosition() const
Definition box2.h:208
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:234
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:143
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr void SetEnd(coord_type x, coord_type y)
Definition box2.h:294
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
double AsRadians() const
Definition eda_angle.h:120
static bool IsGotoPageHref(const wxString &aHref, wxString *aDestination=nullptr)
Check if aHref is a valid internal hyperlink.
High-level wrapper for evaluating mathematical and string expressions in wxString format.
wxString Evaluate(const wxString &aInput)
Main evaluation function - processes input string and evaluates all} expressions.
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:94
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:143
virtual bool IsStroke() const
Definition font.h:101
virtual bool IsItalic() const
Definition font.h:104
virtual bool IsBold() const
Definition font.h:103
virtual bool IsOutline() const
Definition font.h:102
VECTOR2I GetAlignedDrawPosition(const wxString &aText, const VECTOR2I &aAnchor, const TEXT_ATTRIBUTES &aAttributes, const METRICS &aFontMetrics) const
Return the draw position for the first line of text, using the same alignment rules as GAL rendering ...
Definition font.cpp:469
VECTOR2I StringBoundaryLimits(const wxString &aText, const VECTOR2I &aSize, int aThickness, bool aBold, bool aItalic, const METRICS &aFontMetrics) const
Compute the boundary limits of aText (the bounding box of all shapes).
Definition font.cpp:439
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.
double GetOverbarVerticalPosition(double aGlyphHeight) const
Compute the vertical position of an overbar.
Class OUTLINE_FONT implements outline font drawing.
EMBEDDING_PERMISSION GetEmbeddingPermission() const
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
std::unique_ptr< NODE > Parse()
std::vector< int > m_pageHandles
Handles to the page objects.
FILE * m_workFile
Temporary file to construct the stream before zipping.
void emitOutlineFonts()
wxString m_parentPageName
virtual void ClosePage()
Close the current page in the PDF document (and emit its compressed stream).
void emitOutlineNode(OUTLINE_NODE *aNode, int aParentHandle, int aNextNode, int aPrevNode)
Emits a outline item object and recurses into any children.
std::map< int, wxImage > m_imageHandles
int emitOutline()
Starts emitting the outline object.
virtual bool EndPlot() override
int startPdfObject(int aHandle=-1)
Open a new PDF object and returns the handle if the parameter is -1.
virtual void PlotPoly(const std::vector< VECTOR2I > &aCornerList, FILL_T aFill, int aWidth=USE_DEFAULT_LINE_WIDTH, void *aData=nullptr) override
Polygon plotting for PDF.
virtual ~PDF_PLOTTER()
virtual void Circle(const VECTOR2I &pos, int diametre, FILL_T fill, int width) override
Circle drawing for PDF.
virtual void SetCurrentLineWidth(int width, void *aData=nullptr) override
Pen width setting for PDF.
int m_streamLengthHandle
Handle to the deferred stream length.
void PlotImage(const wxImage &aImage, const VECTOR2I &aPos, double aScaleFactor) override
PDF images are handles as inline, not XObject streams...
int m_jsNamesHandle
Handle for Names dictionary with JS.
wxString m_pageName
virtual void SetDash(int aLineWidth, LINE_STYLE aLineStyle) override
PDF supports dashed lines.
void HyperlinkMenu(const BOX2I &aBox, const std::vector< wxString > &aDestURLs) override
Create a clickable hyperlink menu with a rectangular click area.
virtual bool OpenFile(const wxString &aFullFilename) override
Open or create the plot file aFullFilename.
std::string encodeDoubleForPlotter(double aValue) const
Convert a double to a PDF-compatible numeric token (no exponent notation).
int m_fontResDictHandle
Font resource dictionary.
virtual void emitSetRGBColor(double r, double g, double b, double a) override
PDF supports colors fully.
void emitStrokeFonts()
std::map< int, std::pair< BOX2D, wxString > > m_hyperlinkHandles
Handles for all the hyperlink objects that will be deferred.
int m_pageTreeHandle
Handle to the root of the page tree object.
virtual void Text(const VECTOR2I &aPos, const COLOR4D &aColor, const wxString &aText, const EDA_ANGLE &aOrient, const VECTOR2I &aSize, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify, int aWidth, bool aItalic, bool aBold, bool aMultilineAllowed, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, void *aData=nullptr) override
Draw text with the plotter.
int emitGoToAction(int aPageHandle, const VECTOR2I &aBottomLeft, const VECTOR2I &aTopRight)
Emit an action object that instructs a goto coordinates on a page.
void closePdfStream()
Finish the current PDF stream (writes the deferred length, too).
void Bookmark(const BOX2I &aBox, const wxString &aName, const wxString &aGroupName=wxEmptyString) override
Create a bookmark to a symbol.
std::vector< long > m_xrefTable
The PDF xref offset table.
void endPlotEmitResources()
virtual void Rect(const VECTOR2I &p1, const VECTOR2I &p2, FILL_T fill, int width, int aCornerRadius=0) override
Rectangles in PDF.
int startPdfStream(int aHandle=-1)
Start a PDF stream (for the page).
VECTOR2I renderMarkupNode(const MARKUP::NODE *aNode, const VECTOR2I &aPosition, const VECTOR2I &aBaseSize, const EDA_ANGLE &aOrient, bool aTextMirrored, int aWidth, bool aBaseBold, bool aBaseItalic, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, enum GR_TEXT_V_ALIGN_T aV_justify, TEXT_STYLE_FLAGS aTextStyle, std::vector< OVERBAR_INFO > &aOverbars)
Recursively render markup nodes with appropriate styling.
virtual void Arc(const VECTOR2D &aCenter, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aRadius, FILL_T aFill, int aWidth) override
The PDF engine can't directly plot arcs so we use polygonization.
void HyperlinkBox(const BOX2I &aBox, const wxString &aDestinationURL) override
Create a clickable hyperlink with a rectangular click area.
virtual void PenTo(const VECTOR2I &pos, char plume) override
Moveto/lineto primitive, moves the 'pen' to the specified direction.
std::map< wxString, std::vector< std::pair< BOX2I, wxString > > > m_bookmarksInPage
virtual bool StartPlot(const wxString &aPageNumber) override
The PDF engine supports multiple pages; the first one is opened 'for free' the following are to be cl...
bool m_usedBase14Fonts
Set when the non-embeddable font fallback references the base-14 /KicadFont* resources.
virtual void StartPage(const wxString &aPageNumber, const wxString &aPageName=wxEmptyString, const wxString &aParentPageNumber=wxEmptyString, const wxString &aParentPageName=wxEmptyString)
Start a new page in the PDF document.
std::unique_ptr< PDF_OUTLINE_FONT_MANAGER > m_outlineFontManager
OUTLINE_NODE * addOutlineNode(OUTLINE_NODE *aParent, int aActionHandle, const wxString &aTitle)
Add a new outline node entry.
int m_imgResDictHandle
Image resource dictionary.
std::string encodeStringForPlotter(const wxString &aUnicode) override
convert a wxString unicode string to a char string compatible with the accepted string PDF format (co...
std::unique_ptr< PDF_STROKE_FONT_MANAGER > m_strokeFontManager
void Plot3DModel(const wxString &aSourcePath, const std::vector< PDF_3D_VIEW > &a3DViews)
std::vector< wxString > m_pageNumbers
List of user-space page numbers for resolving internal hyperlinks.
int allocPdfObject()
Allocate a new handle in the table of the PDF object.
VECTOR2I renderWord(const wxString &aWord, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aOrient, bool aTextMirrored, int aWidth, bool aBold, bool aItalic, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, enum GR_TEXT_V_ALIGN_T aV_justify, TEXT_STYLE_FLAGS aTextStyle)
Render a single word with the given style parameters.
wxString m_workFilename
std::string encodeByteString(const std::string &aBytes)
void drawOverbars(const std::vector< OVERBAR_INFO > &aOverbars, const EDA_ANGLE &aOrient, const KIFONT::METRICS &aFontMetrics)
Draw overbar lines above text.
virtual void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
PDF can have multiple pages, so SetPageSettings can be called with the outputFile open (but not insid...
std::map< int, std::pair< BOX2D, std::vector< wxString > > > m_hyperlinkMenuHandles
int m_pageStreamHandle
Handle of the page content object.
virtual void PlotText(const VECTOR2I &aPos, const COLOR4D &aColor, const wxString &aText, const TEXT_ATTRIBUTES &aAttributes, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, void *aData=nullptr) override
static std::vector< float > CreateC2WMatrixFromAngles(const VECTOR3D &aTargetPosition, float aCameraDistance, float aYawDegrees, float aPitchDegrees, float aRollDegrees)
Generates the camera to world matrix for use with a 3D View.
std::vector< std::pair< BOX2I, wxString > > m_hyperlinksInPage
List of loaded hyperlinks in current page.
std::unique_ptr< OUTLINE_NODE > m_outlineRoot
Root outline node.
std::vector< std::pair< BOX2I, std::vector< wxString > > > m_hyperlinkMenusInPage
void closePdfObject()
Close the current PDF object.
int m_totalOutlineNodes
Total number of outline nodes.
std::vector< VECTOR2D > arcPath(const VECTOR2D &aCenter, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aRadius)
const std::string & ResourceName() const
std::string BuildDifferencesArray() const
double FontBBoxMaxY() const
double FontBBoxMinY() const
double UnitsPerEm() const
double FontBBoxMinX() const
void SetToUnicodeHandle(int aHandle)
std::string BuildWidthsArray() const
double FontBBoxMaxX() const
void SetFontHandle(int aHandle)
void SetCharProcsHandle(int aHandle)
std::string BuildToUnicodeCMap() const
std::vector< GLYPH > & Glyphs()
double GetDotMarkLenIU(int aLineWidth) const
Definition plotter.cpp:132
double GetDashGapLenIU(int aLineWidth) const
Definition plotter.cpp:144
const PROJECT * m_project
Definition plotter.h:738
wxString m_subject
Definition plotter.h:730
bool m_mirrorIsHorizontal
Definition plotter.h:713
PAGE_INFO m_pageInfo
Definition plotter.h:731
bool m_plotMirror
Definition plotter.h:711
static const int USE_DEFAULT_LINE_WIDTH
Definition plotter.h:140
void MoveTo(const VECTOR2I &pos)
Definition plotter.h:308
void FinishTo(const VECTOR2I &pos)
Definition plotter.h:318
wxString m_author
Definition plotter.h:729
double m_iuPerDeviceUnit
Definition plotter.h:708
VECTOR2I m_plotOffset
Definition plotter.h:710
VECTOR2I m_penLastpos
Definition plotter.h:724
virtual VECTOR2D userToDeviceCoordinates(const VECTOR2I &aCoordinate)
Modify coordinates according to the orientation, scale factor, and offsets trace.
Definition plotter.cpp:91
VECTOR2I m_paperSize
Definition plotter.h:732
virtual VECTOR2D userToDeviceSize(const VECTOR2I &size)
Modify size according to the plotter scale factors (VECTOR2I version, returns a VECTOR2D).
Definition plotter.cpp:116
char m_penState
Definition plotter.h:723
wxString m_creator
Definition plotter.h:726
int m_currentPenWidth
Definition plotter.h:722
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition plotter.h:700
FILE * m_outputFile
Output file.
Definition plotter.h:717
void LineTo(const VECTOR2I &pos)
Definition plotter.h:313
void PenFinish()
Definition plotter.h:324
static const int DO_NOT_SET_LINE_WIDTH
Definition plotter.h:139
RENDER_SETTINGS * m_renderSettings
Definition plotter.h:736
virtual void Text(const VECTOR2I &aPos, const COLOR4D &aColor, const wxString &aText, const EDA_ANGLE &aOrient, const VECTOR2I &aSize, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify, int aPenWidth, bool aItalic, bool aBold, bool aMultilineAllowed, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, void *aData=nullptr)
Draw text with the plotter.
Definition plotter.cpp:547
double m_IUsPerDecimil
Definition plotter.h:706
wxString m_title
Definition plotter.h:728
virtual int GetCurrentLineWidth() const
Definition plotter.h:182
bool m_colorMode
Definition plotter.h:720
double GetDashMarkLenIU(int aLineWidth) const
Definition plotter.cpp:138
wxString m_filename
Definition plotter.h:727
virtual void SetColor(const COLOR4D &color) override
The SetColor implementation is split with the subclasses: the PSLIKE computes the rgb values,...
double plotScaleAdjX
Fine user scale adjust ( = 1.0 if no correction)
void computeTextParameters(const VECTOR2I &aPos, const wxString &aText, const EDA_ANGLE &aOrient, const VECTOR2I &aSize, bool aMirror, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify, int aWidth, bool aItalic, bool aBold, double *wideningFactor, double *ctm_a, double *ctm_b, double *ctm_c, double *ctm_d, double *ctm_e, double *ctm_f, double *heightFactor)
This is the core for postscript/PDF text alignment.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
EDA_ANGLE GetCentralAngle() const
Get the "central angle" of the arc - this is the angle at the point of the "pie slice".
double GetRadius() const
EDA_ANGLE GetStartAngle() const
const VECTOR2I & GetCenter() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
bool IsArcSegment(size_t aSegment) const
const SHAPE_LINE_CHAIN Outline() const
void SetRadius(int aRadius)
Definition shape_rect.h:202
GR_TEXT_H_ALIGN_T m_Halign
GR_TEXT_V_ALIGN_T m_Valign
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
std::string substr(size_t pos=0, size_t len=npos) const
Definition utf8.h:201
const wxString ResolveUriByEnvVars(const wxString &aUri, const PROJECT *aProject)
Replace any environment and/or text variables in URIs.
Definition common.cpp:789
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
@ DEGREES_T
Definition eda_angle.h:31
FILL_T
Definition eda_fill.h:29
@ NO_FILL
Definition eda_fill.h:30
@ FILLED_SHAPE
Fill with object color.
Definition eda_fill.h:31
@ BOLD
Definition font.h:43
@ SUBSCRIPT
Definition font.h:45
@ ITALIC
Definition font.h:44
@ SUPERSCRIPT
Definition font.h:46
unsigned int TEXT_STYLE_FLAGS
Definition font.h:61
static constexpr double ITALIC_TILT
Tilt factor for italic style (this is the scaling factor on dY relative coordinates to give a tilted ...
Definition font.h:58
int GetPenSizeForBold(int aTextSize)
Definition gr_text.cpp:33
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...
const wxChar *const tracePdfPlotter
Flag to enable PDF plotter debug tracing.
void ignore_unused(const T &)
Definition ignore.h:20
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
Plotting engines similar to ps (PostScript, Gerber, svg)
wxString NormalizeFileUri(const wxString &aFileUri)
Normalize file path aFileUri to URI convention.
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
@ CTX_JS_STR
LINE_STYLE
Dashed line types.
bool isOverbar() const
bool isSuperscript() const
wxString asWxString() const
bool isSubscript() const
wxString title
Title of outline node.
std::vector< OUTLINE_NODE * > children
Ordered list of children.
int entryHandle
Allocated handle for this outline entry.
OUTLINE_NODE * AddChild(int aActionHandle, const wxString &aTitle, int aEntryHandle)
int actionHandle
Handle to action.
std::string path
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
int delta
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP
#define M_PI
wxLogTrace helper definitions.
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
VECTOR3< double > VECTOR3D
Definition vector3.h:230