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