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

1226 lines
37 KiB
C++

#include "svgelement.h"
#include "svgpaintelement.h"
#include "svggeometryelement.h"
#include "svgtextelement.h"
#include "svgproperty.h"
#include "svglayoutstate.h"
#include "svgrenderstate.h"
#include <cassert>
namespace lunasvg {
ElementID elementid(std::string_view name)
{
static const struct {
std::string_view name;
ElementID value;
} table[] = {
{"a", ElementID::G},
{"circle", ElementID::Circle},
{"clipPath", ElementID::ClipPath},
{"defs", ElementID::Defs},
{"ellipse", ElementID::Ellipse},
{"g", ElementID::G},
{"image", ElementID::Image},
{"line", ElementID::Line},
{"linearGradient", ElementID::LinearGradient},
{"marker", ElementID::Marker},
{"mask", ElementID::Mask},
{"path", ElementID::Path},
{"pattern", ElementID::Pattern},
{"polygon", ElementID::Polygon},
{"polyline", ElementID::Polyline},
{"radialGradient", ElementID::RadialGradient},
{"rect", ElementID::Rect},
{"stop", ElementID::Stop},
{"style", ElementID::Style},
{"svg", ElementID::Svg},
{"symbol", ElementID::Symbol},
{"text", ElementID::Text},
{"tspan", ElementID::Tspan},
{"use", ElementID::Use}
};
auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
if(it == std::end(table) || it->name != name)
return ElementID::Unknown;
return it->value;
}
SVGTextNode::SVGTextNode(Document* document)
: SVGNode(document)
{
}
void SVGTextNode::setData(const std::string& data)
{
rootElement()->setNeedsLayout();
m_data.assign(data);
}
std::unique_ptr<SVGNode> SVGTextNode::clone(bool deep) const
{
auto node = std::make_unique<SVGTextNode>(document());
node->setData(m_data);
return node;
}
const std::string emptyString;
std::unique_ptr<SVGElement> SVGElement::create(Document* document, ElementID id)
{
switch(id) {
case ElementID::Svg:
return std::make_unique<SVGSVGElement>(document);
case ElementID::Path:
return std::make_unique<SVGPathElement>(document);
case ElementID::G:
return std::make_unique<SVGGElement>(document);
case ElementID::Rect:
return std::make_unique<SVGRectElement>(document);
case ElementID::Circle:
return std::make_unique<SVGCircleElement>(document);
case ElementID::Ellipse:
return std::make_unique<SVGEllipseElement>(document);
case ElementID::Line:
return std::make_unique<SVGLineElement>(document);
case ElementID::Defs:
return std::make_unique<SVGDefsElement>(document);
case ElementID::Polygon:
case ElementID::Polyline:
return std::make_unique<SVGPolyElement>(document, id);
case ElementID::Stop:
return std::make_unique<SVGStopElement>(document);
case ElementID::LinearGradient:
return std::make_unique<SVGLinearGradientElement>(document);
case ElementID::RadialGradient:
return std::make_unique<SVGRadialGradientElement>(document);
case ElementID::Symbol:
return std::make_unique<SVGSymbolElement>(document);
case ElementID::Use:
return std::make_unique<SVGUseElement>(document);
case ElementID::Pattern:
return std::make_unique<SVGPatternElement>(document);
case ElementID::Mask:
return std::make_unique<SVGMaskElement>(document);
case ElementID::ClipPath:
return std::make_unique<SVGClipPathElement>(document);
case ElementID::Marker:
return std::make_unique<SVGMarkerElement>(document);
case ElementID::Image:
return std::make_unique<SVGImageElement>(document);
case ElementID::Style:
return std::make_unique<SVGStyleElement>(document);
case ElementID::Text:
return std::make_unique<SVGTextElement>(document);
case ElementID::Tspan:
return std::make_unique<SVGTSpanElement>(document);
default:
assert(false);
}
return nullptr;
}
SVGElement::SVGElement(Document* document, ElementID id)
: SVGNode(document)
, m_id(id)
{
}
bool SVGElement::hasAttribute(std::string_view name) const
{
auto id = propertyid(name);
if(id == PropertyID::Unknown)
return false;
return hasAttribute(id);
}
const std::string& SVGElement::getAttribute(std::string_view name) const
{
auto id = propertyid(name);
if(id == PropertyID::Unknown)
return emptyString;
return getAttribute(id);
}
bool SVGElement::setAttribute(std::string_view name, const std::string& value)
{
auto id = propertyid(name);
if(id == PropertyID::Unknown)
return false;
return setAttribute(0x1000, id, value);
}
const Attribute* SVGElement::findAttribute(PropertyID id) const
{
for(const auto& attribute : m_attributes) {
if(id == attribute.id()) {
return &attribute;
}
}
return nullptr;
}
bool SVGElement::hasAttribute(PropertyID id) const
{
for(const auto& attribute : m_attributes) {
if(id == attribute.id()) {
return true;
}
}
return false;
}
const std::string& SVGElement::getAttribute(PropertyID id) const
{
for(const auto& attribute : m_attributes) {
if(id == attribute.id()) {
return attribute.value();
}
}
return emptyString;
}
bool SVGElement::setAttribute(int specificity, PropertyID id, const std::string& value)
{
for(auto& attribute : m_attributes) {
if(id == attribute.id()) {
if(specificity < attribute.specificity())
return false;
parseAttribute(id, value);
attribute = Attribute(specificity, id, value);
return true;
}
}
parseAttribute(id, value);
m_attributes.emplace_front(specificity, id, value);
return true;
}
void SVGElement::setAttributes(const AttributeList& attributes)
{
for(const auto& attribute : attributes) {
setAttribute(attribute);
}
}
bool SVGElement::setAttribute(const Attribute& attribute)
{
return setAttribute(attribute.specificity(), attribute.id(), attribute.value());
}
void SVGElement::parseAttribute(PropertyID id, const std::string& value)
{
rootElement()->setNeedsLayout();
if(auto property = getProperty(id)) {
property->parse(value);
}
}
SVGElement* SVGElement::previousElement() const
{
auto parent = parentElement();
if(parent == nullptr)
return nullptr;
const auto& children = parent->children();
auto it = children.begin();
auto end = children.end();
SVGElement* element = nullptr;
for(; it != end; ++it) {
SVGNode* node = &**it;
if(node->isTextNode())
continue;
if(node == this)
return element;
element = static_cast<SVGElement*>(node);
}
return nullptr;
}
SVGElement* SVGElement::nextElement() const
{
auto parent = parentElement();
if(parent == nullptr)
return nullptr;
const auto& children = parent->children();
auto it = children.rbegin();
auto end = children.rend();
SVGElement* element = nullptr;
for(; it != end; ++it) {
SVGNode* node = &**it;
if(node->isTextNode())
continue;
if(node == this)
return element;
element = static_cast<SVGElement*>(node);
}
return nullptr;
}
SVGNode* SVGElement::addChild(std::unique_ptr<SVGNode> child)
{
child->setParentElement(this);
m_children.push_back(std::move(child));
return &*m_children.back();
}
SVGNode* SVGElement::firstChild() const
{
if(m_children.empty())
return nullptr;
return &*m_children.front();
}
SVGNode* SVGElement::lastChild() const
{
if(m_children.empty())
return nullptr;
return &*m_children.back();
}
Rect SVGElement::fillBoundingBox() const
{
auto fillBoundingBox = Rect::Invalid;
for(const auto& child : m_children) {
if(auto element = toSVGElement(child); element && !element->isHiddenElement()) {
fillBoundingBox.unite(element->localTransform().mapRect(element->fillBoundingBox()));
}
}
if(!fillBoundingBox.isValid())
fillBoundingBox = Rect::Empty;
return fillBoundingBox;
}
Rect SVGElement::strokeBoundingBox() const
{
auto strokeBoundingBox = Rect::Invalid;
for(const auto& child : m_children) {
if(auto element = toSVGElement(child); element && !element->isHiddenElement()) {
strokeBoundingBox.unite(element->localTransform().mapRect(element->strokeBoundingBox()));
}
}
if(!strokeBoundingBox.isValid())
strokeBoundingBox = Rect::Empty;
return strokeBoundingBox;
}
Rect SVGElement::paintBoundingBox() const
{
if(m_paintBoundingBox.isValid())
return m_paintBoundingBox;
m_paintBoundingBox = Rect::Empty;
m_paintBoundingBox = strokeBoundingBox();
assert(m_paintBoundingBox.isValid());
if(m_clipper) m_paintBoundingBox.intersect(m_clipper->clipBoundingBox(this));
if(m_masker) m_paintBoundingBox.intersect(m_masker->maskBoundingBox(this));
return m_paintBoundingBox;
}
SVGMarkerElement* SVGElement::getMarker(std::string_view id) const
{
auto element = rootElement()->getElementById(id);
if(element && element->id() == ElementID::Marker)
return static_cast<SVGMarkerElement*>(element);
return nullptr;
}
SVGClipPathElement* SVGElement::getClipper(std::string_view id) const
{
auto element = rootElement()->getElementById(id);
if(element && element->id() == ElementID::ClipPath)
return static_cast<SVGClipPathElement*>(element);
return nullptr;
}
SVGMaskElement* SVGElement::getMasker(std::string_view id) const
{
auto element = rootElement()->getElementById(id);
if(element && element->id() == ElementID::Mask)
return static_cast<SVGMaskElement*>(element);
return nullptr;
}
SVGPaintElement* SVGElement::getPainter(std::string_view id) const
{
auto element = rootElement()->getElementById(id);
if(element && element->isPaintElement())
return static_cast<SVGPaintElement*>(element);
return nullptr;
}
SVGElement* SVGElement::elementFromPoint(float x, float y)
{
auto it = m_children.rbegin();
auto end = m_children.rend();
for(; it != end; ++it) {
auto child = toSVGElement(*it);
if(child && !child->isHiddenElement()) {
if(auto element = child->elementFromPoint(x, y)) {
return element;
}
}
}
if(isPointableElement()) {
auto transform = localTransform();
for(auto parent = parentElement(); parent; parent = parent->parentElement())
transform.postMultiply(parent->localTransform());
auto bbox = transform.mapRect(paintBoundingBox());
if(bbox.contains(x, y)) {
return this;
}
}
return nullptr;
}
void SVGElement::addProperty(SVGProperty& value)
{
m_properties.push_front(&value);
}
SVGProperty* SVGElement::getProperty(PropertyID id) const
{
for(auto property : m_properties) {
if(id == property->id()) {
return property;
}
}
return nullptr;
}
Size SVGElement::currentViewportSize() const
{
auto parent = parentElement();
if(parent == nullptr) {
auto element = static_cast<const SVGSVGElement*>(this);
const auto& viewBox = element->viewBox();
if(viewBox.value().isValid())
return viewBox.value().size();
return Size(300, 150);
}
if(parent->id() == ElementID::Svg) {
auto element = static_cast<const SVGSVGElement*>(parent);
const auto& viewBox = element->viewBox();
if(viewBox.value().isValid())
return viewBox.value().size();
LengthContext lengthContext(element);
auto width = lengthContext.valueForLength(element->width());
auto height = lengthContext.valueForLength(element->height());
return Size(width, height);
}
return parent->currentViewportSize();
}
void SVGElement::cloneChildren(SVGElement* parentElement) const
{
for(const auto& child : m_children) {
parentElement->addChild(child->clone(true));
}
}
std::unique_ptr<SVGNode> SVGElement::clone(bool deep) const
{
auto element = SVGElement::create(document(), m_id);
element->setAttributes(m_attributes);
if(deep) { cloneChildren(element.get()); }
return element;
}
void SVGElement::build()
{
for(const auto& child : m_children) {
if(auto element = toSVGElement(child)) {
element->build();
}
}
}
void SVGElement::layoutElement(const SVGLayoutState& state)
{
m_paintBoundingBox = Rect::Invalid;
m_clipper = getClipper(state.clip_path());
m_masker = getMasker(state.mask());
m_opacity = state.opacity();
m_font_size = state.font_size();
m_display = state.display();
m_overflow = state.overflow();
m_visibility = state.visibility();
m_pointer_events = state.pointer_events();
}
void SVGElement::layoutChildren(SVGLayoutState& state)
{
for(const auto& child : m_children) {
if(auto element = toSVGElement(child)) {
element->layout(state);
}
}
}
void SVGElement::layout(SVGLayoutState& state)
{
SVGLayoutState newState(state, this);
layoutElement(newState);
layoutChildren(newState);
}
void SVGElement::renderChildren(SVGRenderState& state) const
{
for(const auto& child : m_children) {
if(auto element = toSVGElement(child)) {
element->render(state);
}
}
}
void SVGElement::render(SVGRenderState& state) const
{
}
bool SVGElement::isHiddenElement() const
{
if(isDisplayNone())
return true;
switch(m_id) {
case ElementID::Defs:
case ElementID::Symbol:
case ElementID::Marker:
case ElementID::ClipPath:
case ElementID::Mask:
case ElementID::LinearGradient:
case ElementID::RadialGradient:
case ElementID::Pattern:
case ElementID::Stop:
return true;
default:
return false;
}
}
bool SVGElement::isPointableElement() const
{
if(m_pointer_events != PointerEvents::None
&& m_visibility != Visibility::Hidden
&& m_display != Display::None
&& m_opacity != 0.f) {
switch(m_id) {
case ElementID::Line:
case ElementID::Rect:
case ElementID::Ellipse:
case ElementID::Circle:
case ElementID::Polyline:
case ElementID::Polygon:
case ElementID::Path:
case ElementID::Text:
case ElementID::Image:
return true;
default:
break;
}
}
return false;
}
SVGStyleElement::SVGStyleElement(Document* document)
: SVGElement(document, ElementID::Style)
{
}
SVGFitToViewBox::SVGFitToViewBox(SVGElement* element)
: m_viewBox(PropertyID::ViewBox)
, m_preserveAspectRatio(PropertyID::PreserveAspectRatio)
{
element->addProperty(m_viewBox);
element->addProperty(m_preserveAspectRatio);
}
Transform SVGFitToViewBox::viewBoxToViewTransform(const Size& viewportSize) const
{
const auto& viewBoxRect = m_viewBox.value();
if(viewBoxRect.isEmpty() || viewportSize.isEmpty())
return Transform::Identity;
return m_preserveAspectRatio.getTransform(viewBoxRect, viewportSize);
}
Rect SVGFitToViewBox::getClipRect(const Size& viewportSize) const
{
const auto& viewBoxRect = m_viewBox.value();
if(viewBoxRect.isEmpty() || viewportSize.isEmpty())
return Rect(0, 0, viewportSize.w, viewportSize.h);
return m_preserveAspectRatio.getClipRect(viewBoxRect, viewportSize);
}
SVGURIReference::SVGURIReference(SVGElement* element)
: m_href(PropertyID::Href)
{
element->addProperty(m_href);
}
SVGElement* SVGURIReference::getTargetElement(const Document* document) const
{
std::string_view value(m_href.value());
if(value.empty() || value.front() != '#')
return nullptr;
return document->rootElement()->getElementById(value.substr(1));
}
bool SVGPaintServer::applyPaint(SVGRenderState& state) const
{
if(!isRenderable())
return false;
if(m_element) return m_element->applyPaint(state, m_opacity);
state->setColor(m_color.colorWithAlpha(m_opacity));
return true;
}
SVGGraphicsElement::SVGGraphicsElement(Document* document, ElementID id)
: SVGElement(document, id)
, m_transform(PropertyID::Transform)
{
addProperty(m_transform);
}
SVGPaintServer SVGGraphicsElement::getPaintServer(const Paint& paint, float opacity) const
{
if(paint.isNone())
return SVGPaintServer();
if(auto element = getPainter(paint.id()))
return SVGPaintServer(element, paint.color(), opacity);
return SVGPaintServer(nullptr, paint.color(), opacity);
}
StrokeData SVGGraphicsElement::getStrokeData(const SVGLayoutState& state) const
{
LengthContext lengthContext(this);
StrokeData strokeData(lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal));
strokeData.setMiterLimit(state.stroke_miterlimit());
strokeData.setLineCap(state.stroke_linecap());
strokeData.setLineJoin(state.stroke_linejoin());
strokeData.setDashOffset(lengthContext.valueForLength(state.stroke_dashoffset(), LengthDirection::Diagonal));
DashArray dashArray;
for(const auto& dash : state.stroke_dasharray())
dashArray.push_back(lengthContext.valueForLength(dash, LengthDirection::Diagonal));
strokeData.setDashArray(std::move(dashArray));
return strokeData;
}
SVGSVGElement::SVGSVGElement(Document* document)
: SVGGraphicsElement(document, ElementID::Svg)
, SVGFitToViewBox(this)
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent)
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent)
{
addProperty(m_x);
addProperty(m_y);
addProperty(m_width);
addProperty(m_height);
}
Transform SVGSVGElement::localTransform() const
{
LengthContext lengthContext(this);
const Rect viewportRect = {
lengthContext.valueForLength(m_x),
lengthContext.valueForLength(m_y),
lengthContext.valueForLength(m_width),
lengthContext.valueForLength(m_height)
};
if(isRootElement())
return viewBoxToViewTransform(viewportRect.size());
return SVGGraphicsElement::localTransform() * Transform::translated(viewportRect.x, viewportRect.y) * viewBoxToViewTransform(viewportRect.size());
}
void SVGSVGElement::render(SVGRenderState& state) const
{
if(isDisplayNone())
return;
LengthContext lengthContext(this);
const Size viewportSize = {
lengthContext.valueForLength(m_width),
lengthContext.valueForLength(m_height)
};
if(viewportSize.isEmpty())
return;
SVGBlendInfo blendInfo(this);
SVGRenderState newState(this, state, localTransform());
newState.beginGroup(blendInfo);
if(isOverflowHidden())
newState->clipRect(getClipRect(viewportSize), FillRule::NonZero, newState.currentTransform());
renderChildren(newState);
newState.endGroup(blendInfo);
}
SVGRootElement::SVGRootElement(Document* document)
: SVGSVGElement(document)
{
}
SVGRootElement* SVGRootElement::layoutIfNeeded()
{
if(needsLayout())
forceLayout();
return this;
}
SVGElement* SVGRootElement::getElementById(std::string_view id) const
{
auto it = m_idCache.find(id);
if(it == m_idCache.end())
return nullptr;
return it->second;
}
void SVGRootElement::addElementById(const std::string& id, SVGElement* element)
{
m_idCache.emplace(id, element);
}
void SVGRootElement::layout(SVGLayoutState& state)
{
SVGSVGElement::layout(state);
LengthContext lengthContext(this);
if(!width().isPercent()) {
m_intrinsicWidth = lengthContext.valueForLength(width());
} else {
m_intrinsicWidth = 0.f;
}
if(!height().isPercent()) {
m_intrinsicHeight = lengthContext.valueForLength(height());
} else {
m_intrinsicHeight = 0.f;
}
const auto& viewBoxRect = viewBox().value();
if(!viewBoxRect.isEmpty() && (!m_intrinsicWidth || !m_intrinsicHeight)) {
auto intrinsicRatio = viewBoxRect.w / viewBoxRect.h;
if(!m_intrinsicWidth && m_intrinsicHeight)
m_intrinsicWidth = m_intrinsicHeight * intrinsicRatio;
else if(m_intrinsicWidth && !m_intrinsicHeight) {
m_intrinsicHeight = m_intrinsicWidth / intrinsicRatio;
}
}
if(viewBoxRect.isValid() && (!m_intrinsicWidth || !m_intrinsicHeight)) {
m_intrinsicWidth = viewBoxRect.w;
m_intrinsicHeight = viewBoxRect.h;
}
if(!m_intrinsicWidth || !m_intrinsicHeight) {
auto boundingBox = paintBoundingBox();
if(!m_intrinsicWidth)
m_intrinsicWidth = boundingBox.right();
if(!m_intrinsicHeight) {
m_intrinsicHeight = boundingBox.bottom();
}
}
}
void SVGRootElement::forceLayout()
{
SVGLayoutState state;
layout(state);
}
SVGUseElement::SVGUseElement(Document* document)
: SVGGraphicsElement(document, ElementID::Use)
, SVGURIReference(this)
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent)
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent)
{
addProperty(m_x);
addProperty(m_y);
addProperty(m_width);
addProperty(m_height);
}
Transform SVGUseElement::localTransform() const
{
LengthContext lengthContext(this);
const Point translation = {
lengthContext.valueForLength(m_x),
lengthContext.valueForLength(m_y)
};
return SVGGraphicsElement::localTransform() * Transform::translated(translation.x, translation.y);
}
void SVGUseElement::render(SVGRenderState& state) const
{
if(isDisplayNone())
return;
SVGBlendInfo blendInfo(this);
SVGRenderState newState(this, state, localTransform());
newState.beginGroup(blendInfo);
renderChildren(newState);
newState.endGroup(blendInfo);
}
void SVGUseElement::build()
{
if(auto targetElement = getTargetElement(document())) {
if(auto newElement = cloneTargetElement(targetElement)) {
addChild(std::move(newElement));
}
}
SVGGraphicsElement::build();
}
inline bool isDisallowedElement(const SVGElement* element)
{
switch(element->id()) {
case ElementID::Circle:
case ElementID::Ellipse:
case ElementID::G:
case ElementID::Image:
case ElementID::Line:
case ElementID::Path:
case ElementID::Polygon:
case ElementID::Polyline:
case ElementID::Rect:
case ElementID::Svg:
case ElementID::Symbol:
case ElementID::Text:
case ElementID::Tspan:
case ElementID::Use:
return false;
default:
return true;
}
}
std::unique_ptr<SVGElement> SVGUseElement::cloneTargetElement(SVGElement* targetElement)
{
if(targetElement == this || isDisallowedElement(targetElement))
return nullptr;
const auto& idAttr = targetElement->getAttribute(PropertyID::Id);
auto parent = parentElement();
while(parent) {
auto attribute = parent->findAttribute(PropertyID::Id);
if(attribute && idAttr == attribute->value())
return nullptr;
parent = parent->parentElement();
}
auto tagId = targetElement->id();
if(tagId == ElementID::Symbol) {
tagId = ElementID::Svg;
}
auto newElement = SVGElement::create(document(), tagId);
newElement->setAttributes(targetElement->attributes());
if(newElement->id() == ElementID::Svg) {
for(const auto& attribute : attributes()) {
if(attribute.id() == PropertyID::Width || attribute.id() == PropertyID::Height) {
newElement->setAttribute(attribute);
}
}
}
if(newElement->id() != ElementID::Use)
targetElement->cloneChildren(newElement.get());
return newElement;
}
SVGImageElement::SVGImageElement(Document* document)
: SVGGraphicsElement(document, ElementID::Image)
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent)
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 100.f, LengthUnits::Percent)
, m_preserveAspectRatio(PropertyID::PreserveAspectRatio)
{
addProperty(m_x);
addProperty(m_y);
addProperty(m_width);
addProperty(m_height);
addProperty(m_preserveAspectRatio);
}
Rect SVGImageElement::fillBoundingBox() const
{
LengthContext lengthContext(this);
const Rect viewportRect = {
lengthContext.valueForLength(m_x),
lengthContext.valueForLength(m_y),
lengthContext.valueForLength(m_width),
lengthContext.valueForLength(m_height)
};
return viewportRect;
}
Rect SVGImageElement::strokeBoundingBox() const
{
return fillBoundingBox();
}
void SVGImageElement::render(SVGRenderState& state) const
{
if(m_image.isNull() || isDisplayNone() || isVisibilityHidden())
return;
Rect dstRect(fillBoundingBox());
Rect srcRect(0, 0, m_image.width(), m_image.height());
if(dstRect.isEmpty() || srcRect.isEmpty())
return;
m_preserveAspectRatio.transformRect(dstRect, srcRect);
SVGBlendInfo blendInfo(this);
SVGRenderState newState(this, state, localTransform());
newState.beginGroup(blendInfo);
newState->drawImage(m_image, dstRect, srcRect, newState.currentTransform());
newState.endGroup(blendInfo);
}
static Bitmap loadImageResource(const std::string& href)
{
if(href.compare(0, 5, "data:") == 0) {
std::string_view input(href);
auto index = input.find(',', 5);
if(index == std::string_view::npos)
return Bitmap();
input.remove_prefix(index + 1);
return plutovg_surface_load_from_image_base64(input.data(), input.length());
}
return plutovg_surface_load_from_image_file(href.data());
}
void SVGImageElement::parseAttribute(PropertyID id, const std::string& value)
{
if(id == PropertyID::Href) {
m_image = loadImageResource(value);
} else {
SVGGraphicsElement::parseAttribute(id, value);
}
}
SVGSymbolElement::SVGSymbolElement(Document* document)
: SVGGraphicsElement(document, ElementID::Symbol)
, SVGFitToViewBox(this)
{
}
SVGGElement::SVGGElement(Document* document)
: SVGGraphicsElement(document, ElementID::G)
{
}
void SVGGElement::render(SVGRenderState& state) const
{
if(isDisplayNone())
return;
SVGBlendInfo blendInfo(this);
SVGRenderState newState(this, state, localTransform());
newState.beginGroup(blendInfo);
renderChildren(newState);
newState.endGroup(blendInfo);
}
SVGDefsElement::SVGDefsElement(Document* document)
: SVGGraphicsElement(document, ElementID::Defs)
{
}
SVGMarkerElement::SVGMarkerElement(Document* document)
: SVGElement(document, ElementID::Marker)
, SVGFitToViewBox(this)
, m_refX(PropertyID::RefX, LengthDirection::Horizontal, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_refY(PropertyID::RefY, LengthDirection::Vertical, LengthNegativeMode::Allow, 0.f, LengthUnits::None)
, m_markerWidth(PropertyID::MarkerWidth, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 3.f, LengthUnits::None)
, m_markerHeight(PropertyID::MarkerHeight, LengthDirection::Vertical, LengthNegativeMode::Forbid, 3.f, LengthUnits::None)
, m_markerUnits(PropertyID::MarkerUnits, MarkerUnits::StrokeWidth)
, m_orient(PropertyID::Orient)
{
addProperty(m_refX);
addProperty(m_refY);
addProperty(m_markerWidth);
addProperty(m_markerHeight);
addProperty(m_markerUnits);
addProperty(m_orient);
}
Point SVGMarkerElement::refPoint() const
{
LengthContext lengthContext(this);
const Point refPoint = {
lengthContext.valueForLength(m_refX),
lengthContext.valueForLength(m_refY)
};
return refPoint;
}
Size SVGMarkerElement::markerSize() const
{
LengthContext lengthContext(this);
const Size markerSize = {
lengthContext.valueForLength(m_markerWidth),
lengthContext.valueForLength(m_markerHeight)
};
return markerSize;
}
Transform SVGMarkerElement::markerTransform(const Point& origin, float angle, float strokeWidth) const
{
auto transform = Transform::translated(origin.x, origin.y);
if(m_orient.orientType() == SVGAngle::OrientType::Angle) {
transform.rotate(m_orient.value());
} else {
transform.rotate(angle);
}
auto viewTransform = viewBoxToViewTransform(markerSize());
auto refOrigin = viewTransform.mapPoint(refPoint());
if(m_markerUnits.value() == MarkerUnits::StrokeWidth)
transform.scale(strokeWidth, strokeWidth);
transform.translate(-refOrigin.x, -refOrigin.y);
return transform * viewTransform;
}
Rect SVGMarkerElement::markerBoundingBox(const Point& origin, float angle, float strokeWidth) const
{
return markerTransform(origin, angle, strokeWidth).mapRect(paintBoundingBox());
}
void SVGMarkerElement::renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const
{
if(state.hasCycleReference(this))
return;
SVGBlendInfo blendInfo(this);
SVGRenderState newState(this, state, markerTransform(origin, angle, strokeWidth));
newState.beginGroup(blendInfo);
if(isOverflowHidden())
newState->clipRect(getClipRect(markerSize()), FillRule::NonZero, newState.currentTransform());
renderChildren(newState);
newState.endGroup(blendInfo);
}
Transform SVGMarkerElement::localTransform() const
{
return viewBoxToViewTransform(markerSize());
}
SVGClipPathElement::SVGClipPathElement(Document* document)
: SVGGraphicsElement(document, ElementID::ClipPath)
, m_clipPathUnits(PropertyID::ClipPathUnits, Units::UserSpaceOnUse)
{
addProperty(m_clipPathUnits);
}
Rect SVGClipPathElement::clipBoundingBox(const SVGElement* element) const
{
auto clipBoundingBox = paintBoundingBox();
if(m_clipPathUnits.value() == Units::ObjectBoundingBox) {
auto bbox = element->fillBoundingBox();
clipBoundingBox.x = clipBoundingBox.x * bbox.w + bbox.x;
clipBoundingBox.y = clipBoundingBox.y * bbox.h + bbox.y;
clipBoundingBox.w = clipBoundingBox.w * bbox.w;
clipBoundingBox.h = clipBoundingBox.h * bbox.h;
}
return localTransform().mapRect(clipBoundingBox);
}
void SVGClipPathElement::applyClipMask(SVGRenderState& state) const
{
if(state.hasCycleReference(this))
return;
auto maskImage = Canvas::create(state.currentTransform().mapRect(state.paintBoundingBox()));
auto currentTransform = state.currentTransform() * localTransform();
if(m_clipPathUnits.value() == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
currentTransform.translate(bbox.x, bbox.y);
currentTransform.scale(bbox.w, bbox.h);
}
SVGRenderState newState(this, &state, currentTransform, SVGRenderMode::Clipping, maskImage);
renderChildren(newState);
if(clipper()) {
clipper()->applyClipMask(newState);
}
state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f);
}
inline const SVGGeometryElement* toSVGGeometryElement(const SVGNode* node)
{
if(node && node->isGeometryElement())
return static_cast<const SVGGeometryElement*>(node);
return nullptr;
}
void SVGClipPathElement::applyClipPath(SVGRenderState& state) const
{
auto currentTransform = state.currentTransform() * localTransform();
if(m_clipPathUnits.value() == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
currentTransform.translate(bbox.x, bbox.y);
currentTransform.scale(bbox.w, bbox.h);
}
for(const auto& child : children()) {
auto element = toSVGElement(child);
if(element == nullptr || element->isDisplayNone())
continue;
Transform clipTransform(currentTransform);
auto shapeElement = toSVGGeometryElement(element);
if(shapeElement == nullptr) {
if(element->id() != ElementID::Use)
continue;
clipTransform.multiply(element->localTransform());
shapeElement = toSVGGeometryElement(element->firstChild());
}
if(shapeElement == nullptr || !shapeElement->isRenderable())
continue;
state->clipPath(shapeElement->path(), shapeElement->clip_rule(), clipTransform * shapeElement->localTransform());
return;
}
state->clipRect(Rect::Empty, FillRule::NonZero, Transform::Identity);
}
bool SVGClipPathElement::requiresMasking() const
{
if(clipper())
return true;
const SVGGeometryElement* prevShapeElement = nullptr;
for(const auto& child : children()) {
auto element = toSVGElement(child);
if(element == nullptr || element->isDisplayNone())
continue;
auto shapeElement = toSVGGeometryElement(element);
if(shapeElement == nullptr) {
if(element->isTextPositioningElement())
return true;
if(element->id() != ElementID::Use)
continue;
if(element->clipper())
return true;
shapeElement = toSVGGeometryElement(element->firstChild());
}
if(shapeElement == nullptr || !shapeElement->isRenderable())
continue;
if(prevShapeElement || shapeElement->clipper())
return true;
prevShapeElement = shapeElement;
}
return false;
}
SVGMaskElement::SVGMaskElement(Document* document)
: SVGElement(document, ElementID::Mask)
, m_x(PropertyID::X, LengthDirection::Horizontal, LengthNegativeMode::Allow, -10.f, LengthUnits::Percent)
, m_y(PropertyID::Y, LengthDirection::Vertical, LengthNegativeMode::Allow, -10.f, LengthUnits::Percent)
, m_width(PropertyID::Width, LengthDirection::Horizontal, LengthNegativeMode::Forbid, 120.f, LengthUnits::Percent)
, m_height(PropertyID::Height, LengthDirection::Vertical, LengthNegativeMode::Forbid, 120.f, LengthUnits::Percent)
, m_maskUnits(PropertyID::MaskUnits, Units::ObjectBoundingBox)
, m_maskContentUnits(PropertyID::MaskContentUnits, Units::UserSpaceOnUse)
{
addProperty(m_x);
addProperty(m_y);
addProperty(m_width);
addProperty(m_height);
addProperty(m_maskUnits);
addProperty(m_maskContentUnits);
}
Rect SVGMaskElement::maskRect(const SVGElement* element) const
{
LengthContext lengthContext(this, m_maskUnits.value());
Rect maskRect = {
lengthContext.valueForLength(m_x),
lengthContext.valueForLength(m_y),
lengthContext.valueForLength(m_width),
lengthContext.valueForLength(m_height)
};
if(m_maskUnits.value() == Units::ObjectBoundingBox) {
auto bbox = element->fillBoundingBox();
maskRect.x = maskRect.x * bbox.w + bbox.x;
maskRect.y = maskRect.y * bbox.h + bbox.y;
maskRect.w = maskRect.w * bbox.w;
maskRect.h = maskRect.h * bbox.h;
}
return maskRect;
}
Rect SVGMaskElement::maskBoundingBox(const SVGElement* element) const
{
auto maskBoundingBox = paintBoundingBox();
if(m_maskContentUnits.value() == Units::ObjectBoundingBox) {
auto bbox = element->fillBoundingBox();
maskBoundingBox.x = maskBoundingBox.x * bbox.w + bbox.x;
maskBoundingBox.y = maskBoundingBox.y * bbox.h + bbox.y;
maskBoundingBox.w = maskBoundingBox.w * bbox.w;
maskBoundingBox.h = maskBoundingBox.h * bbox.h;
}
return maskBoundingBox.intersected(maskRect(element));
}
void SVGMaskElement::applyMask(SVGRenderState& state) const
{
if(state.hasCycleReference(this))
return;
auto maskImage = Canvas::create(state.currentTransform().mapRect(state.paintBoundingBox()));
maskImage->clipRect(maskRect(state.element()), FillRule::NonZero, state.currentTransform());
auto currentTransform = state.currentTransform();
if(m_maskContentUnits.value() == Units::ObjectBoundingBox) {
auto bbox = state.fillBoundingBox();
currentTransform.translate(bbox.x, bbox.y);
currentTransform.scale(bbox.w, bbox.h);
}
SVGRenderState newState(this, &state, currentTransform, SVGRenderMode::Painting, maskImage);
renderChildren(newState);
if(clipper())
clipper()->applyClipMask(newState);
if(masker()) {
masker()->applyMask(newState);
}
if(m_mask_type == MaskType::Luminance)
maskImage->convertToLuminanceMask();
state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f);
}
void SVGMaskElement::layoutElement(const SVGLayoutState& state)
{
m_mask_type = state.mask_type();
SVGElement::layoutElement(state);
}
} // namespace lunasvg