364
vendor/lunasvg/source/svgpaintelement.cpp
vendored
Normal file
364
vendor/lunasvg/source/svgpaintelement.cpp
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
#include "svgpaintelement.h"
|
||||
#include "svglayoutstate.h"
|
||||
#include "svgrenderstate.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
|
||||
namespace lunasvg {
|
||||
|
||||
SVGPaintElement::SVGPaintElement(Document* document, ElementID id)
|
||||
: SVGElement(document, id)
|
||||
{
|
||||
}
|
||||
|
||||
SVGStopElement::SVGStopElement(Document* document)
|
||||
: SVGElement(document, ElementID::Stop)
|
||||
, m_offset(PropertyID::Offset, 0.f)
|
||||
{
|
||||
addProperty(m_offset);
|
||||
}
|
||||
|
||||
void SVGStopElement::layoutElement(const SVGLayoutState& state)
|
||||
{
|
||||
m_stop_color = state.stop_color();
|
||||
m_stop_opacity = state.stop_opacity();
|
||||
SVGElement::layoutElement(state);
|
||||
}
|
||||
|
||||
GradientStop SVGStopElement::gradientStop(float opacity) const
|
||||
{
|
||||
Color stopColor = m_stop_color.colorWithAlpha(m_stop_opacity * opacity);
|
||||
GradientStop gradientStop = {
|
||||
m_offset.value(), { stopColor.redF(), stopColor.greenF(), stopColor.blueF(), stopColor.alphaF() }
|
||||
};
|
||||
|
||||
return gradientStop;
|
||||
}
|
||||
|
||||
SVGGradientElement::SVGGradientElement(Document* document, ElementID id)
|
||||
: SVGPaintElement(document, id)
|
||||
, SVGURIReference(this)
|
||||
, m_gradientTransform(PropertyID::GradientTransform)
|
||||
, m_gradientUnits(PropertyID::GradientUnits, Units::ObjectBoundingBox)
|
||||
, m_spreadMethod(PropertyID::SpreadMethod, SpreadMethod::Pad)
|
||||
{
|
||||
addProperty(m_gradientTransform);
|
||||
addProperty(m_gradientUnits);
|
||||
addProperty(m_spreadMethod);
|
||||
}
|
||||
|
||||
void SVGGradientElement::collectGradientAttributes(SVGGradientAttributes& attributes) const
|
||||
{
|
||||
if(!attributes.hasGradientTransform() && hasAttribute(PropertyID::GradientTransform))
|
||||
attributes.setGradientTransform(this);
|
||||
if(!attributes.hasSpreadMethod() && hasAttribute(PropertyID::SpreadMethod))
|
||||
attributes.setSpreadMethod(this);
|
||||
if(!attributes.hasGradientUnits() && hasAttribute(PropertyID::GradientUnits))
|
||||
attributes.setGradientUnits(this);
|
||||
if(!attributes.hasGradientContentElement()) {
|
||||
for(const auto& child : children()) {
|
||||
if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) {
|
||||
attributes.setGradientContentElement(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SVGLinearGradientElement::SVGLinearGradientElement(Document* document)
|
||||
: SVGGradientElement(document, ElementID::LinearGradient)
|
||||
, m_x1(PropertyID::X1, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
, m_y1(PropertyID::Y1, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
, m_x2(PropertyID::X2, LengthDirection::Horizontal, LengthNegativeMode::Allow, 100.f, LengthUnits::Percent)
|
||||
, m_y2(PropertyID::Y2, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::Percent)
|
||||
{
|
||||
addProperty(m_x1);
|
||||
addProperty(m_y1);
|
||||
addProperty(m_x2);
|
||||
addProperty(m_y2);
|
||||
}
|
||||
|
||||
SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes() const
|
||||
{
|
||||
SVGLinearGradientAttributes attributes;
|
||||
std::set<const SVGGradientElement*> processedGradients;
|
||||
const SVGGradientElement* current = this;
|
||||
while(true) {
|
||||
current->collectGradientAttributes(attributes);
|
||||
if(current->id() == ElementID::LinearGradient) {
|
||||
auto element = static_cast<const SVGLinearGradientElement*>(current);
|
||||
if(!attributes.hasX1() && element->hasAttribute(PropertyID::X1))
|
||||
attributes.setX1(element);
|
||||
if(!attributes.hasY1() && element->hasAttribute(PropertyID::Y1))
|
||||
attributes.setY1(element);
|
||||
if(!attributes.hasX2() && element->hasAttribute(PropertyID::X2))
|
||||
attributes.setX2(element);
|
||||
if(!attributes.hasY2() && element->hasAttribute(PropertyID::Y2)) {
|
||||
attributes.setY2(element);
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
|
||||
break;
|
||||
processedGradients.insert(current);
|
||||
current = static_cast<const SVGGradientElement*>(targetElement);
|
||||
if(processedGradients.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
static GradientStops buildGradientStops(const SVGGradientElement* element, float opacity)
|
||||
{
|
||||
GradientStops gradientStops;
|
||||
|
||||
const auto& children = element->children();
|
||||
gradientStops.reserve(children.size());
|
||||
for(const auto& child : children) {
|
||||
auto childElement = toSVGElement(child);
|
||||
if(childElement && childElement->id() == ElementID::Stop) {
|
||||
auto stopElement = static_cast<SVGStopElement*>(childElement);
|
||||
gradientStops.push_back(stopElement->gradientStop(opacity));
|
||||
}
|
||||
}
|
||||
|
||||
return gradientStops;
|
||||
}
|
||||
|
||||
bool SVGLinearGradientElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
auto attributes = collectGradientAttributes();
|
||||
auto gradientContentElement = attributes.gradientContentElement();
|
||||
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
|
||||
if(gradientStops.empty())
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.gradientUnits());
|
||||
auto x1 = lengthContext.valueForLength(attributes.x1());
|
||||
auto y1 = lengthContext.valueForLength(attributes.y1());
|
||||
auto x2 = lengthContext.valueForLength(attributes.x2());
|
||||
auto y2 = lengthContext.valueForLength(attributes.y2());
|
||||
if(gradientStops.size() == 1 || (x1 == x2 && y1 == y2)) {
|
||||
const auto& lastStop = gradientStops.back();
|
||||
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto spreadMethod = attributes.spreadMethod();
|
||||
auto gradientUnits = attributes.gradientUnits();
|
||||
auto gradientTransform = attributes.gradientTransform();
|
||||
if(gradientUnits == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
|
||||
}
|
||||
|
||||
state->setLinearGradient(x1, y1, x2, y2, spreadMethod, gradientStops, gradientTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGRadialGradientElement::SVGRadialGradientElement(Document* document)
|
||||
: SVGGradientElement(document, ElementID::RadialGradient)
|
||||
, m_cx(PropertyID::Cx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
|
||||
, m_cy(PropertyID::Cy, LengthDirection::Vertical, LengthNegativeMode::Allow, 50.f, LengthUnits::Percent)
|
||||
, m_r(PropertyID::R, LengthDirection::Diagonal, LengthNegativeMode::Forbid, 50.f, LengthUnits::Percent)
|
||||
, m_fx(PropertyID::Fx, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
|
||||
, m_fy(PropertyID::Fy, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
|
||||
{
|
||||
addProperty(m_cx);
|
||||
addProperty(m_cy);
|
||||
addProperty(m_r);
|
||||
addProperty(m_fx);
|
||||
addProperty(m_fy);
|
||||
}
|
||||
|
||||
bool SVGRadialGradientElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
auto attributes = collectGradientAttributes();
|
||||
auto gradientContentElement = attributes.gradientContentElement();
|
||||
auto gradientStops = buildGradientStops(gradientContentElement, opacity);
|
||||
if(gradientStops.empty())
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.gradientUnits());
|
||||
auto r = lengthContext.valueForLength(attributes.r());
|
||||
if(r == 0.f || gradientStops.size() == 1) {
|
||||
const auto& lastStop = gradientStops.back();
|
||||
state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto fx = lengthContext.valueForLength(attributes.fx());
|
||||
auto fy = lengthContext.valueForLength(attributes.fy());
|
||||
auto cx = lengthContext.valueForLength(attributes.cx());
|
||||
auto cy = lengthContext.valueForLength(attributes.cy());
|
||||
|
||||
auto spreadMethod = attributes.spreadMethod();
|
||||
auto gradientUnits = attributes.gradientUnits();
|
||||
auto gradientTransform = attributes.gradientTransform();
|
||||
if(gradientUnits == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
|
||||
}
|
||||
|
||||
state->setRadialGradient(cx, cy, r, fx, fy, spreadMethod, gradientStops, gradientTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGRadialGradientAttributes SVGRadialGradientElement::collectGradientAttributes() const
|
||||
{
|
||||
SVGRadialGradientAttributes attributes;
|
||||
std::set<const SVGGradientElement*> processedGradients;
|
||||
const SVGGradientElement* current = this;
|
||||
while(true) {
|
||||
current->collectGradientAttributes(attributes);
|
||||
if(current->id() == ElementID::RadialGradient) {
|
||||
auto element = static_cast<const SVGRadialGradientElement*>(current);
|
||||
if(!attributes.hasCx() && element->hasAttribute(PropertyID::Cx))
|
||||
attributes.setCx(element);
|
||||
if(!attributes.hasCy() && element->hasAttribute(PropertyID::Cy))
|
||||
attributes.setCy(element);
|
||||
if(!attributes.hasR() && element->hasAttribute(PropertyID::R))
|
||||
attributes.setR(element);
|
||||
if(!attributes.hasFx() && element->hasAttribute(PropertyID::Fx))
|
||||
attributes.setFx(element);
|
||||
if(!attributes.hasFy() && element->hasAttribute(PropertyID::Fy)) {
|
||||
attributes.setFy(element);
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
|
||||
break;
|
||||
processedGradients.insert(current);
|
||||
current = static_cast<const SVGGradientElement*>(targetElement);
|
||||
if(processedGradients.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
SVGPatternElement::SVGPatternElement(Document* document)
|
||||
: SVGPaintElement(document, ElementID::Pattern)
|
||||
, SVGURIReference(this)
|
||||
, SVGFitToViewBox(this)
|
||||
, 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_patternTransform(PropertyID::PatternTransform)
|
||||
, m_patternUnits(PropertyID::PatternUnits, Units::ObjectBoundingBox)
|
||||
, m_patternContentUnits(PropertyID::PatternContentUnits, Units::UserSpaceOnUse)
|
||||
{
|
||||
addProperty(m_x);
|
||||
addProperty(m_y);
|
||||
addProperty(m_width);
|
||||
addProperty(m_height);
|
||||
addProperty(m_patternTransform);
|
||||
addProperty(m_patternUnits);
|
||||
addProperty(m_patternContentUnits);
|
||||
}
|
||||
|
||||
bool SVGPatternElement::applyPaint(SVGRenderState& state, float opacity) const
|
||||
{
|
||||
if(state.hasCycleReference(this))
|
||||
return false;
|
||||
auto attributes = collectPatternAttributes();
|
||||
auto patternContentElement = attributes.patternContentElement();
|
||||
if(patternContentElement == nullptr)
|
||||
return false;
|
||||
LengthContext lengthContext(this, attributes.patternUnits());
|
||||
Rect patternRect = {
|
||||
lengthContext.valueForLength(attributes.x()),
|
||||
lengthContext.valueForLength(attributes.y()),
|
||||
lengthContext.valueForLength(attributes.width()),
|
||||
lengthContext.valueForLength(attributes.height())
|
||||
};
|
||||
|
||||
if(attributes.patternUnits() == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
patternRect.x = patternRect.x * bbox.w + bbox.x;
|
||||
patternRect.y = patternRect.y * bbox.h + bbox.y;
|
||||
patternRect.w = patternRect.w * bbox.w;
|
||||
patternRect.h = patternRect.h * bbox.h;
|
||||
}
|
||||
|
||||
auto currentTransform = attributes.patternTransform() * state.currentTransform();
|
||||
auto xScale = currentTransform.xScale();
|
||||
auto yScale = currentTransform.yScale();
|
||||
|
||||
auto patternImage = Canvas::create(0, 0, patternRect.w * xScale, patternRect.h * yScale);
|
||||
auto patternImageTransform = Transform::scaled(xScale, yScale);
|
||||
|
||||
const auto& viewBoxRect = attributes.viewBox();
|
||||
if(viewBoxRect.isValid()) {
|
||||
const auto& preserveAspectRatio = attributes.preserveAspectRatio();
|
||||
patternImageTransform.multiply(preserveAspectRatio.getTransform(viewBoxRect, patternRect.size()));
|
||||
} else if(attributes.patternContentUnits() == Units::ObjectBoundingBox) {
|
||||
auto bbox = state.fillBoundingBox();
|
||||
patternImageTransform.scale(bbox.w, bbox.h);
|
||||
}
|
||||
|
||||
SVGRenderState newState(this, &state, patternImageTransform, SVGRenderMode::Painting, patternImage);
|
||||
patternContentElement->renderChildren(newState);
|
||||
|
||||
auto patternTransform = attributes.patternTransform();
|
||||
patternTransform.translate(patternRect.x, patternRect.y);
|
||||
patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height());
|
||||
state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
SVGPatternAttributes SVGPatternElement::collectPatternAttributes() const
|
||||
{
|
||||
SVGPatternAttributes attributes;
|
||||
std::set<const SVGPatternElement*> processedPatterns;
|
||||
const SVGPatternElement* current = this;
|
||||
while(true) {
|
||||
if(!attributes.hasX() && current->hasAttribute(PropertyID::X))
|
||||
attributes.setX(current);
|
||||
if(!attributes.hasY() && current->hasAttribute(PropertyID::Y))
|
||||
attributes.setY(current);
|
||||
if(!attributes.hasWidth() && current->hasAttribute(PropertyID::Width))
|
||||
attributes.setWidth(current);
|
||||
if(!attributes.hasHeight() && current->hasAttribute(PropertyID::Height))
|
||||
attributes.setHeight(current);
|
||||
if(!attributes.hasPatternTransform() && current->hasAttribute(PropertyID::PatternTransform))
|
||||
attributes.setPatternTransform(current);
|
||||
if(!attributes.hasPatternUnits() && current->hasAttribute(PropertyID::PatternUnits))
|
||||
attributes.setPatternUnits(current);
|
||||
if(!attributes.hasPatternContentUnits() && current->hasAttribute(PropertyID::PatternContentUnits))
|
||||
attributes.setPatternContentUnits(current);
|
||||
if(!attributes.hasViewBox() && current->hasAttribute(PropertyID::ViewBox))
|
||||
attributes.setViewBox(current);
|
||||
if(!attributes.hasPreserveAspectRatio() && current->hasAttribute(PropertyID::PreserveAspectRatio))
|
||||
attributes.setPreserveAspectRatio(current);
|
||||
if(!attributes.hasPatternContentElement()) {
|
||||
for(const auto& child : current->children()) {
|
||||
if(child->isElement()) {
|
||||
attributes.setPatternContentElement(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto targetElement = current->getTargetElement(document());
|
||||
if(!targetElement || targetElement->id() != ElementID::Pattern)
|
||||
break;
|
||||
processedPatterns.insert(current);
|
||||
current = static_cast<const SVGPatternElement*>(targetElement);
|
||||
if(processedPatterns.count(current) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
attributes.setDefaultValues(this);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
} // namespace lunasvg
|
||||
Reference in New Issue
Block a user