1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25 /**
26 * @file plotter.cpp
27 * @brief KiCad: Base of all the specialized plotters
28 * the class PLOTTER handle basic functions to plot schematic and boards
29 * with different plot formats.
30 *
31 * There are currently engines for:
32 * HPGL
33 * POSTSCRIPT
34 * GERBER
35 * DXF
36 * an SVG 'plot' is also provided along with the 'print' function by wx, but
37 * is not handled here.
38 */
39
40 #include <trigo.h>
41 #include <eda_item.h>
42 #include <plotters/plotter.h>
43 #include <geometry/shape_line_chain.h>
44 #include <geometry/geometry_utils.h>
45 #include <bezier_curves.h>
46 #include <math/util.h> // for KiROUND
47
48
PLOTTER()49 PLOTTER::PLOTTER( )
50 {
51 m_plotScale = 1;
52 m_currentPenWidth = -1; // To-be-set marker
53 m_penState = 'Z'; // End-of-path idle
54 m_plotMirror = false; // Plot mirror option flag
55 m_mirrorIsHorizontal = true;
56 m_yaxisReversed = false;
57 m_outputFile = nullptr;
58 m_colorMode = false; // Starts as a BW plot
59 m_negativeMode = false;
60
61 // Temporary init to avoid not initialized vars, will be set later
62 m_IUsPerDecimil = 1; // will be set later to the actual value
63 m_iuPerDeviceUnit = 1; // will be set later to the actual value
64 m_renderSettings = nullptr;
65 }
66
67
~PLOTTER()68 PLOTTER::~PLOTTER()
69 {
70 // Emergency cleanup, but closing the file is usually made in EndPlot().
71 if( m_outputFile )
72 fclose( m_outputFile );
73 }
74
75
OpenFile(const wxString & aFullFilename)76 bool PLOTTER::OpenFile( const wxString& aFullFilename )
77 {
78 m_filename = aFullFilename;
79
80 wxASSERT( !m_outputFile );
81
82 // Open the file in text mode (not suitable for all plotters but only for most of them.
83 m_outputFile = wxFopen( m_filename, wxT( "wt" ) );
84
85 if( m_outputFile == nullptr )
86 return false ;
87
88 return true;
89 }
90
91
userToDeviceCoordinates(const wxPoint & aCoordinate)92 DPOINT PLOTTER::userToDeviceCoordinates( const wxPoint& aCoordinate )
93 {
94 wxPoint pos = aCoordinate - m_plotOffset;
95
96 // Don't allow overflows; they can cause rendering failures in some file viewers
97 // (such as Acrobat)
98 int clampSize = MAX_PAGE_SIZE_MILS * m_IUsPerDecimil * 10 / 2;
99 pos.x = std::max( -clampSize, std::min( pos.x, clampSize ) );
100 pos.y = std::max( -clampSize, std::min( pos.y, clampSize ) );
101
102 double x = pos.x * m_plotScale;
103 double y = ( m_paperSize.y - pos.y * m_plotScale );
104
105 if( m_plotMirror )
106 {
107 if( m_mirrorIsHorizontal )
108 x = ( m_paperSize.x - pos.x * m_plotScale );
109 else
110 y = pos.y * m_plotScale;
111 }
112
113 if( m_yaxisReversed )
114 y = m_paperSize.y - y;
115
116 x *= m_iuPerDeviceUnit;
117 y *= m_iuPerDeviceUnit;
118
119 return DPOINT( x, y );
120 }
121
122
userToDeviceSize(const wxSize & size)123 DPOINT PLOTTER::userToDeviceSize( const wxSize& size )
124 {
125 return DPOINT( size.x * m_plotScale * m_iuPerDeviceUnit,
126 size.y * m_plotScale * m_iuPerDeviceUnit );
127 }
128
129
userToDeviceSize(double size) const130 double PLOTTER::userToDeviceSize( double size ) const
131 {
132 return size * m_plotScale * m_iuPerDeviceUnit;
133 }
134
135
136 #define IU_PER_MILS ( m_IUsPerDecimil * 10 )
137
138
GetDotMarkLenIU() const139 double PLOTTER::GetDotMarkLenIU() const
140 {
141 return userToDeviceSize( dot_mark_len( GetCurrentLineWidth() ) );
142 }
143
144
GetDashMarkLenIU() const145 double PLOTTER::GetDashMarkLenIU() const
146 {
147 return userToDeviceSize( dash_mark_len( GetCurrentLineWidth() ) );
148 }
149
150
GetDashGapLenIU() const151 double PLOTTER::GetDashGapLenIU() const
152 {
153 return userToDeviceSize( dash_gap_len( GetCurrentLineWidth() ) );
154 }
155
156
Arc(const SHAPE_ARC & aArc)157 void PLOTTER::Arc( const SHAPE_ARC& aArc )
158 {
159 Arc( wxPoint( aArc.GetCenter() ), aArc.GetStartAngle(), aArc.GetEndAngle(), aArc.GetRadius(),
160 FILL_T::NO_FILL, aArc.GetWidth() );
161 }
162
163
Arc(const wxPoint & centre,double StAngle,double EndAngle,int radius,FILL_T fill,int width)164 void PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
165 FILL_T fill, int width )
166 {
167 wxPoint start, end;
168 const int delta = 50; // increment (in 0.1 degrees) to draw circles
169
170 if( StAngle > EndAngle )
171 std::swap( StAngle, EndAngle );
172
173 SetCurrentLineWidth( width );
174
175 /* Please NOTE the different sign due to Y-axis flip */
176 start.x = centre.x + KiROUND( cosdecideg( radius, -StAngle ) );
177 start.y = centre.y + KiROUND( sindecideg( radius, -StAngle ) );
178
179 if( fill != FILL_T::NO_FILL )
180 {
181 MoveTo( centre );
182 LineTo( start );
183 }
184 else
185 {
186 MoveTo( start );
187 }
188
189 for( int ii = StAngle + delta; ii < EndAngle; ii += delta )
190 {
191 end.x = centre.x + KiROUND( cosdecideg( radius, -ii ) );
192 end.y = centre.y + KiROUND( sindecideg( radius, -ii ) );
193 LineTo( end );
194 }
195
196 end.x = centre.x + KiROUND( cosdecideg( radius, -EndAngle ) );
197 end.y = centre.y + KiROUND( sindecideg( radius, -EndAngle ) );
198
199 if( fill != FILL_T::NO_FILL )
200 {
201 LineTo( end );
202 FinishTo( centre );
203 }
204 else
205 {
206 FinishTo( end );
207 }
208 }
209
210
BezierCurve(const wxPoint & aStart,const wxPoint & aControl1,const wxPoint & aControl2,const wxPoint & aEnd,int aTolerance,int aLineThickness)211 void PLOTTER::BezierCurve( const wxPoint& aStart, const wxPoint& aControl1,
212 const wxPoint& aControl2, const wxPoint& aEnd,
213 int aTolerance, int aLineThickness )
214 {
215 // Generic fallback: Quadratic Bezier curve plotted as a polyline
216 int minSegLen = aLineThickness; // The segment min length to approximate a bezier curve
217
218 std::vector<wxPoint> ctrlPoints;
219 ctrlPoints.push_back( aStart );
220 ctrlPoints.push_back( aControl1 );
221 ctrlPoints.push_back( aControl2 );
222 ctrlPoints.push_back( aEnd );
223
224 BEZIER_POLY bezier_converter( ctrlPoints );
225
226 std::vector<wxPoint> approxPoints;
227 bezier_converter.GetPoly( approxPoints, minSegLen );
228
229 SetCurrentLineWidth( aLineThickness );
230 MoveTo( aStart );
231
232 for( unsigned ii = 1; ii < approxPoints.size()-1; ii++ )
233 LineTo( approxPoints[ii] );
234
235 FinishTo( aEnd );
236 }
237
238
PlotImage(const wxImage & aImage,const wxPoint & aPos,double aScaleFactor)239 void PLOTTER::PlotImage(const wxImage& aImage, const wxPoint& aPos, double aScaleFactor )
240 {
241 wxSize size( aImage.GetWidth() * aScaleFactor, aImage.GetHeight() * aScaleFactor );
242
243 wxPoint start = aPos;
244 start.x -= size.x / 2;
245 start.y -= size.y / 2;
246
247 wxPoint end = start;
248 end.x += size.x;
249 end.y += size.y;
250
251 Rect( start, end, FILL_T::NO_FILL );
252 }
253
254
markerSquare(const wxPoint & position,int radius)255 void PLOTTER::markerSquare( const wxPoint& position, int radius )
256 {
257 double r = KiROUND( radius / 1.4142 );
258 std::vector< wxPoint > corner_list;
259 wxPoint corner;
260 corner.x = position.x + r;
261 corner.y = position.y + r;
262 corner_list.push_back( corner );
263 corner.x = position.x + r;
264 corner.y = position.y - r;
265 corner_list.push_back( corner );
266 corner.x = position.x - r;
267 corner.y = position.y - r;
268 corner_list.push_back( corner );
269 corner.x = position.x - r;
270 corner.y = position.y + r;
271 corner_list.push_back( corner );
272 corner.x = position.x + r;
273 corner.y = position.y + r;
274 corner_list.push_back( corner );
275
276 PlotPoly( corner_list, FILL_T::NO_FILL, GetCurrentLineWidth() );
277 }
278
279
markerCircle(const wxPoint & position,int radius)280 void PLOTTER::markerCircle( const wxPoint& position, int radius )
281 {
282 Circle( position, radius * 2, FILL_T::NO_FILL, GetCurrentLineWidth() );
283 }
284
285
markerLozenge(const wxPoint & position,int radius)286 void PLOTTER::markerLozenge( const wxPoint& position, int radius )
287 {
288 std::vector< wxPoint > corner_list;
289 wxPoint corner;
290 corner.x = position.x;
291 corner.y = position.y + radius;
292 corner_list.push_back( corner );
293 corner.x = position.x + radius;
294 corner.y = position.y,
295 corner_list.push_back( corner );
296 corner.x = position.x;
297 corner.y = position.y - radius;
298 corner_list.push_back( corner );
299 corner.x = position.x - radius;
300 corner.y = position.y;
301 corner_list.push_back( corner );
302 corner.x = position.x;
303 corner.y = position.y + radius;
304 corner_list.push_back( corner );
305
306 PlotPoly( corner_list, FILL_T::NO_FILL, GetCurrentLineWidth() );
307 }
308
309
markerHBar(const wxPoint & pos,int radius)310 void PLOTTER::markerHBar( const wxPoint& pos, int radius )
311 {
312 MoveTo( wxPoint( pos.x - radius, pos.y ) );
313 FinishTo( wxPoint( pos.x + radius, pos.y ) );
314 }
315
316
markerSlash(const wxPoint & pos,int radius)317 void PLOTTER::markerSlash( const wxPoint& pos, int radius )
318 {
319 MoveTo( wxPoint( pos.x - radius, pos.y - radius ) );
320 FinishTo( wxPoint( pos.x + radius, pos.y + radius ) );
321 }
322
323
markerBackSlash(const wxPoint & pos,int radius)324 void PLOTTER::markerBackSlash( const wxPoint& pos, int radius )
325 {
326 MoveTo( wxPoint( pos.x + radius, pos.y - radius ) );
327 FinishTo( wxPoint( pos.x - radius, pos.y + radius ) );
328 }
329
330
markerVBar(const wxPoint & pos,int radius)331 void PLOTTER::markerVBar( const wxPoint& pos, int radius )
332 {
333 MoveTo( wxPoint( pos.x, pos.y - radius ) );
334 FinishTo( wxPoint( pos.x, pos.y + radius ) );
335 }
336
337
Marker(const wxPoint & position,int diametre,unsigned aShapeId)338 void PLOTTER::Marker( const wxPoint& position, int diametre, unsigned aShapeId )
339 {
340 int radius = diametre / 2;
341
342 /* Marker are composed by a series of 'parts' superimposed; not every
343 combination make sense, obviously. Since they are used in order I
344 tried to keep the uglier/more complex constructions at the end.
345 Also I avoided the |/ |\ -/ -\ construction because they're *very*
346 ugly... if needed they could be added anyway... I'd like to see
347 a board with more than 58 drilling/slotting tools!
348 If Visual C++ supported the 0b literals they would be optimally
349 and easily encoded as an integer array. We have to do with octal */
350 static const unsigned char marker_patterns[MARKER_COUNT] = {
351
352 // Bit order: O Square Lozenge - | \ /
353 // First choice: simple shapes
354 0003, // X
355 0100, // O
356 0014, // +
357 0040, // Sq
358 0020, // Lz
359
360 // Two simple shapes
361 0103, // X O
362 0017, // X +
363 0043, // X Sq
364 0023, // X Lz
365 0114, // O +
366 0140, // O Sq
367 0120, // O Lz
368 0054, // + Sq
369 0034, // + Lz
370 0060, // Sq Lz
371
372 // Three simple shapes
373 0117, // X O +
374 0143, // X O Sq
375 0123, // X O Lz
376 0057, // X + Sq
377 0037, // X + Lz
378 0063, // X Sq Lz
379 0154, // O + Sq
380 0134, // O + Lz
381 0074, // + Sq Lz
382
383 // Four simple shapes
384 0174, // O Sq Lz +
385 0163, // X O Sq Lz
386 0157, // X O Sq +
387 0137, // X O Lz +
388 0077, // X Sq Lz +
389
390 // This draws *everything *
391 0177, // X O Sq Lz +
392
393 // Here we use the single bars... so the cross is forbidden
394 0110, // O -
395 0104, // O |
396 0101, // O /
397 0050, // Sq -
398 0044, // Sq |
399 0041, // Sq /
400 0030, // Lz -
401 0024, // Lz |
402 0021, // Lz /
403 0150, // O Sq -
404 0144, // O Sq |
405 0141, // O Sq /
406 0130, // O Lz -
407 0124, // O Lz |
408 0121, // O Lz /
409 0070, // Sq Lz -
410 0064, // Sq Lz |
411 0061, // Sq Lz /
412 0170, // O Sq Lz -
413 0164, // O Sq Lz |
414 0161, // O Sq Lz /
415
416 // Last resort: the backlash component (easy to confound)
417 0102, // \ O
418 0042, // \ Sq
419 0022, // \ Lz
420 0142, // \ O Sq
421 0122, // \ O Lz
422 0062, // \ Sq Lz
423 0162 // \ O Sq Lz
424 };
425
426 if( aShapeId >= MARKER_COUNT )
427 {
428 // Fallback shape
429 markerCircle( position, radius );
430 }
431 else
432 {
433 // Decode the pattern and draw the corresponding parts
434 unsigned char pat = marker_patterns[aShapeId];
435
436 if( pat & 0001 )
437 markerSlash( position, radius );
438
439 if( pat & 0002 )
440 markerBackSlash( position, radius );
441
442 if( pat & 0004 )
443 markerVBar( position, radius );
444
445 if( pat & 0010 )
446 markerHBar( position, radius );
447
448 if( pat & 0020 )
449 markerLozenge( position, radius );
450
451 if( pat & 0040 )
452 markerSquare( position, radius );
453
454 if( pat & 0100 )
455 markerCircle( position, radius );
456 }
457 }
458
459
segmentAsOval(const wxPoint & start,const wxPoint & end,int width,OUTLINE_MODE tracemode)460 void PLOTTER::segmentAsOval( const wxPoint& start, const wxPoint& end, int width,
461 OUTLINE_MODE tracemode )
462 {
463 wxPoint center( (start.x + end.x) / 2, (start.y + end.y) / 2 );
464 wxSize size( end.x - start.x, end.y - start.y );
465 double orient;
466
467 if( size.y == 0 )
468 orient = 0;
469 else if( size.x == 0 )
470 orient = 900;
471 else
472 orient = -ArcTangente( size.y, size.x );
473
474 size.x = KiROUND( EuclideanNorm( size ) ) + width;
475 size.y = width;
476
477 FlashPadOval( center, size, orient, tracemode, nullptr );
478 }
479
480
sketchOval(const wxPoint & pos,const wxSize & aSize,double orient,int width)481 void PLOTTER::sketchOval( const wxPoint& pos, const wxSize& aSize, double orient, int width )
482 {
483 SetCurrentLineWidth( width );
484 width = m_currentPenWidth;
485 int radius, deltaxy, cx, cy;
486 wxSize size( aSize );
487
488 if( size.x > size.y )
489 {
490 std::swap( size.x, size.y );
491 orient = AddAngles( orient, 900 );
492 }
493
494 deltaxy = size.y - size.x; /* distance between centers of the oval */
495 radius = ( size.x - width ) / 2;
496 cx = -radius;
497 cy = -deltaxy / 2;
498 RotatePoint( &cx, &cy, orient );
499 MoveTo( wxPoint( cx + pos.x, cy + pos.y ) );
500 cx = -radius;
501 cy = deltaxy / 2;
502 RotatePoint( &cx, &cy, orient );
503 FinishTo( wxPoint( cx + pos.x, cy + pos.y ) );
504
505 cx = radius;
506 cy = -deltaxy / 2;
507 RotatePoint( &cx, &cy, orient );
508 MoveTo( wxPoint( cx + pos.x, cy + pos.y ) );
509 cx = radius;
510 cy = deltaxy / 2;
511 RotatePoint( &cx, &cy, orient );
512 FinishTo( wxPoint( cx + pos.x, cy + pos.y ) );
513
514 cx = 0;
515 cy = deltaxy / 2;
516 RotatePoint( &cx, &cy, orient );
517 Arc( wxPoint( cx + pos.x, cy + pos.y ), orient + 1800, orient + 3600, radius, FILL_T::NO_FILL );
518 cx = 0;
519 cy = -deltaxy / 2;
520 RotatePoint( &cx, &cy, orient );
521 Arc( wxPoint( cx + pos.x, cy + pos.y ), orient, orient + 1800, radius, FILL_T::NO_FILL );
522 }
523
524
ThickSegment(const wxPoint & start,const wxPoint & end,int width,OUTLINE_MODE tracemode,void * aData)525 void PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, int width,
526 OUTLINE_MODE tracemode, void* aData )
527 {
528 if( tracemode == FILLED )
529 {
530 if( start == end )
531 {
532 Circle( start, width, FILL_T::FILLED_SHAPE, 0 );
533 }
534 else
535 {
536 SetCurrentLineWidth( width );
537 MoveTo( start );
538 FinishTo( end );
539 }
540 }
541 else
542 {
543 SetCurrentLineWidth( -1 );
544 segmentAsOval( start, end, width, tracemode );
545 }
546 }
547
548
ThickArc(const wxPoint & centre,double StAngle,double EndAngle,int radius,int width,OUTLINE_MODE tracemode,void * aData)549 void PLOTTER::ThickArc( const wxPoint& centre, double StAngle, double EndAngle,
550 int radius, int width, OUTLINE_MODE tracemode, void* aData )
551 {
552 if( tracemode == FILLED )
553 {
554 Arc( centre, StAngle, EndAngle, radius, FILL_T::NO_FILL, width );
555 }
556 else
557 {
558 SetCurrentLineWidth( -1 );
559 Arc( centre, StAngle, EndAngle, radius - ( width - m_currentPenWidth ) / 2,
560 FILL_T::NO_FILL, -1 );
561 Arc( centre, StAngle, EndAngle, radius + ( width - m_currentPenWidth ) / 2,
562 FILL_T::NO_FILL, -1 );
563 }
564 }
565
566
ThickRect(const wxPoint & p1,const wxPoint & p2,int width,OUTLINE_MODE tracemode,void * aData)567 void PLOTTER::ThickRect( const wxPoint& p1, const wxPoint& p2, int width,
568 OUTLINE_MODE tracemode, void* aData )
569 {
570 if( tracemode == FILLED )
571 {
572 Rect( p1, p2, FILL_T::NO_FILL, width );
573 }
574 else
575 {
576 SetCurrentLineWidth( -1 );
577 wxPoint offsetp1( p1.x - (width - m_currentPenWidth) / 2,
578 p1.y - (width - m_currentPenWidth) / 2 );
579 wxPoint offsetp2( p2.x + (width - m_currentPenWidth) / 2,
580 p2.y + (width - m_currentPenWidth) / 2 );
581 Rect( offsetp1, offsetp2, FILL_T::NO_FILL, -1 );
582 offsetp1.x += ( width - m_currentPenWidth );
583 offsetp1.y += ( width - m_currentPenWidth );
584 offsetp2.x -= ( width - m_currentPenWidth );
585 offsetp2.y -= ( width - m_currentPenWidth );
586 Rect( offsetp1, offsetp2, FILL_T::NO_FILL, -1 );
587 }
588 }
589
590
ThickCircle(const wxPoint & pos,int diametre,int width,OUTLINE_MODE tracemode,void * aData)591 void PLOTTER::ThickCircle( const wxPoint& pos, int diametre, int width, OUTLINE_MODE tracemode,
592 void* aData )
593 {
594 if( tracemode == FILLED )
595 {
596 Circle( pos, diametre, FILL_T::NO_FILL, width );
597 }
598 else
599 {
600 SetCurrentLineWidth( -1 );
601 Circle( pos, diametre - width + m_currentPenWidth, FILL_T::NO_FILL, -1 );
602 Circle( pos, diametre + width - m_currentPenWidth, FILL_T::NO_FILL, -1 );
603 }
604 }
605
606
FilledCircle(const wxPoint & pos,int diametre,OUTLINE_MODE tracemode,void * aData)607 void PLOTTER::FilledCircle( const wxPoint& pos, int diametre, OUTLINE_MODE tracemode, void* aData )
608 {
609 if( tracemode == FILLED )
610 {
611 Circle( pos, diametre, FILL_T::FILLED_SHAPE, 0 );
612 }
613 else
614 {
615 SetCurrentLineWidth( -1 );
616 Circle( pos, diametre, FILL_T::NO_FILL, -1 );
617 }
618 }
619
620
PlotPoly(const SHAPE_LINE_CHAIN & aCornerList,FILL_T aFill,int aWidth,void * aData)621 void PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, int aWidth, void* aData )
622 {
623 std::vector<wxPoint> cornerList;
624 cornerList.reserve( aCornerList.PointCount() );
625
626 for( int ii = 0; ii < aCornerList.PointCount(); ii++ )
627 cornerList.emplace_back( aCornerList.CPoint( ii ) );
628
629 if( aCornerList.IsClosed() && cornerList.front() != cornerList.back() )
630 cornerList.emplace_back( aCornerList.CPoint( 0 ) );
631
632 PlotPoly( cornerList, aFill, aWidth, aData );
633 }
634