kicad-source/common/common_plotPS_functions.cpp
Александр Закамалдин 2caa72f063 This patch implements width tuning (width correction) for PS plotting of tracks, pads and vias.
This feature is very useful for electronics hobbyists who use DIY PCB technology 
(both toner transfer and photoresist methods).
Also width correction may be useful for PCB designers who take care of track width etching.
This patch also fixes some minor PS plotting issues.
2012-01-07 00:02:38 -06:00

604 lines
19 KiB
C++

/**
* @file common_plotPS_functions.cpp
* @brief Kicad: Common plot Postscript Routines
*/
#include "fctsys.h"
#include "trigo.h"
#include "wxstruct.h"
#include "base_struct.h"
#include "common.h"
#include "plot_common.h"
#include "macros.h"
#include "kicad_string.h"
/* Set the plot offset for the current plotting */
void PS_PLOTTER::set_viewport( wxPoint aOffset, double aScale, bool aMirror )
{
wxASSERT( !output_file );
plotMirror = aMirror;
plot_offset = aOffset;
plot_scale = aScale;
device_scale = 1; /* PS references in decimals */
set_default_line_width( 100 ); /* default line width in 1/1000 inch */
}
/* Set the default line width (in 1/1000 inch) for the current plotting
*/
void PS_PLOTTER::set_default_line_width( int width )
{
default_pen_width = width; // line width in 1/1000 inch
current_pen_width = -1;
}
/* Set the current line width (in 1/1000 inch) for the next plot
*/
void PS_PLOTTER::set_current_line_width( int width )
{
wxASSERT( output_file );
int pen_width;
if( width >= 0 )
pen_width = width;
else
pen_width = default_pen_width;
if( pen_width != current_pen_width )
fprintf( output_file, "%g setlinewidth\n",
user_to_device_size( pen_width ) );
current_pen_width = pen_width;
}
/* Print the postscript set color command:
* r g b setrgbcolor,
* r, g, b = color values (= 0 .. 1.0 )
*
* color = color index in ColorRefs[]
*/
void PS_PLOTTER::set_color( int color )
{
wxASSERT( output_file );
/* Return at invalid color index */
if( color < 0 )
return;
if( color_mode )
{
if( negative_mode )
{
fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
(double) 1.0 - ColorRefs[color].m_Red / 255,
(double) 1.0 - ColorRefs[color].m_Green / 255,
(double) 1.0 - ColorRefs[color].m_Blue / 255 );
}
else
{
fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
(double) ColorRefs[color].m_Red / 255,
(double) ColorRefs[color].m_Green / 255,
(double) ColorRefs[color].m_Blue / 255 );
}
}
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
*/
int bwcolor = WHITE;
if( color != WHITE )
bwcolor = BLACK;
if( negative_mode )
fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
(double) 1.0 - ColorRefs[bwcolor].m_Red / 255,
(double) 1.0 - ColorRefs[bwcolor].m_Green / 255,
(double) 1.0 - ColorRefs[bwcolor].m_Blue / 255 );
else
fprintf( output_file, "%.3g %.3g %.3g setrgbcolor\n",
(double) ColorRefs[bwcolor].m_Red / 255,
(double) ColorRefs[bwcolor].m_Green / 255,
(double) ColorRefs[bwcolor].m_Blue / 255 );
}
}
void PS_PLOTTER::set_dash( bool dashed )
{
wxASSERT( output_file );
if( dashed )
fputs( "dashedline\n", stderr );
else
fputs( "solidline\n", stderr );
}
void PS_PLOTTER::rect( wxPoint p1, wxPoint p2, FILL_T fill, int width )
{
user_to_device_coordinates( p1 );
user_to_device_coordinates( p2 );
set_current_line_width( width );
fprintf( output_file, "%d %d %d %d rect%d\n", p1.x, p1.y,
p2.x - p1.x, p2.y - p1.y, fill );
}
void PS_PLOTTER::circle( wxPoint pos, int diametre, FILL_T fill, int width )
{
wxASSERT( output_file );
user_to_device_coordinates( pos );
double radius = user_to_device_size( diametre / 2.0 );
if( radius < 1 )
radius = 1;
set_current_line_width( width );
fprintf( output_file, "%d %d %g cir%d\n", pos.x, pos.y, radius, fill );
}
/* Plot an arc:
* StAngle, EndAngle = start and end arc in 0.1 degree
*/
void PS_PLOTTER::arc( wxPoint centre, int StAngle, int EndAngle, int radius,
FILL_T fill, int width )
{
wxASSERT( output_file );
if( radius <= 0 )
return;
set_current_line_width( width );
// Calculate start point.
user_to_device_coordinates( centre );
radius = wxRound( user_to_device_size( radius ) );
if( plotMirror )
fprintf( output_file, "%d %d %d %g %g arc%d\n", centre.x, centre.y,
radius, (double) -EndAngle / 10, (double) -StAngle / 10,
fill );
else
fprintf( output_file, "%d %d %d %g %g arc%d\n", centre.x, centre.y,
radius, (double) StAngle / 10, (double) EndAngle / 10,
fill );
}
/*
* Function PlotPoly
* Draw a polygon (filled or not) in POSTSCRIPT format
* param aCornerList = corners list
* param aFill :if true : filled polygon
* param aWidth = line width
*/
void PS_PLOTTER::PlotPoly( std::vector< wxPoint >& aCornerList, FILL_T aFill, int aWidth )
{
if( aCornerList.size() <= 1 )
return;
set_current_line_width( aWidth );
wxPoint pos = aCornerList[0];
user_to_device_coordinates( pos );
fprintf( output_file, "newpath\n%d %d moveto\n", pos.x, pos.y );
for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
{
pos = aCornerList[ii];
user_to_device_coordinates( pos );
fprintf( output_file, "%d %d lineto\n", pos.x, pos.y );
}
// Close path
fprintf( output_file, "poly%d\n", aFill );
}
/*
* Function PlotImage
* Only some plotters can plot image bitmaps
* for plotters that cannot plot a bitmap, a rectangle is plotted
* param aImage = the bitmap
* param aPos = position of the center of the bitmap
* param aScaleFactor = the scale factor to apply to the bitmap size
* (this is not the plot scale factor)
*/
void PS_PLOTTER::PlotImage( wxImage & aImage, wxPoint aPos, double aScaleFactor )
{
wxSize pix_size; // size of the bitmap in pixels
pix_size.x = aImage.GetWidth();
pix_size.y = aImage.GetHeight();
wxSize drawsize; // requested size of image
drawsize.x = wxRound( aScaleFactor * pix_size.x );
drawsize.y = wxRound( aScaleFactor * pix_size.y );
// 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( output_file, "/origstate save def\n" );
fprintf( output_file, "/pix %d string def\n", pix_size.x );
fprintf( output_file, "/greys %d string def\n", pix_size.x );
// Locate lower-left corner of image
user_to_device_coordinates( start );
fprintf( output_file, "%d %d translate\n", start.x, start.y );
// Map image size to device
user_to_device_coordinates( end );
fprintf( output_file, "%d %d scale\n",
ABS(end.x - start.x), ABS(end.y - start.y));
// Dimensions of source image (in pixels
fprintf( output_file, "%d %d 8", pix_size.x, pix_size.y );
// Map unit square to source
fprintf( output_file, " [%d 0 0 %d 0 %d]\n", pix_size.x, -pix_size.y , pix_size.y);
// include image data in ps file
fprintf( output_file, "{currentfile pix readhexstring pop}\n" );
fprintf( output_file, "false 3 colorimage\n");
// Single data source, 3 colors, Output RGB data (hexadecimal)
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( output_file, "\n");
}
int red, green, blue;
red = aImage.GetRed( xx, yy) & 0xFF;
green = aImage.GetGreen( xx, yy) & 0xFF;
blue = aImage.GetBlue( xx, yy) & 0xFF;
fprintf( output_file, "%2.2X%2.2X%2.2X", red, green, blue);
}
}
fprintf( output_file, "\n");
fprintf( output_file, "origstate restore\n" );
}
/* Routine to draw to a new position
*/
void PS_PLOTTER::pen_to( wxPoint pos, char plume )
{
wxASSERT( output_file );
if( plume == 'Z' )
{
if( pen_state != 'Z' )
{
fputs( "stroke\n", output_file );
pen_state = 'Z';
pen_lastpos.x = -1;
pen_lastpos.y = -1;
}
return;
}
user_to_device_coordinates( pos );
if( pen_state == 'Z' )
{
fputs( "newpath\n", output_file );
}
if( pen_state != plume || pos != pen_lastpos )
fprintf( output_file,
"%d %d %sto\n",
pos.x,
pos.y,
( plume=='D' ) ? "line" : "move" );
pen_state = plume;
pen_lastpos = 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::start_plot( FILE* fout )
{
wxASSERT( !output_file );
wxString msg;
output_file = fout;
static const char* PSMacro[] =
{
"/line {\n",
" newpath\n",
" moveto\n",
" lineto\n",
" stroke\n",
"} 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 ",
"grestore stroke } bind def\n",
"/arc2 { newpath 4 index 4 index moveto arc closepath gsave fill ",
"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 { [50 50] 0 setdash } bind def\n",
"/solidline { [] 0 setdash } bind def\n",
"gsave\n",
"0.0072 0.0072 scale\n", // Configure postscript for decimals.
"linemode1\n",
NULL
};
const double DECIMIL_TO_INCH = 0.0001;
time_t time1970 = time( NULL );
fputs( "%!PS-Adobe-3.0\n", output_file ); // Print header
fprintf( output_file, "%%%%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( output_file, "%%%%CreationDate: %s", ctime( &time1970 ) );
fprintf( output_file, "%%%%Title: %s\n", TO_UTF8( filename ) );
fprintf( output_file, "%%%%Pages: 1\n" );
fprintf( output_file, "%%%%PageOrder: Ascend\n" );
// Print boundary box in 1/72 pixels per inch, box is in deci-mils
const double CONV_SCALE = DECIMIL_TO_INCH * 72;
// 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.
fprintf( output_file, "%%%%BoundingBox: 0 0 %d %d\n",
(int) ceil( paper_size.y * CONV_SCALE ),
(int) ceil( paper_size.x * CONV_SCALE ) );
// 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.
wxSize pageSize = pageInfo.GetSizeMils();
if( pageInfo.GetType().Cmp( wxT( "User" ) ) == 0 )
fprintf( output_file, "%%%%DocumentMedia: Custom %d %d 0 () ()\n",
wxRound( pageSize.y * 10 * CONV_SCALE ),
wxRound( pageSize.x * 10 * CONV_SCALE ) );
else // ( if sheet->m_Name does not equal "User" )
fprintf( output_file, "%%%%DocumentMedia: %s %d %d 0 () ()\n",
TO_UTF8( pageInfo.GetType() ),
wxRound( pageSize.y * 10 * CONV_SCALE ),
wxRound( pageSize.x * 10 * CONV_SCALE ) );
fprintf( output_file, "%%%%Orientation: Landscape\n" );
fprintf( output_file, "%%%%EndComments\n" );
// Now specify various other details.
// 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.
fprintf( output_file, "%%%%Page: 1 1\n" );
for( int ii = 0; PSMacro[ii] != NULL; ii++ )
{
fputs( PSMacro[ii], output_file );
}
// (If support for creating postscript files with a portrait orientation
// is ever provided, determine whether it would be necessary to provide
// an "else" command and then an appropriate "sprintf" command here.)
fprintf( output_file, "%d 0 translate 90 rotate\n", paper_size.y );
// Apply the scale adjustments
if( plot_scale_adjX != 1.0 || plot_scale_adjY != 1.0 )
fprintf( output_file, "%g %g scale\n",
plot_scale_adjX, plot_scale_adjY );
// Set default line width ( g_Plot_DefaultPenWidth is in user units )
fprintf( output_file, "%g setlinewidth\n",
user_to_device_size( default_pen_width ) );
return true;
}
bool PS_PLOTTER::end_plot()
{
wxASSERT( output_file );
fputs( "showpage\ngrestore\n%%EOF\n", output_file );
fclose( output_file );
output_file = NULL;
return true;
}
/* Plot oval pad:
* pos - Position of pad.
* Dimensions dx, dy,
* Orient Orient
* The shape is drawn as a segment
*/
void PS_PLOTTER::flash_pad_oval( wxPoint pos, wxSize size, int orient,
EDA_DRAW_MODE_T modetrace )
{
wxASSERT( output_file );
int x0, y0, x1, y1, delta;
// The pad is reduced to an oval by dy > dx
if( size.x > size.y )
{
EXCHG( size.x, size.y );
orient += 900;
if( orient >= 3600 )
orient -= 3600;
}
delta = size.y - size.x;
x0 = 0;
y0 = -delta / 2;
x1 = 0;
y1 = delta / 2;
RotatePoint( &x0, &y0, orient );
RotatePoint( &x1, &y1, orient );
if( modetrace == FILLED )
thick_segment( wxPoint( pos.x + x0, pos.y + y0 ),
wxPoint( pos.x + x1, pos.y + y1 ), size.x, modetrace );
else
sketch_oval( pos, size, orient, -1 );
}
/* Plot round pad or via.
*/
void PS_PLOTTER::flash_pad_circle( wxPoint pos, int diametre,
EDA_DRAW_MODE_T modetrace )
{
int current_line_width;
wxASSERT( output_file );
set_current_line_width( -1 );
current_line_width = get_current_line_width();
if( current_line_width > diametre )
current_line_width = diametre;
if( modetrace == FILLED )
circle( pos, diametre - current_pen_width, FILLED_SHAPE, current_line_width );
else
circle( pos, diametre - current_pen_width, NO_FILL, current_line_width );
set_current_line_width( -1 );
}
/* Plot rectangular pad in any orientation.
*/
void PS_PLOTTER::flash_pad_rect( wxPoint pos, wxSize size,
int orient, EDA_DRAW_MODE_T trace_mode )
{
static std::vector< wxPoint > cornerList;
cornerList.clear();
set_current_line_width( -1 );
int w = current_pen_width;
size.x -= w;
if( size.x < 1 )
size.x = 1;
size.y -= w;
if( size.y < 1 )
size.y = 1;
int dx = size.x / 2;
int dy = size.y / 2;
wxPoint corner;
corner.x = pos.x - dx;
corner.y = pos.y + dy;
cornerList.push_back( corner );
corner.x = pos.x - dx;
corner.y = pos.y - dy;
cornerList.push_back( corner );
corner.x = pos.x + dx;
corner.y = pos.y - dy;
cornerList.push_back( corner );
corner.x = pos.x + dx;
corner.y = pos.y + dy,
cornerList.push_back( corner );
for( unsigned ii = 0; ii < cornerList.size(); ii++ )
{
RotatePoint( &cornerList[ii], pos, orient );
}
cornerList.push_back( cornerList[0] );
PlotPoly( cornerList, ( trace_mode == FILLED ) ? FILLED_SHAPE : NO_FILL );
}
/* Plot trapezoidal pad.
* aPadPos is pad position, aCorners the corners position of the basic shape
* Orientation aPadOrient in 0.1 degrees
* Plot mode FILLED or SKETCH
*/
void PS_PLOTTER::flash_pad_trapez( wxPoint aPadPos, wxPoint aCorners[4],
int aPadOrient, EDA_DRAW_MODE_T aTrace_Mode )
{
static std::vector< wxPoint > cornerList;
cornerList.clear();
for( int ii = 0; ii < 4; ii++ )
cornerList.push_back( aCorners[ii] );
if( aTrace_Mode == FILLED )
{
set_current_line_width( 0 );
}
else
{
set_current_line_width( -1 );
int w = current_pen_width;
// 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, ( aTrace_Mode == FILLED ) ? FILLED_SHAPE : NO_FILL );
}