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