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