mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-08-17 17:02:51 +08:00
SVG does not allow redefining patterns with the same ID, so write each pattern only once.
1566 lines
48 KiB
C++
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
|