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
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;
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()
486 || image.GetMaskRed() != aCurrImage.GetMaskRed()
487 || image.GetMaskGreen() != aCurrImage.GetMaskGreen()
488 || image.GetMaskBlue() != aCurrImage.GetMaskBlue() )
489 continue;
490
491 int pixCount = image.GetWidth() * image.GetHeight();
492
493 if( memcmp( image.GetData(), aCurrImage.GetData(), pixCount * 3 ) != 0 )
494 continue;
495
496 if( image.HasAlpha()
497 && memcmp( image.GetAlpha(), aCurrImage.GetAlpha(), pixCount ) != 0 )
498 continue;
499
500 return imgHandle;
501 }
502
503 return -1;
504 };
505
506 int imgHandle = findHandleForImage( aImage );
507
508 if( imgHandle == -1 )
509 {
510 imgHandle = allocPdfObject();
511 m_imageHandles.emplace( imgHandle, aImage );
512 }
513
514 /* PDF has an uhm... simplified coordinate system handling. There is
515 *one* operator to do everything (the PS concat equivalent). At least
516 they kept the matrix stack to save restore environments. Also images
517 are always emitted at the origin with a size of 1x1 user units.
518 What we need to do is:
519 1) save the CTM end establish the new one
520 2) plot the image
521 3) restore the CTM
522 4) profit
523 */
524 fprintf( m_workFile, "q %g 0 0 %g %g %g cm\n", // Step 1
525 userToDeviceSize( drawsize.x ),
526 userToDeviceSize( drawsize.y ),
527 dev_start.x, dev_start.y );
528
529 fprintf( m_workFile, "/Im%d Do\n", imgHandle );
530 fputs( "Q\n", m_workFile );
531}
532
533
535{
536 m_xrefTable.push_back( 0 );
537 return m_xrefTable.size() - 1;
538}
539
540
542{
543 wxASSERT( m_outputFile );
544 wxASSERT( !m_workFile );
545
546 if( handle < 0)
547 handle = allocPdfObject();
548
549 m_xrefTable[handle] = ftell( m_outputFile );
550 fprintf( m_outputFile, "%d 0 obj\n", handle );
551 return handle;
552}
553
554
556{
557 wxASSERT( m_outputFile );
558 wxASSERT( !m_workFile );
559 fputs( "endobj\n", m_outputFile );
560}
561
562
564{
565 wxASSERT( m_outputFile );
566 wxASSERT( !m_workFile );
567 handle = startPdfObject( handle );
568
569 // This is guaranteed to be handle+1 but needs to be allocated since
570 // you could allocate more object during stream preparation
572
573 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
574 {
575 fprintf( m_outputFile,
576 "<< /Length %d 0 R >>\n" // Length is deferred
577 "stream\n", handle + 1 );
578 }
579 else
580 {
581 fprintf( m_outputFile,
582 "<< /Length %d 0 R /Filter /FlateDecode >>\n" // Length is deferred
583 "stream\n", handle + 1 );
584 }
585
586 // Open a temporary file to accumulate the stream
587 m_workFilename = wxFileName::CreateTempFileName( "" );
588 m_workFile = wxFopen( m_workFilename, wxT( "w+b" ) );
589 wxASSERT( m_workFile );
590 return handle;
591}
592
593
595{
596 wxASSERT( m_workFile );
597
598 long stream_len = ftell( m_workFile );
599
600 if( stream_len < 0 )
601 {
602 wxASSERT( false );
603 return;
604 }
605
606 // Rewind the file, read in the page stream and DEFLATE it
607 fseek( m_workFile, 0, SEEK_SET );
608 unsigned char *inbuf = new unsigned char[stream_len];
609
610 int rc = fread( inbuf, 1, stream_len, m_workFile );
611 wxASSERT( rc == stream_len );
612 ignore_unused( rc );
613
614 // We are done with the temporary file, junk it
615 fclose( m_workFile );
616 m_workFile = nullptr;
617 ::wxRemoveFile( m_workFilename );
618
619 unsigned out_count;
620
621 if( ADVANCED_CFG::GetCfg().m_DebugPDFWriter )
622 {
623 out_count = stream_len;
624 fwrite( inbuf, out_count, 1, m_outputFile );
625 }
626 else
627 {
628 // NULL means memos owns the memory, but provide a hint on optimum size needed.
629 wxMemoryOutputStream memos( nullptr, std::max( 2000l, stream_len ) ) ;
630
631 {
632 /* Somewhat standard parameters to compress in DEFLATE. The PDF spec is
633 * misleading, it says it wants a DEFLATE stream but it really want a ZLIB
634 * stream! (a DEFLATE stream would be generated with -15 instead of 15)
635 * rc = deflateInit2( &zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, 15,
636 * 8, Z_DEFAULT_STRATEGY );
637 */
638
639 wxZlibOutputStream zos( memos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
640
641 zos.Write( inbuf, stream_len );
642 } // flush the zip stream using zos destructor
643
644 wxStreamBuffer* sb = memos.GetOutputStreamBuffer();
645
646 out_count = sb->Tell();
647 fwrite( sb->GetBufferStart(), 1, out_count, m_outputFile );
648 }
649
650 delete[] inbuf;
651 fputs( "\nendstream\n", m_outputFile );
653
654 // Writing the deferred length as an indirect object
656 fprintf( m_outputFile, "%u\n", out_count );
658}
659
660
661void PDF_PLOTTER::StartPage( const wxString& aPageNumber, const wxString& aPageName )
662{
663 wxASSERT( m_outputFile );
664 wxASSERT( !m_workFile );
665
666 m_pageNumbers.push_back( aPageNumber );
667 m_pageName = aPageName;
668
669 // Compute the paper size in IUs
673
674 // Open the content stream; the page object will go later
676
677 /* Now, until ClosePage *everything* must be wrote in workFile, to be
678 compressed later in closePdfStream */
679
680 // Default graphic settings (coordinate system, default color and line style)
681 fprintf( m_workFile,
682 "%g 0 0 %g 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG %g w\n",
683 0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY,
685}
686
687
688void WriteImageStream( const wxImage& aImage, wxDataOutputStream& aOut, wxColor bg, bool colorMode )
689{
690 int w = aImage.GetWidth();
691 int h = aImage.GetHeight();
692
693 for( int y = 0; y < h; y++ )
694 {
695 for( int x = 0; x < w; x++ )
696 {
697 unsigned char r = aImage.GetRed( x, y ) & 0xFF;
698 unsigned char g = aImage.GetGreen( x, y ) & 0xFF;
699 unsigned char b = aImage.GetBlue( x, y ) & 0xFF;
700
701 if( aImage.HasMask() )
702 {
703 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
704 && b == aImage.GetMaskBlue() )
705 {
706 r = bg.Red();
707 g = bg.Green();
708 b = bg.Blue();
709 }
710 }
711
712 if( colorMode )
713 {
714 aOut.Write8( r );
715 aOut.Write8( g );
716 aOut.Write8( b );
717 }
718 else
719 {
720 // Greyscale conversion (CIE 1931)
721 unsigned char grey = KiROUND( r * 0.2126 + g * 0.7152 + b * 0.0722 );
722
723 aOut.Write8( grey );
724 }
725 }
726 }
727}
728
729
730void WriteImageSMaskStream( const wxImage& aImage, wxDataOutputStream& aOut )
731{
732 int w = aImage.GetWidth();
733 int h = aImage.GetHeight();
734
735 if( aImage.HasMask() )
736 {
737 for( int y = 0; y < h; y++ )
738 {
739 for( int x = 0; x < w; x++ )
740 {
741 unsigned char a = 255;
742 unsigned char r = aImage.GetRed( x, y );
743 unsigned char g = aImage.GetGreen( x, y );
744 unsigned char b = aImage.GetBlue( x, y );
745
746 if( r == aImage.GetMaskRed() && g == aImage.GetMaskGreen()
747 && b == aImage.GetMaskBlue() )
748 {
749 a = 0;
750 }
751
752 aOut.Write8( a );
753 }
754 }
755 }
756 else if( aImage.HasAlpha() )
757 {
758 int size = w * h;
759 aOut.Write8( aImage.GetAlpha(), size );
760 }
761}
762
763
765{
766 wxASSERT( m_workFile );
767
768 // Close the page stream (and compress it)
770
771 // Page size is in 1/72 of inch (default user space units). Works like the bbox in postscript
772 // but there is no need for swapping the sizes, since PDF doesn't require a portrait page.
773 // We use the MediaBox but PDF has lots of other less-used boxes that could be used.
774 const double PTsPERMIL = 0.072;
775 VECTOR2D psPaperSize = VECTOR2D( m_pageInfo.GetSizeMils() ) * PTsPERMIL;
776
777 auto iuToPdfUserSpace =
778 [&]( const VECTOR2I& aCoord ) -> VECTOR2D
779 {
780 VECTOR2D pos = VECTOR2D( aCoord ) * PTsPERMIL / ( m_IUsPerDecimil * 10 );
781
782 // PDF y=0 is at bottom of page, invert coordinate
783 VECTOR2D retval( pos.x, psPaperSize.y - pos.y );
784
785 // The pdf plot can be mirrored (from left to right). So mirror the
786 // x coordinate if m_plotMirror is set
787 if( m_plotMirror )
788 {
790 retval.x = ( psPaperSize.x - pos.x );
791 else
792 retval.y = pos.y;
793 }
794
795 return retval;
796 };
797
798 // Handle annotations (at the moment only "link" type objects)
799 std::vector<int> hyperlinkHandles;
800
801 // Allocate all hyperlink objects for the page and calculate their position in user space
802 // coordinates
803 for( const std::pair<BOX2I, wxString>& linkPair : m_hyperlinksInPage )
804 {
805 const BOX2I& box = linkPair.first;
806 const wxString& url = linkPair.second;
807
808 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
809 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
810
811 BOX2D userSpaceBox;
812 userSpaceBox.SetOrigin( bottomLeft );
813 userSpaceBox.SetEnd( topRight );
814
815 hyperlinkHandles.push_back( allocPdfObject() );
816
817 m_hyperlinkHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } );
818 }
819
820 for( const std::pair<BOX2I, std::vector<wxString>>& menuPair : m_hyperlinkMenusInPage )
821 {
822 const BOX2I& box = menuPair.first;
823 const std::vector<wxString>& urls = menuPair.second;
824
825 VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
826 VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
827
828 BOX2D userSpaceBox;
829 userSpaceBox.SetOrigin( bottomLeft );
830 userSpaceBox.SetEnd( topRight );
831
832 hyperlinkHandles.push_back( allocPdfObject() );
833
834 m_hyperlinkMenuHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, urls } } );
835 }
836
837 int hyperLinkArrayHandle = -1;
838
839 // If we have added any annotation links, create an array containing all the objects
840 if( hyperlinkHandles.size() > 0 )
841 {
842 hyperLinkArrayHandle = startPdfObject();
843 bool isFirst = true;
844
845 fputs( "[", m_outputFile );
846
847 for( int handle : hyperlinkHandles )
848 {
849 if( isFirst )
850 isFirst = false;
851 else
852 fprintf( m_outputFile, " " );
853
854 fprintf( m_outputFile, "%d 0 R", handle );
855 }
856
857 fputs( "]\n", m_outputFile );
859 }
860
861 // Emit the page object and put it in the page list for later
862 int pageHandle = startPdfObject();
863 m_pageHandles.push_back( pageHandle );
864
865 fprintf( m_outputFile,
866 "<<\n"
867 "/Type /Page\n"
868 "/Parent %d 0 R\n"
869 "/Resources <<\n"
870 " /ProcSet [/PDF /Text /ImageC /ImageB]\n"
871 " /Font %d 0 R\n"
872 " /XObject %d 0 R >>\n"
873 "/MediaBox [0 0 %g %g]\n"
874 "/Contents %d 0 R\n",
878 psPaperSize.x,
879 psPaperSize.y,
881
882 if( hyperlinkHandles.size() > 0 )
883 fprintf( m_outputFile, "/Annots %d 0 R", hyperLinkArrayHandle );
884
885 fputs( ">>\n", m_outputFile );
886
888
889 // Mark the page stream as idle
891
892 wxString pageOutlineName = wxEmptyString;
893
894 if( m_pageName.IsEmpty() )
895 {
896 pageOutlineName = wxString::Format( _( "Page %s" ), m_pageNumbers.back() );
897 }
898 else
899 {
900 pageOutlineName = wxString::Format( _( "%s (Page %s)" ), m_pageName, m_pageNumbers.back() );
901 }
902
903 int actionHandle = emitGoToAction( pageHandle );
904 OUTLINE_NODE* pageOutlineNode =
905 addOutlineNode( m_outlineRoot.get(), actionHandle, pageOutlineName );
906
907 // let's reorg the symbol bookmarks under a page handle
908 // let's reorg the symbol bookmarks under a page handle
909 for( const auto& [groupName, groupVector] : m_bookmarksInPage )
910 {
911 OUTLINE_NODE* groupOutlineNode = addOutlineNode( pageOutlineNode, actionHandle, groupName );
912
913 for( const std::pair<BOX2I, wxString>& bookmarkPair : groupVector )
914 {
915 const BOX2I& box = bookmarkPair.first;
916 const wxString& ref = bookmarkPair.second;
917
918 VECTOR2I bottomLeft = iuToPdfUserSpace( box.GetPosition() );
919 VECTOR2I topRight = iuToPdfUserSpace( box.GetEnd() );
920
921 actionHandle = emitGoToAction( pageHandle, bottomLeft, topRight );
922
923 addOutlineNode( groupOutlineNode, actionHandle, ref );
924 }
925
926 std::sort( groupOutlineNode->children.begin(), groupOutlineNode->children.end(),
927 []( const OUTLINE_NODE* a, const OUTLINE_NODE* b ) -> bool
928 {
929 return a->title < b->title;
930 } );
931 }
932
933 // Clean up
934 m_hyperlinksInPage.clear();
936 m_bookmarksInPage.clear();
937}
938
939
940bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
941{
942 return StartPlot( aPageNumber, wxEmptyString );
943}
944
945
946bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber, const wxString& aPageName )
947{
948 wxASSERT( m_outputFile );
949
950 // First things first: the customary null object
951 m_xrefTable.clear();
952 m_xrefTable.push_back( 0 );
953 m_hyperlinksInPage.clear();
955 m_hyperlinkHandles.clear();
957 m_bookmarksInPage.clear();
959
960 m_outlineRoot = std::make_unique<OUTLINE_NODE>();
961
962 /* The header (that's easy!). The second line is binary junk required
963 to make the file binary from the beginning (the important thing is
964 that they must have the bit 7 set) */
965 fputs("%PDF-1.5\n%\200\201\202\203\n", m_outputFile);
966
967 /* Allocate an entry for the page tree root, it will go in every page parent entry */
969
970 /* In the same way, the font resource dictionary is used by every page
971 (it *could* be inherited via the Pages tree */
973
975
977
978 /* Now, the PDF is read from the end, (more or less)... so we start
979 with the page stream for page 1. Other more important stuff is written
980 at the end */
981 StartPage( aPageNumber, aPageName );
982 return true;
983}
984
985
986int PDF_PLOTTER::emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft,
987 const VECTOR2I& aTopRight )
988{
989 int actionHandle = allocPdfObject();
990 startPdfObject( actionHandle );
991
992 fprintf( m_outputFile,
993 "<</S /GoTo /D [%d 0 R /FitR %d %d %d %d]\n"
994 ">>\n",
995 aPageHandle, aBottomLeft.x, aBottomLeft.y, aTopRight.x, aTopRight.y );
996
998
999 return actionHandle;
1000}
1001
1002
1003int PDF_PLOTTER::emitGoToAction( int aPageHandle )
1004{
1005 int actionHandle = allocPdfObject();
1006 startPdfObject( actionHandle );
1007
1008 fprintf( m_outputFile,
1009 "<</S /GoTo /D [%d 0 R /Fit]\n"
1010 ">>\n",
1011 aPageHandle );
1012
1014
1015 return actionHandle;
1016}
1017
1018
1019void PDF_PLOTTER::emitOutlineNode( OUTLINE_NODE* node, int parentHandle, int nextNode,
1020 int prevNode )
1021{
1022 int nodeHandle = node->entryHandle;
1023 int prevHandle = -1;
1024 int nextHandle = -1;
1025
1026 for( std::vector<OUTLINE_NODE*>::iterator it = node->children.begin();
1027 it != node->children.end(); it++ )
1028 {
1029 if( it >= node->children.end() - 1 )
1030 {
1031 nextHandle = -1;
1032 }
1033 else
1034 {
1035 nextHandle = ( *( it + 1 ) )->entryHandle;
1036 }
1037
1038 emitOutlineNode( *it, nodeHandle, nextHandle, prevHandle );
1039
1040 prevHandle = ( *it )->entryHandle;
1041 }
1042
1043 // -1 for parentHandle is the outline root itself which is handed elsewhere.
1044 if( parentHandle != -1 )
1045 {
1046 startPdfObject( nodeHandle );
1047
1048 fprintf( m_outputFile,
1049 "<<\n"
1050 "/Title %s\n"
1051 "/Parent %d 0 R\n",
1052 encodeStringForPlotter(node->title ).c_str(),
1053 parentHandle);
1054
1055 if( nextNode > 0 )
1056 {
1057 fprintf( m_outputFile, "/Next %d 0 R\n", nextNode );
1058 }
1059
1060 if( prevNode > 0 )
1061 {
1062 fprintf( m_outputFile, "/Prev %d 0 R\n", prevNode );
1063 }
1064
1065 if( node->children.size() > 0 )
1066 {
1067 fprintf( m_outputFile, "/Count %zd\n", -1 * node->children.size() );
1068 fprintf( m_outputFile, "/First %d 0 R\n", node->children.front()->entryHandle );
1069 fprintf( m_outputFile, "/Last %d 0 R\n", node->children.back()->entryHandle );
1070 }
1071
1072 if( node->actionHandle != -1 )
1073 {
1074 fprintf( m_outputFile, "/A %d 0 R\n", node->actionHandle );
1075 }
1076
1077 fputs( ">>\n", m_outputFile );
1079 }
1080}
1081
1082
1084 const wxString& aTitle )
1085{
1086 OUTLINE_NODE *node = aParent->AddChild( aActionHandle, aTitle, allocPdfObject() );
1088
1089 return node;
1090}
1091
1092
1094{
1095 if( m_outlineRoot->children.size() > 0 )
1096 {
1097 // declare the outline object
1098 m_outlineRoot->entryHandle = allocPdfObject();
1099
1100 emitOutlineNode( m_outlineRoot.get(), -1, -1, -1 );
1101
1102 startPdfObject( m_outlineRoot->entryHandle );
1103
1104 fprintf( m_outputFile,
1105 "<< /Type /Outlines\n"
1106 " /Count %d\n"
1107 " /First %d 0 R\n"
1108 " /Last %d 0 R\n"
1109 ">>\n",
1111 m_outlineRoot->children.front()->entryHandle,
1112 m_outlineRoot->children.back()->entryHandle
1113 );
1114
1116
1117 return m_outlineRoot->entryHandle;
1118 }
1119
1120 return -1;
1121}
1122
1123
1125{
1126 // We can end up here if there was nothing to plot
1127 if( !m_outputFile )
1128 return false;
1129
1130 // Close the current page (often the only one)
1131 ClosePage();
1132
1133 /* We need to declare the resources we're using (fonts in particular)
1134 The useful standard one is the Helvetica family. Adding external fonts
1135 is *very* involved! */
1136 struct {
1137 const char *psname;
1138 const char *rsname;
1139 int font_handle;
1140 } fontdefs[4] = {
1141 { "/Helvetica", "/KicadFont", 0 },
1142 { "/Helvetica-Oblique", "/KicadFontI", 0 },
1143 { "/Helvetica-Bold", "/KicadFontB", 0 },
1144 { "/Helvetica-BoldOblique", "/KicadFontBI", 0 }
1145 };
1146
1147 /* Declare the font resources. Since they're builtin fonts, no descriptors (yay!)
1148 We'll need metrics anyway to do any alignment (these are in the shared with
1149 the postscript engine) */
1150 for( int i = 0; i < 4; i++ )
1151 {
1152 fontdefs[i].font_handle = startPdfObject();
1153 fprintf( m_outputFile,
1154 "<< /BaseFont %s\n"
1155 " /Type /Font\n"
1156 " /Subtype /Type1\n"
1157 /* Adobe is so Mac-based that the nearest thing to Latin1 is
1158 the Windows ANSI encoding! */
1159 " /Encoding /WinAnsiEncoding\n"
1160 ">>\n",
1161 fontdefs[i].psname );
1163 }
1164
1165 // Named font dictionary (was allocated, now we emit it)
1167 fputs( "<<\n", m_outputFile );
1168
1169 for( int i = 0; i < 4; i++ )
1170 {
1171 fprintf( m_outputFile, " %s %d 0 R\n",
1172 fontdefs[i].rsname, fontdefs[i].font_handle );
1173 }
1174
1175 fputs( ">>\n", m_outputFile );
1177
1178 // Named image dictionary (was allocated, now we emit it)
1180 fputs( "<<\n", m_outputFile );
1181
1182 for( const auto& [imgHandle, image] : m_imageHandles )
1183 {
1184 fprintf( m_outputFile, " /Im%d %d 0 R\n", imgHandle, imgHandle );
1185 }
1186
1187 fputs( ">>\n", m_outputFile );
1189
1190 // Emit images with optional SMask for transparency
1191 for( const auto& [imgHandle, image] : m_imageHandles )
1192 {
1193 // Init wxFFile so wxFFileOutputStream won't close file in dtor.
1194 wxFFile outputFFile( m_outputFile );
1195
1196 // Image
1197 startPdfObject( imgHandle );
1198 int imgLenHandle = allocPdfObject();
1199 int smaskHandle = ( image.HasAlpha() || image.HasMask() ) ? allocPdfObject() : -1;
1200
1201 fprintf( m_outputFile,
1202 "<<\n"
1203 "/Type /XObject\n"
1204 "/Subtype /Image\n"
1205 "/BitsPerComponent 8\n"
1206 "/ColorSpace %s\n"
1207 "/Width %d\n"
1208 "/Height %d\n"
1209 "/Filter /FlateDecode\n"
1210 "/Length %d 0 R\n", // Length is deferred
1211 m_colorMode ? "/DeviceRGB" : "/DeviceGray", image.GetWidth(), image.GetHeight(),
1212 imgLenHandle );
1213
1214 if( smaskHandle != -1 )
1215 fprintf( m_outputFile, "/SMask %d 0 R\n", smaskHandle );
1216
1217 fputs( ">>\n", m_outputFile );
1218 fputs( "stream\n", m_outputFile );
1219
1220 long imgStreamStart = ftell( m_outputFile );
1221
1222 {
1223 wxFFileOutputStream ffos( outputFFile );
1224 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1225 wxDataOutputStream dos( zos );
1226
1228 m_colorMode );
1229 }
1230
1231 long imgStreamSize = ftell( m_outputFile ) - imgStreamStart;
1232
1233 fputs( "\nendstream\n", m_outputFile );
1235
1236 startPdfObject( imgLenHandle );
1237 fprintf( m_outputFile, "%ld\n", imgStreamSize );
1239
1240 if( smaskHandle != -1 )
1241 {
1242 // SMask
1243 startPdfObject( smaskHandle );
1244 int smaskLenHandle = allocPdfObject();
1245
1246 fprintf( m_outputFile,
1247 "<<\n"
1248 "/Type /XObject\n"
1249 "/Subtype /Image\n"
1250 "/BitsPerComponent 8\n"
1251 "/ColorSpace /DeviceGray\n"
1252 "/Width %d\n"
1253 "/Height %d\n"
1254 "/Length %d 0 R\n"
1255 "/Filter /FlateDecode\n"
1256 ">>\n", // Length is deferred
1257 image.GetWidth(), image.GetHeight(), smaskLenHandle );
1258
1259 fputs( "stream\n", m_outputFile );
1260
1261 long smaskStreamStart = ftell( m_outputFile );
1262
1263 {
1264 wxFFileOutputStream ffos( outputFFile );
1265 wxZlibOutputStream zos( ffos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB );
1266 wxDataOutputStream dos( zos );
1267
1269 }
1270
1271 long smaskStreamSize = ftell( m_outputFile ) - smaskStreamStart;
1272
1273 fputs( "\nendstream\n", m_outputFile );
1275
1276 startPdfObject( smaskLenHandle );
1277 fprintf( m_outputFile, "%u\n", (unsigned) smaskStreamSize );
1279 }
1280
1281 outputFFile.Detach(); // Don't close it
1282 }
1283
1284 for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
1285 {
1286 const BOX2D& box = linkPair.first;
1287 const wxString& url = linkPair.second;
1288
1289 startPdfObject( linkHandle );
1290
1291 fprintf( m_outputFile,
1292 "<<\n"
1293 "/Type /Annot\n"
1294 "/Subtype /Link\n"
1295 "/Rect [%g %g %g %g]\n"
1296 "/Border [16 16 0]\n",
1297 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
1298
1299 wxString pageNumber;
1300 bool pageFound = false;
1301
1302 if( EDA_TEXT::IsGotoPageHref( url, &pageNumber ) )
1303 {
1304 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1305 {
1306 if( m_pageNumbers[ii] == pageNumber )
1307 {
1308 fprintf( m_outputFile,
1309 "/Dest [%d 0 R /FitB]\n"
1310 ">>\n",
1311 m_pageHandles[ii] );
1312
1313 pageFound = true;
1314 break;
1315 }
1316 }
1317
1318 if( !pageFound )
1319 {
1320 // destination page is not being plotted, assign the NOP action to the link
1321 fprintf( m_outputFile, "/A << /Type /Action /S /NOP >>\n"
1322 ">>\n" );
1323 }
1324 }
1325 else
1326 {
1327 fprintf( m_outputFile,
1328 "/A << /Type /Action /S /URI /URI %s >>\n"
1329 ">>\n",
1330 encodeStringForPlotter( url ).c_str() );
1331 }
1332
1334 }
1335
1336 for( const auto& [ menuHandle, menuPair ] : m_hyperlinkMenuHandles )
1337 {
1338 const BOX2D& box = menuPair.first;
1339 const std::vector<wxString>& urls = menuPair.second;
1340 wxString js = wxT( "ShM([\n" );
1341
1342 for( const wxString& url : urls )
1343 {
1344 if( url.StartsWith( "!" ) )
1345 {
1346 wxString property = url.AfterFirst( '!' );
1347
1348 if( property.Find( "http:" ) >= 0 )
1349 {
1350 wxString href = property.substr( property.Find( "http:" ) );
1351
1352 if( m_project )
1353 href = ResolveUriByEnvVars( href, m_project );
1354
1355 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1356 EscapeString( href, CTX_JS_STR ),
1357 EscapeString( href, CTX_JS_STR ) );
1358 }
1359 else if( property.Find( "https:" ) >= 0 )
1360 {
1361 wxString href = property.substr( property.Find( "https:" ) );
1362
1363 if( m_project )
1364 href = ResolveUriByEnvVars( href, m_project );
1365
1366 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1367 EscapeString( href, CTX_JS_STR ),
1368 EscapeString( href, CTX_JS_STR ) );
1369 }
1370 else if( property.Find( "file:" ) >= 0 )
1371 {
1372 wxString href = property.substr( property.Find( "file:" ) );
1373
1374 if( m_project )
1375 href = ResolveUriByEnvVars( href, m_project );
1376
1377 href = NormalizeFileUri( href );
1378
1379 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1380 EscapeString( href, CTX_JS_STR ),
1381 EscapeString( href, CTX_JS_STR ) );
1382 }
1383 else
1384 {
1385 js += wxString::Format( wxT( "[\"%s\"],\n" ),
1386 EscapeString( property, CTX_JS_STR ) );
1387 }
1388 }
1389 else if( url.StartsWith( "#" ) )
1390 {
1391 wxString pageNumber = url.AfterFirst( '#' );
1392
1393 for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
1394 {
1395 if( m_pageNumbers[ii] == pageNumber )
1396 {
1397 wxString menuText = wxString::Format( _( "Show Page %s" ), pageNumber );
1398
1399 js += wxString::Format( wxT( "[\"%s\", \"#%d\"],\n" ),
1400 EscapeString( menuText, CTX_JS_STR ),
1401 static_cast<int>( ii ) );
1402 break;
1403 }
1404 }
1405 }
1406 else if( url.StartsWith( "http:" ) || url.StartsWith( "https:" )
1407 || url.StartsWith( "file:" ) )
1408 {
1409 wxString href = url;
1410
1411 if( m_project )
1412 href = ResolveUriByEnvVars( url, m_project );
1413
1414 if( url.StartsWith( "file:" ) )
1415 href = NormalizeFileUri( href );
1416
1417 wxString menuText = wxString::Format( _( "Open %s" ), href );
1418
1419 js += wxString::Format( wxT( "[\"%s\", \"%s\"],\n" ),
1420 EscapeString( href, CTX_JS_STR ),
1421 EscapeString( href, CTX_JS_STR ) );
1422 }
1423 }
1424
1425 js += wxT( "]);" );
1426
1427 startPdfObject( menuHandle );
1428
1429 fprintf( m_outputFile,
1430 "<<\n"
1431 "/Type /Annot\n"
1432 "/Subtype /Link\n"
1433 "/Rect [%g %g %g %g]\n"
1434 "/Border [16 16 0]\n",
1435 box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
1436
1437 fprintf( m_outputFile,
1438 "/A << /Type /Action /S /JavaScript /JS %s >>\n"
1439 ">>\n",
1440 encodeStringForPlotter( js ).c_str() );
1441
1443 }
1444
1445 {
1447
1448 wxString js = R"JS(
1449function ShM(aEntries) {
1450 var aParams = [];
1451 for (var i in aEntries) {
1452 aParams.push({
1453 cName: aEntries[i][0],
1454 cReturn: aEntries[i][1]
1455 })
1456 }
1457
1458 var cChoice = app.popUpMenuEx.apply(app, aParams);
1459 if (cChoice != null && cChoice.substring(0, 1) == '#') this.pageNum = parseInt(cChoice.slice(1));
1460 else if (cChoice != null && cChoice.substring(0, 4) == 'http') app.launchURL(cChoice);
1461 else if (cChoice != null && cChoice.substring(0, 4) == 'file') app.openDoc(cChoice.substring(7));
1462}
1463)JS";
1464
1465 fprintf( m_outputFile,
1466 "<< /JavaScript\n"
1467 " << /Names\n"
1468 " [ (JSInit) << /Type /Action /S /JavaScript /JS %s >> ]\n"
1469 " >>\n"
1470 ">>\n",
1471 encodeStringForPlotter( js ).c_str() );
1472
1474 }
1475
1476 /* The page tree: it's a B-tree but luckily we only have few pages!
1477 So we use just an array... The handle was allocated at the beginning,
1478 now we instantiate the corresponding object */
1480 fputs( "<<\n"
1481 "/Type /Pages\n"
1482 "/Kids [\n", m_outputFile );
1483
1484 for( unsigned i = 0; i < m_pageHandles.size(); i++ )
1485 fprintf( m_outputFile, "%d 0 R\n", m_pageHandles[i] );
1486
1487 fprintf( m_outputFile,
1488 "]\n"
1489 "/Count %ld\n"
1490 ">>\n", (long) m_pageHandles.size() );
1492
1493 // The info dictionary
1494 int infoDictHandle = startPdfObject();
1495 char date_buf[250];
1496 time_t ltime = time( nullptr );
1497 strftime( date_buf, 250, "D:%Y%m%d%H%M%S", localtime( &ltime ) );
1498
1499 if( m_title.IsEmpty() )
1500 {
1501 // Windows uses '\' and other platforms use '/' as separator
1502 m_title = m_filename.AfterLast( '\\' );
1503 m_title = m_title.AfterLast( '/' );
1504 }
1505
1506 fprintf( m_outputFile,
1507 "<<\n"
1508 "/Producer (KiCad PDF)\n"
1509 "/CreationDate (%s)\n"
1510 "/Creator %s\n"
1511 "/Title %s\n"
1512 "/Author %s\n"
1513 "/Subject %s\n",
1514 date_buf,
1518 encodeStringForPlotter( m_subject ).c_str() );
1519
1520 fputs( ">>\n", m_outputFile );
1522
1523 // Let's dump in the outline
1524 int outlineHandle = emitOutline();
1525
1526 // The catalog, at last
1527 int catalogHandle = startPdfObject();
1528
1529 if( outlineHandle > 0 )
1530 {
1531 fprintf( m_outputFile,
1532 "<<\n"
1533 "/Type /Catalog\n"
1534 "/Pages %d 0 R\n"
1535 "/Version /1.5\n"
1536 "/PageMode /UseOutlines\n"
1537 "/Outlines %d 0 R\n"
1538 "/Names %d 0 R\n"
1539 "/PageLayout /SinglePage\n"
1540 ">>\n",
1542 outlineHandle,
1544 }
1545 else
1546 {
1547 fprintf( m_outputFile,
1548 "<<\n"
1549 "/Type /Catalog\n"
1550 "/Pages %d 0 R\n"
1551 "/Version /1.5\n"
1552 "/PageMode /UseNone\n"
1553 "/PageLayout /SinglePage\n"
1554 ">>\n",
1556 }
1557
1559
1560 /* Emit the xref table (format is crucial to the byte, each entry must
1561 be 20 bytes long, and object zero must be done in that way). Also
1562 the offset must be kept along for the trailer */
1563 long xref_start = ftell( m_outputFile );
1564 fprintf( m_outputFile,
1565 "xref\n"
1566 "0 %ld\n"
1567 "0000000000 65535 f \n", (long) m_xrefTable.size() );
1568
1569 for( unsigned i = 1; i < m_xrefTable.size(); i++ )
1570 {
1571 fprintf( m_outputFile, "%010ld 00000 n \n", m_xrefTable[i] );
1572 }
1573
1574 // Done the xref, go for the trailer
1575 fprintf( m_outputFile,
1576 "trailer\n"
1577 "<< /Size %lu /Root %d 0 R /Info %d 0 R >>\n"
1578 "startxref\n"
1579 "%ld\n" // The offset we saved before
1580 "%%%%EOF\n",
1581 (unsigned long) m_xrefTable.size(), catalogHandle, infoDictHandle, xref_start );
1582
1583 fclose( m_outputFile );
1584 m_outputFile = nullptr;
1585
1586 return true;
1587}
1588
1589
1590void PDF_PLOTTER::Text( const VECTOR2I& aPos,
1591 const COLOR4D& aColor,
1592 const wxString& aText,
1593 const EDA_ANGLE& aOrient,
1594 const VECTOR2I& aSize,
1595 enum GR_TEXT_H_ALIGN_T aH_justify,
1596 enum GR_TEXT_V_ALIGN_T aV_justify,
1597 int aWidth,
1598 bool aItalic,
1599 bool aBold,
1600 bool aMultilineAllowed,
1601 KIFONT::FONT* aFont,
1602 const KIFONT::METRICS& aFontMetrics,
1603 void* aData )
1604{
1605 // PDF files do not like 0 sized texts which create broken files.
1606 if( aSize.x == 0 || aSize.y == 0 )
1607 return;
1608
1609 // Render phantom text (which will be searchable) behind the stroke font. This won't
1610 // be pixel-accurate, but it doesn't matter for searching.
1611 int render_mode = 3; // invisible
1612
1613 VECTOR2I pos( aPos );
1614 const char *fontname = aItalic ? ( aBold ? "/KicadFontBI" : "/KicadFontI" )
1615 : ( aBold ? "/KicadFontB" : "/KicadFont" );
1616
1617 // Compute the copious transformation parameters of the Current Transform Matrix
1618 double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f;
1619 double wideningFactor, heightFactor;
1620
1621 VECTOR2I t_size( std::abs( aSize.x ), std::abs( aSize.y ) );
1622 bool textMirrored = aSize.x < 0;
1623
1624 computeTextParameters( aPos, aText, aOrient, t_size, textMirrored, aH_justify, aV_justify,
1625 aWidth, aItalic, aBold, &wideningFactor, &ctm_a, &ctm_b, &ctm_c, &ctm_d,
1626 &ctm_e, &ctm_f, &heightFactor );
1627
1628 SetColor( aColor );
1629 SetCurrentLineWidth( aWidth, aData );
1630
1631 wxStringTokenizer str_tok( aText, " ", wxTOKEN_RET_DELIMS );
1632
1633 // If aFont is not specilied (== nullptr), use the default kicad stroke font
1634 if( !aFont )
1635 aFont = KIFONT::FONT::GetFont();
1636
1637 VECTOR2I full_box( aFont->StringBoundaryLimits( aText, t_size, aWidth, aBold, aItalic,
1638 aFontMetrics ) );
1639
1640 if( textMirrored )
1641 full_box.x *= -1;
1642
1643 VECTOR2I box_x( full_box.x, 0 );
1644 VECTOR2I box_y( 0, full_box.y );
1645
1646 RotatePoint( box_x, aOrient );
1647 RotatePoint( box_y, aOrient );
1648
1649 if( aH_justify == GR_TEXT_H_ALIGN_CENTER )
1650 pos -= box_x / 2;
1651 else if( aH_justify == GR_TEXT_H_ALIGN_RIGHT )
1652 pos -= box_x;
1653
1654 if( aV_justify == GR_TEXT_V_ALIGN_CENTER )
1655 pos += box_y / 2;
1656 else if( aV_justify == GR_TEXT_V_ALIGN_TOP )
1657 pos += box_y;
1658
1659 while( str_tok.HasMoreTokens() )
1660 {
1661 wxString word = str_tok.GetNextToken();
1662
1663 computeTextParameters( pos, word, aOrient, t_size, textMirrored, GR_TEXT_H_ALIGN_LEFT,
1664 GR_TEXT_V_ALIGN_BOTTOM, aWidth, aItalic, aBold, &wideningFactor,
1665 &ctm_a, &ctm_b, &ctm_c, &ctm_d, &ctm_e, &ctm_f, &heightFactor );
1666
1667 // Extract the changed width and rotate by the orientation to get the offset for the
1668 // next word
1669 VECTOR2I bbox( aFont->StringBoundaryLimits( word, t_size, aWidth,
1670 aBold, aItalic, aFontMetrics ).x, 0 );
1671
1672 if( textMirrored )
1673 bbox.x *= -1;
1674
1675 RotatePoint( bbox, aOrient );
1676 pos += bbox;
1677
1678 // Don't try to output a blank string
1679 if( word.Trim( false ).Trim( true ).empty() )
1680 continue;
1681
1682 /* We use the full CTM instead of the text matrix because the same
1683 coordinate system will be used for the overlining. Also the %f
1684 for the trig part of the matrix to avoid %g going in exponential
1685 format (which is not supported) */
1686 fprintf( m_workFile, "q %f %f %f %f %f %f cm BT %s %g Tf %d Tr %g Tz ",
1687 ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f,
1688 fontname, heightFactor, render_mode, wideningFactor * 100 );
1689
1690 std::string txt_pdf = encodeStringForPlotter( word );
1691 fprintf( m_workFile, "%s Tj ET\n", txt_pdf.c_str() );
1692 // Restore the CTM
1693 fputs( "Q\n", m_workFile );
1694 }
1695
1696 // Plot the stroked text (if requested)
1697 PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, aWidth, aItalic,
1698 aBold, aMultilineAllowed, aFont, aFontMetrics );
1699}
1700
1701
1703 const COLOR4D& aColor,
1704 const wxString& aText,
1705 const TEXT_ATTRIBUTES& aAttributes,
1706 KIFONT::FONT* aFont,
1707 const KIFONT::METRICS& aFontMetrics,
1708 void* aData )
1709{
1710 VECTOR2I size = aAttributes.m_Size;
1711
1712 // PDF files do not like 0 sized texts which create broken files.
1713 if( size.x == 0 || size.y == 0 )
1714 return;
1715
1716 if( aAttributes.m_Mirrored )
1717 size.x = -size.x;
1718
1719 PDF_PLOTTER::Text( aPos, aColor, aText, aAttributes.m_Angle, size, aAttributes.m_Halign,
1720 aAttributes.m_Valign, aAttributes.m_StrokeWidth, aAttributes.m_Italic,
1721 aAttributes.m_Bold, aAttributes.m_Multiline, aFont, aFontMetrics, aData );
1722}
1723
1724
1725void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL )
1726{
1727 m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
1728}
1729
1730
1731void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
1732{
1733 m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
1734}
1735
1736
1737void PDF_PLOTTER::Bookmark( const BOX2I& aLocation, const wxString& aSymbolReference,
1738 const wxString &aGroupName )
1739{
1740
1741 m_bookmarksInPage[aGroupName].push_back( std::make_pair( aLocation, aSymbolReference ) );
1742}
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:1295
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.
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)
Start 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
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 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)
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:52
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
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:686
wxString m_subject
Definition: plotter.h:678
bool m_mirrorIsHorizontal
Definition: plotter.h:661
PAGE_INFO m_pageInfo
Definition: plotter.h:679
bool m_plotMirror
Definition: plotter.h:659
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:677
double m_iuPerDeviceUnit
Definition: plotter.h:656
VECTOR2I m_plotOffset
Definition: plotter.h:658
VECTOR2I m_penLastpos
Definition: plotter.h:672
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:680
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:671
wxString m_creator
Definition: plotter.h:674
int m_currentPenWidth
Definition: plotter.h:670
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition: plotter.h:648
FILE * m_outputFile
Output file.
Definition: plotter.h:665
static const int DO_NOT_SET_LINE_WIDTH
Definition: plotter.h:108
RENDER_SETTINGS * m_renderSettings
Definition: plotter.h:684
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:691
double m_IUsPerDecimil
Definition: plotter.h:654
wxString m_title
Definition: plotter.h:676
bool m_colorMode
Definition: plotter.h:668
double GetDashMarkLenIU(int aLineWidth) const
Definition: plotter.cpp:138
wxString m_filename
Definition: plotter.h:675
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:364
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: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.
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