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