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