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 (C) 1992-2023, 2024 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
33#include <wx/filename.h>
34#include <wx/mstream.h>
35#include <wx/zstream.h>
36#include <wx/wfstream.h>
37#include <wx/datstrm.h>
38#include <wx/tokenzr.h>
39
40#include <advanced_config.h>
41#include <common.h> // ResolveUriByEnvVars
42#include <eda_text.h> // for IsGotoPageHref
43#include <font/font.h>
44#include <core/ignore.h>
45#include <macros.h>
46#include <trigo.h>
47#include <string_utils.h>
48
50
51
52std::string PDF_PLOTTER::encodeStringForPlotter( const wxString& aText )
53{
54 // returns a string compatible with PDF string convention from a unicode string.
55 // if the initial text is only ASCII7, return the text between ( and ) for a good readability
56 // if the initial text is no ASCII7, return the text between < and >
57 // and encoded using 16 bits hexa (4 digits) by wide char (unicode 16)
58 std::string result;
59
60 // Is aText only ASCII7 ?
61 bool is_ascii7 = true;
62
63 for( size_t ii = 0; ii < aText.Len(); ii++ )
64 {
65 if( aText[ii] >= 0x7F )
66 {
67 is_ascii7 = false;
68 break;
69 }
70 }
71
72 if( is_ascii7 )
73 {
74 result = '(';
75
76 for( unsigned ii = 0; ii < aText.Len(); ii++ )
77 {
78 unsigned int code = aText[ii];
79
80 // These characters must be escaped
81 switch( code )
82 {
83 case '(':
84 case ')':
85 case '\\':
86 result += '\\';
88
89 default:
90 result += code;
91 break;
92 }
93 }
94
95 result += ')';
96 }
97 else
98 {
99 result = "<FEFF";
100
101
102 for( size_t ii = 0; ii < aText.Len(); ii++ )
103 {
104 unsigned int code = aText[ii];
105 char buffer[16];
106 std::snprintf( buffer, sizeof( buffer ), "%4.4X", code );
107 result += buffer;
108
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 fprintf( m_workFile, "%g w\n", 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 fprintf( m_workFile, "%g %g %g rg %g %g %g RG\n", 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 fprintf( m_workFile, "[%d %d] 0 d\n",
196 (int) GetDashMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) );
197 break;
198
199 case LINE_STYLE::DOT:
200 fprintf( m_workFile, "[%d %d] 0 d\n",
201 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) );
202 break;
203
204 case LINE_STYLE::DASHDOT:
205 fprintf( m_workFile, "[%d %d %d %d] 0 d\n",
206 (int) GetDashMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ),
207 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) );
208 break;
209
210 case LINE_STYLE::DASHDOTDOT:
211 fprintf( m_workFile, "[%d %d %d %d %d %d] 0 d\n",
212 (int) GetDashMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ),
213 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ),
214 (int) GetDotMarkLenIU( aLineWidth ), (int) GetDashGapLenIU( aLineWidth ) );
215 break;
216
217 default:
218 fputs( "[] 0 d\n", m_workFile );
219 }
220}
221
222
223void PDF_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill, int width )
224{
225 wxASSERT( m_workFile );
226
227 if( fill == FILL_T::NO_FILL && width <= 0 )
228 return;
229
230 SetCurrentLineWidth( width );
231
232 VECTOR2I size = p2 - p1;
233
234 if( size.x == 0 && size.y == 0 )
235 {
236 // Can't draw zero-sized rectangles
237 MoveTo( VECTOR2I( p1.x, p1.y ) );
238 FinishTo( VECTOR2I( p1.x, p1.y ) );
239
240 return;
241 }
242
243 if( std::min( std::abs( size.x ), std::abs( size.y ) ) < width )
244 {
245 // Too thick stroked rectangles are buggy, draw as polygon
246 std::vector<VECTOR2I> cornerList;
247
248 cornerList.emplace_back( p1.x, p1.y );
249 cornerList.emplace_back( p2.x, p1.y );
250 cornerList.emplace_back( p2.x, p2.y );
251 cornerList.emplace_back( p1.x, p2.y );
252 cornerList.emplace_back( p1.x, p1.y );
253
254 PlotPoly( cornerList, fill, width, nullptr );
255
256 return;
257 }
258
259 VECTOR2D p1_dev = userToDeviceCoordinates( p1 );
260 VECTOR2D p2_dev = userToDeviceCoordinates( p2 );
261
262 char paintOp;
263
264 if( fill == FILL_T::NO_FILL )
265 paintOp = 'S';
266 else
267 paintOp = width > 0 ? 'B' : 'f';
268
269 fprintf( m_workFile, "%g %g %g %g re %c\n", p1_dev.x, p1_dev.y, p2_dev.x - p1_dev.x,
270 p2_dev.y - p1_dev.y, paintOp );
271}
272
273
274void PDF_PLOTTER::Circle( const VECTOR2I& pos, int diametre, FILL_T aFill, int width )
275{
276 wxASSERT( m_workFile );
277
278 if( aFill == FILL_T::NO_FILL && width <= 0 )
279 return;
280
281 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
282 double radius = userToDeviceSize( diametre / 2.0 );
283
284 /* OK. Here's a trick. PDF doesn't support circles or circular angles, that's
285 a fact. You'll have to do with cubic beziers. These *can't* represent
286 circular arcs (NURBS can, beziers don't). But there is a widely known
287 approximation which is really good
288 */
289
290 SetCurrentLineWidth( width );
291
292 // If diameter is less than width, switch to filled mode
293 if( aFill == FILL_T::NO_FILL && diametre < width )
294 {
295 aFill = FILL_T::FILLED_SHAPE;
297
298 radius = userToDeviceSize( ( diametre / 2.0 ) + ( width / 2.0 ) );
299 }
300
301 double magic = radius * 0.551784; // You don't want to know where this come from
302
303 // This is the convex hull for the bezier approximated circle
304 fprintf( m_workFile,
305 "%g %g m "
306 "%g %g %g %g %g %g c "
307 "%g %g %g %g %g %g c "
308 "%g %g %g %g %g %g c "
309 "%g %g %g %g %g %g c %c\n",
310 pos_dev.x - radius, pos_dev.y,
311
312 pos_dev.x - radius, pos_dev.y + magic,
313 pos_dev.x - magic, pos_dev.y + radius,
314 pos_dev.x, pos_dev.y + radius,
315
316 pos_dev.x + magic, pos_dev.y + radius,
317 pos_dev.x + radius, pos_dev.y + magic,
318 pos_dev.x + radius, pos_dev.y,
319
320 pos_dev.x + radius, pos_dev.y - magic,
321 pos_dev.x + magic, pos_dev.y - radius,
322 pos_dev.x, pos_dev.y - radius,
323
324 pos_dev.x - magic, pos_dev.y - radius,
325 pos_dev.x - radius, pos_dev.y - magic,
326 pos_dev.x - radius, pos_dev.y,
327
328 aFill == FILL_T::NO_FILL ? 's' : 'b' );
329}
330
331
332void PDF_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
333 const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth )
334{
335 wxASSERT( m_workFile );
336
337 if( aRadius <= 0 )
338 {
339 Circle( aCenter, aWidth, FILL_T::FILLED_SHAPE, 0 );
340 return;
341 }
342
343 /*
344 * Arcs are not so easily approximated by beziers (in the general case), so we approximate
345 * them in the old way
346 */
347 EDA_ANGLE startAngle = -aStartAngle;
348 EDA_ANGLE endAngle = startAngle - aAngle;
349 VECTOR2I start;
350 VECTOR2I end;
351 const EDA_ANGLE delta( 5, DEGREES_T ); // increment to draw circles
352
353 if( startAngle > endAngle )
354 std::swap( startAngle, endAngle );
355
356 SetCurrentLineWidth( aWidth );
357
358 // Usual trig arc plotting routine...
359 start.x = KiROUND( aCenter.x + aRadius * ( -startAngle ).Cos() );
360 start.y = KiROUND( aCenter.y + aRadius * ( -startAngle ).Sin() );
361 VECTOR2D pos_dev = userToDeviceCoordinates( start );
362 fprintf( m_workFile, "%g %g m ", pos_dev.x, pos_dev.y );
363
364 for( EDA_ANGLE ii = startAngle + delta; ii < endAngle; ii += delta )
365 {
366 end.x = KiROUND( aCenter.x + aRadius * ( -ii ).Cos() );
367 end.y = KiROUND( aCenter.y + aRadius * ( -ii ).Sin() );
368 pos_dev = userToDeviceCoordinates( end );
369 fprintf( m_workFile, "%g %g l ", pos_dev.x, pos_dev.y );
370 }
371
372 end.x = KiROUND( aCenter.x + aRadius * ( -endAngle ).Cos() );
373 end.y = KiROUND( aCenter.y + aRadius * ( -endAngle ).Sin() );
374 pos_dev = userToDeviceCoordinates( end );
375 fprintf( m_workFile, "%g %g l ", pos_dev.x, pos_dev.y );
376
377 // The arc is drawn... if not filled we stroke it, otherwise we finish
378 // closing the pie at the center
379 if( aFill == FILL_T::NO_FILL )
380 {
381 fputs( "S\n", m_workFile );
382 }
383 else
384 {
385 pos_dev = userToDeviceCoordinates( aCenter );
386 fprintf( m_workFile, "%g %g l b\n", pos_dev.x, pos_dev.y );
387 }
388}
389
390
391void PDF_PLOTTER::PlotPoly( const std::vector<VECTOR2I>& aCornerList, FILL_T aFill, int aWidth,
392 void* aData )
393{
394 wxASSERT( m_workFile );
395
396 if( aFill == FILL_T::NO_FILL && aWidth <= 0 )
397 return;
398
399 if( aCornerList.size() <= 1 )
400 return;
401
402 SetCurrentLineWidth( aWidth );
403
404 VECTOR2D pos = userToDeviceCoordinates( aCornerList[0] );
405 fprintf( m_workFile, "%f %f m\n", pos.x, pos.y );
406
407 for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
408 {
409 pos = userToDeviceCoordinates( aCornerList[ii] );
410 fprintf( m_workFile, "%f %f l\n", pos.x, pos.y );
411 }
412
413 // Close path and stroke and/or fill
414 if( aFill == FILL_T::NO_FILL )
415 fputs( "S\n", m_workFile );
416 else if( aWidth == 0 )
417 fputs( "f\n", m_workFile );
418 else
419 fputs( "b\n", m_workFile );
420}
421
422
423void PDF_PLOTTER::PenTo( const VECTOR2I& pos, char plume )
424{
425 wxASSERT( m_workFile );
426
427 if( plume == 'Z' )
428 {
429 if( m_penState != 'Z' )
430 {
431 fputs( "S\n", m_workFile );
432 m_penState = 'Z';
433 m_penLastpos.x = -1;
434 m_penLastpos.y = -1;
435 }
436
437 return;
438 }
439
440 if( m_penState != plume || pos != m_penLastpos )
441 {
442 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
443 fprintf( m_workFile, "%f %f %c\n",
444 pos_dev.x, pos_dev.y,
445 ( plume=='D' ) ? 'l' : 'm' );
446 }
447
448 m_penState = plume;
449 m_penLastpos = pos;
450}
451
452
453void PDF_PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor )
454{
455 wxASSERT( m_workFile );
456 VECTOR2I pix_size( aImage.GetWidth(), aImage.GetHeight() );
457
458 // Requested size (in IUs)
459 VECTOR2D drawsize( aScaleFactor * pix_size.x, aScaleFactor * pix_size.y );
460
461 // calculate the bitmap start position
462 VECTOR2I start( aPos.x - drawsize.x / 2, aPos.y + drawsize.y / 2 );
463 VECTOR2D dev_start = userToDeviceCoordinates( start );
464
465 // Deduplicate images
466 auto findHandleForImage = [&]( const wxImage& aCurrImage ) -> int
467 {
468 for( const auto& [imgHandle, image] : m_imageHandles )
469 {
470 if( image.IsSameAs( aCurrImage ) )
471 return imgHandle;
472
473 if( image.GetWidth() != aCurrImage.GetWidth() )
474 continue;
475
476 if( image.GetHeight() != aCurrImage.GetHeight() )
477 continue;
478
479 if( image.GetType() != aCurrImage.GetType() )
480 continue;
481
482 if( image.HasAlpha() != aCurrImage.HasAlpha() )
483 continue;
484
485 if( image.HasMask() != aCurrImage.HasMask() || image.GetMaskRed() != aCurrImage.GetMaskRed()
486 || image.GetMaskGreen() != aCurrImage.GetMaskGreen()
487 || image.GetMaskBlue() != aCurrImage.GetMaskBlue() )
488 continue;
489
490 int pixCount = image.GetWidth() * image.GetHeight();
491
492 if( memcmp( image.GetData(), aCurrImage.GetData(), pixCount * 3 ) != 0 )
493 continue;
494
495 if( image.HasAlpha() && memcmp( image.GetAlpha(), aCurrImage.GetAlpha(), pixCount ) != 0 )
496 continue;
497
498 return imgHandle;
499 }
500
501 return -1;
502 };
503
504 int imgHandle = findHandleForImage( aImage );
505
506 if( imgHandle == -1 )
507 {
508 imgHandle = allocPdfObject();
509 m_imageHandles.emplace( imgHandle, aImage );
510 }
511
512 /* PDF has an uhm... simplified coordinate system handling. There is
513 *one* operator to do everything (the PS concat equivalent). At least
514 they kept the matrix stack to save restore environments. Also images
515 are always emitted at the origin with a size of 1x1 user units.
516 What we need to do is:
517 1) save the CTM end establish the new one
518 2) plot the image
519 3) restore the CTM
520 4) profit
521 */
522 fprintf( m_workFile, "q %g 0 0 %g %g %g cm\n", // Step 1
523 userToDeviceSize( drawsize.x ),
524 userToDeviceSize( drawsize.y ),
525 dev_start.x, dev_start.y );
526
527 fprintf( m_workFile, "/Im%d Do\n", imgHandle );
528 fputs( "Q\n", m_workFile );
529}
530
531
533{
534 m_xrefTable.push_back( 0 );
535 return m_xrefTable.size() - 1;
536}
537
538
540{
541 wxASSERT( m_outputFile );
542 wxASSERT( !m_workFile );
543
544 if( handle < 0)
545 handle = allocPdfObject();
546
547 m_xrefTable[handle] = ftell( m_outputFile );
548 fprintf( m_outputFile, "%d 0 obj\n", handle );
549 return handle;
550}
551
552
554{
555 wxASSERT( m_outputFile );
556 wxASSERT( !m_workFile );
557 fputs( "endobj\n", m_outputFile );
558}
559
560
562{
563 wxASSERT( m_outputFile );
564 wxASSERT( !m_workFile );
565 handle = startPdfObject( handle );
566
567 // This is guaranteed to be handle+1 but needs to be allocated since
568 // you could allocate more object during stream preparation
570
571 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
572 {
573 fprintf( m_outputFile,
574 "<< /Length %d 0 R >>\n" // Length is deferred
575 "stream\n", handle + 1 );
576 }
577 else
578 {
579 fprintf( m_outputFile,
580 "<< /Length %d 0 R /Filter /FlateDecode >>\n" // Length is deferred
581 "stream\n", handle + 1 );
582 }
583
584 // Open a temporary file to accumulate the stream
585 m_workFilename = wxFileName::CreateTempFileName( "" );
586 m_workFile = wxFopen( m_workFilename, wxT( "w+b" ) );
587 wxASSERT( m_workFile );
588 return handle;
589}
590
591
593{
594 wxASSERT( m_workFile );
595
596 long stream_len = ftell( m_workFile );
597
598 if( stream_len < 0 )
599 {
600 wxASSERT( false );
601 return;
602 }
603
604 // Rewind the file, read in the page stream and DEFLATE it
605 fseek( m_workFile, 0, SEEK_SET );
606 unsigned char *inbuf = new unsigned char[stream_len];
607
608 int rc = fread( inbuf, 1, stream_len, m_workFile );
609 wxASSERT( rc == stream_len );
610 ignore_unused( rc );
611
612 // We are done with the temporary file, junk it
613 fclose( m_workFile );
614 m_workFile = nullptr;
615 ::wxRemoveFile( m_workFilename );
616
617 unsigned out_count;
618
619 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
620 {
621 out_count = stream_len;
622 fwrite( inbuf, out_count, 1, m_outputFile );
623 }
624 else
625 {
626 // NULL means memos owns the memory, but provide a hint on optimum size needed.
627 wxMemoryOutputStream memos( nullptr, std::max( 2000l, stream_len ) ) ;
628
629 {
630 /* Somewhat standard parameters to compress in DEFLATE. The PDF spec is
631 * misleading, it says it wants a DEFLATE stream but it really want a ZLIB
632 * stream! (a DEFLATE stream would be generated with -15 instead of 15)
633 * rc = deflateInit2( &zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, 15,
634 * 8, Z_DEFAULT_STRATEGY );
635 */
636
637 wxZlibOutputStream zos( memos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
638
639 zos.Write( inbuf, stream_len );
640 } // flush the zip stream using zos destructor
641
642 wxStreamBuffer* sb = memos.GetOutputStreamBuffer();
643
644 out_count = sb->Tell();
645 fwrite( sb->GetBufferStart(), 1, out_count, m_outputFile );
646 }
647
648 delete[] inbuf;
649 fputs( "\nendstream\n", m_outputFile );
651
652 // Writing the deferred length as an indirect object
654 fprintf( m_outputFile, "%u\n", out_count );
656}
657
658
659void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageName )
660{
661 wxASSERT( m_outputFile );
662 wxASSERT( !m_workFile );
663
664 m_pageNumbers.push_back( aPageNumber );
665 m_pageName = aPageName;
666
667 // Compute the paper size in IUs
671
672 // Open the content stream; the page object will go later
674
675 /* Now, until ClosePage *everything* must be wrote in workFile, to be
676 compressed later in closePdfStream */
677
678 // Default graphic settings (coordinate system, default color and line style)
679 fprintf( m_workFile,
680 "%g 0 0 %g 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG %g w\n",
681 0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY,
683}
684
685
686void WriteImageStream( const wxImage& aImage, wxDataOutputStream& aOut, wxColor bg, bool colorMode )
687{
688 int w = aImage.GetWidth();
689 int h = aImage.GetHeight();
690
691 for( int y = 0; y < h; y++ )
692 {
693 for( int x = 0; x < w; x++ )
694 {
695 unsigned char r = aImage.GetRed( x, y ) & 0xFF;
696 unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
697 unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
698
699 if( aImage.HasMask() )
700 {
701 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
702 && b == aImage.GetMaskBlue() )
703 {
704 r = bg.Red();
705 g = bg.Green();
706 b = bg.Blue();
707 }
708 }
709
710 if( colorMode )
711 {
712 aOut.Write8( r );
713 aOut.Write8( g );
714 aOut.Write8( b );
715 }
716 else
717 {
718 // Greyscale conversion (CIE 1931)
719 unsigned char grey = KiROUND( r * 0.2126 + g * 0.7152 + b * 0.0722 );
720
721 aOut.Write8( grey );
722 }
723 }
724 }
725}
726
727
728void WriteImageSMaskStream( const wxImage& aImage, wxDataOutputStream& aOut )
729{
730 int w = aImage.GetWidth();
731 int h = aImage.GetHeight();
732
733 if( aImage.HasMask() )
734 {
735 for( int y = 0; y < h; y++ )
736 {
737 for( int x = 0; x < w; x++ )
738 {
739 unsigned char a = 255;
740 unsigned char r = aImage.GetRed( x, y );
741 unsigned char g = aImage.GetGreen( x, y );
742 unsigned char b = aImage.GetBlue( x, y );
743
744 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
745 && b == aImage.GetMaskBlue() )
746 {
747 a = 0;
748 }
749
750 aOut.Write8( a );
751 }
752 }
753 }
754 else if( aImage.HasAlpha() )
755 {
756 int size = w * h;
757 aOut.Write8( aImage.GetAlpha(), size );
758 }
759}
760
761
763{
764 wxASSERT( m_workFile );
765
766 // Close the page stream (and compress it)
768
769 // Page size is in 1/72 of inch (default user space units). Works like the bbox in postscript
770 // but there is no need for swapping the sizes, since PDF doesn't require a portrait page.
771 // We use the MediaBox but PDF has lots of other less-used boxes that could be used.
772 const double PTsPERMIL = 0.072;
773 VECTOR2D psPaperSize = VECTOR2D( m_pageInfo.GetSizeMils() ) * PTsPERMIL;
774
775 auto iuToPdfUserSpace =
776 [&]( const VECTOR2I& aCoord ) -> VECTOR2D
777 {
778 VECTOR2D retval = VECTOR2D( aCoord ) * PTsPERMIL / ( m_IUsPerDecimil * 10 );
779 // PDF y=0 is at bottom of page, invert coordinate
780 retval.y = psPaperSize.y - retval.y;
781 return retval;
782 };
783
784 // Handle annotations (at the moment only "link" type objects)
785 std::vector<int> hyperlinkHandles;
786
787 // Allocate all hyperlink objects for the page and calculate their position in user space
788 // coordinates
789 for( const std::pair<BOX2I, wxString>& linkPair : m_hyperlinksInPage )
790 {
791 const BOX2I& box = linkPair.first;
792 const wxString& url = linkPair.second;
793
794 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
795 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
796
797 BOX2D userSpaceBox;
798 userSpaceBox.SetOrigin( bottomLeft );
799 userSpaceBox.SetEnd( topRight );
800
801 hyperlinkHandles.push_back( allocPdfObject() );
802
803 m_hyperlinkHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } );
804 }
805
806 for( const std::pair<BOX2I, std::vector<wxString>>& menuPair : m_hyperlinkMenusInPage )
807 {
808 const BOX2I& box = menuPair.first;
809 const std::vector<wxString>& urls = menuPair.second;
810
811 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
812 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
813
814 BOX2D userSpaceBox;
815 userSpaceBox.SetOrigin( bottomLeft );
816 userSpaceBox.SetEnd( topRight );
817
818 hyperlinkHandles.push_back( allocPdfObject() );
819
820 m_hyperlinkMenuHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, urls } } );
821 }
822
823 int hyperLinkArrayHandle = -1;
824
825 // If we have added any annotation links, create an array containing all the objects
826 if( hyperlinkHandles.size() > 0 )
827 {
828 hyperLinkArrayHandle = startPdfObject();
829 bool isFirst = true;
830
831 fputs( "[", m_outputFile );
832
833 for( int handle : hyperlinkHandles )
834 {
835 if( isFirst )
836 isFirst = false;
837 else
838 fprintf( m_outputFile, " " );
839
840 fprintf( m_outputFile, "%d 0 R", handle );
841 }
842
843 fputs( "]\n", m_outputFile );
845 }
846
847 // Emit the page object and put it in the page list for later
848 int pageHandle = startPdfObject();
849 m_pageHandles.push_back( pageHandle );
850
851 fprintf( m_outputFile,
852 "<<\n"
853 "/Type /Page\n"
854 "/Parent %d 0 R\n"
855 "/Resources <<\n"
856 " /ProcSet [/PDF /Text /ImageC /ImageB]\n"
857 " /Font %d 0 R\n"
858 " /XObject %d 0 R >>\n"
859 "/MediaBox [0 0 %g %g]\n"
860 "/Contents %d 0 R\n",
864 psPaperSize.x,
865 psPaperSize.y,
867
868 if( hyperlinkHandles.size() > 0 )
869 fprintf( m_outputFile, "/Annots %d 0 R", hyperLinkArrayHandle );
870
871 fputs( ">>\n", m_outputFile );
872
874
875 // Mark the page stream as idle
877
878 wxString pageOutlineName = wxEmptyString;
879
880 if( m_pageName.IsEmpty() )
881 {
882 pageOutlineName = wxString::Format( _( "Page %s" ), m_pageNumbers.back() );
883 }
884 else
885 {
886 pageOutlineName = wxString::Format( _( "%s (Page %s)" ), m_pageName, m_pageNumbers.back() );
887 }
888
889
890 int actionHandle = emitGoToAction( pageHandle );
891 OUTLINE_NODE* pageOutlineNode =
892 addOutlineNode( m_outlineRoot.get(), actionHandle, pageOutlineName );
893
894 // let's reorg the symbol bookmarks under a page handle
895 // let's reorg the symbol bookmarks under a page handle
896 for( const auto& [groupName, groupVector] : m_bookmarksInPage )
897 {
898 OUTLINE_NODE* groupOutlineNode = addOutlineNode( pageOutlineNode, actionHandle, groupName );
899
900 for( const std::pair<BOX2I, wxString>& bookmarkPair : groupVector )
901 {
902 const BOX2I& box = bookmarkPair.first;
903 const wxString& ref = bookmarkPair.second;
904
905 VECTOR2I bottomLeft = iuToPdfUserSpace( box.GetPosition() );
906 VECTOR2I topRight = iuToPdfUserSpace( box.GetEnd() );
907
908 actionHandle = emitGoToAction( pageHandle, bottomLeft, topRight );
909
910 addOutlineNode( groupOutlineNode, actionHandle, ref );
911 }
912
913 std::sort( groupOutlineNode->children.begin(), groupOutlineNode->children.end(),
914 []( const OUTLINE_NODE* a, const OUTLINE_NODE* b ) -> bool
915 {
916 return a->title < b->title;
917 } );
918 }
919
920 // Clean up
921 m_hyperlinksInPage.clear();
923 m_bookmarksInPage.clear();
924}
925
926
927bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
928{
929 return StartPlot( aPageNumber, wxEmptyString );
930}
931
932
933bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber, const wxString& aPageName )
934{
935 wxASSERT( m_outputFile );
936
937 // First things first: the customary null object
938 m_xrefTable.clear();
939 m_xrefTable.push_back( 0 );
940 m_hyperlinksInPage.clear();
942 m_hyperlinkHandles.clear();
944 m_bookmarksInPage.clear();
946
947 m_outlineRoot = std::make_unique<OUTLINE_NODE>();
948
949 /* The header (that's easy!). The second line is binary junk required
950 to make the file binary from the beginning (the important thing is
951 that they must have the bit 7 set) */
952 fputs("%PDF-1.5\n%\200\201\202\203\n", m_outputFile);
953
954 /* Allocate an entry for the page tree root, it will go in every page parent entry */
956
957 /* In the same way, the font resource dictionary is used by every page
958 (it *could* be inherited via the Pages tree */
960
962
964
965 /* Now, the PDF is read from the end, (more or less)... so we start
966 with the page stream for page 1. Other more important stuff is written
967 at the end */
968 StartPage( aPageNumber, aPageName );
969 return true;
970}
971
972
973int PDF_PLOTTER::emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft,
974 const VECTOR2I& aTopRight )
975{
976 int actionHandle = allocPdfObject();
977 startPdfObject( actionHandle );
978
979 fprintf( m_outputFile,
980 "<</S /GoTo /D [%d 0 R /FitR %d %d %d %d]\n"
981 ">>\n",
982 aPageHandle, aBottomLeft.x, aBottomLeft.y, aTopRight.x, aTopRight.y );
983
985
986 return actionHandle;
987}
988
989
990int PDF_PLOTTER::emitGoToAction( int aPageHandle )
991{
992 int actionHandle = allocPdfObject();
993 startPdfObject( actionHandle );
994
995 fprintf( m_outputFile,
996 "<</S /GoTo /D [%d 0 R /Fit]\n"
997 ">>\n",
998 aPageHandle );
999
1001
1002 return actionHandle;
1003}
1004
1005
1006void PDF_PLOTTER::emitOutlineNode( OUTLINE_NODE* node, int parentHandle, int nextNode,
1007 int prevNode )
1008{
1009 int nodeHandle = node->entryHandle;
1010 int prevHandle = -1;
1011 int nextHandle = -1;
1012
1013 for( std::vector<OUTLINE_NODE*>::iterator it = node->children.begin();
1014 it != node->children.end(); it++ )
1015 {
1016 if( it >= node->children.end() - 1 )
1017 {
1018 nextHandle = -1;
1019 }
1020 else
1021 {
1022 nextHandle = ( *( it + 1 ) )->entryHandle;
1023 }
1024
1025 emitOutlineNode( *it, nodeHandle, nextHandle, prevHandle );
1026
1027 prevHandle = ( *it )->entryHandle;
1028 }
1029
1030 // -1 for parentHandle is the outline root itself which is handed elsewhere.
1031 if( parentHandle != -1 )
1032 {
1033 startPdfObject( nodeHandle );
1034
1035 fprintf( m_outputFile,
1036 "<<\n"
1037 "/Title %s\n"
1038 "/Parent %d 0 R\n",
1039 encodeStringForPlotter(node->title ).c_str(),
1040 parentHandle);
1041
1042 if( nextNode > 0 )
1043 {
1044 fprintf( m_outputFile, "/Next %d 0 R\n", nextNode );
1045 }
1046
1047 if( prevNode > 0 )
1048 {
1049 fprintf( m_outputFile, "/Prev %d 0 R\n", prevNode );
1050 }
1051
1052 if( node->children.size() > 0 )
1053 {
1054 fprintf( m_outputFile, "/Count %zd\n", -1 * node->children.size() );
1055 fprintf( m_outputFile, "/First %d 0 R\n", node->children.front()->entryHandle );
1056 fprintf( m_outputFile, "/Last %d 0 R\n", node->children.back()->entryHandle );
1057 }
1058
1059 if( node->actionHandle != -1 )
1060 {
1061 fprintf( m_outputFile, "/A %d 0 R\n", node->actionHandle );
1062 }
1063
1064 fputs( ">>\n", m_outputFile );
1066 }
1067}
1068
1069
1071 const wxString& aTitle )
1072{
1073 OUTLINE_NODE *node = aParent->AddChild( aActionHandle, aTitle, allocPdfObject() );
1075
1076 return node;
1077}
1078
1079
1081{
1082 if( m_outlineRoot->children.size() > 0 )
1083 {
1084 // declare the outline object
1085 m_outlineRoot->entryHandle = allocPdfObject();
1086
1087 emitOutlineNode( m_outlineRoot.get(), -1, -1, -1 );
1088
1089 startPdfObject( m_outlineRoot->entryHandle );
1090
1091 fprintf( m_outputFile,
1092 "<< /Type /Outlines\n"
1093 " /Count %d\n"
1094 " /First %d 0 R\n"
1095 " /Last %d 0 R\n"
1096 ">>\n",
1098 m_outlineRoot->children.front()->entryHandle,
1099 m_outlineRoot->children.back()->entryHandle
1100 );
1101
1103
1104 return m_outlineRoot->entryHandle;
1105 }
1106
1107 return -1;
1108}
1109
1110
1112{
1113 // We can end up here if there was nothing to plot
1114 if( !m_outputFile )
1115 return false;
1116
1117 // Close the current page (often the only one)
1118 ClosePage();
1119
1120 /* We need to declare the resources we're using (fonts in particular)
1121 The useful standard one is the Helvetica family. Adding external fonts
1122 is *very* involved! */
1123 struct {
1124 const char *psname;
1125 const char *rsname;
1126 int font_handle;
1127 } fontdefs[4] = {
1128 { "/Helvetica", "/KicadFont", 0 },
1129 { "/Helvetica-Oblique", "/KicadFontI", 0 },
1130 { "/Helvetica-Bold", "/KicadFontB", 0 },
1131 { "/Helvetica-BoldOblique", "/KicadFontBI", 0 }
1132 };
1133
1134 /* Declare the font resources. Since they're builtin fonts, no descriptors (yay!)
1135 We'll need metrics anyway to do any alignment (these are in the shared with
1136 the postscript engine) */
1137 for( int i = 0; i < 4; i++ )
1138 {
1139 fontdefs[i].font_handle = startPdfObject();
1140 fprintf( m_outputFile,
1141 "<< /BaseFont %s\n"
1142 " /Type /Font\n"
1143 " /Subtype /Type1\n"
1144 /* Adobe is so Mac-based that the nearest thing to Latin1 is
1145 the Windows ANSI encoding! */
1146 " /Encoding /WinAnsiEncoding\n"
1147 ">>\n",
1148 fontdefs[i].psname );
1150 }
1151
1152 // Named font dictionary (was allocated, now we emit it)
1154 fputs( "<<\n", m_outputFile );
1155
1156 for( int i = 0; i < 4; i++ )
1157 {
1158 fprintf( m_outputFile, " %s %d 0 R\n",
1159 fontdefs[i].rsname, fontdefs[i].font_handle );
1160 }
1161
1162 fputs( ">>\n", m_outputFile );
1164
1165 // Named image dictionary (was allocated, now we emit it)
1167 fputs( "<<\n", m_outputFile );
1168
1169 for( const auto& [imgHandle, image] : m_imageHandles )
1170 {
1171 fprintf( m_outputFile, " /Im%d %d 0 R\n", imgHandle, imgHandle );
1172 }
1173
1174 fputs( ">>\n", m_outputFile );
1176
1177 // Emit images with optional SMask for transparency
1178 for( const auto& [imgHandle, image] : m_imageHandles )
1179 {
1180 // Init wxFFile so wxFFileOutputStream won't close file in dtor.
1181 wxFFile outputFFile( m_outputFile );
1182
1183 // Image
1184 startPdfObject( imgHandle );
1185 int imgLenHandle = allocPdfObject();
1186 int smaskHandle = ( image.HasAlpha() || image.HasMask() ) ? allocPdfObject() : -1;
1187
1188 fprintf( m_outputFile,
1189 "<<\n"
1190 "/Type /XObject\n"
1191 "/Subtype /Image\n"
1192 "/BitsPerComponent 8\n"
1193 "/ColorSpace %s\n"
1194 "/Width %d\n"
1195 "/Height %d\n"
1196 "/Filter /FlateDecode\n"
1197 "/Length %d 0 R\n", // Length is deferred
1198 m_colorMode ? "/DeviceRGB" : "/DeviceGray", image.GetWidth(), image.GetHeight(),
1199 imgLenHandle );
1200
1201 if( smaskHandle != -1 )
1202 fprintf( m_outputFile, "/SMask %d 0 R\n", smaskHandle );
1203
1204 fputs( ">>\n", m_outputFile );
1205 fputs( "stream\n", m_outputFile );
1206
1207 long imgStreamStart = ftell( m_outputFile );
1208
1209 {
1210 wxFFileOutputStream ffos( outputFFile );
1211 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1212 wxDataOutputStream dos( zos );
1213
1215 m_colorMode );
1216 }
1217
1218 long imgStreamSize = ftell( m_outputFile ) - imgStreamStart;
1219
1220 fputs( "\nendstream\n", m_outputFile );
1222
1223 startPdfObject( imgLenHandle );
1224 fprintf( m_outputFile, "%ld\n", imgStreamSize );
1226
1227 if( smaskHandle != -1 )
1228 {
1229 // SMask
1230 startPdfObject( smaskHandle );
1231 int smaskLenHandle = allocPdfObject();
1232
1233 fprintf( m_outputFile,
1234 "<<\n"
1235 "/Type /XObject\n"
1236 "/Subtype /Image\n"
1237 "/BitsPerComponent 8\n"
1238 "/ColorSpace /DeviceGray\n"
1239 "/Width %d\n"
1240 "/Height %d\n"
1241 "/Length %d 0 R\n"
1242 "/Filter /FlateDecode\n"
1243 ">>\n", // Length is deferred
1244 image.GetWidth(), image.GetHeight(), smaskLenHandle );
1245
1246 fputs( "stream\n", m_outputFile );
1247
1248 long smaskStreamStart = ftell( m_outputFile );
1249
1250 {
1251 wxFFileOutputStream ffos( outputFFile );
1252 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1253 wxDataOutputStream dos( zos );
1254
1256 }
1257
1258 long smaskStreamSize = ftell( m_outputFile ) - smaskStreamStart;
1259
1260 fputs( "\nendstream\n", m_outputFile );
1262
1263 startPdfObject( smaskLenHandle );
1264 fprintf( m_outputFile, "%u\n", (unsigned) smaskStreamSize );
1266 }
1267
1268 outputFFile.Detach(); // Don't close it
1269 }
1270
1271 for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
1272 {
1273 const BOX2D& box = linkPair.first;
1274 const wxString& url = linkPair.second;
1275
1276 startPdfObject( linkHandle );
1277
1278 fprintf( m_outputFile,
1279 "<<\n"
1280 "/Type /Annot\n"
1281 "/Subtype /Link\n"
1282 "/Rect [%g %g %g %g]\n"
1283 "/Border [16 16 0]\n",
1284 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
1285
1286 wxString pageNumber;
1287 bool pageFound = false;
1288
1289 if( EDA_TEXT::IsGotoPageHref( url, &pageNumber ) )
1290 {
1291 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1292 {
1293 if( m_pageNumbers[ii] == pageNumber )
1294 {
1295 fprintf( m_outputFile,
1296 "/Dest [%d 0 R /FitB]\n"
1297 ">>\n",
1298 m_pageHandles[ii] );
1299
1300 pageFound = true;
1301 break;
1302 }
1303 }
1304
1305 if( !pageFound )
1306 {
1307 // destination page is not being plotted, assign the NOP action to the link
1308 fprintf( m_outputFile, "/A << /Type /Action /S /NOP >>\n"
1309 ">>\n" );
1310 }
1311 }
1312 else
1313 {
1314 fprintf( m_outputFile,
1315 "/A << /Type /Action /S /URI /URI %s >>\n"
1316 ">>\n",
1317 encodeStringForPlotter( url ).c_str() );
1318 }
1319
1321 }
1322
1323 for( const auto& [ menuHandle, menuPair ] : m_hyperlinkMenuHandles )
1324 {
1325 const BOX2D& box = menuPair.first;
1326 const std::vector<wxString>& urls = menuPair.second;
1327 wxString js = wxT( "ShM([\n" );
1328
1329 for( const wxString& url : urls )
1330 {
1331 if( url.StartsWith( "!" ) )
1332 {
1333 wxString property = url.AfterFirst( '!' );
1334
1335 if( property.Find( "http:" ) >= 0 )
1336 {
1337 wxString href = property.substr( property.Find( "http:" ) );
1338
1339 if( m_project )
1340 href = ResolveUriByEnvVars( href, m_project );
1341
1342 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1343 EscapeString( href, CTX_JS_STR ),
1344 EscapeString( href, CTX_JS_STR ) );
1345 }
1346 else if( property.Find( "https:" ) >= 0 )
1347 {
1348 wxString href = property.substr( property.Find( "https:" ) );
1349
1350 if( m_project )
1351 href = ResolveUriByEnvVars( href, m_project );
1352
1353 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1354 EscapeString( href, CTX_JS_STR ),
1355 EscapeString( href, CTX_JS_STR ) );
1356 }
1357 else if( property.Find( "file:" ) >= 0 )
1358 {
1359 wxString href = property.substr( property.Find( "file:" ) );
1360
1361 if( m_project )
1362 href = ResolveUriByEnvVars( href, m_project );
1363
1364 href = NormalizeFileUri( href );
1365
1366 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1367 EscapeString( href, CTX_JS_STR ),
1368 EscapeString( href, CTX_JS_STR ) );
1369 }
1370 else
1371 {
1372 js += wxString::Format( wxT( "[\"%s\"],\n" ),
1373 EscapeString( property, CTX_JS_STR ) );
1374 }
1375 }
1376 else if( url.StartsWith( "#" ) )
1377 {
1378 wxString pageNumber = url.AfterFirst( '#' );
1379
1380 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1381 {
1382 if( m_pageNumbers[ii] == pageNumber )
1383 {
1384 wxString menuText = wxString::Format( _( "Show Page %s" ), pageNumber );
1385
1386 js += wxString::Format( wxT( "[\"%s\", \"#%d\"],\n" ),
1387 EscapeString( menuText, CTX_JS_STR ),
1388 static_cast<int>( ii ) );
1389 break;
1390 }
1391 }
1392 }
1393 else if( url.StartsWith( "http:" ) || url.StartsWith( "https:" )
1394 || url.StartsWith( "file:" ) )
1395 {
1396 wxString href = url;
1397
1398 if( m_project )
1399 href = ResolveUriByEnvVars( url, m_project );
1400
1401 if( url.StartsWith( "file:" ) )
1402 href = NormalizeFileUri( href );
1403
1404 wxString menuText = wxString::Format( _( "Open %s" ), href );
1405
1406 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1407 EscapeString( href, CTX_JS_STR ),
1408 EscapeString( href, CTX_JS_STR ) );
1409 }
1410 }
1411
1412 js += wxT( "]);" );
1413
1414 startPdfObject( menuHandle );
1415
1416 fprintf( m_outputFile,
1417 "<<\n"
1418 "/Type /Annot\n"
1419 "/Subtype /Link\n"
1420 "/Rect [%g %g %g %g]\n"
1421 "/Border [16 16 0]\n",
1422 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
1423
1424 fprintf( m_outputFile,
1425 "/A << /Type /Action /S /JavaScript /JS %s >>\n"
1426 ">>\n",
1427 encodeStringForPlotter( js ).c_str() );
1428
1430 }
1431
1432 {
1434
1435 wxString js = R"JS(
1436function ShM(aEntries) {
1437 var aParams = [];
1438 for (var i in aEntries) {
1439 aParams.push({
1440 cName: aEntries[i][0],
1441 cReturn: aEntries[i][1]
1442 })
1443 }
1444
1445 var cChoice = app.popUpMenuEx.apply(app, aParams);
1446 if (cChoice != null && cChoice.substring(0, 1) == '#') this.pageNum = parseInt(cChoice.slice(1));
1447 else if (cChoice != null && cChoice.substring(0, 4) == 'http') app.launchURL(cChoice);
1448 else if (cChoice != null && cChoice.substring(0, 4) == 'file') app.openDoc(cChoice.substring(7));
1449}
1450)JS";
1451
1452 fprintf( m_outputFile,
1453 "<< /JavaScript\n"
1454 " << /Names\n"
1455 " [ (JSInit) << /Type /Action /S /JavaScript /JS %s >> ]\n"
1456 " >>\n"
1457 ">>\n",
1458 encodeStringForPlotter( js ).c_str() );
1459
1461 }
1462
1463 /* The page tree: it's a B-tree but luckily we only have few pages!
1464 So we use just an array... The handle was allocated at the beginning,
1465 now we instantiate the corresponding object */
1467 fputs( "<<\n"
1468 "/Type /Pages\n"
1469 "/Kids [\n", m_outputFile );
1470
1471 for( unsigned i = 0; i < m_pageHandles.size(); i++ )
1472 fprintf( m_outputFile, "%d 0 R\n", m_pageHandles[i] );
1473
1474 fprintf( m_outputFile,
1475 "]\n"
1476 "/Count %ld\n"
1477 ">>\n", (long) m_pageHandles.size() );
1479
1480
1481 // The info dictionary
1482 int infoDictHandle = startPdfObject();
1483 char date_buf[250];
1484 time_t ltime = time( nullptr );
1485 strftime( date_buf, 250, "D:%Y%m%d%H%M%S", localtime( &ltime ) );
1486
1487 if( m_title.IsEmpty() )
1488 {
1489 // Windows uses '\' and other platforms use '/' as separator
1490 m_title = m_filename.AfterLast( '\\' );
1491 m_title = m_title.AfterLast( '/' );
1492 }
1493
1494 fprintf( m_outputFile,
1495 "<<\n"
1496 "/Producer (KiCad PDF)\n"
1497 "/CreationDate (%s)\n"
1498 "/Creator %s\n"
1499 "/Title %s\n"
1500 "/Author %s\n"
1501 "/Subject %s\n",
1502 date_buf,
1506 encodeStringForPlotter( m_subject ).c_str() );
1507
1508 fputs( ">>\n", m_outputFile );
1510
1511 // Let's dump in the outline
1512 int outlineHandle = emitOutline();
1513
1514 // The catalog, at last
1515 int catalogHandle = startPdfObject();
1516
1517 if( outlineHandle > 0 )
1518 {
1519 fprintf( m_outputFile,
1520 "<<\n"
1521 "/Type /Catalog\n"
1522 "/Pages %d 0 R\n"
1523 "/Version /1.5\n"
1524 "/PageMode /UseOutlines\n"
1525 "/Outlines %d 0 R\n"
1526 "/Names %d 0 R\n"
1527 "/PageLayout /SinglePage\n"
1528 ">>\n",
1530 outlineHandle,
1532 }
1533 else
1534 {
1535 fprintf( m_outputFile,
1536 "<<\n"
1537 "/Type /Catalog\n"
1538 "/Pages %d 0 R\n"
1539 "/Version /1.5\n"
1540 "/PageMode /UseNone\n"
1541 "/PageLayout /SinglePage\n"
1542 ">>\n",
1544 }
1545
1547
1548 /* Emit the xref table (format is crucial to the byte, each entry must
1549 be 20 bytes long, and object zero must be done in that way). Also
1550 the offset must be kept along for the trailer */
1551 long xref_start = ftell( m_outputFile );
1552 fprintf( m_outputFile,
1553 "xref\n"
1554 "0 %ld\n"
1555 "0000000000 65535 f \n", (long) m_xrefTable.size() );
1556
1557 for( unsigned i = 1; i < m_xrefTable.size(); i++ )
1558 {
1559 fprintf( m_outputFile, "%010ld 00000 n \n", m_xrefTable[i] );
1560 }
1561
1562 // Done the xref, go for the trailer
1563 fprintf( m_outputFile,
1564 "trailer\n"
1565 "<< /Size %lu /Root %d 0 R /Info %d 0 R >>\n"
1566 "startxref\n"
1567 "%ld\n" // The offset we saved before
1568 "%%%%EOF\n",
1569 (unsigned long) m_xrefTable.size(), catalogHandle, infoDictHandle, xref_start );
1570
1571 fclose( m_outputFile );
1572 m_outputFile = nullptr;
1573
1574 return true;
1575}
1576
1577
1578void PDF_PLOTTER::Text( const VECTOR2I& aPos,
1579 const COLOR4D& aColor,
1580 const wxString& aText,
1581 const EDA_ANGLE& aOrient,
1582 const VECTOR2I& aSize,
1583 enum GR_TEXT_H_ALIGN_T aH_justify,
1584 enum GR_TEXT_V_ALIGN_T aV_justify,
1585 int aWidth,
1586 bool aItalic,
1587 bool aBold,
1588 bool aMultilineAllowed,
1589 KIFONT::FONT* aFont,
1590 const KIFONT::METRICS& aFontMetrics,
1591 void* aData )
1592{
1593 // PDF files do not like 0 sized texts which create broken files.
1594 if( aSize.x == 0 || aSize.y == 0 )
1595 return;
1596
1597 // Render phantom text (which will be searchable) behind the stroke font. This won't
1598 // be pixel-accurate, but it doesn't matter for searching.
1599 int render_mode = 3; // invisible
1600
1601 VECTOR2I pos( aPos );
1602 const char *fontname = aItalic ? ( aBold ? "/KicadFontBI" : "/KicadFontI" )
1603 : ( aBold ? "/KicadFontB" : "/KicadFont" );
1604
1605 // Compute the copious transformation parameters of the Current Transform Matrix
1606 double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
1607 double wideningFactor, heightFactor;
1608
1609 VECTOR2I t_size( std::abs( aSize.x ), std::abs( aSize.y ) );
1610 bool textMirrored = aSize.x < 0;
1611
1612 computeTextParameters( aPos, aText, aOrient, t_size, textMirrored, aH_justify, aV_justify,
1613 aWidth, aItalic, aBold, &wideningFactor, &ctm_a, &ctm_b, &ctm_c, &ctm_d,
1614 &ctm_e, &ctm_f, &heightFactor );
1615
1616 SetColor( aColor );
1617 SetCurrentLineWidth( aWidth, aData );
1618
1619 wxStringTokenizer str_tok( aText, " ", wxTOKEN_RET_DELIMS );
1620
1621 // If aFont is not specilied (== nullptr), use the default kicad stroke font
1622 if( !aFont )
1623 aFont = KIFONT::FONT::GetFont();
1624
1625 VECTOR2I full_box( aFont->StringBoundaryLimits( aText, t_size, aWidth, aBold, aItalic,
1626 aFontMetrics ) );
1627
1628 if( textMirrored )
1629 full_box.x *= -1;
1630
1631 VECTOR2I box_x( full_box.x, 0 );
1632 VECTOR2I box_y( 0, full_box.y );
1633
1634 RotatePoint( box_x, aOrient );
1635 RotatePoint( box_y, aOrient );
1636
1637 if( aH_justify == GR_TEXT_H_ALIGN_CENTER )
1638 pos -= box_x / 2;
1639 else if( aH_justify == GR_TEXT_H_ALIGN_RIGHT )
1640 pos -= box_x;
1641
1642 if( aV_justify == GR_TEXT_V_ALIGN_CENTER )
1643 pos += box_y / 2;
1644 else if( aV_justify == GR_TEXT_V_ALIGN_TOP )
1645 pos += box_y;
1646
1647 while( str_tok.HasMoreTokens() )
1648 {
1649 wxString word = str_tok.GetNextToken();
1650
1651 computeTextParameters( pos, word, aOrient, t_size, textMirrored, GR_TEXT_H_ALIGN_LEFT,
1652 GR_TEXT_V_ALIGN_BOTTOM, aWidth, aItalic, aBold, &wideningFactor,
1653 &ctm_a, &ctm_b, &ctm_c, &ctm_d, &ctm_e, &ctm_f, &heightFactor );
1654
1655 // Extract the changed width and rotate by the orientation to get the offset for the
1656 // next word
1657 VECTOR2I bbox( aFont->StringBoundaryLimits( word, t_size, aWidth,
1658 aBold, aItalic, aFontMetrics ).x, 0 );
1659
1660 if( textMirrored )
1661 bbox.x *= -1;
1662
1663 RotatePoint( bbox, aOrient );
1664 pos += bbox;
1665
1666 // Don't try to output a blank string
1667 if( word.Trim( false ).Trim( true ).empty() )
1668 continue;
1669
1670 /* We use the full CTM instead of the text matrix because the same
1671 coordinate system will be used for the overlining. Also the %f
1672 for the trig part of the matrix to avoid %g going in exponential
1673 format (which is not supported) */
1674 fprintf( m_workFile, "q %f %f %f %f %f %f cm BT %s %g Tf %d Tr %g Tz ",
1675 ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f,
1676 fontname, heightFactor, render_mode, wideningFactor * 100 );
1677
1678 std::string txt_pdf = encodeStringForPlotter( word );
1679 fprintf( m_workFile, "%s Tj ET\n", txt_pdf.c_str() );
1680 // Restore the CTM
1681 fputs( "Q\n", m_workFile );
1682 }
1683
1684 // Plot the stroked text (if requested)
1685 PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, aWidth, aItalic,
1686 aBold, aMultilineAllowed, aFont, aFontMetrics );
1687}
1688
1689
1691 const COLOR4D& aColor,
1692 const wxString& aText,
1693 const TEXT_ATTRIBUTES& aAttributes,
1694 KIFONT::FONT* aFont,
1695 const KIFONT::METRICS& aFontMetrics,
1696 void* aData )
1697{
1698 VECTOR2I size = aAttributes.m_Size;
1699
1700 // PDF files do not like 0 sized texts which create broken files.
1701 if( size.x == 0 || size.y == 0 )
1702 return;
1703
1704 if( aAttributes.m_Mirrored )
1705 size.x = -size.x;
1706
1707 PDF_PLOTTER::Text( aPos, aColor, aText, aAttributes.m_Angle, size, aAttributes.m_Halign,
1708 aAttributes.m_Valign, aAttributes.m_StrokeWidth, aAttributes.m_Italic,
1709 aAttributes.m_Bold, aAttributes.m_Multiline, aFont, aFontMetrics, aData );
1710}
1711
1712
1713void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL )
1714{
1715 m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
1716}
1717
1718
1719void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
1720{
1721 m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
1722}
1723
1724
1725void PDF_PLOTTER::Bookmark( const BOX2I& aLocation, const wxString& aSymbolReference,
1726 const wxString &aGroupName )
1727{
1728
1729 m_bookmarksInPage[aGroupName].push_back( std::make_pair( aLocation, aSymbolReference ) );
1730}
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:1198
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:146
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:435
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.
virtual void StartPage(const wxString &aPageNumber, const wxString &aPageName=wxEmptyString)
Start a new page in the PDF document.
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
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 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.
int startPdfStream(int handle=-1)
Starts a PDF stream (for the page).
virtual void Rect(const VECTOR2I &p1, const VECTOR2I &p2, FILL_T fill, int width=USE_DEFAULT_LINE_WIDTH) override
Rectangles in PDF.
virtual void Arc(const VECTOR2D &aCenter, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aRadius, FILL_T aFill, int aWidth=USE_DEFAULT_LINE_WIDTH) override
The PDF engine can't directly plot arcs so we use polygonization.
std::map< int, std::pair< BOX2D, wxString > > m_hyperlinkHandles
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)
Emits 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 HyperlinkBox(const BOX2I &aBox, const wxString &aDestinationURL) override
Create a clickable hyperlink with a rectangular click area.
virtual void Circle(const VECTOR2I &pos, int diametre, FILL_T fill, int width=USE_DEFAULT_LINE_WIDTH) override
Circle drawing for PDF.
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...
int startPdfObject(int handle=-1)
Open a new PDF object and returns the handle if the parameter is -1.
OUTLINE_NODE * addOutlineNode(OUTLINE_NODE *aParent, int aActionHandle, const wxString &aTitle)
Adds 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:52
std::vector< wxString > m_pageNumbers
List of loaded hyperlinks in current page.
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
std::vector< std::pair< BOX2I, wxString > > m_hyperlinksInPage
std::unique_ptr< OUTLINE_NODE > m_outlineRoot
Root outline node.
std::vector< std::pair< BOX2I, std::vector< wxString > > > m_hyperlinkMenusInPage
Handles for all the hyperlink objects that will be deferred.
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:683
wxString m_subject
Definition: plotter.h:675
PAGE_INFO m_pageInfo
Definition: plotter.h:676
bool m_plotMirror
Definition: plotter.h:656
static const int USE_DEFAULT_LINE_WIDTH
Definition: plotter.h:109
void MoveTo(const VECTOR2I &pos)
Definition: plotter.h:244
void FinishTo(const VECTOR2I &pos)
Definition: plotter.h:254
wxString m_author
Definition: plotter.h:674
double m_iuPerDeviceUnit
Definition: plotter.h:653
VECTOR2I m_plotOffset
Definition: plotter.h:655
VECTOR2I m_penLastpos
Definition: plotter.h:669
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:677
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:668
wxString m_creator
Definition: plotter.h:671
int m_currentPenWidth
Definition: plotter.h:667
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition: plotter.h:645
FILE * m_outputFile
Output file.
Definition: plotter.h:662
static const int DO_NOT_SET_LINE_WIDTH
Definition: plotter.h:108
RENDER_SETTINGS * m_renderSettings
Definition: plotter.h:681
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:692
double m_IUsPerDecimil
Definition: plotter.h:651
wxString m_title
Definition: plotter.h:673
bool m_colorMode
Definition: plotter.h:665
double GetDashMarkLenIU(int aLineWidth) const
Definition: plotter.cpp:138
wxString m_filename
Definition: plotter.h:672
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:63
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:352
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:361
The common library.
#define _(s)
@ DEGREES_T
Definition: eda_angle.h:31
FILL_T
Definition: eda_shape.h:55
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:390
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.
constexpr int delta
GR_TEXT_H_ALIGN_T
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
@ 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:691
VECTOR2< double > VECTOR2D
Definition: vector2d.h:690