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