Skip to content

Commit

Permalink
Fixed drawing elliptic arcs in wxSVGFileDC.
Browse files Browse the repository at this point in the history
For some combinations of start and end angles, the wrong large-arc-flag was calculated. Fixed by correctly converting the wxDC angles to SVG angles (shift -90 degrees, and invert to clockwise direction).
Arcs with the same start and end point (circles) where not drawn because the angle becomes 0 degrees. Fixed by drawing two half circles.
Elliptic arcs with a non-transparent brush had an extra line from the center to the start point of the arc. Fixed by first drawing the arc without border, then only the border.
Arcs with small angles would become invisible because the start and end point map to the same (integer) coordinate. Very large arcs would be distorted because the start and end point coordinates did not line up. Using floating point values resolves this.
See issue #17557.
  • Loading branch information
MaartenBent committed Jun 4, 2016
1 parent 0ae6fd5 commit 1e78471
Showing 1 changed file with 62 additions and 31 deletions.
93 changes: 62 additions & 31 deletions src/common/dcsvg.cpp
Expand Up @@ -804,10 +804,10 @@ void wxSVGFileDCImpl::DoDrawArc(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2,
if (x1 == x2 && y1 == y2)
{
// drawing full circle fails with default arc. Draw two half arcs instead.
s.Printf(wxT("<path d=\"M%d %d a%s %s 0 %d %d %s %s a%s %s 0 %d %d %s %s"),
s.Printf(wxT("<path d=\"M%d %d a%g %g 0 %d %d %g %g a%g %g 0 %d %d %g %g"),
x1, y1,
NumStr(r1), NumStr(r2), fArc, fSweep, NumStr( r1*2), NumStr(0),
NumStr(r1), NumStr(r2), fArc, fSweep, NumStr(-r1*2), NumStr(0));
r1, r2, fArc, fSweep, r1*2, 0.0,
r1, r2, fArc, fSweep, -r1*2, 0.0);
}
else
{
Expand All @@ -816,8 +816,8 @@ void wxSVGFileDCImpl::DoDrawArc(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2,
if (GetBrush().GetStyle() != wxBRUSHSTYLE_TRANSPARENT)
line.Printf(wxT("L%d %d z"), xc, yc);

s.Printf(wxT("<path d=\"M%d %d A%s %s 0 %d %d %d %d %s"),
x1, y1, NumStr(r1), NumStr(r2), fArc, fSweep, x2, y2, line);
s.Printf(wxT("<path d=\"M%d %d A%g %g 0 %d %d %d %d %s"),
x1, y1, r1, r2, fArc, fSweep, x2, y2, line);
}

s += wxT("\" /> \n");
Expand All @@ -832,8 +832,7 @@ void wxSVGFileDCImpl::DoDrawEllipticArc(wxCoord x,wxCoord y,wxCoord w,wxCoord h,
{
/*
Draws an arc of an ellipse. The current pen is used for drawing the arc
and the current brush is used for drawing the pie. This function is
currently only available for X window and PostScript device contexts.
and the current brush is used for drawing the pie.
x and y specify the x and y coordinates of the upper-left corner of the
rectangle that contains the ellipse.
Expand All @@ -847,45 +846,77 @@ void wxSVGFileDCImpl::DoDrawEllipticArc(wxCoord x,wxCoord y,wxCoord w,wxCoord h,
counter-clockwise motion. If start is equal to end, a complete ellipse
will be drawn. */

//known bug: SVG draws with the current pen along the radii, but this does not happen in wxMSW

NewGraphicsIfNeeded();

wxString s;
//radius
double rx = w / 2;
double ry = h / 2;
double rx = w / 2.0;
double ry = h / 2.0;
// center
double xc = x + rx;
double yc = y + ry;

// start and end coords
double xs, ys, xe, ye;
xs = xc + rx * cos (wxDegToRad(sa));
xe = xc + rx * cos (wxDegToRad(ea));
ys = yc - ry * sin (wxDegToRad(sa));
ye = yc - ry * sin (wxDegToRad(ea));

///now same as circle arc...

double theta1 = atan2(ys-yc, xs-xc);
double theta2 = atan2(ye-yc, xe-xc);

int fArc; // flag for large or small arc 0 means less than 180 degrees
if ( (theta2 - theta1) > 0 ) fArc = 1; else fArc = 0;
// svg arcs have 0 degrees at 12-o'clock instead of 3-o'clock
double start = (sa - 90);
if (start < 0)
start += 360;
while (abs(start) > 360)
start -= (start / abs(start)) * 360;

double end = (ea - 90);
if (end < 0)
end += 360;
while (abs(end) > 360)
end -= (end / abs(end)) * 360;

// svg arcs are in clockwise direction, reverse angle
double angle = end - start;
if (angle <= 0)
angle += 360;

int fArc = angle > 180 ? 1 : 0; // flag for large or small arc
int fSweep = 0; // flag for sweep always 0

wxString arcPath;
if (angle == 360)
{
// Drawing full circle fails with default arc. Draw two half arcs instead.
fArc = 1;
arcPath.Printf(wxT("<path d=\"M%g %g a%g %g 0 %d %d %g %g a%g %g 0 %d %d %g %g"),
(double)x, y + ry,
rx, ry, fArc, fSweep, rx * 2, 0.0,
rx, ry, fArc, fSweep, -rx * 2, 0.0);
}
else
{
arcPath.Printf(wxT("<path d=\"M%g %g A%g %g 0 %d %d %g %g"),
xs, ys, rx, ry, fArc, fSweep, xe, ye);
}

int fSweep;
if ( fabs(theta2 - theta1) > M_PI) fSweep = 1; else fSweep = 0;
// Workaround so SVG does not draw an extra line from the centre of the drawn arc
// to the start point of the arc.
// First draw the arc with the current brush, without a border,
// then draw the border without filling the arc.
if (GetBrush().GetStyle() != wxBRUSHSTYLE_TRANSPARENT)
{
wxDCPenChanger setTransp(*GetOwner(), *wxTRANSPARENT_PEN);
NewGraphicsIfNeeded();

s.Printf ( wxT("<path d=\"M%d %d A%d %d 0.0 %d %d %d %d L %d %d z "),
int(xs), int(ys), int(rx), int(ry),
fArc, fSweep, int(xe), int(ye), int(xc), int(yc) );
wxString arcFill;
arcFill.Printf(wxT(" L%g %g z"), xc, yc);
arcFill = arcPath + arcFill + wxT("\" /> \n");
write(arcFill);
}

s += wxT(" \" /> \n");
wxDCBrushChanger setTransp(*GetOwner(), *wxTRANSPARENT_BRUSH);
NewGraphicsIfNeeded();

if (m_OK)
{
write(s);
}
wxString arcLine = arcPath + wxT("\" /> \n");
write(arcLine);
}

void wxSVGFileDCImpl::DoSetClippingRegion( int x, int y, int width, int height )
Expand Down

0 comments on commit 1e78471

Please sign in to comment.