Files
wxWidgets/src/common/svggc.cpp
T
Maarten Bent 8bf3e155a7 Support pen and brush styles in wxSVGGraphicsContext
SVG does not allow redefining patterns with the same ID, so write each pattern only once.
2026-06-13 22:21:30 +02:00

1566 lines
48 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/common/svggc.cpp
// Purpose: wxGraphicsContext/Renderer backed by wxSVGFileDC
// Author: Blake Madden
// Copyright: (c) Blake Madden
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#include "wx/wxprec.h"
#if wxUSE_SVG && wxUSE_GRAPHICS_CONTEXT
#ifndef WX_PRECOMP
#include "wx/bitmap.h"
#include "wx/icon.h"
#include "wx/image.h"
#include "wx/log.h"
#include "wx/math.h"
#include "wx/pen.h"
#include "wx/brush.h"
#include "wx/font.h"
#endif
#include "wx/svggc.h"
#include "wx/dcsvg.h"
#include "wx/private/graphics.h"
#include "wx/private/svg.h"
#include "wx/private/svggc.h"
#include <cmath>
#include <vector>
// ----------------------------------------------------------------------------
// wxSVGPathSegment
// ----------------------------------------------------------------------------
wxSVGPathSegment wxSVGPathSegment::MoveTo(wxDouble x, wxDouble y)
{
wxSVGPathSegment s;
s.type = MoveSegment;
s.x = x; s.y = y;
return s;
}
wxSVGPathSegment wxSVGPathSegment::LineTo(wxDouble x, wxDouble y)
{
wxSVGPathSegment s;
s.type = LineSegment;
s.x = x;
s.y = y;
return s;
}
wxSVGPathSegment wxSVGPathSegment::CurveTo(wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2, wxDouble x, wxDouble y)
{
wxSVGPathSegment s;
s.type = CurveSegment;
s.x1 = x1;
s.y1 = y1;
s.x2 = x2;
s.y2 = y2;
s.x = x;
s.y = y;
return s;
}
wxSVGPathSegment wxSVGPathSegment::QuadCurveTo(wxDouble cx, wxDouble cy, wxDouble x, wxDouble y)
{
wxSVGPathSegment s;
s.type = QuadCurveSegment;
s.x1 = cx;
s.y1 = cy;
s.x = x;
s.y = y;
return s;
}
wxSVGPathSegment wxSVGPathSegment::Arc(wxDouble x, wxDouble y, wxDouble r, wxDouble startAngle, wxDouble endAngle, bool clockwise)
{
wxSVGPathSegment s;
s.type = ArcSegment;
s.x = x;
s.y = y;
s.r = r;
s.startAngle = startAngle;
s.endAngle = endAngle;
s.clockwise = clockwise;
return s;
}
wxSVGPathSegment wxSVGPathSegment::Close()
{
wxSVGPathSegment s;
s.type = CloseSegment;
return s;
}
// ----------------------------------------------------------------------------
// wxSVGGraphicsMatrixData - standard 2D affine transformation matrix
// ----------------------------------------------------------------------------
wxSVGGraphicsMatrixData::wxSVGGraphicsMatrixData(wxGraphicsRenderer* renderer,
wxDouble a, wxDouble b,
wxDouble c, wxDouble d,
wxDouble tx, wxDouble ty)
: wxGraphicsMatrixData(renderer),
m_a(a), m_b(b), m_c(c), m_d(d), m_tx(tx), m_ty(ty)
{
}
wxGraphicsObjectRefData* wxSVGGraphicsMatrixData::Clone() const
{
return new wxSVGGraphicsMatrixData(GetRenderer(), m_a, m_b, m_c, m_d, m_tx, m_ty);
}
void wxSVGGraphicsMatrixData::Concat(const wxGraphicsMatrixData* t)
{
const auto* o = static_cast<const wxSVGGraphicsMatrixData*>(t);
const wxDouble a = o->m_a * m_a + o->m_b * m_c;
const wxDouble b = o->m_a * m_b + o->m_b * m_d;
const wxDouble c = o->m_c * m_a + o->m_d * m_c;
const wxDouble d = o->m_c * m_b + o->m_d * m_d;
const wxDouble tx = o->m_tx * m_a + o->m_ty * m_c + m_tx;
const wxDouble ty = o->m_tx * m_b + o->m_ty * m_d + m_ty;
m_a = a; m_b = b; m_c = c; m_d = d; m_tx = tx; m_ty = ty;
}
void wxSVGGraphicsMatrixData::Set(wxDouble a, wxDouble b, wxDouble c, wxDouble d,
wxDouble tx, wxDouble ty)
{
m_a = a; m_b = b; m_c = c; m_d = d; m_tx = tx; m_ty = ty;
}
void wxSVGGraphicsMatrixData::Get(wxDouble* a, wxDouble* b, wxDouble* c,
wxDouble* d, wxDouble* tx, wxDouble* ty) const
{
if ( a != nullptr )
*a = m_a;
if ( b != nullptr )
*b = m_b;
if ( c != nullptr )
*c = m_c;
if ( d != nullptr )
*d = m_d;
if ( tx != nullptr )
*tx = m_tx;
if ( ty != nullptr )
*ty = m_ty;
}
void wxSVGGraphicsMatrixData::Invert()
{
const wxDouble det = m_a * m_d - m_b * m_c;
if ( det == 0 )
return;
const wxDouble inv = 1.0 / det;
const wxDouble a = m_d * inv;
const wxDouble b = -m_b * inv;
const wxDouble c = -m_c * inv;
const wxDouble d = m_a * inv;
const wxDouble tx = (m_c * m_ty - m_d * m_tx) * inv;
const wxDouble ty = (m_b * m_tx - m_a * m_ty) * inv;
m_a = a; m_b = b; m_c = c; m_d = d; m_tx = tx; m_ty = ty;
}
bool wxSVGGraphicsMatrixData::IsEqual(const wxGraphicsMatrixData* t) const
{
const auto* o = static_cast<const wxSVGGraphicsMatrixData*>(t);
return m_a == o->m_a && m_b == o->m_b && m_c == o->m_c
&& m_d == o->m_d && m_tx == o->m_tx && m_ty == o->m_ty;
}
bool wxSVGGraphicsMatrixData::IsIdentity() const
{
return m_a == 1.0 && m_b == 0.0 && m_c == 0.0 && m_d == 1.0
&& m_tx == 0.0 && m_ty == 0.0;
}
void wxSVGGraphicsMatrixData::Translate(wxDouble dx, wxDouble dy)
{
m_tx += dx * m_a + dy * m_c;
m_ty += dx * m_b + dy * m_d;
}
void wxSVGGraphicsMatrixData::Scale(wxDouble xScale, wxDouble yScale)
{
m_a *= xScale;
m_b *= xScale;
m_c *= yScale;
m_d *= yScale;
}
void wxSVGGraphicsMatrixData::Rotate(wxDouble angle)
{
const wxDouble s = std::sin(angle);
const wxDouble c = std::cos(angle);
const wxDouble a = m_a * c + m_c * s;
const wxDouble b = m_b * c + m_d * s;
const wxDouble cc = -m_a * s + m_c * c;
const wxDouble dd = -m_b * s + m_d * c;
m_a = a;
m_b = b;
m_c = cc;
m_d = dd;
}
void wxSVGGraphicsMatrixData::TransformPoint(wxDouble* x, wxDouble* y) const
{
const wxDouble xx = *x * m_a + *y * m_c + m_tx;
const wxDouble yy = *x * m_b + *y * m_d + m_ty;
*x = xx;
*y = yy;
}
void wxSVGGraphicsMatrixData::TransformDistance(wxDouble* dx, wxDouble* dy) const
{
const wxDouble xx = *dx * m_a + *dy * m_c;
const wxDouble yy = *dx * m_b + *dy * m_d;
*dx = xx;
*dy = yy;
}
// ----------------------------------------------------------------------------
// wxSVGGraphicsPathData - records path commands for deferred SVG generation
// ----------------------------------------------------------------------------
wxSVGGraphicsPathData::wxSVGGraphicsPathData(wxGraphicsRenderer* renderer)
: wxGraphicsPathData(renderer),
m_curX(0), m_curY(0),
m_hasPoints(false),
m_minX(0), m_minY(0), m_maxX(0), m_maxY(0)
{
}
wxGraphicsObjectRefData* wxSVGGraphicsPathData::Clone() const
{
auto* p = new wxSVGGraphicsPathData(GetRenderer());
p->m_segments = m_segments;
p->m_curX = m_curX;
p->m_curY = m_curY;
p->m_hasPoints = m_hasPoints;
p->m_minX = m_minX;
p->m_minY = m_minY;
p->m_maxX = m_maxX;
p->m_maxY = m_maxY;
return p;
}
void wxSVGGraphicsPathData::MoveToPoint(wxDouble x, wxDouble y)
{
m_segments.push_back(wxSVGPathSegment::MoveTo(x, y));
m_curX = x; m_curY = y;
Extend(x, y);
}
void wxSVGGraphicsPathData::AddLineToPoint(wxDouble x, wxDouble y)
{
m_segments.push_back(wxSVGPathSegment::LineTo(x, y));
m_curX = x; m_curY = y;
Extend(x, y);
}
void wxSVGGraphicsPathData::AddCurveToPoint(wxDouble cx1, wxDouble cy1,
wxDouble cx2, wxDouble cy2,
wxDouble x, wxDouble y)
{
m_segments.push_back(wxSVGPathSegment::CurveTo(cx1, cy1, cx2, cy2, x, y));
m_curX = x; m_curY = y;
Extend(cx1, cy1);
Extend(cx2, cy2);
Extend(x, y);
}
void wxSVGGraphicsPathData::AddQuadCurveToPoint(wxDouble cx, wxDouble cy,
wxDouble x, wxDouble y)
{
m_segments.push_back(wxSVGPathSegment::QuadCurveTo(cx, cy, x, y));
m_curX = x; m_curY = y;
Extend(cx, cy);
Extend(x, y);
}
void wxSVGGraphicsPathData::AddRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h)
{
wxGraphicsPathData::AddRectangle(x, y, w, h);
}
void wxSVGGraphicsPathData::AddCircle(wxDouble x, wxDouble y, wxDouble r)
{
// A full circle is drawn as two arcs to avoid the SVG arc command's ambiguity
// with 360-degree sweeps.
MoveToPoint(x + r, y);
AddArc(x, y, r, 0.0, M_PI, true);
AddArc(x, y, r, M_PI, 2.0 * M_PI, true);
CloseSubpath();
}
void wxSVGGraphicsPathData::AddArcToPoint(wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2, wxDouble r)
{
wxGraphicsPathData::AddArcToPoint(x1, y1, x2, y2, r);
}
void wxSVGGraphicsPathData::AddEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h)
{
wxGraphicsPathData::AddEllipse(x, y, w, h);
}
void wxSVGGraphicsPathData::AddRoundedRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h, wxDouble radius)
{
wxGraphicsPathData::AddRoundedRectangle(x, y, w, h, radius);
}
void wxSVGGraphicsPathData::AddPath(const wxGraphicsPathData* path)
{
const auto* o = static_cast<const wxSVGGraphicsPathData*>(path);
m_segments.insert(m_segments.end(), o->m_segments.begin(), o->m_segments.end());
m_curX = o->m_curX;
m_curY = o->m_curY;
if ( o->m_hasPoints )
{
Extend(o->m_minX, o->m_minY);
Extend(o->m_maxX, o->m_maxY);
}
}
void wxSVGGraphicsPathData::CloseSubpath()
{
m_segments.push_back(wxSVGPathSegment::Close());
}
void wxSVGGraphicsPathData::GetCurrentPoint(wxDouble* x, wxDouble* y) const
{
if ( x != nullptr )
*x = m_curX;
if ( y != nullptr )
*y = m_curY;
}
void wxSVGGraphicsPathData::AddArc(wxDouble x, wxDouble y, wxDouble r,
wxDouble startAngle, wxDouble endAngle, bool clockwise)
{
// Calculate start and end points of the arc for SVG path generation.
const wxDouble sx = x + r * std::cos(startAngle);
const wxDouble sy = y + r * std::sin(startAngle);
const wxDouble ex = x + r * std::cos(endAngle);
const wxDouble ey = y + r * std::sin(endAngle);
if ( !m_hasPoints )
MoveToPoint(sx, sy);
else
AddLineToPoint(sx, sy);
m_segments.push_back(wxSVGPathSegment::Arc(x, y, r, startAngle, endAngle, clockwise));
m_curX = ex; m_curY = ey;
Extend(x - r, y - r);
Extend(x + r, y + r);
}
void* wxSVGGraphicsPathData::GetNativePath() const
{
// Regenerate the "d" string from segments on demand.
const_cast<wxSVGGraphicsPathData*>(this)->m_d = GetDString();
return const_cast<wxString*>(&m_d);
}
void wxSVGGraphicsPathData::Transform(const wxGraphicsMatrixData* matrix)
{
if ( matrix == nullptr )
return;
const auto* m = static_cast<const wxSVGGraphicsMatrixData*>(matrix);
std::vector<wxSVGPathSegment> oldSegments = m_segments;
m_segments.clear();
m_hasPoints = false;
// Arcs must be converted to cubic Béziers before transformation
// because a transformed elliptical arc is generally a generic conic.
for ( const auto& s : oldSegments )
{
if ( s.type == wxSVGPathSegment::ArcSegment )
{
const wxDouble sx = s.x + s.r * std::cos(s.startAngle);
const wxDouble sy = s.y + s.r * std::sin(s.startAngle);
if ( !m_hasPoints )
MoveToPoint(sx, sy);
else
AddLineToPoint(sx, sy);
AddArcToCubics(s.x, s.y, s.r, s.startAngle, s.endAngle, s.clockwise);
}
else
{
m_segments.push_back(s);
if ( s.type != wxSVGPathSegment::CloseSegment )
m_hasPoints = true;
}
}
// Apply the transformation matrix to all points in the path.
m_hasPoints = false;
for ( auto& s : m_segments )
{
switch ( s.type )
{
case wxSVGPathSegment::MoveSegment:
case wxSVGPathSegment::LineSegment:
m->TransformPoint(&s.x, &s.y);
Extend(s.x, s.y);
break;
case wxSVGPathSegment::CurveSegment:
m->TransformPoint(&s.x1, &s.y1);
m->TransformPoint(&s.x2, &s.y2);
m->TransformPoint(&s.x, &s.y);
Extend(s.x1, s.y1);
Extend(s.x2, s.y2);
Extend(s.x, s.y);
break;
case wxSVGPathSegment::QuadCurveSegment:
m->TransformPoint(&s.x1, &s.y1);
m->TransformPoint(&s.x, &s.y);
Extend(s.x1, s.y1);
Extend(s.x, s.y);
break;
case wxSVGPathSegment::ArcSegment:
wxFAIL_MSG("arc segments should have been converted to cubics");
break;
case wxSVGPathSegment::CloseSegment:
break;
}
}
if ( !m_segments.empty() )
{
const auto& last = m_segments.back();
if ( last.type != wxSVGPathSegment::CloseSegment )
{
m_curX = last.x;
m_curY = last.y;
}
}
}
void wxSVGGraphicsPathData::GetBox(wxDouble* x, wxDouble* y, wxDouble* w, wxDouble* h) const
{
if ( !m_hasPoints )
{
if ( x != nullptr )
*x = 0;
if ( y != nullptr)
*y = 0;
if ( w != nullptr )
*w = 0;
if ( h != nullptr )
*h = 0;
return;
}
if ( x != nullptr )
*x = m_minX;
if ( y != nullptr )
*y = m_minY;
if ( w != nullptr )
*w = m_maxX - m_minX;
if ( h != nullptr )
*h = m_maxY - m_minY;
}
bool wxSVGGraphicsPathData::Contains(wxDouble x, wxDouble y,
wxPolygonFillMode fillStyle) const
{
if ( m_segments.empty() )
return false;
// Fast bounding box rejection.
wxDouble bx, by, bw, bh;
GetBox(&bx, &by, &bw, &bh);
if ( x < bx || x > bx + bw || y < by || y > by + bh )
return false;
int windingNumber = 0;
wxPoint2DDouble curPt, startPt;
bool hasStartPt = false;
// Helper to process a single line segment for the winding number algorithm.
const auto processEdge = [&](const wxPoint2DDouble& p1, const wxPoint2DDouble& p2)
{
if ( p1.m_y <= y )
{
if ( p2.m_y > y && (p2.m_x - p1.m_x) * (y - p1.m_y) - (x - p1.m_x) * (p2.m_y - p1.m_y) > 0 )
windingNumber++;
}
else
{
if ( p2.m_y <= y && (p2.m_x - p1.m_x) * (y - p1.m_y) - (x - p1.m_x) * (p2.m_y - p1.m_y) < 0 )
windingNumber--;
}
};
for ( const auto& seg : m_segments )
{
switch ( seg.type )
{
case wxSVGPathSegment::MoveSegment:
if ( hasStartPt )
processEdge(curPt, startPt);
curPt = { seg.x, seg.y };
startPt = curPt;
hasStartPt = true;
break;
case wxSVGPathSegment::LineSegment:
processEdge(curPt, { seg.x, seg.y });
curPt = { seg.x, seg.y };
break;
case wxSVGPathSegment::CurveSegment:
{
const wxPoint2DDouble p0 = curPt;
const wxPoint2DDouble p1 = { seg.x1, seg.y1 };
const wxPoint2DDouble p2 = { seg.x2, seg.y2 };
const wxPoint2DDouble p3 = { seg.x, seg.y };
constexpr int steps = 10;
for ( int i = 1; i <= steps; ++i )
{
const double t = i / (double)steps;
const double tInv = 1.0 - t;
const wxPoint2DDouble next = {
tInv * tInv * tInv * p0.m_x + 3 * tInv * tInv * t * p1.m_x + 3 * tInv * t * t * p2.m_x + t * t * t * p3.m_x,
tInv * tInv * tInv * p0.m_y + 3 * tInv * tInv * t * p1.m_y + 3 * tInv * t * t * p2.m_y + t * t * t * p3.m_y
};
processEdge(curPt, next);
curPt = next;
}
break;
}
case wxSVGPathSegment::QuadCurveSegment:
{
const wxPoint2DDouble p0 = curPt;
const wxPoint2DDouble p1 = { seg.x1, seg.y1 };
const wxPoint2DDouble p2 = { seg.x, seg.y };
constexpr int steps = 10;
for ( int i = 1; i <= steps; ++i )
{
const double t = i / static_cast<double>(steps);
const double tInv = 1.0 - t;
const wxPoint2DDouble next = {
tInv * tInv * p0.m_x + 2 * tInv * t * p1.m_x + t * t * p2.m_x,
tInv * tInv * p0.m_y + 2 * tInv * t * p1.m_y + t * t * p2.m_y
};
processEdge(curPt, next);
curPt = next;
}
break;
}
case wxSVGPathSegment::ArcSegment:
{
wxDouble sweep = seg.endAngle - seg.startAngle;
constexpr wxDouble twoPi = 2.0 * M_PI;
if ( std::abs(sweep) >= twoPi - 1e-9 )
{
sweep = seg.clockwise ? twoPi : -twoPi;
}
else
{
if ( seg.clockwise )
{
while ( sweep <= 0 )
sweep += twoPi;
while ( sweep > twoPi )
sweep -= twoPi;
}
else
{
while ( sweep >= 0 )
sweep -= twoPi;
while ( sweep < -twoPi )
sweep += twoPi;
}
}
const int steps = std::max(1, (int)std::ceil(std::abs(sweep) / (M_PI / 10.0)));
const double angleStep = sweep / steps;
for ( int i = 1; i <= steps; ++i )
{
const double ang = seg.startAngle + i * angleStep;
const wxPoint2DDouble next = { seg.x + seg.r * std::cos(ang), seg.y + seg.r * std::sin(ang) };
processEdge(curPt, next);
curPt = next;
}
break;
}
case wxSVGPathSegment::CloseSegment:
if ( hasStartPt )
{
processEdge(curPt, startPt);
hasStartPt = false;
}
break;
}
}
if ( hasStartPt )
processEdge(curPt, startPt);
if ( fillStyle == wxODDEVEN_RULE )
return (windingNumber % 2) != 0;
return windingNumber != 0;
}
// Generates the SVG "d" attribute string from recorded segments.
wxString wxSVGGraphicsPathData::GetDString() const
{
wxString d;
for ( const auto& s : m_segments )
{
switch ( s.type )
{
case wxSVGPathSegment::MoveSegment:
d += wxString::Format(wxS("M %s %s "),
wxSVG::NumStr(s.x), wxSVG::NumStr(s.y));
break;
case wxSVGPathSegment::LineSegment:
d += wxString::Format(wxS("L %s %s "),
wxSVG::NumStr(s.x), wxSVG::NumStr(s.y));
break;
case wxSVGPathSegment::CurveSegment:
d += wxString::Format(wxS("C %s %s %s %s %s %s "),
wxSVG::NumStr(s.x1), wxSVG::NumStr(s.y1),
wxSVG::NumStr(s.x2), wxSVG::NumStr(s.y2),
wxSVG::NumStr(s.x), wxSVG::NumStr(s.y));
break;
case wxSVGPathSegment::QuadCurveSegment:
d += wxString::Format(wxS("Q %s %s %s %s "),
wxSVG::NumStr(s.x1), wxSVG::NumStr(s.y1),
wxSVG::NumStr(s.x), wxSVG::NumStr(s.y));
break;
case wxSVGPathSegment::ArcSegment:
{
wxDouble sweep = s.endAngle - s.startAngle;
constexpr wxDouble twoPi = 2 * M_PI;
if ( std::abs(sweep) >= twoPi - 1e-9 )
{
sweep = twoPi;
}
else
{
if ( s.clockwise )
{
while ( sweep <= 0 )
sweep += twoPi;
while ( sweep > twoPi )
sweep -= twoPi;
}
else
{
while ( sweep >= 0 )
sweep -= twoPi;
while ( sweep < -twoPi )
sweep += twoPi;
sweep = -sweep;
}
}
if ( sweep >= twoPi - 1e-9 )
{
const wxDouble ox = s.x - s.r, oy = s.y;
d += wxString::Format(wxS("A %s %s 0 1 %d %s %s "),
wxSVG::NumStr(s.r), wxSVG::NumStr(s.r),
s.clockwise ? 1 : 0,
wxSVG::NumStr(s.x + s.r), wxSVG::NumStr(s.y));
d += wxString::Format(wxS("A %s %s 0 1 %d %s %s "),
wxSVG::NumStr(s.r), wxSVG::NumStr(s.r),
s.clockwise ? 1 : 0,
wxSVG::NumStr(ox), wxSVG::NumStr(oy));
}
else
{
const wxDouble ex = s.x + s.r * std::cos(s.endAngle);
const wxDouble ey = s.y + s.r * std::sin(s.endAngle);
const int large = sweep > M_PI ? 1 : 0;
d += wxString::Format(wxS("A %s %s 0 %d %d %s %s "),
wxSVG::NumStr(s.r), wxSVG::NumStr(s.r),
large, s.clockwise ? 1 : 0,
wxSVG::NumStr(ex), wxSVG::NumStr(ey));
}
break;
}
case wxSVGPathSegment::CloseSegment:
d += wxS("Z ");
break;
}
}
return d;
}
void wxSVGGraphicsPathData::Extend(wxDouble x, wxDouble y)
{
if ( !m_hasPoints )
{
m_minX = m_maxX = x;
m_minY = m_maxY = y;
m_hasPoints = true;
return;
}
if ( x < m_minX )
m_minX = x;
if ( x > m_maxX )
m_maxX = x;
if ( y < m_minY )
m_minY = y;
if ( y > m_maxY )
m_maxY = y;
}
// Approximates a circular arc with a series of cubic Béziers.
void wxSVGGraphicsPathData::AddArcToCubics(wxDouble xc, wxDouble yc, wxDouble r,
wxDouble startAngle, wxDouble endAngle, bool clockwise)
{
wxDouble sweep = endAngle - startAngle;
constexpr wxDouble twoPi = 2.0 * M_PI;
if ( std::abs(sweep) >= twoPi - 1e-9 )
{
sweep = clockwise ? twoPi : -twoPi;
}
else
{
if ( clockwise )
{
while ( sweep <= 0 )
sweep += twoPi;
while ( sweep > twoPi )
sweep -= twoPi;
}
else
{
while ( sweep >= 0 )
sweep -= twoPi;
while ( sweep < -twoPi )
sweep += twoPi;
}
}
if ( std::abs(sweep) < 1e-9 )
return;
int n = std::ceil(std::abs(sweep) / (M_PI / 2.0));
wxDouble angleStep = sweep / n;
wxDouble alpha = angleStep / 2.0;
wxDouble kappa = 4.0 / 3.0 * (1.0 - std::cos(alpha)) / std::sin(alpha);
wxDouble a1 = startAngle;
for ( int i = 0; i < n; ++i )
{
wxDouble a2 = a1 + angleStep;
wxDouble cos_a1 = std::cos(a1);
wxDouble sin_a1 = std::sin(a1);
wxDouble cos_a2 = std::cos(a2);
wxDouble sin_a2 = std::sin(a2);
wxDouble cx1 = xc + r * (cos_a1 - kappa * sin_a1);
wxDouble cy1 = yc + r * (sin_a1 + kappa * cos_a1);
wxDouble cx2 = xc + r * (cos_a2 + kappa * sin_a2);
wxDouble cy2 = yc + r * (sin_a2 - kappa * cos_a2);
wxDouble x = xc + r * cos_a2;
wxDouble y = yc + r * sin_a2;
m_segments.push_back(wxSVGPathSegment::CurveTo(cx1, cy1, cx2, cy2, x, y));
a1 = a2;
}
}
// ----------------------------------------------------------------------------
// wxSVGGraphicsPenData / BrushData / FontData / BitmapData
// ----------------------------------------------------------------------------
wxSVGGraphicsPenData::wxSVGGraphicsPenData(wxGraphicsRenderer* renderer,
const wxGraphicsPenInfo& info)
: wxGraphicsObjectRefData(renderer), m_info(info)
{
m_pen.SetColour(info.GetColour());
m_pen.SetWidth(static_cast<int>(info.GetWidth() + 0.5));
m_pen.SetStyle(info.GetStyle());
m_pen.SetCap(info.GetCap());
m_pen.SetJoin(info.GetJoin());
if (m_pen.GetStyle() == wxPENSTYLE_USER_DASH)
{
wxDash* dashes;
if (int nb_dashes = info.GetDashes(&dashes))
m_pen.SetDashes(nb_dashes, dashes);
}
if (m_pen.GetStyle() == wxPENSTYLE_STIPPLE)
{
m_pen.SetStipple(info.GetStipple());
}
}
wxSVGGraphicsBrushData::wxSVGGraphicsBrushData(wxGraphicsRenderer* renderer, const wxBrush& brush)
: wxGraphicsObjectRefData(renderer), m_brush(brush)
{
}
wxSVGGraphicsBrushData::wxSVGGraphicsBrushData(wxGraphicsRenderer* renderer,
wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
: wxGraphicsObjectRefData(renderer),
m_isGradient(true),
m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2),
m_stops(stops), m_matrix(matrix)
{
}
wxSVGGraphicsBrushData::wxSVGGraphicsBrushData(wxGraphicsRenderer* renderer,
wxDouble startX, wxDouble startY, wxDouble endX, wxDouble endY,
wxDouble radius,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
: wxGraphicsObjectRefData(renderer),
m_isGradient(true), m_isRadial(true),
m_x1(startX), m_y1(startY), m_x2(endX), m_y2(endY), m_radius(radius),
m_stops(stops), m_matrix(matrix)
{
}
wxSVGGraphicsFontData::wxSVGGraphicsFontData(wxGraphicsRenderer* renderer,
const wxFont& font, const wxColour& colour)
: wxGraphicsObjectRefData(renderer), m_font(font), m_colour(colour)
{
}
wxSVGGraphicsBitmapData::wxSVGGraphicsBitmapData(wxGraphicsRenderer* renderer, const wxBitmap& bmp)
: wxGraphicsObjectRefData(renderer), m_bitmap(bmp)
{
}
wxGraphicsObjectRefData* wxSVGGraphicsBitmapData::Clone() const
{
return new wxSVGGraphicsBitmapData(GetRenderer(), m_bitmap);
}
// ----------------------------------------------------------------------------
// wxSVGGraphicsRenderer
// ----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(wxSVGGraphicsRenderer, wxGraphicsRenderer);
wxGraphicsRenderer* wxSVGGraphicsRenderer::Get()
{
static wxSVGGraphicsRenderer s_renderer;
return &s_renderer;
}
// These methods are not applicable to a text-based SVG renderer.
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContext(const wxWindowDC&) { return nullptr; }
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContext(const wxMemoryDC&) { return nullptr; }
#if wxUSE_PRINTING_ARCHITECTURE
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContext(const wxPrinterDC&) { return nullptr; }
#endif
#ifdef __WXMSW__
#if wxUSE_ENH_METAFILE
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContext(const wxEnhMetaFileDC&) { return nullptr; }
#endif
#endif
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContextFromNativeContext(void*) { return nullptr; }
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContextFromNativeWindow(void*) { return nullptr; }
#ifdef __WXMSW__
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContextFromNativeHDC(WXHDC) { return nullptr; }
#endif
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContext(wxWindow*) { return nullptr; }
#if wxUSE_IMAGE
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContextFromImage(wxImage&) { return nullptr; }
#endif
wxGraphicsContext* wxSVGGraphicsRenderer::CreateContext(wxSVGFileDC& dc)
{
return wxSVGGraphicsContext::Create(dc);
}
wxGraphicsContext* wxSVGGraphicsRenderer::CreateMeasuringContext() { return nullptr; }
wxGraphicsPath wxSVGGraphicsRenderer::CreatePath()
{
wxGraphicsPath p;
p.SetRefData(new wxSVGGraphicsPathData(this));
return p;
}
wxGraphicsMatrix wxSVGGraphicsRenderer::CreateMatrix(wxDouble a, wxDouble b,
wxDouble c, wxDouble d,
wxDouble tx, wxDouble ty)
{
wxGraphicsMatrix m;
m.SetRefData(new wxSVGGraphicsMatrixData(this, a, b, c, d, tx, ty));
return m;
}
wxGraphicsPen wxSVGGraphicsRenderer::CreatePen(const wxGraphicsPenInfo& info)
{
if ( info.GetStyle() == wxPENSTYLE_TRANSPARENT )
return wxNullGraphicsPen;
wxGraphicsPen p;
p.SetRefData(new wxSVGGraphicsPenData(this, info));
return p;
}
wxGraphicsBrush wxSVGGraphicsRenderer::CreateBrush(const wxBrush& brush)
{
if ( !brush.IsOk() || brush.GetStyle() == wxBRUSHSTYLE_TRANSPARENT )
return wxNullGraphicsBrush;
wxGraphicsBrush b;
b.SetRefData(new wxSVGGraphicsBrushData(this, brush));
return b;
}
wxGraphicsBrush
wxSVGGraphicsRenderer::CreateLinearGradientBrush(wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
{
wxGraphicsBrush b;
b.SetRefData(new wxSVGGraphicsBrushData(this, x1, y1, x2, y2, stops, matrix));
return b;
}
wxGraphicsBrush
wxSVGGraphicsRenderer::CreateRadialGradientBrush(wxDouble startX, wxDouble startY, wxDouble endX, wxDouble endY,
wxDouble radius,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
{
wxGraphicsBrush b;
b.SetRefData(new wxSVGGraphicsBrushData(this, startX, startY, endX, endY, radius, stops, matrix));
return b;
}
wxGraphicsFont wxSVGGraphicsRenderer::CreateFont(const wxFont& font, const wxColour& col)
{
if ( !font.IsOk() )
return wxNullGraphicsFont;
wxGraphicsFont f;
f.SetRefData(new wxSVGGraphicsFontData(this, font, col));
return f;
}
wxGraphicsFont wxSVGGraphicsRenderer::CreateFont(double sizeInPixels,
const wxString& facename,
int flags,
const wxColour& col)
{
return CreateFont(
wxFont(wxFontInfo(sizeInPixels).FaceName(facename).AllFlags(flags)), col);
}
wxGraphicsFont wxSVGGraphicsRenderer::CreateFontAtDPI(const wxFont& font,
const wxRealPoint&,
const wxColour& col)
{
return CreateFont(font, col);
}
wxGraphicsBitmap wxSVGGraphicsRenderer::CreateBitmap(const wxBitmap& bmp)
{
if ( !bmp.IsOk() )
return wxNullGraphicsBitmap;
wxGraphicsBitmap b;
b.SetRefData(new wxSVGGraphicsBitmapData(this, bmp));
return b;
}
#if wxUSE_IMAGE
wxGraphicsBitmap wxSVGGraphicsRenderer::CreateBitmapFromImage(const wxImage& image)
{
if ( !image.IsOk() )
return wxNullGraphicsBitmap;
return CreateBitmap(wxBitmap(image));
}
wxImage wxSVGGraphicsRenderer::CreateImageFromBitmap(const wxGraphicsBitmap& bmp)
{
auto* data = static_cast<wxSVGGraphicsBitmapData*>(bmp.GetRefData());
if ( data != nullptr )
return data->GetBitmap().ConvertToImage();
return wxNullImage;
}
#endif
wxGraphicsBitmap wxSVGGraphicsRenderer::CreateBitmapFromNativeBitmap(void*)
{
// Not applicable for SVG.
return wxNullGraphicsBitmap;
}
wxGraphicsBitmap wxSVGGraphicsRenderer::CreateSubBitmap(const wxGraphicsBitmap& bmp,
wxDouble x, wxDouble y,
wxDouble w, wxDouble h)
{
auto* data = static_cast<wxSVGGraphicsBitmapData*>(bmp.GetRefData());
if ( data == nullptr )
return wxNullGraphicsBitmap;
wxBitmap sub = data->GetBitmap().GetSubBitmap(wxRect(wxRound(x), wxRound(y), wxRound(w), wxRound(h)));
return CreateBitmap(sub);
}
wxString wxSVGGraphicsRenderer::GetName() const { return wxS("svg"); }
void wxSVGGraphicsRenderer::GetVersion(int* major, int* minor, int* micro) const
{
if ( major != nullptr )
*major = 1;
if ( minor != nullptr )
*minor = 0;
if ( micro != nullptr )
*micro = 0;
}
// ----------------------------------------------------------------------------
// wxSVGGraphicsContext
// ----------------------------------------------------------------------------
wxSVGGraphicsContext::wxSVGGraphicsContext(wxSVGFileDCImpl* impl)
: wxGraphicsContext(wxSVGGraphicsRenderer::Get()),
m_impl(impl),
m_writer(impl->m_writer.get())
{
int w = 0, h = 0;
impl->DoGetSize(&w, &h);
m_width = w;
m_height = h;
m_currentPen = impl->m_pen;
m_currentBrush = impl->m_brush;
m_currentFont = impl->m_font;
m_currentTextColour = impl->m_textForegroundColour;
m_transform = GetRenderer()->CreateMatrix();
}
wxSVGGraphicsContext::wxSVGGraphicsContext(wxSVGFileDC& dc)
: wxSVGGraphicsContext(static_cast<wxSVGFileDCImpl*>(dc.GetImpl()))
{
}
wxSVGGraphicsContext::wxSVGGraphicsContext(const wxString& filename,
int width, int height,
double dpi,
const wxString& title)
: wxSVGGraphicsContext(new wxSVGFileDC(filename, width, height, dpi, title))
{
}
wxSVGGraphicsContext::wxSVGGraphicsContext(const wxSize& size,
const wxString& filename,
const wxString& title,
double dpi)
: wxSVGGraphicsContext(new wxSVGFileDC(size, filename, title, dpi))
{
}
wxSVGGraphicsContext::wxSVGGraphicsContext(wxSVGFileDC* dc)
: wxSVGGraphicsContext(static_cast<wxSVGFileDCImpl*>(dc->GetImpl()))
{
m_ownedDC.reset(dc);
}
wxSVGGraphicsContext* wxSVGGraphicsContext::Create(wxSVGFileDC& dc)
{
return new wxSVGGraphicsContext(dc);
}
wxSVGGraphicsContext* wxSVGGraphicsContext::Create(const wxString& filename,
int width, int height,
double dpi,
const wxString& title)
{
return new wxSVGGraphicsContext(filename, width, height, dpi, title);
}
wxSVGGraphicsContext* wxSVGGraphicsContext::Create(const wxSize& size,
const wxString& filename,
const wxString& title,
double dpi)
{
return new wxSVGGraphicsContext(size, filename, title, dpi);
}
wxSVGGraphicsContext::~wxSVGGraphicsContext() = default;
wxString wxSVGGraphicsContext::GetSVGDocument() const
{
return m_impl->GetSVGDocument();
}
bool wxSVGGraphicsContext::Save()
{
return m_impl->Save();
}
void wxSVGGraphicsContext::SetBitmapHandler(wxSVGBitmapHandler* handler)
{
m_impl->SetBitmapHandler(handler);
}
void wxSVGGraphicsContext::PushState()
{
m_stateStack.push({ m_transform, m_writer->GetClipNestingLevel(), m_composition });
}
void wxSVGGraphicsContext::PopState()
{
if ( m_stateStack.empty() )
return;
const State& s = m_stateStack.top();
m_transform = s.transform;
SyncTransformToDC();
if ( m_composition != s.compositionMode )
{
m_composition = s.compositionMode;
m_writer->SetCompositionMode(m_composition);
}
if ( m_writer->GetClipNestingLevel() > s.clipNestingLevel )
{
// Close the clipping groups that were opened since PushState.
m_writer->Write(wxS("</g>\n")); // Close the attribute group
auto level = m_writer->GetClipNestingLevel();
while ( level > s.clipNestingLevel )
{
m_writer->Write(wxS("</g>\n"));
--level;
}
m_writer->SetClipNestingLevel(level);
// Restart the attribute group
m_impl->DoStartNewGraphics();
}
m_stateStack.pop();
}
void wxSVGGraphicsContext::Clip(const wxRegion& region)
{
m_impl->DoSetDeviceClippingRegion(region);
}
void wxSVGGraphicsContext::Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h)
{
m_impl->DoSetClippingRegion(wxRound(x), wxRound(y), wxRound(w), wxRound(h));
}
void wxSVGGraphicsContext::ResetClip()
{
m_impl->DestroyClippingRegion();
}
void wxSVGGraphicsContext::GetClipBox(wxDouble* x, wxDouble* y, wxDouble* w, wxDouble* h)
{
wxRect r;
if ( m_impl->DoGetClippingRect(r) )
{
if ( x != nullptr )
*x = r.x;
if ( y != nullptr )
*y = r.y;
if ( w != nullptr )
*w = r.width;
if ( h != nullptr )
*h = r.height;
}
else
{
if ( x != nullptr )
*x = 0;
if ( y != nullptr )
*y = 0;
if ( w != nullptr )
*w = m_width;
if ( h != nullptr )
*h = m_height;
}
}
void* wxSVGGraphicsContext::GetNativeContext()
{
// A text-based SVG context has no native platform handle.
return nullptr;
}
bool wxSVGGraphicsContext::SetAntialiasMode(wxAntialiasMode antialias)
{
m_antialias = antialias;
// Map wxAntialiasMode to SVG shape-rendering attribute.
if ( antialias == wxANTIALIAS_NONE )
m_impl->SetShapeRenderingMode(wxSVG_SHAPE_RENDERING_CRISP_EDGES);
else
m_impl->SetShapeRenderingMode(wxSVG_SHAPE_RENDERING_AUTO);
return true;
}
bool wxSVGGraphicsContext::SetInterpolationQuality(wxInterpolationQuality interpolation)
{
m_interpolation = interpolation;
// Map wxInterpolationQuality to SVG shape-rendering attribute as a quality hint.
switch ( interpolation )
{
case wxINTERPOLATION_FAST:
m_impl->SetShapeRenderingMode(wxSVG_SHAPE_RENDERING_OPTIMIZE_SPEED);
break;
case wxINTERPOLATION_GOOD:
case wxINTERPOLATION_BEST:
m_impl->SetShapeRenderingMode(wxSVG_SHAPE_RENDERING_GEOMETRIC_PRECISION);
break;
case wxINTERPOLATION_NONE:
m_impl->SetShapeRenderingMode(wxSVG_SHAPE_RENDERING_CRISP_EDGES);
break;
case wxINTERPOLATION_DEFAULT:
m_impl->SetShapeRenderingMode(wxSVG_SHAPE_RENDERING_AUTO);
break;
}
return true;
}
bool wxSVGGraphicsContext::SetCompositionMode(wxCompositionMode op)
{
if ( op == wxCOMPOSITION_ADD || op == wxCOMPOSITION_DIFF || op == wxCOMPOSITION_OVER )
{
m_writer->SetCompositionMode(op);
m_composition = op;
return true;
}
return false;
}
void wxSVGGraphicsContext::BeginLayer(wxDouble opacity)
{
m_impl->BeginLayer(opacity);
}
void wxSVGGraphicsContext::EndLayer()
{
m_impl->EndLayer();
}
void wxSVGGraphicsContext::Translate(wxDouble dx, wxDouble dy)
{
m_transform.Translate(dx, dy);
SyncTransformToDC();
}
void wxSVGGraphicsContext::Scale(wxDouble xScale, wxDouble yScale)
{
m_transform.Scale(xScale, yScale);
SyncTransformToDC();
}
void wxSVGGraphicsContext::Rotate(wxDouble angle)
{
m_transform.Rotate(angle);
SyncTransformToDC();
}
void wxSVGGraphicsContext::ConcatTransform(const wxGraphicsMatrix& matrix)
{
m_transform.Concat(matrix);
SyncTransformToDC();
}
void wxSVGGraphicsContext::SetTransform(const wxGraphicsMatrix& matrix)
{
m_transform = matrix;
SyncTransformToDC();
}
wxGraphicsMatrix wxSVGGraphicsContext::GetTransform() const
{
return m_transform;
}
void wxSVGGraphicsContext::SetPen(const wxGraphicsPen& pen)
{
wxGraphicsContext::SetPen(pen);
m_writer->SetGraphicsPen(pen);
m_writer->MarkGraphicsChanged();
if ( pen.IsNull() )
{
SyncPenToDC(*wxTRANSPARENT_PEN);
return;
}
auto* data = static_cast<wxSVGGraphicsPenData*>(pen.GetRefData());
if ( data != nullptr )
SyncPenToDC(data->GetPen());
}
void wxSVGGraphicsContext::SetBrush(const wxGraphicsBrush& brush)
{
wxGraphicsContext::SetBrush(brush);
m_writer->SetGraphicsBrush(brush);
m_writer->MarkGraphicsChanged();
if ( brush.IsNull() )
{
SyncBrushToDC(*wxTRANSPARENT_BRUSH);
return;
}
auto* data = static_cast<wxSVGGraphicsBrushData*>(brush.GetRefData());
if ( data != nullptr )
{
m_writer->WriteBrushFill(data->GetBrush());
SyncBrushToDC(data->GetBrush());
}
}
void wxSVGGraphicsContext::SetFont(const wxGraphicsFont& font)
{
wxGraphicsContext::SetFont(font);
if ( font.IsNull() )
return;
auto* data = static_cast<wxSVGGraphicsFontData*>(font.GetRefData());
if ( data == nullptr )
return;
m_currentFont = data->GetFont();
m_currentTextColour = data->GetColour();
m_impl->m_font = m_currentFont;
m_impl->m_textForegroundColour = m_currentTextColour;
}
void wxSVGGraphicsContext::SyncPenToDC(const wxPen& pen)
{
// Mirror the pen to the parent DC so its NewGraphicsIfNeeded() closes
// and reopens the current <g> group before the next write.
m_currentPen = pen;
m_impl->m_pen = pen;
m_writer->MarkGraphicsChanged();
}
void wxSVGGraphicsContext::SyncBrushToDC(const wxBrush& brush)
{
// Mirror the brush to the parent DC.
m_currentBrush = brush;
m_impl->m_brush = brush;
m_writer->MarkGraphicsChanged();
}
void wxSVGGraphicsContext::SyncTransformToDC()
{
m_writer->SetGCTransform(GetCurrentTransformAttr());
}
wxString wxSVGGraphicsContext::GetCurrentTransformAttr() const
{
if ( m_transform.IsNull() || m_transform.IsIdentity() )
return wxString();
wxDouble a, b, c, d, tx, ty;
m_transform.Get(&a, &b, &c, &d, &tx, &ty);
return wxString::Format(
wxS(" transform=\"matrix(%s,%s,%s,%s,%s,%s)\""),
wxSVG::NumStr(a),
wxSVG::NumStr(b),
wxSVG::NumStr(c),
wxSVG::NumStr(d),
wxSVG::NumStr(tx),
wxSVG::NumStr(ty));
}
void wxSVGGraphicsContext::AccumulatePathBounds(wxGraphicsPathData* data)
{
// Push the path's local-coordinate bounding box through the current
// transform and feed the four corners to the parent DC's bounding box,
// so wxSVGFileDC::MaxX/MaxY include GC-rendered geometry.
if ( data == nullptr )
return;
wxDouble bx, by, bw, bh;
data->GetBox(&bx, &by, &bw, &bh);
wxDouble cx[4] = { bx, bx + bw, bx + bw, bx };
wxDouble cy[4] = { by, by, by + bh, by + bh };
for ( int i = 0; i < 4; ++i )
{
if ( !m_transform.IsNull() )
m_transform.TransformPoint(&cx[i], &cy[i]);
m_impl->CalcBoundingBox(wxRound(cx[i]), wxRound(cy[i]));
}
}
void wxSVGGraphicsContext::StrokePath(const wxGraphicsPath& path)
{
if ( path.IsNull() || m_currentPen.GetStyle() == wxPENSTYLE_TRANSPARENT )
return;
auto* data = static_cast<wxSVGGraphicsPathData*>(path.GetRefData());
if ( data == nullptr )
return;
m_impl->NewGraphicsIfNeeded();
wxString stroke;
if ( !m_pen.IsNull() )
stroke = m_writer->WriteGraphicsPenStroke(m_pen);
if ( stroke.empty() )
{
stroke = wxSVG::GetPenStroke(m_currentPen.GetColour(),
m_currentPen.GetStyle());
}
const wxString penPattern = wxSVG::GetPenPattern(m_currentPen);
const wxString transform = GetCurrentTransformAttr();
const wxString s = wxString::Format(
wxS(" <path d=\"%s\" fill=\"none\" %s stroke-width=\"%d\" %s%s/>\n"),
data->GetDString(), stroke, m_currentPen.GetWidth(), penPattern, transform);
m_writer->Write(s);
AccumulatePathBounds(data);
}
void wxSVGGraphicsContext::FillPath(const wxGraphicsPath& path, wxPolygonFillMode fillStyle)
{
// Writes an SVG <path> element for the given path using the current brush.
if ( path.IsNull() )
return;
auto* data = static_cast<wxSVGGraphicsPathData*>(path.GetRefData());
if ( data == nullptr )
return;
m_impl->NewGraphicsIfNeeded();
wxString fill;
if ( !m_brush.IsNull() )
fill = m_writer->WriteGraphicsBrushFill(m_brush);
if ( fill.empty() )
{
auto* brushData = static_cast<wxSVGGraphicsBrushData*>(m_brush.GetRefData());
if ( brushData )
fill = wxSVG::GetBrushPattern(brushData->GetBrush());
}
if ( fill.empty() )
{
fill = m_currentBrush.GetStyle() == wxBRUSHSTYLE_TRANSPARENT
? wxString(wxS("fill=\"none\""))
: wxSVG::GetBrushFill(m_currentBrush.GetColour(),
m_currentBrush.GetStyle());
}
const wxString penPattern = wxSVG::GetPenPattern(m_currentPen);
const wxString transform = GetCurrentTransformAttr();
const wxString rule = (fillStyle == wxODDEVEN_RULE)
? wxS("evenodd") : wxS("nonzero");
const wxString s = wxString::Format(
wxS(" <path d=\"%s\" %s fill-rule=\"%s\" stroke=\"none\" %s%s/>\n"),
data->GetDString(), fill, rule, penPattern, transform);
m_writer->Write(s);
AccumulatePathBounds(data);
}
void wxSVGGraphicsContext::DrawPath(const wxGraphicsPath& path, wxPolygonFillMode fillStyle)
{
FillPath(path, fillStyle);
StrokePath(path);
}
void wxSVGGraphicsContext::GetTextExtent(const wxString& text,
wxDouble* width, wxDouble* height,
wxDouble* descent,
wxDouble* externalLeading) const
{
wxCoord w = 0, h = 0, d = 0, e = 0;
m_impl->DoGetTextExtent(text, &w, &h, &d, &e, &m_currentFont);
if ( width != nullptr )
*width = w;
if ( height != nullptr )
*height = h;
if ( descent != nullptr )
*descent = d;
if ( externalLeading != nullptr )
*externalLeading = e;
}
void wxSVGGraphicsContext::GetPartialTextExtents(const wxString& text,
wxArrayDouble& widths) const
{
widths.clear();
widths.reserve(text.length());
for ( size_t i = 1; i <= text.length(); ++i )
{
wxCoord w = 0, h = 0;
m_impl->DoGetTextExtent(text.substr(0, i), &w, &h, nullptr, nullptr, &m_currentFont);
widths.push_back(w);
}
}
void wxSVGGraphicsContext::DrawBitmap(const wxGraphicsBitmap& bmp, wxDouble x, wxDouble y, wxDouble w, wxDouble h)
{
auto* data = static_cast<wxSVGGraphicsBitmapData*>(bmp.GetRefData());
if ( data != nullptr )
{
wxBitmap b = data->GetBitmap();
if ( w != b.GetWidth() || h != b.GetHeight() )
{
wxImage img = b.ConvertToImage();
img.Rescale(wxRound(w), wxRound(h));
b = wxBitmap(img);
}
DrawBitmap(b, x, y, w, h);
}
}
void wxSVGGraphicsContext::DrawBitmap(const wxBitmap& bmp,
wxDouble x, wxDouble y,
wxDouble WXUNUSED(w), wxDouble WXUNUSED(h))
{
// Delegate to the DC; its DoDrawBitmap() will call NewGraphicsIfNeeded().
m_impl->GetOwner()->DrawBitmap(bmp, wxCoord(x), wxCoord(y), false);
}
void wxSVGGraphicsContext::DrawIcon(const wxIcon& icon,
wxDouble x, wxDouble y,
wxDouble WXUNUSED(w), wxDouble WXUNUSED(h))
{
m_impl->GetOwner()->DrawIcon(icon, wxCoord(x), wxCoord(y));
}
#ifdef __WXMSW__
// Not analogous to SVG output.
WXHDC wxSVGGraphicsContext::GetNativeHDC() { return WXHDC(0); }
void wxSVGGraphicsContext::ReleaseNativeHDC(WXHDC) {}
#endif
void wxSVGGraphicsContext::DoDrawText(const wxString& str, wxDouble x, wxDouble y)
{
m_impl->GetOwner()->DrawText(str, wxCoord(x), wxCoord(y));
}
#endif // wxUSE_SVG && wxUSE_GRAPHICS_CONTEXT