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