Files
autosample/vendor/lunasvg/source/svggeometryelement.cpp

325 lines
10 KiB
C++

#include "svggeometryelement.h"
#include "svglayoutstate.h"
#include "svgrenderstate.h"
#include <cmath>
namespace lunasvg {
Rect SVGMarkerPosition::markerBoundingBox(float strokeWidth) const
{
return m_element->markerBoundingBox(m_origin, m_angle, strokeWidth);
}
void SVGMarkerPosition::renderMarker(SVGRenderState& state, float strokeWidth) const
{
m_element->renderMarker(state, m_origin, m_angle, strokeWidth);
}
SVGGeometryElement::SVGGeometryElement(Document* document, ElementID id)
: SVGGraphicsElement(document, id)
{
}
Rect SVGGeometryElement::strokeBoundingBox() const
{
auto strokeBoundingBox = fillBoundingBox();
if(m_stroke.isRenderable()) {
float capLimit = m_strokeData.lineWidth() / 2.f;
if(m_strokeData.lineCap() == LineCap::Square)
capLimit *= PLUTOVG_SQRT2;
float joinLimit = m_strokeData.lineWidth() / 2.f;
if(m_strokeData.lineJoin() == LineJoin::Miter) {
joinLimit *= m_strokeData.miterLimit();
}
strokeBoundingBox.inflate(std::max(capLimit, joinLimit));
}
for(const auto& markerPosition : m_markerPositions)
strokeBoundingBox.unite(markerPosition.markerBoundingBox(m_strokeData.lineWidth()));
return strokeBoundingBox;
}
void SVGGeometryElement::layoutElement(const SVGLayoutState& state)
{
m_fill_rule = state.fill_rule();
m_clip_rule = state.clip_rule();
m_fill = getPaintServer(state.fill(), state.fill_opacity());
m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
m_strokeData = getStrokeData(state);
SVGGraphicsElement::layoutElement(state);
m_path.reset();
m_markerPositions.clear();
m_fillBoundingBox = updateShape(m_path);
updateMarkerPositions(m_markerPositions, state);
}
void SVGGeometryElement::updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state)
{
if(m_path.isEmpty())
return;
auto markerStart = getMarker(state.marker_start());
auto markerMid = getMarker(state.marker_mid());
auto markerEnd = getMarker(state.marker_end());
if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr) {
return;
}
Point origin;
Point startPoint;
Point inslopePoints[2];
Point outslopePoints[2];
int index = 0;
std::array<Point, 3> points;
PathIterator it(m_path);
while(!it.isDone()) {
switch(it.currentSegment(points)) {
case PathCommand::MoveTo:
startPoint = points[0];
inslopePoints[0] = origin;
inslopePoints[1] = points[0];
origin = points[0];
break;
case PathCommand::LineTo:
inslopePoints[0] = origin;
inslopePoints[1] = points[0];
origin = points[0];
break;
case PathCommand::CubicTo:
inslopePoints[0] = points[1];
inslopePoints[1] = points[2];
origin = points[2];
break;
case PathCommand::Close:
inslopePoints[0] = origin;
inslopePoints[1] = points[0];
origin = startPoint;
startPoint = Point();
break;
}
it.next();
if(!it.isDone() && (markerStart || markerMid)) {
it.currentSegment(points);
outslopePoints[0] = origin;
outslopePoints[1] = points[0];
if(index == 0 && markerStart) {
auto slope = outslopePoints[1] - outslopePoints[0];
auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI;
const auto& orient = markerStart->orient();
if(orient.orientType() == SVGAngle::OrientType::AutoStartReverse)
angle -= 180.f;
positions.emplace_back(markerStart, origin, angle);
}
if(index > 0 && markerMid) {
auto inslope = inslopePoints[1] - inslopePoints[0];
auto outslope = outslopePoints[1] - outslopePoints[0];
auto inangle = 180.f * std::atan2(inslope.y, inslope.x) / PLUTOVG_PI;
auto outangle = 180.f * std::atan2(outslope.y, outslope.x) / PLUTOVG_PI;
if(std::abs(inangle - outangle) > 180.f)
inangle += 360.f;
auto angle = (inangle + outangle) * 0.5f;
positions.emplace_back(markerMid, origin, angle);
}
}
if(markerEnd && it.isDone()) {
auto slope = inslopePoints[1] - inslopePoints[0];
auto angle = 180.f * std::atan2(slope.y, slope.x) / PLUTOVG_PI;
positions.emplace_back(markerEnd, origin, angle);
}
index += 1;
}
}
void SVGGeometryElement::render(SVGRenderState& state) const
{
if(!isRenderable())
return;
SVGBlendInfo blendInfo(this);
SVGRenderState newState(this, state, localTransform());
newState.beginGroup(blendInfo);
if(newState.mode() == SVGRenderMode::Clipping) {
newState->setColor(Color::White);
newState->fillPath(m_path, m_clip_rule, newState.currentTransform());
} else {
if(m_fill.applyPaint(newState))
newState->fillPath(m_path, m_fill_rule, newState.currentTransform());
if(m_stroke.applyPaint(newState)) {
newState->strokePath(m_path, m_strokeData, newState.currentTransform());
}
for(const auto& markerPosition : m_markerPositions) {
markerPosition.renderMarker(newState, m_strokeData.lineWidth());
}
}
newState.endGroup(blendInfo);
}
SVGLineElement::SVGLineElement(Document* document)
: SVGGeometryElement(document, ElementID::Line)
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow)
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow)
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow)
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow)
{
addProperty(m_x1);
addProperty(m_y1);
addProperty(m_x2);
addProperty(m_y2);
}
Rect SVGLineElement::updateShape(Path& path)
{
LengthContext lengthContext(this);
auto x1 = lengthContext.valueForLength(m_x1);
auto y1 = lengthContext.valueForLength(m_y1);
auto x2 = lengthContext.valueForLength(m_x2);
auto y2 = lengthContext.valueForLength(m_y2);
path.moveTo(x1, y1);
path.lineTo(x2, y2);
return Rect(x1, y1, x2 - x1, y2 - y1);
}
SVGRectElement::SVGRectElement(Document* document)
: SVGGeometryElement(document, ElementID::Rect)
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow)
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow)
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid)
, m_rx(PropertyID::Rx, LengthDirection::Horizontal, LengthNegativeMode::Forbid)
, m_ry(PropertyID::Ry, LengthDirection::Vertical, LengthNegativeMode::Forbid)
{
addProperty(m_x);
addProperty(m_y);
addProperty(m_width);
addProperty(m_height);
addProperty(m_rx);
addProperty(m_ry);
}
Rect SVGRectElement::updateShape(Path& path)
{
LengthContext lengthContext(this);
auto width = lengthContext.valueForLength(m_width);
auto height = lengthContext.valueForLength(m_height);
if(width <= 0.f || height <= 0.f) {
return Rect::Empty;
}
auto x = lengthContext.valueForLength(m_x);
auto y = lengthContext.valueForLength(m_y);
auto rx = lengthContext.valueForLength(m_rx);
auto ry = lengthContext.valueForLength(m_ry);
if(rx <= 0.f) rx = ry;
if(ry <= 0.f) ry = rx;
rx = std::min(rx, width / 2.f);
ry = std::min(ry, height / 2.f);
path.addRoundRect(x, y, width, height, rx, ry);
return Rect(x, y, width, height);
}
SVGEllipseElement::SVGEllipseElement(Document* document)
: SVGGeometryElement(document, ElementID::Ellipse)
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow)
, m_rx(PropertyID::Rx, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
, m_ry(PropertyID::Ry, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
{
addProperty(m_cx);
addProperty(m_cy);
addProperty(m_rx);
addProperty(m_ry);
}
Rect SVGEllipseElement::updateShape(Path& path)
{
LengthContext lengthContext(this);
auto rx = lengthContext.valueForLength(m_rx);
auto ry = lengthContext.valueForLength(m_ry);
if(rx <= 0.f || ry <= 0.f) {
return Rect::Empty;
}
auto cx = lengthContext.valueForLength(m_cx);
auto cy = lengthContext.valueForLength(m_cy);
path.addEllipse(cx, cy, rx, ry);
return Rect(cx - rx, cy - ry, rx + rx, ry + ry);
}
SVGCircleElement::SVGCircleElement(Document* document)
: SVGGeometryElement(document, ElementID::Circle)
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow)
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow)
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid)
{
addProperty(m_cx);
addProperty(m_cy);
addProperty(m_r);
}
Rect SVGCircleElement::updateShape(Path& path)
{
LengthContext lengthContext(this);
auto r = lengthContext.valueForLength(m_r);
if(r <= 0.f) {
return Rect::Empty;
}
auto cx = lengthContext.valueForLength(m_cx);
auto cy = lengthContext.valueForLength(m_cy);
path.addEllipse(cx, cy, r, r);
return Rect(cx - r, cy - r, r + r, r + r);
}
SVGPolyElement::SVGPolyElement(Document* document, ElementID id)
: SVGGeometryElement(document, id)
, m_points(PropertyID::Points)
{
addProperty(m_points);
}
Rect SVGPolyElement::updateShape(Path& path)
{
const auto& points = m_points.values();
if(points.empty()) {
return Rect::Empty;
}
path.moveTo(points[0].x, points[0].y);
for(size_t i = 1; i < points.size(); i++) {
path.lineTo(points[i].x, points[i].y);
}
if(id() == ElementID::Polygon)
path.close();
return path.boundingRect();
}
SVGPathElement::SVGPathElement(Document* document)
: SVGGeometryElement(document, ElementID::Path)
, m_d(PropertyID::D)
{
addProperty(m_d);
}
Rect SVGPathElement::updateShape(Path& path)
{
path = m_d.value();
return path.boundingRect();
}
} // namespace lunasvg