/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 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, see .
*/
/**
* Plotting engines similar to ps (PostScript, Gerber, svg)
*
* @file plotters_pslike.h
*/
#pragma once
#include "plotter.h"
/**
* The PSLIKE_PLOTTER class is an intermediate class to handle common routines for engines
* working more or less with the postscript imaging model.
*/
class PSLIKE_PLOTTER : public PLOTTER
{
public:
PSLIKE_PLOTTER() :
plotScaleAdjX( 1 ),
plotScaleAdjY( 1 ),
m_textMode( PLOT_TEXT_MODE::PHANTOM )
{
}
/**
* PS and PDF fully implement native text (for the Latin-1 subset)
*/
virtual void SetTextMode( PLOT_TEXT_MODE mode ) override
{
if( mode != PLOT_TEXT_MODE::DEFAULT )
m_textMode = mode;
}
/**
* Set the 'fine' scaling for the postscript engine
*/
void SetScaleAdjust( double scaleX, double scaleY )
{
plotScaleAdjX = scaleX;
plotScaleAdjY = scaleY;
}
// Pad routines are handled with lower level primitives
virtual void FlashPadCircle( const VECTOR2I& aPadPos, int aDiameter,
OUTLINE_MODE aTraceMode, void* aData ) override;
virtual void FlashPadOval( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
const EDA_ANGLE& aPadOrient, OUTLINE_MODE aTraceMode,
void* aData ) override;
virtual void FlashPadRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
const EDA_ANGLE& aPadOrient, OUTLINE_MODE aTraceMode,
void* aData ) override;
virtual void FlashPadRoundRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
int aCornerRadius, const EDA_ANGLE& aOrient,
OUTLINE_MODE aTraceMode, void* aData ) override;
virtual void FlashPadCustom( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
const EDA_ANGLE& aOrient, SHAPE_POLY_SET* aPolygons,
OUTLINE_MODE aTraceMode, void* aData ) override;
virtual void FlashPadTrapez( const VECTOR2I& aPadPos, const VECTOR2I* aCorners,
const EDA_ANGLE& aPadOrient, OUTLINE_MODE aTraceMode,
void* aData ) override;
virtual void FlashRegularPolygon( const VECTOR2I& aShapePos, int aDiameter, int aCornerCount,
const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode,
void* aData ) override;
/**
* The SetColor implementation is split with the subclasses: the PSLIKE computes the rgb
* values, the subclass emits the operator to actually do it
*/
virtual void SetColor( const COLOR4D& color ) override;
protected:
/**
* 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 computeTextParameters( const VECTOR2I& aPos,
const wxString& aText,
const EDA_ANGLE& aOrient,
const VECTOR2I& aSize,
bool aMirror,
enum GR_TEXT_H_ALIGN_T aH_justify,
enum GR_TEXT_V_ALIGN_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
double *wideningFactor,
double *ctm_a,
double *ctm_b,
double *ctm_c,
double *ctm_d,
double *ctm_e,
double *ctm_f,
double *heightFactor );
/// convert a wxString unicode string to a char string compatible with the accepted
/// string plotter format (convert special chars and non ascii7 chars)
virtual std::string encodeStringForPlotter( const wxString& aUnicode );
/// Virtual primitive for emitting the setrgbcolor operator
virtual void emitSetRGBColor( double r, double g, double b, double a ) = 0;
/// Height of the postscript font (from the AFM)
static const double postscriptTextAscent; // = 0.718;
/**
* Sister function for the GRTextWidth in gr_text.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 returnPostscriptTextWidth( const wxString& aText, int aXSize, bool aItalic, bool aBold );
/// Fine user scale adjust ( = 1.0 if no correction)
double plotScaleAdjX, plotScaleAdjY;
/// How to draw text
PLOT_TEXT_MODE m_textMode;
};
class PS_PLOTTER : public PSLIKE_PLOTTER
{
public:
PS_PLOTTER()
{
// The phantom plot in postscript is an hack and reportedly
// crashes Adobe's own postscript interpreter!
m_textMode = PLOT_TEXT_MODE::STROKE;
}
static wxString GetDefaultFileExtension()
{
return wxString( wxT( "ps" ) );
}
virtual PLOT_FORMAT GetPlotterType() const override
{
return PLOT_FORMAT::POST;
}
/**
* 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)
*/
virtual bool StartPlot( const wxString& aPageNumber ) override;
virtual bool EndPlot() override;
/**
* Set the current line width (in IUs) for the next plot.
*/
virtual void SetCurrentLineWidth( int width, void* aData = nullptr ) override;
/**
* PostScript supports dashed lines.
*/
virtual void SetDash( int aLineWidth, PLOT_DASH_TYPE aLineStyle ) override;
virtual void SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil,
double aScale, bool aMirror ) override;
virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill,
int width = USE_DEFAULT_LINE_WIDTH ) override;
virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill,
int width = USE_DEFAULT_LINE_WIDTH ) override;
virtual void Arc( const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aEnd,
FILL_T aFill, int aWidth, int aMaxError ) override;
virtual void PlotPoly( const std::vector& aCornerList, FILL_T aFill,
int aWidth = USE_DEFAULT_LINE_WIDTH, void* aData = nullptr ) override;
/**
* PostScript-likes at the moment are the only plot engines supporting bitmaps.
*/
virtual void PlotImage( const wxImage& aImage, const VECTOR2I& aPos,
double aScaleFactor ) override;
virtual void PenTo( const VECTOR2I& pos, char plume ) override;
virtual void Text( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const EDA_ANGLE& aOrient,
const VECTOR2I& aSize,
enum GR_TEXT_H_ALIGN_T aH_justify,
enum GR_TEXT_V_ALIGN_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
bool aMultilineAllowed,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData = nullptr ) override;
virtual void PlotText( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const TEXT_ATTRIBUTES& aAttributes,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData = nullptr ) override;
protected:
virtual void emitSetRGBColor( double r, double g, double b, double a ) override;
};
class PDF_PLOTTER : public PSLIKE_PLOTTER
{
public:
PDF_PLOTTER() :
m_pageTreeHandle( 0 ),
m_fontResDictHandle( 0 ),
m_pageStreamHandle( 0 ),
m_streamLengthHandle( 0 ),
m_workFile( nullptr ),
m_totalOutlineNodes( 0 )
{
}
virtual PLOT_FORMAT GetPlotterType() const override
{
return PLOT_FORMAT::PDF;
}
static wxString GetDefaultFileExtension()
{
return wxString( wxT( "pdf" ) );
}
/**
* Open or create the plot file aFullFilename.
*
* The base class open the file in text mode, so we should have this
* function overlaid for PDF files, which are binary files.
*
* @param aFullFilename is the full file name of the file to create.
* @return true if success, false if the file cannot be created/opened.
*/
virtual bool OpenFile( const wxString& aFullFilename ) override;
/**
* 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.
*/
virtual bool StartPlot( const wxString& aPageNumber ) override;
virtual bool StartPlot( const wxString& aPageNumber,
const wxString& aPageName = wxEmptyString );
virtual bool EndPlot() override;
/**
* Start a new page in the PDF document.
*/
virtual void StartPage( const wxString& aPageNumber, const wxString& aPageName = wxEmptyString );
/**
* Close the current page in the PDF document (and emit its compressed stream).
*/
virtual void ClosePage();
/**
* 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 actually change the polygon.
*/
virtual void SetCurrentLineWidth( int width, void* aData = nullptr ) override;
/**
* PDF supports dashed lines
*/
virtual void SetDash( int aLineWidth, PLOT_DASH_TYPE aLineStyle ) override;
/**
* PDF can have multiple pages, so SetPageSettings can be called
* with the outputFile open (but not inside a page stream!)
*/
virtual void SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil,
double aScale, bool aMirror ) override;
/**
* Rectangles in PDF. Supported by the native operator.
*/
virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill,
int width = USE_DEFAULT_LINE_WIDTH ) override;
/**
* Circle drawing for PDF. They're approximated by curves, but fill is supported
*/
virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill,
int width = USE_DEFAULT_LINE_WIDTH ) override;
/**
* The PDF engine can't directly plot arcs so we use polygonization.
*/
virtual void Arc( const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aEnd,
FILL_T aFill, int aWidth, int aMaxError ) override;
virtual void Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aEndAngle, double aRadius, FILL_T aFill,
int aWidth = USE_DEFAULT_LINE_WIDTH ) override;
/**
* Polygon plotting for PDF. Everything is supported
*/
virtual void PlotPoly( const std::vector& aCornerList, FILL_T aFill,
int aWidth = USE_DEFAULT_LINE_WIDTH, void* aData = nullptr ) override;
virtual void PenTo( const VECTOR2I& pos, char plume ) override;
virtual void Text( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const EDA_ANGLE& aOrient,
const VECTOR2I& aSize,
enum GR_TEXT_H_ALIGN_T aH_justify,
enum GR_TEXT_V_ALIGN_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
bool aMultilineAllowed,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData = nullptr ) override;
virtual void PlotText( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const TEXT_ATTRIBUTES& aAttributes,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData = nullptr ) override;
void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL ) override;
void HyperlinkMenu( const BOX2I& aBox, const std::vector& aDestURLs ) override;
void Bookmark( const BOX2I& aBox, const wxString& aName, const wxString& aGroupName = wxEmptyString ) override;
/**
* PDF images are handles as inline, not XObject streams...
*/
void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor ) override;
protected:
struct OUTLINE_NODE
{
int actionHandle; ///< Handle to action
wxString title; ///< Title of outline node
int entryHandle; ///< Allocated handle for this outline entry
std::vector children; ///< Ordered list of children
~OUTLINE_NODE()
{
std::for_each( children.begin(), children.end(),
[]( OUTLINE_NODE* node )
{
delete node;
} );
}
OUTLINE_NODE* AddChild( int aActionHandle, const wxString& aTitle, int aEntryHandle )
{
OUTLINE_NODE* child = new OUTLINE_NODE
{
aActionHandle, aTitle, aEntryHandle, {}
};
children.push_back( child );
return child;
}
};
/**
* Adds a new outline node entry
*
* The PDF object handle is automacially allocated
*
* @param aParent Parent node to append the new node to
* @param aActionHandle The handle of an action that may be performed on click, set to -1 for no action
* @param aTitle Title of node to display
*/
OUTLINE_NODE* addOutlineNode( OUTLINE_NODE* aParent, int aActionHandle,
const wxString& aTitle );
/// convert a wxString unicode string to a char string compatible with the accepted
/// string PDF format (convert special chars and non ascii7 chars)
std::string encodeStringForPlotter( const wxString& aUnicode ) override;
/**
* 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.
*/
virtual void emitSetRGBColor( double r, double g, double b, double a ) override;
/**
* 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 allocPdfObject();
/**
* 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 startPdfObject(int handle = -1);
/**
* Close the current PDF object
*/
void closePdfObject();
/**
* 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 startPdfStream(int handle = -1);
/**
* Finish the current PDF stream (writes the deferred length, too)
*/
void closePdfStream();
/**
* Starts emitting the outline object
*/
int emitOutline();
/**
* Emits a outline item object and recurses into any children
*/
void emitOutlineNode( OUTLINE_NODE* aNode, int aParentHandle, int aNextNode, int aPrevNode );
/**
* Emits an action object that instructs a goto coordinates on a page
*
* @return Generated action handle
*/
int emitGoToAction( int aPageHandle, const VECTOR2I& aBottomLeft, const VECTOR2I& aTopRight );
int emitGoToAction( int aPageHandle );
int m_pageTreeHandle; ///< Handle to the root of the page tree object
int m_fontResDictHandle; ///< Font resource dictionary
int m_imgResDictHandle; ///< Image resource dictionary
int m_jsNamesHandle; ///< Handle for Names dictionary with JS
std::vector m_pageHandles; ///< Handles to the page objects
int m_pageStreamHandle; ///< Handle of the page content object
int m_streamLengthHandle; ///< Handle to the deferred stream length
wxString m_workFilename;
wxString m_pageName;
FILE* m_workFile; ///< Temporary file to construct the stream before zipping
std::vector m_xrefTable; ///< The PDF xref offset table
///< List of user-space page numbers for resolving internal hyperlinks
std::vector m_pageNumbers;
///< List of loaded hyperlinks in current page
std::vector> m_hyperlinksInPage;
std::vector>> m_hyperlinkMenusInPage;
///< Handles for all the hyperlink objects that will be deferred
std::map> m_hyperlinkHandles;
std::map>> m_hyperlinkMenuHandles;
std::map>> m_bookmarksInPage;
std::map m_imageHandles;
std::unique_ptr m_outlineRoot; ///< Root outline node
int m_totalOutlineNodes; ///< Total number of outline nodes
};
class SVG_PLOTTER : public PSLIKE_PLOTTER
{
public:
SVG_PLOTTER();
static wxString GetDefaultFileExtension()
{
return wxString( wxT( "svg" ) );
}
virtual PLOT_FORMAT GetPlotterType() const override
{
return PLOT_FORMAT::SVG;
}
/**
* Create SVG file header.
*/
virtual bool StartPlot( const wxString& aPageNumber ) override;
virtual bool EndPlot() override;
/**
* Set the current line width (in IUs) for the next plot.
*/
virtual void SetCurrentLineWidth( int width, void* aData = nullptr ) override;
/**
* SVG supports dashed lines.
*/
virtual void SetDash( int aLineWidth, PLOT_DASH_TYPE aLineStyle ) override;
virtual void SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil,
double aScale, bool aMirror ) override;
virtual void Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T fill,
int width = USE_DEFAULT_LINE_WIDTH ) override;
virtual void Circle( const VECTOR2I& pos, int diametre, FILL_T fill,
int width = USE_DEFAULT_LINE_WIDTH ) override;
virtual void Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle,
const EDA_ANGLE& aEndAngle, double aRadius, FILL_T aFill,
int aWidth = USE_DEFAULT_LINE_WIDTH ) override;
virtual void BezierCurve( const VECTOR2I& aStart, const VECTOR2I& aControl1,
const VECTOR2I& aControl2, const VECTOR2I& aEnd,
int aTolerance,
int aLineThickness = USE_DEFAULT_LINE_WIDTH ) override;
virtual void PlotPoly( const std::vector& aCornerList, FILL_T aFill,
int aWidth = USE_DEFAULT_LINE_WIDTH, void * aData = nullptr ) override;
/**
* PostScript-likes at the moment are the only plot engines supporting bitmaps.
*/
virtual void PlotImage( const wxImage& aImage, const VECTOR2I& aPos,
double aScaleFactor ) override;
virtual void PenTo( const VECTOR2I& pos, char plume ) override;
/**
* Select SVG coordinate precision (number of digits needed for 1 mm )
* (SVG plotter uses always metric unit)
* Should be called only after SetViewport() is called
*
* @param aPrecision = number of digits in mantissa of coordinate
* use a value from 3-6
* do not use value > 6 to avoid overflow in PCBNEW
* do not use value > 4 to avoid overflow for other parts
*/
virtual void SetSvgCoordinatesFormat( unsigned aPrecision ) override;
/**
* Calling this function allows one to define the beginning of a group
* of drawing items (used in SVG format to separate components)
* @param aData should be a string for the SVG ID tag
*/
virtual void StartBlock( void* aData ) override;
/**
* Calling this function allows one to define the end of a group of drawing
* items the group is started by StartBlock()
* @param aData should be null
*/
virtual void EndBlock( void* aData ) override;
virtual void Text( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const EDA_ANGLE& aOrient,
const VECTOR2I& aSize,
enum GR_TEXT_H_ALIGN_T aH_justify,
enum GR_TEXT_V_ALIGN_T aV_justify,
int aWidth,
bool aItalic,
bool aBold,
bool aMultilineAllowed,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData = nullptr ) override;
virtual void PlotText( const VECTOR2I& aPos,
const COLOR4D& aColor,
const wxString& aText,
const TEXT_ATTRIBUTES& aAttributes,
KIFONT::FONT* aFont,
const KIFONT::METRICS& aFontMetrics,
void* aData = nullptr ) override;
protected:
/**
* Initialize m_pen_rgb_color from reduced values r, g ,b
* ( reduced values are 0.0 to 1.0 )
*/
virtual void emitSetRGBColor( double r, double g, double b, double a ) override;
/**
* Output the string which define pen and brush color, shape, transparency
*
* @param aIsGroup If false, do not form a new group for the style.
* @param aExtraStyle If given, the string will be added into the style string before closing
*/
void setSVGPlotStyle( int aLineWidth, bool aIsGroup = true,
const std::string& aExtraStyle = {} );
/**
* Prepare parameters for setSVGPlotStyle()
*/
void setFillMode( FILL_T fill );
FILL_T m_fillMode; // true if the current contour
// rect, arc, circle, polygon must be filled
long m_pen_rgb_color; // current rgb color value: each color has
// a value 0 ... 255, and the 3 colors are
// grouped in a 3x8 bits value
// (written in hex to svg files)
long m_brush_rgb_color; // same as m_pen_rgb_color, used to fill
// some contours.
double m_brush_alpha;
bool m_graphics_changed; // true if a pen/brush parameter is modified
// color, pen size, fill mode ...
// the new SVG stype must be output on file
PLOT_DASH_TYPE m_dashed; // plot line style
unsigned m_precision; // How fine the step size is
// Use 3-6 (3 means um precision, 6 nm precision) in PcbNew
// 3-4 in other modules (avoid values >4 to avoid overflow)
// see also comment for m_useInch.
};