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