kicad/common/plotters/plotter.cpp

796 lines
23 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2017-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* @file plotter.cpp
* @brief KiCad: Base of all the specialized plotters
* the class PLOTTER handle basic functions to plot schematic and boards
* with different plot formats.
*
* There are currently engines for:
* HPGL
* POSTSCRIPT
* GERBER
* DXF
* an SVG 'plot' is also provided along with the 'print' function by wx, but
* is not handled here.
*/
#include <trigo.h>
#include <plotters/plotter.h>
#include <geometry/shape_line_chain.h>
#include <bezier_curves.h>
#include <callback_gal.h>
#include <math/util.h> // for KiROUND
PLOTTER::PLOTTER( )
{
m_plotScale = 1;
m_currentPenWidth = -1; // To-be-set marker
m_penState = 'Z'; // End-of-path idle
m_plotMirror = false; // Plot mirror option flag
m_mirrorIsHorizontal = true;
m_yaxisReversed = false;
m_outputFile = nullptr;
m_colorMode = false; // Starts as a BW plot
m_negativeMode = false;
// Temporary init to avoid not initialized vars, will be set later
m_IUsPerDecimil = 1; // will be set later to the actual value
m_iuPerDeviceUnit = 1; // will be set later to the actual value
m_renderSettings = nullptr;
}
PLOTTER::~PLOTTER()
{
// Emergency cleanup, but closing the file is usually made in EndPlot().
if( m_outputFile )
fclose( m_outputFile );
}
bool PLOTTER::OpenFile( const wxString& aFullFilename )
{
m_filename = aFullFilename;
wxASSERT( !m_outputFile );
// Open the file in text mode (not suitable for all plotters but only for most of them.
m_outputFile = wxFopen( m_filename, wxT( "wt" ) );
if( m_outputFile == nullptr )
return false ;
return true;
}
VECTOR2D PLOTTER::userToDeviceCoordinates( const VECTOR2I& aCoordinate )
{
VECTOR2I pos = aCoordinate - m_plotOffset;
double x = pos.x * m_plotScale;
double y = ( m_paperSize.y - pos.y * m_plotScale );
if( m_plotMirror )
{
if( m_mirrorIsHorizontal )
x = ( m_paperSize.x - pos.x * m_plotScale );
else
y = pos.y * m_plotScale;
}
if( m_yaxisReversed )
y = m_paperSize.y - y;
x *= m_iuPerDeviceUnit;
y *= m_iuPerDeviceUnit;
return VECTOR2D( x, y );
}
VECTOR2D PLOTTER::userToDeviceSize( const VECTOR2I& size )
{
return VECTOR2D( size.x * m_plotScale * m_iuPerDeviceUnit,
size.y * m_plotScale * m_iuPerDeviceUnit );
}
double PLOTTER::userToDeviceSize( double size ) const
{
return size * m_plotScale * m_iuPerDeviceUnit;
}
#define IU_PER_MILS ( m_IUsPerDecimil * 10 )
double PLOTTER::GetDotMarkLenIU( int aLineWidth ) const
{
return userToDeviceSize( m_renderSettings->GetDotLength( aLineWidth ) );
}
double PLOTTER::GetDashMarkLenIU( int aLineWidth ) const
{
return userToDeviceSize( m_renderSettings->GetDashLength( aLineWidth ) );
}
double PLOTTER::GetDashGapLenIU( int aLineWidth ) const
{
return userToDeviceSize( m_renderSettings->GetGapLength( aLineWidth ) );
}
#include <wx/log.h>
void PLOTTER::Arc( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd, FILL_T aFill,
int aWidth )
{
VECTOR2D aCenter = CalcArcCenter( aStart, aMid, aEnd );
EDA_ANGLE startAngle( aStart - aCenter );
EDA_ANGLE endAngle( aEnd - aCenter );
// < 0: left, 0 : on the line, > 0 : right
double det = ( aEnd - aStart ).Cross( aMid - aStart );
int cw = det <= 0;
EDA_ANGLE angle = endAngle - startAngle;
if( cw )
angle.Normalize();
else
angle.NormalizeNegative();
double radius = ( aStart - aCenter ).EuclideanNorm();
Arc( aCenter, startAngle, angle, radius, aFill, aWidth );
}
void PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle,
double aRadius, FILL_T aFill, int aWidth )
{
polyArc( aCenter, aStartAngle, aAngle, aRadius, aFill, aWidth );
}
void PLOTTER::polyArc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aAngle, double aRadius, FILL_T aFill, int aWidth )
{
EDA_ANGLE startAngle = aStartAngle;
EDA_ANGLE endAngle = startAngle + aAngle;
const EDA_ANGLE delta( 5.0, DEGREES_T ); // increment to draw arc
VECTOR2I start, end;
const int sign = 1;
if( aAngle < ANGLE_0 )
std::swap( startAngle, endAngle );
SetCurrentLineWidth( aWidth );
start.x = aCenter.x + KiROUND( aRadius * startAngle.Cos() );
start.y = aCenter.y + sign * KiROUND( aRadius * startAngle.Sin() );
if( aFill != FILL_T::NO_FILL )
{
MoveTo( aCenter );
LineTo( start );
}
else
{
MoveTo( start );
}
for( EDA_ANGLE ii = startAngle + delta; ii < endAngle; ii += delta )
{
end.x = aCenter.x + KiROUND( aRadius * ii.Cos() );
end.y = aCenter.y + sign * KiROUND( aRadius * ii.Sin() );
LineTo( end );
}
end.x = aCenter.x + KiROUND( aRadius * endAngle.Cos() );
end.y = aCenter.y + sign * KiROUND( aRadius * endAngle.Sin() );
if( aFill != FILL_T::NO_FILL )
{
LineTo( end );
FinishTo( aCenter );
}
else
{
FinishTo( end );
}
}
void PLOTTER::BezierCurve( const VECTOR2I& aStart, const VECTOR2I& aControl1,
const VECTOR2I& aControl2, const VECTOR2I& aEnd,
int aTolerance, int aLineThickness )
{
// Generic fallback: Quadratic Bezier curve plotted as a polyline
int minSegLen = aLineThickness; // The segment min length to approximate a bezier curve
std::vector<VECTOR2I> ctrlPoints;
ctrlPoints.reserve( 4 );
ctrlPoints.push_back( aStart );
ctrlPoints.push_back( aControl1 );
ctrlPoints.push_back( aControl2 );
ctrlPoints.push_back( aEnd );
BEZIER_POLY bezier_converter( ctrlPoints );
std::vector<VECTOR2I> approxPoints;
bezier_converter.GetPoly( approxPoints, minSegLen );
SetCurrentLineWidth( aLineThickness );
MoveTo( aStart );
for( unsigned ii = 1; ii < approxPoints.size()-1; ii++ )
LineTo( approxPoints[ii] );
FinishTo( aEnd );
}
void PLOTTER::PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor )
{
VECTOR2I size( aImage.GetWidth() * aScaleFactor, aImage.GetHeight() * aScaleFactor );
VECTOR2I start = aPos;
start.x -= size.x / 2;
start.y -= size.y / 2;
VECTOR2I end = start;
end.x += size.x;
end.y += size.y;
Rect( start, end, FILL_T::NO_FILL );
}
void PLOTTER::markerSquare( const VECTOR2I& position, int radius )
{
double r = KiROUND( radius / 1.4142 );
std::vector<VECTOR2I> corner_list;
VECTOR2I corner;
corner_list.reserve( 4 );
corner.x = position.x + r;
corner.y = position.y + r;
corner_list.push_back( corner );
corner.x = position.x + r;
corner.y = position.y - r;
corner_list.push_back( corner );
corner.x = position.x - r;
corner.y = position.y - r;
corner_list.push_back( corner );
corner.x = position.x - r;
corner.y = position.y + r;
corner_list.push_back( corner );
corner.x = position.x + r;
corner.y = position.y + r;
corner_list.push_back( corner );
PlotPoly( corner_list, FILL_T::NO_FILL, GetCurrentLineWidth() );
}
void PLOTTER::markerCircle( const VECTOR2I& position, int radius )
{
Circle( position, radius * 2, FILL_T::NO_FILL, GetCurrentLineWidth() );
}
void PLOTTER::markerLozenge( const VECTOR2I& position, int radius )
{
std::vector<VECTOR2I> corner_list;
VECTOR2I corner;
corner_list.reserve( 4 );
corner.x = position.x;
corner.y = position.y + radius;
corner_list.push_back( corner );
corner.x = position.x + radius;
corner.y = position.y,
corner_list.push_back( corner );
corner.x = position.x;
corner.y = position.y - radius;
corner_list.push_back( corner );
corner.x = position.x - radius;
corner.y = position.y;
corner_list.push_back( corner );
corner.x = position.x;
corner.y = position.y + radius;
corner_list.push_back( corner );
PlotPoly( corner_list, FILL_T::NO_FILL, GetCurrentLineWidth() );
}
void PLOTTER::markerHBar( const VECTOR2I& pos, int radius )
{
MoveTo( VECTOR2I( pos.x - radius, pos.y ) );
FinishTo( VECTOR2I( pos.x + radius, pos.y ) );
}
void PLOTTER::markerSlash( const VECTOR2I& pos, int radius )
{
MoveTo( VECTOR2I( pos.x - radius, pos.y - radius ) );
FinishTo( VECTOR2I( pos.x + radius, pos.y + radius ) );
}
void PLOTTER::markerBackSlash( const VECTOR2I& pos, int radius )
{
MoveTo( VECTOR2I( pos.x + radius, pos.y - radius ) );
FinishTo( VECTOR2I( pos.x - radius, pos.y + radius ) );
}
void PLOTTER::markerVBar( const VECTOR2I& pos, int radius )
{
MoveTo( VECTOR2I( pos.x, pos.y - radius ) );
FinishTo( VECTOR2I( pos.x, pos.y + radius ) );
}
void PLOTTER::Marker( const VECTOR2I& position, int diametre, unsigned aShapeId )
{
int radius = diametre / 2;
/* Marker are composed by a series of 'parts' superimposed; not every
combination make sense, obviously. Since they are used in order I
tried to keep the uglier/more complex constructions at the end.
Also I avoided the |/ |\ -/ -\ construction because they're *very*
ugly... if needed they could be added anyway... I'd like to see
a board with more than 58 drilling/slotting tools!
If Visual C++ supported the 0b literals they would be optimally
and easily encoded as an integer array. We have to do with octal */
static const unsigned char marker_patterns[MARKER_COUNT] = {
// Bit order: O Square Lozenge - | \ /
// First choice: simple shapes
0003, // X
0100, // O
0014, // +
0040, // Sq
0020, // Lz
// Two simple shapes
0103, // X O
0017, // X +
0043, // X Sq
0023, // X Lz
0114, // O +
0140, // O Sq
0120, // O Lz
0054, // + Sq
0034, // + Lz
0060, // Sq Lz
// Three simple shapes
0117, // X O +
0143, // X O Sq
0123, // X O Lz
0057, // X + Sq
0037, // X + Lz
0063, // X Sq Lz
0154, // O + Sq
0134, // O + Lz
0074, // + Sq Lz
// Four simple shapes
0174, // O Sq Lz +
0163, // X O Sq Lz
0157, // X O Sq +
0137, // X O Lz +
0077, // X Sq Lz +
// This draws *everything *
0177, // X O Sq Lz +
// Here we use the single bars... so the cross is forbidden
0110, // O -
0104, // O |
0101, // O /
0050, // Sq -
0044, // Sq |
0041, // Sq /
0030, // Lz -
0024, // Lz |
0021, // Lz /
0150, // O Sq -
0144, // O Sq |
0141, // O Sq /
0130, // O Lz -
0124, // O Lz |
0121, // O Lz /
0070, // Sq Lz -
0064, // Sq Lz |
0061, // Sq Lz /
0170, // O Sq Lz -
0164, // O Sq Lz |
0161, // O Sq Lz /
// Last resort: the backlash component (easy to confound)
0102, // \ O
0042, // \ Sq
0022, // \ Lz
0142, // \ O Sq
0122, // \ O Lz
0062, // \ Sq Lz
0162 // \ O Sq Lz
};
if( aShapeId >= MARKER_COUNT )
{
// Fallback shape
markerCircle( position, radius );
}
else
{
// Decode the pattern and draw the corresponding parts
unsigned char pat = marker_patterns[aShapeId];
if( pat & 0001 )
markerSlash( position, radius );
if( pat & 0002 )
markerBackSlash( position, radius );
if( pat & 0004 )
markerVBar( position, radius );
if( pat & 0010 )
markerHBar( position, radius );
if( pat & 0020 )
markerLozenge( position, radius );
if( pat & 0040 )
markerSquare( position, radius );
if( pat & 0100 )
markerCircle( position, radius );
}
}
void PLOTTER::segmentAsOval( const VECTOR2I& start, const VECTOR2I& end, int aWidth,
OUTLINE_MODE aTraceMode )
{
VECTOR2I center( ( start.x + end.x ) / 2, ( start.y + end.y ) / 2 );
VECTOR2I size( end.x - start.x, end.y - start.y );
EDA_ANGLE orient( size );
orient = -orient; // this is due to our Y axis orientation
size.x = KiROUND( EuclideanNorm( size ) ) + aWidth;
size.y = aWidth;
FlashPadOval( center, size, orient, aTraceMode, nullptr );
}
void PLOTTER::sketchOval( const VECTOR2I& aPos, const VECTOR2I& aSize, const EDA_ANGLE& aOrient,
int aWidth )
{
SetCurrentLineWidth( aWidth );
EDA_ANGLE orient( aOrient );
VECTOR2I size( aSize );
if( size.x > size.y )
{
std::swap( size.x, size.y );
orient += ANGLE_90;
}
int deltaxy = size.y - size.x; /* distance between centers of the oval */
int radius = size.x / 2;
// Build a vertical oval shape giving the start and end points of arcs and edges,
// and the middle point of arcs
std::vector<VECTOR2I> corners;
corners.reserve( 6 );
// Shape is (x = corner and arc ends, c = arc centre)
// xcx
//
// xcx
int half_height = deltaxy / 2;
corners.emplace_back( -radius, -half_height );
corners.emplace_back( -radius, half_height );
corners.emplace_back( 0, half_height );
corners.emplace_back( radius, half_height );
corners.emplace_back( radius, -half_height );
corners.emplace_back( 0, -half_height );
// Rotate and move to the actual position
for( size_t ii = 0; ii < corners.size(); ii++ )
{
RotatePoint( corners[ii], orient );
corners[ii] += aPos;
}
// Gen shape (2 lines and 2 180 deg arcs):
MoveTo( corners[0] );
FinishTo( corners[1] );
Arc( corners[2], -orient, ANGLE_180, radius, FILL_T::NO_FILL );
MoveTo( corners[3] );
FinishTo( corners[4] );
Arc( corners[5], -orient, -ANGLE_180, radius, FILL_T::NO_FILL );
}
void PLOTTER::ThickSegment( const VECTOR2I& start, const VECTOR2I& end, int width,
OUTLINE_MODE tracemode, void* aData )
{
if( tracemode == FILLED )
{
if( start == end )
{
Circle( start, width, FILL_T::FILLED_SHAPE, 0 );
}
else
{
SetCurrentLineWidth( width );
MoveTo( start );
FinishTo( end );
}
}
else
{
SetCurrentLineWidth( -1 );
segmentAsOval( start, end, width, tracemode );
}
}
void PLOTTER::ThickArc( const VECTOR2D& centre, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aAngle, double aRadius, int aWidth,
OUTLINE_MODE aTraceMode, void* aData )
{
if( aTraceMode == FILLED )
{
Arc( centre, aStartAngle, aAngle, aRadius, FILL_T::NO_FILL, aWidth );
}
else
{
SetCurrentLineWidth( -1 );
Arc( centre, aStartAngle, aAngle, aRadius - ( aWidth - m_currentPenWidth ) / 2,
FILL_T::NO_FILL, -1 );
Arc( centre, aStartAngle, aAngle, aRadius + ( aWidth - m_currentPenWidth ) / 2,
FILL_T::NO_FILL, -1 );
}
}
void PLOTTER::ThickArc( const EDA_SHAPE& aArcShape, OUTLINE_MODE aTraceMode, void* aData )
{
VECTOR2D center = aArcShape.getCenter();
VECTOR2D mid = aArcShape.GetArcMid();
VECTOR2D start = aArcShape.GetStart();
VECTOR2D end = aArcShape.GetEnd();
EDA_ANGLE startAngle( start - center );
EDA_ANGLE endAngle( end - center );
EDA_ANGLE angle = endAngle - startAngle;
// < 0: left, 0 : on the line, > 0 : right
double det = ( end - start ).Cross( mid - start );
if( det <= 0 ) // cw
angle.Normalize();
else
angle.NormalizeNegative();
double radius = ( start - center ).EuclideanNorm();
ThickArc( center, startAngle, angle, radius, aArcShape.GetWidth(), aTraceMode, aData );
}
void PLOTTER::ThickRect( const VECTOR2I& p1, const VECTOR2I& p2, int width,
OUTLINE_MODE tracemode, void* aData )
{
if( tracemode == FILLED )
{
Rect( p1, p2, FILL_T::NO_FILL, width );
}
else
{
SetCurrentLineWidth( -1 );
VECTOR2I offsetp1( p1.x - ( width - m_currentPenWidth ) / 2,
p1.y - (width - m_currentPenWidth) / 2 );
VECTOR2I offsetp2( p2.x + ( width - m_currentPenWidth ) / 2,
p2.y + (width - m_currentPenWidth) / 2 );
Rect( offsetp1, offsetp2, FILL_T::NO_FILL, -1 );
offsetp1.x += ( width - m_currentPenWidth );
offsetp1.y += ( width - m_currentPenWidth );
offsetp2.x -= ( width - m_currentPenWidth );
offsetp2.y -= ( width - m_currentPenWidth );
Rect( offsetp1, offsetp2, FILL_T::NO_FILL, -1 );
}
}
void PLOTTER::ThickCircle( const VECTOR2I& pos, int diametre, int width, OUTLINE_MODE tracemode,
void* aData )
{
if( tracemode == FILLED )
{
Circle( pos, diametre, FILL_T::NO_FILL, width );
}
else
{
SetCurrentLineWidth( -1 );
Circle( pos, diametre - width + m_currentPenWidth, FILL_T::NO_FILL, -1 );
Circle( pos, diametre + width - m_currentPenWidth, FILL_T::NO_FILL, -1 );
}
}
void PLOTTER::FilledCircle( const VECTOR2I& pos, int diametre, OUTLINE_MODE tracemode, void* aData )
{
if( tracemode == FILLED )
{
Circle( pos, diametre, FILL_T::FILLED_SHAPE, 0 );
}
else
{
SetCurrentLineWidth( -1 );
Circle( pos, diametre, FILL_T::NO_FILL, -1 );
}
}
void PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, int aWidth, void* aData )
{
std::vector<VECTOR2I> cornerList;
cornerList.reserve( aCornerList.PointCount() );
for( int ii = 0; ii < aCornerList.PointCount(); ii++ )
cornerList.emplace_back( aCornerList.CPoint( ii ) );
if( aCornerList.IsClosed() && cornerList.front() != cornerList.back() )
cornerList.emplace_back( aCornerList.CPoint( 0 ) );
PlotPoly( cornerList, aFill, aWidth, aData );
}
void PLOTTER::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 )
{
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
SetColor( aColor );
SetCurrentLineWidth( aPenWidth, aData );
if( aPenWidth == 0 && aBold ) // Use default values if aPenWidth == 0
aPenWidth = GetPenSizeForBold( std::min( aSize.x, aSize.y ) );
if( aPenWidth < 0 )
aPenWidth = -aPenWidth;
CALLBACK_GAL callback_gal( empty_opts,
// Stroke callback
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
{
MoveTo( aPt1 );
LineTo( aPt2 );
PenFinish();
},
// Polygon callback
[&]( const SHAPE_LINE_CHAIN& aPoly )
{
PlotPoly( aPoly, FILL_T::FILLED_SHAPE, 0, aData );
} );
TEXT_ATTRIBUTES attributes;
attributes.m_Angle = aOrient;
attributes.m_StrokeWidth = aPenWidth;
attributes.m_Italic = aItalic;
attributes.m_Bold = aBold;
attributes.m_Halign = aH_justify;
attributes.m_Valign = aV_justify;
attributes.m_Size = aSize;
// if Size.x is < 0, the text is mirrored (we have no other param to know a text is mirrored)
if( attributes.m_Size.x < 0 )
{
attributes.m_Size.x = -attributes.m_Size.x;
attributes.m_Mirrored = true;
}
if( !aFont )
aFont = KIFONT::FONT::GetFont();
aFont->Draw( &callback_gal, aText, aPos, attributes, aFontMetrics );
}
void PLOTTER::PlotText( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const TEXT_ATTRIBUTES& aAttributes,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData )
{
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
TEXT_ATTRIBUTES attributes = aAttributes;
int penWidth = attributes.m_StrokeWidth;
SetColor( aColor );
SetCurrentLineWidth( penWidth, aData );
if( penWidth == 0 && attributes.m_Bold ) // Use default values if aPenWidth == 0
penWidth = GetPenSizeForBold( std::min( attributes.m_Size.x, attributes.m_Size.y ) );
if( penWidth < 0 )
penWidth = -penWidth;
attributes.m_StrokeWidth = penWidth;
CALLBACK_GAL callback_gal( empty_opts,
// Stroke callback
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
{
MoveTo( aPt1 );
LineTo( aPt2 );
PenFinish();
},
// Polygon callback
[&]( const SHAPE_LINE_CHAIN& aPoly )
{
PlotPoly( aPoly, FILL_T::FILLED_SHAPE, 0, aData );
} );
if( !aFont )
aFont = KIFONT::FONT::GetFont();
aFont->Draw( &callback_gal, aText, aPos, attributes, aFontMetrics );
}