diff --git a/common/plotters/class_plotter.cpp b/common/plotters/class_plotter.cpp new file mode 100644 index 0000000000..7f231e2dfd --- /dev/null +++ b/common/plotters/class_plotter.cpp @@ -0,0 +1,541 @@ +/* + * 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 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 class_plotter.cpp + * @brief KiCad: Base of all the plot routines + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +PLOTTER::PLOTTER( ) +{ + plotScale = 1; + defaultPenWidth = 0; + currentPenWidth = -1; // To-be-set marker + penState = 'Z'; // End-of-path idle + m_plotMirror = false; // Plot mirror option flag + m_mirrorIsHorizontal = true; + m_yaxisReversed = false; + outputFile = 0; + colorMode = false; // Starts as a BW plot + negativeMode = false; + // Temporary init to avoid not initialized vars, will be set later + m_IUsPerDecimil = 1; // will be set later to the actual value + iuPerDeviceUnit = 1; // will be set later to the actual value + m_dotMarkLength_mm = 0.1; // Dotted line parameter in mm: segment + // Dashed line parameter is 5 * dotted line mark + // Dashed line gap is 3 * dotted line mark +} + +PLOTTER::~PLOTTER() +{ + // Emergency cleanup, but closing the file is + // usually made in EndPlot(). + if( outputFile ) + fclose( outputFile ); +} + + +bool PLOTTER::OpenFile( const wxString& aFullFilename ) +{ + filename = aFullFilename; + + wxASSERT( !outputFile ); + + // Open the file in text mode (not suitable for all plotters + // but only for most of them + outputFile = wxFopen( filename, wxT( "wt" ) ); + + if( outputFile == NULL ) + return false ; + + return true; +} + + +DPOINT PLOTTER::userToDeviceCoordinates( const wxPoint& aCoordinate ) +{ + wxPoint pos = aCoordinate - plotOffset; + + double x = pos.x * plotScale; + double y = ( paperSize.y - pos.y * plotScale ); + + if( m_plotMirror ) + { + if( m_mirrorIsHorizontal ) + x = ( paperSize.x - pos.x * plotScale ); + else + y = pos.y * plotScale; + } + + if( m_yaxisReversed ) + y = paperSize.y - y; + + x *= iuPerDeviceUnit; + y *= iuPerDeviceUnit; + + return DPOINT( x, y ); +} + + +DPOINT PLOTTER::userToDeviceSize( const wxSize& size ) +{ + return DPOINT( size.x * plotScale * iuPerDeviceUnit, + size.y * plotScale * iuPerDeviceUnit ); +} + + +double PLOTTER::userToDeviceSize( double size ) const +{ + return size * plotScale * iuPerDeviceUnit; +} + + +double PLOTTER::GetDotMarkLenIU() const +{ + return userToDeviceSize( std::max( 1.0, + m_dotMarkLength_mm * 10000 / 25.4 * m_IUsPerDecimil - GetCurrentLineWidth() ) ); +} + + +double PLOTTER::GetDashMarkLenIU() const +{ + return std::max( GetDashGapLenIU(), 5.0 * GetDotMarkLenIU() ); +} + + +double PLOTTER::GetDashGapLenIU() const +{ + return 3.0 * GetDotMarkLenIU() + userToDeviceSize( 2 * GetCurrentLineWidth() ); +} + +void PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius, + FILL_T fill, int width ) +{ + wxPoint start, end; + const int delta = 50; // increment (in 0.1 degrees) to draw circles + + if( StAngle > EndAngle ) + std::swap( StAngle, EndAngle ); + + SetCurrentLineWidth( width ); + /* Please NOTE the different sign due to Y-axis flip */ + start.x = centre.x + KiROUND( cosdecideg( radius, -StAngle ) ); + start.y = centre.y + KiROUND( sindecideg( radius, -StAngle ) ); + MoveTo( start ); + for( int ii = StAngle + delta; ii < EndAngle; ii += delta ) + { + end.x = centre.x + KiROUND( cosdecideg( radius, -ii ) ); + end.y = centre.y + KiROUND( sindecideg( radius, -ii ) ); + LineTo( end ); + } + + end.x = centre.x + KiROUND( cosdecideg( radius, -EndAngle ) ); + end.y = centre.y + KiROUND( sindecideg( radius, -EndAngle ) ); + FinishTo( end ); +} + + +void PLOTTER::PlotImage(const wxImage & aImage, const wxPoint& aPos, double aScaleFactor ) +{ + wxSize size( aImage.GetWidth() * aScaleFactor, + aImage.GetHeight() * aScaleFactor ); + + wxPoint start = aPos; + start.x -= size.x / 2; + start.y -= size.y / 2; + + wxPoint end = start; + end.x += size.x; + end.y += size.y; + + Rect( start, end, NO_FILL ); +} + + +void PLOTTER::markerSquare( const wxPoint& position, int radius ) +{ + double r = KiROUND( radius / 1.4142 ); + std::vector< wxPoint > corner_list; + wxPoint 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 ); + corner.x = position.x + r; + corner.y = position.y + r; + corner_list.push_back( corner ); + + PlotPoly( corner_list, NO_FILL, GetCurrentLineWidth() ); +} + + +void PLOTTER::markerCircle( const wxPoint& position, int radius ) +{ + Circle( position, radius * 2, NO_FILL, GetCurrentLineWidth() ); +} + + +void PLOTTER::markerLozenge( const wxPoint& position, int radius ) +{ + std::vector< wxPoint > corner_list; + wxPoint 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 ); + 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, NO_FILL, GetCurrentLineWidth() ); +} + + +void PLOTTER::markerHBar( const wxPoint& pos, int radius ) +{ + MoveTo( wxPoint( pos.x - radius, pos.y ) ); + FinishTo( wxPoint( pos.x + radius, pos.y ) ); +} + + +void PLOTTER::markerSlash( const wxPoint& pos, int radius ) +{ + MoveTo( wxPoint( pos.x - radius, pos.y - radius ) ); + FinishTo( wxPoint( pos.x + radius, pos.y + radius ) ); +} + + +void PLOTTER::markerBackSlash( const wxPoint& pos, int radius ) +{ + MoveTo( wxPoint( pos.x + radius, pos.y - radius ) ); + FinishTo( wxPoint( pos.x - radius, pos.y + radius ) ); +} + + +void PLOTTER::markerVBar( const wxPoint& pos, int radius ) +{ + MoveTo( wxPoint( pos.x, pos.y - radius ) ); + FinishTo( wxPoint( pos.x, pos.y + radius ) ); +} + + +void PLOTTER::Marker( const wxPoint& 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 wxPoint& start, const wxPoint& end, int width, + EDA_DRAW_MODE_T tracemode ) +{ + wxPoint center( (start.x + end.x) / 2, (start.y + end.y) / 2 ); + wxSize size( end.x - start.x, end.y - start.y ); + double orient; + + if( size.y == 0 ) + orient = 0; + else if( size.x == 0 ) + orient = 900; + else + orient = -ArcTangente( size.y, size.x ); + + size.x = KiROUND( EuclideanNorm( size ) ) + width; + size.y = width; + + FlashPadOval( center, size, orient, tracemode, NULL ); +} + + +void PLOTTER::sketchOval( const wxPoint& pos, const wxSize& aSize, double orient, int width ) +{ + SetCurrentLineWidth( width ); + width = currentPenWidth; + int radius, deltaxy, cx, cy; + wxSize size( aSize ); + + if( size.x > size.y ) + { + std::swap( size.x, size.y ); + orient = AddAngles( orient, 900 ); + } + + deltaxy = size.y - size.x; /* distance between centers of the oval */ + radius = ( size.x - width ) / 2; + cx = -radius; + cy = -deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + MoveTo( wxPoint( cx + pos.x, cy + pos.y ) ); + cx = -radius; + cy = deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + FinishTo( wxPoint( cx + pos.x, cy + pos.y ) ); + + cx = radius; + cy = -deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + MoveTo( wxPoint( cx + pos.x, cy + pos.y ) ); + cx = radius; + cy = deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + FinishTo( wxPoint( cx + pos.x, cy + pos.y ) ); + + cx = 0; + cy = deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + Arc( wxPoint( cx + pos.x, cy + pos.y ), + orient + 1800, orient + 3600, + radius, NO_FILL ); + cx = 0; + cy = -deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + Arc( wxPoint( cx + pos.x, cy + pos.y ), + orient, orient + 1800, + radius, NO_FILL ); +} + + +void PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, int width, + EDA_DRAW_MODE_T tracemode, void* aData ) +{ + if( tracemode == FILLED ) + { + SetCurrentLineWidth( width ); + MoveTo( start ); + FinishTo( end ); + } + else + { + SetCurrentLineWidth( -1 ); + segmentAsOval( start, end, width, tracemode ); + } +} + + +void PLOTTER::ThickArc( const wxPoint& centre, double StAngle, double EndAngle, + int radius, int width, EDA_DRAW_MODE_T tracemode, void* aData ) +{ + if( tracemode == FILLED ) + Arc( centre, StAngle, EndAngle, radius, NO_FILL, width ); + else + { + SetCurrentLineWidth( -1 ); + Arc( centre, StAngle, EndAngle, + radius - ( width - currentPenWidth ) / 2, NO_FILL, -1 ); + Arc( centre, StAngle, EndAngle, + radius + ( width - currentPenWidth ) / 2, NO_FILL, -1 ); + } +} + + +void PLOTTER::ThickRect( const wxPoint& p1, const wxPoint& p2, int width, + EDA_DRAW_MODE_T tracemode, void* aData ) +{ + if( tracemode == FILLED ) + Rect( p1, p2, NO_FILL, width ); + else + { + SetCurrentLineWidth( -1 ); + wxPoint offsetp1( p1.x - (width - currentPenWidth) / 2, + p1.y - (width - currentPenWidth) / 2 ); + wxPoint offsetp2( p2.x + (width - currentPenWidth) / 2, + p2.y + (width - currentPenWidth) / 2 ); + Rect( offsetp1, offsetp2, NO_FILL, -1 ); + offsetp1.x += (width - currentPenWidth); + offsetp1.y += (width - currentPenWidth); + offsetp2.x -= (width - currentPenWidth); + offsetp2.y -= (width - currentPenWidth); + Rect( offsetp1, offsetp2, NO_FILL, -1 ); + } +} + + +void PLOTTER::ThickCircle( const wxPoint& pos, int diametre, int width, + EDA_DRAW_MODE_T tracemode, void* aData ) +{ + if( tracemode == FILLED ) + Circle( pos, diametre, NO_FILL, width ); + else + { + SetCurrentLineWidth( -1 ); + Circle( pos, diametre - width + currentPenWidth, NO_FILL, -1 ); + Circle( pos, diametre + width - currentPenWidth, NO_FILL, -1 ); + } +} + + +void PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aCornerList, FILL_T aFill, + int aWidth, void * aData ) +{ + std::vector< wxPoint > cornerList; + + for( int ii = 0; ii < aCornerList.PointCount(); ii++ ) + cornerList.push_back( wxPoint( aCornerList.CPoint( ii ) ) ); + + PlotPoly( cornerList , aFill, aWidth, aData ); +} + + +void PLOTTER::SetPageSettings( const PAGE_INFO& aPageSettings ) +{ + pageInfo = aPageSettings; +} diff --git a/common/plotters/common_plotDXF_functions.cpp b/common/plotters/common_plotDXF_functions.cpp new file mode 100644 index 0000000000..fe5fc8718b --- /dev/null +++ b/common/plotters/common_plotDXF_functions.cpp @@ -0,0 +1,982 @@ +/** + * @file common_plotDXF_functions.cpp + * @brief KiCad: Common plot DXF Routines. + */ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2017 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 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Oblique angle for DXF native text + * (I don't remember if 15 degrees is the ISO value... it looks nice anyway) + */ +static const double DXF_OBLIQUE_ANGLE = 15; + +/* The layer/colors palette. The acad/DXF palette is divided in 3 zones: + + - The primary colors (1 - 9) + - An HSV zone (10-250, 5 values x 2 saturations x 10 hues + - Greys (251 - 255) + + There is *no* black... the white does it on paper, usually, and + anyway it depends on the plotter configuration, since DXF colors + are meant to be logical only (they represent *both* line color and + width); later version with plot styles only complicate the matter! + + As usual, brown and magenta/purple are difficult to place since + they are actually variations of other colors. + */ +static const struct +{ + const char *name; + int color; +} dxf_layer[NBCOLORS] = +{ + { "BLACK", 7 }, // In DXF, color 7 is *both* white and black! + { "GRAY1", 251 }, + { "GRAY2", 8 }, + { "GRAY3", 9 }, + { "WHITE", 7 }, + { "LYELLOW", 51 }, + { "BLUE1", 178 }, + { "GREEN1", 98 }, + { "CYAN1", 138 }, + { "RED1", 18 }, + { "MAGENTA1", 228 }, + { "BROWN1", 58 }, + { "BLUE2", 5 }, + { "GREEN2", 3 }, + { "CYAN2", 4 }, + { "RED2", 1 }, + { "MAGENTA2", 6 }, + { "BROWN2", 54 }, + { "BLUE3", 171 }, + { "GREEN3", 91 }, + { "CYAN3", 131 }, + { "RED3", 11 }, + { "MAGENTA3", 221 }, + { "YELLOW3", 2 }, + { "BLUE4", 5 }, + { "GREEN4", 3 }, + { "CYAN4", 4 }, + { "RED4", 1 }, + { "MAGENTA4", 6 }, + { "YELLOW4", 2 } +}; + + +static const char* getDXFLineType( PlotDashType aType ) +{ + switch( aType ) + { + case PLOTDASHTYPE_SOLID: return "CONTINUOUS"; + case PLOTDASHTYPE_DASH: return "DASHED"; + case PLOTDASHTYPE_DOT: return "DOTTED"; + case PLOTDASHTYPE_DASHDOT: return "DASHDOT"; + } + + wxFAIL_MSG( "Unhandled PlotDashType" ); + return "CONTINUOUS"; +} + + +// A helper function to create a color name acceptable in DXF files +// DXF files do not use a RGB definition +static wxString getDXFColorName( COLOR4D aColor ) +{ + EDA_COLOR_T color = ColorFindNearest( int( aColor.r*255 ), + int( aColor.g*255 ), + int( aColor.b*255 ) ); + wxString cname( dxf_layer[color].name ); + return cname; +} + +/** + * Set the scale/position for the DXF plot + * The DXF engine doesn't support line widths and mirroring. The output + * coordinate system is in the first quadrant (in mm) + */ +void DXF_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + plotOffset = aOffset; + plotScale = aScale; + + /* DXF paper is 'virtual' so there is no need of a paper size. + Also this way we can handle the aux origin which can be useful + (for example when aligning to a mechanical drawing) */ + paperSize.x = 0; + paperSize.y = 0; + + /* Like paper size DXF units are abstract too. Anyway there is a + * system variable (MEASUREMENT) which will be set to 1 to indicate + * metric units */ + m_IUsPerDecimil = aIusPerDecimil; + iuPerDeviceUnit = 1.0 / aIusPerDecimil; // Gives a DXF in decimils + iuPerDeviceUnit *= 0.00254; // ... now in mm + + SetDefaultLineWidth( 0 ); // No line width on DXF + m_plotMirror = false; // No mirroring on DXF + m_currentColor = COLOR4D::BLACK; +} + +/** + * Opens the DXF plot with a skeleton header + */ +bool DXF_PLOTTER::StartPlot() +{ + wxASSERT( outputFile ); + + // DXF HEADER - Boilerplate + // Defines the minimum for drawing i.e. the angle system and the + // 4 linetypes (CONTINUOUS, DOTDASH, DASHED and DOTTED) + fputs( " 0\n" + "SECTION\n" + " 2\n" + "HEADER\n" + " 9\n" + "$ANGBASE\n" + " 50\n" + "0.0\n" + " 9\n" + "$ANGDIR\n" + " 70\n" + " 1\n" + " 9\n" + "$MEASUREMENT\n" + " 70\n" + "0\n" + " 0\n" // This means 'metric units' + "ENDSEC\n" + " 0\n" + "SECTION\n" + " 2\n" + "TABLES\n" + " 0\n" + "TABLE\n" + " 2\n" + "LTYPE\n" + " 70\n" + "4\n" + " 0\n" + "LTYPE\n" + " 5\n" + "40F\n" + " 2\n" + "CONTINUOUS\n" + " 70\n" + "0\n" + " 3\n" + "Solid line\n" + " 72\n" + "65\n" + " 73\n" + "0\n" + " 40\n" + "0.0\n" + " 0\n" + "LTYPE\n" + " 5\n" + "410\n" + " 2\n" + "DASHDOT\n" + " 70\n" + "0\n" + " 3\n" + "Dash Dot ____ _ ____ _\n" + " 72\n" + "65\n" + " 73\n" + "4\n" + " 40\n" + "2.0\n" + " 49\n" + "1.25\n" + " 49\n" + "-0.25\n" + " 49\n" + "0.25\n" + " 49\n" + "-0.25\n" + " 0\n" + "LTYPE\n" + " 5\n" + "411\n" + " 2\n" + "DASHED\n" + " 70\n" + "0\n" + " 3\n" + "Dashed __ __ __ __ __\n" + " 72\n" + "65\n" + " 73\n" + "2\n" + " 40\n" + "0.75\n" + " 49\n" + "0.5\n" + " 49\n" + "-0.25\n" + " 0\n" + "LTYPE\n" + " 5\n" + "43B\n" + " 2\n" + "DOTTED\n" + " 70\n" + "0\n" + " 3\n" + "Dotted . . . .\n" + " 72\n" + "65\n" + " 73\n" + "2\n" + " 40\n" + "0.2\n" + " 49\n" + "0.0\n" + " 49\n" + "-0.2\n" + " 0\n" + "ENDTAB\n", + outputFile ); + + // Text styles table + // Defines 4 text styles, one for each bold/italic combination + fputs( " 0\n" + "TABLE\n" + " 2\n" + "STYLE\n" + " 70\n" + "4\n", outputFile ); + + static const char *style_name[4] = {"KICAD", "KICADB", "KICADI", "KICADBI"}; + for(int i = 0; i < 4; i++ ) + { + fprintf( outputFile, + " 0\n" + "STYLE\n" + " 2\n" + "%s\n" // Style name + " 70\n" + "0\n" // Standard flags + " 40\n" + "0\n" // Non-fixed height text + " 41\n" + "1\n" // Width factor (base) + " 42\n" + "1\n" // Last height (mandatory) + " 50\n" + "%g\n" // Oblique angle + " 71\n" + "0\n" // Generation flags (default) + " 3\n" + // The standard ISO font (when kicad is build with it + // the dxf text in acad matches *perfectly*) + "isocp.shx\n", // Font name (when not bigfont) + // Apply a 15 degree angle to italic text + style_name[i], i < 2 ? 0 : DXF_OBLIQUE_ANGLE ); + } + + + // Layer table - one layer per color + fprintf( outputFile, + " 0\n" + "ENDTAB\n" + " 0\n" + "TABLE\n" + " 2\n" + "LAYER\n" + " 70\n" + "%d\n", NBCOLORS ); + + /* The layer/colors palette. The acad/DXF palette is divided in 3 zones: + + - The primary colors (1 - 9) + - An HSV zone (10-250, 5 values x 2 saturations x 10 hues + - Greys (251 - 255) + */ + + for( EDA_COLOR_T i = BLACK; i < NBCOLORS; i = NextColor(i) ) + { + fprintf( outputFile, + " 0\n" + "LAYER\n" + " 2\n" + "%s\n" // Layer name + " 70\n" + "0\n" // Standard flags + " 62\n" + "%d\n" // Color number + " 6\n" + "CONTINUOUS\n",// Linetype name + dxf_layer[i].name, dxf_layer[i].color ); + } + + // End of layer table, begin entities + fputs( " 0\n" + "ENDTAB\n" + " 0\n" + "ENDSEC\n" + " 0\n" + "SECTION\n" + " 2\n" + "ENTITIES\n", outputFile ); + + return true; +} + + +bool DXF_PLOTTER::EndPlot() +{ + wxASSERT( outputFile ); + + // DXF FOOTER + fputs( " 0\n" + "ENDSEC\n" + " 0\n" + "EOF\n", outputFile ); + fclose( outputFile ); + outputFile = NULL; + + return true; +} + + +/** + * The DXF exporter handles 'colors' as layers... + */ +void DXF_PLOTTER::SetColor( COLOR4D color ) +{ + if( ( colorMode ) + || ( color == COLOR4D::BLACK ) + || ( color == COLOR4D::WHITE ) ) + { + m_currentColor = color; + } + else + m_currentColor = COLOR4D::BLACK; +} + +/** + * DXF rectangle: fill not supported + */ +void DXF_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + MoveTo( p1 ); + LineTo( wxPoint( p1.x, p2.y ) ); + LineTo( wxPoint( p2.x, p2.y ) ); + LineTo( wxPoint( p2.x, p1.y ) ); + FinishTo( wxPoint( p1.x, p1.y ) ); +} + + +/** + * DXF circle: full functionality; it even does 'fills' drawing a + * circle with a dual-arc polyline wide as the radius. + * + * I could use this trick to do other filled primitives + */ +void DXF_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + double radius = userToDeviceSize( diameter / 2 ); + DPOINT centre_dev = userToDeviceCoordinates( centre ); + if( radius > 0 ) + { + wxString cname = getDXFColorName( m_currentColor ); + + if( !fill ) + { + fprintf( outputFile, "0\nCIRCLE\n8\n%s\n10\n%g\n20\n%g\n40\n%g\n", + TO_UTF8( cname ), + centre_dev.x, centre_dev.y, radius ); + } + + if( fill == FILLED_SHAPE ) + { + double r = radius*0.5; + fprintf( outputFile, "0\nPOLYLINE\n"); + fprintf( outputFile, "8\n%s\n66\n1\n70\n1\n", TO_UTF8( cname )); + fprintf( outputFile, "40\n%g\n41\n%g\n", radius, radius); + fprintf( outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname )); + fprintf( outputFile, "10\n%g\n 20\n%g\n42\n1.0\n", + centre_dev.x-r, centre_dev.y ); + fprintf( outputFile, "0\nVERTEX\n8\n%s\n", TO_UTF8( cname )); + fprintf( outputFile, "10\n%g\n 20\n%g\n42\n1.0\n", + centre_dev.x+r, centre_dev.y ); + fprintf( outputFile, "0\nSEQEND\n"); + } + } +} + + +/** + * DXF polygon: doesn't fill it but at least it close the filled ones + * DXF does not know thick outline. + * It does not know thhick segments, therefore filled polygons with thick outline + * are converted to inflated polygon by aWidth/2 + */ +void DXF_PLOTTER::PlotPoly( const std::vector& aCornerList, + FILL_T aFill, int aWidth, void * aData ) +{ + if( aCornerList.size() <= 1 ) + return; + + unsigned last = aCornerList.size() - 1; + + // Plot outlines with lines (thickness = 0) to define the polygon + if( aWidth <= 0 ) + { + MoveTo( aCornerList[0] ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + LineTo( aCornerList[ii] ); + + // Close polygon if 'fill' requested + if( aFill ) + { + if( aCornerList[last] != aCornerList[0] ) + LineTo( aCornerList[0] ); + } + + PenFinish(); + + return; + } + + + // if the polygon outline has thickness, and is not filled + // (i.e. is a polyline) plot outlines with thick segments + if( aWidth > 0 && !aFill ) + { + MoveTo( aCornerList[0] ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + ThickSegment( aCornerList[ii-1], aCornerList[ii], + aWidth, FILLED, NULL ); + + return; + } + + // The polygon outline has thickness, and is filled + // Build and plot the polygon which contains the initial + // polygon and its thick outline + SHAPE_POLY_SET bufferOutline; + SHAPE_POLY_SET bufferPolybase; + const int circleToSegmentsCount = 16; + + bufferPolybase.NewOutline(); + + // enter outline as polygon: + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + { + TransformRoundedEndsSegmentToPolygon( bufferOutline, + aCornerList[ii-1], aCornerList[ii], circleToSegmentsCount, aWidth ); + } + + // enter the initial polygon: + for( unsigned ii = 0; ii < aCornerList.size(); ii++ ) + { + bufferPolybase.Append( aCornerList[ii] ); + } + + // Merge polygons to build the polygon which contains the initial + // polygon and its thick outline + + // create the outline which contains thick outline: + bufferPolybase.BooleanAdd( bufferOutline, SHAPE_POLY_SET::PM_FAST ); + bufferPolybase.Fracture( SHAPE_POLY_SET::PM_FAST ); + + if( bufferPolybase.OutlineCount() < 1 ) // should not happen + return; + + const SHAPE_LINE_CHAIN& path = bufferPolybase.COutline( 0 ); + + if( path.PointCount() < 2 ) // should not happen + return; + + // Now, output the final polygon to DXF file: + last = path.PointCount() - 1; + VECTOR2I point = path.CPoint( 0 ); + + wxPoint startPoint( point.x, point.y ); + MoveTo( startPoint ); + + for( int ii = 1; ii < path.PointCount(); ii++ ) + { + point = path.CPoint( ii ); + LineTo( wxPoint( point.x, point.y ) ); + } + + // Close polygon, if needed + point = path.CPoint( last ); + wxPoint endPoint( point.x, point.y ); + + if( endPoint != startPoint ) + LineTo( startPoint ); + + PenFinish(); +} + + +void DXF_PLOTTER::PenTo( const wxPoint& pos, char plume ) +{ + wxASSERT( outputFile ); + if( plume == 'Z' ) + { + return; + } + DPOINT pos_dev = userToDeviceCoordinates( pos ); + DPOINT pen_lastpos_dev = userToDeviceCoordinates( penLastpos ); + + if( penLastpos != pos && plume == 'D' ) + { + wxASSERT( m_currentLineType >= 0 && m_currentLineType < 4 ); + // DXF LINE + wxString cname = getDXFColorName( m_currentColor ); + const char *lname = getDXFLineType( (PlotDashType) m_currentLineType ); + fprintf( outputFile, "0\nLINE\n8\n%s\n6\n%s\n10\n%g\n20\n%g\n11\n%g\n21\n%g\n", + TO_UTF8( cname ), lname, + pen_lastpos_dev.x, pen_lastpos_dev.y, pos_dev.x, pos_dev.y ); + } + penLastpos = pos; +} + + +void DXF_PLOTTER::SetDash( int dashed ) +{ + wxASSERT( dashed >= 0 && dashed < 4 ); + m_currentLineType = dashed; +} + + +void DXF_PLOTTER::ThickSegment( const wxPoint& aStart, const wxPoint& aEnd, int aWidth, + EDA_DRAW_MODE_T aPlotMode, void* aData ) +{ + if( aPlotMode == SKETCH ) + { + std::vector cornerList; + SHAPE_POLY_SET outlineBuffer; + TransformOvalClearanceToPolygon( outlineBuffer, + aStart, aEnd, aWidth, 32 , 1.0 ); + const SHAPE_LINE_CHAIN& path = outlineBuffer.COutline(0 ); + + for( int jj = 0; jj < path.PointCount(); jj++ ) + cornerList.push_back( wxPoint( path.CPoint( jj ).x , path.CPoint( jj ).y ) ); + + // Ensure the polygon is closed + if( cornerList[0] != cornerList[cornerList.size() - 1] ) + cornerList.push_back( cornerList[0] ); + + PlotPoly( cornerList, NO_FILL ); + } + else + { + MoveTo( aStart ); + FinishTo( aEnd ); + } +} + +/* Plot an arc in DXF format + * Filling is not supported + */ +void DXF_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius, + FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + + if( radius <= 0 ) + return; + + // In DXF, arcs are drawn CCW. + // In Kicad, arcs are CW or CCW + // If StAngle > EndAngle, it is CW. So transform it to CCW + if( StAngle > EndAngle ) + { + std::swap( StAngle, EndAngle ); + } + + DPOINT centre_dev = userToDeviceCoordinates( centre ); + double radius_dev = userToDeviceSize( radius ); + + // Emit a DXF ARC entity + wxString cname = getDXFColorName( m_currentColor ); + fprintf( outputFile, + "0\nARC\n8\n%s\n10\n%g\n20\n%g\n40\n%g\n50\n%g\n51\n%g\n", + TO_UTF8( cname ), + centre_dev.x, centre_dev.y, radius_dev, + StAngle / 10.0, EndAngle / 10.0 ); +} + +/** + * DXF oval pad: always done in sketch mode + */ +void DXF_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient, + EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxASSERT( outputFile ); + wxSize size( aSize ); + + /* The chip is reduced to an oval tablet with size.y > size.x + * (Oval vertical orientation 0) */ + if( size.x > size.y ) + { + std::swap( size.x, size.y ); + orient = AddAngles( orient, 900 ); + } + + sketchOval( pos, size, orient, -1 ); +} + + +/** + * DXF round pad: always done in sketch mode; it could be filled but it isn't + * pretty if other kinds of pad aren't... + */ +void DXF_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, + EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxASSERT( outputFile ); + Circle( pos, diametre, NO_FILL ); +} + + +/** + * DXF rectangular pad: alwayd done in sketch mode + */ +void DXF_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize, + double orient, EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxASSERT( outputFile ); + wxSize size; + int ox, oy, fx, fy; + + size.x = padsize.x / 2; + size.y = padsize.y / 2; + + if( size.x < 0 ) + size.x = 0; + if( size.y < 0 ) + size.y = 0; + + // If a dimension is zero, the trace is reduced to 1 line + if( size.x == 0 ) + { + ox = pos.x; + oy = pos.y - size.y; + RotatePoint( &ox, &oy, pos.x, pos.y, orient ); + fx = pos.x; + fy = pos.y + size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + MoveTo( wxPoint( ox, oy ) ); + FinishTo( wxPoint( fx, fy ) ); + return; + } + if( size.y == 0 ) + { + ox = pos.x - size.x; + oy = pos.y; + RotatePoint( &ox, &oy, pos.x, pos.y, orient ); + fx = pos.x + size.x; + fy = pos.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + MoveTo( wxPoint( ox, oy ) ); + FinishTo( wxPoint( fx, fy ) ); + return; + } + + ox = pos.x - size.x; + oy = pos.y - size.y; + RotatePoint( &ox, &oy, pos.x, pos.y, orient ); + MoveTo( wxPoint( ox, oy ) ); + + fx = pos.x - size.x; + fy = pos.y + size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + LineTo( wxPoint( fx, fy ) ); + + fx = pos.x + size.x; + fy = pos.y + size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + LineTo( wxPoint( fx, fy ) ); + + fx = pos.x + size.x; + fy = pos.y - size.y; + RotatePoint( &fx, &fy, pos.x, pos.y, orient ); + LineTo( wxPoint( fx, fy ) ); + + FinishTo( wxPoint( ox, oy ) ); +} + +void DXF_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize, + int aCornerRadius, double aOrient, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + SHAPE_POLY_SET outline; + const int segmentToCircleCount = 64; + TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient, + aCornerRadius, segmentToCircleCount ); + + // TransformRoundRectToPolygon creates only one convex polygon + SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); + + MoveTo( wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) ); + + for( int ii = 1; ii < poly.PointCount(); ++ii ) + LineTo( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + FinishTo( wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) ); +} + +void DXF_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize, + SHAPE_POLY_SET* aPolygons, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt ) + { + SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt ); + + MoveTo( wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) ); + + for( int ii = 1; ii < poly.PointCount(); ++ii ) + LineTo( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + FinishTo(wxPoint( poly.Point( 0 ).x, poly.Point( 0 ).y ) ); + } +} + + +/** + * DXF trapezoidal pad: only sketch mode is supported + */ +void DXF_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint *aCorners, + double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode, void* aData ) +{ + wxASSERT( outputFile ); + wxPoint coord[4]; /* coord actual corners of a trapezoidal trace */ + + for( int ii = 0; ii < 4; ii++ ) + { + coord[ii] = aCorners[ii]; + RotatePoint( &coord[ii], aPadOrient ); + coord[ii] += aPadPos; + } + + // Plot edge: + MoveTo( coord[0] ); + LineTo( coord[1] ); + LineTo( coord[2] ); + LineTo( coord[3] ); + FinishTo( coord[0] ); +} + +/** + * Checks if a given string contains non-ASCII characters. + * FIXME: the performance of this code is really poor, but in this case it can be + * acceptable because the plot operation is not called very often. + * @param string String to check + * @return true if it contains some non-ASCII character, false if all characters are + * inside ASCII range (<=255). + */ +bool containsNonAsciiChars( const wxString& string ) +{ + for( unsigned i = 0; i < string.length(); i++ ) + { + wchar_t ch = string[i]; + if( ch > 255 ) + return true; + } + return false; +} + +void DXF_PLOTTER::Text( const wxPoint& aPos, + COLOR4D aColor, + const wxString& aText, + double aOrient, + const wxSize& aSize, + enum EDA_TEXT_HJUSTIFY_T aH_justify, + enum EDA_TEXT_VJUSTIFY_T aV_justify, + int aWidth, + bool aItalic, + bool aBold, + bool aMultilineAllowed, + void* aData ) +{ + // Fix me: see how to use DXF text mode for multiline texts + if( aMultilineAllowed && !aText.Contains( wxT( "\n" ) ) ) + aMultilineAllowed = false; // the text has only one line. + + if( textAsLines || containsNonAsciiChars( aText ) || aMultilineAllowed ) + { + // output text as graphics. + // Perhaps multiline texts could be handled as DXF text entity + // but I do not want spend time about this (JPC) + PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, + aWidth, aItalic, aBold, aMultilineAllowed ); + } + else + { + /* Emit text as a text entity. This loses formatting and shape but it's + more useful as a CAD object */ + DPOINT origin_dev = userToDeviceCoordinates( aPos ); + SetColor( aColor ); + wxString cname = getDXFColorName( m_currentColor ); + DPOINT size_dev = userToDeviceSize( aSize ); + int h_code = 0, v_code = 0; + switch( aH_justify ) + { + case GR_TEXT_HJUSTIFY_LEFT: + h_code = 0; + break; + case GR_TEXT_HJUSTIFY_CENTER: + h_code = 1; + break; + case GR_TEXT_HJUSTIFY_RIGHT: + h_code = 2; + break; + } + switch( aV_justify ) + { + case GR_TEXT_VJUSTIFY_TOP: + v_code = 3; + break; + case GR_TEXT_VJUSTIFY_CENTER: + v_code = 2; + break; + case GR_TEXT_VJUSTIFY_BOTTOM: + v_code = 1; + break; + } + + // Position, size, rotation and alignment + // The two alignment point usages is somewhat idiot (see the DXF ref) + // Anyway since we don't use the fit/aligned options, they're the same + fprintf( outputFile, + " 0\n" + "TEXT\n" + " 7\n" + "%s\n" // Text style + " 8\n" + "%s\n" // Layer name + " 10\n" + "%g\n" // First point X + " 11\n" + "%g\n" // Second point X + " 20\n" + "%g\n" // First point Y + " 21\n" + "%g\n" // Second point Y + " 40\n" + "%g\n" // Text height + " 41\n" + "%g\n" // Width factor + " 50\n" + "%g\n" // Rotation + " 51\n" + "%g\n" // Oblique angle + " 71\n" + "%d\n" // Mirror flags + " 72\n" + "%d\n" // H alignment + " 73\n" + "%d\n", // V alignment + aBold ? (aItalic ? "KICADBI" : "KICADB") + : (aItalic ? "KICADI" : "KICAD"), + TO_UTF8( cname ), + origin_dev.x, origin_dev.x, + origin_dev.y, origin_dev.y, + size_dev.y, fabs( size_dev.x / size_dev.y ), + aOrient / 10.0, + aItalic ? DXF_OBLIQUE_ANGLE : 0, + size_dev.x < 0 ? 2 : 0, // X mirror flag + h_code, v_code ); + + /* There are two issue in emitting the text: + - Our overline character (~) must be converted to the appropriate + control sequence %%O or %%o + - Text encoding in DXF is more or less unspecified since depends on + the DXF declared version, the acad version reading it *and* some + system variables to be put in the header handled only by newer acads + Also before R15 unicode simply is not supported (you need to use + bigfonts which are a massive PITA). Common denominator solution: + use Latin1 (and however someone could choke on it, anyway). Sorry + for the extended latin people. If somewant want to try fixing this + recent version seems to use UTF-8 (and not UCS2 like the rest of + Windows) + + XXX Actually there is a *third* issue: older DXF formats are limited + to 255 bytes records (it was later raised to 2048); since I'm lazy + and text so long is not probable I just don't implement this rule. + If someone is interested in fixing this, you have to emit the first + partial lines with group code 3 (max 250 bytes each) and then finish + with a group code 1 (less than 250 bytes). The DXF refs explains it + in no more details... + */ + + bool overlining = false; + fputs( " 1\n", outputFile ); + for( unsigned i = 0; i < aText.length(); i++ ) + { + /* Here I do a bad thing: writing the output one byte at a time! + but today I'm lazy and I have no idea on how to coerce a Unicode + wxString to spit out latin1 encoded text ... + + Atleast stdio is *supposed* to do output buffering, so there is + hope is not too slow */ + wchar_t ch = aText[i]; + if( ch > 255 ) + { + // I can't encode this... + putc( '?', outputFile ); + } + else + { + if( ch == '~' ) + { + // Handle the overline toggle + fputs( overlining ? "%%o" : "%%O", outputFile ); + overlining = !overlining; + } + else + { + putc( ch, outputFile ); + } + } + } + putc( '\n', outputFile ); + } +} + diff --git a/common/plotters/common_plotGERBER_functions.cpp b/common/plotters/common_plotGERBER_functions.cpp new file mode 100644 index 0000000000..a8f794f214 --- /dev/null +++ b/common/plotters/common_plotGERBER_functions.cpp @@ -0,0 +1,974 @@ +/* + * 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 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 common_plotGERBER_functions.cpp + * @brief Common GERBER plot routines. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +GERBER_PLOTTER::GERBER_PLOTTER() +{ + workFile = NULL; + finalFile = NULL; + currentAperture = apertures.end(); + m_apertureAttribute = 0; + + // number of digits after the point (number of digits of the mantissa + // Be carefull: the Gerber coordinates are stored in an integer + // so 6 digits (inches) or 5 digits (mm) is a good value + // To avoid overflow, 7 digits (inches) or 6 digits is a max. + // with lower values than 6 digits (inches) or 5 digits (mm), + // Creating self-intersecting polygons from non-intersecting polygons + // happen easily. + m_gerberUnitInch = false; + m_gerberUnitFmt = 6; + m_useX2Attributes = false; + m_useNetAttributes = true; +} + + +void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + wxASSERT( aMirror == false ); + m_plotMirror = false; + plotOffset = aOffset; + wxASSERT( aScale == 1 ); // aScale parameter is not used in Gerber + plotScale = 1; // Plot scale is *always* 1.0 + + m_IUsPerDecimil = aIusPerDecimil; + // gives now a default value to iuPerDeviceUnit (because the units of the caller is now known) + // which could be modified later by calling SetGerberCoordinatesFormat() + iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 ); + + // We don't handle the filmbox, and it's more useful to keep the + // origin at the origin + paperSize.x = 0; + paperSize.y = 0; + SetDefaultLineWidth( 100 * aIusPerDecimil ); // Arbitrary default +} + + +void GERBER_PLOTTER::SetGerberCoordinatesFormat( int aResolution, bool aUseInches ) +{ + m_gerberUnitInch = aUseInches; + m_gerberUnitFmt = aResolution; + + iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 ); + + if( ! m_gerberUnitInch ) + iuPerDeviceUnit *= 25.4; // gerber output in mm +} + + +void GERBER_PLOTTER::emitDcode( const DPOINT& pt, int dcode ) +{ + + fprintf( outputFile, "X%dY%dD%02d*\n", + KiROUND( pt.x ), KiROUND( pt.y ), dcode ); +} + + +void GERBER_PLOTTER::clearNetAttribute() +{ + // disable a Gerber net attribute (exists only in X2 with net attributes mode). + if( m_objectAttributesDictionnary.empty() ) // No net attribute or not X2 mode + return; + + // Remove all net attributes from object attributes dictionnary + fputs( "%TD*%\n", outputFile ); + + m_objectAttributesDictionnary.clear(); +} + + +void GERBER_PLOTTER::StartBlock( void* aData ) +{ + // Currently, it is the same as EndBlock(): clear all aperture net attributes + EndBlock( aData ); +} + + +void GERBER_PLOTTER::EndBlock( void* aData ) +{ + // Remove all net attributes from object attributes dictionnary + clearNetAttribute(); +} + + +void GERBER_PLOTTER::formatNetAttribute( GBR_NETLIST_METADATA* aData ) +{ + // print a Gerber net attribute record. + // it is added to the object attributes dictionnary + // On file, only modified or new attributes are printed. + if( aData == NULL || !m_useX2Attributes || !m_useNetAttributes ) + return; + + bool clearDict; + std::string short_attribute_string; + + if( !FormatNetAttribute( short_attribute_string, m_objectAttributesDictionnary, + aData, clearDict ) ) + return; + + if( clearDict ) + clearNetAttribute(); + + if( !short_attribute_string.empty() ) + fputs( short_attribute_string.c_str(), outputFile ); +} + + +bool GERBER_PLOTTER::StartPlot() +{ + wxASSERT( outputFile ); + + finalFile = outputFile; // the actual gerber file will be created later + + // Create a temporary filename to store gerber file + // note tmpfile() does not work under Vista and W7 in user mode + m_workFilename = filename + wxT(".tmp"); + workFile = wxFopen( m_workFilename, wxT( "wt" )); + outputFile = workFile; + wxASSERT( outputFile ); + + if( outputFile == NULL ) + return false; + + for( unsigned ii = 0; ii < m_headerExtraLines.GetCount(); ii++ ) + { + if( ! m_headerExtraLines[ii].IsEmpty() ) + fprintf( outputFile, "%s\n", TO_UTF8( m_headerExtraLines[ii] ) ); + } + + // Set coordinate format to 3.6 or 4.5 absolute, leading zero omitted + // the number of digits for the integer part of coordintes is needed + // in gerber format, but is not very important when omitting leading zeros + // It is fixed here to 3 (inch) or 4 (mm), but is not actually used + int leadingDigitCount = m_gerberUnitInch ? 3 : 4; + + fprintf( outputFile, "%%FSLAX%d%dY%d%d*%%\n", + leadingDigitCount, m_gerberUnitFmt, + leadingDigitCount, m_gerberUnitFmt ); + fprintf( outputFile, + "G04 Gerber Fmt %d.%d, Leading zero omitted, Abs format (unit %s)*\n", + leadingDigitCount, m_gerberUnitFmt, + m_gerberUnitInch ? "inch" : "mm" ); + + wxString Title = creator + wxT( " " ) + GetBuildVersion(); + fprintf( outputFile, "G04 Created by KiCad (%s) date %s*\n", + TO_UTF8( Title ), TO_UTF8( DateAndTime() ) ); + + /* Mass parameter: unit = INCHES/MM */ + if( m_gerberUnitInch ) + fputs( "%MOIN*%\n", outputFile ); + else + fputs( "%MOMM*%\n", outputFile ); + + // Be sure the usual dark polarity is selected: + fputs( "%LPD*%\n", outputFile ); + + // Specify linear interpol (G01): + fputs( "G01*\n", outputFile ); + + fputs( "G04 APERTURE LIST*\n", outputFile ); + + return true; +} + + +bool GERBER_PLOTTER::EndPlot() +{ + char line[1024]; + wxString msg; + + wxASSERT( outputFile ); + + /* Outfile is actually a temporary file i.e. workFile */ + fputs( "M02*\n", outputFile ); + fflush( outputFile ); + + fclose( workFile ); + workFile = wxFopen( m_workFilename, wxT( "rt" )); + wxASSERT( workFile ); + outputFile = finalFile; + + // Placement of apertures in RS274X + while( fgets( line, 1024, workFile ) ) + { + fputs( line, outputFile ); + + if( strcmp( strtok( line, "\n\r" ), "G04 APERTURE LIST*" ) == 0 ) + { + writeApertureList(); + fputs( "G04 APERTURE END LIST*\n", outputFile ); + } + } + + fclose( workFile ); + fclose( finalFile ); + ::wxRemoveFile( m_workFilename ); + outputFile = 0; + + return true; +} + + +void GERBER_PLOTTER::SetDefaultLineWidth( int width ) +{ + defaultPenWidth = width; + currentAperture = apertures.end(); +} + + +void GERBER_PLOTTER::SetCurrentLineWidth( int width, void* aData ) +{ + if( width == DO_NOT_SET_LINE_WIDTH ) + return; + + int pen_width; + + if( width > 0 ) + pen_width = width; + else + pen_width = defaultPenWidth; + + GBR_METADATA* gbr_metadata = static_cast( aData ); + int aperture_attribute = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; + + selectAperture( wxSize( pen_width, pen_width ), APERTURE::Plotting, aperture_attribute ); + currentPenWidth = pen_width; +} + + +std::vector::iterator GERBER_PLOTTER::getAperture( const wxSize& aSize, + APERTURE::APERTURE_TYPE aType, int aApertureAttribute ) +{ + int last_D_code = 9; + + // Search an existing aperture + std::vector::iterator tool = apertures.begin(); + + while( tool != apertures.end() ) + { + last_D_code = tool->m_DCode; + + if( (tool->m_Type == aType) && (tool->m_Size == aSize) && (tool->m_ApertureAttribute == aApertureAttribute) ) + return tool; + + ++tool; + } + + // Allocate a new aperture + APERTURE new_tool; + new_tool.m_Size = aSize; + new_tool.m_Type = aType; + new_tool.m_DCode = last_D_code + 1; + new_tool.m_ApertureAttribute = aApertureAttribute; + + apertures.push_back( new_tool ); + + return apertures.end() - 1; +} + + +void GERBER_PLOTTER::selectAperture( const wxSize& aSize, + APERTURE::APERTURE_TYPE aType, + int aApertureAttribute ) +{ + bool change = ( currentAperture == apertures.end() ) || + ( currentAperture->m_Type != aType ) || + ( currentAperture->m_Size != aSize ); + + if( !m_useX2Attributes || !m_useNetAttributes ) + aApertureAttribute = 0; + else + change = change || ( currentAperture->m_ApertureAttribute != aApertureAttribute ); + + if( change ) + { + // Pick an existing aperture or create a new one + currentAperture = getAperture( aSize, aType, aApertureAttribute ); + fprintf( outputFile, "D%d*\n", currentAperture->m_DCode ); + } +} + + +void GERBER_PLOTTER::writeApertureList() +{ + wxASSERT( outputFile ); + char cbuf[1024]; + + // Init + for( std::vector::iterator tool = apertures.begin(); + tool != apertures.end(); ++tool ) + { + // apertude sizes are in inch or mm, regardless the + // coordinates format + double fscale = 0.0001 * plotScale / m_IUsPerDecimil; // inches + + if(! m_gerberUnitInch ) + fscale *= 25.4; // size in mm + + int attribute = tool->m_ApertureAttribute; + + if( attribute != m_apertureAttribute ) + fputs( GBR_APERTURE_METADATA::FormatAttribute( + (GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB) attribute ).c_str(), outputFile ); + + char* text = cbuf + sprintf( cbuf, "%%ADD%d", tool->m_DCode ); + + /* Please note: the Gerber specs for mass parameters say that + exponential syntax is *not* allowed and the decimal point should + also be always inserted. So the %g format is ruled out, but %f is fine + (the # modifier forces the decimal point). Sadly the %f formatter + can't remove trailing zeros but thats not a problem, since nothing + forbid it (the file is only slightly longer) */ + + switch( tool->m_Type ) + { + case APERTURE::Circle: + sprintf( text, "C,%#f*%%\n", tool->m_Size.x * fscale ); + break; + + case APERTURE::Rect: + sprintf( text, "R,%#fX%#f*%%\n", + tool->m_Size.x * fscale, + tool->m_Size.y * fscale ); + break; + + case APERTURE::Plotting: + sprintf( text, "C,%#f*%%\n", tool->m_Size.x * fscale ); + break; + + case APERTURE::Oval: + sprintf( text, "O,%#fX%#f*%%\n", + tool->m_Size.x * fscale, + tool->m_Size.y * fscale ); + break; + } + + fputs( cbuf, outputFile ); + + m_apertureAttribute = attribute; + + // Currently reset the aperture attribute. Perhaps a better optimization + // is to store the last attribute + if( attribute ) + { + fputs( "%TD*%\n", outputFile ); + m_apertureAttribute = 0; + } + + } +} + + +void GERBER_PLOTTER::PenTo( const wxPoint& aPos, char plume ) +{ + wxASSERT( outputFile ); + DPOINT pos_dev = userToDeviceCoordinates( aPos ); + + switch( plume ) + { + case 'Z': + break; + + case 'U': + emitDcode( pos_dev, 2 ); + break; + + case 'D': + emitDcode( pos_dev, 1 ); + } + + penState = plume; +} + + +void GERBER_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + std::vector< wxPoint > cornerList; + + // Build corners list + cornerList.push_back( p1 ); + wxPoint corner(p1.x, p2.y); + cornerList.push_back( corner ); + cornerList.push_back( p2 ); + corner.x = p2.x; + corner.y = p1.y; + cornerList.push_back( corner ); + cornerList.push_back( p1 ); + + PlotPoly( cornerList, fill, width ); +} + + +void GERBER_PLOTTER::Circle( const wxPoint& aCenter, int aDiameter, FILL_T aFill, int aWidth ) +{ + Arc( aCenter, 0, 3600, aDiameter / 2, aFill, aWidth ); +} + + +void GERBER_PLOTTER::Arc( const wxPoint& aCenter, double aStAngle, double aEndAngle, + int aRadius, FILL_T aFill, int aWidth ) +{ + SetCurrentLineWidth( aWidth ); + + wxPoint start, end; + start.x = aCenter.x + KiROUND( cosdecideg( aRadius, aStAngle ) ); + start.y = aCenter.y - KiROUND( sindecideg( aRadius, aStAngle ) ); + MoveTo( start ); + end.x = aCenter.x + KiROUND( cosdecideg( aRadius, aEndAngle ) ); + end.y = aCenter.y - KiROUND( sindecideg( aRadius, aEndAngle ) ); + DPOINT devEnd = userToDeviceCoordinates( end ); + DPOINT devCenter = userToDeviceCoordinates( aCenter ) - userToDeviceCoordinates( start ); + + fprintf( outputFile, "G75*\n" ); // Multiquadrant mode + + if( aStAngle < aEndAngle ) + fprintf( outputFile, "G03" ); + else + fprintf( outputFile, "G02" ); + + fprintf( outputFile, "X%dY%dI%dJ%dD01*\n", + KiROUND( devEnd.x ), KiROUND( devEnd.y ), + KiROUND( devCenter.x ), KiROUND( devCenter.y ) ); + fprintf( outputFile, "G01*\n" ); // Back to linear interp. +} + + +void GERBER_PLOTTER:: PlotPoly( const std::vector< wxPoint >& aCornerList, + FILL_T aFill, int aWidth, void * aData ) +{ + if( aCornerList.size() <= 1 ) + return; + + // Gerber format does not know filled polygons with thick outline + // Therefore, to plot a filled polygon with outline having a thickness, + // one should plot outline as thick segments + GBR_METADATA* gbr_metadata = static_cast( aData ); + + SetCurrentLineWidth( aWidth, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + if( aFill ) + { + fputs( "G36*\n", outputFile ); + + MoveTo( aCornerList[0] ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + LineTo( aCornerList[ii] ); + + FinishTo( aCornerList[0] ); + fputs( "G37*\n", outputFile ); + } + + if( aWidth > 0 ) + { + MoveTo( aCornerList[0] ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + LineTo( aCornerList[ii] ); + + // Ensure the thick outline is closed for filled polygons + // (if not filled, could be only a polyline) + if( aFill && ( aCornerList[aCornerList.size()-1] != aCornerList[0] ) ) + LineTo( aCornerList[0] ); + + PenFinish(); + } +} + + +void GERBER_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, int width, + EDA_DRAW_MODE_T tracemode, void* aData ) +{ + if( tracemode == FILLED ) + { + GBR_METADATA *gbr_metadata = static_cast( aData ); + SetCurrentLineWidth( width, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + MoveTo( start ); + FinishTo( end ); + } + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + segmentAsOval( start, end, width, tracemode ); + } +} + +void GERBER_PLOTTER::ThickArc( const wxPoint& centre, double StAngle, double EndAngle, + int radius, int width, EDA_DRAW_MODE_T tracemode, void* aData ) +{ + GBR_METADATA *gbr_metadata = static_cast( aData ); + SetCurrentLineWidth( width, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + if( tracemode == FILLED ) + Arc( centre, StAngle, EndAngle, radius, NO_FILL, DO_NOT_SET_LINE_WIDTH ); + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + Arc( centre, StAngle, EndAngle, + radius - ( width - currentPenWidth ) / 2, + NO_FILL, DO_NOT_SET_LINE_WIDTH ); + Arc( centre, StAngle, EndAngle, + radius + ( width - currentPenWidth ) / 2, NO_FILL, + DO_NOT_SET_LINE_WIDTH ); + } +} + + +void GERBER_PLOTTER::ThickRect( const wxPoint& p1, const wxPoint& p2, int width, + EDA_DRAW_MODE_T tracemode, void* aData ) +{ + GBR_METADATA *gbr_metadata = static_cast( aData ); + SetCurrentLineWidth( width, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + if( tracemode == FILLED ) + Rect( p1, p2, NO_FILL, DO_NOT_SET_LINE_WIDTH ); + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + wxPoint offsetp1( p1.x - (width - currentPenWidth) / 2, + p1.y - (width - currentPenWidth) / 2 ); + wxPoint offsetp2( p2.x + (width - currentPenWidth) / 2, + p2.y + (width - currentPenWidth) / 2 ); + Rect( offsetp1, offsetp2, NO_FILL, -1 ); + offsetp1.x += (width - currentPenWidth); + offsetp1.y += (width - currentPenWidth); + offsetp2.x -= (width - currentPenWidth); + offsetp2.y -= (width - currentPenWidth); + Rect( offsetp1, offsetp2, NO_FILL, DO_NOT_SET_LINE_WIDTH ); + } +} + + +void GERBER_PLOTTER::ThickCircle( const wxPoint& pos, int diametre, int width, + EDA_DRAW_MODE_T tracemode, void* aData ) +{ + GBR_METADATA *gbr_metadata = static_cast( aData ); + SetCurrentLineWidth( width, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + if( tracemode == FILLED ) + Circle( pos, diametre, NO_FILL, DO_NOT_SET_LINE_WIDTH ); + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata ); + Circle( pos, diametre - (width - currentPenWidth), + NO_FILL, DO_NOT_SET_LINE_WIDTH ); + Circle( pos, diametre + (width - currentPenWidth), + NO_FILL, DO_NOT_SET_LINE_WIDTH ); + } +} + + +void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxSize size( diametre, diametre ); + GBR_METADATA* gbr_metadata = static_cast( aData ); + + if( trace_mode == SKETCH ) + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + Circle( pos, diametre - currentPenWidth, NO_FILL, DO_NOT_SET_LINE_WIDTH ); + } + else + { + DPOINT pos_dev = userToDeviceCoordinates( pos ); + + int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; + selectAperture( size, APERTURE::Circle, aperture_attrib ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + emitDcode( pos_dev, 3 ); + } +} + + +void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient, + EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxASSERT( outputFile ); + int x0, y0, x1, y1, delta; + wxSize size( aSize ); + GBR_METADATA* gbr_metadata = static_cast( aData ); + + /* Plot a flashed shape. */ + if( ( orient == 0 || orient == 900 || orient == 1800 || orient == 2700 ) + && trace_mode == FILLED ) + { + if( orient == 900 || orient == 2700 ) /* orientation turned 90 deg. */ + std::swap( size.x, size.y ); + + DPOINT pos_dev = userToDeviceCoordinates( pos ); + int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; + selectAperture( size, APERTURE::Oval, aperture_attrib ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + emitDcode( pos_dev, 3 ); + } + else /* Plot pad as a segment. */ + { + if( size.x > size.y ) + { + std::swap( size.x, size.y ); + + if( orient < 2700 ) + orient += 900; + else + orient -= 2700; + } + + if( trace_mode == FILLED ) + { + // TODO: use an aperture macro to declare the rotated pad + // + + // Flash a pad anchor, if a netlist attribute is set + if( aData ) + FlashPadCircle( pos, size.x, trace_mode, aData ); + + // The pad is reduced to an segment with dy > dx + delta = size.y - size.x; + x0 = 0; + y0 = -delta / 2; + x1 = 0; + y1 = delta / 2; + RotatePoint( &x0, &y0, orient ); + RotatePoint( &x1, &y1, orient ); + GBR_METADATA metadata; + + if( gbr_metadata ) + { + metadata = *gbr_metadata; + + // If the pad is drawn on a copper layer, + // set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR + if( metadata.IsCopper() ) + metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR ); + + // Clear .P attribute, only allowed for flashed items + wxString attrname( ".P" ); + metadata.m_NetlistMetadata.ClearAttribute( &attrname ); + } + + ThickSegment( wxPoint( pos.x + x0, pos.y + y0 ), + wxPoint( pos.x + x1, pos.y + y1 ), + size.x, trace_mode, &metadata ); + } + else + { + sketchOval( pos, size, orient, -1 ); + } + } +} + + +void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize, + double orient, EDA_DRAW_MODE_T trace_mode, void* aData ) + +{ + wxASSERT( outputFile ); + wxSize size( aSize ); + GBR_METADATA* gbr_metadata = static_cast( aData ); + + // Plot as an aperture flash + switch( int( orient ) ) + { + case 900: + case 2700: // rotation of 90 degrees or 270 swaps sizes + std::swap( size.x, size.y ); + + // Pass through + case 0: + case 1800: + if( trace_mode == SKETCH ) + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + Rect( wxPoint( pos.x - (size.x - currentPenWidth) / 2, + pos.y - (size.y - currentPenWidth) / 2 ), + wxPoint( pos.x + (size.x - currentPenWidth) / 2, + pos.y + (size.y - currentPenWidth) / 2 ), + NO_FILL ); + } + else + { + DPOINT pos_dev = userToDeviceCoordinates( pos ); + int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0; + selectAperture( size, APERTURE::Rect, aperture_attrib ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + emitDcode( pos_dev, 3 ); + } + break; + + default: // plot pad shape as polygon + { + // XXX to do: use an aperture macro to declare the rotated pad + wxPoint coord[4]; + // coord[0] is assumed the lower left + // coord[1] is assumed the upper left + // coord[2] is assumed the upper right + // coord[3] is assumed the lower right + + /* Trace the outline. */ + coord[0].x = -size.x/2; // lower left + coord[0].y = size.y/2; + coord[1].x = -size.x/2; // upper left + coord[1].y = -size.y/2; + coord[2].x = size.x/2; // upper right + coord[2].y = -size.y/2; + coord[3].x = size.x/2; // lower right + coord[3].y = size.y/2; + + FlashPadTrapez( pos, coord, orient, trace_mode, aData ); + } + break; + } +} + +void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize, + int aCornerRadius, double aOrient, + EDA_DRAW_MODE_T aTraceMode, void* aData ) + +{ + // Currently, a Pad RoundRect is plotted as polygon. + // TODO: use Aperture macro and flash it + SHAPE_POLY_SET outline; + const int segmentToCircleCount = 64; + TransformRoundRectToPolygon( outline, aPadPos, aSize, aOrient, + aCornerRadius, segmentToCircleCount ); + + std::vector< wxPoint > cornerList; + cornerList.reserve( segmentToCircleCount + 5 ); + // TransformRoundRectToPolygon creates only one convex polygon + SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); + + for( int ii = 0; ii < poly.PointCount(); ++ii ) + cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + // Close polygon + cornerList.push_back( cornerList[0] ); + + GBR_METADATA gbr_metadata; + + if( aData ) + { + gbr_metadata = *static_cast( aData ); + // If the pad is drawn on a copper layer, + // set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR + if( gbr_metadata.IsCopper() ) + gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR ); + + wxString attrname( ".P" ); + gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers + } + + PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &gbr_metadata ); + + // Now, flash a pad anchor, if a netlist attribute is set + // (remove me when a Aperture macro will be used) + if( aData && aTraceMode == FILLED ) + { + int diameter = std::min( aSize.x, aSize.y ); + FlashPadCircle( aPadPos, diameter, aTraceMode , aData ); + } +} + +void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize, + SHAPE_POLY_SET* aPolygons, + EDA_DRAW_MODE_T aTraceMode, void* aData ) + +{ + // A Pad custom is plotted as polygon. + + // A flashed circle @aPadPos is added (anchor pad) + // However, because the anchor pad can be circle or rect, we use only + // a circle not bigger than the rect. + // the main purpose is to print a flashed DCode as pad anchor + FlashPadCircle( aPadPos, std::min( aSize.x, aSize.y ), aTraceMode, aData ); + GBR_METADATA gbr_metadata; + + if( aData ) + { + gbr_metadata = *static_cast( aData ); + // If the pad is drawn on a copper layer, + // set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR + if( gbr_metadata.IsCopper() ) + gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR ); + + wxString attrname( ".P" ); + gbr_metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers + } + + std::vector< wxPoint > cornerList; + + for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt ) + { + SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt ); + cornerList.clear(); + + for( int ii = 0; ii < poly.PointCount(); ++ii ) + cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + // Close polygon + cornerList.push_back( cornerList[0] ); + + PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &gbr_metadata ); + } +} + + +void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners, + double aPadOrient, EDA_DRAW_MODE_T aTrace_Mode, void* aData ) + +{ + // Currently, a Pad Trapezoid is plotted as polygon. + // TODO: use Aperture macro and flash it + + // polygon corners list + std::vector< wxPoint > cornerList; + + for( int ii = 0; ii < 4; ii++ ) + cornerList.push_back( aCorners[ii] ); + + // Now, flash a pad anchor, if a netlist attribute is set + // (remove me when a Aperture macro will be used) + if( aData && (aTrace_Mode==FILLED) ) + { + // Calculate the radius of the circle inside the shape + // It is the smaller dist from shape pos to edges + int radius = INT_MAX; + + for( unsigned ii = 0, jj = cornerList.size()-1; ii < cornerList.size(); + jj = ii, ii++ ) + { + SEG segment( aCorners[ii], aCorners[jj] ); + int dist = segment.LineDistance( VECTOR2I( 0, 0) ); + radius = std::min( radius, dist ); + } + + FlashPadCircle( aPadPos, radius*2, aTrace_Mode, aData ); + } + + // Draw the polygon and fill the interior as required + for( unsigned ii = 0; ii < 4; ii++ ) + { + RotatePoint( &cornerList[ii], aPadOrient ); + cornerList[ii] += aPadPos; + } + + // Close the polygon + cornerList.push_back( cornerList[0] ); + GBR_METADATA* gbr_metadata = static_cast( aData ); + + GBR_METADATA metadata; + + if( gbr_metadata ) + { + metadata = *gbr_metadata; + // If the pad is drawn on a copper layer, + // set attribute to GBR_APERTURE_ATTRIB_CONDUCTOR + if( metadata.IsCopper() ) + metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR ); + + wxString attrname( ".P" ); + metadata.m_NetlistMetadata.ClearAttribute( &attrname ); // not allowed on inner layers + } + + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &metadata ); + PlotPoly( cornerList, aTrace_Mode==FILLED ? FILLED_SHAPE : NO_FILL, USE_DEFAULT_LINE_WIDTH, &metadata ); +} + + +void GERBER_PLOTTER::Text( const wxPoint& aPos, const COLOR4D aColor, + const wxString& aText, double aOrient, const wxSize& aSize, + enum EDA_TEXT_HJUSTIFY_T aH_justify, enum EDA_TEXT_VJUSTIFY_T aV_justify, + int aWidth, bool aItalic, bool aBold, bool aMultilineAllowed, + void* aData ) +{ + GBR_METADATA* gbr_metadata = static_cast( aData ); + + if( gbr_metadata ) + formatNetAttribute( &gbr_metadata->m_NetlistMetadata ); + + PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, + aH_justify, aV_justify, aWidth, aItalic, aBold, aMultilineAllowed, aData ); +} + + +void GERBER_PLOTTER::SetLayerPolarity( bool aPositive ) +{ + if( aPositive ) + fprintf( outputFile, "%%LPD*%%\n" ); + else + fprintf( outputFile, "%%LPC*%%\n" ); +} diff --git a/common/plotters/common_plotHPGL_functions.cpp b/common/plotters/common_plotHPGL_functions.cpp new file mode 100644 index 0000000000..d211a04576 --- /dev/null +++ b/common/plotters/common_plotHPGL_functions.cpp @@ -0,0 +1,693 @@ +/* + * 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 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 common_plotHPGL_functions.cpp + * @brief KiCad: Common plot HPGL Routines + * Since this plot engine is mostly intended for import in external programs, + * sadly HPGL/2 isn't supported a lot... some of the primitives use overlapped + * strokes to fill the shape + */ + +/* Some HPGL commands: + * Note: the HPGL unit is 25 micrometers + * All commands MUST be terminated by a semi-colon or a linefeed. + * Spaces can NOT be substituted for required commas in the syntax of a command. + * + * + * AA (Arc Absolute): Angle is a floating point # (requires non integer value) + * Draws an arc with the center at (X,Y). + * A positive angle creates a counter-clockwise arc. + * If the chord angle is specified, + * this will be the number of degrees used for stepping around the arc. + * If no value is given then a default value of five degrees is used. + * AA x, y, a {,b}; + * + * AR (Arc Relative): + * AR Dx, Dy, a {, b}; + * + * CA (Alternate Character Set): + * CA {n}; + * + * CI (Circle): + * CI r {,b}; + * + * CP (Character Plot): + * CP {h, v}; + * h [-127.9999 .. 127.9999] Anzahl der Zeichen horizontal + * v [-127.9999 .. 127.9999] Anzahl der Zeichen vertikal + * + * CS (Standard Character Set): + * CS {n}; + * + * DR (Relative Direction for Label Text): + * DR s, a; + * + * DI (Absolute Direction for Label Text): + * DI {s, a}; + * + * DT (Define Terminator - this character becomes unavailable except to terminate a label string. + * Default is ^C control-C): + * DT t; + * + * EA (rEctangle Absolute - Unfilled, from current position to diagonal x,y): + * EA x, y; + * + * ER (rEctangle Relative - Unfilled, from current position to diagonal x,y): + * ER x,y; + * + * FT (Fill Type): + * FT {s {,l {a}}}; + * + * IM (Input Mask): + * IM {f}; + * + * IN (Initialize): This command instructs the controller to begin processing the HPGL plot file. + * Without this, the commands in the file are received but never executed. + * If multiple IN s are found during execution of the file, + * the controller performs a Pause/Cancel operation. + * All motion from the previous job, yet to be executed, is lost, + * and the new information is executed. + * IN; + * + * IP Input P1 and P2: + * IP {P1x, P1y {, P2x, P2y}}; + * + * IW (Input Window): + * IW {XUL, YUL, XOR, YOR}; + * + * LB (Label): + * LB c1 .. cn t; + * + * PA (Plot Absolute): Moves to an absolute HPGL position and sets absolute mode for + * future PU and PD commands. If no arguments follow the command, + * only absolute mode is set. + * PA {x1, y1 {{PU|PD|,} ..., ..., xn, yn}}; + * P1x, P1y, P2x, P2y [Integer in ASCII] + * + * PD (Pen Down): Executes pen then moves to the requested position + * if one is specified. This position is dependent on whether absolute + * or relative mode is set. This command performs no motion in 3-D mode, + * but the outputs and feedrates are affected. + * PD {x, y}; + * + * PM Polygon mode + * associated commands: + * PM2 End polygon mode + * FP Fill polygon + * EP Draw polygon outline + * + * PR (Plot Relative): Moves to the relative position specified and sets relative mode + * for future PU and PD commands. + * If no arguments follow the command, only relative mode is set. + * PR {Dx1, Dy1 {{PU|PD|,} ..., ..., Dxn, Dyn}}; + * + * PS (Paper Size): + * PS {n}; + * + * PT (Pen Thickness): in mm + * PT {l}; + * + * PU (Pen Up): Executes pen then moves to the requested position + * if one is specified. This position is dependent on whether absolute + * or relative mode is set. + * This command performs no motion in 3-D mode, but the outputs + * and feedrates are affected. + * PU {x, y}; + * + * RA (Rectangle Absolute - Filled, from current position to diagonal x,y): + * RA x, y; + * + * RO (Rotate Coordinate System): + * RO; + * + * RR (Rectangle Relative - Filled, from current position to diagonal x,y): + * RR x, y; + * + * SA (Select Alternate Set): + * SA; + * + * SC (Scale): + * SC {Xmin, Xmax, Ymin, Ymax}; + * + * SI (Absolute Character Size): + * SI b, h; + * b [-127.9999 .. 127.9999, keine 0] + * h [-127.9999 .. 127.9999, keine 0] + * + * SL (Character Slant): + * SL {a}; + * a [-3.5 .. -0.5, 0.5 .. 3.5] +* + * SP (Select Pen): Selects a new pen or tool for use. + * If no pen number or a value of zero is given, + * the controller performs an EOF (end of file command). + * Once an EOF is performed, no motion is executed, + * until a new IN command is received. + * SP n; + * + * SR (Relative Character Size): + * SR {b, h}; + * b [-127.9999 .. 127.9999, keine 0] + * h [-127.9999 .. 127.9999, keine 0] + * + * SS (Select Standard Set): + * SS; + * + * TL (Tick Length): + * TL {tp {, tm}}; + * + * UC (User Defined Character): + * UC {i,} x1, y1, {i,} x2, y2, ... {i,} xn, yn; + * + * VS (Velocity Select): + * VS {v {, n}}; + * v [1 .. 40] in cm/s + * n [1 .. 8] + * + * XT (X Tick): + * XT; + * + * YT (Y Tick): + * YT; + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// The hpgl command to close a polygon def, fill it and plot outline: +// PM 2; ends the polygon definition and closes it if not closed +// FP; fills the polygon +// EP; draws the polygon outline. It usually gives a better look to the filled polygon +static const char hpgl_end_polygon_cmd[] = "PM 2; FP; EP;\n"; + +// HPGL scale factor (1 PLU = 1/40mm = 25 micrometers) +static const double PLUsPERDECIMIL = 0.102041; + +HPGL_PLOTTER::HPGL_PLOTTER() +{ + SetPenSpeed( 40 ); // Default pen speed = 40 cm/s; Pen speed is *always* in cm + SetPenNumber( 1 ); // Default pen num = 1 + SetPenDiameter( 0.0 ); +} + +void HPGL_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + plotOffset = aOffset; + plotScale = aScale; + m_IUsPerDecimil = aIusPerDecimil; + iuPerDeviceUnit = PLUsPERDECIMIL / aIusPerDecimil; + /* Compute the paper size in IUs */ + paperSize = pageInfo.GetSizeMils(); + paperSize.x *= 10.0 * aIusPerDecimil; + paperSize.y *= 10.0 * aIusPerDecimil; + SetDefaultLineWidth( 0 ); // HPGL has pen sizes instead + m_plotMirror = aMirror; +} + + +/** + * At the start of the HPGL plot pen speed and number are requested + */ +bool HPGL_PLOTTER::StartPlot() +{ + wxASSERT( outputFile ); + fprintf( outputFile, "IN;VS%d;PU;PA;SP%d;\n", penSpeed, penNumber ); + + // Set HPGL Pen Thickness (in mm) (usefull in polygon fill command) + double penThicknessMM = userToDeviceSize( penDiameter )/40; + fprintf( outputFile, "PT %.1f;\n", penThicknessMM ); + + return true; +} + + +/** + * HPGL end of plot: pen return and release + */ +bool HPGL_PLOTTER::EndPlot() +{ + wxASSERT( outputFile ); + fputs( "PU;PA;SP0;\n", outputFile ); + fclose( outputFile ); + outputFile = NULL; + return true; +} + + +void HPGL_PLOTTER::SetPenDiameter( double diameter ) +{ + penDiameter = diameter; +} + +/** + * HPGL rectangle: fill not supported + */ +void HPGL_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + DPOINT p2dev = userToDeviceCoordinates( p2 ); + MoveTo( p1 ); + fprintf( outputFile, "EA %.0f,%.0f;\n", p2dev.x, p2dev.y ); + PenFinish(); +} + + +// HPGL circle +void HPGL_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_T fill, + int width ) +{ + wxASSERT( outputFile ); + double radius = userToDeviceSize( diameter / 2 ); + SetCurrentLineWidth( width ); + + if( fill == FILLED_SHAPE ) + { + // Draw the filled area + MoveTo( centre ); + fprintf( outputFile, "PM 0; CI %g;\n", radius ); + fprintf( outputFile, hpgl_end_polygon_cmd ); // Close, fill polygon and draw outlines + PenFinish(); + } + + if( radius > 0 ) + { + MoveTo( centre ); + fprintf( outputFile, "CI %g;\n", radius ); + PenFinish(); + } +} + + +/** + * HPGL polygon: + */ + +void HPGL_PLOTTER::PlotPoly( const std::vector& aCornerList, + FILL_T aFill, int aWidth, void * aData ) +{ + if( aCornerList.size() <= 1 ) + return; + + SetCurrentLineWidth( aWidth ); + MoveTo( aCornerList[0] ); + + if( aFill == FILLED_SHAPE ) + { + // Draw the filled area + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + fprintf( outputFile, "PM 0;\n" ); // Start polygon + + for( unsigned ii = 1; ii < aCornerList.size(); ++ii ) + LineTo( aCornerList[ii] ); + + int ii = aCornerList.size() - 1; + + if( aCornerList[ii] != aCornerList[0] ) + LineTo( aCornerList[0] ); + + fprintf( outputFile, hpgl_end_polygon_cmd ); // Close, fill polygon and draw outlines + } + else + { + // Plot only the polygon outline. + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + LineTo( aCornerList[ii] ); + + // Always close polygon if filled. + if( aFill ) + { + int ii = aCornerList.size() - 1; + + if( aCornerList[ii] != aCornerList[0] ) + LineTo( aCornerList[0] ); + } + } + + PenFinish(); +} + + +/** + * Pen control logic (remove redundant pen activations) + */ +void HPGL_PLOTTER::penControl( char plume ) +{ + wxASSERT( outputFile ); + + switch( plume ) + { + case 'U': + + if( penState != 'U' ) + { + fputs( "PU;", outputFile ); + penState = 'U'; + } + + break; + + case 'D': + + if( penState != 'D' ) + { + fputs( "PD;", outputFile ); + penState = 'D'; + } + + break; + + case 'Z': + fputs( "PU;", outputFile ); + penState = 'U'; + penLastpos.x = -1; + penLastpos.y = -1; + break; + } +} + + +void HPGL_PLOTTER::PenTo( const wxPoint& pos, char plume ) +{ + wxASSERT( outputFile ); + + if( plume == 'Z' ) + { + penControl( 'Z' ); + return; + } + + penControl( plume ); + DPOINT pos_dev = userToDeviceCoordinates( pos ); + + if( penLastpos != pos ) + fprintf( outputFile, "PA %.0f,%.0f;\n", pos_dev.x, pos_dev.y ); + + penLastpos = pos; +} + + +/** + * HPGL supports dashed lines + */ +void HPGL_PLOTTER::SetDash( int dashed ) +{ + wxASSERT( outputFile ); + + switch( dashed ) + { + case PLOTDASHTYPE_DASH: + fprintf( outputFile, "LT -2 4 1;\n" ); + break; + case PLOTDASHTYPE_DOT: + fprintf( outputFile, "LT -1 2 1;\n" ); + break; + case PLOTDASHTYPE_DASHDOT: + fprintf( outputFile, "LT -4 6 1;\n" ); + break; + default: + fputs( "LT;\n", outputFile ); + } +} + + +void HPGL_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, + int width, EDA_DRAW_MODE_T tracemode, void* aData ) +{ + wxASSERT( outputFile ); + wxPoint center; + wxSize size; + + // Suppress overlap if pen is too big + if( penDiameter >= width ) + { + MoveTo( start ); + FinishTo( end ); + } + else + segmentAsOval( start, end, width, tracemode ); +} + + +/* Plot an arc: + * Center = center coord + * Stangl, endAngle = angle of beginning and end + * Radius = radius of the arc + * Command + * PU PY x, y; PD start_arc_X AA, start_arc_Y, angle, NbSegm; PU; + * Or PU PY x, y; PD start_arc_X AA, start_arc_Y, angle, PU; + */ +void HPGL_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius, + FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + double angle; + + if( radius <= 0 ) + return; + + DPOINT centre_dev = userToDeviceCoordinates( centre ); + + if( m_plotMirror ) + angle = StAngle - EndAngle; + else + angle = EndAngle - StAngle; + + NORMALIZE_ANGLE_180( angle ); + angle /= 10; + + // Calculate arc start point: + wxPoint cmap; + cmap.x = centre.x + KiROUND( cosdecideg( radius, StAngle ) ); + cmap.y = centre.y - KiROUND( sindecideg( radius, StAngle ) ); + DPOINT cmap_dev = userToDeviceCoordinates( cmap ); + + fprintf( outputFile, + "PU;PA %.0f,%.0f;PD;AA %.0f,%.0f,", + cmap_dev.x, cmap_dev.y, + centre_dev.x, centre_dev.y ); + fprintf( outputFile, "%.0f", angle ); + fprintf( outputFile, ";PU;\n" ); + PenFinish(); +} + + +/* Plot oval pad. + */ +void HPGL_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient, + EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxASSERT( outputFile ); + int deltaxy, cx, cy; + wxSize size( aSize ); + + /* The pad will be drawn as an oblong shape with size.y > size.x + * (Oval vertical orientation 0) + */ + if( size.x > size.y ) + { + std::swap( size.x, size.y ); + orient = AddAngles( orient, 900 ); + } + + deltaxy = size.y - size.x; // distance between centers of the oval + + if( trace_mode == FILLED ) + { + FlashPadRect( pos, wxSize( size.x, deltaxy + KiROUND( penDiameter ) ), + orient, trace_mode, aData ); + cx = 0; cy = deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData ); + cx = 0; cy = -deltaxy / 2; + RotatePoint( &cx, &cy, orient ); + FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData ); + } + else // Plot in outline mode. + { + sketchOval( pos, size, orient, KiROUND( penDiameter ) ); + } +} + + +/* Plot round pad or via. + */ +void HPGL_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, + EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + wxASSERT( outputFile ); + DPOINT pos_dev = userToDeviceCoordinates( pos ); + + int radius = diametre / 2; + + if( trace_mode == FILLED ) + { + // if filled mode, the pen diameter is removed from diameter + // to keep the pad size + radius -= KiROUND( penDiameter ) / 2; + } + + if( radius < 0 ) + radius = 0; + + double rsize = userToDeviceSize( radius ); + + if( trace_mode == FILLED ) // Plot in filled mode. + { + // A filled polygon uses always the current point to start the polygon. + // Gives a correct current starting point for the circle + MoveTo( wxPoint( pos.x+radius, pos.y ) ); + // Plot filled area and its outline + fprintf( outputFile, "PM 0; PA %.0f,%.0f;CI %.0f;%s", + pos_dev.x, pos_dev.y, rsize, hpgl_end_polygon_cmd ); + } + else + { + // Draw outline only: + fprintf( outputFile, "PA %.0f,%.0f;CI %.0f;\n", + pos_dev.x, pos_dev.y, rsize ); + } + + PenFinish(); +} + + +void HPGL_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize, + double orient, EDA_DRAW_MODE_T trace_mode, void* aData ) +{ + // Build rect polygon: + std::vector corners; + + int dx = padsize.x / 2; + int dy = padsize.y / 2; + + if( trace_mode == FILLED ) + { + // in filled mode, the pen diameter is removed from size + // to compensate the extra size due to this pen size + dx -= KiROUND( penDiameter ) / 2; + dx = std::max( dx, 0); + dy -= KiROUND( penDiameter ) / 2; + dy = std::max( dy, 0); + } + + + corners.push_back( wxPoint( - dx, - dy ) ); + corners.push_back( wxPoint( - dx, + dy ) ); + corners.push_back( wxPoint( + dx, + dy ) ); + corners.push_back( wxPoint( + dx, - dy ) ); + + + for( unsigned ii = 0; ii < corners.size(); ii++ ) + { + RotatePoint( &corners[ii], orient ); + corners[ii] += pos; + } + + PlotPoly( corners, trace_mode == FILLED ? FILLED_SHAPE : NO_FILL ); +} + + +void HPGL_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize, + int aCornerRadius, double aOrient, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + SHAPE_POLY_SET outline; + const int segmentToCircleCount = 32; + + wxSize size = aSize; + + if( aTraceMode == FILLED ) + { + // in filled mode, the pen diameter is removed from size + // to keep the pad size + size.x -= KiROUND( penDiameter ) / 2; + size.x = std::max( size.x, 0); + size.y -= KiROUND( penDiameter ) / 2; + size.y = std::max( size.y, 0); + + // keep aCornerRadius to a value < min size x,y < 2: + aCornerRadius = std::min( aCornerRadius, std::min( size.x, size.y ) /2 ); + } + + TransformRoundRectToPolygon( outline, aPadPos, size, aOrient, + aCornerRadius, segmentToCircleCount ); + + // TransformRoundRectToPolygon creates only one convex polygon + std::vector< wxPoint > cornerList; + cornerList.reserve( segmentToCircleCount + 4 ); + SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); + + for( int ii = 0; ii < poly.PointCount(); ++ii ) + cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL ); +} + +void HPGL_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize, + SHAPE_POLY_SET* aPolygons, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + std::vector< wxPoint > cornerList; + + for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt ) + { + SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt ); + + cornerList.clear(); + cornerList.reserve( poly.PointCount() ); + + for( int ii = 1; ii < poly.PointCount(); ++ii ) + cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL ); + } +} + + +void HPGL_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners, + double aPadOrient, EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + std::vector< wxPoint > cornerList; + cornerList.reserve( 4 ); + + for( int ii = 0; ii < 4; ii++ ) + { + wxPoint coord( aCorners[ii] ); + RotatePoint( &coord, aPadOrient ); + coord += aPadPos; + cornerList.push_back( coord ); + } + + PlotPoly( cornerList, aTraceMode == FILLED ? FILLED_SHAPE : NO_FILL ); +} diff --git a/common/plotters/common_plotPDF_functions.cpp b/common/plotters/common_plotPDF_functions.cpp new file mode 100644 index 0000000000..990faf1a3d --- /dev/null +++ b/common/plotters/common_plotPDF_functions.cpp @@ -0,0 +1,857 @@ +/** + * @file common_plotPDF_functions.cpp + * @brief Kicad: Common plot PDF Routines + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2012 Lorenzo Marcantonio, l.marcantonio@logossrl.com + * Copyright (C) 1992-2017 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Open or create the plot file aFullFilename + * return true if success, false if the file cannot be created/opened + * + * Opens the PDF file in binary mode + */ +bool PDF_PLOTTER::OpenFile( const wxString& aFullFilename ) +{ + filename = aFullFilename; + + wxASSERT( !outputFile ); + + // Open the PDF file in binary mode + outputFile = wxFopen( filename, wxT( "wb" ) ); + + if( outputFile == NULL ) + return false ; + + return true; +} + +void PDF_PLOTTER::SetPageSettings( const PAGE_INFO& aPageSettings ) +{ + pageInfo = aPageSettings; +} + +void PDF_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + m_plotMirror = aMirror; + plotOffset = aOffset; + plotScale = aScale; + m_IUsPerDecimil = aIusPerDecimil; + + // The CTM is set to 1 user unit per decimil + iuPerDeviceUnit = 1.0 / aIusPerDecimil; + + SetDefaultLineWidth( 100 / iuPerDeviceUnit ); // arbitrary default + + /* The paper size in this engined is handled page by page + Look in the StartPage function */ +} + + +/** + * Pen width setting for PDF. Since the specs *explicitly* says that a 0 + * width is a bad thing to use (since it results in 1 pixel traces), we + * convert such requests to the minimal width (like 1) + * Note pen width = 0 is used in plot polygons to plot filled polygons with + * no outline thickness + * use in this case pen width = 1 does not actally change the polygon + */ +void PDF_PLOTTER::SetCurrentLineWidth( int width, void* aData ) +{ + wxASSERT( workFile ); + int pen_width; + + if( width > 0 ) + pen_width = width; + else if( width == 0 ) + pen_width = 1; + else + pen_width = defaultPenWidth; + + if( pen_width != currentPenWidth ) + fprintf( workFile, "%g w\n", + userToDeviceSize( pen_width ) ); + + currentPenWidth = pen_width; +} + + +/** + * PDF supports colors fully. It actually has distinct fill and pen colors, + * but we set both at the same time. + * + * XXX Keeping them divided could result in a minor optimization in + * eeschema filled shapes, but would propagate to all the other plot + * engines. Also arcs are filled as pies but only the arc is stroked so + * it would be difficult to handle anyway. + */ +void PDF_PLOTTER::emitSetRGBColor( double r, double g, double b ) +{ + wxASSERT( workFile ); + fprintf( workFile, "%g %g %g rg %g %g %g RG\n", + r, g, b, r, g, b ); +} + +/** + * PDF supports dashed lines + */ +void PDF_PLOTTER::SetDash( int dashed ) +{ + wxASSERT( workFile ); + switch( dashed ) + { + case PLOTDASHTYPE_DASH: + fprintf( workFile, "[%d %d] 0 d\n", + (int) GetDashMarkLenIU(), (int) GetDashGapLenIU() ); + break; + case PLOTDASHTYPE_DOT: + fprintf( workFile, "[%d %d] 0 d\n", + (int) GetDotMarkLenIU(), (int) GetDashGapLenIU() ); + break; + case PLOTDASHTYPE_DASHDOT: + fprintf( workFile, "[%d %d %d %d] 0 d\n", + (int) GetDashMarkLenIU(), (int) GetDashGapLenIU(), + (int) GetDotMarkLenIU(), (int) GetDashGapLenIU() ); + break; + default: + fputs( "[] 0 d\n", workFile ); + } +} + + +/** + * Rectangles in PDF. Supported by the native operator + */ +void PDF_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + wxASSERT( workFile ); + DPOINT p1_dev = userToDeviceCoordinates( p1 ); + DPOINT p2_dev = userToDeviceCoordinates( p2 ); + + SetCurrentLineWidth( width ); + fprintf( workFile, "%g %g %g %g re %c\n", p1_dev.x, p1_dev.y, + p2_dev.x - p1_dev.x, p2_dev.y - p1_dev.y, + fill == NO_FILL ? 'S' : 'B' ); +} + + +/** + * Circle drawing for PDF. They're approximated by curves, but fill is supported + */ +void PDF_PLOTTER::Circle( const wxPoint& pos, int diametre, FILL_T aFill, int width ) +{ + wxASSERT( workFile ); + DPOINT pos_dev = userToDeviceCoordinates( pos ); + double radius = userToDeviceSize( diametre / 2.0 ); + + /* OK. Here's a trick. PDF doesn't support circles or circular angles, that's + a fact. You'll have to do with cubic beziers. These *can't* represent + circular arcs (NURBS can, beziers don't). But there is a widely known + approximation which is really good + */ + + SetCurrentLineWidth( width ); + double magic = radius * 0.551784; // You don't want to know where this come from + + // This is the convex hull for the bezier approximated circle + fprintf( workFile, "%g %g m " + "%g %g %g %g %g %g c " + "%g %g %g %g %g %g c " + "%g %g %g %g %g %g c " + "%g %g %g %g %g %g c %c\n", + pos_dev.x - radius, pos_dev.y, + + pos_dev.x - radius, pos_dev.y + magic, + pos_dev.x - magic, pos_dev.y + radius, + pos_dev.x, pos_dev.y + radius, + + pos_dev.x + magic, pos_dev.y + radius, + pos_dev.x + radius, pos_dev.y + magic, + pos_dev.x + radius, pos_dev.y, + + pos_dev.x + radius, pos_dev.y - magic, + pos_dev.x + magic, pos_dev.y - radius, + pos_dev.x, pos_dev.y - radius, + + pos_dev.x - magic, pos_dev.y - radius, + pos_dev.x - radius, pos_dev.y - magic, + pos_dev.x - radius, pos_dev.y, + + aFill == NO_FILL ? 's' : 'b' ); +} + + +/** + * The PDF engine can't directly plot arcs, it uses the base emulation. + * So no filled arcs (not a great loss... ) + */ +void PDF_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius, + FILL_T fill, int width ) +{ + wxASSERT( workFile ); + if( radius <= 0 ) + return; + + /* Arcs are not so easily approximated by beziers (in the general case), + so we approximate them in the old way */ + wxPoint start, end; + const int delta = 50; // increment (in 0.1 degrees) to draw circles + + if( StAngle > EndAngle ) + std::swap( StAngle, EndAngle ); + + SetCurrentLineWidth( width ); + + // Usual trig arc plotting routine... + start.x = centre.x + KiROUND( cosdecideg( radius, -StAngle ) ); + start.y = centre.y + KiROUND( sindecideg( radius, -StAngle ) ); + DPOINT pos_dev = userToDeviceCoordinates( start ); + fprintf( workFile, "%g %g m ", pos_dev.x, pos_dev.y ); + for( int ii = StAngle + delta; ii < EndAngle; ii += delta ) + { + end.x = centre.x + KiROUND( cosdecideg( radius, -ii ) ); + end.y = centre.y + KiROUND( sindecideg( radius, -ii ) ); + pos_dev = userToDeviceCoordinates( end ); + fprintf( workFile, "%g %g l ", pos_dev.x, pos_dev.y ); + } + + end.x = centre.x + KiROUND( cosdecideg( radius, -EndAngle ) ); + end.y = centre.y + KiROUND( sindecideg( radius, -EndAngle ) ); + pos_dev = userToDeviceCoordinates( end ); + fprintf( workFile, "%g %g l ", pos_dev.x, pos_dev.y ); + + // The arc is drawn... if not filled we stroke it, otherwise we finish + // closing the pie at the center + if( fill == NO_FILL ) + { + fputs( "S\n", workFile ); + } + else + { + pos_dev = userToDeviceCoordinates( centre ); + fprintf( workFile, "%g %g l b\n", pos_dev.x, pos_dev.y ); + } +} + + +/** + * Polygon plotting for PDF. Everything is supported + */ +void PDF_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList, + FILL_T aFill, int aWidth, void * aData ) +{ + wxASSERT( workFile ); + if( aCornerList.size() <= 1 ) + return; + + SetCurrentLineWidth( aWidth ); + + DPOINT pos = userToDeviceCoordinates( aCornerList[0] ); + fprintf( workFile, "%g %g m\n", pos.x, pos.y ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + { + pos = userToDeviceCoordinates( aCornerList[ii] ); + fprintf( workFile, "%g %g l\n", pos.x, pos.y ); + } + + // Close path and stroke(/fill) + fprintf( workFile, "%c\n", aFill == NO_FILL ? 'S' : 'b' ); +} + + +void PDF_PLOTTER::PenTo( const wxPoint& pos, char plume ) +{ + wxASSERT( workFile ); + if( plume == 'Z' ) + { + if( penState != 'Z' ) + { + fputs( "S\n", workFile ); + penState = 'Z'; + penLastpos.x = -1; + penLastpos.y = -1; + } + return; + } + + if( penState != plume || pos != penLastpos ) + { + DPOINT pos_dev = userToDeviceCoordinates( pos ); + fprintf( workFile, "%g %g %c\n", + pos_dev.x, pos_dev.y, + ( plume=='D' ) ? 'l' : 'm' ); + } + penState = plume; + penLastpos = pos; +} + +/** + * PDF images are handles as inline, not XObject streams... + */ +void PDF_PLOTTER::PlotImage( const wxImage & aImage, const wxPoint& aPos, + double aScaleFactor ) +{ + wxASSERT( workFile ); + wxSize pix_size( aImage.GetWidth(), aImage.GetHeight() ); + + // Requested size (in IUs) + DPOINT drawsize( aScaleFactor * pix_size.x, + aScaleFactor * pix_size.y ); + + // calculate the bitmap start position + wxPoint start( aPos.x - drawsize.x / 2, + aPos.y + drawsize.y / 2); + + DPOINT dev_start = userToDeviceCoordinates( start ); + + /* PDF has an uhm... simplified coordinate system handling. There is + *one* operator to do everything (the PS concat equivalent). At least + they kept the matrix stack to save restore environments. Also images + are always emitted at the origin with a size of 1x1 user units. + What we need to do is: + 1) save the CTM end estabilish the new one + 2) plot the image + 3) restore the CTM + 4) profit + */ + fprintf( workFile, "q %g 0 0 %g %g %g cm\n", // Step 1 + userToDeviceSize( drawsize.x ), + userToDeviceSize( drawsize.y ), + dev_start.x, dev_start.y ); + + /* An inline image is a cross between a dictionary and a stream. + A real ugly construct (compared with the elegance of the PDF + format). Also it accepts some 'abbreviations', which is stupid + since the content stream is usually compressed anyway... */ + fprintf( workFile, + "BI\n" + " /BPC 8\n" + " /CS %s\n" + " /W %d\n" + " /H %d\n" + "ID\n", colorMode ? "/RGB" : "/G", pix_size.x, pix_size.y ); + + /* Here comes the stream (in binary!). I *could* have hex or ascii84 + encoded it, but who cares? I'll go through zlib anyway */ + for( int y = 0; y < pix_size.y; y++ ) + { + for( int x = 0; x < pix_size.x; x++ ) + { + unsigned char r = aImage.GetRed( x, y ) & 0xFF; + unsigned char g = aImage.GetGreen( x, y ) & 0xFF; + unsigned char b = aImage.GetBlue( x, y ) & 0xFF; + // As usual these days, stdio buffering has to suffeeeeerrrr + if( colorMode ) + { + putc( r, workFile ); + putc( g, workFile ); + putc( b, workFile ); + } + else + { + // Grayscale conversion + putc( (r + g + b) / 3, workFile ); + } + } + } + + fputs( "EI Q\n", workFile ); // Finish step 2 and do step 3 +} + + +/** + * Allocate a new handle in the table of the PDF object. The + * handle must be completed using startPdfObject. It's an in-RAM operation + * only, no output is done. + */ +int PDF_PLOTTER::allocPdfObject() +{ + xrefTable.push_back( 0 ); + return xrefTable.size() - 1; +} + + +/** + * Open a new PDF object and returns the handle if the parameter is -1. + * Otherwise fill in the xref entry for the passed object + */ +int PDF_PLOTTER::startPdfObject(int handle) +{ + wxASSERT( outputFile ); + wxASSERT( !workFile ); + + if( handle < 0) + handle = allocPdfObject(); + + xrefTable[handle] = ftell( outputFile ); + fprintf( outputFile, "%d 0 obj\n", handle ); + return handle; +} + + +/** + * Close the current PDF object + */ +void PDF_PLOTTER::closePdfObject() +{ + wxASSERT( outputFile ); + wxASSERT( !workFile ); + fputs( "endobj\n", outputFile ); +} + + +/** + * Starts a PDF stream (for the page). Returns the object handle opened + * Pass -1 (default) for a fresh object. Especially from PDF 1.5 streams + * can contain a lot of things, but for the moment we only handle page + * content. + */ +int PDF_PLOTTER::startPdfStream(int handle) +{ + wxASSERT( outputFile ); + wxASSERT( !workFile ); + handle = startPdfObject( handle ); + + // This is guaranteed to be handle+1 but needs to be allocated since + // you could allocate more object during stream preparation + streamLengthHandle = allocPdfObject(); + fprintf( outputFile, + "<< /Length %d 0 R /Filter /FlateDecode >>\n" // Length is deferred + "stream\n", handle + 1 ); + + // Open a temporary file to accumulate the stream + workFilename = filename + wxT(".tmp"); + workFile = wxFopen( workFilename, wxT( "w+b" )); + wxASSERT( workFile ); + return handle; +} + + +/** + * Finish the current PDF stream (writes the deferred length, too) + */ +void PDF_PLOTTER::closePdfStream() +{ + wxASSERT( workFile ); + + long stream_len = ftell( workFile ); + + if( stream_len < 0 ) + { + wxASSERT( false ); + return; + } + + // Rewind the file, read in the page stream and DEFLATE it + fseek( workFile, 0, SEEK_SET ); + unsigned char *inbuf = new unsigned char[stream_len]; + + int rc = fread( inbuf, 1, stream_len, workFile ); + wxASSERT( rc == stream_len ); + (void) rc; + + // We are done with the temporary file, junk it + fclose( workFile ); + workFile = 0; + ::wxRemoveFile( workFilename ); + + // NULL means memos owns the memory, but provide a hint on optimum size needed. + wxMemoryOutputStream memos( NULL, std::max( 2000l, stream_len ) ) ; + + { + /* Somewhat standard parameters to compress in DEFLATE. The PDF spec is + * misleading, it says it wants a DEFLATE stream but it really want a ZLIB + * stream! (a DEFLATE stream would be generated with -15 instead of 15) + * rc = deflateInit2( &zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, 15, + * 8, Z_DEFAULT_STRATEGY ); + */ + + wxZlibOutputStream zos( memos, wxZ_BEST_COMPRESSION, wxZLIB_ZLIB ); + + zos.Write( inbuf, stream_len ); + + delete[] inbuf; + + } // flush the zip stream using zos destructor + + wxStreamBuffer* sb = memos.GetOutputStreamBuffer(); + + unsigned out_count = sb->Tell(); + + fwrite( sb->GetBufferStart(), 1, out_count, outputFile ); + + fputs( "endstream\n", outputFile ); + closePdfObject(); + + // Writing the deferred length as an indirect object + startPdfObject( streamLengthHandle ); + fprintf( outputFile, "%u\n", out_count ); + closePdfObject(); +} + +/** + * Starts a new page in the PDF document + */ +void PDF_PLOTTER::StartPage() +{ + wxASSERT( outputFile ); + wxASSERT( !workFile ); + + // Compute the paper size in IUs + paperSize = pageInfo.GetSizeMils(); + paperSize.x *= 10.0 / iuPerDeviceUnit; + paperSize.y *= 10.0 / iuPerDeviceUnit; + + // Open the content stream; the page object will go later + pageStreamHandle = startPdfStream(); + + /* Now, until ClosePage *everything* must be wrote in workFile, to be + compressed later in closePdfStream */ + + // Default graphic settings (coordinate system, default color and line style) + fprintf( workFile, + "%g 0 0 %g 0 0 cm 1 J 1 j 0 0 0 rg 0 0 0 RG %g w\n", + 0.0072 * plotScaleAdjX, 0.0072 * plotScaleAdjY, + userToDeviceSize( defaultPenWidth ) ); +} + +/** + * Close the current page in the PDF document (and emit its compressed stream) + */ +void PDF_PLOTTER::ClosePage() +{ + wxASSERT( workFile ); + + // Close the page stream (and compress it) + closePdfStream(); + + // Emit the page object and put it in the page list for later + pageHandles.push_back( startPdfObject() ); + + /* Page size is in 1/72 of inch (default user space units) + Works like the bbox in postscript but there is no need for + swapping the sizes, since PDF doesn't require a portrait page. + We use the MediaBox but PDF has lots of other less used boxes + to use */ + + const double BIGPTsPERMIL = 0.072; + wxSize psPaperSize = pageInfo.GetSizeMils(); + + fprintf( outputFile, + "<<\n" + "/Type /Page\n" + "/Parent %d 0 R\n" + "/Resources <<\n" + " /ProcSet [/PDF /Text /ImageC /ImageB]\n" + " /Font %d 0 R >>\n" + "/MediaBox [0 0 %d %d]\n" + "/Contents %d 0 R\n" + ">>\n", + pageTreeHandle, + fontResDictHandle, + int( ceil( psPaperSize.x * BIGPTsPERMIL ) ), + int( ceil( psPaperSize.y * BIGPTsPERMIL ) ), + pageStreamHandle ); + closePdfObject(); + + // Mark the page stream as idle + pageStreamHandle = 0; +} + +/** + * The PDF engine supports multiple pages; the first one is opened + * 'for free' the following are to be closed and reopened. Between + * each page parameters can be set + */ +bool PDF_PLOTTER::StartPlot() +{ + wxASSERT( outputFile ); + + // First things first: the customary null object + xrefTable.clear(); + xrefTable.push_back( 0 ); + + /* The header (that's easy!). The second line is binary junk required + to make the file binary from the beginning (the important thing is + that they must have the bit 7 set) */ + fputs( "%PDF-1.5\n%\200\201\202\203\n", outputFile ); + + /* Allocate an entry for the page tree root, it will go in every page + parent entry */ + pageTreeHandle = allocPdfObject(); + + /* In the same way, the font resource dictionary is used by every page + (it *could* be inherited via the Pages tree */ + fontResDictHandle = allocPdfObject(); + + /* Now, the PDF is read from the end, (more or less)... so we start + with the page stream for page 1. Other more important stuff is written + at the end */ + StartPage(); + return true; +} + + +bool PDF_PLOTTER::EndPlot() +{ + wxASSERT( outputFile ); + + // Close the current page (often the only one) + ClosePage(); + + /* We need to declare the resources we're using (fonts in particular) + The useful standard one is the Helvetica family. Adding external fonts + is *very* involved! */ + struct { + const char *psname; + const char *rsname; + int font_handle; + } fontdefs[4] = { + { "/Helvetica", "/KicadFont", 0 }, + { "/Helvetica-Oblique", "/KicadFontI", 0 }, + { "/Helvetica-Bold", "/KicadFontB", 0 }, + { "/Helvetica-BoldOblique", "/KicadFontBI", 0 } + }; + + /* Declare the font resources. Since they're builtin fonts, no descriptors (yay!) + We'll need metrics anyway to do any aligment (these are in the shared with + the postscript engine) */ + for( int i = 0; i < 4; i++ ) + { + fontdefs[i].font_handle = startPdfObject(); + fprintf( outputFile, + "<< /BaseFont %s\n" + " /Type /Font\n" + " /Subtype /Type1\n" + + /* Adobe is so Mac-based that the nearest thing to Latin1 is + the Windows ANSI encoding! */ + " /Encoding /WinAnsiEncoding\n" + ">>\n", + fontdefs[i].psname ); + closePdfObject(); + } + + // Named font dictionary (was allocated, now we emit it) + startPdfObject( fontResDictHandle ); + fputs( "<<\n", outputFile ); + for( int i = 0; i < 4; i++ ) + { + fprintf( outputFile, " %s %d 0 R\n", + fontdefs[i].rsname, fontdefs[i].font_handle ); + } + fputs( ">>\n", outputFile ); + closePdfObject(); + + /* The page tree: it's a B-tree but luckily we only have few pages! + So we use just an array... The handle was allocated at the beginning, + now we instantiate the corresponding object */ + startPdfObject( pageTreeHandle ); + fputs( "<<\n" + "/Type /Pages\n" + "/Kids [\n", outputFile ); + + for( unsigned i = 0; i < pageHandles.size(); i++ ) + fprintf( outputFile, "%d 0 R\n", pageHandles[i] ); + + fprintf( outputFile, + "]\n" + "/Count %ld\n" + ">>\n", (long) pageHandles.size() ); + closePdfObject(); + + + // The info dictionary + int infoDictHandle = startPdfObject(); + char date_buf[250]; + time_t ltime = time( NULL ); + strftime( date_buf, 250, "D:%Y%m%d%H%M%S", + localtime( <ime ) ); + + if( title.IsEmpty() ) + { + // Windows uses '\' and other platforms ue '/' as sepatator + title = filename.AfterLast('\\'); + title = title.AfterLast('/'); + } + + fprintf( outputFile, + "<<\n" + "/Producer (KiCAD PDF)\n" + "/CreationDate (%s)\n" + "/Creator (%s)\n" + "/Title (%s)\n" + "/Trapped false\n", + date_buf, + TO_UTF8( creator ), + TO_UTF8( title ) ); + + fputs( ">>\n", outputFile ); + closePdfObject(); + + // The catalog, at last + int catalogHandle = startPdfObject(); + fprintf( outputFile, + "<<\n" + "/Type /Catalog\n" + "/Pages %d 0 R\n" + "/Version /1.5\n" + "/PageMode /UseNone\n" + "/PageLayout /SinglePage\n" + ">>\n", pageTreeHandle ); + closePdfObject(); + + /* Emit the xref table (format is crucial to the byte, each entry must + be 20 bytes long, and object zero must be done in that way). Also + the offset must be kept along for the trailer */ + long xref_start = ftell( outputFile ); + fprintf( outputFile, + "xref\n" + "0 %ld\n" + "0000000000 65535 f \n", (long) xrefTable.size() ); + for( unsigned i = 1; i < xrefTable.size(); i++ ) + { + fprintf( outputFile, "%010ld 00000 n \n", xrefTable[i] ); + } + + // Done the xref, go for the trailer + fprintf( outputFile, + "trailer\n" + "<< /Size %lu /Root %d 0 R /Info %d 0 R >>\n" + "startxref\n" + "%ld\n" // The offset we saved before + "%%%%EOF\n", + (unsigned long) xrefTable.size(), catalogHandle, infoDictHandle, xref_start ); + + fclose( outputFile ); + outputFile = NULL; + + return true; +} + +void PDF_PLOTTER::Text( const wxPoint& aPos, + const COLOR4D aColor, + const wxString& aText, + double aOrient, + const wxSize& aSize, + enum EDA_TEXT_HJUSTIFY_T aH_justify, + enum EDA_TEXT_VJUSTIFY_T aV_justify, + int aWidth, + bool aItalic, + bool aBold, + bool aMultilineAllowed, + void* aData ) +{ + // PDF files do not like 0 sized texts which create broken files. + if( aSize.x == 0 || aSize.y == 0 ) + return; + + // Fix me: see how to use PDF text mode for multiline texts + if( aMultilineAllowed && !aText.Contains( wxT( "\n" ) ) ) + aMultilineAllowed = false; // the text has only one line. + + // Emit native PDF text (if requested) + // Currently: is not supported, because only our stroke font is alloxed: disable it + // However, shadowed texts (searchable texts) works reasonably well because + // pixel accurate precision is not requested, so we add searchable texts + // behind our stroked font texts + bool use_native_font = false; + // render_mode 0 shows the text, render_mode 3 is invisible + int render_mode = use_native_font ? 0 : 3; + + const char *fontname = aItalic ? (aBold ? "/KicadFontBI" : "/KicadFontI") + : (aBold ? "/KicadFontB" : "/KicadFont"); + + // Compute the copious tranformation parameters of the Curent Transform Matrix + double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f; + double wideningFactor, heightFactor; + + computeTextParameters( aPos, aText, aOrient, aSize, m_plotMirror, aH_justify, + aV_justify, aWidth, aItalic, aBold, + &wideningFactor, &ctm_a, &ctm_b, &ctm_c, + &ctm_d, &ctm_e, &ctm_f, &heightFactor ); + + SetColor( aColor ); + SetCurrentLineWidth( aWidth, aData ); + + /* We use the full CTM instead of the text matrix because the same + coordinate system will be used for the overlining. Also the %f + for the trig part of the matrix to avoid %g going in exponential + format (which is not supported) + render_mode 0 shows the text, render_mode 3 is invisible */ + fprintf( workFile, "q %f %f %f %f %g %g cm BT %s %g Tf %d Tr %g Tz ", + ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f, + fontname, heightFactor, render_mode, + wideningFactor * 100 ); + + // The text must be escaped correctly + fputsPostscriptString( workFile, aText ); + fputs( " Tj ET\n", workFile ); + + // We are in text coordinates, plot the overbars, if we're not doing phantom text + if( use_native_font ) + { + std::vector pos_pairs; + postscriptOverlinePositions( aText, aSize.x, aItalic, aBold, &pos_pairs ); + int overbar_y = KiROUND( aSize.y * 1.1 ); + for( unsigned i = 0; i < pos_pairs.size(); i += 2) + { + /* This is a nontrivial situation: we are *not* in the user + coordinate system, so the userToDeviceCoordinates function + can't be used! Strange as it may seem, the userToDeviceSize + is the right function to use here... */ + DPOINT dev_from = userToDeviceSize( wxSize( pos_pairs[i], overbar_y ) ); + DPOINT dev_to = userToDeviceSize( wxSize( pos_pairs[i + 1], overbar_y ) ); + fprintf( workFile, "%g %g m %g %g l ", + dev_from.x, dev_from.y, dev_to.x, dev_to.y ); + } + } + + // Stroke and restore the CTM + fputs( "S Q\n", workFile ); + + // Plot the stroked text (if requested) + if( !use_native_font ) + { + PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, + aWidth, aItalic, aBold, aMultilineAllowed ); + } +} + diff --git a/common/plotters/common_plotPS_functions.cpp b/common/plotters/common_plotPS_functions.cpp new file mode 100644 index 0000000000..8288b013be --- /dev/null +++ b/common/plotters/common_plotPS_functions.cpp @@ -0,0 +1,1177 @@ +/* + * 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 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 common_plotPS_functions.cpp + * @brief Kicad: Common plot Postscript Routines + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Forward declaration of the font width metrics + (yes extern! this is the way to forward declare variables */ +extern const double hv_widths[256]; +extern const double hvb_widths[256]; +extern const double hvo_widths[256]; +extern const double hvbo_widths[256]; + +const double PSLIKE_PLOTTER::postscriptTextAscent = 0.718; + + +// Common routines for Postscript-like plotting engines + +void PSLIKE_PLOTTER::SetDefaultLineWidth( int width ) +{ + defaultPenWidth = width; + currentPenWidth = -1; +} + + +void PSLIKE_PLOTTER::SetColor( COLOR4D color ) +{ + if( colorMode ) + { + if( negativeMode ) + emitSetRGBColor( 1 - color.r, 1 - color.g, 1 - color.b ); + else + emitSetRGBColor( color.r, color.g, color.b ); + } + else + { + /* B/W Mode - Use BLACK or WHITE for all items + * note the 2 colors are used in B&W mode, mainly by Pcbnew to draw + * holes in white on pads in black + */ + double k = 1; // White + if( color != COLOR4D::WHITE ) + k = 0; + if( negativeMode ) + emitSetRGBColor( 1 - k, 1 - k, 1 - k ); + else + emitSetRGBColor( k, k, k ); + } +} + + +void PSLIKE_PLOTTER::FlashPadOval( const wxPoint& aPadPos, const wxSize& aSize, + double aPadOrient, EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + wxASSERT( outputFile ); + int x0, y0, x1, y1, delta; + wxSize size( aSize ); + + // The pad is reduced to an oval by dy > dx + if( size.x > size.y ) + { + std::swap( size.x, size.y ); + aPadOrient = AddAngles( aPadOrient, 900 ); + } + + delta = size.y - size.x; + x0 = 0; + y0 = -delta / 2; + x1 = 0; + y1 = delta / 2; + RotatePoint( &x0, &y0, aPadOrient ); + RotatePoint( &x1, &y1, aPadOrient ); + + if( aTraceMode == FILLED ) + ThickSegment( wxPoint( aPadPos.x + x0, aPadPos.y + y0 ), + wxPoint( aPadPos.x + x1, aPadPos.y + y1 ), size.x, aTraceMode, NULL ); + else + sketchOval( aPadPos, size, aPadOrient, -1 ); +} + + +void PSLIKE_PLOTTER::FlashPadCircle( const wxPoint& aPadPos, int aDiameter, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + if( aTraceMode == FILLED ) + Circle( aPadPos, aDiameter, FILLED_SHAPE, 0 ); + else // Plot a ring: + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + int linewidth = GetCurrentLineWidth(); + + // avoid aDiameter <= 1 ) + if( linewidth > aDiameter-2 ) + linewidth = aDiameter-2; + + Circle( aPadPos, aDiameter - linewidth, NO_FILL, linewidth ); + } + + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); +} + + +void PSLIKE_PLOTTER::FlashPadRect( const wxPoint& aPadPos, const wxSize& aSize, + double aPadOrient, EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + static std::vector< wxPoint > cornerList; + wxSize size( aSize ); + cornerList.clear(); + + if( aTraceMode == FILLED ) + SetCurrentLineWidth( 0 ); + else + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + + size.x -= GetCurrentLineWidth(); + size.y -= GetCurrentLineWidth(); + + if( size.x < 1 ) + size.x = 1; + + if( size.y < 1 ) + size.y = 1; + + int dx = size.x / 2; + int dy = size.y / 2; + + wxPoint corner; + corner.x = aPadPos.x - dx; + corner.y = aPadPos.y + dy; + cornerList.push_back( corner ); + corner.x = aPadPos.x - dx; + corner.y = aPadPos.y - dy; + cornerList.push_back( corner ); + corner.x = aPadPos.x + dx; + corner.y = aPadPos.y - dy; + cornerList.push_back( corner ); + corner.x = aPadPos.x + dx; + corner.y = aPadPos.y + dy, + cornerList.push_back( corner ); + + for( unsigned ii = 0; ii < cornerList.size(); ii++ ) + { + RotatePoint( &cornerList[ii], aPadPos, aPadOrient ); + } + + cornerList.push_back( cornerList[0] ); + + PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, + GetCurrentLineWidth() ); +} + +void PSLIKE_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize, + int aCornerRadius, double aOrient, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + wxSize size( aSize ); + + if( aTraceMode == FILLED ) + SetCurrentLineWidth( 0 ); + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + size.x -= GetCurrentLineWidth(); + size.y -= GetCurrentLineWidth(); + aCornerRadius -= GetCurrentLineWidth()/2; + } + + + SHAPE_POLY_SET outline; + const int segmentToCircleCount = 64; + TransformRoundRectToPolygon( outline, aPadPos, size, aOrient, + aCornerRadius, segmentToCircleCount ); + + std::vector< wxPoint > cornerList; + cornerList.reserve( segmentToCircleCount + 5 ); + // TransformRoundRectToPolygon creates only one convex polygon + SHAPE_LINE_CHAIN& poly = outline.Outline( 0 ); + + for( int ii = 0; ii < poly.PointCount(); ++ii ) + cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + // Close polygon + cornerList.push_back( cornerList[0] ); + + PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, + GetCurrentLineWidth() ); +} + +void PSLIKE_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize, + SHAPE_POLY_SET* aPolygons, + EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + wxSize size( aSize ); + + if( aTraceMode == FILLED ) + SetCurrentLineWidth( 0 ); + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + size.x -= GetCurrentLineWidth(); + size.y -= GetCurrentLineWidth(); + } + + + std::vector< wxPoint > cornerList; + + for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt ) + { + SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt ); + cornerList.clear(); + + for( int ii = 0; ii < poly.PointCount(); ++ii ) + cornerList.push_back( wxPoint( poly.Point( ii ).x, poly.Point( ii ).y ) ); + + // Close polygon + cornerList.push_back( cornerList[0] ); + + PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, + GetCurrentLineWidth() ); + } +} + +void PSLIKE_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint *aCorners, + double aPadOrient, EDA_DRAW_MODE_T aTraceMode, void* aData ) +{ + static std::vector< wxPoint > cornerList; + cornerList.clear(); + + for( int ii = 0; ii < 4; ii++ ) + cornerList.push_back( aCorners[ii] ); + + if( aTraceMode == FILLED ) + { + SetCurrentLineWidth( 0 ); + } + else + { + SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH ); + int w = GetCurrentLineWidth(); + // offset polygon by w + // coord[0] is assumed the lower left + // coord[1] is assumed the upper left + // coord[2] is assumed the upper right + // coord[3] is assumed the lower right + + /* Trace the outline. */ + cornerList[0].x += w; + cornerList[0].y -= w; + cornerList[1].x += w; + cornerList[1].y += w; + cornerList[2].x -= w; + cornerList[2].y += w; + cornerList[3].x -= w; + cornerList[3].y -= w; + } + + for( int ii = 0; ii < 4; ii++ ) + { + RotatePoint( &cornerList[ii], aPadOrient ); + cornerList[ii] += aPadPos; + } + + cornerList.push_back( cornerList[0] ); + PlotPoly( cornerList, ( aTraceMode == FILLED ) ? FILLED_SHAPE : NO_FILL, + GetCurrentLineWidth() ); +} + + +/** + * Write on a stream a string escaped for postscript/PDF + */ +void PSLIKE_PLOTTER::fputsPostscriptString(FILE *fout, const wxString& txt) +{ + putc( '(', fout ); + for( unsigned i = 0; i < txt.length(); i++ ) + { + // Lazyness made me use stdio buffering yet another time... + wchar_t ch = txt[i]; + + if( ch < 256 ) + { + switch (ch) + { + // The ~ shouldn't reach the outside + case '~': + break; + // These characters must be escaped + case '(': + case ')': + case '\\': + putc( '\\', fout ); + + // FALLTHRU + default: + putc( ch, fout ); + break; + } + } + } + + putc( ')', fout ); +} + + +/** + * Sister function for the GraphicTextWidth in drawtxt.cpp + * Does the same processing (i.e. calculates a text string width) but + * using postscript metrics for the Helvetica font (optionally used for + * PS and PDF plotting + */ +int PSLIKE_PLOTTER::returnPostscriptTextWidth( const wxString& aText, int aXSize, + bool aItalic, bool aBold ) +{ + const double *width_table = aBold ? ( aItalic ? hvbo_widths : hvb_widths ) + : ( aItalic ? hvo_widths : hv_widths ); + double tally = 0; + + for( unsigned i = 0; i < aText.length(); i++ ) + { + wchar_t AsciiCode = aText[i]; + // Skip the negation marks and untabled points + if( AsciiCode != '~' && AsciiCode < 256 ) + { + tally += width_table[AsciiCode]; + } + } + + // Widths are proportional to height, but height is enlarged by a + // scaling factor + return KiROUND( aXSize * tally / postscriptTextAscent ); +} + + +/** + * Computes the x coordinates for the overlining in a string of text. + * Fills the passed vector with couples of (start, stop) values to be + * used in the text coordinate system (use computeTextParameters to + * obtain the parameters to estabilish such a system) + */ +void PSLIKE_PLOTTER::postscriptOverlinePositions( const wxString& aText, int aXSize, + bool aItalic, bool aBold, + std::vector *pos_pairs ) +{ + /* XXX This function is *too* similar to returnPostscriptTextWidth. + Consider merging them... */ + const double *width_table = aBold ? ( aItalic ? hvbo_widths : hvb_widths ) + : ( aItalic ? hvo_widths : hv_widths ); + double tally = 0; + + for( unsigned i = 0; i < aText.length(); i++ ) + { + wchar_t AsciiCode = aText[i]; + // Skip the negation marks and untabled points + if( AsciiCode != '~' && AsciiCode < 256 ) + { + tally += width_table[AsciiCode]; + } + else + { + if( AsciiCode == '~' ) + pos_pairs->push_back( KiROUND( aXSize * tally / postscriptTextAscent ) ); + } + } + + // Special rule: we have to complete the last bar if the ~ aren't matched + if( pos_pairs->size() % 2 == 1 ) + pos_pairs->push_back( KiROUND( aXSize * tally / postscriptTextAscent ) ); +} + +void PS_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + wxASSERT( !outputFile ); + m_plotMirror = aMirror; + plotOffset = aOffset; + plotScale = aScale; + m_IUsPerDecimil = aIusPerDecimil; + iuPerDeviceUnit = 1.0 / aIusPerDecimil; + /* Compute the paper size in IUs */ + paperSize = pageInfo.GetSizeMils(); + paperSize.x *= 10.0 * aIusPerDecimil; + paperSize.y *= 10.0 * aIusPerDecimil; + SetDefaultLineWidth( 100 * aIusPerDecimil ); // arbitrary default +} + + +/** This is the core for postscript/PDF text alignment + * It computes the transformation matrix to generate a user space + * system aligned with the text. Even the PS uses the concat + * operator to simplify PDF generation (concat is everything PDF + * has to modify the CTM. Lots of parameters, both in and out. + */ +void PSLIKE_PLOTTER::computeTextParameters( const wxPoint& aPos, + const wxString& aText, + int aOrient, + const wxSize& aSize, + bool aMirror, + enum EDA_TEXT_HJUSTIFY_T aH_justify, + enum EDA_TEXT_VJUSTIFY_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 ) +{ + // Compute the starting position (compensated for alignment) + wxPoint start_pos = aPos; + + // This is an approximation of the text bounds (in IUs) + int tw = returnPostscriptTextWidth( aText, aSize.x, aItalic, aWidth ); + int th = aSize.y; + int dx, dy; + + switch( aH_justify ) + { + case GR_TEXT_HJUSTIFY_CENTER: + dx = -tw / 2; + break; + + case GR_TEXT_HJUSTIFY_RIGHT: + dx = -tw; + break; + + case GR_TEXT_HJUSTIFY_LEFT: + dx = 0; + break; + } + + switch( aV_justify ) + { + case GR_TEXT_VJUSTIFY_CENTER: + dy = th / 2; + break; + + case GR_TEXT_VJUSTIFY_TOP: + dy = th; + break; + + case GR_TEXT_VJUSTIFY_BOTTOM: + dy = 0; + break; + } + + RotatePoint( &dx, &dy, aOrient ); + RotatePoint( &tw, &th, aOrient ); + start_pos.x += dx; + start_pos.y += dy; + DPOINT pos_dev = userToDeviceCoordinates( start_pos ); + DPOINT sz_dev = userToDeviceSize( aSize ); + + // Now returns the final values... the widening factor + *wideningFactor = sz_dev.x / sz_dev.y; + + // Mirrored texts must be plotted as mirrored! + if( m_plotMirror ) + { + *wideningFactor = -*wideningFactor; + aOrient = -aOrient; + } + + // The CTM transformation matrix + double alpha = DECIDEG2RAD( aOrient ); + double sinalpha = sin( alpha ); + double cosalpha = cos( alpha ); + + *ctm_a = cosalpha; + *ctm_b = sinalpha; + *ctm_c = -sinalpha; + *ctm_d = cosalpha; + *ctm_e = pos_dev.x; + *ctm_f = pos_dev.y; + + // This is because the letters are less than 1 unit high + *heightFactor = sz_dev.y / postscriptTextAscent; +} + + +/* Set the current line width (in IUs) for the next plot + */ +void PS_PLOTTER::SetCurrentLineWidth( int width, void* aData ) +{ + wxASSERT( outputFile ); + int pen_width; + + if( width >= 0 ) + pen_width = width; + else + pen_width = defaultPenWidth; + + if( pen_width != GetCurrentLineWidth() ) + fprintf( outputFile, "%g setlinewidth\n", userToDeviceSize( pen_width ) ); + + currentPenWidth = pen_width; +} + + +void PS_PLOTTER::emitSetRGBColor( double r, double g, double b ) +{ + wxASSERT( outputFile ); + + // XXX why %.3g ? shouldn't %g suffice? who cares... + fprintf( outputFile, "%.3g %.3g %.3g setrgbcolor\n", r, g, b ); +} + + +/** + * Postscript supports dashed lines + */ +void PS_PLOTTER::SetDash( int dashed ) +{ + switch( dashed ) + { + case PLOTDASHTYPE_DASH: + fprintf( outputFile, "[%d %d] 0 setdash\n", + (int) GetDashMarkLenIU(), (int) GetDashGapLenIU() ); + break; + case PLOTDASHTYPE_DOT: + fprintf( outputFile, "[%d %d] 0 setdash\n", + (int) GetDotMarkLenIU(), (int) GetDashGapLenIU() ); + break; + case PLOTDASHTYPE_DASHDOT: + fprintf( outputFile, "[%d %d %d %d] 0 setdash\n", + (int) GetDashMarkLenIU(), (int) GetDashGapLenIU(), + (int) GetDotMarkLenIU(), (int) GetDashGapLenIU() ); + break; + default: + fputs( "solidline\n", outputFile ); + } +} + + +void PS_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + DPOINT p1_dev = userToDeviceCoordinates( p1 ); + DPOINT p2_dev = userToDeviceCoordinates( p2 ); + + SetCurrentLineWidth( width ); + fprintf( outputFile, "%g %g %g %g rect%d\n", p1_dev.x, p1_dev.y, + p2_dev.x - p1_dev.x, p2_dev.y - p1_dev.y, fill ); +} + + +void PS_PLOTTER::Circle( const wxPoint& pos, int diametre, FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + DPOINT pos_dev = userToDeviceCoordinates( pos ); + double radius = userToDeviceSize( diametre / 2.0 ); + + SetCurrentLineWidth( width ); + fprintf( outputFile, "%g %g %g cir%d\n", pos_dev.x, pos_dev.y, radius, fill ); +} + + +void PS_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, + int radius, FILL_T fill, int width ) +{ + wxASSERT( outputFile ); + if( radius <= 0 ) + return; + + if( StAngle > EndAngle ) + std::swap( StAngle, EndAngle ); + + SetCurrentLineWidth( width ); + + // Calculate start point. + DPOINT centre_dev = userToDeviceCoordinates( centre ); + double radius_dev = userToDeviceSize( radius ); + + if( m_plotMirror ) + { + if( m_mirrorIsHorizontal ) + { + StAngle = 1800.0 -StAngle; + EndAngle = 1800.0 -EndAngle; + std::swap( StAngle, EndAngle ); + } + else + { + StAngle = -StAngle; + EndAngle = -EndAngle; + } + } + + fprintf( outputFile, "%g %g %g %g %g arc%d\n", centre_dev.x, centre_dev.y, + radius_dev, StAngle / 10.0, EndAngle / 10.0, fill ); +} + + +void PS_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList, + FILL_T aFill, int aWidth, void * aData ) +{ + if( aCornerList.size() <= 1 ) + return; + + SetCurrentLineWidth( aWidth ); + + DPOINT pos = userToDeviceCoordinates( aCornerList[0] ); + fprintf( outputFile, "newpath\n%g %g moveto\n", pos.x, pos.y ); + + for( unsigned ii = 1; ii < aCornerList.size(); ii++ ) + { + pos = userToDeviceCoordinates( aCornerList[ii] ); + fprintf( outputFile, "%g %g lineto\n", pos.x, pos.y ); + } + + // Close/(fill) the path + fprintf( outputFile, "poly%d\n", aFill ); +} + + +/** + * Postscript-likes at the moment are the only plot engines supporting bitmaps... + */ +void PS_PLOTTER::PlotImage( const wxImage & aImage, const wxPoint& aPos, + double aScaleFactor ) +{ + wxSize pix_size; // size of the bitmap in pixels + pix_size.x = aImage.GetWidth(); + pix_size.y = aImage.GetHeight(); + DPOINT drawsize( aScaleFactor * pix_size.x, + aScaleFactor * pix_size.y ); // requested size of image + + // calculate the bottom left corner position of bitmap + wxPoint start = aPos; + start.x -= drawsize.x / 2; // left + start.y += drawsize.y / 2; // bottom (Y axis reversed) + + // calculate the top right corner position of bitmap + wxPoint end; + end.x = start.x + drawsize.x; + end.y = start.y - drawsize.y; + + fprintf( outputFile, "/origstate save def\n" ); + fprintf( outputFile, "/pix %d string def\n", pix_size.x ); + + // Locate lower-left corner of image + DPOINT start_dev = userToDeviceCoordinates( start ); + fprintf( outputFile, "%g %g translate\n", start_dev.x, start_dev.y ); + // Map image size to device + DPOINT end_dev = userToDeviceCoordinates( end ); + fprintf( outputFile, "%g %g scale\n", + std::abs(end_dev.x - start_dev.x), std::abs(end_dev.y - start_dev.y)); + + // Dimensions of source image (in pixels + fprintf( outputFile, "%d %d 8", pix_size.x, pix_size.y ); + // Map unit square to source + fprintf( outputFile, " [%d 0 0 %d 0 %d]\n", pix_size.x, -pix_size.y , pix_size.y); + // include image data in ps file + fprintf( outputFile, "{currentfile pix readhexstring pop}\n" ); + + if( colorMode ) + fputs( "false 3 colorimage\n", outputFile ); + else + fputs( "image\n", outputFile ); + // Single data source, 3 colors, Output RGB data (hexadecimal) + // (or the same downscaled to gray) + int jj = 0; + + for( int yy = 0; yy < pix_size.y; yy ++ ) + { + for( int xx = 0; xx < pix_size.x; xx++, jj++ ) + { + if( jj >= 16 ) + { + jj = 0; + fprintf( outputFile, "\n"); + } + + int red, green, blue; + red = aImage.GetRed( xx, yy) & 0xFF; + green = aImage.GetGreen( xx, yy) & 0xFF; + blue = aImage.GetBlue( xx, yy) & 0xFF; + + if( colorMode ) + fprintf( outputFile, "%2.2X%2.2X%2.2X", red, green, blue ); + else + fprintf( outputFile, "%2.2X", (red + green + blue) / 3 ); + } + } + + fprintf( outputFile, "\n"); + fprintf( outputFile, "origstate restore\n" ); +} + + +void PS_PLOTTER::PenTo( const wxPoint& pos, char plume ) +{ + wxASSERT( outputFile ); + + if( plume == 'Z' ) + { + if( penState != 'Z' ) + { + fputs( "stroke\n", outputFile ); + penState = 'Z'; + penLastpos.x = -1; + penLastpos.y = -1; + } + + return; + } + + if( penState == 'Z' ) + { + fputs( "newpath\n", outputFile ); + } + + if( penState != plume || pos != penLastpos ) + { + DPOINT pos_dev = userToDeviceCoordinates( pos ); + fprintf( outputFile, "%g %g %sto\n", + pos_dev.x, pos_dev.y, + ( plume=='D' ) ? "line" : "move" ); + } + + penState = plume; + penLastpos = pos; +} + + +/** + * The code within this function (and the CloseFilePS function) + * creates postscript files whose contents comply with Adobe's + * Document Structuring Convention, as documented by assorted + * details described within the following URLs: + * + * http://en.wikipedia.org/wiki/Document_Structuring_Conventions + * http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf + * + * + * BBox is the boundary box (position and size of the "client rectangle" + * for drawings (page - margins) in mils (0.001 inch) + */ +bool PS_PLOTTER::StartPlot() +{ + wxASSERT( outputFile ); + wxString msg; + + static const char* PSMacro[] = + { + "%%BeginProlog\n", + "/line { newpath moveto lineto stroke } bind def\n", + "/cir0 { newpath 0 360 arc stroke } bind def\n", + "/cir1 { newpath 0 360 arc gsave fill grestore stroke } bind def\n", + "/cir2 { newpath 0 360 arc gsave fill grestore stroke } bind def\n", + "/arc0 { newpath arc stroke } bind def\n", + "/arc1 { newpath 4 index 4 index moveto arc closepath gsave fill\n", + " grestore stroke } bind def\n", + "/arc2 { newpath 4 index 4 index moveto arc closepath gsave fill\n", + " grestore stroke } bind def\n", + "/poly0 { stroke } bind def\n", + "/poly1 { closepath gsave fill grestore stroke } bind def\n", + "/poly2 { closepath gsave fill grestore stroke } bind def\n", + "/rect0 { rectstroke } bind def\n", + "/rect1 { rectfill } bind def\n", + "/rect2 { rectfill } bind def\n", + "/linemode0 { 0 setlinecap 0 setlinejoin 0 setlinewidth } bind def\n", + "/linemode1 { 1 setlinecap 1 setlinejoin } bind def\n", + "/dashedline { [200] 100 setdash } bind def\n", + "/solidline { [] 0 setdash } bind def\n", + + // This is for 'hidden' text (search anchors for PDF) + "/phantomshow { moveto\n", + " /KicadFont findfont 0.000001 scalefont setfont\n", + " show } bind def\n", + + // This is for regular postscript text + "/textshow { gsave\n", + " findfont exch scalefont setfont concat 1 scale 0 0 moveto show\n", + " } bind def\n", + + // Utility for getting Latin1 encoded fonts + "/reencodefont {\n", + " findfont dup length dict begin\n", + " { 1 index /FID ne\n", + " { def }\n", + " { pop pop } ifelse\n", + " } forall\n", + " /Encoding ISOLatin1Encoding def\n", + " currentdict\n", + " end } bind def\n" + + // Remap AdobeStandard fonts to Latin1 + "/KicadFont /Helvetica reencodefont definefont pop\n", + "/KicadFont-Bold /Helvetica-Bold reencodefont definefont pop\n", + "/KicadFont-Oblique /Helvetica-Oblique reencodefont definefont pop\n", + "/KicadFont-BoldOblique /Helvetica-BoldOblique reencodefont definefont pop\n", + "%%EndProlog\n", + NULL + }; + + time_t time1970 = time( NULL ); + + fputs( "%!PS-Adobe-3.0\n", outputFile ); // Print header + + fprintf( outputFile, "%%%%Creator: %s\n", TO_UTF8( creator ) ); + + /* A "newline" character ("\n") is not included in the following string, + because it is provided by the ctime() function. */ + fprintf( outputFile, "%%%%CreationDate: %s", ctime( &time1970 ) ); + fprintf( outputFile, "%%%%Title: %s\n", TO_UTF8( filename ) ); + fprintf( outputFile, "%%%%Pages: 1\n" ); + fprintf( outputFile, "%%%%PageOrder: Ascend\n" ); + + // Print boundary box in 1/72 pixels per inch, box is in mils + const double BIGPTsPERMIL = 0.072; + + /* The coordinates of the lower left corner of the boundary + box need to be "rounded down", but the coordinates of its + upper right corner need to be "rounded up" instead. */ + wxSize psPaperSize = pageInfo.GetSizeMils(); + + if( !pageInfo.IsPortrait() ) + psPaperSize.Set( pageInfo.GetHeightMils(), pageInfo.GetWidthMils() ); + + fprintf( outputFile, "%%%%BoundingBox: 0 0 %d %d\n", + (int) ceil( psPaperSize.x * BIGPTsPERMIL ), + (int) ceil( psPaperSize.y * BIGPTsPERMIL ) ); + + // Specify the size of the sheet and the name associated with that size. + // (If the "User size" option has been selected for the sheet size, + // identify the sheet size as "Custom" (rather than as "User"), but + // otherwise use the name assigned by KiCad for each sheet size.) + // + // (The Document Structuring Convention also supports sheet weight, + // sheet color, and sheet type properties being specified within a + // %%DocumentMedia comment, but they are not being specified here; + // a zero and two null strings are subsequently provided instead.) + // + // (NOTE: m_Size.y is *supposed* to be listed before m_Size.x; + // the order in which they are specified is not wrong!) + // Also note pageSize is given in mils, not in internal units and must be + // converted to internal units. + + if( pageInfo.IsCustom() ) + fprintf( outputFile, "%%%%DocumentMedia: Custom %d %d 0 () ()\n", + KiROUND( psPaperSize.x * BIGPTsPERMIL ), + KiROUND( psPaperSize.y * BIGPTsPERMIL ) ); + + else // a standard paper size + fprintf( outputFile, "%%%%DocumentMedia: %s %d %d 0 () ()\n", + TO_UTF8( pageInfo.GetType() ), + KiROUND( psPaperSize.x * BIGPTsPERMIL ), + KiROUND( psPaperSize.y * BIGPTsPERMIL ) ); + + if( pageInfo.IsPortrait() ) + fprintf( outputFile, "%%%%Orientation: Portrait\n" ); + else + fprintf( outputFile, "%%%%Orientation: Landscape\n" ); + + fprintf( outputFile, "%%%%EndComments\n" ); + + // Now specify various other details. + + for( int ii = 0; PSMacro[ii] != NULL; ii++ ) + { + fputs( PSMacro[ii], outputFile ); + } + + // The following string has been specified here (rather than within + // PSMacro[]) to highlight that it has been provided to ensure that the + // contents of the postscript file comply with the details specified + // within the Document Structuring Convention. + fputs( "%%Page: 1 1\n" + "%%BeginPageSetup\n" + "gsave\n" + "0.0072 0.0072 scale\n" // Configure postscript for decimils coordinates + "linemode1\n", outputFile ); + + + // Rototranslate the coordinate to achieve the landscape layout + if( !pageInfo.IsPortrait() ) + fprintf( outputFile, "%d 0 translate 90 rotate\n", 10 * psPaperSize.x ); + + // Apply the user fine scale adjustments + if( plotScaleAdjX != 1.0 || plotScaleAdjY != 1.0 ) + fprintf( outputFile, "%g %g scale\n", + plotScaleAdjX, plotScaleAdjY ); + + // Set default line width + fprintf( outputFile, "%g setlinewidth\n", userToDeviceSize( defaultPenWidth ) ); + fputs( "%%EndPageSetup\n", outputFile ); + + return true; +} + + +bool PS_PLOTTER::EndPlot() +{ + wxASSERT( outputFile ); + fputs( "showpage\n" + "grestore\n" + "%%EOF\n", outputFile ); + fclose( outputFile ); + outputFile = NULL; + + return true; +} + + + +void PS_PLOTTER::Text( const wxPoint& aPos, + const COLOR4D aColor, + const wxString& aText, + double aOrient, + const wxSize& aSize, + enum EDA_TEXT_HJUSTIFY_T aH_justify, + enum EDA_TEXT_VJUSTIFY_T aV_justify, + int aWidth, + bool aItalic, + bool aBold, + bool aMultilineAllowed, + void* aData ) +{ + SetCurrentLineWidth( aWidth ); + SetColor( aColor ); + + // Fix me: see how to use PS text mode for multiline texts + if( aMultilineAllowed && !aText.Contains( wxT( "\n" ) ) ) + aMultilineAllowed = false; // the text has only one line. + + // Draw the native postscript text (if requested) + // Currently: does not work: disable it + bool use_native = false; // = m_textMode == PLOTTEXTMODE_NATIVE && !aMultilineAllowed; + + if( use_native ) + { + const char *fontname = aItalic ? (aBold ? "/KicadFont-BoldOblique" + : "/KicadFont-Oblique") + : (aBold ? "/KicadFont-Bold" + : "/KicadFont"); + + // Compute the copious tranformation parameters + double ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f; + double wideningFactor, heightFactor; + computeTextParameters( aPos, aText, aOrient, aSize, m_plotMirror, aH_justify, + aV_justify, aWidth, aItalic, aBold, + &wideningFactor, &ctm_a, &ctm_b, &ctm_c, + &ctm_d, &ctm_e, &ctm_f, &heightFactor ); + + + // The text must be escaped correctly, the others are the various + // parameters. The CTM is formatted with %f since sin/cos tends + // to make %g use exponential notation (which is not supported) + fputsPostscriptString( outputFile, aText ); + fprintf( outputFile, " %g [%f %f %f %f %f %f] %g %s textshow\n", + wideningFactor, ctm_a, ctm_b, ctm_c, ctm_d, ctm_e, ctm_f, + heightFactor, fontname ); + + /* The textshow operator retained the coordinate system, we use it + * to plot the overbars. See the PDF sister function for more + * details */ + + std::vector pos_pairs; + postscriptOverlinePositions( aText, aSize.x, aItalic, aBold, &pos_pairs ); + int overbar_y = KiROUND( aSize.y * 1.1 ); + + for( unsigned i = 0; i < pos_pairs.size(); i += 2) + { + DPOINT dev_from = userToDeviceSize( wxSize( pos_pairs[i], overbar_y ) ); + DPOINT dev_to = userToDeviceSize( wxSize( pos_pairs[i + 1], overbar_y ) ); + fprintf( outputFile, "%g %g %g %g line ", + dev_from.x, dev_from.y, dev_to.x, dev_to.y ); + } + + // Restore the CTM + fputs( "grestore\n", outputFile ); + } + + // Draw the hidden postscript text (if requested) + if( m_textMode == PLOTTEXTMODE_PHANTOM ) + { + fputsPostscriptString( outputFile, aText ); + DPOINT pos_dev = userToDeviceCoordinates( aPos ); + fprintf( outputFile, " %g %g phantomshow\n", pos_dev.x, pos_dev.y ); + } + + // Draw the stroked text (if requested) + if( !use_native ) + { + PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, + aWidth, aItalic, aBold, aMultilineAllowed ); + } +} + + +/** + * Character widths for Helvetica + */ +const double hv_widths[256] = { + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.355, 0.556, 0.556, 0.889, 0.667, 0.191, + 0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, + 0.556, 0.556, 0.278, 0.278, 0.584, 0.584, 0.584, 0.556, + 1.015, 0.667, 0.667, 0.722, 0.722, 0.667, 0.611, 0.778, + 0.722, 0.278, 0.500, 0.667, 0.556, 0.833, 0.722, 0.778, + 0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944, + 0.667, 0.667, 0.611, 0.278, 0.278, 0.278, 0.469, 0.556, + 0.333, 0.556, 0.556, 0.500, 0.556, 0.556, 0.278, 0.556, + 0.556, 0.222, 0.222, 0.500, 0.222, 0.833, 0.556, 0.556, + 0.556, 0.556, 0.333, 0.500, 0.278, 0.556, 0.500, 0.722, + 0.500, 0.500, 0.500, 0.334, 0.260, 0.334, 0.584, 0.278, + 0.278, 0.278, 0.222, 0.556, 0.333, 1.000, 0.556, 0.556, + 0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278, + 0.278, 0.222, 0.222, 0.333, 0.333, 0.350, 0.556, 1.000, + 0.333, 1.000, 0.500, 0.333, 0.944, 0.278, 0.278, 0.667, + 0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.260, 0.556, + 0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333, + 0.400, 0.584, 0.333, 0.333, 0.333, 0.556, 0.537, 0.278, + 0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611, + 0.667, 0.667, 0.667, 0.667, 0.667, 0.667, 1.000, 0.722, + 0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278, + 0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584, + 0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.500, + 0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.584, + 0.611, 0.556, 0.556, 0.556, 0.556, 0.500, 0.556, 0.500 +}; + +/** + * Character widths for Helvetica-Bold + */ +const double hvb_widths[256] = { + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.333, 0.474, 0.556, 0.556, 0.889, 0.722, 0.238, + 0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, + 0.556, 0.556, 0.333, 0.333, 0.584, 0.584, 0.584, 0.611, + 0.975, 0.722, 0.722, 0.722, 0.722, 0.667, 0.611, 0.778, + 0.722, 0.278, 0.556, 0.722, 0.611, 0.833, 0.722, 0.778, + 0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944, + 0.667, 0.667, 0.611, 0.333, 0.278, 0.333, 0.584, 0.556, + 0.333, 0.556, 0.611, 0.556, 0.611, 0.556, 0.333, 0.611, + 0.611, 0.278, 0.278, 0.556, 0.278, 0.889, 0.611, 0.611, + 0.611, 0.611, 0.389, 0.556, 0.333, 0.611, 0.556, 0.778, + 0.556, 0.556, 0.500, 0.389, 0.280, 0.389, 0.584, 0.278, + 0.278, 0.278, 0.278, 0.556, 0.500, 1.000, 0.556, 0.556, + 0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.500, 0.500, 0.350, 0.556, 1.000, + 0.333, 1.000, 0.556, 0.333, 0.944, 0.278, 0.278, 0.667, + 0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.280, 0.556, + 0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333, + 0.400, 0.584, 0.333, 0.333, 0.333, 0.611, 0.556, 0.278, + 0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611, + 0.722, 0.722, 0.722, 0.722, 0.722, 0.722, 1.000, 0.722, + 0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278, + 0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584, + 0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.556, + 0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278, + 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.584, + 0.611, 0.611, 0.611, 0.611, 0.611, 0.556, 0.611, 0.556 +}; + +/** + * Character widths for Helvetica-Oblique + */ +const double hvo_widths[256] = { + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.355, 0.556, 0.556, 0.889, 0.667, 0.191, + 0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, + 0.556, 0.556, 0.278, 0.278, 0.584, 0.584, 0.584, 0.556, + 1.015, 0.667, 0.667, 0.722, 0.722, 0.667, 0.611, 0.778, + 0.722, 0.278, 0.500, 0.667, 0.556, 0.833, 0.722, 0.778, + 0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944, + 0.667, 0.667, 0.611, 0.278, 0.278, 0.278, 0.469, 0.556, + 0.333, 0.556, 0.556, 0.500, 0.556, 0.556, 0.278, 0.556, + 0.556, 0.222, 0.222, 0.500, 0.222, 0.833, 0.556, 0.556, + 0.556, 0.556, 0.333, 0.500, 0.278, 0.556, 0.500, 0.722, + 0.500, 0.500, 0.500, 0.334, 0.260, 0.334, 0.584, 0.278, + 0.278, 0.278, 0.222, 0.556, 0.333, 1.000, 0.556, 0.556, + 0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278, + 0.278, 0.222, 0.222, 0.333, 0.333, 0.350, 0.556, 1.000, + 0.333, 1.000, 0.500, 0.333, 0.944, 0.278, 0.278, 0.667, + 0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.260, 0.556, + 0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333, + 0.400, 0.584, 0.333, 0.333, 0.333, 0.556, 0.537, 0.278, + 0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611, + 0.667, 0.667, 0.667, 0.667, 0.667, 0.667, 1.000, 0.722, + 0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278, + 0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584, + 0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.500, + 0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.584, + 0.611, 0.556, 0.556, 0.556, 0.556, 0.500, 0.556, 0.500 +}; + +/** + * Character widths for Helvetica-BoldOblique + */ +const double hvbo_widths[256] = { + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, 0.278, + 0.278, 0.333, 0.474, 0.556, 0.556, 0.889, 0.722, 0.238, + 0.333, 0.333, 0.389, 0.584, 0.278, 0.333, 0.278, 0.278, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, + 0.556, 0.556, 0.333, 0.333, 0.584, 0.584, 0.584, 0.611, + 0.975, 0.722, 0.722, 0.722, 0.722, 0.667, 0.611, 0.778, + 0.722, 0.278, 0.556, 0.722, 0.611, 0.833, 0.722, 0.778, + 0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944, + 0.667, 0.667, 0.611, 0.333, 0.278, 0.333, 0.584, 0.556, + 0.333, 0.556, 0.611, 0.556, 0.611, 0.556, 0.333, 0.611, + 0.611, 0.278, 0.278, 0.556, 0.278, 0.889, 0.611, 0.611, + 0.611, 0.611, 0.389, 0.556, 0.333, 0.611, 0.556, 0.778, + 0.556, 0.556, 0.500, 0.389, 0.280, 0.389, 0.584, 0.278, + 0.278, 0.278, 0.278, 0.556, 0.500, 1.000, 0.556, 0.556, + 0.333, 1.000, 0.667, 0.333, 1.000, 0.278, 0.278, 0.278, + 0.278, 0.278, 0.278, 0.500, 0.500, 0.350, 0.556, 1.000, + 0.333, 1.000, 0.556, 0.333, 0.944, 0.278, 0.278, 0.667, + 0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.280, 0.556, + 0.333, 0.737, 0.370, 0.556, 0.584, 0.333, 0.737, 0.333, + 0.400, 0.584, 0.333, 0.333, 0.333, 0.611, 0.556, 0.278, + 0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611, + 0.722, 0.722, 0.722, 0.722, 0.722, 0.722, 1.000, 0.722, + 0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278, + 0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584, + 0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611, + 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.556, + 0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278, + 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.584, + 0.611, 0.611, 0.611, 0.611, 0.611, 0.556, 0.611, 0.556 +}; diff --git a/common/plotters/common_plotSVG_functions.cpp b/common/plotters/common_plotSVG_functions.cpp new file mode 100644 index 0000000000..b8aacc3bc5 --- /dev/null +++ b/common/plotters/common_plotSVG_functions.cpp @@ -0,0 +1,645 @@ +/** + * @file common_plotPS_functions.cpp + * @brief Kicad: Common plot SVG functions + */ + +/* + * 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) 1992-2017 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 + */ + +/* Some info on basic items SVG format, used here: + * The root element of all SVG files is the element. + * + * The element is used to group SVG shapes together. + * Once grouped you can transform the whole group of shapes as if it was a single shape. + * This is an advantage compared to a nested element + * which cannot be the target of transformation by itself. + * + * The element represents a rectangle. + * Using this element you can draw rectangles of various width, height, + * with different stroke (outline) and fill colors, with sharp or rounded corners etc. + * + * + * + * + * + * + * + * The element is used to draw circles. + * + * + * The element is used to draw ellipses. + * An ellipse is a circle that does not have equal height and width. + * Its radius in the x and y directions are different, in other words. + * + * + * The element is used to draw lines. + * + * + * + * + * The element is used to draw multiple connected lines + * Here is a simple example: + * + * + * + * The element is used to draw with multiple (3 or more) sides / edges. + * Here is a simple example: + * + * + * + * The element is used to draw advanced shapes combined from lines and arcs, + * with or without fill. + * It is probably the most advanced and versatile SVG shape of them all. + * It is probably also the hardest element to master. + * + * + * Draw an elliptic arc: it is one of basic path command: + * + * flag_arc_large: 0 = small arc > 180 deg, 1 = large arc > 180 deg + * flag_sweep : 0 = CCW, 1 = CW + * The center of ellipse is automatically calculated. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * Function XmlEsc + * translates '<' to "<", '>' to ">" and so on, according to the spec: + * http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping + * May be moved to a library if needed generally, but not expecting that. + */ +static wxString XmlEsc( const wxString& aStr, bool isAttribute = false ) +{ + wxString escaped; + + escaped.reserve( aStr.length() ); + + for( wxString::const_iterator it = aStr.begin(); it != aStr.end(); ++it ) + { + const wxChar c = *it; + + switch( c ) + { + case wxS( '<' ): + escaped.append( wxS( "<" ) ); + break; + case wxS( '>' ): + escaped.append( wxS( ">" ) ); + break; + case wxS( '&' ): + escaped.append( wxS( "&" ) ); + break; + case wxS( '\r' ): + escaped.append( wxS( " " ) ); + break; + default: + if( isAttribute ) + { + switch( c ) + { + case wxS( '"' ): + escaped.append( wxS( """ ) ); + break; + case wxS( '\t' ): + escaped.append( wxS( " " ) ); + break; + case wxS( '\n' ): + escaped.append( wxS( " " )); + break; + default: + escaped.append(c); + } + } + else + escaped.append(c); + } + } + + return escaped; +} + + +SVG_PLOTTER::SVG_PLOTTER() +{ + m_graphics_changed = true; + SetTextMode( PLOTTEXTMODE_STROKE ); + m_fillMode = NO_FILL; // or FILLED_SHAPE or FILLED_WITH_BG_BODYCOLOR + m_pen_rgb_color = 0; // current color value (black) + m_brush_rgb_color = 0; // current color value (black) + m_dashed = false; +} + + +void SVG_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil, + double aScale, bool aMirror ) +{ + m_plotMirror = aMirror; + m_yaxisReversed = true; // unlike other plotters, SVG has Y axis reversed + plotOffset = aOffset; + plotScale = aScale; + m_IUsPerDecimil = aIusPerDecimil; + iuPerDeviceUnit = 1.0 / aIusPerDecimil; + /* Compute the paper size in IUs */ + paperSize = pageInfo.GetSizeMils(); + paperSize.x *= 10.0 * aIusPerDecimil; + paperSize.y *= 10.0 * aIusPerDecimil; + SetDefaultLineWidth( 100 * aIusPerDecimil ); // arbitrary default +} + + +void SVG_PLOTTER::SetColor( COLOR4D color ) +{ + PSLIKE_PLOTTER::SetColor( color ); + + if( m_graphics_changed ) + setSVGPlotStyle(); +} + + +void SVG_PLOTTER::setFillMode( FILL_T fill ) +{ + if( m_fillMode != fill ) + { + m_graphics_changed = true; + m_fillMode = fill; + } +} + + +void SVG_PLOTTER::setSVGPlotStyle() +{ + fputs( "\n\n", outputFile ); + + m_graphics_changed = false; +} + +/* Set the current line width (in IUs) for the next plot + */ +void SVG_PLOTTER::SetCurrentLineWidth( int width, void* aData ) +{ + int pen_width; + + if( width >= 0 ) + pen_width = width; + else + pen_width = defaultPenWidth; + + if( pen_width != currentPenWidth ) + { + m_graphics_changed = true; + currentPenWidth = pen_width; + } + + if( m_graphics_changed ) + setSVGPlotStyle(); +} + + +/* initialize m_red, m_green, m_blue ( 0 ... 255) + * from reduced values r, g ,b ( 0.0 to 1.0 ) + */ +void SVG_PLOTTER::emitSetRGBColor( double r, double g, double b ) +{ + int red = (int) ( 255.0 * r ); + int green = (int) ( 255.0 * g ); + int blue = (int) ( 255.0 * b ); + long rgb_color = (red << 16) | (green << 8) | blue; + + if( m_pen_rgb_color != rgb_color ) + { + m_graphics_changed = true; + m_pen_rgb_color = rgb_color; + + // Currently, use the same color for brush and pen + // (i.e. to draw and fill a contour) + m_brush_rgb_color = rgb_color; + } +} + + +/** + * SVG supports dashed lines + */ +void SVG_PLOTTER::SetDash( int dashed ) +{ + if( m_dashed != dashed ) + { + m_graphics_changed = true; + m_dashed = dashed; + } + + if( m_graphics_changed ) + setSVGPlotStyle(); +} + + +void SVG_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width ) +{ + EDA_RECT rect( p1, wxSize( p2.x -p1.x, p2.y -p1.y ) ); + rect.Normalize(); + DPOINT org_dev = userToDeviceCoordinates( rect.GetOrigin() ); + DPOINT end_dev = userToDeviceCoordinates( rect.GetEnd() ); + DSIZE size_dev = end_dev - org_dev; + // Ensure size of rect in device coordinates is > 0 + // I don't know if this is a SVG issue or a Inkscape issue, but + // Inkscape has problems with negative or null values for width and/or height, so avoid them + DBOX rect_dev( org_dev, size_dev); + rect_dev.Normalize(); + + setFillMode( fill ); + SetCurrentLineWidth( width ); + + // Rectangles having a 0 size value for height or width are just not drawn on Inscape, + // so use a line when happens. + if( rect_dev.GetSize().x == 0.0 || rect_dev.GetSize().y == 0.0 ) // Draw a line + fprintf( outputFile, + "\n", + rect_dev.GetPosition().x, rect_dev.GetPosition().y, + rect_dev.GetEnd().x, rect_dev.GetEnd().y + ); + + else + fprintf( outputFile, + "\n", + rect_dev.GetPosition().x, rect_dev.GetPosition().y, + rect_dev.GetSize().x, rect_dev.GetSize().y, + 0.0 // radius of rounded corners + ); +} + + +void SVG_PLOTTER::Circle( const wxPoint& pos, int diametre, FILL_T fill, int width ) +{ + DPOINT pos_dev = userToDeviceCoordinates( pos ); + double radius = userToDeviceSize( diametre / 2.0 ); + + setFillMode( fill ); + SetCurrentLineWidth( width ); + + fprintf( outputFile, + " \n", + pos_dev.x, pos_dev.y, radius ); +} + + +void SVG_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius, + FILL_T fill, int width ) +{ + /* Draws an arc of a circle, centred on (xc,yc), with starting point + * (x1, y1) and ending at (x2, y2). The current pen is used for the outline + * and the current brush for filling the shape. + * + * The arc is drawn in an anticlockwise direction from the start point to + * the end point + */ + + if( radius <= 0 ) + return; + + if( StAngle > EndAngle ) + std::swap( StAngle, EndAngle ); + + setFillMode( fill ); + SetCurrentLineWidth( width ); + + // Calculate start point. + DPOINT centre_dev = userToDeviceCoordinates( centre ); + double radius_dev = userToDeviceSize( radius ); + + if( !m_yaxisReversed ) // Should be never the case + { + double tmp = StAngle; + StAngle = -EndAngle; + EndAngle = -tmp; + } + + if( m_plotMirror ) + { + if( m_mirrorIsHorizontal ) + { + StAngle = 1800.0 -StAngle; + EndAngle = 1800.0 -EndAngle; + std::swap( StAngle, EndAngle ); + } + else + { + StAngle = -StAngle; + EndAngle = -EndAngle; + } + } + + DPOINT start; + start.x = radius_dev; + RotatePoint( &start.x, &start.y, StAngle ); + DPOINT end; + end.x = radius_dev; + RotatePoint( &end.x, &end.y, EndAngle ); + start += centre_dev; + end += centre_dev; + + double theta1 = DECIDEG2RAD( StAngle ); + + if( theta1 < 0 ) + theta1 = theta1 + M_PI * 2; + + double theta2 = DECIDEG2RAD( EndAngle ); + + if( theta2 < 0 ) + theta2 = theta2 + M_PI * 2; + + if( theta2 < theta1 ) + theta2 = theta2 + M_PI * 2; + + int flg_arc = 0; // flag for large or small arc. 0 means less than 180 degrees + + if( fabs( theta2 - theta1 ) > M_PI ) + flg_arc = 1; + + int flg_sweep = 0; // flag for sweep always 0 + + // Draw a single arc: an arc is one of 3 curve commands (2 other are 2 bezier curves) + // params are start point, radius1, radius2, X axe rotation, + // flag arc size (0 = small arc > 180 deg, 1 = large arc > 180 deg), + // sweep arc ( 0 = CCW, 1 = CW), + // end point + fprintf( outputFile, "\n", + start.x, start.y, radius_dev, radius_dev, + flg_arc, flg_sweep, + end.x, end.y ); +} + + +void SVG_PLOTTER::PlotPoly( const std::vector& aCornerList, + FILL_T aFill, int aWidth, void * aData ) +{ + if( aCornerList.size() <= 1 ) + return; + + setFillMode( aFill ); + SetCurrentLineWidth( aWidth ); + + switch( aFill ) + { + case NO_FILL: + fprintf( outputFile, " \n" ); +} + + +/** + * Postscript-likes at the moment are the only plot engines supporting bitmaps... + */ +void SVG_PLOTTER::PlotImage( const wxImage& aImage, const wxPoint& aPos, + double aScaleFactor ) +{ + // in svg file we must insert a link to a png image file to plot an image + // the image itself is not included in the svg file. + // So we prefer skip the image, and just draw a rectangle, + // like other plotters which do not support images + + PLOTTER::PlotImage( aImage, aPos, aScaleFactor ); + +} + + +void SVG_PLOTTER::PenTo( const wxPoint& pos, char plume ) +{ + if( plume == 'Z' ) + { + if( penState != 'Z' ) + { + fputs( "\" />\n", outputFile ); + penState = 'Z'; + penLastpos.x = -1; + penLastpos.y = -1; + } + + return; + } + + if( penState == 'Z' ) // here plume = 'D' or 'U' + { + DPOINT pos_dev = userToDeviceCoordinates( pos ); + + // Ensure we do not use a fill mode when moving tne pen, + // in SVG mode (i;e. we are plotting only basic lines, not a filled area + if( m_fillMode != NO_FILL ) + { + setFillMode( NO_FILL ); + setSVGPlotStyle(); + } + + fprintf( outputFile, "\n", + " \n", + "\n", + (double) paperSize.x / m_IUsPerDecimil * 2.54 / 10000, + (double) paperSize.y / m_IUsPerDecimil * 2.54 / 10000, + origin.x, origin.y, + (int) ( paperSize.x / m_IUsPerDecimil ), + (int) ( paperSize.y / m_IUsPerDecimil) ); + + // Write title + char date_buf[250]; + time_t ltime = time( NULL ); + strftime( date_buf, 250, "%Y/%m/%d %H:%M:%S", + localtime( <ime ) ); + + fprintf( outputFile, + "SVG Picture created as %s date %s \n", + TO_UTF8( XmlEsc( wxFileName( filename ).GetFullName() ) ), date_buf ); + // End of header + fprintf( outputFile, " Picture generated by %s \n", + TO_UTF8( XmlEsc( creator ) ) ); + + // output the pen and brush color (RVB values in hex) and opacity + double opacity = 1.0; // 0.0 (transparent to 1.0 (solid) + fprintf( outputFile, + "\n", outputFile ); + return true; +} + + +bool SVG_PLOTTER::EndPlot() +{ + fputs( " \n\n", outputFile ); + fclose( outputFile ); + outputFile = NULL; + + return true; +} + + +void SVG_PLOTTER::Text( const wxPoint& aPos, + const COLOR4D aColor, + const wxString& aText, + double aOrient, + const wxSize& aSize, + enum EDA_TEXT_HJUSTIFY_T aH_justify, + enum EDA_TEXT_VJUSTIFY_T aV_justify, + int aWidth, + bool aItalic, + bool aBold, + bool aMultilineAllowed, + void* aData ) +{ + setFillMode( NO_FILL ); + SetColor( aColor ); + SetCurrentLineWidth( aWidth ); + + // TODO: see if the postscript native text code can be used in SVG plotter + + PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, + aWidth, aItalic, aBold, aMultilineAllowed ); +} diff --git a/common/plotters/common_plot_functions.cpp b/common/plotters/common_plot_functions.cpp new file mode 100644 index 0000000000..3ce45d9afd --- /dev/null +++ b/common/plotters/common_plot_functions.cpp @@ -0,0 +1,165 @@ +/** + * @file common_plot_functions.cpp + * @brief Kicad: Common plotting functions + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2017 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "worksheet_shape_builder.h" +#include "class_worksheet_dataitem.h" +#include + + + +wxString GetDefaultPlotExtension( PlotFormat aFormat ) +{ + switch( aFormat ) + { + case PLOT_FORMAT_DXF: + return DXF_PLOTTER::GetDefaultFileExtension(); + + case PLOT_FORMAT_POST: + return PS_PLOTTER::GetDefaultFileExtension(); + + case PLOT_FORMAT_PDF: + return PDF_PLOTTER::GetDefaultFileExtension(); + + case PLOT_FORMAT_HPGL: + return HPGL_PLOTTER::GetDefaultFileExtension(); + + case PLOT_FORMAT_GERBER: + return GERBER_PLOTTER::GetDefaultFileExtension(); + + case PLOT_FORMAT_SVG: + return SVG_PLOTTER::GetDefaultFileExtension(); + + default: + wxASSERT( false ); + return wxEmptyString; + } +} + + + +void PlotWorkSheet( PLOTTER* plotter, const TITLE_BLOCK& aTitleBlock, + const PAGE_INFO& aPageInfo, + int aSheetNumber, int aNumberOfSheets, + const wxString &aSheetDesc, const wxString &aFilename ) +{ + /* Note: Page sizes values are given in mils + */ + double iusPerMil = plotter->GetIUsPerDecimil() * 10.0; + + COLOR4D plotColor = plotter->GetColorMode() ? COLOR4D( RED ) : COLOR4D::BLACK; + plotter->SetColor( plotColor ); + WS_DRAW_ITEM_LIST drawList; + + // Print only a short filename, if aFilename is the full filename + wxFileName fn( aFilename ); + + // Prepare plot parameters + drawList.SetPenSize(PLOTTER::USE_DEFAULT_LINE_WIDTH ); + drawList.SetMilsToIUfactor( iusPerMil ); + drawList.SetSheetNumber( aSheetNumber ); + drawList.SetSheetCount( aNumberOfSheets ); + drawList.SetFileName( fn.GetFullName() ); // Print only the short filename + drawList.SetSheetName( aSheetDesc ); + + + drawList.BuildWorkSheetGraphicList( aPageInfo, + aTitleBlock, plotColor, plotColor ); + + // Draw item list + for( WS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item; + item = drawList.GetNext() ) + { + plotter->SetCurrentLineWidth( PLOTTER::USE_DEFAULT_LINE_WIDTH ); + + switch( item->GetType() ) + { + case WS_DRAW_ITEM_BASE::wsg_line: + { + WS_DRAW_ITEM_LINE* line = (WS_DRAW_ITEM_LINE*) item; + plotter->SetCurrentLineWidth( line->GetPenWidth() ); + plotter->MoveTo( line->GetStart() ); + plotter->FinishTo( line->GetEnd() ); + } + break; + + case WS_DRAW_ITEM_BASE::wsg_rect: + { + WS_DRAW_ITEM_RECT* rect = (WS_DRAW_ITEM_RECT*) item; + plotter->Rect( rect->GetStart(), + rect->GetEnd(), + NO_FILL, + rect->GetPenWidth() ); + } + break; + + case WS_DRAW_ITEM_BASE::wsg_text: + { + WS_DRAW_ITEM_TEXT* text = (WS_DRAW_ITEM_TEXT*) item; + plotter->Text( text->GetTextPos(), text->GetColor(), + text->GetShownText(), text->GetTextAngle(), + text->GetTextSize(), + text->GetHorizJustify(), text->GetVertJustify(), + text->GetPenWidth(), + text->IsItalic(), text->IsBold(), + text->IsMultilineAllowed() ); + } + break; + + case WS_DRAW_ITEM_BASE::wsg_poly: + { + WS_DRAW_ITEM_POLYGON* poly = (WS_DRAW_ITEM_POLYGON*) item; + plotter->PlotPoly( poly->m_Corners, + poly->IsFilled() ? FILLED_SHAPE : NO_FILL, + poly->GetPenWidth() ); + } + break; + + case WS_DRAW_ITEM_BASE::wsg_bitmap: + { + WS_DRAW_ITEM_BITMAP* bm = (WS_DRAW_ITEM_BITMAP*) item; + + WORKSHEET_DATAITEM_BITMAP* parent = (WORKSHEET_DATAITEM_BITMAP*)bm->GetParent(); + + if( parent->m_ImageBitmap == NULL ) + break; + + parent->m_ImageBitmap->PlotImage( plotter, bm->GetPosition(), + plotColor, PLOTTER::USE_DEFAULT_LINE_WIDTH ); + } + break; + } + } +}