1062 lines
28 KiB
C++
1062 lines
28 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
|
|
* Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
|
|
* Copyright (C) 1992-2021 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/********************************/
|
|
/* Low level graphics routines */
|
|
/********************************/
|
|
|
|
|
|
#include <gr_basic.h>
|
|
#include <trigo.h>
|
|
#include <eda_item.h>
|
|
#include <base_screen.h>
|
|
#include <bezier_curves.h>
|
|
#include <math_for_graphics.h>
|
|
#include <wx/graphics.h>
|
|
#include <geometry/geometry_utils.h>
|
|
#include <math/util.h> // for KiROUND
|
|
|
|
#include <algorithm>
|
|
|
|
static const bool FILLED = true;
|
|
static const bool NOT_FILLED = false;
|
|
|
|
/* Important Note:
|
|
* These drawing functions clip draw item before send these items to wxDC draw
|
|
* functions. For guy who asks why i did it, see a sample of problems encountered
|
|
* when pixels
|
|
* coordinates overflow 16 bits values:
|
|
* http://trac.wxwidgets.org/ticket/10446
|
|
* Problems can be found under Windows **and** Linux (mainly when drawing arcs)
|
|
* (mainly at low zoom values (2, 1 or 0.5), in Pcbnew)
|
|
* some of these problems could be now fixed in recent distributions.
|
|
*
|
|
* Currently (feb 2009) there are overflow problems when drawing solid (filled)
|
|
* polygons under linux without clipping
|
|
*
|
|
* So before removing clipping functions, be aware these bug (they are not in
|
|
* KiCad or wxWidgets) are fixed by testing how are drawn complex lines arcs
|
|
* and solid polygons under Windows and Linux and remember users can have old
|
|
* versions with bugs
|
|
*/
|
|
|
|
|
|
/* Definitions for enabling and disabling debugging features in gr_basic.cpp.
|
|
* Please remember to set these back to 0 before making LAUNCHPAD commits.
|
|
*/
|
|
#define DEBUG_DUMP_CLIP_ERROR_COORDS 0 // Set to 1 to dump clip algorithm errors.
|
|
#define DEBUG_DUMP_CLIP_COORDS 0 // Set to 1 to dump clipped coordinates.
|
|
|
|
|
|
// For draw mode = XOR GR_XOR or GR_NXOR by background color
|
|
GR_DRAWMODE g_XorMode = GR_NXOR;
|
|
|
|
|
|
static void ClipAndDrawPoly( EDA_RECT* ClipBox, wxDC* DC, const wxPoint* Points, int n );
|
|
|
|
/* These functions are used by corresponding functions
|
|
* ( GRSCircle is called by GRCircle for instance) after mapping coordinates
|
|
* from user units to screen units(pixels coordinates)
|
|
*/
|
|
static void GRSRect( EDA_RECT* aClipBox, wxDC* aDC, int x1, int y1,
|
|
int x2, int y2, int aWidth, COLOR4D aColor,
|
|
wxPenStyle aStyle = wxPENSTYLE_SOLID );
|
|
|
|
/**/
|
|
|
|
static int GRLastMoveToX, GRLastMoveToY;
|
|
static bool s_ForceBlackPen; /* if true: draws in black instead of
|
|
* color for printing. */
|
|
static int xcliplo = 0,
|
|
ycliplo = 0,
|
|
xcliphi = 2000,
|
|
ycliphi = 2000;
|
|
|
|
static COLOR4D s_DC_lastcolor( 0, 0, 0, 0 );
|
|
static COLOR4D s_DC_lastbrushcolor( 0, 0, 0, 0 );
|
|
static bool s_DC_lastbrushfill = false;
|
|
static wxDC* s_DC_lastDC = nullptr;
|
|
|
|
static void WinClipAndDrawLine( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int width )
|
|
{
|
|
GRLastMoveToX = x2;
|
|
GRLastMoveToY = y2;
|
|
|
|
if( ClipBox )
|
|
{
|
|
EDA_RECT clipbox(*ClipBox);
|
|
clipbox.Inflate(width/2);
|
|
if( ClipLine( &clipbox, x1, y1, x2, y2 ) )
|
|
return;
|
|
}
|
|
|
|
DC->DrawLine( x1, y1, x2, y2 );
|
|
}
|
|
|
|
|
|
/* Forcing a reset of the current pen.
|
|
* Must be called after changing the graphical device before any trace.
|
|
*/
|
|
void GRResetPenAndBrush( wxDC* DC )
|
|
{
|
|
GRSetBrush( DC, BLACK ); // Force no fill
|
|
s_DC_lastbrushcolor = COLOR4D::UNSPECIFIED;
|
|
s_DC_lastcolor = COLOR4D::UNSPECIFIED;
|
|
s_DC_lastDC = nullptr;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set a pen style, width, color, and alpha into the given device context.
|
|
*/
|
|
void GRSetColorPen( wxDC* DC, COLOR4D Color, int width, wxPenStyle style )
|
|
{
|
|
wxDash dots[2] = { 1, 3 };
|
|
|
|
// Under OSX and while printing when wxPen is set to 0, renderer follows the request drawing
|
|
// nothing & in the bitmap world the minimum is enough to light a pixel, in vectorial one not
|
|
if( width <= 1 )
|
|
width = DC->DeviceToLogicalXRel( 1 );
|
|
|
|
if( s_ForceBlackPen )
|
|
Color = COLOR4D::BLACK;
|
|
|
|
const wxPen& curr_pen = DC->GetPen();
|
|
|
|
if( !curr_pen.IsOk() || curr_pen.GetColour() != Color.ToColour()
|
|
|| curr_pen.GetWidth() != width
|
|
|| curr_pen.GetStyle() != style )
|
|
{
|
|
wxPen pen;
|
|
pen.SetColour( Color.ToColour() );
|
|
|
|
if( style == wxPENSTYLE_DOT )
|
|
{
|
|
style = wxPENSTYLE_USER_DASH;
|
|
pen.SetDashes( 2, dots );
|
|
}
|
|
|
|
pen.SetWidth( width );
|
|
pen.SetStyle( style );
|
|
DC->SetPen( pen );
|
|
}
|
|
else
|
|
{
|
|
// Should be not needed, but on Linux, in printing process
|
|
// the curr pen settings needs to be sometimes re-initialized
|
|
// Clearly, this is due to a bug, related to SetBrush(),
|
|
// but we have to live with it, at least on wxWidgets 3.0
|
|
DC->SetPen( curr_pen );
|
|
}
|
|
}
|
|
|
|
|
|
void GRSetBrush( wxDC* DC, COLOR4D Color, bool fill )
|
|
{
|
|
if( s_ForceBlackPen )
|
|
Color = COLOR4D::BLACK;
|
|
|
|
if( s_DC_lastbrushcolor != Color
|
|
|| s_DC_lastbrushfill != fill
|
|
|| s_DC_lastDC != DC )
|
|
{
|
|
wxBrush brush;
|
|
|
|
brush.SetColour( Color.ToColour() );
|
|
|
|
if( fill )
|
|
brush.SetStyle( wxBRUSHSTYLE_SOLID );
|
|
else
|
|
brush.SetStyle( wxBRUSHSTYLE_TRANSPARENT );
|
|
|
|
DC->SetBrush( brush );
|
|
|
|
s_DC_lastbrushcolor = Color;
|
|
s_DC_lastbrushfill = fill;
|
|
s_DC_lastDC = DC;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @param flagforce True to force a black pen whenever the asked color.
|
|
*/
|
|
void GRForceBlackPen( bool flagforce )
|
|
{
|
|
s_ForceBlackPen = flagforce;
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if a black pen was forced.
|
|
*/
|
|
bool GetGRForceBlackPenState( void )
|
|
{
|
|
return s_ForceBlackPen;
|
|
}
|
|
|
|
|
|
void GRPutPixel( EDA_RECT* ClipBox, wxDC* DC, int x, int y, COLOR4D Color )
|
|
{
|
|
if( ClipBox && !ClipBox->Contains( x, y ) )
|
|
return;
|
|
|
|
GRSetColorPen( DC, Color );
|
|
DC->DrawPoint( x, y );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a line, in object space.
|
|
*/
|
|
void GRLine( EDA_RECT* ClipBox,
|
|
wxDC* DC,
|
|
int x1,
|
|
int y1,
|
|
int x2,
|
|
int y2,
|
|
int width,
|
|
COLOR4D Color,
|
|
wxPenStyle aStyle)
|
|
{
|
|
GRSetColorPen( DC, Color, width, aStyle );
|
|
WinClipAndDrawLine( ClipBox, DC, x1, y1, x2, y2, width );
|
|
GRLastMoveToX = x2;
|
|
GRLastMoveToY = y2;
|
|
}
|
|
|
|
|
|
void GRLine( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aStart, wxPoint aEnd, int aWidth,
|
|
COLOR4D aColor, wxPenStyle aStyle )
|
|
{
|
|
GRLine( aClipBox, aDC, aStart.x, aStart.y, aEnd.x, aEnd.y, aWidth, aColor, aStyle );
|
|
}
|
|
|
|
|
|
/*
|
|
* Move to a new position, in object space.
|
|
*/
|
|
void GRMoveTo( int x, int y )
|
|
{
|
|
GRLastMoveToX = x;
|
|
GRLastMoveToY = y;
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw line to a new position, in object space.
|
|
*/
|
|
void GRLineTo( EDA_RECT* ClipBox, wxDC* DC, int x, int y, int width, COLOR4D Color )
|
|
{
|
|
GRLine( ClipBox, DC, GRLastMoveToX, GRLastMoveToY, x, y, width, Color );
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw an array of lines (not a polygon).
|
|
*
|
|
* @param aClipBox is the clip box.
|
|
* @param aDC is the device context into which drawing should occur.
|
|
* @param aLines is a list of pair of coordinate in user space: a pair for each line.
|
|
* @param aWidth is the width of each line.
|
|
* @param aColor is the color to draw the lines.
|
|
* @see COLOR4D
|
|
*/
|
|
void GRLineArray( EDA_RECT* aClipBox, wxDC* aDC, std::vector<wxPoint>& aLines,
|
|
int aWidth, COLOR4D aColor )
|
|
{
|
|
if( aLines.empty() )
|
|
return;
|
|
|
|
GRSetColorPen( aDC, aColor, aWidth );
|
|
|
|
if( aClipBox )
|
|
aClipBox->Inflate( aWidth / 2 );
|
|
|
|
for( unsigned i = 0; i < aLines.size(); i += 2 )
|
|
{
|
|
int x1 = aLines[i].x;
|
|
int y1 = aLines[i].y;
|
|
int x2 = aLines[i + 1].x;
|
|
int y2 = aLines[i + 1].y;
|
|
if( ( aClipBox == nullptr ) || !ClipLine( aClipBox, x1, y1, x2, y2 ) )
|
|
aDC->DrawLine( x1, y1, x2, y2 );
|
|
}
|
|
|
|
GRMoveTo( aLines[aLines.size() - 1].x, aLines[aLines.size() - 1].y );
|
|
|
|
if( aClipBox )
|
|
aClipBox->Inflate(-aWidth/2);
|
|
}
|
|
|
|
|
|
// Draw the outline of a thick segment with rounded ends
|
|
void GRCSegm( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int width, int aPenSize, COLOR4D Color )
|
|
{
|
|
GRLastMoveToX = x2;
|
|
GRLastMoveToY = y2;
|
|
|
|
if( ClipBox )
|
|
{
|
|
EDA_RECT clipbox(*ClipBox);
|
|
clipbox.Inflate(width/2);
|
|
|
|
if( ClipLine( &clipbox, x1, y1, x2, y2 ) )
|
|
return;
|
|
}
|
|
|
|
if( width <= 2 ) /* single line or 2 pixels */
|
|
{
|
|
GRSetColorPen( DC, Color, width );
|
|
DC->DrawLine( x1, y1, x2, y2 );
|
|
return;
|
|
}
|
|
|
|
GRSetBrush( DC, Color, NOT_FILLED );
|
|
GRSetColorPen( DC, Color, aPenSize );
|
|
|
|
int radius = (width + 1) >> 1;
|
|
int dx = x2 - x1;
|
|
int dy = y2 - y1;
|
|
double angle = -ArcTangente( dy, dx );
|
|
wxPoint start;
|
|
wxPoint end;
|
|
wxPoint org( x1, y1);
|
|
int len = (int) hypot( dx, dy );
|
|
|
|
// We know if the DC is mirrored, to draw arcs
|
|
int slx = DC->DeviceToLogicalX( 1 ) - DC->DeviceToLogicalX( 0 );
|
|
int sly = DC->DeviceToLogicalY( 1 ) - DC->DeviceToLogicalY( 0 );
|
|
bool mirrored = (slx > 0 && sly < 0) || (slx < 0 && sly > 0);
|
|
|
|
// first edge
|
|
start.x = 0;
|
|
start.y = radius;
|
|
end.x = len;
|
|
end.y = radius;
|
|
RotatePoint( &start, angle);
|
|
RotatePoint( &end, angle);
|
|
|
|
start += org;
|
|
end += org;
|
|
|
|
DC->DrawLine( start, end );
|
|
|
|
// first rounded end
|
|
end.x = 0;
|
|
end.y = -radius;
|
|
RotatePoint( &end, angle);
|
|
end += org;
|
|
|
|
if( !mirrored )
|
|
DC->DrawArc( end, start, org );
|
|
else
|
|
DC->DrawArc( start, end, org );
|
|
|
|
// second edge
|
|
start.x = len;
|
|
start.y = -radius;
|
|
RotatePoint( &start, angle);
|
|
start += org;
|
|
|
|
DC->DrawLine( start, end );
|
|
|
|
// second rounded end
|
|
end.x = len;
|
|
end.y = radius;
|
|
RotatePoint( &end, angle);
|
|
end += org;
|
|
|
|
if( !mirrored )
|
|
DC->DrawArc( end.x, end.y, start.x, start.y, x2, y2 );
|
|
else
|
|
DC->DrawArc( start.x, start.y, end.x, end.y, x2, y2 );
|
|
}
|
|
|
|
|
|
void GRCSegm( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int width, COLOR4D Color )
|
|
{
|
|
GRCSegm( ClipBox, DC, x1, y1, x2, y2, width, 0, Color );
|
|
}
|
|
|
|
|
|
void GRCSegm( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aStart, wxPoint aEnd,
|
|
int aWidth, COLOR4D aColor )
|
|
{
|
|
GRCSegm( aClipBox, aDC, aStart.x, aStart.y, aEnd.x, aEnd.y, aWidth, 0, aColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw segment (full) with rounded ends in object space (real coords.).
|
|
*/
|
|
void GRFillCSegm( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int width, COLOR4D Color )
|
|
{
|
|
GRSetColorPen( DC, Color, width );
|
|
WinClipAndDrawLine( ClipBox, DC, x1, y1, x2, y2, width );
|
|
}
|
|
|
|
|
|
void GRFilledSegment( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aStart, wxPoint aEnd,
|
|
int aWidth, COLOR4D aColor )
|
|
{
|
|
GRSetColorPen( aDC, aColor, aWidth );
|
|
WinClipAndDrawLine( aClipBox, aDC, aStart.x, aStart.y, aEnd.x, aEnd.y, aWidth );
|
|
}
|
|
|
|
|
|
static bool IsGRSPolyDrawable( EDA_RECT* ClipBox, int n, const wxPoint* Points )
|
|
{
|
|
if( !ClipBox )
|
|
return true;
|
|
|
|
if( n <= 0 )
|
|
return false;
|
|
|
|
int Xmin, Xmax, Ymin, Ymax;
|
|
|
|
Xmin = Xmax = Points[0].x;
|
|
Ymin = Ymax = Points[0].y;
|
|
|
|
for( int ii = 1; ii < n; ii++ ) // calculate rectangle
|
|
{
|
|
Xmin = std::min( Xmin, Points[ii].x );
|
|
Xmax = std::max( Xmax, Points[ii].x );
|
|
Ymin = std::min( Ymin, Points[ii].y );
|
|
Ymax = std::max( Ymax, Points[ii].y );
|
|
}
|
|
|
|
xcliplo = ClipBox->GetX();
|
|
ycliplo = ClipBox->GetY();
|
|
xcliphi = ClipBox->GetRight();
|
|
ycliphi = ClipBox->GetBottom();
|
|
|
|
if( Xmax < xcliplo )
|
|
return false;
|
|
if( Xmin > xcliphi )
|
|
return false;
|
|
if( Ymax < ycliplo )
|
|
return false;
|
|
if( Ymin > ycliphi )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a new polyline and fill it if Fill, in screen space.
|
|
*/
|
|
static void GRSPoly( EDA_RECT* ClipBox, wxDC* DC, int n, const wxPoint* Points, bool Fill,
|
|
int width, COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
if( !IsGRSPolyDrawable( ClipBox, n, Points ) )
|
|
return;
|
|
|
|
if( Fill && ( n > 2 ) )
|
|
{
|
|
GRSetBrush( DC, BgColor, FILLED );
|
|
GRSetColorPen( DC, Color, width );
|
|
|
|
/* clip before send the filled polygon to wxDC, because under linux
|
|
* (GTK?) polygons having large coordinates are incorrectly drawn
|
|
* (integer overflow in coordinates, I am guessing)
|
|
*/
|
|
ClipAndDrawPoly( ClipBox, DC, Points, n );
|
|
}
|
|
else
|
|
{
|
|
|
|
GRMoveTo( Points[0].x, Points[0].y );
|
|
|
|
for( int i = 1; i < n; ++i )
|
|
{
|
|
GRLineTo( ClipBox, DC, Points[i].x, Points[i].y, width, Color );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a new closed polyline and fill it if Fill, in screen space.
|
|
*/
|
|
static void GRSClosedPoly( EDA_RECT* aClipBox, wxDC* aDC, int aPointCount, const wxPoint* aPoints,
|
|
bool aFill, int aWidth, COLOR4D aColor, COLOR4D aBgColor )
|
|
{
|
|
if( !IsGRSPolyDrawable( aClipBox, aPointCount, aPoints ) )
|
|
return;
|
|
|
|
if( aFill && ( aPointCount > 2 ) )
|
|
{
|
|
GRLastMoveToX = aPoints[aPointCount - 1].x;
|
|
GRLastMoveToY = aPoints[aPointCount - 1].y;
|
|
GRSetBrush( aDC, aBgColor, FILLED );
|
|
GRSetColorPen( aDC, aColor, aWidth );
|
|
ClipAndDrawPoly( aClipBox, aDC, aPoints, aPointCount );
|
|
}
|
|
else
|
|
{
|
|
|
|
GRMoveTo( aPoints[0].x, aPoints[0].y );
|
|
|
|
for( int i = 1; i < aPointCount; ++i )
|
|
{
|
|
GRLineTo( aClipBox, aDC, aPoints[i].x, aPoints[i].y, aWidth, aColor );
|
|
}
|
|
|
|
int lastpt = aPointCount - 1;
|
|
|
|
// Close the polygon
|
|
if( aPoints[lastpt] != aPoints[0] )
|
|
{
|
|
GRLineTo( aClipBox, aDC, aPoints[0].x, aPoints[0].y, aWidth, aColor );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a new polyline and fill it if Fill, in drawing space.
|
|
*/
|
|
void GRPoly( EDA_RECT* ClipBox, wxDC* DC, int n, const wxPoint* Points, bool Fill, int width,
|
|
COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
GRSPoly( ClipBox, DC, n, Points, Fill, width, Color, BgColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a closed polyline and fill it if Fill, in object space.
|
|
*/
|
|
void GRClosedPoly( EDA_RECT* ClipBox, wxDC* DC, int n, const wxPoint* Points, bool Fill,
|
|
COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
GRClosedPoly( ClipBox, DC, n, Points, Fill, 0, Color, BgColor );
|
|
}
|
|
|
|
|
|
void GRClosedPoly( EDA_RECT* ClipBox, wxDC* DC, int n, const wxPoint* Points, bool Fill, int width,
|
|
COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
GRSClosedPoly( ClipBox, DC, n, Points, Fill, width, Color, BgColor );
|
|
}
|
|
|
|
|
|
static bool clipCircle( EDA_RECT* aClipBox, int xc, int yc, int r, int aWidth )
|
|
{
|
|
// Clip circles that are outside the ClipBox.
|
|
if( aClipBox )
|
|
{
|
|
int x0, y0, xm, ym;
|
|
x0 = aClipBox->GetX();
|
|
y0 = aClipBox->GetY();
|
|
xm = aClipBox->GetRight();
|
|
ym = aClipBox->GetBottom();
|
|
|
|
r += aWidth;
|
|
|
|
if( xc < ( x0 - r ) )
|
|
return true;
|
|
|
|
if( yc < ( y0 - r ) )
|
|
return true;
|
|
|
|
if( xc > ( r + xm ) )
|
|
return true;
|
|
|
|
if( yc > ( r + ym ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void GRCircle( EDA_RECT* ClipBox, wxDC* DC, int xc, int yc, int r, int width, COLOR4D Color )
|
|
{
|
|
if( clipCircle( ClipBox, xc, yc, r, width ) || r <= 0 )
|
|
return;
|
|
|
|
GRSetBrush( DC, Color, NOT_FILLED );
|
|
GRSetColorPen( DC, Color, width );
|
|
DC->DrawEllipse( xc - r, yc - r, r + r, r + r );
|
|
}
|
|
|
|
|
|
void GRCircle( EDA_RECT* ClipBox, wxDC* DC, int x, int y, int r, COLOR4D Color )
|
|
{
|
|
GRCircle( ClipBox, DC, x, y, r, 0, Color );
|
|
}
|
|
|
|
|
|
void GRCircle( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aPos, int aRadius, int aWidth,
|
|
COLOR4D aColor )
|
|
{
|
|
GRCircle( aClipBox, aDC, aPos.x, aPos.y, aRadius, aWidth, aColor );
|
|
}
|
|
|
|
|
|
void GRFilledCircle( EDA_RECT* ClipBox, wxDC* DC, int x, int y, int r,
|
|
int width, COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
if( clipCircle( ClipBox, x, y, r, width ) || r <= 0 )
|
|
return;
|
|
|
|
GRSetBrush( DC, BgColor, FILLED );
|
|
GRSetColorPen( DC, Color, width );
|
|
DC->DrawEllipse( x - r, y - r, r + r, r + r );
|
|
}
|
|
|
|
|
|
void GRFilledCircle( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aPos, int aRadius, COLOR4D aColor )
|
|
{
|
|
GRFilledCircle( aClipBox, aDC, aPos.x, aPos.y, aRadius, 0, aColor, aColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw an arc in user space.
|
|
*/
|
|
void GRArc1( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int xc, int yc, COLOR4D Color )
|
|
{
|
|
GRArc1( ClipBox, DC, x1, y1, x2, y2, xc, yc, 0, Color );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw an arc, width = width in user space.
|
|
*/
|
|
void GRArc1( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int xc, int yc, int width, COLOR4D Color )
|
|
{
|
|
/* Clip arcs off screen. */
|
|
if( ClipBox )
|
|
{
|
|
int x0, y0, xm, ym, r;
|
|
x0 = ClipBox->GetX();
|
|
y0 = ClipBox->GetY();
|
|
xm = ClipBox->GetRight();
|
|
ym = ClipBox->GetBottom();
|
|
r = KiROUND( Distance( x1, y1, xc, yc ) );
|
|
if( xc < ( x0 - r ) )
|
|
return;
|
|
if( yc < ( y0 - r ) )
|
|
return;
|
|
if( xc > ( r + xm ) )
|
|
return;
|
|
if( yc > ( r + ym ) )
|
|
return;
|
|
}
|
|
|
|
GRSetBrush( DC, Color );
|
|
GRSetColorPen( DC, Color, width );
|
|
DC->DrawArc( x1, y1, x2, y2, xc, yc );
|
|
}
|
|
|
|
|
|
void GRArc1( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aStart, wxPoint aEnd,
|
|
wxPoint aCenter, int aWidth, COLOR4D aColor )
|
|
{
|
|
GRArc1( aClipBox, aDC, aStart.x, aStart.y, aEnd.x, aEnd.y, aCenter.x, aCenter.y,
|
|
aWidth, aColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a filled arc in drawing space.
|
|
*/
|
|
void GRFilledArc( EDA_RECT* ClipBox,
|
|
wxDC* DC,
|
|
int x,
|
|
int y,
|
|
double StAngle,
|
|
double EndAngle,
|
|
int r,
|
|
int width,
|
|
COLOR4D Color,
|
|
COLOR4D BgColor )
|
|
{
|
|
int x1, y1, x2, y2;
|
|
|
|
/* Clip arcs off screen */
|
|
if( ClipBox )
|
|
{
|
|
int x0, y0, xm, ym;
|
|
x0 = ClipBox->GetX();
|
|
y0 = ClipBox->GetY();
|
|
xm = ClipBox->GetRight();
|
|
ym = ClipBox->GetBottom();
|
|
|
|
if( x < ( x0 - r - 1 ) )
|
|
return;
|
|
|
|
if( y < ( y0 - r - 1 ) )
|
|
return;
|
|
|
|
if( x > ( r + xm + 1 ) )
|
|
return;
|
|
|
|
if( y > ( r + ym + 1 ) )
|
|
return;
|
|
}
|
|
|
|
x1 = r;
|
|
y1 = 0;
|
|
RotatePoint( &x1, &y1, EndAngle );
|
|
|
|
x2 = r;
|
|
y2 = 0;
|
|
RotatePoint( &x2, &y2, StAngle );
|
|
|
|
GRSetBrush( DC, BgColor, FILLED );
|
|
GRSetColorPen( DC, Color, width );
|
|
DC->DrawArc( x + x1, y - y1, x + x2, y - y2, x, y );
|
|
}
|
|
|
|
|
|
void GRFilledArc( EDA_RECT* ClipBox, wxDC* DC, int x, int y,
|
|
double StAngle, double EndAngle, int r,
|
|
COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
GRFilledArc( ClipBox, DC, x, y, StAngle, EndAngle, r, 0, Color, BgColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw an arc in drawing space.
|
|
*/
|
|
void GRArc( EDA_RECT* ClipBox, wxDC* DC, int xc, int yc, double StAngle,
|
|
double EndAngle, int r, COLOR4D Color )
|
|
{
|
|
int x1, y1, x2, y2;
|
|
|
|
/* Clip arcs off screen */
|
|
if( ClipBox )
|
|
{
|
|
int radius = r + 1;
|
|
int x0, y0, xm, ym, x, y;
|
|
x0 = ClipBox->GetX();
|
|
y0 = ClipBox->GetY();
|
|
xm = ClipBox->GetRight();
|
|
ym = ClipBox->GetBottom();
|
|
x = xc;
|
|
y = yc;
|
|
|
|
if( x < ( x0 - radius ) )
|
|
return;
|
|
|
|
if( y < ( y0 - radius ) )
|
|
return;
|
|
|
|
if( x > ( xm + radius ) )
|
|
return;
|
|
|
|
if( y > ( ym + radius ) )
|
|
return;
|
|
}
|
|
|
|
x1 = r;
|
|
y1 = 0;
|
|
RotatePoint( &x1, &y1, EndAngle );
|
|
|
|
x2 = r;
|
|
y2 = 0;
|
|
RotatePoint( &x2, &y2, StAngle );
|
|
|
|
GRSetBrush( DC, Color, NOT_FILLED );
|
|
GRSetColorPen( DC, Color );
|
|
DC->DrawArc( xc + x1, yc - y1, xc + x2, yc - y2, xc, yc );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw an arc with width = width in drawing space.
|
|
*/
|
|
void GRArc( EDA_RECT* ClipBox,
|
|
wxDC* DC,
|
|
int x,
|
|
int y,
|
|
double StAngle,
|
|
double EndAngle,
|
|
int r,
|
|
int width,
|
|
COLOR4D Color )
|
|
{
|
|
int x1, y1, x2, y2;
|
|
|
|
/* Clip arcs off screen. */
|
|
if( ClipBox )
|
|
{
|
|
int x0, y0, xm, ym;
|
|
x0 = ClipBox->GetX();
|
|
y0 = ClipBox->GetY();
|
|
xm = ClipBox->GetRight();
|
|
ym = ClipBox->GetBottom();
|
|
|
|
if( x < ( x0 - r - width ) )
|
|
return;
|
|
|
|
if( y < ( y0 - r - width ) )
|
|
return;
|
|
|
|
if( x > ( r + xm + width ) )
|
|
return;
|
|
|
|
if( y > ( r + ym + width ) )
|
|
return;
|
|
}
|
|
|
|
x1 = r;
|
|
y1 = 0;
|
|
RotatePoint( &x1, &y1, EndAngle );
|
|
|
|
x2 = r;
|
|
y2 = 0;
|
|
RotatePoint( &x2, &y2, StAngle );
|
|
|
|
GRSetBrush( DC, Color );
|
|
GRSetColorPen( DC, Color, width );
|
|
DC->DrawArc( x + x1, y - y1, x + x2, y - y2, x, y );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a rectangle in drawing space.
|
|
*/
|
|
void GRRect( EDA_RECT* aClipBox, wxDC* aDC, int x1, int y1, int x2, int y2, COLOR4D aColor )
|
|
{
|
|
GRSRect( aClipBox, aDC, x1, y1, x2, y2, 0, aColor );
|
|
}
|
|
|
|
|
|
void GRRectPs( EDA_RECT* aClipBox, wxDC* aDC, const EDA_RECT& aRect, COLOR4D aColor,
|
|
wxPenStyle aStyle )
|
|
{
|
|
int x1 = aRect.GetX();
|
|
int y1 = aRect.GetY();
|
|
int x2 = aRect.GetRight();
|
|
int y2 = aRect.GetBottom();
|
|
|
|
GRSRect( aClipBox, aDC, x1, y1, x2, y2, 0, aColor, aStyle );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a rectangle (thick lines) in drawing space.
|
|
*/
|
|
void GRRect( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2, int width, COLOR4D Color )
|
|
{
|
|
GRSRect( ClipBox, DC, x1, y1, x2, y2, width, Color );
|
|
}
|
|
|
|
|
|
void GRRect( EDA_RECT* aClipBox, wxDC* aDC, const EDA_RECT& aRect, int aWidth, COLOR4D aColor )
|
|
{
|
|
int x1 = aRect.GetX();
|
|
int y1 = aRect.GetY();
|
|
int x2 = aRect.GetRight();
|
|
int y2 = aRect.GetBottom();
|
|
|
|
GRSRect( aClipBox, aDC, x1, y1, x2, y2, aWidth, aColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a rectangle (filled with AreaColor) in drawing space.
|
|
*/
|
|
void GRFilledRect( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
GRSFilledRect( ClipBox, DC, x1, y1, x2, y2, 0, Color, BgColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a rectangle (filled with AreaColor) in drawing space.
|
|
*/
|
|
void GRFilledRect( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2,
|
|
int width, COLOR4D Color, COLOR4D BgColor )
|
|
{
|
|
GRSFilledRect( ClipBox, DC, x1, y1, x2, y2, width, Color, BgColor );
|
|
}
|
|
|
|
|
|
/*
|
|
* Draw a rectangle in screen space.
|
|
*/
|
|
void GRSRect( EDA_RECT* aClipBox, wxDC* aDC, int x1, int y1, int x2, int y2,
|
|
int aWidth, COLOR4D aColor, wxPenStyle aStyle )
|
|
{
|
|
wxPoint points[5];
|
|
points[0] = wxPoint( x1, y1 );
|
|
points[1] = wxPoint( x1, y2 );
|
|
points[2] = wxPoint( x2, y2 );
|
|
points[3] = wxPoint( x2, y1 );
|
|
points[4] = points[0];
|
|
GRSClosedPoly( aClipBox, aDC, 5, points, NOT_FILLED, aWidth, aColor, aColor );
|
|
}
|
|
|
|
|
|
void GRSFilledRect( EDA_RECT* aClipBox, wxDC* aDC, int x1, int y1, int x2, int y2,
|
|
int aWidth, COLOR4D aColor, COLOR4D aBgColor )
|
|
{
|
|
wxPoint points[5];
|
|
points[0] = wxPoint( x1, y1 );
|
|
points[1] = wxPoint( x1, y2 );
|
|
points[2] = wxPoint( x2, y2 );
|
|
points[3] = wxPoint( x2, y1 );
|
|
points[4] = points[0];
|
|
|
|
GRSetBrush( aDC, aBgColor, FILLED );
|
|
GRSetColorPen( aDC, aBgColor, aWidth );
|
|
|
|
if( aClipBox && ( aWidth > 0 ) )
|
|
{
|
|
EDA_RECT clipbox( *aClipBox );
|
|
clipbox.Inflate( aWidth );
|
|
ClipAndDrawPoly( &clipbox, aDC, points, 5 ); // polygon approach is more accurate
|
|
}
|
|
else
|
|
{
|
|
ClipAndDrawPoly(aClipBox, aDC, points, 5 );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used to clip a polygon and draw it as Filled Polygon.
|
|
*
|
|
* Uses the Sutherland and Hodgman algo to clip the given poly against a rectangle. This
|
|
* rectangle is the drawing area this is useful under Linux (2009) because filled polygons
|
|
* are incorrectly drawn if they have too large coordinates (seems due to integer overflows
|
|
* in calculations). Could be removed in some years, if become unnecessary.
|
|
*/
|
|
|
|
#include <SutherlandHodgmanClipPoly.h>
|
|
|
|
void ClipAndDrawPoly( EDA_RECT* aClipBox, wxDC* aDC, const wxPoint* Points, int n )
|
|
{
|
|
if( aClipBox == nullptr )
|
|
{
|
|
aDC->DrawPolygon( n, Points );
|
|
return;
|
|
}
|
|
|
|
// A clip box exists: clip and draw the polygon.
|
|
static std::vector<wxPoint> clippedPolygon;
|
|
static pointVector inputPolygon, outputPolygon;
|
|
|
|
inputPolygon.clear();
|
|
outputPolygon.clear();
|
|
clippedPolygon.clear();
|
|
|
|
for( int ii = 0; ii < n; ii++ )
|
|
inputPolygon.push_back( PointF( (REAL) Points[ii].x, (REAL) Points[ii].y ) );
|
|
|
|
RectF window( (REAL) aClipBox->GetX(), (REAL) aClipBox->GetY(),
|
|
(REAL) aClipBox->GetWidth(), (REAL) aClipBox->GetHeight() );
|
|
|
|
SutherlandHodgman sh( window );
|
|
sh.Clip( inputPolygon, outputPolygon );
|
|
|
|
for( cpointIterator cit = outputPolygon.begin(); cit != outputPolygon.end(); ++cit )
|
|
{
|
|
clippedPolygon.emplace_back( KiROUND( cit->X ), KiROUND( cit->Y ) );
|
|
}
|
|
|
|
if( clippedPolygon.size() )
|
|
aDC->DrawPolygon( clippedPolygon.size(), &clippedPolygon[0] );
|
|
}
|
|
|
|
|
|
void GRBezier( EDA_RECT* aClipBox, wxDC* aDC, std::vector<wxPoint>& aPoint,
|
|
int aWidth, COLOR4D aColor )
|
|
{
|
|
std::vector<wxPoint> output;
|
|
|
|
BEZIER_POLY converter( aPoint );
|
|
converter.GetPoly( output, aWidth );
|
|
|
|
GRPoly( aClipBox, aDC, output.size(), &output[0], false, aWidth, aColor, aColor );
|
|
}
|
|
|
|
|
|
void GRDrawAnchor( EDA_RECT *aClipBox, wxDC *aDC, int x, int y, int aSize, COLOR4D aColor )
|
|
{
|
|
int anchor_size = aDC->DeviceToLogicalXRel( aSize );
|
|
|
|
GRLine( aClipBox, aDC, x - anchor_size, y, x + anchor_size, y, 0, aColor );
|
|
GRLine( aClipBox, aDC, x, y - anchor_size, x, y + anchor_size, 0, aColor );
|
|
}
|
|
|
|
|
|
void GRDrawWrappedText( wxDC& aDC, wxString const& aText )
|
|
{
|
|
wxStringTokenizer tokenizer( aText, " " );
|
|
wxSize const dc_size = aDC.GetSize();
|
|
wxSize const margin = aDC.GetTextExtent( " " );
|
|
std::vector<wxString> lines;
|
|
wxString line_accumulator;
|
|
int total_height = 0;
|
|
|
|
while( tokenizer.HasMoreTokens() )
|
|
{
|
|
wxString word = tokenizer.GetNextToken();
|
|
wxSize linesize = aDC.GetTextExtent( line_accumulator + " " + word );
|
|
|
|
if( linesize.x >= dc_size.x - margin.x && !line_accumulator.IsEmpty() )
|
|
{
|
|
lines.push_back( line_accumulator );
|
|
line_accumulator = word;
|
|
}
|
|
else
|
|
{
|
|
line_accumulator += " ";
|
|
line_accumulator += word;
|
|
}
|
|
}
|
|
|
|
if( !line_accumulator.IsEmpty() )
|
|
{
|
|
lines.push_back( line_accumulator );
|
|
}
|
|
|
|
for( auto const& line: lines )
|
|
{
|
|
wxSize linesize = aDC.GetTextExtent( line );
|
|
total_height += linesize.y;
|
|
}
|
|
|
|
int top = ( dc_size.y - total_height ) / 2;
|
|
int pos = top;
|
|
|
|
for( auto const& line: lines )
|
|
{
|
|
wxSize linesize = aDC.GetTextExtent( line );
|
|
aDC.DrawText( line, ( dc_size.x - linesize.x ) / 2, pos );
|
|
pos += linesize.y;
|
|
}
|
|
}
|