KiCad PCB EDA Suite
Loading...
Searching...
No Matches
PDF_plotter.cpp
Go to the documentation of this file.
1
6/*
7 * This program source code file is part of KiCad, a free EDA CAD application.
8 *
9 * Copyright (C) 1992-2012 Lorenzo Marcantonio, [email protected]
10 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, you may find one here:
24 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
25 * or you may search the http://www.gnu.org website for the version 2 license,
26 * or you may write to the Free Software Foundation, Inc.,
27 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
28 */
29
30#include <algorithm>
31#include <cstdio> // snprintf
32#include <stack>
33
34#include <wx/filename.h>
35#include <wx/mstream.h>
36#include <wx/zstream.h>
37#include <wx/wfstream.h>
38#include <wx/datstrm.h>
39#include <wx/tokenzr.h>
40
41#include <advanced_config.h>
42#include <common.h> // ResolveUriByEnvVars
43#include <eda_text.h> // for IsGotoPageHref
44#include <font/font.h>
45#include <core/ignore.h>
46#include <macros.h>
47#include <trigo.h>
48#include <string_utils.h>
49#include <fmt/format.h>
50#include <fmt/chrono.h>
51#include <fmt/ranges.h>
52
54
55
56std::string PDF_PLOTTER::encodeStringForPlotter( const wxString& aText )
57{
58 // returns a string compatible with PDF string convention from a unicode string.
59 // if the initial text is only ASCII7, return the text between ( and ) for a good readability
60 // if the initial text is no ASCII7, return the text between < and >
61 // and encoded using 16 bits hexa (4 digits) by wide char (unicode 16)
62 std::string result;
63
64 // Is aText only ASCII7 ?
65 bool is_ascii7 = true;
66
67 for( size_t ii = 0; ii < aText.Len(); ii++ )
68 {
69 if( aText[ii] >= 0x7F )
70 {
71 is_ascii7 = false;
72 break;
73 }
74 }
75
76 if( is_ascii7 )
77 {
78 result = '(';
79
80 for( unsigned ii = 0; ii < aText.Len(); ii++ )
81 {
82 unsigned int code = aText[ii];
83
84 // These characters must be escaped
85 switch( code )
86 {
87 case '(':
88 case ')':
89 case '\\':
90 result += '\\';
92
93 default:
94 result += code;
95 break;
96 }
97 }
98
99 result += ')';
100 }
101 else
102 {
103 result = "<FEFF";
104
105 for( size_t ii = 0; ii < aText.Len(); ii++ )
106 {
107 unsigned int code = aText[ii];
108 result += fmt::format("{:04X}", code);
109 }
110
111 result += '>';
112 }
113
114 return result;
115}
116
117
118bool PDF_PLOTTER::OpenFile( const wxString& aFullFilename )
119{
120 m_filename = aFullFilename;
121
122 wxASSERT( !m_outputFile );
123
124 // Open the PDF file in binary mode
125 m_outputFile = wxFopen( m_filename, wxT( "wb" ) );
126
127 if( m_outputFile == nullptr )
128 return false ;
129
130 return true;
131}
132
133
134void PDF_PLOTTER::SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil,
135 double aScale, bool aMirror )
136{
137 m_plotMirror = aMirror;
138 m_plotOffset = aOffset;
139 m_plotScale = aScale;
140 m_IUsPerDecimil = aIusPerDecimil;
141
142 // The CTM is set to 1 user unit per decimal
143 m_iuPerDeviceUnit = 1.0 / aIusPerDecimil;
144
145 /* The paper size in this engine is handled page by page
146 Look in the StartPage function */
147}
148
149
150void PDF_PLOTTER::SetCurrentLineWidth( int aWidth, void* aData )
151{
152 wxASSERT( m_workFile );
153
154 if( aWidth == DO_NOT_SET_LINE_WIDTH )
155 return;
156 else if( aWidth == USE_DEFAULT_LINE_WIDTH )
158
159 if( aWidth == 0 )
160 aWidth = 1;
161
162 wxASSERT_MSG( aWidth > 0, "Plotter called to set negative pen width" );
163
164 if( aWidth != m_currentPenWidth )
165 fmt::println( m_workFile, "{:g} w", userToDeviceSize( aWidth ) );
166
167 m_currentPenWidth = aWidth;
168}
169
170
171void PDF_PLOTTER::emitSetRGBColor( double r, double g, double b, double a )
172{
173 wxASSERT( m_workFile );
174
175 // PDF treats all colors as opaque, so the best we can do with alpha is generate an
176 // appropriate blended color assuming white paper.
177 if( a < 1.0 )
178 {
179 r = ( r * a ) + ( 1 - a );
180 g = ( g * a ) + ( 1 - a );
181 b = ( b * a ) + ( 1 - a );
182 }
183
184 fmt::println( m_workFile, "{:g} {:g} {:g} rg {:g} {:g} {:g} RG", r, g, b, r, g, b );
185}
186
187
188void PDF_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle )
189{
190 wxASSERT( m_workFile );
191
192 switch( aLineStyle )
193 {
194 case LINE_STYLE::DASH:
195 fmt::println( m_workFile, "[{} {}] 0 d",
196 (int) GetDashMarkLenIU( aLineWidth ),
197 (int) GetDashGapLenIU( aLineWidth ) );
198 break;
199
200 case LINE_STYLE::DOT:
201 fmt::println( m_workFile, "[{} {}] 0 d",
202 (int) GetDotMarkLenIU( aLineWidth ),
203 (int) GetDashGapLenIU( aLineWidth ) );
204 break;
205
206 case LINE_STYLE::DASHDOT:
207 fmt::println( m_workFile, "[{} {} {} {}] 0 d",
208 (int) GetDashMarkLenIU( aLineWidth ),
209 (int) GetDashGapLenIU( aLineWidth ),
210 (int) GetDotMarkLenIU( aLineWidth ),
211 (int) GetDashGapLenIU( aLineWidth ) );
212 break;
213
214 case LINE_STYLE::DASHDOTDOT:
215 fmt::println( m_workFile, "[{} {} {} {} {} {}] 0 d",
216 (int) GetDashMarkLenIU( aLineWidth ),
217 (int) GetDashGapLenIU( aLineWidth ),
218 (int) GetDotMarkLenIU( aLineWidth ),
219 (int) GetDashGapLenIU( aLineWidth ),
220 (int) GetDotMarkLenIU( aLineWidth ),
221 (int) GetDashGapLenIU( aLineWidth ) );
222 break;
223
224 default:
225 fmt::println( m_workFile, "[] 0 d\n" );
226 }
227}
228
229
230void PDF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width )
231{
232 wxASSERT( m_workFile );
233
234 if( fill == FILL_T::NO_FILL && width == 0 )
235 return;
236
237 SetCurrentLineWidth( width );
238
239 VECTOR2I size = p2 - p1;
240
241 if( size.x == 0 && size.y == 0 )
242 {
243 // Can't draw zero-sized rectangles
244 MoveTo( VECTOR2I( p1.x, p1.y ) );
245 FinishTo( VECTOR2I( p1.x, p1.y ) );
246
247 return;
248 }
249
250 if( std::min( std::abs( size.x ), std::abs( size.y ) ) < width )
251 {
252 // Too thick stroked rectangles are buggy, draw as polygon
253 std::vector<VECTOR2I> cornerList;
254
255 cornerList.emplace_back( p1.x, p1.y );
256 cornerList.emplace_back( p2.x, p1.y );
257 cornerList.emplace_back( p2.x, p2.y );
258 cornerList.emplace_back( p1.x, p2.y );
259 cornerList.emplace_back( p1.x, p1.y );
260
261 PlotPoly( cornerList, fill, width, nullptr );
262
263 return;
264 }
265
266 VECTOR2D p1_dev = userToDeviceCoordinates( p1 );
267 VECTOR2D p2_dev = userToDeviceCoordinates( p2 );
268
269 char paintOp;
270
271 if( fill == FILL_T::NO_FILL )
272 paintOp = 'S';
273 else
274 paintOp = width > 0 ? 'B' : 'f';
275
276 fmt::println( m_workFile, "{:g} {:g} {:g} {:g} re {}",
277 p1_dev.x,
278 p1_dev.y,
279 p2_dev.x - p1_dev.x,
280 p2_dev.y - p1_dev.y,
281 paintOp );
282}
283
284
285void PDF_PLOTTER::Circle( const VECTOR2I& pos, int diametre, FILL_T aFill, int width )
286{
287 wxASSERT( m_workFile );
288
289 if( aFill == FILL_T::NO_FILL && width == 0 )
290 return;
291
292 SetCurrentLineWidth( width );
293
294 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
295 double radius = userToDeviceSize( diametre / 2.0 );
296
297 // If diameter is less than width, switch to filled mode
298 if( aFill == FILL_T::NO_FILL && diametre < GetCurrentLineWidth() )
299 {
300 aFill = FILL_T::FILLED_SHAPE;
301 radius = userToDeviceSize( ( diametre / 2.0 ) + ( width / 2.0 ) );
302 }
303
304 /* OK. Here's a trick. PDF doesn't support circles or circular angles, that's
305 a fact. You'll have to do with cubic beziers. These *can't* represent
306 circular arcs (NURBS can, beziers don't). But there is a widely known
307 approximation which is really good
308 */
309
310 double magic = radius * 0.551784; // You don't want to know where this come from
311
312 // This is the convex hull for the bezier approximated circle
313 fmt::println( m_workFile,
314 "{:g} {:g} m "
315 "{:g} {:g} {:g} {:g} {:g} {:g} c "
316 "{:g} {:g} {:g} {:g} {:g} {:g} c "
317 "{:g} {:g} {:g} {:g} {:g} {:g} c "
318 "{:g} {:g} {:g} {:g} {:g} {:g} c {}",
319 pos_dev.x - radius, pos_dev.y,
320
321 pos_dev.x - radius, pos_dev.y + magic,
322 pos_dev.x - magic, pos_dev.y + radius,
323 pos_dev.x, pos_dev.y + radius,
324
325 pos_dev.x + magic, pos_dev.y + radius,
326 pos_dev.x + radius, pos_dev.y + magic,
327 pos_dev.x + radius, pos_dev.y,
328
329 pos_dev.x + radius, pos_dev.y - magic,
330 pos_dev.x + magic, pos_dev.y - radius,
331 pos_dev.x, pos_dev.y - radius,
332
333 pos_dev.x - magic, pos_dev.y - radius,
334 pos_dev.x - radius, pos_dev.y - magic,
335 pos_dev.x - radius, pos_dev.y,
336
337 aFill == FILL_T::NO_FILL ? 's' : 'b' );
338}
339
340
341void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
342 const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth )
343{
344 wxASSERT( m_workFile );
345
346 SetCurrentLineWidth( aWidth );
347
348 if( aRadius <= 0 )
349 {
350 Circle( aCenter, GetCurrentLineWidth(), FILL_T::FILLED_SHAPE, 0 );
351 return;
352 }
353
354 /*
355 * Arcs are not so easily approximated by beziers (in the general case), so we approximate
356 * them in the old way
357 */
358 EDA_ANGLE startAngle = -aStartAngle;
359 EDA_ANGLE endAngle = startAngle - aAngle;
360 VECTOR2I start;
362 const EDA_ANGLE delta( 5, DEGREES_T ); // increment to draw circles
363
364 if( startAngle > endAngle )
365 std::swap( startAngle, endAngle );
366
367 // Usual trig arc plotting routine...
368 start.x = KiROUND( aCenter.x + aRadius * ( -startAngle ).Cos() );
369 start.y = KiROUND( aCenter.y + aRadius * ( -startAngle ).Sin() );
370 VECTOR2D pos_dev = userToDeviceCoordinates( start );
371 fmt::print( m_workFile, "{:g} {:g} m ", pos_dev.x, pos_dev.y );
372
373 for( EDA_ANGLE ii = startAngle + delta; ii < endAngle; ii += delta )
374 {
375 end.x = KiROUND( aCenter.x + aRadius * ( -ii ).Cos() );
376 end.y = KiROUND( aCenter.y + aRadius * ( -ii ).Sin() );
377 pos_dev = userToDeviceCoordinates( end );
378 fmt::print( m_workFile, "{:g} {:g} l ", pos_dev.x, pos_dev.y );
379 }
380
381 end.x = KiROUND( aCenter.x + aRadius * ( -endAngle ).Cos() );
382 end.y = KiROUND( aCenter.y + aRadius * ( -endAngle ).Sin() );
383 pos_dev = userToDeviceCoordinates( end );
384 fmt::print( m_workFile, "{:g} {:g} l ", pos_dev.x, pos_dev.y );
385
386 // The arc is drawn... if not filled we stroke it, otherwise we finish
387 // closing the pie at the center
388 if( aFill == FILL_T::NO_FILL )
389 {
390 fmt::println( m_workFile, "S" );
391 }
392 else
393 {
394 pos_dev = userToDeviceCoordinates( aCenter );
395 fmt::println( m_workFile, "{:g} {:g} l b", pos_dev.x, pos_dev.y );
396 }
397}
398
399
400void PDF_PLOTTER::PlotPoly( const std::vector<VECTOR2I>& aCornerList, FILL_T aFill, int aWidth,
401 void* aData )
402{
403 wxASSERT( m_workFile );
404
405 if( aCornerList.size() <= 1 )
406 return;
407
408 if( aFill == FILL_T::NO_FILL && aWidth == 0 )
409 return;
410
411 SetCurrentLineWidth( aWidth );
412
413 VECTOR2D pos = userToDeviceCoordinates( aCornerList[0] );
414 fmt::println( m_workFile, "{:f} {:f} m", pos.x, pos.y );
415
416 for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
417 {
418 pos = userToDeviceCoordinates( aCornerList[ii] );
419 fmt::println( m_workFile, "{:f} {:f} l", pos.x, pos.y );
420 }
421
422 // Close path and stroke and/or fill
423 if( aFill == FILL_T::NO_FILL )
424 fmt::println( m_workFile, "S" );
425 else if( aWidth == 0 )
426 fmt::println( m_workFile, "f" );
427 else
428 fmt::println( m_workFile, "b" );
429}
430
431
432void PDF_PLOTTER::PenTo( const VECTOR2I& pos, char plume )
433{
434 wxASSERT( m_workFile );
435
436 if( plume == 'Z' )
437 {
438 if( m_penState != 'Z' )
439 {
440 fmt::println( m_workFile, "S" );
441 m_penState = 'Z';
442 m_penLastpos.x = -1;
443 m_penLastpos.y = -1;
444 }
445
446 return;
447 }
448
449 if( m_penState != plume || pos != m_penLastpos )
450 {
451 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
452 fmt::println( m_workFile, "{:f} {:f} {}",
453 pos_dev.x,
454 pos_dev.y,
455 plume == 'D' ? 'l' : 'm' );
456 }
457
458 m_penState = plume;
459 m_penLastpos = pos;
460}
461
462
463void PDF_PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor )
464{
465 wxASSERT( m_workFile );
466 VECTOR2I pix_size( aImage.GetWidth(), aImage.GetHeight() );
467
468 // Requested size (in IUs)
469 VECTOR2D drawsize( aScaleFactor * pix_size.x, aScaleFactor * pix_size.y );
470
471 // calculate the bitmap start position
472 VECTOR2I start( aPos.x - drawsize.x / 2, aPos.y + drawsize.y / 2 );
473 VECTOR2D dev_start = userToDeviceCoordinates( start );
474
475 // Deduplicate images
476 auto findHandleForImage = [&]( const wxImage& aCurrImage ) -> int
477 {
478 for( const auto& [imgHandle, image] : m_imageHandles )
479 {
480 if( image.IsSameAs( aCurrImage ) )
481 return imgHandle;
482
483 if( image.GetWidth() != aCurrImage.GetWidth() )
484 continue;
485
486 if( image.GetHeight() != aCurrImage.GetHeight() )
487 continue;
488
489 if( image.GetType() != aCurrImage.GetType() )
490 continue;
491
492 if( image.HasAlpha() != aCurrImage.HasAlpha() )
493 continue;
494
495 if( image.HasMask() != aCurrImage.HasMask()
496 || image.GetMaskRed() != aCurrImage.GetMaskRed()
497 || image.GetMaskGreen() != aCurrImage.GetMaskGreen()
498 || image.GetMaskBlue() != aCurrImage.GetMaskBlue() )
499 continue;
500
501 int pixCount = image.GetWidth() * image.GetHeight();
502
503 if( memcmp( image.GetData(), aCurrImage.GetData(), pixCount * 3 ) != 0 )
504 continue;
505
506 if( image.HasAlpha() && memcmp( image.GetAlpha(), aCurrImage.GetAlpha(), pixCount ) != 0 )
507 continue;
508
509 return imgHandle;
510 }
511
512 return -1;
513 };
514
515 int imgHandle = findHandleForImage( aImage );
516
517 if( imgHandle == -1 )
518 {
519 imgHandle = allocPdfObject();
520 m_imageHandles.emplace( imgHandle, aImage );
521 }
522
523 /* PDF has an uhm... simplified coordinate system handling. There is
524 *one* operator to do everything (the PS concat equivalent). At least
525 they kept the matrix stack to save restore environments. Also images
526 are always emitted at the origin with a size of 1x1 user units.
527 What we need to do is:
528 1) save the CTM end establish the new one
529 2) plot the image
530 3) restore the CTM
531 4) profit
532 */
533 fmt::println( m_workFile, "q {:g} 0 0 {:g} {:g} {:g} cm", // Step 1
534 userToDeviceSize( drawsize.x ),
535 userToDeviceSize( drawsize.y ),
536 dev_start.x,
537 dev_start.y );
538
539 fmt::println( m_workFile, "/Im{} Do", imgHandle );
540 fmt::println( m_workFile, "Q" );
541}
542
543
545{
546 m_xrefTable.push_back( 0 );
547 return m_xrefTable.size() - 1;
548}
549
550
552{
553 wxASSERT( m_outputFile );
554 wxASSERT( !m_workFile );
555
556 if( aHandle < 0 )
557 aHandle = allocPdfObject();
558
559 m_xrefTable[aHandle] = ftell( m_outputFile );
560 fmt::println( m_outputFile, "{} 0 obj", aHandle );
561 return aHandle;
562}
563
564
566{
567 wxASSERT( m_outputFile );
568 wxASSERT( !m_workFile );
569 fmt::println( m_outputFile, "endobj" );
570}
571
572
574{
575 wxASSERT( m_outputFile );
576 wxASSERT( !m_workFile );
577 int handle = startPdfObject( aHandle );
578
579 // This is guaranteed to be handle+1 but needs to be allocated since
580 // you could allocate more object during stream preparation
582
583 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
584 {
585 fmt::println( m_outputFile,
586 "<< /Length {} 0 R >>\n"
587 "stream",
588 handle + 1 );
589 }
590 else
591 {
592 fmt::println( m_outputFile,
593 "<< /Length {} 0 R /Filter /FlateDecode >>\n"
594 "stream",
595 handle + 1 );
596 }
597
598 // Open a temporary file to accumulate the stream
599 m_workFilename = wxFileName::CreateTempFileName( "" );
600 m_workFile = wxFopen( m_workFilename, wxT( "w+b" ) );
601 wxASSERT( m_workFile );
602 return handle;
603}
604
605
607{
608 wxASSERT( m_workFile );
609
610 long stream_len = ftell( m_workFile );
611
612 if( stream_len < 0 )
613 {
614 wxASSERT( false );
615 return;
616 }
617
618 // Rewind the file, read in the page stream and DEFLATE it
619 fseek( m_workFile, 0, SEEK_SET );
620 unsigned char *inbuf = new unsigned char[stream_len];
621
622 int rc = fread( inbuf, 1, stream_len, m_workFile );
623 wxASSERT( rc == stream_len );
624 ignore_unused( rc );
625
626 // We are done with the temporary file, junk it
627 fclose( m_workFile );
628 m_workFile = nullptr;
629 ::wxRemoveFile( m_workFilename );
630
631 unsigned out_count;
632
633 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
634 {
635 out_count = stream_len;
636 fwrite( inbuf, out_count, 1, m_outputFile );
637 }
638 else
639 {
640 // NULL means memos owns the memory, but provide a hint on optimum size needed.
641 wxMemoryOutputStream memos( nullptr, std::max( 2000l, stream_len ) ) ;
642
643 {
644 /* Somewhat standard parameters to compress in DEFLATE. The PDF spec is
645 * misleading, it says it wants a DEFLATE stream but it really want a ZLIB
646 * stream! (a DEFLATE stream would be generated with -15 instead of 15)
647 * rc = deflateInit2( &zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, 15,
648 * 8, Z_DEFAULT_STRATEGY );
649 */
650
651 wxZlibOutputStream zos( memos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
652
653 zos.Write( inbuf, stream_len );
654 } // flush the zip stream using zos destructor
655
656 wxStreamBuffer* sb = memos.GetOutputStreamBuffer();
657
658 out_count = sb->Tell();
659 fwrite( sb->GetBufferStart(), 1, out_count, m_outputFile );
660 }
661
662 delete[] inbuf;
663 fmt::print( m_outputFile, "\nendstream\n" );
665
666 // Writing the deferred length as an indirect object
668 fmt::println( m_outputFile, "{}", out_count );
670}
671
672
673void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageName,
674 const wxString& aParentPageNumber, const wxString& aParentPageName )
675{
676 wxASSERT( m_outputFile );
677 wxASSERT( !m_workFile );
678
679 m_pageNumbers.push_back( aPageNumber );
680 m_pageName = aPageName.IsEmpty() ? wxString::Format( _( "Page %s" ),
681 aPageNumber )
682 : wxString::Format( _( "%s (Page %s)" ),
683 aPageName,
684 aPageNumber );
685 m_parentPageName = aParentPageName.IsEmpty() ? wxString::Format( _( "Page %s" ),
686 aParentPageNumber )
687 : wxString::Format( _( "%s (Page %s)" ),
688 aParentPageName,
689 aParentPageNumber );
690
691 // Compute the paper size in IUs
695
696 // Set m_currentPenWidth to a unused value to ensure the pen width
697 // will be initialized to a the right value in pdf file by the first item to plot
699
700 if( !m_3dExportMode )
701 {
702 // Open the content stream; the page object will go later
704
705 /* Now, until ClosePage *everything* must be wrote in workFile, to be
706 compressed later in closePdfStream */
707
708 // Default graphic settings (coordinate system, default color and line style)
709 fmt::println( m_workFile,
710 "{:g} 0 0 {:g} 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG {:g} w",
711 0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY,
713 }
714}
715
716
717void WriteImageStream( const wxImage& aImage, wxDataOutputStream& aOut, wxColor bg, bool colorMode )
718{
719 int w = aImage.GetWidth();
720 int h = aImage.GetHeight();
721
722 for( int y = 0; y < h; y++ )
723 {
724 for( int x = 0; x < w; x++ )
725 {
726 unsigned char r = aImage.GetRed( x, y ) & 0xFF;
727 unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
728 unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
729
730 if( aImage.HasMask() )
731 {
732 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
733 && b == aImage.GetMaskBlue() )
734 {
735 r = bg.Red();
736 g = bg.Green();
737 b = bg.Blue();
738 }
739 }
740
741 if( colorMode )
742 {
743 aOut.Write8( r );
744 aOut.Write8( g );
745 aOut.Write8( b );
746 }
747 else
748 {
749 // Greyscale conversion (CIE 1931)
750 unsigned char grey = KiROUND( r * 0.2126 + g * 0.7152 + b * 0.0722 );
751
752 aOut.Write8( grey );
753 }
754 }
755 }
756}
757
758
759void WriteImageSMaskStream( const wxImage& aImage, wxDataOutputStream& aOut )
760{
761 int w = aImage.GetWidth();
762 int h = aImage.GetHeight();
763
764 if( aImage.HasMask() )
765 {
766 for( int y = 0; y < h; y++ )
767 {
768 for( int x = 0; x < w; x++ )
769 {
770 unsigned char a = 255;
771 unsigned char r = aImage.GetRed( x, y );
772 unsigned char g = aImage.GetGreen( x, y );
773 unsigned char b = aImage.GetBlue( x, y );
774
775 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
776 && b == aImage.GetMaskBlue() )
777 {
778 a = 0;
779 }
780
781 aOut.Write8( a );
782 }
783 }
784 }
785 else if( aImage.HasAlpha() )
786 {
787 int size = w * h;
788 aOut.Write8( aImage.GetAlpha(), size );
789 }
790}
791
792
794{
795 // non 3d exports need this
796 if( m_pageStreamHandle != -1 )
797 {
798 wxASSERT( m_workFile );
799
800 // Close the page stream (and compress it)
802 }
803
804 // Page size is in 1/72 of inch (default user space units). Works like the bbox in postscript
805 // but there is no need for swapping the sizes, since PDF doesn't require a portrait page.
806 // We use the MediaBox but PDF has lots of other less-used boxes that could be used.
807 const double PTsPERMIL = 0.072;
808 VECTOR2D psPaperSize = VECTOR2D( m_pageInfo.GetSizeMils() ) * PTsPERMIL;
809
810 auto iuToPdfUserSpace =
811 [&]( const VECTOR2I& aCoord ) -> VECTOR2D
812 {
813 VECTOR2D pos = VECTOR2D( aCoord ) * PTsPERMIL / ( m_IUsPerDecimil * 10 );
814
815 // PDF y=0 is at bottom of page, invert coordinate
816 VECTOR2D retval( pos.x, psPaperSize.y - pos.y );
817
818 // The pdf plot can be mirrored (from left to right). So mirror the
819 // x coordinate if m_plotMirror is set
820 if( m_plotMirror )
821 {
823 retval.x = ( psPaperSize.x - pos.x );
824 else
825 retval.y = pos.y;
826 }
827
828 return retval;
829 };
830
831 // Handle annotations (at the moment only "link" type objects)
832 std::vector<int> annotHandles;
833
834 // Allocate all hyperlink objects for the page and calculate their position in user space
835 // coordinates
836 for( const std::pair<BOX2I, wxString>& linkPair : m_hyperlinksInPage )
837 {
838 const BOX2I& box = linkPair.first;
839 const wxString& url = linkPair.second;
840
841 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
842 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
843
844 BOX2D userSpaceBox;
845 userSpaceBox.SetOrigin( bottomLeft );
846 userSpaceBox.SetEnd( topRight );
847
848 annotHandles.push_back( allocPdfObject() );
849
850 m_hyperlinkHandles.insert( { annotHandles.back(), { userSpaceBox, url } } );
851 }
852
853 for( const std::pair<BOX2I, std::vector<wxString>>& menuPair : m_hyperlinkMenusInPage )
854 {
855 const BOX2I& box = menuPair.first;
856 const std::vector<wxString>& urls = menuPair.second;
857
858 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
859 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
860
861 BOX2D userSpaceBox;
862 userSpaceBox.SetOrigin( bottomLeft );
863 userSpaceBox.SetEnd( topRight );
864
865 annotHandles.push_back( allocPdfObject() );
866
867 m_hyperlinkMenuHandles.insert( { annotHandles.back(), { userSpaceBox, urls } } );
868 }
869
870 int annot3DHandle = -1;
871
872 if( m_3dExportMode )
873 {
874 annot3DHandle = allocPdfObject();
875 annotHandles.push_back( annot3DHandle );
876 }
877
878
879 int hyperLinkArrayHandle = -1;
880
881 // If we have added any annotation links, create an array containing all the objects
882 if( annotHandles.size() > 0 )
883 {
884 hyperLinkArrayHandle = startPdfObject();
885 bool isFirst = true;
886
887 fmt::print( m_outputFile, "[" );
888
889 for( int handle : annotHandles )
890 {
891 if( isFirst )
892 isFirst = false;
893 else
894 fmt::print( m_outputFile, " " );
895
896 fmt::print( m_outputFile, "{} 0 R", handle );
897 }
898
899 fmt::println( m_outputFile, "]" );
901 }
902
903 // Emit the page object and put it in the page list for later
904 int pageHandle = startPdfObject();
905 m_pageHandles.push_back( pageHandle );
906
907 fmt::print( m_outputFile,
908 "<<\n"
909 "/Type /Page\n"
910 "/Parent {} 0 R\n"
911 "/Resources <<\n"
912 " /ProcSet [/PDF /Text /ImageC /ImageB]\n"
913 " /Font {} 0 R\n"
914 " /XObject {} 0 R >>\n"
915 "/MediaBox [0 0 {:g} {:g}]\n"
916 "/Contents {} 0 R\n",
917 m_pageTreeHandle, m_fontResDictHandle, m_imgResDictHandle, psPaperSize.x, psPaperSize.y,
919
920 if( annotHandles.size() > 0 )
921 fmt::print( m_outputFile, "/Annots {} 0 R", hyperLinkArrayHandle );
922
923 fmt::print( m_outputFile, ">>\n" );
924
926
927 if( m_3dExportMode )
928 {
929 startPdfObject( annot3DHandle );
930 fmt::print( m_outputFile,
931 "<<\n"
932 "/Type /Annot\n"
933 "/Subtype /3D\n"
934 "/Rect [0 0 {:g} {:g}]\n"
935 "/NM (3D Annotation)\n"
936 "/3DD {} 0 R\n"
937 "/3DV 0\n"
938 "/3DA<</A/PO/D/PC/TB true/NP true>>\n"
939 "/3DI true\n"
940 "/P {} 0 R\n"
941 ">>\n",
942 psPaperSize.x, psPaperSize.y, m_3dModelHandle, pageHandle );
943
945 }
946
947 // Mark the page stream as idle
949
950 int actionHandle = emitGoToAction( pageHandle );
951 PDF_PLOTTER::OUTLINE_NODE* parent_node = m_outlineRoot.get();
952
953 if( !m_parentPageName.IsEmpty() )
954 {
955 // Search for the parent node iteratively through the entire tree
956 std::stack<OUTLINE_NODE*> nodes;
957 nodes.push( m_outlineRoot.get() );
958
959 while( !nodes.empty() )
960 {
961 OUTLINE_NODE* node = nodes.top();
962 nodes.pop();
963
964 // Check if this node matches
965 if( node->title == m_parentPageName )
966 {
967 parent_node = node;
968 break;
969 }
970
971 // Add all children to the stack
972 for( OUTLINE_NODE* child : node->children )
973 nodes.push( child );
974 }
975 }
976
977 OUTLINE_NODE* pageOutlineNode = addOutlineNode( parent_node, actionHandle, m_pageName );
978
979 // let's reorg the symbol bookmarks under a page handle
980 // let's reorg the symbol bookmarks under a page handle
981 for( const auto& [groupName, groupVector] : m_bookmarksInPage )
982 {
983 OUTLINE_NODE* groupOutlineNode = addOutlineNode( pageOutlineNode, actionHandle, groupName );
984
985 for( const std::pair<BOX2I, wxString>& bookmarkPair : groupVector )
986 {
987 const BOX2I& box = bookmarkPair.first;
988 const wxString& ref = bookmarkPair.second;
989
990 VECTOR2I bottomLeft = iuToPdfUserSpace( box.GetPosition() );
991 VECTOR2I topRight = iuToPdfUserSpace( box.GetEnd() );
992
993 actionHandle = emitGoToAction( pageHandle, bottomLeft, topRight );
994
995 addOutlineNode( groupOutlineNode, actionHandle, ref );
996 }
997
998 std::sort( groupOutlineNode->children.begin(), groupOutlineNode->children.end(),
999 []( const OUTLINE_NODE* a, const OUTLINE_NODE* b ) -> bool
1000 {
1001 return a->title < b->title;
1002 } );
1003 }
1004
1005 // Clean up
1006 m_hyperlinksInPage.clear();
1007 m_hyperlinkMenusInPage.clear();
1008 m_bookmarksInPage.clear();
1009}
1010
1011
1012bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
1013{
1014 return StartPlot( aPageNumber, wxEmptyString );
1015}
1016
1017
1018bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber, const wxString& aPageName )
1019{
1020 wxASSERT( m_outputFile );
1021
1022 // First things first: the customary null object
1023 m_xrefTable.clear();
1024 m_xrefTable.push_back( 0 );
1025 m_hyperlinksInPage.clear();
1026 m_hyperlinkMenusInPage.clear();
1027 m_hyperlinkHandles.clear();
1028 m_hyperlinkMenuHandles.clear();
1029 m_bookmarksInPage.clear();
1031
1032 m_outlineRoot = std::make_unique<OUTLINE_NODE>();
1033
1034 /* The header (that's easy!). The second line is binary junk required
1035 to make the file binary from the beginning (the important thing is
1036 that they must have the bit 7 set) */
1037 fmt::print( m_outputFile, "%PDF-1.5\n%\200\201\202\203\n" );
1038
1039 /* Allocate an entry for the page tree root, it will go in every page parent entry */
1041
1042 /* In the same way, the font resource dictionary is used by every page
1043 (it *could* be inherited via the Pages tree */
1045
1047
1049
1050 /* Now, the PDF is read from the end, (more or less)... so we start
1051 with the page stream for page 1. Other more important stuff is written
1052 at the end */
1053 StartPage( aPageNumber, aPageName );
1054 return true;
1055}
1056
1057
1058int PDF_PLOTTER::emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft,
1059 const VECTOR2I& aTopRight )
1060{
1061 int actionHandle = allocPdfObject();
1062 startPdfObject( actionHandle );
1063
1064 fmt::print( m_outputFile,
1065 "<</S /GoTo /D [{} 0 R /FitR {} {} {} {}]\n"
1066 ">>\n",
1067 aPageHandle,
1068 aBottomLeft.x,
1069 aBottomLeft.y,
1070 aTopRight.x,
1071 aTopRight.y );
1072
1074
1075 return actionHandle;
1076}
1077
1078
1079int PDF_PLOTTER::emitGoToAction( int aPageHandle )
1080{
1081 int actionHandle = allocPdfObject();
1082 startPdfObject( actionHandle );
1083
1084 fmt::println( m_outputFile,
1085 "<</S /GoTo /D [{} 0 R /Fit]\n"
1086 ">>",
1087 aPageHandle );
1088
1090
1091 return actionHandle;
1092}
1093
1094
1095void PDF_PLOTTER::emitOutlineNode( OUTLINE_NODE* node, int parentHandle, int nextNode,
1096 int prevNode )
1097{
1098 int nodeHandle = node->entryHandle;
1099 int prevHandle = -1;
1100 int nextHandle = -1;
1101
1102 for( std::vector<OUTLINE_NODE*>::iterator it = node->children.begin();
1103 it != node->children.end(); it++ )
1104 {
1105 if( it >= node->children.end() - 1 )
1106 {
1107 nextHandle = -1;
1108 }
1109 else
1110 {
1111 nextHandle = ( *( it + 1 ) )->entryHandle;
1112 }
1113
1114 emitOutlineNode( *it, nodeHandle, nextHandle, prevHandle );
1115
1116 prevHandle = ( *it )->entryHandle;
1117 }
1118
1119 // -1 for parentHandle is the outline root itself which is handed elsewhere.
1120 if( parentHandle != -1 )
1121 {
1122 startPdfObject( nodeHandle );
1123
1124 fmt::print( m_outputFile,
1125 "<<\n"
1126 "/Title {}\n"
1127 "/Parent {} 0 R\n",
1129 parentHandle);
1130
1131 if( nextNode > 0 )
1132 {
1133 fmt::println( m_outputFile, "/Next {} 0 R", nextNode );
1134 }
1135
1136 if( prevNode > 0 )
1137 {
1138 fmt::println( m_outputFile, "/Prev {} 0 R", prevNode );
1139 }
1140
1141 if( node->children.size() > 0 )
1142 {
1143 int32_t count = -1 * static_cast<int32_t>( node->children.size() );
1144 fmt::println( m_outputFile, "/Count {}", count );
1145 fmt::println( m_outputFile, "/First {} 0 R", node->children.front()->entryHandle );
1146 fmt::println( m_outputFile, "/Last {} 0 R", node->children.back()->entryHandle );
1147 }
1148
1149 if( node->actionHandle != -1 )
1150 {
1151 fmt::println( m_outputFile, "/A {} 0 R", node->actionHandle );
1152 }
1153
1154 fmt::println( m_outputFile, ">>" );
1156 }
1157}
1158
1159
1161 const wxString& aTitle )
1162{
1163 OUTLINE_NODE *node = aParent->AddChild( aActionHandle, aTitle, allocPdfObject() );
1165
1166 return node;
1167}
1168
1169
1171{
1172 if( m_outlineRoot->children.size() > 0 )
1173 {
1174 // declare the outline object
1175 m_outlineRoot->entryHandle = allocPdfObject();
1176
1177 emitOutlineNode( m_outlineRoot.get(), -1, -1, -1 );
1178
1179 startPdfObject( m_outlineRoot->entryHandle );
1180
1181 fmt::print( m_outputFile,
1182 "<< /Type /Outlines\n"
1183 " /Count {}\n"
1184 " /First {} 0 R\n"
1185 " /Last {} 0 R\n"
1186 ">>\n",
1188 m_outlineRoot->children.front()->entryHandle,
1189 m_outlineRoot->children.back()->entryHandle
1190 );
1191
1193
1194 return m_outlineRoot->entryHandle;
1195 }
1196
1197 return -1;
1198}
1199
1201{
1202 /* We need to declare the resources we're using (fonts in particular)
1203 The useful standard one is the Helvetica family. Adding external fonts
1204 is *very* involved! */
1205 struct {
1206 const char *psname;
1207 const char *rsname;
1208 int font_handle;
1209 } fontdefs[4] = {
1210 { "/Helvetica", "/KicadFont", 0 },
1211 { "/Helvetica-Oblique", "/KicadFontI", 0 },
1212 { "/Helvetica-Bold", "/KicadFontB", 0 },
1213 { "/Helvetica-BoldOblique", "/KicadFontBI", 0 }
1214 };
1215
1216 /* Declare the font resources. Since they're builtin fonts, no descriptors (yay!)
1217 We'll need metrics anyway to do any alignment (these are in the shared with
1218 the postscript engine) */
1219 for( int i = 0; i < 4; i++ )
1220 {
1221 fontdefs[i].font_handle = startPdfObject();
1222 fmt::println( m_outputFile,
1223 "<< /BaseFont {}\n"
1224 " /Type /Font\n"
1225 " /Subtype /Type1\n"
1226 " /Encoding /WinAnsiEncoding\n"
1227 ">>",
1228 fontdefs[i].psname );
1230 }
1231
1232 // Named font dictionary (was allocated, now we emit it)
1234 fmt::println( m_outputFile, "<<" );
1235
1236 for( int i = 0; i < 4; i++ )
1237 {
1238 fmt::println( m_outputFile, " {} {} 0 R",
1239 fontdefs[i].rsname, fontdefs[i].font_handle );
1240 }
1241
1242 fmt::println( m_outputFile, ">>" );
1244
1245 // Named image dictionary (was allocated, now we emit it)
1247 fmt::println( m_outputFile, "<<\n" );
1248
1249 for( const auto& [imgHandle, image] : m_imageHandles )
1250 {
1251 fmt::print( m_outputFile, " /Im{} {} 0 R\n", imgHandle, imgHandle );
1252 }
1253
1254 fmt::println( m_outputFile, ">>" );
1256
1257 // Emit images with optional SMask for transparency
1258 for( const auto& [imgHandle, image] : m_imageHandles )
1259 {
1260 // Init wxFFile so wxFFileOutputStream won't close file in dtor.
1261 wxFFile outputFFile( m_outputFile );
1262
1263 // Image
1264 startPdfObject( imgHandle );
1265 int imgLenHandle = allocPdfObject();
1266 int smaskHandle = ( image.HasAlpha() || image.HasMask() ) ? allocPdfObject() : -1;
1267
1268 fmt::print( m_outputFile,
1269 "<<\n"
1270 "/Type /XObject\n"
1271 "/Subtype /Image\n"
1272 "/BitsPerComponent 8\n"
1273 "/ColorSpace {}\n"
1274 "/Width {}\n"
1275 "/Height {}\n"
1276 "/Filter /FlateDecode\n"
1277 "/Length {} 0 R\n", // Length is deferred
1278 m_colorMode ? "/DeviceRGB" : "/DeviceGray",
1279 image.GetWidth(),
1280 image.GetHeight(),
1281 imgLenHandle );
1282
1283 if( smaskHandle != -1 )
1284 fmt::println( m_outputFile, "/SMask {} 0 R", smaskHandle );
1285
1286 fmt::println( m_outputFile, ">>" );
1287 fmt::println( m_outputFile, "stream" );
1288
1289 long imgStreamStart = ftell( m_outputFile );
1290
1291 {
1292 wxFFileOutputStream ffos( outputFFile );
1293 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1294 wxDataOutputStream dos( zos );
1295
1297 m_colorMode );
1298 }
1299
1300 long imgStreamSize = ftell( m_outputFile ) - imgStreamStart;
1301
1302 fmt::print( m_outputFile, "\nendstream\n" );
1304
1305 startPdfObject( imgLenHandle );
1306 fmt::println( m_outputFile, "{}", imgStreamSize );
1308
1309 if( smaskHandle != -1 )
1310 {
1311 // SMask
1312 startPdfObject( smaskHandle );
1313 int smaskLenHandle = allocPdfObject();
1314
1315 fmt::print( m_outputFile,
1316 "<<\n"
1317 "/Type /XObject\n"
1318 "/Subtype /Image\n"
1319 "/BitsPerComponent 8\n"
1320 "/ColorSpace /DeviceGray\n"
1321 "/Width {}\n"
1322 "/Height {}\n"
1323 "/Length {} 0 R\n"
1324 "/Filter /FlateDecode\n"
1325 ">>\n", // Length is deferred
1326 image.GetWidth(),
1327 image.GetHeight(),
1328 smaskLenHandle );
1329
1330 fmt::println( m_outputFile, "stream" );
1331
1332 long smaskStreamStart = ftell( m_outputFile );
1333
1334 {
1335 wxFFileOutputStream ffos( outputFFile );
1336 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1337 wxDataOutputStream dos( zos );
1338
1340 }
1341
1342 long smaskStreamSize = ftell( m_outputFile ) - smaskStreamStart;
1343
1344 fmt::print( m_outputFile, "\nendstream\n" );
1346
1347 startPdfObject( smaskLenHandle );
1348 fmt::println( m_outputFile, "{}", (unsigned) smaskStreamSize );
1350 }
1351
1352 outputFFile.Detach(); // Don't close it
1353 }
1354
1355 for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
1356 {
1357 BOX2D box = linkPair.first;
1358 wxString url = linkPair.second;
1359
1360 startPdfObject( linkHandle );
1361
1362 fmt::print( m_outputFile,
1363 "<<\n"
1364 "/Type /Annot\n"
1365 "/Subtype /Link\n"
1366 "/Rect [{:g} {:g} {:g} {:g}]\n"
1367 "/Border [16 16 0]\n",
1368 box.GetLeft(),
1369 box.GetBottom(),
1370 box.GetRight(),
1371 box.GetTop() );
1372
1373 wxString pageNumber;
1374 bool pageFound = false;
1375
1376 if( EDA_TEXT::IsGotoPageHref( url, &pageNumber ) )
1377 {
1378 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1379 {
1380 if( m_pageNumbers[ii] == pageNumber )
1381 {
1382 fmt::print( m_outputFile,
1383 "/Dest [{} 0 R /FitB]\n"
1384 ">>\n",
1385 m_pageHandles[ii] );
1386
1387 pageFound = true;
1388 break;
1389 }
1390 }
1391
1392 if( !pageFound )
1393 {
1394 // destination page is not being plotted, assign the NOP action to the link
1395 fmt::print( m_outputFile, "/A << /Type /Action /S /NOP >>\n"
1396 ">>\n" );
1397 }
1398 }
1399 else
1400 {
1401 if( m_project )
1402 url = ResolveUriByEnvVars( url, m_project );
1403
1404 fmt::print( m_outputFile,
1405 "/A << /Type /Action /S /URI /URI {} >>\n"
1406 ">>\n",
1407 encodeStringForPlotter( url ) );
1408 }
1409
1411 }
1412
1413 for( const auto& [ menuHandle, menuPair ] : m_hyperlinkMenuHandles )
1414 {
1415 const BOX2D& box = menuPair.first;
1416 const std::vector<wxString>& urls = menuPair.second;
1417 wxString js = wxT( "ShM([\n" );
1418
1419 for( const wxString& url : urls )
1420 {
1421 if( url.StartsWith( "!" ) )
1422 {
1423 wxString property = url.AfterFirst( '!' );
1424
1425 if( property.Find( "http:" ) >= 0 )
1426 {
1427 wxString href = property.substr( property.Find( "http:" ) );
1428
1429 if( m_project )
1430 href = ResolveUriByEnvVars( href, m_project );
1431
1432 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1433 EscapeString( href, CTX_JS_STR ),
1434 EscapeString( href, CTX_JS_STR ) );
1435 }
1436 else if( property.Find( "https:" ) >= 0 )
1437 {
1438 wxString href = property.substr( property.Find( "https:" ) );
1439
1440 if( m_project )
1441 href = ResolveUriByEnvVars( href, m_project );
1442
1443 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1444 EscapeString( href, CTX_JS_STR ),
1445 EscapeString( href, CTX_JS_STR ) );
1446 }
1447 else if( property.Find( "file:" ) >= 0 )
1448 {
1449 wxString href = property.substr( property.Find( "file:" ) );
1450
1451 if( m_project )
1452 href = ResolveUriByEnvVars( href, m_project );
1453
1454 href = NormalizeFileUri( href );
1455
1456 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1457 EscapeString( href, CTX_JS_STR ),
1458 EscapeString( href, CTX_JS_STR ) );
1459 }
1460 else
1461 {
1462 js += wxString::Format( wxT( "[\"%s\"],\n" ),
1463 EscapeString( property, CTX_JS_STR ) );
1464 }
1465 }
1466 else if( url.StartsWith( "#" ) )
1467 {
1468 wxString pageNumber = url.AfterFirst( '#' );
1469
1470 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1471 {
1472 if( m_pageNumbers[ii] == pageNumber )
1473 {
1474 wxString menuText = wxString::Format( _( "Show Page %s" ), pageNumber );
1475
1476 js += wxString::Format( wxT( "[\"%s\", \"#%d\"],\n" ),
1477 EscapeString( menuText, CTX_JS_STR ),
1478 static_cast<int>( ii ) );
1479 break;
1480 }
1481 }
1482 }
1483 else if( url.StartsWith( "http:" ) || url.StartsWith( "https:" )
1484 || url.StartsWith( "file:" ) )
1485 {
1486 wxString href = url;
1487
1488 if( m_project )
1489 href = ResolveUriByEnvVars( url, m_project );
1490
1491 if( url.StartsWith( "file:" ) )
1492 href = NormalizeFileUri( href );
1493
1494 wxString menuText = wxString::Format( _( "Open %s" ), href );
1495
1496 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1497 EscapeString( href, CTX_JS_STR ),
1498 EscapeString( href, CTX_JS_STR ) );
1499 }
1500 }
1501
1502 js += wxT( "]);" );
1503
1504 startPdfObject( menuHandle );
1505
1506 fmt::print( m_outputFile,
1507 "<<\n"
1508 "/Type /Annot\n"
1509 "/Subtype /Link\n"
1510 "/Rect [{:g} {:g} {:g} {:g}]\n"
1511 "/Border [16 16 0]\n",
1512 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
1513
1514 fmt::print( m_outputFile,
1515 "/A << /Type /Action /S /JavaScript /JS {} >>\n"
1516 ">>\n",
1517 encodeStringForPlotter( js ) );
1518
1520 }
1521
1522 {
1524
1525 wxString js = R"JS(
1526function ShM(aEntries) {
1527 var aParams = [];
1528 for (var i in aEntries) {
1529 aParams.push({
1530 cName: aEntries[i][0],
1531 cReturn: aEntries[i][1]
1532 })
1533 }
1534
1535 var cChoice = app.popUpMenuEx.apply(app, aParams);
1536 if (cChoice != null && cChoice.substring(0, 1) == '#') this.pageNum = parseInt(cChoice.slice(1));
1537 else if (cChoice != null && cChoice.substring(0, 4) == 'http') app.launchURL(cChoice);
1538 else if (cChoice != null && cChoice.substring(0, 4) == 'file') app.openDoc(cChoice.substring(7));
1539}
1540)JS";
1541
1542 fmt::print( m_outputFile,
1543 "<< /JavaScript\n"
1544 " << /Names\n"
1545 " [ (JSInit) << /Type /Action /S /JavaScript /JS {} >> ]\n"
1546 " >>\n"
1547 ">>\n",
1548 encodeStringForPlotter( js ) );
1549
1551 }
1552}
1553
1555{
1556 // We can end up here if there was nothing to plot
1557 if( !m_outputFile )
1558 return false;
1559
1560 // Close the current page (often the only one)
1561 ClosePage();
1562
1563 if( !m_3dExportMode )
1564 {
1566 }
1567
1568 /* The page tree: it's a B-tree but luckily we only have few pages!
1569 So we use just an array... The handle was allocated at the beginning,
1570 now we instantiate the corresponding object */
1572 fmt::print( m_outputFile,
1573 "<<\n"
1574 "/Type /Pages\n"
1575 "/Kids [\n" );
1576
1577 for( unsigned i = 0; i < m_pageHandles.size(); i++ )
1578 fmt::println( m_outputFile, "{} 0 R", m_pageHandles[i] );
1579
1580 fmt::print( m_outputFile,
1581 "]\n"
1582 "/Count {}\n"
1583 ">>\n", m_pageHandles.size() );
1585
1586 int infoDictHandle = startPdfObject();
1587
1588 std::string dt = fmt::format( "D:{:%Y:%m:%d:%H:%M:%S}", fmt::localtime( std::time( nullptr ) ) );
1589
1590 if( m_title.IsEmpty() )
1591 {
1592 // Windows uses '\' and other platforms use '/' as separator
1593 m_title = m_filename.AfterLast( '\\' );
1594 m_title = m_title.AfterLast( '/' );
1595 }
1596
1597 fmt::print( m_outputFile,
1598 "<<\n"
1599 "/Producer (KiCad PDF)\n"
1600 "/CreationDate ({})\n"
1601 "/Creator {}\n"
1602 "/Title {}\n"
1603 "/Author {}\n"
1604 "/Subject {}\n",
1605 dt,
1610
1611 fmt::println( m_outputFile, ">>" );
1613
1614 // Let's dump in the outline
1615 int outlineHandle = -1;
1616 if( !m_3dExportMode )
1617 {
1618 outlineHandle = emitOutline();
1619 }
1620
1621 // The catalog, at last
1622 int catalogHandle = startPdfObject();
1623
1624 if( outlineHandle > 0 )
1625 {
1626 fmt::println( m_outputFile,
1627 "<<\n"
1628 "/Type /Catalog\n"
1629 "/Pages {} 0 R\n"
1630 "/Version /1.5\n"
1631 "/PageMode /UseOutlines\n"
1632 "/Outlines {} 0 R\n"
1633 "/Names {} 0 R\n"
1634 "/PageLayout /SinglePage\n"
1635 ">>",
1637 outlineHandle,
1639 }
1640 else
1641 {
1642 fmt::println( m_outputFile,
1643 "<<\n"
1644 "/Type /Catalog\n"
1645 "/Pages {} 0 R\n"
1646 "/Version /1.5\n"
1647 "/PageMode /UseNone\n"
1648 "/PageLayout /SinglePage\n"
1649 ">>",
1651 }
1652
1654
1655 /* Emit the xref table (format is crucial to the byte, each entry must
1656 be 20 bytes long, and object zero must be done in that way). Also
1657 the offset must be kept along for the trailer */
1658 long xref_start = ftell( m_outputFile );
1659 fmt::print( m_outputFile,
1660 "xref\n"
1661 "0 {}\n"
1662 "0000000000 65535 f \n",
1663 m_xrefTable.size() );
1664
1665 for( unsigned i = 1; i < m_xrefTable.size(); i++ )
1666 {
1667 fmt::print( m_outputFile, "{:010d} 00000 n \n", m_xrefTable[i] );
1668 }
1669
1670 // Done the xref, go for the trailer
1671 fmt::print( m_outputFile,
1672 "trailer\n"
1673 "<< /Size {} /Root {} 0 R /Info {} 0 R >>\n"
1674 "startxref\n"
1675 "{}\n" // The offset we saved before
1676 "%%EOF\n",
1677 m_xrefTable.size(),
1678 catalogHandle,
1679 infoDictHandle,
1680 xref_start );
1681
1682 fclose( m_outputFile );
1683 m_outputFile = nullptr;
1684
1685 return true;
1686}
1687
1688
1689void PDF_PLOTTER::Text( const VECTOR2I& aPos,
1690 const COLOR4D& aColor,
1691 const wxString& aText,
1692 const EDA_ANGLE& aOrient,
1693 const VECTOR2I& aSize,
1694 enum GR_TEXT_H_ALIGN_T aH_justify,
1695 enum GR_TEXT_V_ALIGN_T aV_justify,
1696 int aWidth,
1697 bool aItalic,
1698 bool aBold,
1699 bool aMultilineAllowed,
1700 KIFONT::FONT* aFont,
1701 const KIFONT::METRICS& aFontMetrics,
1702 void* aData )
1703{
1704 // PDF files do not like 0 sized texts which create broken files.
1705 if( aSize.x == 0 || aSize.y == 0 )
1706 return;
1707
1708 // Render phantom text (which will be searchable) behind the stroke font. This won't
1709 // be pixel-accurate, but it doesn't matter for searching.
1710 int render_mode = 3; // invisible
1711
1712 VECTOR2I pos( aPos );
1713 const char *fontname = aItalic ? ( aBold ? "/KicadFontBI" : "/KicadFontI" )
1714 : ( aBold ? "/KicadFontB" : "/KicadFont" );
1715
1716 // Compute the copious transformation parameters of the Current Transform Matrix
1717 double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
1718 double wideningFactor, heightFactor;
1719
1720 VECTOR2I t_size( std::abs( aSize.x ), std::abs( aSize.y ) );
1721 bool textMirrored = aSize.x < 0;
1722
1723 computeTextParameters( aPos, aText, aOrient, t_size, textMirrored, aH_justify, aV_justify,
1724 aWidth, aItalic, aBold, &wideningFactor, &ctm_a, &ctm_b, &ctm_c, &ctm_d,
1725 &ctm_e, &ctm_f, &heightFactor );
1726
1727 SetColor( aColor );
1728 SetCurrentLineWidth( aWidth, aData );
1729
1730 wxStringTokenizer str_tok( aText, " ", wxTOKEN_RET_DELIMS );
1731
1732 // If aFont is not specilied (== nullptr), use the default kicad stroke font
1733 if( !aFont )
1734 aFont = KIFONT::FONT::GetFont();
1735
1736 VECTOR2I full_box( aFont->StringBoundaryLimits( aText, t_size, aWidth, aBold, aItalic,
1737 aFontMetrics ) );
1738
1739 if( textMirrored )
1740 full_box.x *= -1;
1741
1742 VECTOR2I box_x( full_box.x, 0 );
1743 VECTOR2I box_y( 0, full_box.y );
1744
1745 RotatePoint( box_x, aOrient );
1746 RotatePoint( box_y, aOrient );
1747
1748 if( aH_justify == GR_TEXT_H_ALIGN_CENTER )
1749 pos -= box_x / 2;
1750 else if( aH_justify == GR_TEXT_H_ALIGN_RIGHT )
1751 pos -= box_x;
1752
1753 if( aV_justify == GR_TEXT_V_ALIGN_CENTER )
1754 pos += box_y / 2;
1755 else if( aV_justify == GR_TEXT_V_ALIGN_TOP )
1756 pos += box_y;
1757
1758 while( str_tok.HasMoreTokens() )
1759 {
1760 wxString word = str_tok.GetNextToken();
1761
1762 computeTextParameters( pos, word, aOrient, t_size, textMirrored, GR_TEXT_H_ALIGN_LEFT,
1763 GR_TEXT_V_ALIGN_BOTTOM, aWidth, aItalic, aBold, &wideningFactor,
1764 &ctm_a, &ctm_b, &ctm_c, &ctm_d, &ctm_e, &ctm_f, &heightFactor );
1765
1766 // Extract the changed width and rotate by the orientation to get the offset for the
1767 // next word
1768 VECTOR2I bbox( aFont->StringBoundaryLimits( word, t_size, aWidth,
1769 aBold, aItalic, aFontMetrics ).x, 0 );
1770
1771 if( textMirrored )
1772 bbox.x *= -1;
1773
1774 RotatePoint( bbox, aOrient );
1775 pos += bbox;
1776
1777 // Don't try to output a blank string
1778 if( word.Trim( false ).Trim( true ).empty() )
1779 continue;
1780
1781 /* We use the full CTM instead of the text matrix because the same
1782 coordinate system will be used for the overlining. Also the %f
1783 for the trig part of the matrix to avoid %g going in exponential
1784 format (which is not supported) */
1785 fmt::print( m_workFile, "q {:f} {:f} {:f} {:f} {:f} {:f} cm BT {} {:g} Tf {} Tr {:g} Tz ",
1786 ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f,
1787 fontname,
1788 heightFactor,
1789 render_mode,
1790 wideningFactor * 100 );
1791
1792 std::string txt_pdf = encodeStringForPlotter( word );
1793 fmt::println( m_workFile, "{} Tj ET", txt_pdf );
1794 // Restore the CTM
1795 fmt::println( m_workFile, "Q" );
1796 }
1797
1798 // Plot the stroked text (if requested)
1799 PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, aWidth, aItalic,
1800 aBold, aMultilineAllowed, aFont, aFontMetrics );
1801}
1802
1803
1805 const COLOR4D& aColor,
1806 const wxString& aText,
1807 const TEXT_ATTRIBUTES& aAttributes,
1808 KIFONT::FONT* aFont,
1809 const KIFONT::METRICS& aFontMetrics,
1810 void* aData )
1811{
1812 VECTOR2I size = aAttributes.m_Size;
1813
1814 // PDF files do not like 0 sized texts which create broken files.
1815 if( size.x == 0 || size.y == 0 )
1816 return;
1817
1818 if( aAttributes.m_Mirrored )
1819 size.x = -size.x;
1820
1821 PDF_PLOTTER::Text( aPos, aColor, aText, aAttributes.m_Angle, size, aAttributes.m_Halign,
1822 aAttributes.m_Valign, aAttributes.m_StrokeWidth, aAttributes.m_Italic,
1823 aAttributes.m_Bold, aAttributes.m_Multiline, aFont, aFontMetrics, aData );
1824}
1825
1826
1827void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL )
1828{
1829 m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
1830}
1831
1832
1833void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
1834{
1835 m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
1836}
1837
1838
1839void PDF_PLOTTER::Bookmark( const BOX2I& aLocation, const wxString& aSymbolReference,
1840 const wxString &aGroupName )
1841{
1842
1843 m_bookmarksInPage[aGroupName].push_back( std::make_pair( aLocation, aSymbolReference ) );
1844}
1845
1846
1847void PDF_PLOTTER::Plot3DModel( const wxString& aSourcePath,
1848 const std::vector<PDF_3D_VIEW>& a3DViews )
1849{
1850 std::map<float, int> m_fovMap;
1851 std::vector<int> m_viewHandles;
1852
1853 for( const PDF_3D_VIEW& view : a3DViews )
1854 {
1855 int fovHandle = -1;
1856 if( !m_fovMap.contains( view.m_fov ) )
1857 {
1858 fovHandle = allocPdfObject();
1859 m_fovMap[view.m_fov] = fovHandle;
1860
1861 startPdfObject( fovHandle );
1862 fmt::print( m_outputFile,
1863 "<<\n"
1864 "/FOV {}\n"
1865 "/PS /Min\n"
1866 "/Subtype /P\n"
1867 ">>\n",
1868 view.m_fov );
1870 }
1871 else
1872 {
1873 fovHandle = m_fovMap[view.m_fov];
1874 }
1875
1876 int viewHandle = allocPdfObject();
1877 startPdfObject( viewHandle );
1878
1879 fmt::print( m_outputFile,
1880 "<<\n"
1881 "/Type /3DView\n"
1882 "/XN ({})\n"
1883 "/IN ({})\n"
1884 "/MS /M\n"
1885 "/C2W [{:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f} {:f}]\n"
1886 "/CO {:f}\n"
1887 "/NR false\n"
1888 "/BG<<\n"
1889 "/Type /3DBG\n"
1890 "/Subtype /SC\n"
1891 "/CS /DeviceRGB\n"
1892 "/C [1.000000 1.000000 1.000000]>>\n"
1893 "/P {} 0 R\n"
1894 "/LS<<\n"
1895 "/Type /3DLightingScheme\n"
1896 "/Subtype /CAD>>\n"
1897 ">>\n",
1898 view.m_name, view.m_name, view.m_cameraMatrix[0],
1899 view.m_cameraMatrix[1],
1900 view.m_cameraMatrix[2], view.m_cameraMatrix[3], view.m_cameraMatrix[4],
1901 view.m_cameraMatrix[5], view.m_cameraMatrix[6], view.m_cameraMatrix[7],
1902 view.m_cameraMatrix[8], view.m_cameraMatrix[9], view.m_cameraMatrix[10],
1903 view.m_cameraMatrix[11],
1904 view.m_cameraCenter,
1905 fovHandle );
1906
1908
1909 m_viewHandles.push_back( viewHandle );
1910 }
1911
1913
1914 // so we can get remotely stuff the length afterwards
1915 int modelLenHandle = allocPdfObject();
1916
1917 fmt::print( m_outputFile,
1918 "<<\n"
1919 "/Type /3D\n"
1920 "/Subtype /U3D\n"
1921 "/DV 0\n" );
1922
1923 fmt::print( m_outputFile, "/VA [" );
1924 for( int viewHandle : m_viewHandles )
1925 {
1926 fmt::print( m_outputFile, "{} 0 R ", viewHandle );
1927 }
1928 fmt::print( m_outputFile, "]\n" );
1929
1930 fmt::print( m_outputFile,
1931 "/Length {} 0 R\n"
1932 "/Filter /FlateDecode\n"
1933 ">>\n", // Length is deferred
1934 modelLenHandle );
1935
1936 fmt::println( m_outputFile, "stream" );
1937
1938 wxFFile outputFFile( m_outputFile );
1939
1940 fflush( m_outputFile );
1941 long imgStreamStart = ftell( m_outputFile );
1942
1943 size_t model_stored_size = 0;
1944 {
1945 wxFFileOutputStream ffos( outputFFile );
1946 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1947
1948 wxFFileInputStream fileStream( aSourcePath );
1949 if( !fileStream.IsOk() )
1950 {
1951 wxLogError( _( "Failed to open 3D model file: %s" ), aSourcePath );
1952 }
1953
1954 zos.Write( fileStream );
1955 }
1956 fflush( m_outputFile );
1957 model_stored_size = ftell( m_outputFile );
1958 model_stored_size -= imgStreamStart; // Get the size of the compressed stream
1959
1960 fmt::print( m_outputFile, "\nendstream\n" );
1962
1963 startPdfObject( modelLenHandle );
1964 fmt::println( m_outputFile, "{}", (unsigned) model_stored_size );
1966
1967 outputFFile.Detach(); // Don't close it
1968}
void WriteImageSMaskStream(const wxImage &aImage, wxDataOutputStream &aOut)
void WriteImageStream(const wxImage &aImage, wxDataOutputStream &aOut, wxColor bg, bool colorMode)
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
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 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
static bool IsGotoPageHref(const wxString &aHref, wxString *aDestination=nullptr)
Check if aHref is a valid internal hyperlink.
Definition: eda_text.cpp:1296
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
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
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
wxColour ToColour() const
Definition: color4d.cpp:220
int GetDefaultPenWidth() const
virtual const COLOR4D & GetBackgroundColor() const =0
Return current background color settings.
const VECTOR2D & GetSizeMils() const
Definition: page_info.h:144
std::vector< int > m_pageHandles
Handles to the page objects.
FILE * m_workFile
Temporary file to construct the stream before zipping.
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 void Circle(const VECTOR2I &pos, int diametre, FILL_T fill, int width) override
Circle drawing for PDF.
virtual void SetCurrentLineWidth(int width, void *aData=nullptr) override
Pen width setting for PDF.
int m_streamLengthHandle
Handle to the deferred stream length.
void PlotImage(const wxImage &aImage, const VECTOR2I &aPos, double aScaleFactor) override
PDF images are handles as inline, not XObject streams...
int m_jsNamesHandle
Handle for Names dictionary with JS.
wxString m_pageName
virtual void SetDash(int aLineWidth, LINE_STYLE aLineStyle) override
PDF supports dashed lines.
void HyperlinkMenu(const BOX2I &aBox, const std::vector< wxString > &aDestURLs) override
Create a clickable hyperlink menu with a rectangular click area.
virtual bool OpenFile(const wxString &aFullFilename) override
Open or create the plot file aFullFilename.
int m_fontResDictHandle
Font resource dictionary.
virtual void emitSetRGBColor(double r, double g, double b, double a) override
PDF supports colors fully.
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()
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.
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...
Definition: PDF_plotter.cpp:56
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.
wxString m_workFilename
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
virtual void Rect(const VECTOR2I &p1, const VECTOR2I &p2, FILL_T fill, int width) override
Rectangles in PDF.
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.
double GetDotMarkLenIU(int aLineWidth) const
Definition: plotter.cpp:132
double GetDashGapLenIU(int aLineWidth) const
Definition: plotter.cpp:144
const PROJECT * m_project
Definition: plotter.h:681
wxString m_subject
Definition: plotter.h:673
bool m_mirrorIsHorizontal
Definition: plotter.h:656
PAGE_INFO m_pageInfo
Definition: plotter.h:674
bool m_plotMirror
Definition: plotter.h:654
static const int USE_DEFAULT_LINE_WIDTH
Definition: plotter.h:125
void MoveTo(const VECTOR2I &pos)
Definition: plotter.h:261
void FinishTo(const VECTOR2I &pos)
Definition: plotter.h:271
wxString m_author
Definition: plotter.h:672
double m_iuPerDeviceUnit
Definition: plotter.h:651
VECTOR2I m_plotOffset
Definition: plotter.h:653
VECTOR2I m_penLastpos
Definition: plotter.h:667
virtual VECTOR2D userToDeviceCoordinates(const VECTOR2I &aCoordinate)
Modify coordinates according to the orientation, scale factor, and offsets trace.
Definition: plotter.cpp:91
VECTOR2I m_paperSize
Definition: plotter.h:675
virtual VECTOR2D userToDeviceSize(const VECTOR2I &size)
Modify size according to the plotter scale factors (VECTOR2I version, returns a VECTOR2D).
Definition: plotter.cpp:116
char m_penState
Definition: plotter.h:666
wxString m_creator
Definition: plotter.h:669
int m_currentPenWidth
Definition: plotter.h:665
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition: plotter.h:643
FILE * m_outputFile
Output file.
Definition: plotter.h:660
static const int DO_NOT_SET_LINE_WIDTH
Definition: plotter.h:124
RENDER_SETTINGS * m_renderSettings
Definition: plotter.h:679
virtual void Text(const VECTOR2I &aPos, const COLOR4D &aColor, const wxString &aText, const EDA_ANGLE &aOrient, const VECTOR2I &aSize, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify, int aPenWidth, bool aItalic, bool aBold, bool aMultilineAllowed, KIFONT::FONT *aFont, const KIFONT::METRICS &aFontMetrics, void *aData=nullptr)
Draw text with the plotter.
Definition: plotter.cpp:624
double m_IUsPerDecimil
Definition: plotter.h:649
wxString m_title
Definition: plotter.h:671
virtual int GetCurrentLineWidth() const
Definition: plotter.h:167
bool m_colorMode
Definition: plotter.h:663
double GetDashMarkLenIU(int aLineWidth) const
Definition: plotter.cpp:138
wxString m_filename
Definition: plotter.h:670
virtual void SetColor(const COLOR4D &color) override
The SetColor implementation is split with the subclasses: the PSLIKE computes the rgb values,...
Definition: PS_plotter.cpp:64
double plotScaleAdjX
Fine user scale adjust ( = 1.0 if no correction)
void computeTextParameters(const VECTOR2I &aPos, const wxString &aText, const EDA_ANGLE &aOrient, const VECTOR2I &aSize, bool aMirror, enum GR_TEXT_H_ALIGN_T aH_justify, enum GR_TEXT_V_ALIGN_T aV_justify, int aWidth, bool aItalic, bool aBold, double *wideningFactor, double *ctm_a, double *ctm_b, double *ctm_c, double *ctm_d, double *ctm_e, double *ctm_f, double *heightFactor)
This is the core for postscript/PDF text alignment.
Definition: PS_plotter.cpp:302
GR_TEXT_H_ALIGN_T m_Halign
GR_TEXT_V_ALIGN_T m_Valign
const wxString ResolveUriByEnvVars(const wxString &aUri, const PROJECT *aProject)
Replace any environment and/or text variables in URIs.
Definition: common.cpp:366
The common library.
#define _(s)
@ DEGREES_T
Definition: eda_angle.h:31
FILL_T
Definition: eda_shape.h:56
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:393
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:...
@ CTX_JS_STR
Definition: string_utils.h:58
LINE_STYLE
Dashed line types.
Definition: stroke_params.h:46
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
constexpr 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
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