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