499 lines
14 KiB
C++
499 lines
14 KiB
C++
#ifndef LUNASVG_SVGELEMENT_H
|
|
#define LUNASVG_SVGELEMENT_H
|
|
|
|
#include "lunasvg.h"
|
|
#include "svgproperty.h"
|
|
|
|
#include <string>
|
|
#include <forward_list>
|
|
#include <list>
|
|
#include <map>
|
|
|
|
namespace lunasvg {
|
|
|
|
class Document;
|
|
class SVGElement;
|
|
class SVGRootElement;
|
|
|
|
class SVGNode {
|
|
public:
|
|
SVGNode(Document* document)
|
|
: m_document(document)
|
|
{}
|
|
|
|
virtual ~SVGNode() = default;
|
|
virtual bool isTextNode() const { return false; }
|
|
virtual bool isElement() const { return false; }
|
|
virtual bool isPaintElement() const { return false; }
|
|
virtual bool isGraphicsElement() const { return false; }
|
|
virtual bool isGeometryElement() const { return false; }
|
|
virtual bool isTextPositioningElement() const { return false; }
|
|
|
|
Document* document() const { return m_document; }
|
|
SVGRootElement* rootElement() const { return m_document->rootElement(); }
|
|
|
|
SVGElement* parentElement() const { return m_parentElement; }
|
|
void setParentElement(SVGElement* parent) { m_parentElement = parent; }
|
|
|
|
bool isRootElement() const { return m_parentElement == nullptr; }
|
|
|
|
virtual std::unique_ptr<SVGNode> clone(bool deep) const = 0;
|
|
|
|
private:
|
|
SVGNode(const SVGNode&) = delete;
|
|
SVGNode& operator=(const SVGNode&) = delete;
|
|
Document* m_document;
|
|
SVGElement* m_parentElement = nullptr;
|
|
};
|
|
|
|
class SVGTextNode final : public SVGNode {
|
|
public:
|
|
SVGTextNode(Document* document);
|
|
|
|
bool isTextNode() const final { return true; }
|
|
|
|
const std::string& data() const { return m_data; }
|
|
void setData(const std::string& data);
|
|
|
|
std::unique_ptr<SVGNode> clone(bool deep) const final;
|
|
|
|
private:
|
|
std::string m_data;
|
|
};
|
|
|
|
class Attribute {
|
|
public:
|
|
Attribute() = default;
|
|
Attribute(int specificity, PropertyID id, std::string value)
|
|
: m_specificity(specificity), m_id(id), m_value(std::move(value))
|
|
{}
|
|
|
|
int specificity() const { return m_specificity; }
|
|
PropertyID id() const { return m_id; }
|
|
const std::string& value() const { return m_value; }
|
|
|
|
private:
|
|
int m_specificity;
|
|
PropertyID m_id;
|
|
std::string m_value;
|
|
};
|
|
|
|
using AttributeList = std::forward_list<Attribute>;
|
|
|
|
enum class ElementID : uint8_t {
|
|
Unknown = 0,
|
|
Star,
|
|
Circle,
|
|
ClipPath,
|
|
Defs,
|
|
Ellipse,
|
|
G,
|
|
Image,
|
|
Line,
|
|
LinearGradient,
|
|
Marker,
|
|
Mask,
|
|
Path,
|
|
Pattern,
|
|
Polygon,
|
|
Polyline,
|
|
RadialGradient,
|
|
Rect,
|
|
Stop,
|
|
Style,
|
|
Svg,
|
|
Symbol,
|
|
Text,
|
|
Tspan,
|
|
Use
|
|
};
|
|
|
|
ElementID elementid(std::string_view name);
|
|
|
|
using SVGNodeList = std::list<std::unique_ptr<SVGNode>>;
|
|
using SVGPropertyList = std::forward_list<SVGProperty*>;
|
|
|
|
class SVGMarkerElement;
|
|
class SVGClipPathElement;
|
|
class SVGMaskElement;
|
|
class SVGPaintElement;
|
|
class SVGLayoutState;
|
|
class SVGRenderState;
|
|
|
|
extern const std::string emptyString;
|
|
|
|
class SVGElement : public SVGNode {
|
|
public:
|
|
static std::unique_ptr<SVGElement> create(Document* document, ElementID id);
|
|
|
|
SVGElement(Document* document, ElementID id);
|
|
virtual ~SVGElement() = default;
|
|
|
|
bool hasAttribute(std::string_view name) const;
|
|
const std::string& getAttribute(std::string_view name) const;
|
|
bool setAttribute(std::string_view name, const std::string& value);
|
|
|
|
const Attribute* findAttribute(PropertyID id) const;
|
|
bool hasAttribute(PropertyID id) const;
|
|
const std::string& getAttribute(PropertyID id) const;
|
|
bool setAttribute(int specificity, PropertyID id, const std::string& value);
|
|
void setAttributes(const AttributeList& attributes);
|
|
bool setAttribute(const Attribute& attribute);
|
|
|
|
virtual void parseAttribute(PropertyID id, const std::string& value);
|
|
|
|
SVGElement* previousElement() const;
|
|
SVGElement* nextElement() const;
|
|
|
|
SVGNode* addChild(std::unique_ptr<SVGNode> child);
|
|
SVGNode* firstChild() const;
|
|
SVGNode* lastChild() const;
|
|
|
|
ElementID id() const { return m_id; }
|
|
const AttributeList& attributes() const { return m_attributes; }
|
|
const SVGPropertyList& properties() const { return m_properties; }
|
|
const SVGNodeList& children() const { return m_children; }
|
|
|
|
virtual Transform localTransform() const { return Transform::Identity; }
|
|
virtual Rect fillBoundingBox() const;
|
|
virtual Rect strokeBoundingBox() const;
|
|
virtual Rect paintBoundingBox() const;
|
|
|
|
SVGMarkerElement* getMarker(std::string_view id) const;
|
|
SVGClipPathElement* getClipper(std::string_view id) const;
|
|
SVGMaskElement* getMasker(std::string_view id) const;
|
|
SVGPaintElement* getPainter(std::string_view id) const;
|
|
|
|
SVGElement* elementFromPoint(float x, float y);
|
|
|
|
template<typename T>
|
|
void transverse(T callback);
|
|
|
|
void addProperty(SVGProperty& value);
|
|
SVGProperty* getProperty(PropertyID id) const;
|
|
Size currentViewportSize() const;
|
|
float font_size() const { return m_font_size; }
|
|
|
|
void cloneChildren(SVGElement* parentElement) const;
|
|
std::unique_ptr<SVGNode> clone(bool deep) const final;
|
|
|
|
virtual void build();
|
|
|
|
virtual void layoutElement(const SVGLayoutState& state);
|
|
void layoutChildren(SVGLayoutState& state);
|
|
virtual void layout(SVGLayoutState& state);
|
|
|
|
void renderChildren(SVGRenderState& state) const;
|
|
virtual void render(SVGRenderState& state) const;
|
|
|
|
bool isDisplayNone() const { return m_display == Display::None; }
|
|
bool isOverflowHidden() const { return m_overflow == Overflow::Hidden; }
|
|
bool isVisibilityHidden() const { return m_visibility != Visibility::Visible; }
|
|
|
|
bool isHiddenElement() const;
|
|
bool isPointableElement() const;
|
|
|
|
const SVGClipPathElement* clipper() const { return m_clipper; }
|
|
const SVGMaskElement* masker() const { return m_masker; }
|
|
float opacity() const { return m_opacity; }
|
|
|
|
bool isElement() const final { return true; }
|
|
|
|
private:
|
|
mutable Rect m_paintBoundingBox = Rect::Invalid;
|
|
const SVGClipPathElement* m_clipper = nullptr;
|
|
const SVGMaskElement* m_masker = nullptr;
|
|
float m_opacity = 1.f;
|
|
|
|
float m_font_size = 12.f;
|
|
Display m_display = Display::Inline;
|
|
Overflow m_overflow = Overflow::Visible;
|
|
Visibility m_visibility = Visibility::Visible;
|
|
PointerEvents m_pointer_events = PointerEvents::Auto;
|
|
|
|
ElementID m_id;
|
|
AttributeList m_attributes;
|
|
SVGPropertyList m_properties;
|
|
SVGNodeList m_children;
|
|
};
|
|
|
|
inline const SVGElement* toSVGElement(const SVGNode* node)
|
|
{
|
|
if(node && node->isElement())
|
|
return static_cast<const SVGElement*>(node);
|
|
return nullptr;
|
|
}
|
|
|
|
inline SVGElement* toSVGElement(SVGNode* node)
|
|
{
|
|
if(node && node->isElement())
|
|
return static_cast<SVGElement*>(node);
|
|
return nullptr;
|
|
}
|
|
|
|
inline SVGElement* toSVGElement(const std::unique_ptr<SVGNode>& node)
|
|
{
|
|
return toSVGElement(node.get());
|
|
}
|
|
|
|
template<typename T>
|
|
inline void SVGElement::transverse(T callback)
|
|
{
|
|
callback(this);
|
|
for(const auto& child : m_children) {
|
|
if(auto element = toSVGElement(child)) {
|
|
element->transverse(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
class SVGStyleElement final : public SVGElement {
|
|
public:
|
|
SVGStyleElement(Document* document);
|
|
};
|
|
|
|
class SVGFitToViewBox {
|
|
public:
|
|
SVGFitToViewBox(SVGElement* element);
|
|
|
|
const SVGRect& viewBox() const { return m_viewBox; }
|
|
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
|
|
Transform viewBoxToViewTransform(const Size& viewportSize) const;
|
|
Rect getClipRect(const Size& viewportSize) const;
|
|
|
|
private:
|
|
SVGRect m_viewBox;
|
|
SVGPreserveAspectRatio m_preserveAspectRatio;
|
|
};
|
|
|
|
class SVGURIReference {
|
|
public:
|
|
SVGURIReference(SVGElement* element);
|
|
|
|
const SVGString& href() const { return m_href; }
|
|
const std::string& hrefString() const { return m_href.value(); }
|
|
SVGElement* getTargetElement(const Document* document) const;
|
|
|
|
private:
|
|
SVGString m_href;
|
|
};
|
|
|
|
class SVGPaintServer {
|
|
public:
|
|
SVGPaintServer() = default;
|
|
SVGPaintServer(const SVGPaintElement* element, const Color& color, float opacity)
|
|
: m_element(element), m_color(color), m_opacity(opacity)
|
|
{}
|
|
|
|
bool isRenderable() const { return m_opacity > 0.f && (m_element || m_color.alpha() > 0); }
|
|
|
|
const SVGPaintElement* element() const { return m_element; }
|
|
const Color& color() const { return m_color; }
|
|
float opacity() const { return m_opacity; }
|
|
|
|
bool applyPaint(SVGRenderState& state) const;
|
|
|
|
private:
|
|
const SVGPaintElement* m_element = nullptr;
|
|
Color m_color = Color::Transparent;
|
|
float m_opacity = 0.f;
|
|
};
|
|
|
|
class SVGGraphicsElement : public SVGElement {
|
|
public:
|
|
SVGGraphicsElement(Document* document, ElementID id);
|
|
|
|
bool isGraphicsElement() const final { return true; }
|
|
|
|
const SVGTransform& transform() const { return m_transform; }
|
|
Transform localTransform() const override { return m_transform.value(); }
|
|
|
|
SVGPaintServer getPaintServer(const Paint& paint, float opacity) const;
|
|
StrokeData getStrokeData(const SVGLayoutState& state) const;
|
|
|
|
private:
|
|
SVGTransform m_transform;
|
|
};
|
|
|
|
class SVGSVGElement : public SVGGraphicsElement, public SVGFitToViewBox {
|
|
public:
|
|
SVGSVGElement(Document* document);
|
|
|
|
const SVGLength& x() const { return m_x; }
|
|
const SVGLength& y() const { return m_y; }
|
|
const SVGLength& width() const { return m_width; }
|
|
const SVGLength& height() const { return m_height; }
|
|
|
|
Transform localTransform() const override;
|
|
void render(SVGRenderState& state) const override;
|
|
|
|
private:
|
|
SVGLength m_x;
|
|
SVGLength m_y;
|
|
SVGLength m_width;
|
|
SVGLength m_height;
|
|
};
|
|
|
|
class SVGRootElement final : public SVGSVGElement {
|
|
public:
|
|
SVGRootElement(Document* document);
|
|
|
|
float intrinsicWidth() const { return m_intrinsicWidth; }
|
|
float intrinsicHeight() const { return m_intrinsicHeight; }
|
|
|
|
void setNeedsLayout() { m_intrinsicWidth = -1.f; }
|
|
bool needsLayout() const { return m_intrinsicWidth == -1.f; }
|
|
|
|
SVGRootElement* layoutIfNeeded();
|
|
|
|
SVGElement* getElementById(std::string_view id) const;
|
|
void addElementById(const std::string& id, SVGElement* element);
|
|
void layout(SVGLayoutState& state) final;
|
|
|
|
void forceLayout();
|
|
|
|
private:
|
|
std::map<std::string, SVGElement*, std::less<>> m_idCache;
|
|
float m_intrinsicWidth{-1.f};
|
|
float m_intrinsicHeight{-1.f};
|
|
};
|
|
|
|
class SVGUseElement final : public SVGGraphicsElement, public SVGURIReference {
|
|
public:
|
|
SVGUseElement(Document* document);
|
|
|
|
const SVGLength& x() const { return m_x; }
|
|
const SVGLength& y() const { return m_y; }
|
|
const SVGLength& width() const { return m_width; }
|
|
const SVGLength& height() const { return m_height; }
|
|
|
|
Transform localTransform() const final;
|
|
void render(SVGRenderState& state) const final;
|
|
void build() final;
|
|
|
|
private:
|
|
std::unique_ptr<SVGElement> cloneTargetElement(SVGElement* targetElement);
|
|
SVGLength m_x;
|
|
SVGLength m_y;
|
|
SVGLength m_width;
|
|
SVGLength m_height;
|
|
};
|
|
|
|
class SVGImageElement final : public SVGGraphicsElement {
|
|
public:
|
|
SVGImageElement(Document* document);
|
|
|
|
const SVGLength& x() const { return m_x; }
|
|
const SVGLength& y() const { return m_y; }
|
|
const SVGLength& width() const { return m_width; }
|
|
const SVGLength& height() const { return m_height; }
|
|
const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
|
|
const Bitmap& image() const { return m_image; }
|
|
|
|
Rect fillBoundingBox() const final;
|
|
Rect strokeBoundingBox() const final;
|
|
void render(SVGRenderState& state) const final;
|
|
void parseAttribute(PropertyID id, const std::string& value) final;
|
|
|
|
private:
|
|
SVGLength m_x;
|
|
SVGLength m_y;
|
|
SVGLength m_width;
|
|
SVGLength m_height;
|
|
SVGPreserveAspectRatio m_preserveAspectRatio;
|
|
Bitmap m_image;
|
|
};
|
|
|
|
class SVGSymbolElement final : public SVGGraphicsElement, public SVGFitToViewBox {
|
|
public:
|
|
SVGSymbolElement(Document* document);
|
|
};
|
|
|
|
class SVGGElement final : public SVGGraphicsElement {
|
|
public:
|
|
SVGGElement(Document* document);
|
|
|
|
void render(SVGRenderState& state) const final;
|
|
};
|
|
|
|
class SVGDefsElement final : public SVGGraphicsElement {
|
|
public:
|
|
SVGDefsElement(Document* document);
|
|
};
|
|
|
|
class SVGMarkerElement final : public SVGElement, public SVGFitToViewBox {
|
|
public:
|
|
SVGMarkerElement(Document* document);
|
|
|
|
const SVGLength& refX() const { return m_refX; }
|
|
const SVGLength& refY() const { return m_refY; }
|
|
const SVGLength& markerWidth() const { return m_markerWidth; }
|
|
const SVGLength& markerHeight() const { return m_markerHeight; }
|
|
const SVGEnumeration<MarkerUnits>& markerUnits() const { return m_markerUnits; }
|
|
const SVGAngle& orient() const { return m_orient; }
|
|
|
|
Point refPoint() const;
|
|
Size markerSize() const;
|
|
|
|
Transform markerTransform(const Point& origin, float angle, float strokeWidth) const;
|
|
Rect markerBoundingBox(const Point& origin, float angle, float strokeWidth) const;
|
|
void renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const;
|
|
|
|
Transform localTransform() const final;
|
|
|
|
private:
|
|
SVGLength m_refX;
|
|
SVGLength m_refY;
|
|
SVGLength m_markerWidth;
|
|
SVGLength m_markerHeight;
|
|
SVGEnumeration<MarkerUnits> m_markerUnits;
|
|
SVGAngle m_orient;
|
|
};
|
|
|
|
class SVGClipPathElement final : public SVGGraphicsElement {
|
|
public:
|
|
SVGClipPathElement(Document* document);
|
|
|
|
const SVGEnumeration<Units>& clipPathUnits() const { return m_clipPathUnits; }
|
|
Rect clipBoundingBox(const SVGElement* element) const;
|
|
|
|
void applyClipMask(SVGRenderState& state) const;
|
|
void applyClipPath(SVGRenderState& state) const;
|
|
|
|
bool requiresMasking() const;
|
|
|
|
private:
|
|
SVGEnumeration<Units> m_clipPathUnits;
|
|
};
|
|
|
|
class SVGMaskElement final : public SVGElement {
|
|
public:
|
|
SVGMaskElement(Document* document);
|
|
|
|
const SVGLength& x() const { return m_x; }
|
|
const SVGLength& y() const { return m_y; }
|
|
const SVGLength& width() const { return m_width; }
|
|
const SVGLength& height() const { return m_height; }
|
|
const SVGEnumeration<Units>& maskUnits() const { return m_maskUnits; }
|
|
const SVGEnumeration<Units>& maskContentUnits() const { return m_maskContentUnits; }
|
|
|
|
Rect maskRect(const SVGElement* element) const;
|
|
Rect maskBoundingBox(const SVGElement* element) const;
|
|
void applyMask(SVGRenderState& state) const;
|
|
|
|
void layoutElement(const SVGLayoutState& state) final;
|
|
|
|
private:
|
|
SVGLength m_x;
|
|
SVGLength m_y;
|
|
SVGLength m_width;
|
|
SVGLength m_height;
|
|
SVGEnumeration<Units> m_maskUnits;
|
|
SVGEnumeration<Units> m_maskContentUnits;
|
|
MaskType m_mask_type = MaskType::Luminance;
|
|
};
|
|
|
|
} // namespace lunasvg
|
|
|
|
#endif // LUNASVG_SVGELEMENT_H
|