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