diff --git a/include/tgfx/core/FontStyle.h b/include/tgfx/core/FontStyle.h new file mode 100644 index 00000000..92fda35d --- /dev/null +++ b/include/tgfx/core/FontStyle.h @@ -0,0 +1,111 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +namespace tgfx { + +/** + * Font style object + */ +class FontStyle { + public: + enum class Weight { + Invisible, + Thin, + ExtraLight, + Light, + Normal, + Medium, + SemiBold, + Bold, + ExtraBold, + Black, + ExtraBlack, + }; + + enum class Width { + UltraCondensed, + ExtraCondensed, + Condensed, + SemiCondensed, + Normal, + SemiExpanded, + Expanded, + ExtraExpanded, + UltraExpanded, + }; + + enum class Slant { + Upright, + Italic, + Oblique, + }; + + constexpr FontStyle(Weight weight, Width width, Slant slant) + : value(static_cast(std::clamp(weight, Weight::Invisible, Weight::ExtraBlack)) + + (static_cast(std::clamp(width, Width::UltraCondensed, Width::UltraExpanded)) + << 16) + + (static_cast(std::clamp(slant, Slant::Upright, Slant::Oblique)) << 24)) { + } + + constexpr FontStyle() : FontStyle{Weight::Normal, Width::Normal, Slant::Upright} { + } + + bool operator==(const FontStyle& rhs) const { + return value == rhs.value; + } + + Weight weight() const { + return static_cast(value & 0xFFFF); + } + + Width width() const { + return static_cast((value >> 16) & 0xFF); + } + + Slant slant() const { + return static_cast((value >> 24) & 0xFF); + } + + static constexpr FontStyle Normal() { + return FontStyle(Weight::Normal, Width::Normal, Slant::Upright); + } + + static constexpr FontStyle Bold() { + return FontStyle(Weight::Bold, Width::Normal, Slant::Upright); + } + + static constexpr FontStyle Italic() { + return FontStyle(Weight::Normal, Width::Normal, Slant::Italic); + } + + static constexpr FontStyle BoldItalic() { + return FontStyle(Weight::Bold, Width::Normal, Slant::Italic); + } + + private: + uint32_t value; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/core/Stream.h b/include/tgfx/core/Stream.h index b5f85935..12a50cca 100644 --- a/include/tgfx/core/Stream.h +++ b/include/tgfx/core/Stream.h @@ -20,6 +20,7 @@ #include #include +#include "tgfx/core/Data.h" namespace tgfx { /** @@ -35,6 +36,11 @@ class Stream { */ static std::unique_ptr MakeFromFile(const std::string& filePath); + /** + * Creates a stream from the specified data. Returns nullptr on failure. + */ + static std::unique_ptr MakeFromData(std::shared_ptr data); + /** * Returns the total length of the stream. If this cannot be done, returns 0. */ @@ -64,6 +70,13 @@ class Stream { * beginning after this call returns. */ virtual bool rewind() = 0; + + /** + * Returns the starting address for the data. If this cannot be done, returns nullptr. + */ + virtual const void* getMemoryBase() { + return nullptr; + } }; /** diff --git a/include/tgfx/core/Typeface.h b/include/tgfx/core/Typeface.h index 6c4bd6a5..2e63f338 100644 --- a/include/tgfx/core/Typeface.h +++ b/include/tgfx/core/Typeface.h @@ -22,6 +22,7 @@ #include #include #include "tgfx/core/Data.h" +#include "tgfx/core/FontStyle.h" namespace tgfx { /** @@ -56,6 +57,9 @@ class Typeface { static std::shared_ptr MakeFromName(const std::string& fontFamily, const std::string& fontStyle); + static std::shared_ptr MakeFromStyle(const std::string& fontFamily, + FontStyle fontStyle); + /** * Creates a new typeface for the given file path and ttc index. Returns nullptr if the typeface * can't be created. diff --git a/include/tgfx/svg/FontManager.h b/include/tgfx/svg/FontManager.h new file mode 100644 index 00000000..cab02139 --- /dev/null +++ b/include/tgfx/svg/FontManager.h @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include "tgfx/core/FontStyle.h" +#include "tgfx/core/Typeface.h" + +namespace tgfx { +/** + * FontManager provides functionality to enumerate Typefaces and match them based on FontStyle. + */ +class FontManager { + public: + /** + * Destructor for FontManager + */ + virtual ~FontManager() = default; + + /** + * Match a font family name and style, and return the corresponding Typeface + */ + std::shared_ptr matchTypeface(const std::string& familyName, FontStyle style) const; + + /** + * Match a font family name, style, character, and language, and return the corresponding Typeface + */ + std::shared_ptr getFallbackTypeface(const std::string& familyName, FontStyle style, + Unichar character) const; + + private: + virtual std::shared_ptr onMatchTypeface(const std::string& familyName, + FontStyle style) const = 0; + virtual std::shared_ptr onGetFallbackTypeface(const std::string& familyName, + FontStyle style, + Unichar character) const = 0; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/ResourceLoader.h b/include/tgfx/svg/ResourceLoader.h new file mode 100644 index 00000000..146fcd22 --- /dev/null +++ b/include/tgfx/svg/ResourceLoader.h @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/core/Data.h" +#include "tgfx/core/Image.h" + +namespace tgfx { + +/** + * LoadResourceProvider is an interface for loading resources (e.g. images, font) from + * external sources. + */ +class ResourceLoader { + public: + /** + * Data is a wrapper for raw data. + */ + virtual ~ResourceLoader() = default; + + /** + * Load a generic resource specified by |path| + |name|, and return as an Data object. + */ + virtual std::shared_ptr load(const std::string& resourcePath, + const std::string& resourceName) const = 0; + + /** + * Load an image asset specified by |path| + |name|, and returns the Image object. + */ + virtual std::shared_ptr loadImage(const std::string& resourcePath, + const std::string& resourceName) const = 0; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/SVGAttribute.h b/include/tgfx/svg/SVGAttribute.h new file mode 100644 index 00000000..d2da36f6 --- /dev/null +++ b/include/tgfx/svg/SVGAttribute.h @@ -0,0 +1,118 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { +enum class SVGAttribute { + ClipRule, + Color, + ColorInterpolation, + ColorInterpolationFilters, + Cx, // , , : center x position + Cy, // , , : center y position + Fill, + FillOpacity, + FillRule, + Filter, + FilterUnits, + FontFamily, + FontSize, + FontStyle, + FontWeight, + Fx, // : focal point x position + Fy, // : focal point y position + GradientUnits, + GradientTransform, + Height, + Href, + Opacity, + Points, + PreserveAspectRatio, + R, // , : radius + Rx, // ,: horizontal (corner) radius + Ry, // ,: vertical (corner) radius + SpreadMethod, + Stroke, + StrokeDashArray, + StrokeDashOffset, + StrokeOpacity, + StrokeLineCap, + StrokeLineJoin, + StrokeMiterLimit, + StrokeWidth, + Transform, + Text, + TextAnchor, + ViewBox, + Visibility, + Width, + X, + X1, // : first endpoint x + X2, // : second endpoint x + Y, + Y1, // : first endpoint y + Y2, // : second endpoint y + + Unknown, +}; + +struct SVGPresentationAttributes { + static SVGPresentationAttributes MakeInitial(); + + SVGProperty Fill; + SVGProperty FillOpacity; + SVGProperty FillRule; + SVGProperty ClipRule; + + SVGProperty Stroke; + SVGProperty StrokeDashArray; + SVGProperty StrokeDashOffset; + SVGProperty StrokeLineCap; + SVGProperty StrokeLineJoin; + SVGProperty StrokeMiterLimit; + SVGProperty StrokeOpacity; + SVGProperty StrokeWidth; + + SVGProperty Visibility; + + SVGProperty Color; + SVGProperty ColorInterpolation; + SVGProperty ColorInterpolationFilters; + + SVGProperty FontFamily; + SVGProperty FontStyle; + SVGProperty FontSize; + SVGProperty FontWeight; + SVGProperty TextAnchor; + + // uninherited + SVGProperty Opacity; + SVGProperty ClipPath; + SVGProperty Display; + SVGProperty Mask; + SVGProperty Filter; + SVGProperty StopColor; + SVGProperty StopOpacity; + SVGProperty FloodColor; + SVGProperty FloodOpacity; + SVGProperty LightingColor; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/SVGDOM.h b/include/tgfx/svg/SVGDOM.h new file mode 100644 index 00000000..90e1b1cb --- /dev/null +++ b/include/tgfx/svg/SVGDOM.h @@ -0,0 +1,109 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Data.h" +#include "tgfx/core/Picture.h" +#include "tgfx/core/Size.h" +#include "tgfx/core/Stream.h" +#include "tgfx/svg/FontManager.h" +#include "tgfx/svg/ResourceLoader.h" +#include "tgfx/svg/node/SVGRoot.h" + +namespace tgfx { + +class SVGNode; +using SVGIDMapper = std::unordered_map>; + +/** + * Options for rendering an SVGDOM object. Users can customize fonts, image resources, and shape + * text. Implementations should be based on the abstract class interfaces. + */ +struct SVGDOMOptions { + /** + * If fontManager is null, text will not be rendered. + */ + std::shared_ptr fontManager = nullptr; + /** + * If resourceProvider is null, base64 image resources will still be parsed, but non-base64 images + * will be loaded from absolute paths. + */ + std::shared_ptr resourceProvider = nullptr; +}; + +/** + * The SVGDOM class represents an SVG Document Object Model (DOM). It provides functionality to + * traverse the SVG DOM tree and render the SVG. + * + * Usage: + * + * 1. Traversing the SVG DOM tree: + * - Use getRoot() to obtain the root node. From the root node, you can access its attributes + * and child nodes, and then visit the child nodes. + * + * 2. Rendering the SVG: + * - Use setContainerSize() to set the size of the canvas. If not set, the dimensions of the root + * node will be used. + * - Use render() to draw the SVG onto a canvas. + */ +class SVGDOM { + public: + /** + * Creates an SVGDOM object from the provided stream. + */ + static std::shared_ptr Make(Stream& stream, SVGDOMOptions options = {}); + + /** + * Returns the root SVG node. + */ + const std::shared_ptr& getRoot() const; + + /** + * Renders the SVG to the provided canvas. + * @param canvas The canvas to render to. + * @param fontManager The font manager for rendering SVG text. If no text rendering is needed, + * this can be nullptr, and text will not be rendered. If no specific font is set, the default + * font will be used for rendering. + */ + void render(Canvas* canvas); + + /** + * Sets the size of the container that the SVG will be rendered into. + */ + void setContainerSize(const Size& size); + + /** + * Returns the size of the container that the SVG will be rendered into. + */ + const Size& getContainerSize() const; + + private: + /** + * Construct a new SVGDOM object + */ + SVGDOM(std::shared_ptr root, SVGDOMOptions options, SVGIDMapper&& mapper); + + const std::shared_ptr root = nullptr; + const SVGIDMapper nodeIDMapper = {}; + const SVGDOMOptions options = {}; + Size containerSize = {}; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/SVGTypes.h b/include/tgfx/svg/SVGTypes.h new file mode 100644 index 00000000..ec68e6e4 --- /dev/null +++ b/include/tgfx/svg/SVGTypes.h @@ -0,0 +1,921 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include "tgfx/core/Color.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/PathTypes.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +using SVGColorType = Color; +using SVGIntegerType = int; +using SVGNumberType = float; +using SVGStringType = std::string; +using SVGViewBoxType = Rect; +using SVGTransformType = Matrix; +using SVGPointsType = std::vector; + +enum class SVGPropertyState { + Unspecified, + Inherit, + Value, +}; + +// https://www.w3.org/TR/SVG11/intro.html#TermProperty +template +class SVGProperty { + public: + using ValueT = T; + + SVGProperty() = default; + + explicit SVGProperty(SVGPropertyState state) : _state(state) { + } + + explicit SVGProperty(const T& value) : _state(SVGPropertyState::Value) { + _value = value; + } + + explicit SVGProperty(T&& value) : _state(SVGPropertyState::Value) { + _value = std::move(value); + } + + template + void init(Args&&... args) { + _state = SVGPropertyState::Value; + _value.emplace(std::forward(args)...); + } + + constexpr bool isInheritable() const { + return Inheritable; + } + + bool isValue() const { + return _state == SVGPropertyState::Value; + } + + T* getMaybeNull() const { + return _value.value_or(nullptr); + } + + void set(SVGPropertyState state) { + _state = state; + if (_state != SVGPropertyState::Value) { + _value.reset(); + } + } + + void set(const T& value) { + _state = SVGPropertyState::Value; + _value = value; + } + + void set(T&& value) { + _state = SVGPropertyState::Value; + _value = std::move(value); + } + + T* operator->() { + return &_value.value(); + } + + const T* operator->() const { + return &_value.value(); + } + + T& operator*() { + return *_value; + } + + const T& operator*() const { + return *_value; + } + + private: + SVGPropertyState _state = SVGPropertyState::Unspecified; + std::optional _value; +}; + +class SVGLength { + public: + enum class Unit { + Unknown, + Number, + Percentage, + EMS, + EXS, + PX, + CM, + MM, + IN, + PT, + PC, + }; + + SVGLength() = default; + + explicit SVGLength(float v, Unit u = Unit::Number) : _value(v), _unit(u) { + } + + bool operator==(const SVGLength& other) const { + return _unit == other._unit && _value == other._value; + } + + bool operator!=(const SVGLength& other) const { + return !(*this == other); + } + + const float& value() const { + return _value; + } + + const Unit& unit() const { + return _unit; + } + + private: + float _value = 0.0f; + Unit _unit = Unit::Unknown; +}; + +// https://www.w3.org/TR/SVG11/linking.html#IRIReference +class SVGIRI { + public: + enum class Type { + Local, + Nonlocal, + DataURI, + }; + + SVGIRI() = default; + + SVGIRI(Type t, SVGStringType iri) : _type(t), _iri(std::move(iri)) { + } + + Type type() const { + return _type; + } + + const SVGStringType& iri() const { + return _iri; + } + + bool operator==(const SVGIRI& other) const { + return _type == other._type && _iri == other._iri; + } + + bool operator!=(const SVGIRI& other) const { + return !(*this == other); + } + + private: + Type _type = Type::Local; + SVGStringType _iri = {}; +}; + +// https://www.w3.org/TR/SVG11/types.html#InterfaceSVGColor +class SVGColor { + public: + enum class Type { + CurrentColor, + Color, + ICCColor, + }; + + using Vars = std::vector; + + SVGColor() = default; + + explicit SVGColor(const SVGColorType& c) : _color(c) { + } + + SVGColor(Type t, Vars&& vars) + : _type(t), _vars(vars.empty() ? nullptr : std::make_shared(std::move(vars))) { + } + + SVGColor(const SVGColorType& c, Vars&& vars) + : _color(c), _vars(vars.empty() ? nullptr : std::make_shared(std::move(vars))) { + } + + bool operator==(const SVGColor& other) const { + return _type == other._type && _color == other._color && _vars == other._vars; + } + + bool operator!=(const SVGColor& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const SVGColorType& color() const { + return _color; + } + + std::shared_ptr vars() const { + return _vars ? _vars : nullptr; + } + + private: + Type _type = Type::Color; + SVGColorType _color = Color::Black(); + std::shared_ptr _vars = nullptr; +}; + +class SVGPaint { + public: + enum class Type { + None, + Color, + IRI, + }; + + SVGPaint() = default; + + explicit SVGPaint(Type t) : _type(t), _color(Color::Black()) { + } + + explicit SVGPaint(const SVGColor& color) : _type(Type::Color), _color(std::move(color)) { + } + + SVGPaint(SVGIRI iri, SVGColor color) + : _type(Type::IRI), _color(std::move(color)), _iri(std::move(iri)) { + } + + bool operator==(const SVGPaint& other) const { + return _type == other._type && _color == other._color && _iri == other._iri; + } + + bool operator!=(const SVGPaint& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const SVGColor& color() const { + return _color; + } + + const SVGIRI& iri() const { + return _iri; + } + + private: + Type _type = Type::None; + SVGColor _color = SVGColor(Color::Black()); + SVGIRI _iri = {}; +}; + +// | none (used for clip/mask/filter properties) +class SVGFuncIRI { + public: + enum class Type { + None, + IRI, + }; + + SVGFuncIRI() = default; + + explicit SVGFuncIRI(Type t) : _type(t) { + } + + explicit SVGFuncIRI(SVGIRI&& iri) : _type(Type::IRI), _iri(std::move(iri)) { + } + + bool operator==(const SVGFuncIRI& other) const { + return _type == other._type && _iri == other._iri; + } + + bool operator!=(const SVGFuncIRI& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const SVGIRI& iri() const { + return _iri; + } + + private: + Type _type = Type::None; + SVGIRI _iri = {}; +}; + +enum class SVGLineCap { + Butt, + Round, + Square, +}; + +class SVGLineJoin { + public: + enum class Type { + Miter, + Round, + Bevel, + Inherit, + }; + + SVGLineJoin() = default; + + explicit SVGLineJoin(Type t) : _type(t) { + } + + bool operator==(const SVGLineJoin& other) const { + return _type == other._type; + } + + bool operator!=(const SVGLineJoin& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Inherit; +}; + +class SVGSpreadMethod { + public: + enum class Type { + Pad, + Repeat, + Reflect, + }; + + SVGSpreadMethod() = default; + + explicit SVGSpreadMethod(Type t) : _type(t) { + } + + bool operator==(const SVGSpreadMethod& other) const { + return _type == other._type; + } + + bool operator!=(const SVGSpreadMethod& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Pad; +}; + +class SVGFillRule { + public: + enum class Type { + NonZero, + EvenOdd, + Inherit, + }; + + SVGFillRule() = default; + + explicit SVGFillRule(Type t) : _type(t) { + } + + bool operator==(const SVGFillRule& other) const { + return _type == other._type; + } + + bool operator!=(const SVGFillRule& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + PathFillType asFillType() const { + return _type == Type::EvenOdd ? PathFillType::EvenOdd : PathFillType::Winding; + } + + private: + Type _type = Type::Inherit; +}; + +class SVGVisibility { + public: + enum class Type { + Visible, + Hidden, + Collapse, + Inherit, + }; + + SVGVisibility() = default; + + explicit SVGVisibility(Type t) : _type(t) { + } + + bool operator==(const SVGVisibility& other) const { + return _type == other._type; + } + + bool operator!=(const SVGVisibility& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Visible; +}; + +class SVGDashArray { + public: + enum class Type { + None, + DashArray, + Inherit, + }; + + SVGDashArray() = default; + + explicit SVGDashArray(Type t) : _type(t) { + } + + explicit SVGDashArray(std::vector&& dashArray) + : _type(Type::DashArray), _dashArray(std::move(dashArray)) { + } + + bool operator==(const SVGDashArray& other) const { + return _type == other._type && _dashArray == other._dashArray; + } + + bool operator!=(const SVGDashArray& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const std::vector& dashArray() const { + return _dashArray; + } + + private: + Type _type = Type::None; + std::vector _dashArray; +}; + +class SVGStopColor { + public: + enum class Type { + Color, + CurrentColor, + ICCColor, + Inherit, + }; + + SVGStopColor() = default; + + explicit SVGStopColor(Type t) : _type(t) { + } + + explicit SVGStopColor(const SVGColorType& c) : _color(c) { + } + + bool operator==(const SVGStopColor& other) const { + return _type == other._type && _color == other._color; + } + + bool operator!=(const SVGStopColor& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const SVGColorType& color() const { + return _color; + } + + private: + Type _type = Type::Color; + SVGColorType _color = Color::Black(); +}; + +class SVGObjectBoundingBoxUnits { + public: + enum class Type { + UserSpaceOnUse, + ObjectBoundingBox, + }; + + SVGObjectBoundingBoxUnits() = default; + + explicit SVGObjectBoundingBoxUnits(Type t) : _type(t) { + } + + bool operator==(const SVGObjectBoundingBoxUnits& other) const { + return _type == other._type; + } + + bool operator!=(const SVGObjectBoundingBoxUnits& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::UserSpaceOnUse; +}; + +class SVGFontFamily { + public: + enum class Type { + Family, + Inherit, + }; + + SVGFontFamily() = default; + + explicit SVGFontFamily(std::string family) : _type(Type::Family), _family(std::move(family)) { + } + + bool operator==(const SVGFontFamily& other) const { + return _type == other._type && _family == other._family; + } + + bool operator!=(const SVGFontFamily& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const std::string& family() const { + return _family; + } + + private: + Type _type = Type::Inherit; + std::string _family; +}; + +class SVGFontStyle { + public: + enum class Type { + Normal, + Italic, + Oblique, + Inherit, + }; + + SVGFontStyle() = default; + + explicit SVGFontStyle(Type t) : _type(t) { + } + + bool operator==(const SVGFontStyle& other) const { + return _type == other._type; + } + + bool operator!=(const SVGFontStyle& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Inherit; +}; + +class SVGFontSize { + public: + enum class Type { + Length, + Inherit, + }; + + SVGFontSize() = default; + + explicit SVGFontSize(const SVGLength& s) : _type(Type::Length), _size(s) { + } + + bool operator==(const SVGFontSize& other) const { + return _type == other._type && _size == other._size; + } + + bool operator!=(const SVGFontSize& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + const SVGLength& size() const { + return _size; + } + + private: + Type _type = Type::Inherit; + SVGLength _size = SVGLength(0.0f); +}; + +class SVGFontWeight { + public: + enum class Type { + W100, + W200, + W300, + W400, + W500, + W600, + W700, + W800, + W900, + Normal, + Bold, + Bolder, + Lighter, + Inherit, + }; + + SVGFontWeight() = default; + + explicit SVGFontWeight(Type t) : _type(t) { + } + + bool operator==(const SVGFontWeight& other) const { + return _type == other._type; + } + + bool operator!=(const SVGFontWeight& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Inherit; +}; + +struct SVGPreserveAspectRatio { + enum class Align { + // These values are chosen such that bits [0,1] encode X alignment, and + // bits [2,3] encode Y alignment. + XMinYMin = 0x00, + XMidYMin = 0x01, + XMaxYMin = 0x02, + XMinYMid = 0x04, + XMidYMid = 0x05, + XMaxYMid = 0x06, + XMinYMax = 0x08, + XMidYMax = 0x09, + XMaxYMax = 0x0a, + + None = 0x10, + }; + + enum class Scale { + Meet, + Slice, + }; + + Align align = Align::XMidYMid; + Scale scale = Scale::Meet; +}; + +class SVGTextAnchor { + public: + enum class Type { + Start, + Middle, + End, + Inherit, + }; + + SVGTextAnchor() = default; + + explicit SVGTextAnchor(Type t) : _type(t) { + } + + bool operator==(const SVGTextAnchor& other) const { + return _type == other._type; + } + + bool operator!=(const SVGTextAnchor& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + float getAlignmentFactor() const { + switch (_type) { + case SVGTextAnchor::Type::Start: + return 0.0f; + case SVGTextAnchor::Type::Middle: + return -0.5f; + case SVGTextAnchor::Type::End: + return -1.0f; + case SVGTextAnchor::Type::Inherit: + return 0.0f; + } + } + + private: + Type _type = Type::Inherit; +}; + +// https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute +class SVGFeInputType { + public: + enum class Type { + SourceGraphic, + SourceAlpha, + BackgroundImage, + BackgroundAlpha, + FillPaint, + StrokePaint, + FilterPrimitiveReference, + Unspecified, + }; + + SVGFeInputType() = default; + + explicit SVGFeInputType(Type t) : _type(t) { + } + + explicit SVGFeInputType(SVGStringType id) + : _type(Type::FilterPrimitiveReference), _id(std::move(id)) { + } + + bool operator==(const SVGFeInputType& other) const { + return _type == other._type && _id == other._id; + } + + bool operator!=(const SVGFeInputType& other) const { + return !(*this == other); + } + + const std::string& id() const { + return _id; + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Unspecified; + std::string _id; +}; + +enum class SVGFeColorMatrixType { + Matrix, + Saturate, + HueRotate, + LuminanceToAlpha, +}; + +using SVGFeColorMatrixValues = std::vector; + +enum class SVGFeCompositeOperator { + Over, + In, + Out, + Atop, + Xor, + Arithmetic, +}; + +class SVGFeTurbulenceBaseFrequency { + public: + SVGFeTurbulenceBaseFrequency() = default; + + SVGFeTurbulenceBaseFrequency(SVGNumberType freqX, SVGNumberType freqY) + : _freqX(freqX), _freqY(freqY) { + } + + SVGNumberType freqX() const { + return _freqX; + } + + SVGNumberType freqY() const { + return _freqY; + } + + private: + SVGNumberType _freqX = 0.0f; + SVGNumberType _freqY = 0.0f; +}; + +struct SVGFeTurbulenceType { + enum class Type { + FractalNoise, + Turbulence, + }; + + Type type = Type::Turbulence; + + SVGFeTurbulenceType() = default; + + explicit SVGFeTurbulenceType(Type inputType) : type(inputType) { + } +}; + +enum class SVGXmlSpace { + Default, + Preserve, +}; + +enum class SVGColorspace { + Auto, + SRGB, + LinearRGB, +}; + +// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty +enum class SVGDisplay { + Inline, + None, +}; + +// https://www.w3.org/TR/SVG11/filters.html#TransferFunctionElementAttributes +enum class SVGFeFuncType { + Identity, + Table, + Discrete, + Linear, + Gamma, +}; + +class SVGMaskType { + public: + enum class Type { + Luminance, + Alpha, + }; + + SVGMaskType() = default; + + explicit SVGMaskType(Type t) : _type(t) { + } + + bool operator==(const SVGMaskType& other) const { + return _type == other._type; + } + + bool operator!=(const SVGMaskType& other) const { + return !(*this == other); + } + + Type type() const { + return _type; + } + + private: + Type _type = Type::Luminance; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/SVGValue.h b/include/tgfx/svg/SVGValue.h new file mode 100644 index 00000000..f8463bdd --- /dev/null +++ b/include/tgfx/svg/SVGValue.h @@ -0,0 +1,93 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGValue { + public: + enum class Type { + Color, + Filter, + Length, + Number, + ObjectBoundingBoxUnits, + PreserveAspectRatio, + StopColor, + String, + Transform, + ViewBox, + }; + + Type type() const { + return _type; + } + + template + const T* as() const { + return _type == T::_type ? static_cast(this) : nullptr; + } + + protected: + explicit SVGValue(Type t) : _type(t) { + } + + private: + Type _type; +}; + +template +class SVGWrapperValue final : public SVGValue { + public: + explicit SVGWrapperValue(const T& v) : INHERITED(ValueType), wrappedValue(v) { + } + + // NOLINTBEGIN + // Allow implicit conversion to the wrapped type operator. + operator const T&() const { + return wrappedValue; + } + // NOLINTEND + + const T* operator->() const { + return &wrappedValue; + } + + // Stack-only + void* operator new(size_t) = delete; + void* operator new(size_t, void*) = delete; + + private: + const T& wrappedValue; + + using INHERITED = SVGValue; +}; + +using SVGLengthValue = SVGWrapperValue; +using SVGTransformValue = SVGWrapperValue; +using SVGViewBoxValue = SVGWrapperValue; +using SVGNumberValue = SVGWrapperValue; +using SVGStringValue = SVGWrapperValue; +using SVGPreserveAspectRatioValue = + SVGWrapperValue; +using SVGObjectBoundingBoxUnitsValue = + SVGWrapperValue; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGCircle.h b/include/tgfx/svg/node/SVGCircle.h new file mode 100644 index 00000000..d290a369 --- /dev/null +++ b/include/tgfx/svg/node/SVGCircle.h @@ -0,0 +1,65 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Paint.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGShape.h" + +namespace tgfx { + +class SVGLengthContext; +class SVGRenderContext; + +class SVGCircle final : public SVGShape { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGCircle()); + } + + SVG_ATTR(Cx, SVGLength, SVGLength(0)) + SVG_ATTR(Cy, SVGLength, SVGLength(0)) + SVG_ATTR(R, SVGLength, SVGLength(0)) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType type) const override; + + void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType, std::shared_ptr pathEffect) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + + private: + // resolve and return the center and radius values + std::tuple resolve(const SVGLengthContext& lengthContext) const; + + SVGCircle(); + + using INHERITED = SVGShape; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGClipPath.h b/include/tgfx/svg/node/SVGClipPath.h new file mode 100644 index 00000000..647c7c35 --- /dev/null +++ b/include/tgfx/svg/node/SVGClipPath.h @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Path.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGRenderContext; + +class SVGClipPath final : public SVGHiddenContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGClipPath()); + } + + SVG_ATTR(ClipPathUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse)) + + private: + friend class SVGRenderContext; + + SVGClipPath(); + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + Path resolveClip(const SVGRenderContext& context) const; + + using INHERITED = SVGHiddenContainer; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGContainer.h b/include/tgfx/svg/node/SVGContainer.h new file mode 100644 index 00000000..389e337f --- /dev/null +++ b/include/tgfx/svg/node/SVGContainer.h @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGTransformableNode.h" + +namespace tgfx { + +class SVGRenderContext; + +class SVGContainer : public SVGTransformableNode { + public: + void appendChild(std::shared_ptr node) override; + const std::vector>& getChildren() const; + bool hasChildren() const final; + + protected: + explicit SVGContainer(SVGTag); + + void onRender(const SVGRenderContext& context) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + + template + void forEachChild(Func func) const { + for (const auto& child : children) { + if (child->tag() == NodeType::tag) { + func(static_cast(child.get())); + } + } + } + + std::vector> children; + + private: + using INHERITED = SVGTransformableNode; +}; + +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGDefs.h b/include/tgfx/svg/node/SVGDefs.h new file mode 100644 index 00000000..e6943cad --- /dev/null +++ b/include/tgfx/svg/node/SVGDefs.h @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/svg/node/SVGHiddenContainer.h" + +namespace tgfx { + +class SVGDefs : public SVGHiddenContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGDefs()); + } + + private: + SVGDefs() : INHERITED(SVGTag::Defs) { + } + + using INHERITED = SVGHiddenContainer; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGEllipse.h b/include/tgfx/svg/node/SVGEllipse.h new file mode 100644 index 00000000..e7f9536c --- /dev/null +++ b/include/tgfx/svg/node/SVGEllipse.h @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Path.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGShape.h" + +namespace tgfx { + +class SVGLengthContext; +class SVGRenderContext; + +class SVGEllipse final : public SVGShape { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGEllipse()); + } + + SVG_ATTR(Cx, SVGLength, SVGLength(0)) + SVG_ATTR(Cy, SVGLength, SVGLength(0)) + + SVG_OPTIONAL_ATTR(Rx, SVGLength) + SVG_OPTIONAL_ATTR(Ry, SVGLength) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType) const override; + + void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType, std::shared_ptr pathEffect) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + private: + SVGEllipse(); + + Rect resolve(const SVGLengthContext& context) const; + + using INHERITED = SVGShape; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFe.h b/include/tgfx/svg/node/SVGFe.h new file mode 100644 index 00000000..f08e64a7 --- /dev/null +++ b/include/tgfx/svg/node/SVGFe.h @@ -0,0 +1,104 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFilterContext; +class SVGFe : public SVGHiddenContainer { + public: + static bool IsFilterEffect(const std::shared_ptr& node) { + switch (node->tag()) { + case SVGTag::FeBlend: + case SVGTag::FeColorMatrix: + case SVGTag::FeComponentTransfer: + case SVGTag::FeComposite: + case SVGTag::FeDiffuseLighting: + case SVGTag::FeDisplacementMap: + case SVGTag::FeFlood: + case SVGTag::FeGaussianBlur: + case SVGTag::FeImage: + case SVGTag::FeMerge: + case SVGTag::FeMorphology: + case SVGTag::FeOffset: + case SVGTag::FeSpecularLighting: + case SVGTag::FeTurbulence: + return true; + default: + return false; + } + } + + std::shared_ptr makeImageFilter(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const; + + // https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion + Rect resolveFilterSubregion(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const; + + /** + * Resolves the colorspace within which this filter effect should be applied. + * Spec: https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperties + * 'color-interpolation-filters' property. + */ + virtual SVGColorspace resolveColorspace(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const; + + /** Propagates any inherited presentation attributes in the given context. */ + void applyProperties(SVGRenderContext* context) const; + + SVG_ATTR(In, SVGFeInputType, SVGFeInputType()) + + SVG_ATTR(Result, SVGStringType, SVGStringType()) + SVG_OPTIONAL_ATTR(X, SVGLength) + SVG_OPTIONAL_ATTR(Y, SVGLength) + SVG_OPTIONAL_ATTR(Width, SVGLength) + SVG_OPTIONAL_ATTR(Height, SVGLength) + + protected: + explicit SVGFe(SVGTag t) : INHERITED(t) { + } + + virtual std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const = 0; + + virtual std::vector getInputs() const = 0; + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + /** + * Resolves the rect specified by the x, y, width and height attributes (if specified) on this + * filter effect. These attributes are resolved according to the given length context and + * the value of 'primitiveUnits' on the parent element. + */ + Rect resolveBoundaries(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const; + + using INHERITED = SVGHiddenContainer; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeBlend.h b/include/tgfx/svg/node/SVGFeBlend.h new file mode 100644 index 00000000..490ae1bf --- /dev/null +++ b/include/tgfx/svg/node/SVGFeBlend.h @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +class SVGRenderContext; + +namespace tgfx { + +class SVGFeBlend : public SVGFe { + public: + enum class Mode { + Normal, + Multiply, + Screen, + Darken, + Lighten, + }; + + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeBlend()); + } + + SVG_ATTR(BlendMode, Mode, Mode::Normal) + SVG_ATTR(In2, SVGFeInputType, SVGFeInputType()) + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {this->getIn(), this->getIn2()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeBlend() : INHERITED(SVGTag::FeBlend) { + } + + using INHERITED = SVGFe; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGFeColorMatrix.h b/include/tgfx/svg/node/SVGFeColorMatrix.h new file mode 100644 index 00000000..554d0a5f --- /dev/null +++ b/include/tgfx/svg/node/SVGFeColorMatrix.h @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +class SVGFilterContext; +class SVGRenderContext; + +namespace tgfx { + +using ColorMatrix = std::array; + +class SVGFeColorMatrix final : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeColorMatrix()); + } + + SVG_ATTR(Type, SVGFeColorMatrixType, SVGFeColorMatrixType(SVGFeColorMatrixType::Matrix)) + SVG_ATTR(Values, SVGFeColorMatrixValues, SVGFeColorMatrixValues()) + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {this->getIn()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeColorMatrix() : INHERITED(SVGTag::FeColorMatrix) { + } + + ColorMatrix makeMatrixForType() const; + + static ColorMatrix MakeSaturate(SVGNumberType sat); + + static ColorMatrix MakeHueRotate(SVGNumberType degrees); + + static ColorMatrix MakeLuminanceToAlpha(); + + using INHERITED = SVGFe; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeComponentTransfer.h b/include/tgfx/svg/node/SVGFeComponentTransfer.h new file mode 100644 index 00000000..3b312a25 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeComponentTransfer.h @@ -0,0 +1,95 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +class SVGFilterContext; +class SVGRenderContext; + +namespace tgfx { + +class SVGFeFunc final : public SVGHiddenContainer { + public: + static std::shared_ptr MakeFuncA() { + return std::shared_ptr(new SVGFeFunc(SVGTag::FeFuncA)); + } + + static std::shared_ptr MakeFuncR() { + return std::shared_ptr(new SVGFeFunc(SVGTag::FeFuncR)); + } + + static std::shared_ptr MakeFuncG() { + return std::shared_ptr(new SVGFeFunc(SVGTag::FeFuncG)); + } + + static std::shared_ptr MakeFuncB() { + return std::shared_ptr(new SVGFeFunc(SVGTag::FeFuncB)); + } + + SVG_ATTR(Amplitude, SVGNumberType, 1) + SVG_ATTR(Exponent, SVGNumberType, 1) + SVG_ATTR(Intercept, SVGNumberType, 0) + SVG_ATTR(Offset, SVGNumberType, 0) + SVG_ATTR(Slope, SVGNumberType, 1) + SVG_ATTR(TableValues, std::vector, {}) + SVG_ATTR(Type, SVGFeFuncType, SVGFeFuncType::Identity) + + std::vector getTable() const; + + protected: + bool parseAndSetAttribute(const std::string&, const std::string&) override; + + private: + SVGFeFunc(SVGTag tag) : INHERITED(tag) { + } + + using INHERITED = SVGHiddenContainer; +}; + +class SVGFeComponentTransfer final : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeComponentTransfer()); + } + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& /*context*/, + const SVGFilterContext& /*filterContext*/) const override { + return nullptr; + }; + + std::vector getInputs() const override { + return {this->getIn()}; + } + + private: + SVGFeComponentTransfer() : INHERITED(SVGTag::FeComponentTransfer) { + } + + using INHERITED = SVGFe; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeComposite.h b/include/tgfx/svg/node/SVGFeComposite.h new file mode 100644 index 00000000..46b71e1c --- /dev/null +++ b/include/tgfx/svg/node/SVGFeComposite.h @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFilterContext; +class SVGRenderContext; + +class SVGFeComposite final : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeComposite()); + } + + SVG_ATTR(In2, SVGFeInputType, SVGFeInputType()) + SVG_ATTR(K1, SVGNumberType, SVGNumberType(0)) + SVG_ATTR(K2, SVGNumberType, SVGNumberType(0)) + SVG_ATTR(K3, SVGNumberType, SVGNumberType(0)) + SVG_ATTR(K4, SVGNumberType, SVGNumberType(0)) + SVG_ATTR(Operator, SVGFeCompositeOperator, SVGFeCompositeOperator::Over) + + protected: + std::vector getInputs() const override { + return {this->getIn(), this->getIn2()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + private: + SVGFeComposite() : INHERITED(SVGTag::FeComposite) { + } + + static BlendMode BlendModeForOperator(SVGFeCompositeOperator op); + + using INHERITED = SVGFe; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGFeDisplacementMap.h b/include/tgfx/svg/node/SVGFeDisplacementMap.h new file mode 100644 index 00000000..3bb94a05 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeDisplacementMap.h @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFilterContext; +class SVGRenderContext; + +class SVGFeDisplacementMap : public SVGFe { + public: + enum class ChannelSelector { + R, // the red channel + G, // the green channel + B, // the blue channel + A, // the alpha channel + }; + + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeDisplacementMap()); + } + + SVG_ATTR(In2, SVGFeInputType, SVGFeInputType()) + SVG_ATTR(XChannelSelector, ChannelSelector, ChannelSelector::A) + SVG_ATTR(YChannelSelector, ChannelSelector, ChannelSelector::A) + SVG_ATTR(Scale, SVGNumberType, SVGNumberType(0)) + + SVGColorspace resolveColorspace(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const final; + + protected: + std::vector getInputs() const override { + return {this->getIn(), this->getIn2()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + private: + SVGFeDisplacementMap() : INHERITED(SVGTag::FeDisplacementMap) { + } + + using INHERITED = SVGFe; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeFlood.h b/include/tgfx/svg/node/SVGFeFlood.h new file mode 100644 index 00000000..c7d20325 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeFlood.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SkImageFilter; +class SVGFilterContext; +class SVGRenderContext; + +class SVGFeFlood : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeFlood()); + } + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& /*context*/, + const SVGFilterContext& /*filterContext*/) const override { + return nullptr; + }; + + std::vector getInputs() const override { + return {}; + } + + private: + SVGFeFlood() : INHERITED(SVGTag::FeFlood) { + } + + using INHERITED = SVGFe; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGFeGaussianBlur.h b/include/tgfx/svg/node/SVGFeGaussianBlur.h new file mode 100644 index 00000000..89010cf1 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeGaussianBlur.h @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFilterContext; +class SVGRenderContext; + +class SVGFeGaussianBlur : public SVGFe { + public: + struct StdDeviation { + SVGNumberType X; + SVGNumberType Y; + }; + + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeGaussianBlur()); + } + + SVG_ATTR(stdDeviation, StdDeviation, StdDeviation({0, 0})) + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {this->getIn()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeGaussianBlur() : INHERITED(SVGTag::FeGaussianBlur) { + } + + using INHERITED = SVGFe; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeImage.h b/include/tgfx/svg/node/SVGFeImage.h new file mode 100644 index 00000000..d7bc4536 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeImage.h @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +class SVGFilterContext; +class SVGRenderContext; + +namespace tgfx { + +class SVGFeImage : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeImage()); + } + + SVG_ATTR(Href, SVGIRI, SVGIRI()) + SVG_ATTR(PreserveAspectRatio, SVGPreserveAspectRatio, SVGPreserveAspectRatio()) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {}; + } + + private: + SVGFeImage() : INHERITED(SVGTag::FeImage) { + } + + using INHERITED = SVGFe; +}; + +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGFeLightSource.h b/include/tgfx/svg/node/SVGFeLightSource.h new file mode 100644 index 00000000..c54c550f --- /dev/null +++ b/include/tgfx/svg/node/SVGFeLightSource.h @@ -0,0 +1,105 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SkSVGFeLightSource : public SVGHiddenContainer { + public: + void appendChild(std::shared_ptr) final { + } + + protected: + explicit SkSVGFeLightSource(SVGTag tag) : INHERITED(tag) { + } + + private: + using INHERITED = SVGHiddenContainer; +}; + +class SVGFeDistantLight final : public SkSVGFeLightSource { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeDistantLight()); + } + + // pk::SkPoint3 computeDirection() const; + + SVG_ATTR(Azimuth, SVGNumberType, 0) + SVG_ATTR(Elevation, SVGNumberType, 0) + + private: + SVGFeDistantLight() : INHERITED(SVGTag::FeDistantLight) { + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + using INHERITED = SkSVGFeLightSource; +}; + +class SVGFePointLight final : public SkSVGFeLightSource { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFePointLight()); + } + + SVG_ATTR(X, SVGNumberType, 0) + SVG_ATTR(Y, SVGNumberType, 0) + SVG_ATTR(Z, SVGNumberType, 0) + + private: + SVGFePointLight() : INHERITED(SVGTag::FePointLight) { + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + using INHERITED = SkSVGFeLightSource; +}; + +class SVGFeSpotLight final : public SkSVGFeLightSource { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeSpotLight()); + } + + SVG_ATTR(X, SVGNumberType, 0) + SVG_ATTR(Y, SVGNumberType, 0) + SVG_ATTR(Z, SVGNumberType, 0) + SVG_ATTR(PointsAtX, SVGNumberType, 0) + SVG_ATTR(PointsAtY, SVGNumberType, 0) + SVG_ATTR(PointsAtZ, SVGNumberType, 0) + SVG_ATTR(SpecularExponent, SVGNumberType, 1) + + SVG_OPTIONAL_ATTR(LimitingConeAngle, SVGNumberType) + + private: + SVGFeSpotLight() : INHERITED(SVGTag::FeSpotLight) { + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + using INHERITED = SkSVGFeLightSource; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeLighting.h b/include/tgfx/svg/node/SVGFeLighting.h new file mode 100644 index 00000000..3d4f06e9 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeLighting.h @@ -0,0 +1,100 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/core/ImageFilter.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFeDistantLight; +class SVGFePointLight; +class SVGFeSpotLight; + +class SVGFeLighting : public SVGFe { + public: + struct KernelUnitLength { + SVGNumberType Dx; + SVGNumberType Dy; + }; + + SVG_ATTR(SurfaceScale, SVGNumberType, 1) + SVG_OPTIONAL_ATTR(UnitLength, KernelUnitLength) + + protected: + explicit SVGFeLighting(SVGTag t) : INHERITED(t) { + } + + std::vector getInputs() const final { + return {this->getIn()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& /*context*/, const SVGFilterContext& /*filterContext*/) const final { + return nullptr; + }; + + private: + using INHERITED = SVGFe; +}; + +class SVGFeSpecularLighting final : public SVGFeLighting { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeSpecularLighting()); + } + + SVG_ATTR(SpecularConstant, SVGNumberType, 1) + SVG_ATTR(SpecularExponent, SVGNumberType, 1) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeSpecularLighting() : INHERITED(SVGTag::FeSpecularLighting) { + } + + using INHERITED = SVGFeLighting; +}; + +class SVGFeDiffuseLighting final : public SVGFeLighting { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeDiffuseLighting()); + } + + SVG_ATTR(DiffuseConstant, SVGNumberType, 1) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeDiffuseLighting() : INHERITED(SVGTag::FeDiffuseLighting) { + } + + using INHERITED = SVGFeLighting; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeMerge.h b/include/tgfx/svg/node/SVGFeMerge.h new file mode 100644 index 00000000..b3656d4e --- /dev/null +++ b/include/tgfx/svg/node/SVGFeMerge.h @@ -0,0 +1,71 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +// https://www.w3.org/TR/SVG11/filters.html#feMergeNodeElement +class SVGFeMergeNode : public SVGHiddenContainer { + public: + static constexpr SVGTag tag = SVGTag::FeMergeNode; + + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeMergeNode()); + } + + SVG_ATTR(In, SVGFeInputType, SVGFeInputType()) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeMergeNode() : INHERITED(tag) { + } + + using INHERITED = SVGHiddenContainer; +}; + +// https://www.w3.org/TR/SVG11/filters.html#feMergeElement +class SVGFeMerge : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeMerge()); + } + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override; + + private: + SVGFeMerge() : INHERITED(SVGTag::FeMerge) { + } + + using INHERITED = SVGFe; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeMorphology.h b/include/tgfx/svg/node/SVGFeMorphology.h new file mode 100644 index 00000000..24bf4191 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeMorphology.h @@ -0,0 +1,65 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFeMorphology : public SVGFe { + public: + struct Radius { + SVGNumberType fX; + SVGNumberType fY; + }; + + enum class Operator { + kErode, + kDilate, + }; + + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeMorphology()); + } + + SVG_ATTR(MorphOperator, Operator, Operator::kErode) + SVG_ATTR(MorphRadius, Radius, Radius({0, 0})) + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {this->getIn()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeMorphology() : INHERITED(SVGTag::FeMorphology) { + } + + using INHERITED = SVGFe; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeOffset.h b/include/tgfx/svg/node/SVGFeOffset.h new file mode 100644 index 00000000..77abd7bf --- /dev/null +++ b/include/tgfx/svg/node/SVGFeOffset.h @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SkImageFilter; +class SVGFilterContext; +class SVGRenderContext; + +class SVGFeOffset : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeOffset()); + } + + SVG_ATTR(Dx, SVGNumberType, SVGNumberType(0)) + SVG_ATTR(Dy, SVGNumberType, SVGNumberType(0)) + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {this->getIn()}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeOffset() : INHERITED(SVGTag::FeOffset) { + } + + using INHERITED = SVGFe; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGFeTurbulence.h b/include/tgfx/svg/node/SVGFeTurbulence.h new file mode 100644 index 00000000..63be89e5 --- /dev/null +++ b/include/tgfx/svg/node/SVGFeTurbulence.h @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFeTurbulence : public SVGFe { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFeTurbulence()); + } + + SVG_ATTR(BaseFrequency, SVGFeTurbulenceBaseFrequency, SVGFeTurbulenceBaseFrequency({})) + SVG_ATTR(NumOctaves, SVGIntegerType, SVGIntegerType(1)) + SVG_ATTR(Seed, SVGNumberType, SVGNumberType(0)) + SVG_ATTR(TurbulenceType, SVGFeTurbulenceType, + SVGFeTurbulenceType(SVGFeTurbulenceType::Type::Turbulence)) + + protected: + std::shared_ptr onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const override; + + std::vector getInputs() const override { + return {}; + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGFeTurbulence() : INHERITED(SVGTag::FeTurbulence) { + } + + using INHERITED = SVGFe; +}; + +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGFilter.h b/include/tgfx/svg/node/SVGFilter.h new file mode 100644 index 00000000..8b14184e --- /dev/null +++ b/include/tgfx/svg/node/SVGFilter.h @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "svg/SVGFilterContext.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGFilter final : public SVGHiddenContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGFilter()); + } + + /** Propagates any inherited presentation attributes in the given context. */ + void applyProperties(SVGRenderContext* context) const; + + std::shared_ptr buildFilterDAG(const SVGRenderContext& context) const; + + SVG_ATTR(X, SVGLength, SVGLength(-10, SVGLength::Unit::Percentage)) + SVG_ATTR(Y, SVGLength, SVGLength(-10, SVGLength::Unit::Percentage)) + SVG_ATTR(Width, SVGLength, SVGLength(120, SVGLength::Unit::Percentage)) + SVG_ATTR(Height, SVGLength, SVGLength(120, SVGLength::Unit::Percentage)) + SVG_ATTR(FilterUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox)) + SVG_ATTR(PrimitiveUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse)) + + private: + SVGFilter() : INHERITED(SVGTag::Filter) { + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr buildDropShadowFilter(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const; + + std::shared_ptr buildInnerShadowFilter(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const; + + using INHERITED = SVGHiddenContainer; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGGradient.h b/include/tgfx/svg/node/SVGGradient.h new file mode 100644 index 00000000..c6d1a306 --- /dev/null +++ b/include/tgfx/svg/node/SVGGradient.h @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Color.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/TileMode.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGStop.h" + +namespace tgfx { + +class SVGGradient : public SVGHiddenContainer { + public: + SVG_ATTR(Href, SVGIRI, SVGIRI()) + SVG_ATTR(GradientTransform, SVGTransformType, SVGTransformType(Matrix::I())) + SVG_ATTR(SpreadMethod, SVGSpreadMethod, SVGSpreadMethod(SVGSpreadMethod::Type::Pad)) + SVG_ATTR(GradientUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox)) + + protected: + explicit SVGGradient(SVGTag t) : INHERITED(t) { + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + bool onAsPaint(const SVGRenderContext& context, Paint* paint) const final; + + virtual std::shared_ptr onMakeShader(const SVGRenderContext& context, + const std::vector& colors, + const std::vector& positions, + TileMode tileMode, + const Matrix& localMatrix) const = 0; + + private: + void collectColorStops(const SVGRenderContext&, std::vector&, std::vector&) const; + Color resolveStopColor(const SVGRenderContext&, const SVGStop&) const; + + using INHERITED = SVGHiddenContainer; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGGroup.h b/include/tgfx/svg/node/SVGGroup.h new file mode 100644 index 00000000..a713a2e5 --- /dev/null +++ b/include/tgfx/svg/node/SVGGroup.h @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/svg/node/SVGContainer.h" + +namespace tgfx { +class SVGGroup : public SVGContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGGroup()); + } + + private: + SVGGroup() : INHERITED(SVGTag::G) { + } + + using INHERITED = SVGContainer; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGHiddenContainer.h b/include/tgfx/svg/node/SVGHiddenContainer.h new file mode 100644 index 00000000..79c63df8 --- /dev/null +++ b/include/tgfx/svg/node/SVGHiddenContainer.h @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/svg/node/SVGContainer.h" + +namespace tgfx { + +class SVGHiddenContainer : public SVGContainer { + protected: + explicit SVGHiddenContainer(SVGTag t) : INHERITED(t) { + } + + void onRender(const SVGRenderContext& /*context*/) const final { + //abort rendering children nodes + } + + private: + using INHERITED = SVGContainer; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGImage.h b/include/tgfx/svg/node/SVGImage.h new file mode 100644 index 00000000..d51f3020 --- /dev/null +++ b/include/tgfx/svg/node/SVGImage.h @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Image.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/ResourceLoader.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGTransformableNode.h" + +namespace tgfx { + +class SVGRenderContext; + +namespace skresources { +class ResourceProvider; +} + +class SVGImage final : public SVGTransformableNode { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGImage()); + } + + void appendChild(std::shared_ptr /*node*/) override { + } + + struct ImageInfo { + std::shared_ptr image; + Rect destinationRect; + }; + + bool onPrepareToRender(SVGRenderContext* conetxt) const override; + void onRender(const SVGRenderContext& conetxt) const override; + Path onAsPath(const SVGRenderContext& conetxt) const override; + Rect onObjectBoundingBox(const SVGRenderContext& conetxt) const override; + static ImageInfo LoadImage(const std::shared_ptr& resourceProvider, + const SVGIRI& iri, const Rect& viewPort, SVGPreserveAspectRatio ratio); + + SVG_ATTR(X, SVGLength, SVGLength(0)) + SVG_ATTR(Y, SVGLength, SVGLength(0)) + SVG_ATTR(Width, SVGLength, SVGLength(0)) + SVG_ATTR(Height, SVGLength, SVGLength(0)) + SVG_ATTR(Href, SVGIRI, SVGIRI()) + SVG_ATTR(PreserveAspectRatio, SVGPreserveAspectRatio, SVGPreserveAspectRatio()) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGImage() : INHERITED(SVGTag::Image) { + } + + using INHERITED = SVGTransformableNode; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGLine.h b/include/tgfx/svg/node/SVGLine.h new file mode 100644 index 00000000..8a671b5d --- /dev/null +++ b/include/tgfx/svg/node/SVGLine.h @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Point.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGShape.h" + +namespace tgfx { + +class SVGLine final : public SVGShape { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGLine()); + } + + SVG_ATTR(X1, SVGLength, SVGLength(0)) + SVG_ATTR(Y1, SVGLength, SVGLength(0)) + SVG_ATTR(X2, SVGLength, SVGLength(0)) + SVG_ATTR(Y2, SVGLength, SVGLength(0)) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType) const override; + + void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType, std::shared_ptr pathEffect) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + private: + SVGLine(); + + // resolve and return the two endpoints + std::tuple resolve(const SVGLengthContext& context) const; + + using INHERITED = SVGShape; +}; + +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGLinearGradient.h b/include/tgfx/svg/node/SVGLinearGradient.h new file mode 100644 index 00000000..b1864caa --- /dev/null +++ b/include/tgfx/svg/node/SVGLinearGradient.h @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Shader.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGGradient.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGLinearGradient final : public SVGGradient { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGLinearGradient()); + } + + SVG_ATTR(X1, SVGLength, SVGLength(0, SVGLength::Unit::Percentage)) + SVG_ATTR(Y1, SVGLength, SVGLength(0, SVGLength::Unit::Percentage)) + SVG_ATTR(X2, SVGLength, SVGLength(100, SVGLength::Unit::Percentage)) + SVG_ATTR(Y2, SVGLength, SVGLength(0, SVGLength::Unit::Percentage)) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr onMakeShader(const SVGRenderContext& context, + const std::vector& colors, + const std::vector& positions, TileMode tileMode, + const Matrix& localMatrix) const override; + + private: + SVGLinearGradient(); + + using INHERITED = SVGGradient; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGMask.h b/include/tgfx/svg/node/SVGMask.h new file mode 100644 index 00000000..67d6e657 --- /dev/null +++ b/include/tgfx/svg/node/SVGMask.h @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGMask final : public SVGHiddenContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGMask()); + } + + SVG_OPTIONAL_ATTR(X, SVGLength) + SVG_OPTIONAL_ATTR(Y, SVGLength) + SVG_OPTIONAL_ATTR(Width, SVGLength) + SVG_OPTIONAL_ATTR(Height, SVGLength) + + SVG_ATTR(MaskUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox)) + SVG_ATTR(MaskContentUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse)) + SVG_ATTR(MaskType, SVGMaskType, SVGMaskType(SVGMaskType::Type::Luminance)) + + private: + friend class SVGRenderContext; + + SVGMask() : INHERITED(SVGTag::Mask) { + } + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + Rect bounds(const SVGRenderContext& context) const; + + void renderMask(const SVGRenderContext& context) const; + + using INHERITED = SVGHiddenContainer; +}; + +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGNode.h b/include/tgfx/svg/node/SVGNode.h new file mode 100644 index 00000000..2cf8a5cf --- /dev/null +++ b/include/tgfx/svg/node/SVGNode.h @@ -0,0 +1,290 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGAttribute.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGValue; +class SVGRenderContext; + +/** + * Enumeration of SVG element tags, where each SVG element corresponds to a specific tag. + */ +enum class SVGTag { + Circle, + ClipPath, + Defs, + Ellipse, + FeBlend, + FeColorMatrix, + FeComponentTransfer, + FeComposite, + FeDiffuseLighting, + FeDisplacementMap, + FeDistantLight, + FeFlood, + FeFuncA, + FeFuncR, + FeFuncG, + FeFuncB, + FeGaussianBlur, + FeImage, + FeMerge, + FeMergeNode, + FeMorphology, + FeOffset, + FePointLight, + FeSpecularLighting, + FeSpotLight, + FeTurbulence, + Filter, + G, + Image, + Line, + LinearGradient, + Mask, + Path, + Pattern, + Polygon, + Polyline, + RadialGradient, + Rect, + Stop, + Svg, + Text, + TextLiteral, + TextPath, + TSpan, + Use +}; + +#define SVG_PRES_ATTR(attr_name, attr_type, attr_inherited) \ + private: \ + bool set##attr_name(std::optional>&& pr) { \ + if (pr.has_value()) { \ + this->set##attr_name(std::move(*pr)); \ + } \ + return pr.has_value(); \ + } \ + \ + public: \ + const SVGProperty& get##attr_name() const { \ + return presentationAttributes.attr_name; \ + } \ + void set##attr_name(const SVGProperty& v) { \ + auto* dest = &presentationAttributes.attr_name; \ + if (!dest->isInheritable() || v.isValue()) { \ + /* TODO: If dest is not inheritable, handle v == "inherit" */ \ + *dest = v; \ + } else { \ + dest->set(SVGPropertyState::Inherit); \ + } \ + } \ + void set##attr_name(SVGProperty&& v) { \ + auto* dest = &presentationAttributes.attr_name; \ + if (!dest->isInheritable() || v.isValue()) { \ + /* TODO: If dest is not inheritable, handle v == "inherit" */ \ + *dest = std::move(v); \ + } else { \ + dest->set(SVGPropertyState::Inherit); \ + } \ + } + +/** + * Abstract base class for SVG nodes, representing an element in SVG with common attributes and + * methods. + */ +class SVGNode { + public: + virtual ~SVGNode(); + + SVGTag tag() const { + return _tag; + } + + // Inheritable presentation attributes + SVG_PRES_ATTR(ClipRule, SVGFillRule, true) + SVG_PRES_ATTR(Color, SVGColorType, true) + SVG_PRES_ATTR(ColorInterpolation, SVGColorspace, true) + SVG_PRES_ATTR(ColorInterpolationFilters, SVGColorspace, true) + SVG_PRES_ATTR(FillRule, SVGFillRule, true) + SVG_PRES_ATTR(Fill, SVGPaint, true) + SVG_PRES_ATTR(FillOpacity, SVGNumberType, true) + SVG_PRES_ATTR(FontFamily, SVGFontFamily, true) + SVG_PRES_ATTR(FontSize, SVGFontSize, true) + SVG_PRES_ATTR(FontStyle, SVGFontStyle, true) + SVG_PRES_ATTR(FontWeight, SVGFontWeight, true) + SVG_PRES_ATTR(Stroke, SVGPaint, true) + SVG_PRES_ATTR(StrokeDashArray, SVGDashArray, true) + SVG_PRES_ATTR(StrokeDashOffset, SVGLength, true) + SVG_PRES_ATTR(StrokeLineCap, SVGLineCap, true) + SVG_PRES_ATTR(StrokeLineJoin, SVGLineJoin, true) + SVG_PRES_ATTR(StrokeMiterLimit, SVGNumberType, true) + SVG_PRES_ATTR(StrokeOpacity, SVGNumberType, true) + SVG_PRES_ATTR(StrokeWidth, SVGLength, true) + SVG_PRES_ATTR(TextAnchor, SVGTextAnchor, true) + SVG_PRES_ATTR(Visibility, SVGVisibility, true) + + // Non-inheritable presentation attributes + SVG_PRES_ATTR(ClipPath, SVGFuncIRI, false) + SVG_PRES_ATTR(Display, SVGDisplay, false) + SVG_PRES_ATTR(Mask, SVGFuncIRI, false) + SVG_PRES_ATTR(Filter, SVGFuncIRI, false) + SVG_PRES_ATTR(Opacity, SVGNumberType, false) + SVG_PRES_ATTR(StopColor, SVGColor, false) + SVG_PRES_ATTR(StopOpacity, SVGNumberType, false) + SVG_PRES_ATTR(FloodColor, SVGColor, false) + SVG_PRES_ATTR(FloodOpacity, SVGNumberType, false) + SVG_PRES_ATTR(LightingColor, SVGColor, false) + + /** + * Renders the SVG node to the provided context. + */ + void render(const SVGRenderContext& context) const; + + /** + * Converts the SVG node to a paint object. + */ + bool asPaint(const SVGRenderContext& context, Paint* paint) const; + + /** + * Converts the SVG node to a path object. + */ + Path asPath(const SVGRenderContext& context) const; + + /** + * Returns the object bounding box of the SVG node. + */ + Rect objectBoundingBox(const SVGRenderContext& context) const; + + /** + * Returns the parent node of the SVG node. + */ + virtual bool hasChildren() const { + return false; + } + + /** + * Appends a child node to the SVG node. + */ + virtual void appendChild(std::shared_ptr node) = 0; + + protected: + explicit SVGNode(SVGTag tag); + + void setAttribute(SVGAttribute attribute, const SVGValue& value); + + bool setAttribute(const std::string& attributeName, const std::string& attributeValue); + + virtual bool parseAndSetAttribute(const std::string& name, const std::string& value); + + static Matrix ComputeViewboxMatrix(const Rect& viewBox, const Rect& viewPort, + SVGPreserveAspectRatio PreAspectRatio); + + virtual void onSetAttribute(SVGAttribute /*attribute*/, const SVGValue& /*value*/){}; + + // Called before onRender(), to apply local attributes to the context. Unlike onRender(), + // onPrepareToRender() bubbles up the inheritance chain: override should always call + // INHERITED::onPrepareToRender(), unless they intend to short-circuit rendering + // (return false). + // Implementations are expected to return true if rendering is to continue, or false if + // the node/subtree rendering is disabled. + virtual bool onPrepareToRender(SVGRenderContext* context) const; + + virtual void onRender(const SVGRenderContext& context) const = 0; + + virtual bool onAsPaint(const SVGRenderContext& /*context*/, Paint* /*paint*/) const { + return false; + } + + virtual Path onAsPath(const SVGRenderContext& context) const = 0; + + virtual Rect onObjectBoundingBox(const SVGRenderContext& /*context*/) const { + return Rect::MakeEmpty(); + } + + private: + SVGTag _tag; + SVGPresentationAttributes presentationAttributes; + + friend class SVGNodeConstructor; + friend class SVGRenderContext; +}; + +//NOLINTBEGIN +#undef SVG_PRES_ATTR // presentation attributes are only defined for the base class + +#define SVG_ATTR_SETTERS(attr_name, attr_type, attr_default, set_cp, set_mv) \ + private: \ + bool set##attr_name(const std::optional& pr) { \ + if (pr.has_value()) { \ + this->set##attr_name(*pr); \ + } \ + return pr.has_value(); \ + } \ + bool set##attr_name(std::optional&& pr) { \ + if (pr.has_value()) { \ + this->set##attr_name(std::move(*pr)); \ + } \ + return pr.has_value(); \ + } \ + \ + public: \ + void set##attr_name(const attr_type& a) { \ + set_cp(a); \ + } \ + void set##attr_name(attr_type&& a) { \ + set_mv(std::move(a)); \ + } + +#define SVG_ATTR(attr_name, attr_type, attr_default) \ + private: \ + attr_type attr_name = attr_default; \ + \ + public: \ + const attr_type& get##attr_name() const { \ + return attr_name; \ + } \ + SVG_ATTR_SETTERS( \ + attr_name, attr_type, attr_default, [this](const attr_type& a) { this->attr_name = a; }, \ + [this](attr_type&& a) { this->attr_name = std::move(a); }) + +#define SVG_OPTIONAL_ATTR(attr_name, attr_type) \ + private: \ + std::optional attr_name; \ + \ + public: \ + const std::optional& get##attr_name() const { \ + return attr_name; \ + } \ + SVG_ATTR_SETTERS( \ + attr_name, attr_type, attr_default, [this](const attr_type& a) { this->attr_name = a; }, \ + [this](attr_type&& a) { this->attr_name = a; }) +//NOLINTEND + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGPath.h b/include/tgfx/svg/node/SVGPath.h new file mode 100644 index 00000000..0d8092e5 --- /dev/null +++ b/include/tgfx/svg/node/SVGPath.h @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/node/SVGShape.h" + +namespace tgfx { + +class SVGPath final : public SVGShape { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGPath()); + } + + SVG_ATTR(ShapePath, Path, Path()) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType) const override; + + void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType, std::shared_ptr pathEffect) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + + private: + SVGPath(); + + using INHERITED = SVGShape; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGPattern.h b/include/tgfx/svg/node/SVGPattern.h new file mode 100644 index 00000000..ca61cae0 --- /dev/null +++ b/include/tgfx/svg/node/SVGPattern.h @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/core/Paint.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGPattern final : public SVGHiddenContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGPattern()); + } + + SVG_ATTR(Href, SVGIRI, SVGIRI()) + SVG_OPTIONAL_ATTR(X, SVGLength) + SVG_OPTIONAL_ATTR(Y, SVGLength) + SVG_OPTIONAL_ATTR(Width, SVGLength) + SVG_OPTIONAL_ATTR(Height, SVGLength) + SVG_OPTIONAL_ATTR(PatternTransform, SVGTransformType) + SVG_ATTR(PatternUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox)) + SVG_ATTR(ContentUnits, SVGObjectBoundingBoxUnits, + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse)) + + protected: + SVGPattern(); + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + bool onAsPaint(const SVGRenderContext& context, Paint* paint) const override; + + private: + struct PatternAttributes { + std::optional x, y, width, height; + std::optional patternTransform; + }; + + const SVGPattern* resolveHref(const SVGRenderContext& context, + PatternAttributes* attribute) const; + + const SVGPattern* hrefTarget(const SVGRenderContext& context) const; + + using INHERITED = SVGHiddenContainer; +}; + +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGPoly.h b/include/tgfx/svg/node/SVGPoly.h new file mode 100644 index 00000000..75e6a273 --- /dev/null +++ b/include/tgfx/svg/node/SVGPoly.h @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGShape.h" + +namespace tgfx { + +// Handles and elements. +class SVGPoly final : public SVGShape { + public: + static std::shared_ptr MakePolygon() { + return std::shared_ptr(new SVGPoly(SVGTag::Polygon)); + } + + static std::shared_ptr MakePolyline() { + return std::shared_ptr(new SVGPoly(SVGTag::Polyline)); + } + + SVG_ATTR(Points, SVGPointsType, SVGPointsType()) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType) const override; + + void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType, std::shared_ptr pathEffect) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + + private: + SVGPoly(SVGTag tag); + + mutable Path path; // mutated in onDraw(), to apply inherited fill types. + + using INHERITED = SVGShape; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGRadialGradient.h b/include/tgfx/svg/node/SVGRadialGradient.h new file mode 100644 index 00000000..99861219 --- /dev/null +++ b/include/tgfx/svg/node/SVGRadialGradient.h @@ -0,0 +1,55 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Matrix.h" +#include "tgfx/core/TileMode.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGGradient.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGRadialGradient final : public SVGGradient { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGRadialGradient()); + } + + SVG_ATTR(Cx, SVGLength, SVGLength(50, SVGLength::Unit::Percentage)) + SVG_ATTR(Cy, SVGLength, SVGLength(50, SVGLength::Unit::Percentage)) + SVG_ATTR(R, SVGLength, SVGLength(50, SVGLength::Unit::Percentage)) + SVG_OPTIONAL_ATTR(Fx, SVGLength) + SVG_OPTIONAL_ATTR(Fy, SVGLength) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + std::shared_ptr onMakeShader(const SVGRenderContext& context, + const std::vector& colors, + const std::vector& positions, TileMode tileMode, + const Matrix& localMatrix) const override; + + private: + SVGRadialGradient(); + + using INHERITED = SVGGradient; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGRect.h b/include/tgfx/svg/node/SVGRect.h new file mode 100644 index 00000000..8be66f7f --- /dev/null +++ b/include/tgfx/svg/node/SVGRect.h @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/RRect.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGShape.h" + +namespace tgfx { + +class SVGRect final : public SVGShape { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGRect()); + } + + SVG_ATTR(X, SVGLength, SVGLength(0)) + SVG_ATTR(Y, SVGLength, SVGLength(0)) + SVG_ATTR(Width, SVGLength, SVGLength(0)) + SVG_ATTR(Height, SVGLength, SVGLength(0)) + + SVG_OPTIONAL_ATTR(Rx, SVGLength) + SVG_OPTIONAL_ATTR(Ry, SVGLength) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType) const override; + + void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType, std::shared_ptr pathEffect) const override; + + Path onAsPath(const SVGRenderContext& context) const override; + + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + + private: + SVGRect(); + + RRect resolve(const SVGLengthContext& lengthContext) const; + + using INHERITED = SVGShape; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGRoot.h b/include/tgfx/svg/node/SVGRoot.h new file mode 100644 index 00000000..04e6a1f4 --- /dev/null +++ b/include/tgfx/svg/node/SVGRoot.h @@ -0,0 +1,69 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Size.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/SVGValue.h" +#include "tgfx/svg/node/SVGContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGLengthContext; + +class SVGRoot : public SVGContainer { + public: + enum class Type { + kRoot, + kInner, + }; + static std::shared_ptr Make(Type t = Type::kInner) { + return std::shared_ptr(new SVGRoot(t)); + } + + SVG_ATTR(X, SVGLength, SVGLength(0)) + SVG_ATTR(Y, SVGLength, SVGLength(0)) + SVG_ATTR(Width, SVGLength, SVGLength(100, SVGLength::Unit::Percentage)) + SVG_ATTR(Height, SVGLength, SVGLength(100, SVGLength::Unit::Percentage)) + SVG_ATTR(PreserveAspectRatio, SVGPreserveAspectRatio, SVGPreserveAspectRatio()) + + SVG_OPTIONAL_ATTR(ViewBox, SVGViewBoxType) + + Size intrinsicSize(const SVGLengthContext& lengthContext) const; + + void renderNode(const SVGRenderContext& context, const SVGIRI& iri) const; + + protected: + bool onPrepareToRender(SVGRenderContext* context) const override; + + void onSetAttribute(SVGAttribute attribute, const SVGValue& value) override; + + private: + explicit SVGRoot(Type t) : INHERITED(SVGTag::Svg), type(t) { + } + + // Some attributes behave differently for the outermost svg element. + const Type type; + + using INHERITED = SVGContainer; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGShape.h b/include/tgfx/svg/node/SVGShape.h new file mode 100644 index 00000000..29005cfd --- /dev/null +++ b/include/tgfx/svg/node/SVGShape.h @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/PathEffect.h" +#include "tgfx/svg/node/SVGTransformableNode.h" + +namespace tgfx { + +class SVGLengthContext; +class SVGNode; +class SVGRenderContext; +enum class SVGTag; + +class SVGShape : public SVGTransformableNode { + public: + void appendChild(std::shared_ptr node) override; + + protected: + explicit SVGShape(SVGTag tag); + + void onRender(const SVGRenderContext& context) const override; + + virtual void onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType fillType) const = 0; + + virtual void onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType fillType, + std::shared_ptr pathEffect) const = 0; + + private: + using INHERITED = SVGTransformableNode; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGStop.h b/include/tgfx/svg/node/SVGStop.h new file mode 100644 index 00000000..e82ce40e --- /dev/null +++ b/include/tgfx/svg/node/SVGStop.h @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGHiddenContainer.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGStop : public SVGHiddenContainer { + public: + static constexpr SVGTag tag = SVGTag::Stop; + + static std::shared_ptr Make() { + return std::shared_ptr(new SVGStop()); + } + + SVG_ATTR(Offset, SVGLength, SVGLength(0, SVGLength::Unit::Percentage)) + + protected: + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + SVGStop(); + + using INHERITED = SVGHiddenContainer; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGText.h b/include/tgfx/svg/node/SVGText.h new file mode 100644 index 00000000..7bf54fc2 --- /dev/null +++ b/include/tgfx/svg/node/SVGText.h @@ -0,0 +1,156 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/core/TextBlob.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGTransformableNode.h" + +namespace tgfx { + +class SVGRenderContext; + +using ShapedTextCallback = + std::function&)>; + +// Base class for text-rendering nodes. +class SVGTextFragment : public SVGTransformableNode { + public: + void renderText(const SVGRenderContext& context, const ShapedTextCallback& function) const; + + protected: + explicit SVGTextFragment(SVGTag t) : INHERITED(t) { + } + + virtual void onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const = 0; + + // Text nodes other than the root element are not rendered directly. + void onRender(const SVGRenderContext& /*context*/) const override { + } + + private: + Path onAsPath(const SVGRenderContext& context) const override; + + using INHERITED = SVGTransformableNode; +}; + +// Base class for nestable text containers (, , etc). +class SVGTextContainer : public SVGTextFragment { + public: + SVG_ATTR(X, std::vector, {}) + SVG_ATTR(Y, std::vector, {}) + SVG_ATTR(Dx, std::vector, {}) + SVG_ATTR(Dy, std::vector, {}) + SVG_ATTR(Rotate, std::vector, {}) + + void appendChild(std::shared_ptr child) final; + + protected: + explicit SVGTextContainer(SVGTag t) : INHERITED(t) { + } + + void onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const override; + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + private: + std::vector> children; + + using INHERITED = SVGTextFragment; +}; + +class SVGText final : public SVGTextContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGText()); + } + + private: + SVGText() : INHERITED(SVGTag::Text) { + } + + void onRender(const SVGRenderContext& context) const override; + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + Path onAsPath(const SVGRenderContext& context) const override; + + using INHERITED = SVGTextContainer; +}; + +class SVGTSpan final : public SVGTextContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGTSpan()); + } + + private: + SVGTSpan() : INHERITED(SVGTag::TSpan) { + } + + using INHERITED = SVGTextContainer; +}; + +class SVGTextLiteral final : public SVGTextFragment { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGTextLiteral()); + } + + SVG_ATTR(Text, SVGStringType, SVGStringType()) + + private: + SVGTextLiteral() : INHERITED(SVGTag::TextLiteral) { + } + + void onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const override; + + void appendChild(std::shared_ptr /*node*/) override { + } + + using INHERITED = SVGTextFragment; +}; + +class SVGTextPath final : public SVGTextContainer { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGTextPath()); + } + + SVG_ATTR(Href, SVGIRI, {}) + SVG_ATTR(StartOffset, SVGLength, SVGLength(0)) + + private: + SVGTextPath() : INHERITED(SVGTag::TextPath) { + } + + void onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const override; + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + using INHERITED = SVGTextContainer; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/node/SVGTransformableNode.h b/include/tgfx/svg/node/SVGTransformableNode.h new file mode 100644 index 00000000..5555cf97 --- /dev/null +++ b/include/tgfx/svg/node/SVGTransformableNode.h @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/SVGValue.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGRenderContext; +enum class SVGAttribute; + +class SVGTransformableNode : public SVGNode { + public: + void setTransform(const SVGTransformType& t) { + transform = t; + } + + protected: + SVGTransformableNode(SVGTag tag); + + bool onPrepareToRender(SVGRenderContext* context) const override; + + void onSetAttribute(SVGAttribute attribute, const SVGValue& value) override; + + void mapToParent(Path* path) const; + + void mapToParent(Rect* rect) const; + + private: + SVGTransformType transform; + + using INHERITED = SVGNode; +}; +} // namespace tgfx diff --git a/include/tgfx/svg/node/SVGUse.h b/include/tgfx/svg/node/SVGUse.h new file mode 100644 index 00000000..a9766ce0 --- /dev/null +++ b/include/tgfx/svg/node/SVGUse.h @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGTransformableNode.h" + +namespace tgfx { + +class SVGRenderContext; + +/** + * Implements support for (reference) elements. + * (https://www.w3.org/TR/SVG11/struct.html#UseElement) + */ +class SVGUse final : public SVGTransformableNode { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SVGUse()); + } + + void appendChild(std::shared_ptr) override{}; + + SVG_ATTR(X, SVGLength, SVGLength(0)) + SVG_ATTR(Y, SVGLength, SVGLength(0)) + SVG_ATTR(Href, SVGIRI, SVGIRI()) + + protected: + bool onPrepareToRender(SVGRenderContext* context) const override; + void onRender(const SVGRenderContext& context) const override; + Path onAsPath(const SVGRenderContext& context) const override; + Rect onObjectBoundingBox(const SVGRenderContext& context) const override; + + private: + SVGUse(); + + bool parseAndSetAttribute(const std::string& name, const std::string& value) override; + + using INHERITED = SVGTransformableNode; +}; +} // namespace tgfx \ No newline at end of file diff --git a/include/tgfx/svg/xml/XMLDOM.h b/include/tgfx/svg/xml/XMLDOM.h index 4bdf81d9..b875bd5c 100644 --- a/include/tgfx/svg/xml/XMLDOM.h +++ b/include/tgfx/svg/xml/XMLDOM.h @@ -22,6 +22,7 @@ #include #include #include "tgfx/core/Data.h" +#include "tgfx/core/Stream.h" namespace tgfx { @@ -33,7 +34,10 @@ struct DOMAttribute { std::string value; }; -enum class DOMNodeType { Element, Text }; +enum class DOMNodeType { + Element, + Text, +}; struct DOMNode { std::string name; @@ -77,11 +81,11 @@ class DOM { ~DOM(); /** - * Constructs a DOM tree from XML text data. - * @param data XML text data. + * Constructs a DOM tree from XML text stream. + * @param stream XML text stream. * @return The DOM tree. Returns nullptr if construction fails. */ - static std::shared_ptr MakeFromData(const Data& data); + static std::shared_ptr Make(Stream& stream); /** * Creates a deep copy of a DOM tree. diff --git a/resources/apitest/SVG/blur.svg b/resources/apitest/SVG/blur.svg new file mode 100644 index 00000000..809f1aec --- /dev/null +++ b/resources/apitest/SVG/blur.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/resources/apitest/SVG/complex1.svg b/resources/apitest/SVG/complex1.svg new file mode 100644 index 00000000..224471c8 --- /dev/null +++ b/resources/apitest/SVG/complex1.svg @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/resources/apitest/SVG/complex2.svg b/resources/apitest/SVG/complex2.svg new file mode 100644 index 00000000..3b5ba170 --- /dev/null +++ b/resources/apitest/SVG/complex2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/apitest/SVG/complex3.svg b/resources/apitest/SVG/complex3.svg new file mode 100644 index 00000000..4d34e2ab --- /dev/null +++ b/resources/apitest/SVG/complex3.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/apitest/SVG/complex4.svg b/resources/apitest/SVG/complex4.svg new file mode 100644 index 00000000..91b3264e --- /dev/null +++ b/resources/apitest/SVG/complex4.svg @@ -0,0 +1 @@ +5日6日7日8日9日10日11日00.20.40.6商品用券情况用券未使用券 \ No newline at end of file diff --git a/resources/apitest/SVG/complex5.svg b/resources/apitest/SVG/complex5.svg new file mode 100644 index 00000000..55d46b91 --- /dev/null +++ b/resources/apitest/SVG/complex5.svg @@ -0,0 +1,1502 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/apitest/SVG/complex6.svg b/resources/apitest/SVG/complex6.svg new file mode 100644 index 00000000..e9a04ef3 --- /dev/null +++ b/resources/apitest/SVG/complex6.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/apitest/SVG/complex7.svg b/resources/apitest/SVG/complex7.svg new file mode 100644 index 00000000..9a0d53d3 --- /dev/null +++ b/resources/apitest/SVG/complex7.svg @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/apitest/SVG/jpg.svg b/resources/apitest/SVG/jpg.svg new file mode 100644 index 00000000..0771a79e --- /dev/null +++ b/resources/apitest/SVG/jpg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/resources/apitest/SVG/mask.svg b/resources/apitest/SVG/mask.svg new file mode 100644 index 00000000..1f982449 --- /dev/null +++ b/resources/apitest/SVG/mask.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/apitest/SVG/path.svg b/resources/apitest/SVG/path.svg new file mode 100644 index 00000000..b516f00a --- /dev/null +++ b/resources/apitest/SVG/path.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/apitest/SVG/png.svg b/resources/apitest/SVG/png.svg new file mode 100644 index 00000000..921c2a13 --- /dev/null +++ b/resources/apitest/SVG/png.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/resources/apitest/SVG/radialGradient.svg b/resources/apitest/SVG/radialGradient.svg new file mode 100644 index 00000000..4747a517 --- /dev/null +++ b/resources/apitest/SVG/radialGradient.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/apitest/SVG/text.svg b/resources/apitest/SVG/text.svg new file mode 100644 index 00000000..d242aab0 --- /dev/null +++ b/resources/apitest/SVG/text.svg @@ -0,0 +1,8 @@ + + + + SVG GVS + diff --git a/resources/apitest/SVG/textFont.svg b/resources/apitest/SVG/textFont.svg new file mode 100644 index 00000000..32c6f043 --- /dev/null +++ b/resources/apitest/SVG/textFont.svg @@ -0,0 +1,9 @@ + + + + SVG GVS + SVG GVS + diff --git a/src/core/utils/Stream.cpp b/src/core/utils/Stream.cpp index bdeb70b1..63e41448 100644 --- a/src/core/utils/Stream.cpp +++ b/src/core/utils/Stream.cpp @@ -17,10 +17,14 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "tgfx/core/Stream.h" +#include +#include #include #include #include +#include #include "core/utils/Log.h" +#include "tgfx/core/Data.h" namespace tgfx { static std::mutex& locker = *new std::mutex; @@ -61,6 +65,53 @@ class FileStream : public Stream { size_t length = 0; }; +class MemoryStream : public Stream { + public: + explicit MemoryStream(std::shared_ptr data) : data(std::move(data)) { + } + + ~MemoryStream() override = default; + + size_t size() const override { + return data->size(); + } + + bool seek(size_t position) override { + offset = position > data->size() ? data->size() : position; + return true; + } + + bool move(int moveOffset) override { + return this->seek(offset + static_cast(moveOffset)); + } + + size_t read(void* buffer, size_t size) override { + size_t dataSize = data->size(); + + if (size > dataSize - offset) { + size = dataSize - offset; + } + if (buffer) { + memcpy(buffer, data->bytes() + offset, size); + } + offset += size; + return size; + } + + bool rewind() override { + offset = 0; + return true; + } + + const void* getMemoryBase() override { + return data->data(); + } + + private: + std::shared_ptr data = nullptr; + size_t offset = 0; +}; + std::string GetProtocolFromPath(const std::string& path) { size_t pos = path.find("://"); if (pos != std::string::npos) { @@ -99,6 +150,13 @@ std::unique_ptr Stream::MakeFromFile(const std::string& filePath) { return std::make_unique(file, length); } +std::unique_ptr Stream::MakeFromData(std::shared_ptr data) { + if (data == nullptr) { + return nullptr; + } + return std::make_unique(data); +} + void StreamFactory::RegisterCustomProtocol(const std::string& customProtocol, std::shared_ptr factory) { if (customProtocol.empty() || factory == nullptr) { diff --git a/src/core/vectors/coregraphics/CGTypeface.cpp b/src/core/vectors/coregraphics/CGTypeface.cpp index e3dad9c1..5be35b23 100644 --- a/src/core/vectors/coregraphics/CGTypeface.cpp +++ b/src/core/vectors/coregraphics/CGTypeface.cpp @@ -17,8 +17,10 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "CGTypeface.h" +#include #include "CGScalerContext.h" #include "core/utils/UniqueID.h" +#include "tgfx/core/FontStyle.h" #include "tgfx/core/Typeface.h" #include "tgfx/core/UTF.h" @@ -33,6 +35,7 @@ std::string StringFromCFString(CFStringRef src) { std::shared_ptr Typeface::MakeFromName(const std::string& fontFamily, const std::string& fontStyle) { + CFMutableDictionaryRef cfAttributes = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!fontFamily.empty()) { @@ -65,6 +68,70 @@ std::shared_ptr Typeface::MakeFromName(const std::string& fontFamily, return typeface; } +static constexpr std::array FontWeightMap = {-1.0f, -0.6f, -0.5f, -0.4f, 0.0f, 0.23f, + 0.3f, 0.4f, 0.56f, 0.62f, 0.7f}; + +static constexpr std::array FontWidthMap = {-0.8f, -0.6f, -0.4f, -0.2f, 0.0f, + 0.2f, 0.4f, 0.6f, 0.8f}; + +static constexpr std::array FontSlantMap = {-1.0f, 0.0f, 1.0f}; + +std::shared_ptr Typeface::MakeFromStyle(const std::string& fontFamily, + FontStyle fontStyle) { + + CFMutableDictionaryRef cfAttributes = CFDictionaryCreateMutable( + kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (!fontFamily.empty()) { + const auto* cfFontName = + CFStringCreateWithCString(kCFAllocatorDefault, fontFamily.c_str(), kCFStringEncodingUTF8); + if (cfFontName) { + CFDictionaryAddValue(cfAttributes, kCTFontFamilyNameAttribute, cfFontName); + CFRelease(cfFontName); + } + } + + CFMutableDictionaryRef cfTraits = CFDictionaryCreateMutable( + kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (cfTraits) { + float fontWeight = FontWeightMap[static_cast(fontStyle.weight())]; + CFNumberRef cfWeight = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &fontWeight); + if (cfWeight) { + CFDictionaryAddValue(cfTraits, kCTFontWeightTrait, cfWeight); + CFRelease(cfWeight); + } + + float fontWidth = FontWidthMap[static_cast(fontStyle.width())]; + CFNumberRef cfWidth = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &fontWidth); + if (cfWidth) { + CFDictionaryAddValue(cfTraits, kCTFontWidthTrait, cfWidth); + CFRelease(cfWidth); + } + + float fontSlant = FontSlantMap[static_cast(fontStyle.slant())]; + CFNumberRef cfSlant = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &fontSlant); + if (cfSlant) { + CFDictionaryAddValue(cfTraits, kCTFontSlantTrait, cfSlant); + CFRelease(cfSlant); + } + + CFDictionaryAddValue(cfAttributes, kCTFontTraitsAttribute, cfTraits); + CFRelease(cfTraits); + } + + std::shared_ptr typeface; + const auto* cfDesc = CTFontDescriptorCreateWithAttributes(cfAttributes); + if (cfDesc) { + const auto* ctFont = CTFontCreateWithFontDescriptor(cfDesc, 0, nullptr); + if (ctFont) { + typeface = CGTypeface::Make(ctFont); + CFRelease(ctFont); + } + CFRelease(cfDesc); + } + CFRelease(cfAttributes); + return typeface; +} + std::shared_ptr Typeface::MakeFromPath(const std::string& fontPath, int) { CGDataProviderRef cgDataProvider = CGDataProviderCreateWithFilename(fontPath.c_str()); if (cgDataProvider == nullptr) { diff --git a/src/core/vectors/freetype/FTTypeface.cpp b/src/core/vectors/freetype/FTTypeface.cpp index 4d48df2c..51dae603 100644 --- a/src/core/vectors/freetype/FTTypeface.cpp +++ b/src/core/vectors/freetype/FTTypeface.cpp @@ -31,6 +31,11 @@ std::shared_ptr Typeface::MakeFromName(const std::string& fontFamily, return SystemFont::MakeFromName(fontFamily, fontStyle); } +std::shared_ptr Typeface::MakeFromStyle(const std::string& fontFamily, + FontStyle fontStyle) { + return SystemFont::MakeFromStyle(fontFamily, fontStyle); +} + std::shared_ptr Typeface::MakeFromPath(const std::string& fontPath, int ttcIndex) { return FTTypeface::Make(FTFontData(fontPath, ttcIndex)); } diff --git a/src/core/vectors/freetype/SystemFont.cpp b/src/core/vectors/freetype/SystemFont.cpp index 1cfa1889..48aec333 100644 --- a/src/core/vectors/freetype/SystemFont.cpp +++ b/src/core/vectors/freetype/SystemFont.cpp @@ -18,6 +18,7 @@ #include "SystemFont.h" #include +#include "tgfx/core/FontStyle.h" #ifdef _WIN32 #include @@ -136,6 +137,33 @@ DWriteFontStyle ToDWriteFontStyle(const std::string& fontStyle) { return dWriteFontStyle; } +static constexpr std::array FontWeightMap = { + DWRITE_FONT_WEIGHT_THIN, DWRITE_FONT_WEIGHT_THIN, DWRITE_FONT_WEIGHT_EXTRA_LIGHT, + DWRITE_FONT_WEIGHT_LIGHT, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_WEIGHT_MEDIUM, + DWRITE_FONT_WEIGHT_SEMI_BOLD, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_WEIGHT_EXTRA_BOLD, + DWRITE_FONT_WEIGHT_BLACK, DWRITE_FONT_WEIGHT_EXTRA_BLACK}; + +static constexpr std::array FontWidthMap = { + DWRITE_FONT_STRETCH_ULTRA_CONDENSED, DWRITE_FONT_STRETCH_EXTRA_CONDENSED, + DWRITE_FONT_STRETCH_CONDENSED, DWRITE_FONT_STRETCH_SEMI_CONDENSED, + DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STRETCH_SEMI_EXPANDED, + DWRITE_FONT_STRETCH_EXPANDED, DWRITE_FONT_STRETCH_EXTRA_EXPANDED, + DWRITE_FONT_STRETCH_ULTRA_EXPANDED}; + +static constexpr std::array FontSlantMap = { + DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STYLE_ITALIC, DWRITE_FONT_STYLE_OBLIQUE}; + +DWriteFontStyle ToDWriteFontStyle(FontStyle fontStyle) { + std::string key; + key.resize(fontStyle.length()); + std::transform(fontStyle.begin(), fontStyle.end(), key.begin(), ::tolower); + DWriteFontStyle dWriteFontStyle{}; + dWriteFontStyle.weight = FontWeightMap[static_cast(fontStyle.weight())]; + dWriteFontStyle.stretch = FontWidthMap[static_cast(fontStyle.width())]; + dWriteFontStyle.fontStyle = FontSlantMap[static_cast(fontStyle.slant())]; + return dWriteFontStyle; +} + std::wstring ToWstring(const std::string& input) { std::wstring result; int len = MultiByteToWideChar(CP_ACP, 0, input.c_str(), input.size(), nullptr, 0); @@ -174,7 +202,7 @@ void SafeRelease(T** ppT) { } std::shared_ptr MakeFromFontName(const std::string& fontFamily, - const std::string& fontStyle) { + const DWriteFontStyle& fontStyle) { if (fontFamily.empty()) { return nullptr; } @@ -187,10 +215,8 @@ std::shared_ptr MakeFromFontName(const std::string& fontFamily, hResult = writeFactory->GetSystemFontSet(&fontSet); } if (SUCCEEDED(hResult)) { - DWriteFontStyle dWriteFontStyle = ToDWriteFontStyle(fontStyle); - hResult = - fontSet->GetMatchingFonts(ToWstring(fontFamily).c_str(), dWriteFontStyle.weight, - dWriteFontStyle.stretch, dWriteFontStyle.fontStyle, &fontSet); + hResult = fontSet->GetMatchingFonts(ToWstring(fontFamily).c_str(), fontStyle.weight, + fontStyle.stretch, fontStyle.fontStyle, &fontSet); } UINT32 fontCount = 0; if (SUCCEEDED(hResult)) { @@ -237,8 +263,19 @@ std::shared_ptr MakeFromFontName(const std::string& fontFamily, std::shared_ptr SystemFont::MakeFromName(const std::string& fontFamily, const std::string& fontStyle) { #ifdef _WIN32 - return MakeFromFontName(fontFamily, fontStyle); + auto dWriteFontStyle = ToDWriteFontStyle(fontStyle); + return MakeFromFontName(fontFamily, dWriteFontStyle); +#endif + return nullptr; +} + +std::shared_ptr SystemFont::MakeFromStyle(const std::string& fontFamily, + FontStyle fontStyle) { +#ifdef _WIN32 + auto dWriteFontStyle = ToDWriteFontStyle(fontStyle); + return MakeFromFontName(fontFamily, dWriteFontStyle); #endif return nullptr; } + } // namespace tgfx diff --git a/src/core/vectors/freetype/SystemFont.h b/src/core/vectors/freetype/SystemFont.h index b8020117..0a3462ab 100644 --- a/src/core/vectors/freetype/SystemFont.h +++ b/src/core/vectors/freetype/SystemFont.h @@ -25,5 +25,8 @@ class SystemFont { public: static std::shared_ptr MakeFromName(const std::string& fontFamily, const std::string& fontStyle); + + static std::shared_ptr MakeFromStyle(const std::string& fontFamily, + FontStyle fontStyle); }; } // namespace tgfx \ No newline at end of file diff --git a/src/svg/ElementWriter.cpp b/src/svg/ElementWriter.cpp index 20e0fe14..d7a0cd8a 100644 --- a/src/svg/ElementWriter.cpp +++ b/src/svg/ElementWriter.cpp @@ -17,7 +17,6 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "ElementWriter.h" -#include <_types/_uint32_t.h> #include #include #include "SVGExportContext.h" diff --git a/src/svg/FontManager.cpp b/src/svg/FontManager.cpp new file mode 100644 index 00000000..c968e714 --- /dev/null +++ b/src/svg/FontManager.cpp @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/FontManager.h" +#include +#include +#include "core/utils/Log.h" + +namespace tgfx { + +std::shared_ptr FontManager::matchTypeface(const std::string& familyName, + FontStyle style) const { + return onMatchTypeface(familyName, style); +} + +std::shared_ptr FontManager::getFallbackTypeface(const std::string& familyName, + FontStyle style, + Unichar character) const { + return onGetFallbackTypeface(familyName, style, character); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/ResourceLoader.cpp b/src/svg/ResourceLoader.cpp new file mode 100644 index 00000000..e4d31076 --- /dev/null +++ b/src/svg/ResourceLoader.cpp @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/ResourceLoader.h" +#include +#include +#include +#include + +namespace tgfx { + +class SystemResourceLoader final : public ResourceLoader { + public: + explicit SystemResourceLoader(std::string basePath) : basePath(std::move(basePath)) { + } + + std::shared_ptr load(const std::string& resourcePath, + const std::string& resourceName) const override { + return Data::MakeFromFile(basePath + "/" + resourcePath + "/" + resourceName); + }; + + std::shared_ptr loadImage(const std::string& resourcePath, + const std::string& resourceName) const override { + return Image::MakeFromFile(basePath + "/" + resourcePath + "/" + resourceName); + } + + private: + const std::string basePath; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGAttribute.cpp b/src/svg/SVGAttribute.cpp new file mode 100644 index 00000000..74d9b8a4 --- /dev/null +++ b/src/svg/SVGAttribute.cpp @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/SVGAttribute.h" +#include "tgfx/core/Color.h" + +namespace tgfx { +SVGPresentationAttributes SVGPresentationAttributes::MakeInitial() { + SVGPresentationAttributes result; + + result.Fill.set(SVGPaint(SVGColor(Color::Black()))); + result.FillOpacity.set(static_cast(1)); + result.FillRule.set(SVGFillRule(SVGFillRule::Type::NonZero)); + result.ClipRule.set(SVGFillRule(SVGFillRule::Type::NonZero)); + + result.Stroke.set(SVGPaint(SVGPaint::Type::None)); + result.StrokeDashArray.set(SVGDashArray(SVGDashArray::Type::None)); + result.StrokeDashOffset.set(SVGLength(0)); + result.StrokeLineCap.set(SVGLineCap::Butt); + result.StrokeLineJoin.set(SVGLineJoin(SVGLineJoin::Type::Miter)); + result.StrokeMiterLimit.set(static_cast(4)); + result.StrokeOpacity.set(static_cast(1)); + result.StrokeWidth.set(SVGLength(1)); + + result.Visibility.set(SVGVisibility(SVGVisibility::Type::Visible)); + + result.Color.set(SVGColorType(Color::Black())); + result.ColorInterpolation.set(SVGColorspace::SRGB); + result.ColorInterpolationFilters.set(SVGColorspace::LinearRGB); + + result.FontFamily.init("default"); + result.FontStyle.init(SVGFontStyle::Type::Normal); + result.FontSize.init(SVGLength(24)); + result.FontWeight.init(SVGFontWeight::Type::Normal); + result.TextAnchor.init(SVGTextAnchor::Type::Start); + + result.Display.init(SVGDisplay::Inline); + + result.StopColor.set(SVGColor(Color::Black())); + result.StopOpacity.set(static_cast(1)); + result.FloodColor.set(SVGColor(Color::Black())); + result.FloodOpacity.set(static_cast(1)); + result.LightingColor.set(SVGColor(Color::White())); + + return result; +} + +} // namespace tgfx diff --git a/src/svg/SVGAttributeParser.cpp b/src/svg/SVGAttributeParser.cpp new file mode 100644 index 00000000..fb1736c8 --- /dev/null +++ b/src/svg/SVGAttributeParser.cpp @@ -0,0 +1,1225 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SVGAttributeParser.h" +#include +#include +#include +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "core/utils/MathExtra.h" +#include "svg/SVGUtils.h" +#include "tgfx/core/Color.h" +#include "tgfx/core/Typeface.h" +#include "tgfx/core/UTF.h" +#include "tgfx/svg/SVGTypes.h" + +namespace { + +inline bool is_between(char c, char min, char max) { + ASSERT(min <= max); + return static_cast(c - min) <= static_cast(max - min); +} + +inline bool is_ws(char c) { + return is_between(c, 1, 32); +} + +inline bool is_sep(char c) { + return is_ws(c) || c == ',' || c == ';'; +} + +inline bool is_nl(char c) { + return c == '\n' || c == '\r' || c == '\f'; +} + +inline bool is_hex(char c) { + return is_between(c, 'a', 'f') || is_between(c, 'A', 'F') || is_between(c, '0', '9'); +} + +} // namespace + +namespace tgfx { + +SVGAttributeParser::SVGAttributeParser(std::string attributeString) + : currentPos(attributeString.data()), endPos(currentPos + attributeString.size()) { +} + +template +inline bool SVGAttributeParser::advanceWhile(F f) { + const auto* initial = currentPos; + while (currentPos < endPos && f(*currentPos)) { + currentPos++; + } + return currentPos != initial; +} + +bool SVGAttributeParser::matchStringToken(const char* token, const char** newPos) const { + const char* c = currentPos; + + while (c < endPos && *token && *c == *token) { + c++; + token++; + } + + if (*token) { + return false; + } + + if (newPos) { + *newPos = c; + } + + return true; +} + +bool SVGAttributeParser::parseEOSToken() { + return currentPos == endPos; +} + +bool SVGAttributeParser::parseSepToken() { + return this->advanceWhile(is_sep); +} + +bool SVGAttributeParser::parseWSToken() { + return this->advanceWhile(is_ws); +} + +bool SVGAttributeParser::parseCommaWspToken() { + // comma-wsp: + // (wsp+ comma? wsp*) | (comma wsp*) + return this->parseWSToken() || this->parseExpectedStringToken(","); +} + +bool SVGAttributeParser::parseExpectedStringToken(const char* expected) { + const char* newPos; + if (!matchStringToken(expected, &newPos)) { + return false; + } + + currentPos = newPos; + return true; +} + +bool SVGAttributeParser::parseScalarToken(float* res) { + if (const char* next = SVGParse::FindScalar(currentPos, res)) { + currentPos = next; + return true; + } + return false; +} + +bool SVGAttributeParser::parseInt32Token(int32_t* res) { + if (const char* next = SVGParse::FindS32(currentPos, res)) { + currentPos = next; + return true; + } + return false; +} + +bool SVGAttributeParser::matchHexToken(const char** newPos) const { + *newPos = currentPos; + while (*newPos < endPos && is_hex(**newPos)) { + ++*newPos; + } + return *newPos != currentPos; +} + +bool SVGAttributeParser::parseEscape(Unichar* c) { + // \(hexDigit{1,6}whitespace?|[^newline|hexDigit]) + RestoreCurPos restoreCurPos(this); + + if (!this->parseExpectedStringToken("\\")) { + return false; + } + const char* hexEnd; + if (this->matchHexToken(&hexEnd)) { + if (hexEnd - currentPos > 6) { + hexEnd = currentPos + 6; + } + char hexString[7]; + auto hexSize = static_cast(hexEnd - currentPos); + memcpy(hexString, currentPos, hexSize); + hexString[hexSize] = '\0'; + uint32_t cp; + const char* hexFound = SVGParse::FindHex(hexString, &cp); + if (!hexFound || cp < 1 || (0xD800 <= cp && cp <= 0xDFFF) || 0x10FFFF < cp) { + cp = 0xFFFD; + } + *c = static_cast(cp); + currentPos = hexEnd; + this->parseWSToken(); + } else if (this->parseEOSToken() || is_nl(*currentPos)) { + *c = 0xFFFD; + return false; + } else { + if (*c = UTF::NextUTF8(¤tPos, endPos); *c < 0) { + return false; + } + } + + restoreCurPos.clear(); + return true; +} + +bool SVGAttributeParser::parseIdentToken(std::string* ident) { + // + // (--|-?([a-z|A-Z|_|non-ASCII]|escape))([a-z|A-Z|0-9|_|-|non-ASCII]|escape)? + RestoreCurPos restoreCurPos(this); + + Unichar c; + if (this->parseExpectedStringToken("--")) { + ident->append("--"); + } else { + if (this->parseExpectedStringToken("-")) { + ident->append("-"); + } + if (this->parseEscape(&c)) { + // ident->appendUnichar(c); + } else { + if (c = UTF::NextUTF8(¤tPos, endPos); c < 0) { + return false; + } + if ((c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && (c != '_') && + (c < 0x80 || 0x10FFFF < c)) { + return false; + } + ident->append(UTF::ToUTF8(c)); + } + } + while (currentPos < endPos) { + if (this->parseEscape(&c)) { + ident->append(UTF::ToUTF8(c)); + continue; + } + const char* next = currentPos; + if (c = UTF::NextUTF8(&next, endPos); c < 0) { + break; + } + if ((c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && (c < '0' || '9' < c) && (c != '_') && + (c != '-') && (c < 0x80 || 0x10FFFF < c)) { + break; + } + ident->append(UTF::ToUTF8(c)); + currentPos = next; + } + + restoreCurPos.clear(); + return true; +} + +bool SVGAttributeParser::parseLengthUnitToken(SVGLength::Unit& unit) { + struct Unit { + Unit(std::string name, SVGLength::Unit u) : unitName(std::move(name)), unit(u) { + } + std::string unitName; + SVGLength::Unit unit; + }; + + static const std::array unitInfo = { + Unit("%", SVGLength::Unit::Percentage), Unit("em", SVGLength::Unit::EMS), + Unit("ex", SVGLength::Unit::EXS), Unit("px", SVGLength::Unit::PX), + Unit("cm", SVGLength::Unit::CM), Unit("mm", SVGLength::Unit::MM), + Unit("in", SVGLength::Unit::IN), Unit("pt", SVGLength::Unit::PT), + Unit("pc", SVGLength::Unit::PC), + }; + + return std::any_of(unitInfo.begin(), unitInfo.end(), [&](const Unit& item) { + if (this->parseExpectedStringToken(item.unitName.data())) { + unit = item.unit; + return true; + } + return false; + }); +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeColor +bool SVGAttributeParser::parseNamedColorToken(Color* c) { + RestoreCurPos restoreCurPos(this); + + std::string ident; + if (!this->parseIdentToken(&ident)) { + return false; + } + if (!SVGParse::FindNamedColor(ident.c_str(), c)) { + return false; + } + + restoreCurPos.clear(); + return true; +} + +bool SVGAttributeParser::parseHexColorToken(Color* c) { + RestoreCurPos restoreCurPos(this); + + const char* hexEnd; + if (!this->parseExpectedStringToken("#") || !this->matchHexToken(&hexEnd)) { + return false; + } + + uint32_t v; + std::string hexString(currentPos, static_cast(hexEnd - currentPos)); + SVGParse::FindHex(hexString.c_str(), &v); + + switch (hexString.size()) { + case 6: + // matched #xxxxxxx + break; + case 3: + // matched '#xxx; + v = ((v << 12) & 0x00f00000) | ((v << 8) & 0x000ff000) | ((v << 4) & 0x00000ff0) | + ((v << 0) & 0x0000000f); + break; + default: + return false; + } + + *c = Uint32ToColor(v | 0xff000000); + currentPos = hexEnd; + + restoreCurPos.clear(); + return true; +} + +bool SVGAttributeParser::parseColorComponentIntegralToken(int32_t* c) { + const char* p = SVGParse::FindS32(currentPos, c); + if (!p || *p == '.') { + // No value parsed, or fractional value. + return false; + } + + if (*p == '%') { + *c = static_cast(std::round(static_cast(*c) * 255.0f / 100)); + *c = std::clamp(*c, 0, 255); + p++; + } + + currentPos = p; + return true; +} + +bool SVGAttributeParser::parseColorComponentFractionalToken(int32_t* c) { + float s; + const char* p = SVGParse::FindScalar(currentPos, &s); + if (!p || *p != '%') { + // Floating point must be a percentage (CSS2 rgb-percent syntax). + return false; + } + p++; // Skip '%' + + *c = static_cast(std::round(s * 255.0f / 100)); + *c = std::clamp(*c, 0, 255); + currentPos = p; + return true; +} + +bool SVGAttributeParser::parseColorComponentScalarToken(int32_t* c) { + float s; + if (const char* p = SVGParse::FindScalar(currentPos, &s)) { + *c = static_cast(std::round(s * 255.0f)); + *c = std::clamp(*c, 0, 255); + currentPos = p; + return true; + } + return false; +} + +bool SVGAttributeParser::parseColorComponentToken(int32_t* c) { + return parseColorComponentIntegralToken(c) || parseColorComponentFractionalToken(c); +} + +bool SVGAttributeParser::parseRGBColorToken(Color* c) { + return this->parseParenthesized( + "rgb", + [this](Color* c) -> bool { + int32_t r = 0; + int32_t g = 0; + int32_t b = 0; + if (this->parseColorComponentToken(&r) && this->parseSepToken() && + this->parseColorComponentToken(&g) && this->parseSepToken() && + this->parseColorComponentToken(&b)) { + + *c = Color::FromRGBA(static_cast(r), static_cast(g), + static_cast(b)); + return true; + } + return false; + }, + c); +} + +bool SVGAttributeParser::parseRGBAColorToken(Color* c) { + return this->parseParenthesized( + "rgba", + [this](Color* c) -> bool { + int32_t r = 0; + int32_t g = 0; + int32_t b = 0; + int32_t a = 0; + if (this->parseColorComponentToken(&r) && this->parseSepToken() && + this->parseColorComponentToken(&g) && this->parseSepToken() && + this->parseColorComponentToken(&b) && this->parseSepToken() && + this->parseColorComponentScalarToken(&a)) { + + *c = Color::FromRGBA(static_cast(r), static_cast(g), + static_cast(b), static_cast(a)); + return true; + } + return false; + }, + c); +} + +bool SVGAttributeParser::parseColorToken(Color* c) { + return this->parseHexColorToken(c) || this->parseNamedColorToken(c) || + this->parseRGBAColorToken(c) || this->parseRGBColorToken(c); +} + +bool SVGAttributeParser::parseSVGColorType(SVGColorType* color) { + Color c; + if (!this->parseColorToken(&c)) { + return false; + } + *color = SVGColorType(c); + return true; +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeColor +// And https://www.w3.org/TR/CSS2/syndata.html#color-units for the alternative +// forms supported by SVG (e.g. RGB percentages). +template <> +bool SVGAttributeParser::parse(SVGColorType* color) { + this->parseWSToken(); + if (!this->parseSVGColorType(color)) { + return false; + } + this->parseWSToken(); + return this->parseEOSToken(); +} + +bool SVGAttributeParser::parseSVGColor(SVGColor* color, SVGColor::Vars&& vars) { + static const constexpr int kVarsLimit = 32; + + if (SVGColorType c; this->parseSVGColorType(&c)) { + *color = SVGColor(c, std::move(vars)); + return true; + } + if (this->parseExpectedStringToken("currentColor")) { + *color = SVGColor(SVGColor::Type::CurrentColor, std::move(vars)); + return true; + } + // https://drafts.csswg.org/css-variables/#using-variables + if (this->parseParenthesized( + "var", + [this, &vars](SVGColor* colorResult) -> bool { + std::string ident; + if (!this->parseIdentToken(&ident) || ident.size() < 2 || + ident.compare(0, 2, "--") != 0) { + return false; + } + ident.erase(0, 2); + vars.push_back(std::move(ident)); + this->parseWSToken(); + if (!this->parseExpectedStringToken(",")) { + *colorResult = SVGColor(Color::Black(), std::move(vars)); + return true; + } + this->parseWSToken(); + if (this->matchStringToken(")")) { + *colorResult = SVGColor(Color::Black(), std::move(vars)); + return true; + } + return vars.size() < kVarsLimit && this->parseSVGColor(colorResult, std::move(vars)); + }, + color)) { + return true; + } + return false; +} + +// https://www.w3.org/TR/SVG11/types.html#InterfaceSVGColor +template <> +bool SVGAttributeParser::parse(SVGColor* color) { + this->parseWSToken(); + if (!this->parseSVGColor(color, SVGColor::Vars())) { + return false; + } + this->parseWSToken(); + return this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/linking.html#IRIReference +template <> +bool SVGAttributeParser::parse(SVGIRI* iri) { + // consume preceding whitespace + this->parseWSToken(); + + SVGIRI::Type iriType; + if (this->parseExpectedStringToken("#")) { + iriType = SVGIRI::Type::Local; + } else if (this->matchStringToken("data:")) { + iriType = SVGIRI::Type::DataURI; + } else { + iriType = SVGIRI::Type::Nonlocal; + } + + const auto* start = currentPos; + if (!this->advanceWhile([](char c) -> bool { return c != ')'; })) { + return false; + } + *iri = SVGIRI(iriType, std::string(start, static_cast(currentPos - start))); + return true; +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeFuncIRI +bool SVGAttributeParser::parseFuncIRI(SVGFuncIRI* iri) { + return this->parseParenthesized( + "url", + [this](SVGFuncIRI* iriResult) -> bool { + SVGIRI iri; + if (this->parse(&iri)) { + *iriResult = SVGFuncIRI(std::move(iri)); + return true; + } + return false; + }, + iri); +} + +template <> +bool SVGAttributeParser::parse(SVGStringType* result) { + if (this->parseEOSToken()) { + return false; + } + *result = SVGStringType(currentPos); + currentPos += result->size(); + return this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeNumber +template <> +bool SVGAttributeParser::parse(SVGNumberType* number) { + // consume WS + this->parseWSToken(); + + float s; + if (this->parseScalarToken(&s)) { + *number = static_cast(s); + // consume trailing separators + this->parseSepToken(); + return true; + } + + return false; +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeInteger +bool SVGAttributeParser::parseInteger(SVGIntegerType* number) { + // consume WS + this->parseWSToken(); + + // consume optional '+' + this->parseExpectedStringToken("+"); + + SVGIntegerType i; + if (this->parseInt32Token(&i)) { + *number = i; + // consume trailing separators + this->parseSepToken(); + return true; + } + + return false; +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeLength +template <> +bool SVGAttributeParser::parse(SVGLength* length) { + float s; + SVGLength::Unit u = SVGLength::Unit::Number; + + if (this->parseScalarToken(&s) && + (this->parseLengthUnitToken(u) || this->parseSepToken() || this->parseEOSToken())) { + *length = SVGLength(s, u); + // consume trailing separators + this->parseSepToken(); + return true; + } + + return false; +} + +// https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute +bool SVGAttributeParser::parseViewBox(SVGViewBoxType* vb) { + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + this->parseWSToken(); + + bool parsedValue = false; + if (this->parseScalarToken(&x) && this->parseSepToken() && this->parseScalarToken(&y) && + this->parseSepToken() && this->parseScalarToken(&w) && this->parseSepToken() && + this->parseScalarToken(&h)) { + + *vb = static_cast(Rect::MakeXYWH(x, y, w, h)); + parsedValue = true; + // consume trailing whitespace + this->parseWSToken(); + } + return parsedValue && this->parseEOSToken(); +} + +template +bool SVGAttributeParser::parseParenthesized(const char* prefix, Func f, T* result) { + RestoreCurPos restoreCurPos(this); + + this->parseWSToken(); + if (prefix && !this->parseExpectedStringToken(prefix)) { + return false; + } + this->parseWSToken(); + if (!this->parseExpectedStringToken("(")) { + return false; + } + this->parseWSToken(); + + if (!f(result)) { + return false; + } + + this->parseWSToken(); + if (!this->parseExpectedStringToken(")")) { + return false; + } + + restoreCurPos.clear(); + return true; +} + +bool SVGAttributeParser::parseMatrixToken(Matrix* matrix) { + return this->parseParenthesized( + "matrix", + [this](Matrix* m) -> bool { + float scalars[6]; + for (int i = 0; i < 6; ++i) { + if (!this->parseScalarToken(scalars + i) || (i <= 4 && !this->parseSepToken())) { + return false; + } + } + + m->setAll(scalars[0], scalars[2], scalars[4], scalars[1], scalars[3], scalars[5]); + return true; + }, + matrix); +} + +bool SVGAttributeParser::parseTranslateToken(Matrix* matrix) { + return this->parseParenthesized( + "translate", + [this](Matrix* m) -> bool { + float tx = 0.0; + float ty = 0.0; + this->parseWSToken(); + if (!this->parseScalarToken(&tx)) { + return false; + } + + if (!this->parseSepToken() || !this->parseScalarToken(&ty)) { + ty = 0.0; + } + + m->setTranslate(tx, ty); + return true; + }, + matrix); +} + +bool SVGAttributeParser::parseScaleToken(Matrix* matrix) { + return this->parseParenthesized( + "scale", + [this](Matrix* m) -> bool { + float sx = 0.0; + float sy = 0.0; + if (!this->parseScalarToken(&sx)) { + return false; + } + + if (!(this->parseSepToken() && this->parseScalarToken(&sy))) { + sy = sx; + } + + m->setScale(sx, sy); + return true; + }, + matrix); +} + +bool SVGAttributeParser::parseRotateToken(Matrix* matrix) { + return this->parseParenthesized( + "rotate", + [this](Matrix* m) -> bool { + float angle; + if (!this->parseScalarToken(&angle)) { + return false; + } + + float cx = 0; + float cy = 0; + // optional [ ] + if (this->parseSepToken() && this->parseScalarToken(&cx)) { + if (!(this->parseSepToken() && this->parseScalarToken(&cy))) { + return false; + } + } + + m->setRotate(angle, cx, cy); + return true; + }, + matrix); +} + +bool SVGAttributeParser::parseSkewXToken(Matrix* matrix) { + return this->parseParenthesized( + "skewX", + [this](Matrix* m) -> bool { + float angle; + if (!this->parseScalarToken(&angle)) { + return false; + } + m->setSkewX(tanf(DegreesToRadians(angle))); + return true; + }, + matrix); +} + +bool SVGAttributeParser::parseSkewYToken(Matrix* matrix) { + return this->parseParenthesized( + "skewY", + [this](Matrix* m) -> bool { + float angle; + if (!this->parseScalarToken(&angle)) { + return false; + } + m->setSkewY(tanf(DegreesToRadians(angle))); + return true; + }, + matrix); +} + +// https://www.w3.org/TR/SVG11/coords.html#TransformAttribute +template <> +bool SVGAttributeParser::parse(SVGTransformType* t) { + Matrix matrix = Matrix::I(); + + bool parsed = false; + while (true) { + Matrix m; + + if (!(this->parseMatrixToken(&m) || this->parseTranslateToken(&m) || + this->parseScaleToken(&m) || this->parseRotateToken(&m) || this->parseSkewXToken(&m) || + this->parseSkewYToken(&m))) { + break; + } + + matrix.preConcat(m); + parsed = true; + + this->parseCommaWspToken(); + } + + this->parseWSToken(); + if (!parsed || !this->parseEOSToken()) { + return false; + } + + *t = SVGTransformType(matrix); + return true; +} + +// https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint +template <> +bool SVGAttributeParser::parse(SVGPaint* paint) { + SVGColor c; + SVGFuncIRI iri; + bool parsedValue = false; + + this->parseWSToken(); + if (this->parseSVGColor(&c, SVGColor::Vars())) { + *paint = SVGPaint(std::move(c)); + parsedValue = true; + } else if (this->parseExpectedStringToken("none")) { + *paint = SVGPaint(SVGPaint::Type::None); + parsedValue = true; + } else if (this->parseFuncIRI(&iri)) { + // optional fallback color + this->parseWSToken(); + this->parseSVGColor(&c, SVGColor::Vars()); + *paint = SVGPaint(iri.iri(), std::move(c)); + parsedValue = true; + } + this->parseWSToken(); + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/masking.html#ClipPathProperty +// https://www.w3.org/TR/SVG11/masking.html#MaskProperty +// https://www.w3.org/TR/SVG11/filters.html#FilterProperty +template <> +bool SVGAttributeParser::parse(SVGFuncIRI* funcIRI) { + SVGStringType iri; + bool parsedValue = false; + + if (this->parseExpectedStringToken("none")) { + *funcIRI = SVGFuncIRI(); + parsedValue = true; + } else if (this->parseFuncIRI(funcIRI)) { + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty +template <> +bool SVGAttributeParser::parse(SVGLineCap* cap) { + static const struct { + SVGLineCap fType; + const char* fName; + } gCapInfo[] = { + {SVGLineCap::Butt, "butt"}, + {SVGLineCap::Round, "round"}, + {SVGLineCap::Square, "square"}, + }; + + bool parsedValue = false; + for (auto i : gCapInfo) { + if (this->parseExpectedStringToken(i.fName)) { + *cap = (i.fType); + parsedValue = true; + break; + } + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty +template <> +bool SVGAttributeParser::parse(SVGLineJoin* join) { + struct JoinInfo { + SVGLineJoin::Type type; + const char* name; + }; + static const std::vector joinInfoMap = { + {SVGLineJoin::Type::Miter, "miter"}, + {SVGLineJoin::Type::Round, "round"}, + {SVGLineJoin::Type::Bevel, "bevel"}, + {SVGLineJoin::Type::Inherit, "inherit"}, + }; + + bool parsedValue = false; + for (auto i : joinInfoMap) { + if (this->parseExpectedStringToken(i.name)) { + *join = SVGLineJoin(i.type); + parsedValue = true; + break; + } + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits +template <> +bool SVGAttributeParser::parse(SVGObjectBoundingBoxUnits* objectBoundingBoxUnits) { + bool parsedValue = false; + if (this->parseExpectedStringToken("userSpaceOnUse")) { + *objectBoundingBoxUnits = + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse); + parsedValue = true; + } else if (this->parseExpectedStringToken("objectBoundingBox")) { + *objectBoundingBoxUnits = + SVGObjectBoundingBoxUnits(SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox); + parsedValue = true; + } + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/shapes.html#PolygonElementPointsAttribute +template <> +bool SVGAttributeParser::parse(SVGPointsType* points) { + SVGPointsType pts; + + // Skip initial wsp. + // list-of-points: + // wsp* coordinate-pairs? wsp* + this->advanceWhile(is_ws); + + bool parsedValue = false; + for (;;) { + // Adjacent coordinate-pairs separated by comma-wsp. + // coordinate-pairs: + // coordinate-pair + // | coordinate-pair comma-wsp coordinate-pairs + if (parsedValue && !this->parseCommaWspToken()) { + break; + } + + float x; + float y; + if (!this->parseScalarToken(&x)) { + break; + } + + // Coordinate values separated by comma-wsp or '-'. + // coordinate-pair: + // coordinate comma-wsp coordinate + // | coordinate negative-coordinate + if (!this->parseCommaWspToken() && !this->parseEOSToken() && *currentPos != '-') { + break; + } + + if (!this->parseScalarToken(&y)) { + break; + } + + pts.push_back(Point::Make(x, y)); + parsedValue = true; + } + + if (parsedValue && this->parseEOSToken()) { + *points = std::move(pts); + return true; + } + + return false; +} + +// https://www.w3.org/TR/SVG11/painting.html#FillRuleProperty +template <> +bool SVGAttributeParser::parse(SVGFillRule* fillRule) { + static const struct { + SVGFillRule::Type fType; + const char* fName; + } gFillRuleInfo[] = { + {SVGFillRule::Type::NonZero, "nonzero"}, + {SVGFillRule::Type::EvenOdd, "evenodd"}, + {SVGFillRule::Type::Inherit, "inherit"}, + }; + + bool parsedValue = false; + for (auto i : gFillRuleInfo) { + if (this->parseExpectedStringToken(i.fName)) { + *fillRule = SVGFillRule(i.fType); + parsedValue = true; + break; + } + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/painting.html#VisibilityProperty +template <> +bool SVGAttributeParser::parse(SVGVisibility* visibility) { + static const struct { + SVGVisibility::Type fType; + const char* fName; + } gVisibilityInfo[] = { + {SVGVisibility::Type::Visible, "visible"}, + {SVGVisibility::Type::Hidden, "hidden"}, + {SVGVisibility::Type::Collapse, "collapse"}, + {SVGVisibility::Type::Inherit, "inherit"}, + }; + + bool parsedValue = false; + for (const auto& parseInfo : gVisibilityInfo) { + if (this->parseExpectedStringToken(parseInfo.fName)) { + *visibility = SVGVisibility(parseInfo.fType); + parsedValue = true; + break; + } + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty +template <> +bool SVGAttributeParser::parse(SVGDashArray* dashArray) { + bool parsedValue = false; + if (this->parseExpectedStringToken("none")) { + *dashArray = SVGDashArray(SVGDashArray::Type::None); + parsedValue = true; + } else if (this->parseExpectedStringToken("inherit")) { + *dashArray = SVGDashArray(SVGDashArray::Type::Inherit); + parsedValue = true; + } else { + std::vector dashes; + for (;;) { + SVGLength dash; + // parseLength() also consumes trailing separators. + if (!this->parse(&dash)) { + break; + } + + dashes.push_back(dash); + parsedValue = true; + } + + if (parsedValue) { + *dashArray = SVGDashArray(std::move(dashes)); + } + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/text.html#FontFamilyProperty +template <> +bool SVGAttributeParser::parse(SVGFontFamily* family) { + bool parsedValue = false; + if (this->parseExpectedStringToken("inherit")) { + *family = SVGFontFamily(); + parsedValue = true; + } else { + // The spec allows specifying a comma-separated list for explicit fallback order. + // For now, we only use the first entry and rely on the font manager to handle fallback. + const auto* comma = strchr(currentPos, ','); + auto family_name = comma ? std::string(currentPos, static_cast(comma - currentPos)) + : std::string(currentPos); + *family = SVGFontFamily(family_name); + currentPos += strlen(currentPos); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/text.html#FontSizeProperty +template <> +bool SVGAttributeParser::parse(SVGFontSize* size) { + bool parsedValue = false; + if (this->parseExpectedStringToken("inherit")) { + *size = SVGFontSize(); + parsedValue = true; + } else { + SVGLength length; + if (this->parse(&length)) { + *size = SVGFontSize(length); + parsedValue = true; + } + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/text.html#FontStyleProperty +template <> +bool SVGAttributeParser::parse(SVGFontStyle* style) { + static constexpr std::tuple styleMap[] = { + {"normal", SVGFontStyle::Type::Normal}, + {"italic", SVGFontStyle::Type::Italic}, + {"oblique", SVGFontStyle::Type::Oblique}, + {"inherit", SVGFontStyle::Type::Inherit}, + }; + + bool parsedValue = false; + SVGFontStyle::Type type; + + if (this->parseEnumMap(styleMap, &type)) { + *style = SVGFontStyle(type); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/text.html#FontWeightProperty +template <> +bool SVGAttributeParser::parse(SVGFontWeight* weight) { + static constexpr std::tuple weightMap[] = { + {"normal", SVGFontWeight::Type::Normal}, {"bold", SVGFontWeight::Type::Bold}, + {"bolder", SVGFontWeight::Type::Bolder}, {"lighter", SVGFontWeight::Type::Lighter}, + {"100", SVGFontWeight::Type::W100}, {"200", SVGFontWeight::Type::W200}, + {"300", SVGFontWeight::Type::W300}, {"400", SVGFontWeight::Type::W400}, + {"500", SVGFontWeight::Type::W500}, {"600", SVGFontWeight::Type::W600}, + {"700", SVGFontWeight::Type::W700}, {"800", SVGFontWeight::Type::W800}, + {"900", SVGFontWeight::Type::W900}, {"inherit", SVGFontWeight::Type::Inherit}, + }; + + bool parsedValue = false; + SVGFontWeight::Type type; + + if (this->parseEnumMap(weightMap, &type)) { + *weight = SVGFontWeight(type); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/text.html#TextAnchorProperty +template <> +bool SVGAttributeParser::parse(SVGTextAnchor* anchor) { + static constexpr std::tuple anchorMap[] = { + {"start", SVGTextAnchor::Type::Start}, + {"middle", SVGTextAnchor::Type::Middle}, + {"end", SVGTextAnchor::Type::End}, + {"inherit", SVGTextAnchor::Type::Inherit}, + }; + + bool parsedValue = false; + SVGTextAnchor::Type type; + + if (this->parseEnumMap(anchorMap, &type)) { + *anchor = SVGTextAnchor(type); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute +bool SVGAttributeParser::parsePreserveAspectRatio(SVGPreserveAspectRatio* par) { + static constexpr std::tuple alignMap[] = { + {"none", SVGPreserveAspectRatio::Align::None}, + {"xMinYMin", SVGPreserveAspectRatio::Align::XMinYMin}, + {"xMidYMin", SVGPreserveAspectRatio::Align::XMidYMin}, + {"xMaxYMin", SVGPreserveAspectRatio::Align::XMaxYMin}, + {"xMinYMid", SVGPreserveAspectRatio::Align::XMinYMid}, + {"xMidYMid", SVGPreserveAspectRatio::Align::XMidYMid}, + {"xMaxYMid", SVGPreserveAspectRatio::Align::XMaxYMid}, + {"xMinYMax", SVGPreserveAspectRatio::Align::XMinYMax}, + {"xMidYMax", SVGPreserveAspectRatio::Align::XMidYMax}, + {"xMaxYMax", SVGPreserveAspectRatio::Align::XMaxYMax}, + }; + + static constexpr std::tuple scaleMap[] = { + {"meet", SVGPreserveAspectRatio::Scale::Meet}, + {"slice", SVGPreserveAspectRatio::Scale::Slice}, + }; + + bool parsedValue = false; + + // ignoring optional 'defer' + this->parseExpectedStringToken("defer"); + this->parseWSToken(); + + if (this->parseEnumMap(alignMap, &par->align)) { + parsedValue = true; + + // optional scaling selector + this->parseWSToken(); + this->parseEnumMap(scaleMap, &par->scale); + } + + return parsedValue && this->parseEOSToken(); +} + +template <> +bool SVGAttributeParser::parse(SVGPreserveAspectRatio* par) { + return this->parsePreserveAspectRatio(par); +} + +// https://www.w3.org/TR/SVG11/types.html#DataTypeCoordinates +template +bool SVGAttributeParser::parseList(std::vector* vals) { + ASSERT(vals->empty()); + + T v; + for (;;) { + if (!this->parse(&v)) { + break; + } + + vals->push_back(v); + + this->parseCommaWspToken(); + } + + return !vals->empty() && this->parseEOSToken(); +} + +template <> +bool SVGAttributeParser::parse(std::vector* lengths) { + return this->parseList(lengths); +} + +template <> +bool SVGAttributeParser::parse(std::vector* numbers) { + return this->parseList(numbers); +} + +template <> +bool SVGAttributeParser::parse(SVGColorspace* colorspace) { + static constexpr std::tuple colorspaceMap[] = { + {"auto", SVGColorspace::Auto}, + {"sRGB", SVGColorspace::SRGB}, + {"linearRGB", SVGColorspace::LinearRGB}, + }; + + return this->parseEnumMap(colorspaceMap, colorspace) && this->parseEOSToken(); +} + +// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty +template <> +bool SVGAttributeParser::parse(SVGDisplay* display) { + static const struct { + SVGDisplay fType; + const char* fName; + } gDisplayInfo[] = { + {SVGDisplay::Inline, "inline"}, + {SVGDisplay::None, "none"}, + }; + + bool parsedValue = false; + for (const auto& parseInfo : gDisplayInfo) { + if (this->parseExpectedStringToken(parseInfo.fName)) { + *display = parseInfo.fType; + parsedValue = true; + break; + } + } + + return parsedValue && this->parseEOSToken(); +} + +template <> +bool SVGAttributeParser::parse(SVGMaskType* maskType) { + static constexpr std::tuple typeMap[] = { + {"luminance", SVGMaskType::Type::Luminance}, + {"alpha", SVGMaskType::Type::Alpha}, + }; + + bool parsedValue = false; + SVGMaskType::Type type; + + if (this->parseEnumMap(typeMap, &type)) { + *maskType = SVGMaskType(type); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGAttributeParser.h b/src/svg/SVGAttributeParser.h new file mode 100644 index 00000000..9e7a81ca --- /dev/null +++ b/src/svg/SVGAttributeParser.h @@ -0,0 +1,181 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include "tgfx/core/Color.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Typeface.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGAttributeParser { + public: + explicit SVGAttributeParser(std::string); + + bool parseInteger(SVGIntegerType*); + bool parseViewBox(SVGViewBoxType*); + bool parsePreserveAspectRatio(SVGPreserveAspectRatio*); + + bool parse(SVGIntegerType* v) { + return parseInteger(v); + } + + template + using ParseResult = std::optional; + + template + static ParseResult parse(const std::string& value) { + ParseResult result; + T parsedValue; + if (SVGAttributeParser(value).parse(&parsedValue)) { + result = parsedValue; + } + return result; + } + + template + static ParseResult parse(const std::string& expectedName, const std::string& name, + const std::string& value) { + if (name == expectedName) { + return parse(value); + } + + return ParseResult(); + } + + template + static ParseResult parseProperty(const std::string& expectedName, + const std::string& name, const std::string& value) { + if (name != expectedName) { + return ParseResult(); + } + + if (value == "inherit") { + PropertyT result(SVGPropertyState::Inherit); + return ParseResult(result); + } + + auto pr = parse(value); + if (pr.has_value()) { + PropertyT result(*pr); + return ParseResult(result); + } + + return ParseResult(); + } + + // Stack-only + void* operator new(size_t) = delete; + void* operator new(size_t, void*) = delete; + + private: + class RestoreCurPos { + public: + explicit RestoreCurPos(SVGAttributeParser* self) : self(self), currentPos(self->currentPos) { + } + + ~RestoreCurPos() { + if (self) { + self->currentPos = this->currentPos; + } + } + + void clear() { + self = nullptr; + } + + private: + SVGAttributeParser* self; + const char* currentPos; + + public: + RestoreCurPos(const RestoreCurPos&) = delete; + RestoreCurPos& operator=(const RestoreCurPos&) = delete; + }; + + template + bool parse(T*); + + template + bool advanceWhile(F func); + + bool matchStringToken(const char* token, const char** newPos = nullptr) const; + bool matchHexToken(const char** newPos) const; + + bool parseWSToken(); + bool parseEOSToken(); + bool parseSepToken(); + bool parseCommaWspToken(); + bool parseExpectedStringToken(const char*); + bool parseScalarToken(float*); + bool parseInt32Token(int32_t*); + bool parseEscape(Unichar*); + bool parseIdentToken(std::string*); + bool parseLengthUnitToken(SVGLength::Unit&); + bool parseNamedColorToken(Color*); + bool parseHexColorToken(Color*); + bool parseColorComponentScalarToken(int32_t*); + bool parseColorComponentIntegralToken(int32_t*); + bool parseColorComponentFractionalToken(int32_t*); + bool parseColorComponentToken(int32_t*); + bool parseColorToken(Color*); + bool parseRGBColorToken(Color*); + bool parseRGBAColorToken(Color*); + bool parseSVGColor(SVGColor*, SVGColor::Vars&&); + bool parseSVGColorType(SVGColorType*); + bool parseFuncIRI(SVGFuncIRI*); + + // Transform helpers + bool parseMatrixToken(Matrix*); + bool parseTranslateToken(Matrix*); + bool parseScaleToken(Matrix*); + bool parseRotateToken(Matrix*); + bool parseSkewXToken(Matrix*); + bool parseSkewYToken(Matrix*); + + // Parses a sequence of 'WS* WS* ()', where the nested sequence + // is handled by the passed functor. + template + bool parseParenthesized(const char* prefix, Func, T* result); + + template + bool parseList(std::vector*); + + template + bool parseEnumMap(const TArray& arr, T* result) { + for (size_t i = 0; i < std::size(arr); ++i) { + if (this->parseExpectedStringToken(std::get<0>(arr[i]))) { + *result = std::get<1>(arr[i]); + return true; + } + } + return false; + } + + // The current position in the input string. + const char* currentPos; + const char* endPos; +}; +} // namespace tgfx diff --git a/src/svg/SVGDOM.cpp b/src/svg/SVGDOM.cpp new file mode 100644 index 00000000..51d40423 --- /dev/null +++ b/src/svg/SVGDOM.cpp @@ -0,0 +1,119 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/SVGDOM.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGLengthContext.h" +#include "svg/SVGNodeConstructor.h" +#include "svg/SVGRenderContext.h" +#include "svg/SystemFontManager.h" +#include "svg/SystemResourceLoader.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Data.h" +#include "tgfx/core/Recorder.h" +#include "tgfx/core/Size.h" +#include "tgfx/core/Surface.h" +#include "tgfx/svg/SVGAttribute.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/SVGValue.h" +#include "tgfx/svg/node/SVGContainer.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/xml/XMLDOM.h" + +namespace tgfx { + +std::shared_ptr SVGDOM::Make(Stream& stream, SVGDOMOptions options) { + // Parse the data into an XML DOM structure + auto xmlDom = DOM::Make(stream); + if (!xmlDom) { + return nullptr; + } + + // Convert the XML structure to an SVG structure, translating XML elements and attributes into + // SVG elements and attributes + SVGIDMapper mapper; + ConstructionContext constructionContext(&mapper); + auto root = + SVGNodeConstructor::ConstructSVGNode(constructionContext, xmlDom->getRootNode().get()); + if (!root || root->tag() != SVGTag::Svg) { + return nullptr; + } + + // Create SVGDOM with the root node and ID mapper + return std::shared_ptr( + new SVGDOM(std::static_pointer_cast(root), std::move(options), std::move(mapper))); +} + +SVGDOM::SVGDOM(std::shared_ptr root, SVGDOMOptions options, SVGIDMapper&& mapper) + : root(std::move(root)), nodeIDMapper(std::move(mapper)), options(std::move(options)) { +} + +const std::shared_ptr& SVGDOM::getRoot() const { + return root; +} + +void SVGDOM::render(Canvas* canvas) { + // If the container size is not set, use the size of the root SVG element. + auto drawSize = containerSize; + if (drawSize.isEmpty()) { + auto rootWidth = root->getWidth(); + auto rootHeight = root->getHeight(); + if (root->getViewBox().has_value()) { + SVGLengthContext viewportLengthContext(root->getViewBox()->size()); + drawSize = Size::Make( + viewportLengthContext.resolve(rootWidth, SVGLengthContext::LengthType::Horizontal), + viewportLengthContext.resolve(rootHeight, SVGLengthContext::LengthType::Vertical)); + } else { + drawSize = Size::Make(rootWidth.value(), rootHeight.value()); + } + } + if (!canvas || !root || drawSize.isEmpty()) { + return; + } + + SVGLengthContext lengthContext(drawSize); + SVGPresentationContext presentationContext; + + auto resourceLoader = + options.resourceProvider ? options.resourceProvider : SystemResourceLoader::Make(); + auto fontManager = options.fontManager ? options.fontManager : SystemFontManager::Make(); + + SVGRenderContext renderContext(canvas, fontManager, resourceLoader, nodeIDMapper, lengthContext, + presentationContext, {nullptr, nullptr}, canvas->getMatrix()); + + root->render(renderContext); +} + +const Size& SVGDOM::getContainerSize() const { + return containerSize; +} + +void SVGDOM::setContainerSize(const Size& size) { + containerSize = size; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGExportContext.cpp b/src/svg/SVGExportContext.cpp index 51ce7f68..198a32eb 100644 --- a/src/svg/SVGExportContext.cpp +++ b/src/svg/SVGExportContext.cpp @@ -17,7 +17,6 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "SVGExportContext.h" -#include <_types/_uint32_t.h> #include #include #include @@ -380,6 +379,14 @@ Bitmap SVGExportContext::ImageExportToBitmap(Context* context, auto surface = Surface::Make(context, image->width(), image->height()); auto* canvas = surface->getCanvas(); canvas->drawImage(image); + + Bitmap bitmap(surface->width(), surface->height()); + auto* pixels = bitmap.lockPixels(); + if (surface->readPixels(bitmap.info(), pixels)) { + bitmap.unlockPixels(); + return bitmap; + } + bitmap.unlockPixels(); return Bitmap(); } diff --git a/src/svg/SVGExportContext.h b/src/svg/SVGExportContext.h index d8f1fdb7..d35ee7b3 100644 --- a/src/svg/SVGExportContext.h +++ b/src/svg/SVGExportContext.h @@ -18,7 +18,6 @@ #pragma once -#include <_types/_uint32_t.h> #include #include #include diff --git a/src/svg/SVGFilterContext.cpp b/src/svg/SVGFilterContext.cpp new file mode 100644 index 00000000..afb3d0fc --- /dev/null +++ b/src/svg/SVGFilterContext.cpp @@ -0,0 +1,132 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SVGFilterContext.h" +#include +#include "core/utils/Log.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/BlendMode.h" +#include "tgfx/core/ColorFilter.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +const SVGFilterContext::Result* SVGFilterContext::findResultById(const SVGStringType& ID) const { + auto iter = results.find(ID); + return iter != results.end() ? &iter->second : nullptr; +} + +const Rect& SVGFilterContext::filterPrimitiveSubregion(const SVGFeInputType& input) const { + const Result* res = nullptr; + if (input.type() == SVGFeInputType::Type::FilterPrimitiveReference) { + auto iter = results.find(input.id()); + res = iter != results.end() ? &iter->second : nullptr; + } else if (input.type() == SVGFeInputType::Type::Unspecified) { + res = &previousResult; + } + return res ? res->filterSubregion : _filterEffectsRegion; +} + +void SVGFilterContext::registerResult(const SVGStringType& ID, + const std::shared_ptr& result, + const Rect& subregion, SVGColorspace resultColorspace) { + ASSERT(!ID.empty()); + results[ID] = {result, subregion, resultColorspace}; +} + +void SVGFilterContext::setPreviousResult(const std::shared_ptr& result, + const Rect& subregion, SVGColorspace resultColorspace) { + previousResult = {result, subregion, resultColorspace}; +} + +bool SVGFilterContext::previousResultIsSourceGraphic() const { + return previousResult.imageFilter == nullptr; +} + +// https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute +std::tuple, SVGColorspace> SVGFilterContext::getInput( + const SVGRenderContext& context, const SVGFeInputType& inputType) const { + SVGColorspace inputCS = SVGColorspace::SRGB; + std::shared_ptr result; + switch (inputType.type()) { + case SVGFeInputType::Type::SourceAlpha: { + std::array colorMatrix{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}; + auto colorFilter = ColorFilter::Matrix(colorMatrix); + result = ImageFilter::ColorFilter(colorFilter); + break; + } + case SVGFeInputType::Type::SourceGraphic: + // Do nothing. + break; + case SVGFeInputType::Type::FillPaint: { + const auto& fillPaint = context.fillPaint(); + if (fillPaint.has_value()) { + auto colorFilter = ColorFilter::Blend(fillPaint->getColor(), BlendMode::DstIn); + result = ImageFilter::ColorFilter(colorFilter); + } + break; + } + case SVGFeInputType::Type::StrokePaint: { + const auto& strokePaint = context.strokePaint(); + if (strokePaint.has_value()) { + auto colorFilter = ColorFilter::Blend(strokePaint->getColor(), BlendMode::DstIn); + result = ImageFilter::ColorFilter(colorFilter); + } + break; + } + case SVGFeInputType::Type::FilterPrimitiveReference: { + const Result* res = findResultById(inputType.id()); + if (res) { + result = res->imageFilter; + inputCS = res->colorspace; + } + break; + } + case SVGFeInputType::Type::Unspecified: { + result = previousResult.imageFilter; + inputCS = previousResult.colorspace; + break; + } + default: + break; + } + + return {result, inputCS}; +} + +SVGColorspace SVGFilterContext::resolveInputColorspace(const SVGRenderContext& context, + const SVGFeInputType& inputType) const { + return std::get<1>(this->getInput(context, inputType)); +} + +std::shared_ptr SVGFilterContext::resolveInput(const SVGRenderContext& context, + const SVGFeInputType& inputType) const { + return std::get<0>(this->getInput(context, inputType)); +} + +std::shared_ptr SVGFilterContext::resolveInput( + const SVGRenderContext& context, const SVGFeInputType& inputType, + SVGColorspace /*resultColorspace*/) const { + //TODO (YGAurora) - waiting for color space implementation + return resolveInput(context, inputType); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGFilterContext.h b/src/svg/SVGFilterContext.h new file mode 100644 index 00000000..7350f4ee --- /dev/null +++ b/src/svg/SVGFilterContext.h @@ -0,0 +1,86 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include "svg/SVGRenderContext.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGFilterContext { + public: + SVGFilterContext(const Rect& filterEffectsRegion, const SVGObjectBoundingBoxUnits& primitiveUnits) + : _filterEffectsRegion(filterEffectsRegion), _primitiveUnits(primitiveUnits), + previousResult({nullptr, filterEffectsRegion, SVGColorspace::SRGB}) { + } + + const Rect& filterEffectsRegion() const { + return _filterEffectsRegion; + } + + const Rect& filterPrimitiveSubregion(const SVGFeInputType& input) const; + + const SVGObjectBoundingBoxUnits& primitiveUnits() const { + return _primitiveUnits; + } + + void registerResult(const SVGStringType& ID, const std::shared_ptr& result, + const Rect& subregion, SVGColorspace resultColorspace); + + void setPreviousResult(const std::shared_ptr& result, const Rect& subregion, + SVGColorspace resultColorspace); + + bool previousResultIsSourceGraphic() const; + + SVGColorspace resolveInputColorspace(const SVGRenderContext& context, + const SVGFeInputType& inputType) const; + + std::shared_ptr resolveInput(const SVGRenderContext& context, + const SVGFeInputType& inputType) const; + + std::shared_ptr resolveInput(const SVGRenderContext& context, + const SVGFeInputType& inputType, + SVGColorspace resultColorspace) const; + + private: + struct Result { + std::shared_ptr imageFilter; + Rect filterSubregion; + SVGColorspace colorspace; + }; + + const Result* findResultById(const SVGStringType& ID) const; + + std::tuple, SVGColorspace> getInput( + const SVGRenderContext& context, const SVGFeInputType& inputType) const; + + Rect _filterEffectsRegion; + + SVGObjectBoundingBoxUnits _primitiveUnits; + + std::unordered_map results; + + Result previousResult; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGLengthContext.cpp b/src/svg/SVGLengthContext.cpp new file mode 100644 index 00000000..c7d0d42a --- /dev/null +++ b/src/svg/SVGLengthContext.cpp @@ -0,0 +1,124 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SVGLengthContext.h" +#include "core/utils/Log.h" + +namespace tgfx { + +namespace { + +float length_size_for_type(const Size& viewport, SVGLengthContext::LengthType t) { + switch (t) { + case SVGLengthContext::LengthType::Horizontal: + return viewport.width; + case SVGLengthContext::LengthType::Vertical: + return viewport.height; + case SVGLengthContext::LengthType::Other: { + // https://www.w3.org/TR/SVG11/coords.html#Units_viewport_percentage + const float rsqrt2 = 1.0f / std::sqrt(2.0f); + const float w = viewport.width; + const float h = viewport.height; + return rsqrt2 * std::sqrt(w * w + h * h); + } + } + ASSERT(false); // Not reached. + return 0; +} + +// Multipliers for DPI-relative units. +constexpr float INMultiplier = 1.00f; +constexpr float PTMultiplier = INMultiplier / 72.272f; +constexpr float PCMultiplier = PTMultiplier * 12; +constexpr float MMMultiplier = INMultiplier / 25.4f; +constexpr float CMMultiplier = MMMultiplier * 10; + +} // namespace + +float SVGLengthContext::resolve(const SVGLength& length, LengthType type) const { + switch (length.unit()) { + case SVGLength::Unit::Number: { + if (patternUnit.has_value()) { + if (patternUnit.value().type() == SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox) { + return length.value() * length_size_for_type(_viewPort, type); + } else { + return length.value(); + } + } else { + return length.value(); + } + } + case SVGLength::Unit::PX: + return length.value(); + case SVGLength::Unit::Percentage: + return length.value() * length_size_for_type(_viewPort, type) / 100; + case SVGLength::Unit::CM: + return length.value() * dpi * CMMultiplier; + case SVGLength::Unit::MM: + return length.value() * dpi * MMMultiplier; + case SVGLength::Unit::IN: + return length.value() * dpi * INMultiplier; + case SVGLength::Unit::PT: + return length.value() * dpi * PTMultiplier; + case SVGLength::Unit::PC: + return length.value() * dpi * PCMultiplier; + default: + //unsupported unit type + ASSERT(false); + return 0; + } +} + +Rect SVGLengthContext::resolveRect(const SVGLength& x, const SVGLength& y, const SVGLength& width, + const SVGLength& height) const { + return Rect::MakeXYWH(this->resolve(x, SVGLengthContext::LengthType::Horizontal), + this->resolve(y, SVGLengthContext::LengthType::Vertical), + this->resolve(width, SVGLengthContext::LengthType::Horizontal), + this->resolve(height, SVGLengthContext::LengthType::Vertical)); +} + +std::tuple SVGLengthContext::resolveOptionalRadii( + const std::optional& optionalRx, const std::optional& optionalRy) const { + // https://www.w3.org/TR/SVG2/shapes.html#RectElement + // + // The used values for rx and ry are determined from the computed values by following these + // steps in order: + // + // 1. If both rx and ry have a computed value of auto (since auto is the initial value for both + // properties, this will also occur if neither are specified by the author or if all + // author-supplied values are invalid), then the used value of both rx and ry is 0. + // (This will result in square corners.) + // 2. Otherwise, convert specified values to absolute values as follows: + // 1. If rx is set to a length value or a percentage, but ry is auto, calculate an absolute + // length equivalent for rx, resolving percentages against the used width of the + // rectangle; the absolute value for ry is the same. + // 2. If ry is set to a length value or a percentage, but rx is auto, calculate the absolute + // length equivalent for ry, resolving percentages against the used height of the + // rectangle; the absolute value for rx is the same. + // 3. If both rx and ry were set to lengths or percentages, absolute values are generated + // individually, resolving rx percentages against the used width, and resolving ry + // percentages against the used height. + const float rx = + optionalRx.has_value() ? resolve(*optionalRx, SVGLengthContext::LengthType::Horizontal) : 0; + const float ry = + optionalRy.has_value() ? resolve(*optionalRy, SVGLengthContext::LengthType::Vertical) : 0; + + return {optionalRx.has_value() ? rx : ry, optionalRy.has_value() ? ry : rx}; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGLengthContext.h b/src/svg/SVGLengthContext.h new file mode 100644 index 00000000..358a64f1 --- /dev/null +++ b/src/svg/SVGLengthContext.h @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/core/Size.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGLengthContext { + public: + explicit SVGLengthContext(const Size& viewport, float dpi = 90) : _viewPort(viewport), dpi(dpi) { + } + + enum class LengthType { + Horizontal, + Vertical, + Other, + }; + + const Size& viewPort() const { + return _viewPort; + } + void setViewPort(const Size& viewPort) { + _viewPort = viewPort; + } + + float resolve(const SVGLength&, LengthType) const; + + Rect resolveRect(const SVGLength& x, const SVGLength& y, const SVGLength& w, + const SVGLength& h) const; + + std::tuple resolveOptionalRadii(const std::optional& optionalRx, + const std::optional& optionalRy) const; + + void setPatternUnits(SVGObjectBoundingBoxUnits unit) { + patternUnit = unit; + } + + void clearPatternUnits() { + patternUnit.reset(); + } + + std::optional getPatternUnits() const { + return patternUnit; + } + + private: + Size _viewPort; + float dpi; + std::optional patternUnit; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGNodeConstructor.cpp b/src/svg/SVGNodeConstructor.cpp new file mode 100644 index 00000000..bfc5228f --- /dev/null +++ b/src/svg/SVGNodeConstructor.cpp @@ -0,0 +1,363 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SVGNodeConstructor.h" +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGUtils.h" +#include "tgfx/svg/node/SVGCircle.h" +#include "tgfx/svg/node/SVGClipPath.h" +#include "tgfx/svg/node/SVGDefs.h" +#include "tgfx/svg/node/SVGEllipse.h" +#include "tgfx/svg/node/SVGFeBlend.h" +#include "tgfx/svg/node/SVGFeColorMatrix.h" +#include "tgfx/svg/node/SVGFeComponentTransfer.h" +#include "tgfx/svg/node/SVGFeComposite.h" +#include "tgfx/svg/node/SVGFeDisplacementMap.h" +#include "tgfx/svg/node/SVGFeFlood.h" +#include "tgfx/svg/node/SVGFeGaussianBlur.h" +#include "tgfx/svg/node/SVGFeImage.h" +#include "tgfx/svg/node/SVGFeLightSource.h" +#include "tgfx/svg/node/SVGFeLighting.h" +#include "tgfx/svg/node/SVGFeMerge.h" +#include "tgfx/svg/node/SVGFeMorphology.h" +#include "tgfx/svg/node/SVGFeOffset.h" +#include "tgfx/svg/node/SVGFeTurbulence.h" +#include "tgfx/svg/node/SVGFilter.h" +#include "tgfx/svg/node/SVGGroup.h" +#include "tgfx/svg/node/SVGImage.h" +#include "tgfx/svg/node/SVGLine.h" +#include "tgfx/svg/node/SVGLinearGradient.h" +#include "tgfx/svg/node/SVGMask.h" +#include "tgfx/svg/node/SVGNode.h" +#include "tgfx/svg/node/SVGPath.h" +#include "tgfx/svg/node/SVGPattern.h" +#include "tgfx/svg/node/SVGPoly.h" +#include "tgfx/svg/node/SVGRadialGradient.h" +#include "tgfx/svg/node/SVGRect.h" +#include "tgfx/svg/node/SVGRoot.h" +#include "tgfx/svg/node/SVGStop.h" +#include "tgfx/svg/node/SVGText.h" +#include "tgfx/svg/node/SVGUse.h" + +namespace tgfx { + +bool SVGNodeConstructor::SetIRIAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + auto parseResult = SVGAttributeParser::parse(stringValue); + if (!parseResult.has_value()) { + return false; + } + + node.setAttribute(attr, SVGStringValue(parseResult->iri())); + return true; +} + +bool SVGNodeConstructor::SetStringAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + SVGStringType strType = SVGStringType(stringValue); + node.setAttribute(attr, SVGStringValue(strType)); + return true; +} + +bool SVGNodeConstructor::SetTransformAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + auto parseResult = SVGAttributeParser::parse(stringValue); + if (!parseResult.has_value()) { + return false; + } + + node.setAttribute(attr, SVGTransformValue(*parseResult)); + return true; +} + +bool SVGNodeConstructor::SetLengthAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + auto parseResult = SVGAttributeParser::parse(stringValue); + if (!parseResult.has_value()) { + return false; + } + + node.setAttribute(attr, SVGLengthValue(*parseResult)); + return true; +} + +bool SVGNodeConstructor::SetViewBoxAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + SVGViewBoxType viewBox; + SVGAttributeParser parser(stringValue); + if (!parser.parseViewBox(&viewBox)) { + return false; + } + + node.setAttribute(attr, SVGViewBoxValue(viewBox)); + return true; +} + +bool SVGNodeConstructor::SetObjectBoundingBoxUnitsAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + auto parseResult = SVGAttributeParser::parse(stringValue); + if (!parseResult.has_value()) { + return false; + } + + node.setAttribute(attr, SVGObjectBoundingBoxUnitsValue(*parseResult)); + return true; +} + +bool SVGNodeConstructor::SetPreserveAspectRatioAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue) { + SVGPreserveAspectRatio par; + SVGAttributeParser parser(stringValue); + if (!parser.parsePreserveAspectRatio(&par)) { + return false; + } + + node.setAttribute(attr, SVGPreserveAspectRatioValue(par)); + return true; +} + +std::string TrimmedString(const char* first, const char* last) { + ASSERT(first); + ASSERT(last); + ASSERT(first <= last); + + while (first <= last && *first <= ' ') { + first++; + } + while (first <= last && *last <= ' ') { + last--; + } + + ASSERT(last - first + 1 >= 0); + return std::string(first, static_cast(last - first + 1)); +} + +// Breaks a "foo: bar; baz: ..." string into key:value pairs. +class StyleIterator { + public: + explicit StyleIterator(const std::string& str) : pos(str.data()) { + } + + std::tuple next() { + std::string name; + std::string value; + + if (pos) { + const char* sep = this->nextSeparator(); + const char* valueSep = strchr(pos, ':'); + if (valueSep && valueSep < sep) { + name = TrimmedString(pos, valueSep - 1); + value = TrimmedString(valueSep + 1, sep - 1); + } + + pos = *sep ? sep + 1 : nullptr; + } + + return std::make_tuple(name, value); + } + + private: + const char* nextSeparator() const { + const char* sep = pos; + while (*sep != ';' && *sep != '\0') { + sep++; + } + return sep; + } + + const char* pos; +}; + +bool SVGNodeConstructor::SetStyleAttributes(SVGNode& node, SVGAttribute, + const std::string& stringValue) { + + std::string name; + std::string value; + StyleIterator iter(stringValue); + for (;;) { + std::tie(name, value) = iter.next(); + if (name.empty()) { + break; + } + SetAttribute(node, name, value); + } + + return true; +} + +std::unordered_map SVGNodeConstructor::attributeParseInfo = { + {"cx", {SVGAttribute::Cx, SVGNodeConstructor::SetLengthAttribute}}, + {"cy", {SVGAttribute::Cy, SVGNodeConstructor::SetLengthAttribute}}, + {"filterUnits", + {SVGAttribute::FilterUnits, SVGNodeConstructor::SetObjectBoundingBoxUnitsAttribute}}, + // focal point x & y + {"fx", {SVGAttribute::Fx, SVGNodeConstructor::SetLengthAttribute}}, + {"fy", {SVGAttribute::Fy, SVGNodeConstructor::SetLengthAttribute}}, + {"height", {SVGAttribute::Height, SVGNodeConstructor::SetLengthAttribute}}, + {"preserveAspectRatio", + {SVGAttribute::PreserveAspectRatio, SVGNodeConstructor::SetPreserveAspectRatioAttribute}}, + {"r", {SVGAttribute::R, SVGNodeConstructor::SetLengthAttribute}}, + {"rx", {SVGAttribute::Rx, SVGNodeConstructor::SetLengthAttribute}}, + {"ry", {SVGAttribute::Ry, SVGNodeConstructor::SetLengthAttribute}}, + {"style", {SVGAttribute::Unknown, SVGNodeConstructor::SetStyleAttributes}}, + {"text", {SVGAttribute::Text, SVGNodeConstructor::SetStringAttribute}}, + {"transform", {SVGAttribute::Transform, SVGNodeConstructor::SetTransformAttribute}}, + {"viewBox", {SVGAttribute::ViewBox, SVGNodeConstructor::SetViewBoxAttribute}}, + {"width", {SVGAttribute::Width, SVGNodeConstructor::SetLengthAttribute}}, + {"x", {SVGAttribute::X, SVGNodeConstructor::SetLengthAttribute}}, + {"x1", {SVGAttribute::X1, SVGNodeConstructor::SetLengthAttribute}}, + {"x2", {SVGAttribute::X2, SVGNodeConstructor::SetLengthAttribute}}, + {"xlink:href", {SVGAttribute::Href, SVGNodeConstructor::SetIRIAttribute}}, + {"y", {SVGAttribute::Y, SVGNodeConstructor::SetLengthAttribute}}, + {"y1", {SVGAttribute::Y1, SVGNodeConstructor::SetLengthAttribute}}, + {"y2", {SVGAttribute::Y2, SVGNodeConstructor::SetLengthAttribute}}, +}; + +using ElementFactory = std::function()>; +std::unordered_map ElementFactories = { + {"a", []() -> std::shared_ptr { return SVGGroup::Make(); }}, + {"circle", []() -> std::shared_ptr { return SVGCircle::Make(); }}, + {"clipPath", []() -> std::shared_ptr { return SVGClipPath::Make(); }}, + {"defs", []() -> std::shared_ptr { return SVGDefs::Make(); }}, + {"ellipse", []() -> std::shared_ptr { return SVGEllipse::Make(); }}, + {"feBlend", []() -> std::shared_ptr { return SVGFeBlend::Make(); }}, + {"feColorMatrix", []() -> std::shared_ptr { return SVGFeColorMatrix::Make(); }}, + {"feComponentTransfer", + []() -> std::shared_ptr { return SVGFeComponentTransfer::Make(); }}, + {"feComposite", []() -> std::shared_ptr { return SVGFeComposite::Make(); }}, + {"feDiffuseLighting", + []() -> std::shared_ptr { return SVGFeDiffuseLighting::Make(); }}, + {"feDisplacementMap", + []() -> std::shared_ptr { return SVGFeDisplacementMap::Make(); }}, + {"feDistantLight", []() -> std::shared_ptr { return SVGFeDistantLight::Make(); }}, + {"feFlood", []() -> std::shared_ptr { return SVGFeFlood::Make(); }}, + {"feFuncA", []() -> std::shared_ptr { return SVGFeFunc::MakeFuncA(); }}, + {"feFuncB", []() -> std::shared_ptr { return SVGFeFunc::MakeFuncB(); }}, + {"feFuncG", []() -> std::shared_ptr { return SVGFeFunc::MakeFuncG(); }}, + {"feFuncR", []() -> std::shared_ptr { return SVGFeFunc::MakeFuncR(); }}, + {"feGaussianBlur", []() -> std::shared_ptr { return SVGFeGaussianBlur::Make(); }}, + {"feImage", []() -> std::shared_ptr { return SVGFeImage::Make(); }}, + {"feMerge", []() -> std::shared_ptr { return SVGFeMerge::Make(); }}, + {"feMergeNode", []() -> std::shared_ptr { return SVGFeMergeNode::Make(); }}, + {"feMorphology", []() -> std::shared_ptr { return SVGFeMorphology::Make(); }}, + {"feOffset", []() -> std::shared_ptr { return SVGFeOffset::Make(); }}, + {"fePointLight", []() -> std::shared_ptr { return SVGFePointLight::Make(); }}, + {"feSpecularLighting", + []() -> std::shared_ptr { return SVGFeSpecularLighting::Make(); }}, + {"feSpotLight", []() -> std::shared_ptr { return SVGFeSpotLight::Make(); }}, + {"feTurbulence", []() -> std::shared_ptr { return SVGFeTurbulence::Make(); }}, + {"filter", []() -> std::shared_ptr { return SVGFilter::Make(); }}, + {"g", []() -> std::shared_ptr { return SVGGroup::Make(); }}, + {"image", []() -> std::shared_ptr { return SVGImage::Make(); }}, + {"line", []() -> std::shared_ptr { return SVGLine::Make(); }}, + {"linearGradient", []() -> std::shared_ptr { return SVGLinearGradient::Make(); }}, + {"mask", []() -> std::shared_ptr { return SVGMask::Make(); }}, + {"path", []() -> std::shared_ptr { return SVGPath::Make(); }}, + {"pattern", []() -> std::shared_ptr { return SVGPattern::Make(); }}, + {"polygon", []() -> std::shared_ptr { return SVGPoly::MakePolygon(); }}, + {"polyline", []() -> std::shared_ptr { return SVGPoly::MakePolyline(); }}, + {"radialGradient", []() -> std::shared_ptr { return SVGRadialGradient::Make(); }}, + {"rect", []() -> std::shared_ptr { return SVGRect::Make(); }}, + {"stop", []() -> std::shared_ptr { return SVGStop::Make(); }}, + // "svg" handled explicitly + {"text", []() -> std::shared_ptr { return SVGText::Make(); }}, + {"textPath", []() -> std::shared_ptr { return SVGTextPath::Make(); }}, + {"tspan", []() -> std::shared_ptr { return SVGTSpan::Make(); }}, + {"use", []() -> std::shared_ptr { return SVGUse::Make(); }}, +}; + +bool SVGNodeConstructor::SetAttribute(SVGNode& node, const std::string& name, + const std::string& value) { + if (node.parseAndSetAttribute(name, value)) { + // Handled by new code path + return true; + } + + if (auto iter = attributeParseInfo.find(name); iter != attributeParseInfo.end()) { + auto setter = iter->second.setter; + return setter(node, iter->second.attribute, value); + } + return true; +} + +void SVGNodeConstructor::ParseNodeAttributes(const DOMNode* xmlNode, + const std::shared_ptr& svgNode, + SVGIDMapper* mapper) { + + for (const auto& attr : xmlNode->attributes) { + auto name = attr.name; + auto value = attr.value; + if (name == "id") { + mapper->insert({value, svgNode}); + continue; + } + SetAttribute(*svgNode, name, value); + } +} + +std::shared_ptr SVGNodeConstructor::ConstructSVGNode(const ConstructionContext& context, + const DOMNode* xmlNode) { + std::string elementName = xmlNode->name; + const auto elementType = xmlNode->type; + + if (elementType == DOMNodeType::Text) { + // Text literals require special handling. + ASSERT(xmlNode->attributes.empty()); + auto text = SVGTextLiteral::Make(); + text->setText(xmlNode->name); + context.parentNode->appendChild(std::move(text)); + return nullptr; + } + + ASSERT(elementType == DOMNodeType::Element); + + auto makeNode = [](const ConstructionContext& context, + const std::string& elementName) -> std::shared_ptr { + if (elementName == "svg") { + // Outermost SVG element must be tagged as such. + return SVGRoot::Make(context.parentNode ? SVGRoot::Type::kInner : SVGRoot::Type::kRoot); + } + + if (auto iter = ElementFactories.find(elementName); iter != ElementFactories.end()) { + return iter->second(); + } + //can't find the element factory + ASSERT(false); + }; + + auto node = makeNode(context, elementName); + if (!node) { + return nullptr; + } + + ParseNodeAttributes(xmlNode, node, context.nodeIDMapper); + + ConstructionContext localCtx(context, node); + std::shared_ptr child = xmlNode->firstChild; + while (child) { + std::shared_ptr childNode = ConstructSVGNode(localCtx, child.get()); + if (childNode) { + node->appendChild(std::move(childNode)); + } + child = child->nextSibling; + } + return node; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGNodeConstructor.h b/src/svg/SVGNodeConstructor.h new file mode 100644 index 00000000..ebdb5ce3 --- /dev/null +++ b/src/svg/SVGNodeConstructor.h @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/svg/SVGDOM.h" +#include "tgfx/svg/xml/XMLDOM.h" + +namespace tgfx { + +struct ConstructionContext { + explicit ConstructionContext(SVGIDMapper* mapper) : parentNode(nullptr), nodeIDMapper(mapper) { + } + ConstructionContext(const ConstructionContext& other, const std::shared_ptr& newParent) + : parentNode(newParent.get()), nodeIDMapper(other.nodeIDMapper) { + } + + SVGNode* parentNode; + SVGIDMapper* nodeIDMapper; +}; + +using AttributeSetter = std::function; +struct AttrParseInfo { + SVGAttribute attribute; + AttributeSetter setter; +}; + +class SVGNodeConstructor { + public: + static std::shared_ptr ConstructSVGNode(const ConstructionContext& context, + const DOMNode* xmlNode); + + static bool SetAttribute(SVGNode& node, const std::string& name, const std::string& value); + + private: + static void ParseNodeAttributes(const DOMNode* xmlNode, const std::shared_ptr& svgNode, + SVGIDMapper* mapper); + + static bool SetStyleAttributes(SVGNode& node, SVGAttribute, const std::string& stringValue); + + static bool SetPreserveAspectRatioAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue); + + static bool SetObjectBoundingBoxUnitsAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue); + + static bool SetViewBoxAttribute(SVGNode& node, SVGAttribute attr, const std::string& stringValue); + + static bool SetLengthAttribute(SVGNode& node, SVGAttribute attr, const std::string& stringValue); + + static bool SetTransformAttribute(SVGNode& node, SVGAttribute attr, + const std::string& stringValue); + + static bool SetStringAttribute(SVGNode& node, SVGAttribute attr, const std::string& stringValue); + + static bool SetIRIAttribute(SVGNode& node, SVGAttribute attr, const std::string& stringValue); + + static std::unordered_map attributeParseInfo; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGParseColor.cpp b/src/svg/SVGParseColor.cpp new file mode 100644 index 00000000..e0678db2 --- /dev/null +++ b/src/svg/SVGParseColor.cpp @@ -0,0 +1,334 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "svg/SVGUtils.h" +#include "tgfx/core/Color.h" + +namespace { +const char* colorNames[] = { + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "black", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgreen", + "lightgrey", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen", +}; + +struct ColorRec { + uint8_t r, g, b; +}; + +constexpr ColorRec colors[] = { + {0xf0, 0xf8, 0xff}, // aliceblue + {0xfa, 0xeb, 0xd7}, // antiquewhite + {0x00, 0xff, 0xff}, // aqua + {0x7f, 0xff, 0xd4}, // aquamarine + {0xf0, 0xff, 0xff}, // azure + {0xf5, 0xf5, 0xdc}, // beige + {0xff, 0xe4, 0xc4}, // bisque + {0x00, 0x00, 0x00}, // black + {0xff, 0xeb, 0xcd}, // blanchedalmond + {0x00, 0x00, 0xff}, // blue + {0x8a, 0x2b, 0xe2}, // blueviolet + {0xa5, 0x2a, 0x2a}, // brown + {0xde, 0xb8, 0x87}, // burlywood + {0x5f, 0x9e, 0xa0}, // cadetblue + {0x7f, 0xff, 0x00}, // chartreuse + {0xd2, 0x69, 0x1e}, // chocolate + {0xff, 0x7f, 0x50}, // coral + {0x64, 0x95, 0xed}, // cornflowerblue + {0xff, 0xf8, 0xdc}, // cornsilk + {0xdc, 0x14, 0x3c}, // crimson + {0x00, 0xff, 0xff}, // cyan + {0x00, 0x00, 0x8b}, // darkblue + {0x00, 0x8b, 0x8b}, // darkcyan + {0xb8, 0x86, 0x0b}, // darkgoldenrod + {0xa9, 0xa9, 0xa9}, // darkgray + {0x00, 0x64, 0x00}, // darkgreen + {0xbd, 0xb7, 0x6b}, // darkkhaki + {0x8b, 0x00, 0x8b}, // darkmagenta + {0x55, 0x6b, 0x2f}, // darkolivegreen + {0xff, 0x8c, 0x00}, // darkorange + {0x99, 0x32, 0xcc}, // darkorchid + {0x8b, 0x00, 0x00}, // darkred + {0xe9, 0x96, 0x7a}, // darksalmon + {0x8f, 0xbc, 0x8f}, // darkseagreen + {0x48, 0x3d, 0x8b}, // darkslateblue + {0x2f, 0x4f, 0x4f}, // darkslategray + {0x00, 0xce, 0xd1}, // darkturquoise + {0x94, 0x00, 0xd3}, // darkviolet + {0xff, 0x14, 0x93}, // deeppink + {0x00, 0xbf, 0xff}, // deepskyblue + {0x69, 0x69, 0x69}, // dimgray + {0x1e, 0x90, 0xff}, // dodgerblue + {0xb2, 0x22, 0x22}, // firebrick + {0xff, 0xfa, 0xf0}, // floralwhite + {0x22, 0x8b, 0x22}, // forestgreen + {0xff, 0x00, 0xff}, // fuchsia + {0xdc, 0xdc, 0xdc}, // gainsboro + {0xf8, 0xf8, 0xff}, // ghostwhite + {0xff, 0xd7, 0x00}, // gold + {0xda, 0xa5, 0x20}, // goldenrod + {0x80, 0x80, 0x80}, // gray + {0x00, 0x80, 0x00}, // green + {0xad, 0xff, 0x2f}, // greenyellow + {0xf0, 0xff, 0xf0}, // honeydew + {0xff, 0x69, 0xb4}, // hotpink + {0xcd, 0x5c, 0x5c}, // indianred + {0x4b, 0x00, 0x82}, // indigo + {0xff, 0xff, 0xf0}, // ivory + {0xf0, 0xe6, 0x8c}, // khaki + {0xe6, 0xe6, 0xfa}, // lavender + {0xff, 0xf0, 0xf5}, // lavenderblush + {0x7c, 0xfc, 0x00}, // lawngreen + {0xff, 0xfa, 0xcd}, // lemonchiffon + {0xad, 0xd8, 0xe6}, // lightblue + {0xf0, 0x80, 0x80}, // lightcoral + {0xe0, 0xff, 0xff}, // lightcyan + {0xfa, 0xfa, 0xd2}, // lightgoldenrodyellow + {0x90, 0xee, 0x90}, // lightgreen + {0xd3, 0xd3, 0xd3}, // lightgrey + {0xff, 0xb6, 0xc1}, // lightpink + {0xff, 0xa0, 0x7a}, // lightsalmon + {0x20, 0xb2, 0xaa}, // lightseagreen + {0x87, 0xce, 0xfa}, // lightskyblue + {0x77, 0x88, 0x99}, // lightslategray + {0xb0, 0xc4, 0xde}, // lightsteelblue + {0xff, 0xff, 0xe0}, // lightyellow + {0x00, 0xff, 0x00}, // lime + {0x32, 0xcd, 0x32}, // limegreen + {0xfa, 0xf0, 0xe6}, // linen + {0xff, 0x00, 0xff}, // magenta + {0x80, 0x00, 0x00}, // maroon + {0x66, 0xcd, 0xaa}, // mediumaquamarine + {0x00, 0x00, 0xcd}, // mediumblue + {0xba, 0x55, 0xd3}, // mediumorchid + {0x93, 0x70, 0xdb}, // mediumpurple + {0x3c, 0xb3, 0x71}, // mediumseagreen + {0x7b, 0x68, 0xee}, // mediumslateblue + {0x00, 0xfa, 0x9a}, // mediumspringgreen + {0x48, 0xd1, 0xcc}, // mediumturquoise + {0xc7, 0x15, 0x85}, // mediumvioletred + {0x19, 0x19, 0x70}, // midnightblue + {0xf5, 0xff, 0xfa}, // mintcream + {0xff, 0xe4, 0xe1}, // mistyrose + {0xff, 0xe4, 0xb5}, // moccasin + {0xff, 0xde, 0xad}, // navajowhite + {0x00, 0x00, 0x80}, // navy + {0xfd, 0xf5, 0xe6}, // oldlace + {0x80, 0x80, 0x00}, // olive + {0x6b, 0x8e, 0x23}, // olivedrab + {0xff, 0xa5, 0x00}, // orange + {0xff, 0x45, 0x00}, // orangered + {0xda, 0x70, 0xd6}, // orchid + {0xee, 0xe8, 0xaa}, // palegoldenrod + {0x98, 0xfb, 0x98}, // palegreen + {0xaf, 0xee, 0xee}, // paleturquoise + {0xdb, 0x70, 0x93}, // palevioletred + {0xff, 0xef, 0xd5}, // papayawhip + {0xff, 0xda, 0xb9}, // peachpuff + {0xcd, 0x85, 0x3f}, // peru + {0xff, 0xc0, 0xcb}, // pink + {0xdd, 0xa0, 0xdd}, // plum + {0xb0, 0xe0, 0xe6}, // powderblue + {0x80, 0x00, 0x80}, // purple + {0xff, 0x00, 0x00}, // red + {0xbc, 0x8f, 0x8f}, // rosybrown + {0x41, 0x69, 0xe1}, // royalblue + {0x8b, 0x45, 0x13}, // saddlebrown + {0xfa, 0x80, 0x72}, // salmon + {0xf4, 0xa4, 0x60}, // sandybrown + {0x2e, 0x8b, 0x57}, // seagreen + {0xff, 0xf5, 0xee}, // seashell + {0xa0, 0x52, 0x2d}, // sienna + {0xc0, 0xc0, 0xc0}, // silver + {0x87, 0xce, 0xeb}, // skyblue + {0x6a, 0x5a, 0xcd}, // slateblue + {0x70, 0x80, 0x90}, // slategray + {0xff, 0xfa, 0xfa}, // snow + {0x00, 0xff, 0x7f}, // springgreen + {0x46, 0x82, 0xb4}, // steelblue + {0xd2, 0xb4, 0x8c}, // tan + {0x00, 0x80, 0x80}, // teal + {0xd8, 0xbf, 0xd8}, // thistle + {0xff, 0x63, 0x47}, // tomato + {0x40, 0xe0, 0xd0}, // turquoise + {0xee, 0x82, 0xee}, // violet + {0xf5, 0xde, 0xb3}, // wheat + {0xff, 0xff, 0xff}, // white + {0xf5, 0xf5, 0xf5}, // whitesmoke + {0xff, 0xff, 0x00}, // yellow + {0x9a, 0xcd, 0x32}, // yellowgreen +}; +} // namespace + +namespace tgfx { +const char* SVGParse::FindNamedColor(const char* name, Color* color) { + const auto rec = + std::lower_bound(std::begin(colorNames), std::end(colorNames), + name, // key + [](const char* name, const char* key) { return strcmp(name, key) < 0; }); + + if (rec == std::end(colorNames) || 0 != strcmp(name, *rec)) { + return nullptr; + } + + if (color) { + int64_t index = rec - colorNames; + *color = Color::FromRGBA(colors[index].r, colors[index].g, colors[index].b); + } + + return name + strlen(*rec); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGRenderContext.cpp b/src/svg/SVGRenderContext.cpp new file mode 100644 index 00000000..d049cdfd --- /dev/null +++ b/src/svg/SVGRenderContext.cpp @@ -0,0 +1,508 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SVGRenderContext.h" +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Color.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/MaskFilter.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/PathEffect.h" +#include "tgfx/core/Recorder.h" +#include "tgfx/core/Rect.h" +#include "tgfx/core/Stroke.h" +#include "tgfx/core/Surface.h" +#include "tgfx/svg/node/SVGClipPath.h" +#include "tgfx/svg/node/SVGFilter.h" +#include "tgfx/svg/node/SVGMask.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { +namespace { + +LineCap toCap(const SVGLineCap& cap) { + switch (cap) { + case SVGLineCap::Butt: + return LineCap::Butt; + case SVGLineCap::Round: + return LineCap::Round; + case SVGLineCap::Square: + return LineCap::Square; + } +} + +LineJoin toJoin(const SVGLineJoin& join) { + switch (join.type()) { + case SVGLineJoin::Type::Miter: + return LineJoin::Miter; + case SVGLineJoin::Type::Round: + return LineJoin::Round; + case SVGLineJoin::Type::Bevel: + return LineJoin::Bevel; + default: + ASSERT(false); + return LineJoin::Miter; + } +} + +std::shared_ptr dash_effect(const SVGPresentationAttributes& props, + const SVGLengthContext& lengthContext) { + if (props.StrokeDashArray->type() != SVGDashArray::Type::DashArray) { + return nullptr; + } + + const auto& da = *props.StrokeDashArray; + const auto count = da.dashArray().size(); + std::vector intervals; + intervals.reserve(count); + for (const auto& dash : da.dashArray()) { + intervals.push_back(lengthContext.resolve(dash, SVGLengthContext::LengthType::Other)); + } + + if (count & 1) { + // If an odd number of values is provided, then the list of values + // is repeated to yield an even number of values. + intervals.resize(count * 2); + memcpy(intervals.data() + count, intervals.data(), count * sizeof(float)); + } + + ASSERT((intervals.size() & 1) == 0); + + const auto phase = + lengthContext.resolve(*props.StrokeDashOffset, SVGLengthContext::LengthType::Other); + + return PathEffect::MakeDash(intervals.data(), static_cast(intervals.size()), phase); +} + +} // namespace + +SVGPresentationContext::SVGPresentationContext() + : _inherited(SVGPresentationAttributes::MakeInitial()) { +} + +SVGRenderContext::SVGRenderContext(Canvas* canvas, const std::shared_ptr& fontManager, + const std::shared_ptr& resourceProvider, + const SVGIDMapper& mapper, const SVGLengthContext& lengthContext, + const SVGPresentationContext& presentContext, + const OBBScope& scope, const Matrix& matrix) + : _fontManager(fontManager), _resourceProvider(resourceProvider), nodeIDMapper(mapper), + _lengthContext(lengthContext), _presentationContext(presentContext), _canvas(canvas), + canvasSaveCount(canvas->getSaveCount()), scope(scope), _matrix(matrix) { +} + +SVGRenderContext::SVGRenderContext(const SVGRenderContext& other) + : SVGRenderContext(other._canvas, other._fontManager, other._resourceProvider, + other.nodeIDMapper, *other._lengthContext, *other._presentationContext, + other.scope, other._matrix) { +} + +SVGRenderContext::SVGRenderContext(const SVGRenderContext& other, Canvas* canvas) + : SVGRenderContext(canvas, other._fontManager, other._resourceProvider, other.nodeIDMapper, + *other._lengthContext, *other._presentationContext, other.scope, + other._matrix) { +} + +SVGRenderContext::SVGRenderContext(const SVGRenderContext& other, + const SVGLengthContext& lengthContext) + : SVGRenderContext(other._canvas, other._fontManager, other._resourceProvider, + other.nodeIDMapper, lengthContext, *other._presentationContext, other.scope, + other._matrix) { +} + +SVGRenderContext::SVGRenderContext(const SVGRenderContext& other, Canvas* canvas, + const SVGLengthContext& lengthContext) + : SVGRenderContext(canvas, other._fontManager, other._resourceProvider, other.nodeIDMapper, + lengthContext, *other._presentationContext, other.scope, other._matrix) { +} + +SVGRenderContext::SVGRenderContext(const SVGRenderContext& other, const SVGNode* node) + : SVGRenderContext(other._canvas, other._fontManager, other._resourceProvider, + other.nodeIDMapper, *other._lengthContext, *other._presentationContext, + OBBScope{node, this}, other._matrix) { +} + +SVGRenderContext::~SVGRenderContext() { + _canvas->restoreToCount(canvasSaveCount); +} + +SVGRenderContext SVGRenderContext::CopyForPaint(const SVGRenderContext& context, Canvas* canvas, + const SVGLengthContext& lengthContext) { + SVGRenderContext copyContext(context, canvas, lengthContext); + copyContext.deferredPaintOpacity = context.deferredPaintOpacity; + return copyContext; +} + +std::shared_ptr SVGRenderContext::findNodeById(const SVGIRI& iri) const { + if (iri.type() != SVGIRI::Type::Local) { + return nullptr; + } + auto iter = nodeIDMapper.find(iri.iri()); + if (iter != nodeIDMapper.end()) { + return iter->second; + } + return nullptr; +} + +void SVGRenderContext::applyPresentationAttributes(const SVGPresentationAttributes& attrs, + uint32_t flags) { +#define ApplyLazyInheritedAttribute(ATTR) \ + do { \ + /* All attributes should be defined on the inherited context. */ \ + ASSERT(_presentationContext->_inherited.ATTR.isValue()); \ + const auto& attr = attrs.ATTR; \ + if (attr.isValue() && *attr != *_presentationContext->_inherited.ATTR) { \ + /* Update the local attribute value */ \ + _presentationContext.writable()->_inherited.ATTR.set(*attr); \ + } \ + } while (false) + + ApplyLazyInheritedAttribute(Fill); + ApplyLazyInheritedAttribute(FillOpacity); + ApplyLazyInheritedAttribute(FillRule); + ApplyLazyInheritedAttribute(FontFamily); + ApplyLazyInheritedAttribute(FontSize); + ApplyLazyInheritedAttribute(FontStyle); + ApplyLazyInheritedAttribute(FontWeight); + ApplyLazyInheritedAttribute(ClipRule); + ApplyLazyInheritedAttribute(Stroke); + ApplyLazyInheritedAttribute(StrokeDashOffset); + ApplyLazyInheritedAttribute(StrokeDashArray); + ApplyLazyInheritedAttribute(StrokeLineCap); + ApplyLazyInheritedAttribute(StrokeLineJoin); + ApplyLazyInheritedAttribute(StrokeMiterLimit); + ApplyLazyInheritedAttribute(StrokeOpacity); + ApplyLazyInheritedAttribute(StrokeWidth); + ApplyLazyInheritedAttribute(TextAnchor); + ApplyLazyInheritedAttribute(Visibility); + ApplyLazyInheritedAttribute(Color); + ApplyLazyInheritedAttribute(ColorInterpolation); + ApplyLazyInheritedAttribute(ColorInterpolationFilters); + +#undef ApplyLazyInheritedAttribute + + if (attrs.ClipPath.isValue()) { + saveOnce(); + _canvas->clipPath(applyClip(*attrs.ClipPath)); + } + + const bool hasFilter = attrs.Filter.isValue(); + if (attrs.Opacity.isValue()) { + _canvas->saveLayerAlpha(this->applyOpacity(*attrs.Opacity, flags, hasFilter)); + } + + if (attrs.Mask.isValue()) { + if (auto maskFilter = this->applyMask(*attrs.Mask)) { + Paint maskPaint; + maskPaint.setMaskFilter(maskFilter); + _canvas->saveLayer(&maskPaint); + } + } + + if (hasFilter) { + if (auto imageFilter = this->applyFilter(*attrs.Filter)) { + Paint filterPaint; + filterPaint.setImageFilter(imageFilter); + _canvas->saveLayer(&filterPaint); + } + } +} + +float SVGRenderContext::applyOpacity(float opacity, uint32_t flags, bool hasFilter) { + opacity = std::clamp(opacity, 0.0f, 1.0f); + const auto& props = _presentationContext->_inherited; + const bool hasFill = props.Fill->type() != SVGPaint::Type::None; + const bool hasStroke = props.Stroke->type() != SVGPaint::Type::None; + + // We can apply the opacity as paint alpha if it only affects one atomic draw. + // For now, this means all of the following must be true: + // - the target node doesn't have any descendants; + // - it only has a stroke or a fill (but not both); + // - it does not have a filter. + // Going forward, we may needto refine this heuristic (e.g. to accommodate markers). + if ((flags & static_cast(ApplyFlags::Leaf)) && (hasFill ^ hasStroke) && !hasFilter) { + deferredPaintOpacity *= opacity; + return 1.0f; + } + return opacity; +} + +std::shared_ptr SVGRenderContext::applyFilter(const SVGFuncIRI& filter) const { + if (filter.type() != SVGFuncIRI::Type::IRI) { + return nullptr; + } + + const auto node = this->findNodeById(filter.iri()); + if (!node || node->tag() != SVGTag::Filter) { + return nullptr; + } + + const auto* filterNode = reinterpret_cast(node.get()); + return filterNode->buildFilterDAG(*this); +} + +void SVGRenderContext::saveOnce() { + if (_canvas->getSaveCount() == canvasSaveCount) { + _canvas->save(); + } +} + +Path SVGRenderContext::applyClip(const SVGFuncIRI& clip) const { + if (clip.type() != SVGFuncIRI::Type::IRI) { + return Path(); + } + + const auto clipNode = this->findNodeById(clip.iri()); + if (!clipNode || clipNode->tag() != SVGTag::ClipPath) { + return Path(); + } + + return static_cast(clipNode.get())->resolveClip(*this); +} + +std::shared_ptr SVGRenderContext::applyMask(const SVGFuncIRI& mask) { + if (mask.type() != SVGFuncIRI::Type::IRI) { + return nullptr; + } + + const auto node = this->findNodeById(mask.iri()); + if (!node || node->tag() != SVGTag::Mask) { + return nullptr; + } + + auto maskNode = std::static_pointer_cast(node); + auto maskBound = maskNode->bounds(*this); + + Recorder maskRecorder; + auto* maskCanvas = maskRecorder.beginRecording(); + { + SVGRenderContext maskContext(*this, maskCanvas); + maskNode->renderMask(maskContext); + } + auto picture = maskRecorder.finishRecordingAsPicture(); + if (!picture) { + return nullptr; + } + + auto transMatrix = _matrix * Matrix::MakeTrans(-maskBound.left, -maskBound.top); + auto shaderImage = + Image::MakeFrom(picture, static_cast(maskBound.width() * _matrix.getScaleX()), + static_cast(maskBound.height() * _matrix.getScaleY()), &transMatrix); + auto shader = Shader::MakeImageShader(shaderImage, TileMode::Decal, TileMode::Decal); + if (!shader) { + return nullptr; + } + return MaskFilter::MakeShader(shader); +} + +std::optional SVGRenderContext::commonPaint(const SVGPaint& svgPaint, float opacity) const { + if (svgPaint.type() == SVGPaint::Type::None) { + return std::optional(); + } + + std::optional paint = Paint(); + switch (svgPaint.type()) { + case SVGPaint::Type::Color: + paint->setColor(this->resolveSVGColor(svgPaint.color())); + break; + case SVGPaint::Type::IRI: { + // Our property inheritance is borked as it follows the render path and not the tree + // hierarchy. To avoid gross transgressions like leaf node presentation attributes + // leaking into the paint server context, use a pristine presentation context when + // following hrefs. + // + // Preserve the OBB scope because some paints use object bounding box coords + // (e.g. gradient control points), which requires access to the render context + // and node being rendered. + SVGPresentationContext presentContext; + presentContext._namedColors = _presentationContext->_namedColors; + SVGRenderContext localContext(_canvas, _fontManager, _resourceProvider, nodeIDMapper, + *_lengthContext, presentContext, scope, Matrix::I()); + + const auto node = this->findNodeById(svgPaint.iri()); + if (!node || !node->asPaint(localContext, &(paint.value()))) { + // Use the fallback color. + paint->setColor(this->resolveSVGColor(svgPaint.color())); + } + } break; + default: + break; + } + paint->setAntiAlias(true); + + // We observe 3 opacity components: + // - initial paint server opacity (e.g. color stop opacity) + // - paint-specific opacity (e.g. 'fill-opacity', 'stroke-opacity') + // - deferred opacity override (optimization for leaf nodes 'opacity') + paint->setAlpha(std::clamp(paint->getAlpha() * opacity * deferredPaintOpacity, 0.0f, 1.0f)); + return paint; +} + +std::optional SVGRenderContext::fillPaint() const { + const auto& props = _presentationContext->_inherited; + auto paint = this->commonPaint(*props.Fill, *props.FillOpacity); + + if (paint.has_value()) { + paint->setStyle(PaintStyle::Fill); + } + + return paint; +} + +std::optional SVGRenderContext::strokePaint() const { + const auto& props = _presentationContext->_inherited; + auto paint = this->commonPaint(*props.Stroke, *props.StrokeOpacity); + + if (paint.has_value()) { + paint->setStyle(PaintStyle::Stroke); + Stroke stroke; + stroke.width = _lengthContext->resolve(*props.StrokeWidth, SVGLengthContext::LengthType::Other); + stroke.cap = toCap(*props.StrokeLineCap); + stroke.join = toJoin(*props.StrokeLineJoin); + stroke.miterLimit = *props.StrokeMiterLimit; + paint->setStroke(stroke); + } + return paint; +} + +std::shared_ptr SVGRenderContext::strokePathEffect() const { + const auto& props = _presentationContext->_inherited; + return dash_effect(props, *_lengthContext); +} + +SVGColorType SVGRenderContext::resolveSVGColor(const SVGColor& color) const { + if (_presentationContext->_namedColors) { + for (auto&& ident : *color.vars()) { + auto iter = _presentationContext->_namedColors->find(ident); + if (iter != _presentationContext->_namedColors->end()) { + return iter->second; + } + } + } + switch (color.type()) { + case SVGColor::Type::Color: + return color.color(); + case SVGColor::Type::CurrentColor: + return *_presentationContext->_inherited.Color; + case SVGColor::Type::ICCColor: + return Color::Black(); + } +} + +Font SVGRenderContext::resolveFont() const { + if (!_fontManager) { + return Font(); + } + auto weight = [](const SVGFontWeight& w) { + switch (w.type()) { + case SVGFontWeight::Type::W100: + return FontStyle::Weight::Thin; + case SVGFontWeight::Type::W200: + return FontStyle::Weight::ExtraLight; + case SVGFontWeight::Type::W300: + return FontStyle::Weight::Light; + case SVGFontWeight::Type::W400: + return FontStyle::Weight::Normal; + case SVGFontWeight::Type::W500: + return FontStyle::Weight::Medium; + case SVGFontWeight::Type::W600: + return FontStyle::Weight::SemiBold; + case SVGFontWeight::Type::W700: + return FontStyle::Weight::Bold; + case SVGFontWeight::Type::W800: + return FontStyle::Weight::ExtraBold; + case SVGFontWeight::Type::W900: + return FontStyle::Weight::Black; + case SVGFontWeight::Type::Normal: + return FontStyle::Weight::Normal; + case SVGFontWeight::Type::Bold: + return FontStyle::Weight::Bold; + case SVGFontWeight::Type::Bolder: + return FontStyle::Weight::ExtraBold; + case SVGFontWeight::Type::Lighter: + return FontStyle::Weight::Light; + case SVGFontWeight::Type::Inherit: { + return FontStyle::Weight::Normal; + } + } + }; + + auto slant = [](const SVGFontStyle& s) { + switch (s.type()) { + case SVGFontStyle::Type::Normal: + return FontStyle::Slant::Upright; + case SVGFontStyle::Type::Italic: + return FontStyle::Slant::Italic; + case SVGFontStyle::Type::Oblique: + return FontStyle::Slant::Oblique; + case SVGFontStyle::Type::Inherit: { + return FontStyle::Slant::Upright; + } + } + }; + + const auto& family = presentationContext()._inherited.FontFamily->family(); + const FontStyle style(weight(*presentationContext()._inherited.FontWeight), + FontStyle::Width::Normal, + slant(*presentationContext()._inherited.FontStyle)); + + const auto size = lengthContext().resolve(presentationContext()._inherited.FontSize->size(), + SVGLengthContext::LengthType::Vertical); + + auto typeface = _fontManager->matchTypeface(family, style); + if (!typeface) { + typeface = _fontManager->matchTypeface("", style); + } + Font font(std::move(typeface), size); + return font; +} + +SVGRenderContext::OBBTransform SVGRenderContext::transformForCurrentBoundBox( + SVGObjectBoundingBoxUnits unit) const { + if (!scope.node || unit.type() == SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse) { + return {{0, 0}, {1, 1}}; + } + ASSERT(scope.context); + + const auto obb = scope.node->objectBoundingBox(*scope.context); + return {{obb.x(), obb.y()}, {obb.width(), obb.height()}}; +} + +Rect SVGRenderContext::resolveOBBRect(const SVGLength& x, const SVGLength& y, const SVGLength& w, + const SVGLength& h, SVGObjectBoundingBoxUnits unit) const { + CopyOnWrite lengthContext(_lengthContext); + + if (unit.type() == SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox) { + *lengthContext.writable() = SVGLengthContext({1, 1}); + } + + auto r = lengthContext->resolveRect(x, y, w, h); + const auto transform = this->transformForCurrentBoundBox(unit); + + return Rect::MakeXYWH(transform.scale.x * r.x() + transform.offset.x, + transform.scale.y * r.y() + transform.offset.y, + transform.scale.x * r.width(), transform.scale.y * r.height()); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGRenderContext.h b/src/svg/SVGRenderContext.h new file mode 100644 index 00000000..86c8f440 --- /dev/null +++ b/src/svg/SVGRenderContext.h @@ -0,0 +1,253 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "svg/SVGLengthContext.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/MaskFilter.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/PathEffect.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/Recorder.h" +#include "tgfx/core/Rect.h" +#include "tgfx/core/Size.h" +#include "tgfx/gpu/Context.h" +#include "tgfx/svg/FontManager.h" +#include "tgfx/svg/ResourceLoader.h" +#include "tgfx/svg/SVGAttribute.h" +#include "tgfx/svg/SVGDOM.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGNode; + +template +class CopyOnWrite { + public: + explicit CopyOnWrite(const T& initial) : object(&initial) { + } + + explicit CopyOnWrite(const T* initial) : object(initial) { + } + + // Constructor for delayed initialization. + CopyOnWrite() : object(nullptr) { + } + + CopyOnWrite(const CopyOnWrite& that) { + *this = that; + } + + CopyOnWrite(CopyOnWrite&& that) noexcept { + *this = std::move(that); + } + + CopyOnWrite& operator=(const CopyOnWrite& that) { + optional = that.optional; + object = optional.has_value() ? &optional.value() : that.object; + return *this; + } + + CopyOnWrite& operator=(CopyOnWrite&& that) { + optional = std::move(that.optional); + object = optional.has_value() ? &optional.value() : that.object; + return *this; + } + + /** + * Returns a writable T*. The first time this is called the initial object is cloned. + */ + T* writable() { + if (!optional.has_value()) { + optional = *object; + object = &optional.value(); + } + return &optional.value(); + } + + const T* get() const { + return object; + } + + /** + * Operators for treating this as though it were a const pointer. + */ + + const T* operator->() const { + return object; + } + + const T& operator*() const { + return *object; + } + + private: + const T* object; + std::optional optional; +}; + +struct SVGPresentationContext { + SVGPresentationContext(); + SVGPresentationContext(const SVGPresentationContext& other) = default; + SVGPresentationContext& operator=(const SVGPresentationContext& other) = default; + + const std::unordered_map* _namedColors = nullptr; + // Inherited presentation attributes, computed for the current node. + SVGPresentationAttributes _inherited; +}; + +class SVGRenderContext { + public: + // Captures data required for object bounding box resolution. + struct OBBScope { + const SVGNode* node; + const SVGRenderContext* context; + }; + + SVGRenderContext(Canvas* canvas, const std::shared_ptr& fontManager, + const std::shared_ptr& resourceProvider, + const SVGIDMapper& mapper, const SVGLengthContext& lengthContext, + const SVGPresentationContext& presentContext, const OBBScope& scope, + const Matrix& matrix); + SVGRenderContext(const SVGRenderContext& other); + SVGRenderContext(const SVGRenderContext& other, Canvas* canvas); + SVGRenderContext(const SVGRenderContext& other, const SVGLengthContext& lengthContext); + SVGRenderContext(const SVGRenderContext& other, Canvas* canvas, + const SVGLengthContext& lengthContext); + // Establish a new OBB scope. Normally used when entering a node's render scope. + SVGRenderContext(const SVGRenderContext& other, const SVGNode* node); + ~SVGRenderContext(); + + static SVGRenderContext CopyForPaint(const SVGRenderContext& context, Canvas* canvas, + const SVGLengthContext& lengthContext); + + const SVGLengthContext& lengthContext() const { + return *_lengthContext; + } + + SVGLengthContext* writableLengthContext() { + return _lengthContext.writable(); + } + + const SVGPresentationContext& presentationContext() const { + return *_presentationContext; + } + + Canvas* canvas() const { + return _canvas; + } + + void saveOnce(); + + void concat(const Matrix& inputMatrix) { + _matrix.preConcat(inputMatrix); + } + + const Matrix& matrix() const { + return _matrix; + } + + enum class ApplyFlags { + Leaf = 1 << 0, // the target node doesn't have descendants + }; + + void applyPresentationAttributes(const SVGPresentationAttributes& attrs, uint32_t flags); + + // Note: the id->node association is cleared for the lifetime of the returned value + // (effectively breaks reference cycles, assuming appropriate return value scoping). + std::shared_ptr findNodeById(const SVGIRI&) const; + + std::optional fillPaint() const; + + std::optional strokePaint() const; + + std::shared_ptr strokePathEffect() const; + + SVGColorType resolveSVGColor(const SVGColor&) const; + + Font resolveFont() const; + + // The local computed clip path (not inherited). + Path clipPath() const { + return _clipPath.value_or(Path()); + }; + + const std::shared_ptr& resourceProvider() const { + return _resourceProvider; + } + + const std::shared_ptr& fontManager() const { + return _fontManager; + } + + // Returns the translate/scale transformation required to map into the current OBB scope, + // with the specified units. + struct OBBTransform { + Point offset; + Point scale; + }; + + OBBTransform transformForCurrentBoundBox(SVGObjectBoundingBoxUnits) const; + + Rect resolveOBBRect(const SVGLength& x, const SVGLength& y, const SVGLength& w, + const SVGLength& h, SVGObjectBoundingBoxUnits unit) const; + + // Stack-only + void* operator new(size_t) = delete; + void* operator new(size_t, void*) = delete; + SVGRenderContext& operator=(const SVGRenderContext&) = delete; + + private: + float applyOpacity(float opacity, uint32_t flags, bool hasFilter); + std::shared_ptr applyFilter(const SVGFuncIRI& filter) const; + Path applyClip(const SVGFuncIRI& clip) const; + std::shared_ptr applyMask(const SVGFuncIRI& mask); + + std::optional commonPaint(const SVGPaint& paint, float opacity) const; + + std::shared_ptr _fontManager; + std::shared_ptr _resourceProvider; + + const SVGIDMapper& nodeIDMapper; + CopyOnWrite _lengthContext; + CopyOnWrite _presentationContext; + Canvas* _canvas; + int canvasSaveCount = 0; + + // clipPath, if present for the current context (not inherited). + std::optional _clipPath; + + // Deferred opacity optimization for leaf nodes. + float deferredPaintOpacity = 1; + + // Current object bounding box scope. + const OBBScope scope; + + Matrix _matrix = Matrix::I(); +}; +} // namespace tgfx diff --git a/src/svg/SVGTextBuilder.h b/src/svg/SVGTextBuilder.h index 98b41bb6..5932d69b 100644 --- a/src/svg/SVGTextBuilder.h +++ b/src/svg/SVGTextBuilder.h @@ -18,7 +18,6 @@ #pragma once -#include #include #include "core/utils/GlyphConverter.h" #include "tgfx/core/GlyphRun.h" diff --git a/src/svg/SVGUtils.cpp b/src/svg/SVGUtils.cpp index 4b640923..e1e7b77d 100644 --- a/src/svg/SVGUtils.cpp +++ b/src/svg/SVGUtils.cpp @@ -199,6 +199,61 @@ std::string FloatToString(float value) { return result; } +std::shared_ptr Base64Decode(const std::string& encodedString) { + static const std::array decodingTable = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, + 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64}; + + size_t inLength = encodedString.size(); + if (inLength % 4 != 0) { + return nullptr; + } + + size_t outLength = inLength / 4 * 3; + if (encodedString[inLength - 1] == '=') { + outLength--; + } + if (encodedString[inLength - 2] == '=') { + outLength--; + } + + auto* out = static_cast(malloc(outLength)); + auto outData = Data::MakeAdopted(out, outLength, Data::FreeProc); + + for (size_t i = 0, j = 0; i < inLength;) { + uint32_t a = encodedString[i] == '=' + ? 0 & i++ + : decodingTable[static_cast(encodedString[i++])]; + uint32_t b = encodedString[i] == '=' + ? 0 & i++ + : decodingTable[static_cast(encodedString[i++])]; + uint32_t c = encodedString[i] == '=' + ? 0 & i++ + : decodingTable[static_cast(encodedString[i++])]; + uint32_t d = encodedString[i] == '=' + ? 0 & i++ + : decodingTable[static_cast(encodedString[i++])]; + + uint32_t triple = (a << 18) + (b << 12) + (c << 6) + d; + + if (j < outLength) { + out[j++] = (triple >> 16) & 0xFF; + } + if (j < outLength) { + out[j++] = (triple >> 8) & 0xFF; + } + if (j < outLength) { + out[j++] = triple & 0xFF; + } + } + + return outData; +} + void Base64Encode(unsigned char const* bytesToEncode, size_t length, char* ret) { static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -287,4 +342,390 @@ std::shared_ptr AsDataUri(const std::shared_ptr& encodedData) { return dataUri; } +namespace { +inline bool is_between(int c, int min, int max) { + return static_cast(c - min) <= static_cast(max - min); +} + +inline bool is_ws(int c) { + return is_between(c, 1, 32); +} + +inline bool is_digit(int c) { + return is_between(c, '0', '9'); +} + +int to_hex(int c) { + if (is_digit(c)) { + return c - '0'; + } + + c |= 0x20; // make us lower-case + return is_between(c, 'a', 'f') ? c + 10 - 'a' : -1; +} + +inline bool is_hex(int c) { + return to_hex(c) >= 0; +} + +const char* skip_ws(const char str[]) { + ASSERT(str); + while (is_ws(*str)) { + str++; + } + return str; +} + +template +tgfx::Unichar next_fail(const T** ptr, const T* end) { + *ptr = end; + return -1; +} + +inline bool is_sep(int c) { + return is_ws(c) || c == ',' || c == ';'; +} + +const char* skip_sep(const char str[]) { + ASSERT(str); + while (is_sep(*str)) { + str++; + } + return str; +} + +bool lookup_str(const char str[], const char** table, int count) { + while (--count >= 0) { + if (!strcmp(str, table[count])) { + return true; + } + } + return false; +} + +inline bool is_lower(int c) { + return is_between(c, 'a', 'z'); +} + +inline int to_upper(int c) { + return c - 'a' + 'A'; +} + +// If unable to read count points from str into value, this will return nullptr +// to signal the failure. Otherwise, it will return the next offset to read from. +const char* find_points(const char str[], Point value[], int count, bool isRelative, + Point* relative) { + str = SVGParse::FindScalars(str, &value[0].x, count * 2); + if (isRelative) { + for (int index = 0; index < count; index++) { + value[index].x += relative->x; + value[index].y += relative->y; + } + } + return str; +} + +// If unable to read a scalar from str into value, this will return nullptr +// to signal the failure. Otherwise, it will return the next offset to read from. +const char* find_scalar(const char str[], float* value, bool isRelative, float relative) { + str = SVGParse::FindScalar(str, value); + if (!str) { + return nullptr; + } + if (isRelative) { + *value += relative; + } + str = skip_sep(str); + return str; +} + +// https://www.w3.org/TR/SVG11/paths.html#PathDataBNF +// flag: +// "0" | "1" +const char* find_flag(const char str[], bool* value) { + if (!str) { + return nullptr; + } + if (str[0] != '1' && str[0] != '0') { + return nullptr; + } + *value = str[0] != '0'; + str = skip_sep(str + 1); + return str; +} +} // namespace + +std::tuple> PathMakeFromSVGString(const std::string& pathString) { + const char* data = pathString.c_str(); + // We will write all data to this local path and only write it + // to result if the whole parsing succeeds. + auto path = std::make_shared(); + auto first = Point::Zero(); + auto opOrigin = Point::Zero(); + auto lastOpOrigin = Point::Zero(); + + // We will use find_points and find_scalar to read into these. + // There might not be enough data to fill them, so to avoid + // MSAN warnings about using uninitialized bytes, we initialize + // them there. + Point points[3] = {}; + float scratch = 0; + char op = '\0'; + char previousOp = '\0'; + bool relative = false; + for (;;) { + if (!data) { + // Truncated data + return {false, nullptr}; + } + data = skip_ws(data); + if (data[0] == '\0') { + break; + } + char ch = data[0]; + if (is_digit(ch) || ch == '-' || ch == '+' || ch == '.') { + if (op == '\0' || op == 'Z') { + return {false, nullptr}; + } + } else if (is_sep(ch)) { + data = skip_sep(data); + } else { + op = ch; + relative = false; + if (is_lower(op)) { + op = static_cast(to_upper(op)); + relative = true; + } + data++; + data = skip_sep(data); + } + switch (op) { + case 'M': // Move + data = find_points(data, points, 1, relative, &opOrigin); + // find_points might have failed, so this might be the + // previous point. However, data will be set to nullptr + // if it failed, so we will check this at the top of the loop. + path->moveTo(points[0]); + previousOp = '\0'; + op = 'L'; + opOrigin = points[0]; + break; + case 'L': // Line + data = find_points(data, points, 1, relative, &opOrigin); + path->lineTo(points[0]); + opOrigin = points[0]; + break; + case 'H': // Horizontal Line + data = find_scalar(data, &scratch, relative, opOrigin.x); + // Similarly, if there wasn't a scalar to read, data will + // be set to nullptr and this lineTo is bogus but will + // be ultimately ignored when the next time through the loop + // detects that and bails out. + path->lineTo(scratch, opOrigin.y); + opOrigin.x = scratch; + break; + case 'V': // Vertical Line + data = find_scalar(data, &scratch, relative, opOrigin.y); + path->lineTo(opOrigin.x, scratch); + opOrigin.y = scratch; + break; + case 'C': // Cubic Bezier Curve + data = find_points(data, points, 3, relative, &opOrigin); + goto cubicCommon; + case 'S': // Continued "Smooth" Cubic Bezier Curve + data = find_points(data, &points[1], 2, relative, &opOrigin); + points[0] = opOrigin; + if (previousOp == 'C' || previousOp == 'S') { + points[0].x -= lastOpOrigin.x - opOrigin.x; + points[0].y -= lastOpOrigin.y - opOrigin.y; + } + cubicCommon: + path->cubicTo(points[0], points[1], points[2]); + lastOpOrigin = points[1]; + opOrigin = points[2]; + break; + case 'Q': // Quadratic Bezier Curve + data = find_points(data, points, 2, relative, &opOrigin); + goto quadraticCommon; + case 'T': // Continued Quadratic Bezier Curve + data = find_points(data, &points[1], 1, relative, &opOrigin); + points[0] = opOrigin; + if (previousOp == 'Q' || previousOp == 'T') { + points[0].x -= lastOpOrigin.x - opOrigin.x; + points[0].y -= lastOpOrigin.y - opOrigin.y; + } + quadraticCommon: + path->quadTo(points[0], points[1]); + lastOpOrigin = points[0]; + opOrigin = points[1]; + break; + case 'A': { // Arc (Elliptical) + auto xyRadii = Point::Zero(); + float angle = 0.0f; + bool largeArc = true; + bool sweep = true; + + if (data = find_points(data, &xyRadii, 1, false, nullptr); !data) break; + if (data = skip_sep(data); !data) break; + if (data = find_scalar(data, &angle, false, 0); !data) break; + if (data = skip_sep(data); !data) break; + if (data = find_flag(data, &largeArc); !data) break; + if (data = skip_sep(data); !data) break; + if (data = find_flag(data, &sweep); !data) break; + if (data = skip_sep(data); !data) break; + if (data = find_points(data, &points[0], 1, relative, &opOrigin); !data) break; + + auto arcSize = largeArc ? PathArcSize::Large : PathArcSize::Small; + path->arcTo(xyRadii.x, xyRadii.y, angle, arcSize, !sweep, points[0]); + path->getLastPoint(&opOrigin); + } break; + case 'Z': // Close Path + path->close(); + opOrigin = first; + break; + default: + return {false, nullptr}; + } + if (previousOp == 0) { + first = opOrigin; + } + previousOp = op; + } + return {true, path}; +} + +const char* SVGParse::FindHex(const char str[], uint32_t* value) { + ASSERT(str); + str = skip_ws(str); + + if (!is_hex(*str)) { + return nullptr; + } + + uint32_t n = 0; + int max_digits = 8; + int digit; + while ((digit = to_hex(*str)) >= 0) { + if (--max_digits < 0) { + return nullptr; + } + n = (n << 4) | static_cast(digit); + str += 1; + } + + if (*str == 0 || is_ws(*str)) { + if (value) { + *value = n; + } + return str; + } + return nullptr; +} + +const char* SVGParse::FindS32(const char str[], int32_t* value) { + ASSERT(str); + str = skip_ws(str); + + int sign = 1; + int64_t maxAbsValue = std::numeric_limits::max(); + if (*str == '-') { + sign = -1; + maxAbsValue = -static_cast(std::numeric_limits::min()); + str += 1; + } + + if (!is_digit(*str)) { + return nullptr; + } + + int64_t n = 0; + while (is_digit(*str)) { + n = 10 * n + *str - '0'; + if (n > maxAbsValue) { + return nullptr; + } + + str += 1; + } + + if (value) { + *value = static_cast(sign * n); + } + return str; +} + +const char* SVGParse::FindScalar(const char str[], float* value) { + ASSERT(str); + str = skip_ws(str); + + char* stop; + auto v = static_cast(strtod(str, &stop)); + if (str == stop) { + return nullptr; + } + if (value) { + *value = v; + } + return stop; +} + +const char* SVGParse::FindScalars(const char str[], float value[], int count) { + ASSERT(count >= 0); + + if (count > 0) { + for (;;) { + str = SVGParse::FindScalar(str, value); + if (--count == 0 || str == nullptr) { + break; + } + + // keep going + str = skip_sep(str); + if (value) { + value += 1; + } + } + } + return str; +} + +bool SVGParse::FindBool(const char str[], bool* value) { + static const char* yesSet[] = {"yes", "1", "true"}; + static const char* noSet[] = {"no", "0", "false"}; + + if (lookup_str(str, yesSet, std::size(yesSet))) { + if (value) { + *value = true; + } + return true; + } else if (lookup_str(str, noSet, std::size(noSet))) { + if (value) { + *value = false; + } + return true; + } + return false; +} + +int SVGParse::FindList(const char target[], const char list[]) { + auto len = strlen(target); + int index = 0; + + for (;;) { + const char* end = strchr(list, ','); + size_t entryLen = end == nullptr ? strlen(list) : static_cast(end - list); + + if (entryLen == len && memcmp(target, list, len) == 0) { + return index; + } + if (end == nullptr) { + break; + } + + list = end + 1; // skip the ',' + index += 1; + } + return -1; +} + } // namespace tgfx \ No newline at end of file diff --git a/src/svg/SVGUtils.h b/src/svg/SVGUtils.h index 26b0eb74..5c429d1b 100644 --- a/src/svg/SVGUtils.h +++ b/src/svg/SVGUtils.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include "tgfx/core/Bitmap.h" #include "tgfx/core/Color.h" @@ -49,6 +50,8 @@ enum class PathEncoding { std::string ToSVGPath(const Path& path, PathEncoding = PathEncoding::Absolute); +std::tuple> PathMakeFromSVGString(const std::string& pathString); + std::string ToSVGTransform(const Matrix& matrix); /** @@ -69,6 +72,8 @@ std::string FloatToString(float value); void Base64Encode(unsigned char const* bytesToEncode, size_t length, char* ret); +std::shared_ptr Base64Decode(const std::string& encodedString); + /** * Returns data uri from bytes. * it will use any cached data if available, otherwise will encode as png. @@ -77,4 +82,23 @@ std::shared_ptr AsDataUri(const Pixmap& pixmap); std::shared_ptr AsDataUri(const std::shared_ptr& encodedData); +inline Color Uint32ToColor(uint32_t value) { + return Color::FromRGBA((value >> 16) & 0xff, (value >> 8) & 0xff, (value >> 0) & 0xff, + (value >> 24) & 0xff); +} + +// Common functions for SVG conversion, used to find specific data types in a string. +// Returns the char pointer after the found data, or nullptr if not found. +class SVGParse { + public: + static const char* FindHex(const char str[], uint32_t* value); + static const char* FindNamedColor(const char str[], Color* color); + static const char* FindS32(const char str[], int32_t* value); + static const char* FindScalar(const char str[], float* value); + static const char* FindScalars(const char str[], float value[], int count); + static bool FindBool(const char str[], bool* value); + // return the index of str in list[], or -1 if not found + static int FindList(const char target[], const char list[]); +}; + } // namespace tgfx \ No newline at end of file diff --git a/src/svg/SystemFontManager.cpp b/src/svg/SystemFontManager.cpp new file mode 100644 index 00000000..c99e831c --- /dev/null +++ b/src/svg/SystemFontManager.cpp @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SystemFontManager.h" +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "tgfx/core/FontStyle.h" +#include "tgfx/core/Typeface.h" + +namespace tgfx { + +std::shared_ptr SystemFontManager::onMatchTypeface(const std::string& familyName, + FontStyle style) const { + return Typeface::MakeFromStyle(familyName, style); +} + +std::shared_ptr SystemFontManager::onGetFallbackTypeface(const std::string& familyName, + FontStyle style, Unichar) const { + return onMatchTypeface(familyName, style); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SystemFontManager.h b/src/svg/SystemFontManager.h new file mode 100644 index 00000000..bcc76b47 --- /dev/null +++ b/src/svg/SystemFontManager.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include "tgfx/core/FontStyle.h" +#include "tgfx/core/Typeface.h" +#include "tgfx/svg/FontManager.h" + +namespace tgfx { + +class SystemFontManager : public FontManager { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SystemFontManager); + } + + protected: + SystemFontManager() = default; + + std::shared_ptr onMatchTypeface(const std::string& familyName, + FontStyle style) const override; + + std::shared_ptr onGetFallbackTypeface(const std::string& familyName, FontStyle style, + Unichar character) const override; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/SystemResourceLoader.h b/src/svg/SystemResourceLoader.h new file mode 100644 index 00000000..cf6ea340 --- /dev/null +++ b/src/svg/SystemResourceLoader.h @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/svg/ResourceLoader.h" + +namespace tgfx { +class SystemResourceLoader final : public ResourceLoader { + public: + static std::shared_ptr Make() { + return std::shared_ptr(new SystemResourceLoader); + } + + std::shared_ptr load(const std::string& resourcePath, + const std::string& resourceName) const override { + if (resourceName.empty() && resourcePath.empty()) { + return nullptr; + } + if (resourcePath.empty()) { + return Data::MakeFromFile(resourceName); + } + if (resourceName.empty()) { + return Data::MakeFromFile(resourcePath); + } + return Data::MakeFromFile(resourcePath + "/" + resourceName); + }; + + std::shared_ptr loadImage(const std::string& resourcePath, + const std::string& resourceName) const override { + if (resourceName.empty() && resourcePath.empty()) { + return nullptr; + } + if (resourcePath.empty()) { + return Image::MakeFromFile(resourceName); + } + if (resourceName.empty()) { + return Image::MakeFromFile(resourcePath); + } + return Image::MakeFromFile(resourcePath + "/" + resourceName); + } + + protected: + SystemResourceLoader() = default; +}; + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGCircle.cpp b/src/svg/node/SVGCircle.cpp new file mode 100644 index 00000000..3bf34b73 --- /dev/null +++ b/src/svg/node/SVGCircle.cpp @@ -0,0 +1,86 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGCircle.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +SVGCircle::SVGCircle() : INHERITED(SVGTag::Circle) { +} + +bool SVGCircle::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + setCx(SVGAttributeParser::parse("cx", name, value)) || + setCy(SVGAttributeParser::parse("cy", name, value)) || + setR(SVGAttributeParser::parse("r", name, value)); +} + +std::tuple SVGCircle::resolve(const SVGLengthContext& lengthContext) const { + const auto cx = lengthContext.resolve(Cx, SVGLengthContext::LengthType::Horizontal); + const auto cy = lengthContext.resolve(Cy, SVGLengthContext::LengthType::Vertical); + const auto r = lengthContext.resolve(R, SVGLengthContext::LengthType::Other); + + return std::make_tuple(Point::Make(cx, cy), r); +} + +void SVGCircle::onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType /*type*/) const { + auto [pos, r] = resolve(lengthContext); + + if (r > 0) { + canvas->drawCircle(pos.x, pos.y, r, paint); + } +} + +void SVGCircle::onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType /*fillType*/, + std::shared_ptr pathEffect) const { + if (!pathEffect) { + return; + } + + auto [pos, r] = resolve(lengthContext); + if (r > 0) { + Path path; + path.addOval(Rect::MakeXYWH(pos.x - r, pos.y - r, 2 * r, 2 * r)); + if (pathEffect->filterPath(&path)) { + canvas->drawPath(path, paint); + } + } +}; + +Path SVGCircle::onAsPath(const SVGRenderContext& context) const { + auto [pos, r] = this->resolve(context.lengthContext()); + + Path path; + path.addOval(Rect::MakeXYWH(pos.x - r, pos.y - r, 2 * r, 2 * r)); + this->mapToParent(&path); + return path; +} + +Rect SVGCircle::onObjectBoundingBox(const SVGRenderContext& context) const { + const auto [pos, r] = this->resolve(context.lengthContext()); + return Rect::MakeXYWH(pos.x - r, pos.y - r, 2 * r, 2 * r); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGClipPath.cpp b/src/svg/node/SVGClipPath.cpp new file mode 100644 index 00000000..d5d19eb8 --- /dev/null +++ b/src/svg/node/SVGClipPath.cpp @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGClipPath.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Path.h" + +namespace tgfx { + +SVGClipPath::SVGClipPath() : INHERITED(SVGTag::ClipPath) { +} + +bool SVGClipPath::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setClipPathUnits( + SVGAttributeParser::parse("clipPathUnits", name, value)); +} + +Path SVGClipPath::resolveClip(const SVGRenderContext& context) const { + auto clip = this->asPath(context); + + const auto transform = context.transformForCurrentBoundBox(ClipPathUnits); + const auto m = Matrix::MakeTrans(transform.offset.x, transform.offset.y) * + Matrix::MakeScale(transform.scale.x, transform.scale.y); + clip.transform(m); + + return clip; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGContainer.cpp b/src/svg/node/SVGContainer.cpp new file mode 100644 index 00000000..8ed9bfc7 --- /dev/null +++ b/src/svg/node/SVGContainer.cpp @@ -0,0 +1,75 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGContainer.h" +#include "core/utils/Log.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/PathTypes.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +class SVGRenderContext; + +SVGContainer::SVGContainer(SVGTag t) : INHERITED(t) { +} + +void SVGContainer::appendChild(std::shared_ptr node) { + ASSERT(node); + children.push_back(std::move(node)); +} + +const std::vector>& SVGContainer::getChildren() const { + return children; +} + +bool SVGContainer::hasChildren() const { + return !children.empty(); +} + +void SVGContainer::onRender(const SVGRenderContext& context) const { + for (const auto& i : children) { + i->render(context); + } +} + +Path SVGContainer::onAsPath(const SVGRenderContext& context) const { + Path path; + + for (const auto& child : children) { + const Path childPath = child->asPath(context); + path.addPath(childPath, PathOp::Union); + } + + this->mapToParent(&path); + return path; +} + +Rect SVGContainer::onObjectBoundingBox(const SVGRenderContext& context) const { + Rect bounds = Rect::MakeEmpty(); + + for (const auto& child : children) { + const Rect childBounds = child->objectBoundingBox(context); + bounds.join(childBounds); + } + + return bounds; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGEllipse.cpp b/src/svg/node/SVGEllipse.cpp new file mode 100644 index 00000000..c462e23f --- /dev/null +++ b/src/svg/node/SVGEllipse.cpp @@ -0,0 +1,79 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// +#include "tgfx/svg/node/SVGEllipse.h" +// #include "SVGRectPriv.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +SVGEllipse::SVGEllipse() : INHERITED(SVGTag::Ellipse) { +} + +bool SVGEllipse::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setCx(SVGAttributeParser::parse("cx", name, value)) || + this->setCy(SVGAttributeParser::parse("cy", name, value)) || + this->setRx(SVGAttributeParser::parse("rx", name, value)) || + this->setRy(SVGAttributeParser::parse("ry", name, value)); +} + +Rect SVGEllipse::resolve(const SVGLengthContext& lengthContext) const { + const auto cx = lengthContext.resolve(Cx, SVGLengthContext::LengthType::Horizontal); + const auto cy = lengthContext.resolve(Cy, SVGLengthContext::LengthType::Vertical); + + // https://www.w3.org/TR/SVG2/shapes.html#EllipseElement + // + // An auto value for either rx or ry is converted to a used value, following the rules given + // above for rectangles (but without any clamping based on width or height). + const auto [rx, ry] = lengthContext.resolveOptionalRadii(Rx, Ry); + + // A computed value of zero for either dimension, or a computed value of auto for both + // dimensions, disables rendering of the element. + return (rx > 0 && ry > 0) ? Rect::MakeXYWH(cx - rx, cy - ry, rx * 2, ry * 2) : Rect::MakeEmpty(); +} + +void SVGEllipse::onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType /*fillType*/) const { + canvas->drawOval(this->resolve(lengthContext), paint); +} + +void SVGEllipse::onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType /*fillType*/, + std::shared_ptr pathEffect) const { + if (!pathEffect) { + return; + } + + Path path; + path.addOval(resolve(lengthContext)); + if (pathEffect->filterPath(&path)) { + canvas->drawPath(path, paint); + } +}; + +Path SVGEllipse::onAsPath(const SVGRenderContext& context) const { + Path path; + path.addOval(this->resolve(context.lengthContext())); + this->mapToParent(&path); + return path; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFe.cpp b/src/svg/node/SVGFe.cpp new file mode 100644 index 00000000..d1eeef15 --- /dev/null +++ b/src/svg/node/SVGFe.cpp @@ -0,0 +1,145 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFe.h" +#include +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +std::shared_ptr SVGFe::makeImageFilter(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const { + return this->onMakeImageFilter(context, filterContext); +}; + +Rect SVGFe::resolveBoundaries(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const { + const auto x = X.has_value() ? *X : SVGLength(0, SVGLength::Unit::Percentage); + const auto y = Y.has_value() ? *Y : SVGLength(0, SVGLength::Unit::Percentage); + const auto w = Width.has_value() ? *Width : SVGLength(100, SVGLength::Unit::Percentage); + const auto h = Height.has_value() ? *Height : SVGLength(100, SVGLength::Unit::Percentage); + + return context.resolveOBBRect(x, y, w, h, filterContext.primitiveUnits()); +} + +static bool AnyIsStandardInput(const SVGFilterContext& filterContext, + const std::vector& inputs) { + for (const auto& in : inputs) { + switch (in.type()) { + case SVGFeInputType::Type::FilterPrimitiveReference: + break; + case SVGFeInputType::Type::SourceGraphic: + case SVGFeInputType::Type::SourceAlpha: + case SVGFeInputType::Type::BackgroundImage: + case SVGFeInputType::Type::BackgroundAlpha: + case SVGFeInputType::Type::FillPaint: + case SVGFeInputType::Type::StrokePaint: + return true; + case SVGFeInputType::Type::Unspecified: + // Unspecified means previous result (which may be SourceGraphic). + if (filterContext.previousResultIsSourceGraphic()) { + return true; + } + break; + } + } + + return false; +} + +Rect SVGFe::resolveFilterSubregion(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const { + // From https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion, + // the default filter effect subregion is equal to the union of the subregions defined + // for all "referenced nodes" (filter effect inputs). If there are no inputs, the + // default subregion is equal to the filter effects region + // (https://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion). + const std::vector inputs = this->getInputs(); + Rect defaultSubregion; + if (inputs.empty() || AnyIsStandardInput(filterContext, inputs)) { + defaultSubregion = filterContext.filterEffectsRegion(); + } else { + defaultSubregion = filterContext.filterPrimitiveSubregion(inputs[0]); + for (size_t i = 1; i < inputs.size(); i++) { + defaultSubregion.join(filterContext.filterPrimitiveSubregion(inputs[i])); + } + } + + // Next resolve the rect specified by the x, y, width, height attributes on this filter effect. + // If those attributes were given, they override the corresponding attribute of the default + // filter effect subregion calculated above. + const Rect boundaries = this->resolveBoundaries(context, filterContext); + + // Compute and return the fully resolved subregion. + return Rect::MakeXYWH(X.has_value() ? boundaries.left : defaultSubregion.left, + Y.has_value() ? boundaries.top : defaultSubregion.top, + Width.has_value() ? boundaries.width() : defaultSubregion.width(), + Height.has_value() ? boundaries.height() : defaultSubregion.height()); +} + +SVGColorspace SVGFe::resolveColorspace(const SVGRenderContext& context, + const SVGFilterContext&) const { + constexpr SVGColorspace DEFAULT_COLOR_SPACE = SVGColorspace::SRGB; + const SVGColorspace colorspace = + *context.presentationContext()._inherited.ColorInterpolationFilters; + return colorspace == SVGColorspace::Auto ? DEFAULT_COLOR_SPACE : colorspace; +} + +void SVGFe::applyProperties(SVGRenderContext* context) const { + this->onPrepareToRender(context); +} + +bool SVGFe::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setIn(SVGAttributeParser::parse("in", name, value)) || + this->setResult(SVGAttributeParser::parse("result", name, value)) || + this->setX(SVGAttributeParser::parse("x", name, value)) || + this->setY(SVGAttributeParser::parse("y", name, value)) || + this->setWidth(SVGAttributeParser::parse("width", name, value)) || + this->setHeight(SVGAttributeParser::parse("height", name, value)); +} + +template <> +bool SVGAttributeParser::parse(SVGFeInputType* type) { + static constexpr std::tuple typeMap[] = { + {"SourceGraphic", SVGFeInputType::Type::SourceGraphic}, + {"SourceAlpha", SVGFeInputType::Type::SourceAlpha}, + {"BackgroundImage", SVGFeInputType::Type::BackgroundImage}, + {"BackgroundAlpha", SVGFeInputType::Type::BackgroundAlpha}, + {"FillPaint", SVGFeInputType::Type::FillPaint}, + {"StrokePaint", SVGFeInputType::Type::StrokePaint}, + }; + + SVGStringType resultId; + SVGFeInputType::Type tempType; + bool parsedValue = false; + if (this->parseEnumMap(typeMap, &tempType)) { + *type = SVGFeInputType(tempType); + parsedValue = true; + } else if (parse(&resultId)) { + *type = SVGFeInputType(resultId); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeBlend.cpp b/src/svg/node/SVGFeBlend.cpp new file mode 100644 index 00000000..3f8b494a --- /dev/null +++ b/src/svg/node/SVGFeBlend.cpp @@ -0,0 +1,55 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeBlend.h" +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/ImageFilter.h" + +namespace tgfx { + +bool SVGFeBlend::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setIn2(SVGAttributeParser::parse("in2", name, value)) || + this->setBlendMode(SVGAttributeParser::parse("mode", name, value)); +} + +std::shared_ptr SVGFeBlend::onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const { + const SVGColorspace colorspace = this->resolveColorspace(context, filterContext); + const std::shared_ptr background = + filterContext.resolveInput(context, In2, colorspace); + const std::shared_ptr foreground = + filterContext.resolveInput(context, this->getIn(), colorspace); + return ImageFilter::Compose(background, foreground); + // TODO (YG),relay on ImageFilters::Blend to implement this +} + +template <> +bool SVGAttributeParser::parse(SVGFeBlend::Mode* mode) { + static constexpr std::tuple blendMap[] = { + {"normal", SVGFeBlend::Mode::Normal}, {"multiply", SVGFeBlend::Mode::Multiply}, + {"screen", SVGFeBlend::Mode::Screen}, {"darken", SVGFeBlend::Mode::Darken}, + {"lighten", SVGFeBlend::Mode::Lighten}, + }; + + return this->parseEnumMap(blendMap, mode) && this->parseEOSToken(); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeColorMatrix.cpp b/src/svg/node/SVGFeColorMatrix.cpp new file mode 100644 index 00000000..6a27e6c6 --- /dev/null +++ b/src/svg/node/SVGFeColorMatrix.cpp @@ -0,0 +1,158 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeColorMatrix.h" +#include +#include "core/utils/MathExtra.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "tgfx/core/ColorFilter.h" +#include "tgfx/core/ImageFilter.h" + +class SVGRenderContext; + +namespace tgfx { + +bool SVGFeColorMatrix::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setType(SVGAttributeParser::parse("type", name, value)) || + this->setValues(SVGAttributeParser::parse("values", name, value)); +} + +template <> +bool SVGAttributeParser::parse(SVGFeColorMatrixType* type) { + static constexpr std::tuple typeMap[] = { + {"matrix", SVGFeColorMatrixType::Matrix}, + {"saturate", SVGFeColorMatrixType::Saturate}, + {"hueRotate", SVGFeColorMatrixType::HueRotate}, + {"luminanceToAlpha", SVGFeColorMatrixType::LuminanceToAlpha}, + }; + + return this->parseEnumMap(typeMap, type) && this->parseEOSToken(); +} + +ColorMatrix SVGFeColorMatrix::makeMatrixForType() const { + if (Values.empty() && Type != SVGFeColorMatrixType::LuminanceToAlpha) { + return ColorMatrix(); + } + + switch (Type) { + case SVGFeColorMatrixType::Matrix: { + if (Values.size() < 20) { + return ColorMatrix(); + } + ColorMatrix m; + std::copy_n(Values.data(), 20, m.begin()); + return m; + } + case SVGFeColorMatrixType::Saturate: + return MakeSaturate(!Values.empty() ? Values[0] : 1); + case SVGFeColorMatrixType::HueRotate: + return MakeHueRotate(!Values.empty() ? Values[0] : 0); + case SVGFeColorMatrixType::LuminanceToAlpha: + return MakeLuminanceToAlpha(); + } +} + +ColorMatrix SVGFeColorMatrix::MakeSaturate(SVGNumberType sat) { + enum { + R_Scale = 0, + G_Scale = 6, + B_Scale = 12, + A_Scale = 18, + + R_Trans = 4, + G_Trans = 9, + B_Trans = 14, + A_Trans = 19, + }; + + constexpr float HueR = 0.213f; + constexpr float HueG = 0.715f; + constexpr float HueB = 0.072f; + + auto setrow = [](float row[], float r, float g, float b) { + row[0] = r; + row[1] = g; + row[2] = b; + }; + + ColorMatrix matrix; + + matrix.fill(0.0f); + float R = HueR * (1 - sat); + float G = HueG * (1 - sat); + float B = HueB * (1 - sat); + setrow(matrix.data() + 0, R + sat, G, B); + setrow(matrix.data() + 5, R, G + sat, B); + setrow(matrix.data() + 10, R, G, B + sat); + matrix[A_Scale] = 1; + return matrix; +} + +ColorMatrix SVGFeColorMatrix::MakeHueRotate(SVGNumberType degrees) { + float theta = DegreesToRadians(degrees); + SVGNumberType c = std::cos(theta); + SVGNumberType s = std::sin(theta); + return ColorMatrix{0.213f + c * 0.787f + s * -0.213f, + 0.715f + c * -0.715f + s * -0.715f, + 0.072f + c * -0.072f + s * 0.928f, + 0, + 0, + + 0.213f + c * -0.213f + s * 0.143f, + 0.715f + c * 0.285f + s * 0.140f, + 0.072f + c * -0.072f + s * -0.283f, + 0, + 0, + + 0.213f + c * -0.213f + s * -0.787f, + 0.715f + c * -0.715f + s * 0.715f, + 0.072f + c * 0.928f + s * 0.072f, + 0, + 0, + + 0, + 0, + 0, + 1, + 0}; +} + +/** See ITU-R Recommendation BT.709 at http://www.itu.int/rec/R-REC-BT.709/ .*/ +constexpr float LUM_COEFF_R = 0.2126f; +constexpr float LUM_COEFF_G = 0.7152f; +constexpr float LUM_COEFF_B = 0.0722f; + +ColorMatrix SVGFeColorMatrix::MakeLuminanceToAlpha() { + return ColorMatrix{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, LUM_COEFF_R, LUM_COEFF_G, LUM_COEFF_B, 0, 0}; +} + +std::shared_ptr SVGFeColorMatrix::onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const { + auto colorspace = resolveColorspace(context, filterContext); + auto colorFilter = ImageFilter::ColorFilter(ColorFilter::Matrix(makeMatrixForType())); + + if (auto inputFilter = filterContext.resolveInput(context, this->getIn(), colorspace)) { + colorFilter = ImageFilter::Compose(colorFilter, inputFilter); + } + return colorFilter; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeComponentTransfer.cpp b/src/svg/node/SVGFeComponentTransfer.cpp new file mode 100644 index 00000000..fb40af80 --- /dev/null +++ b/src/svg/node/SVGFeComponentTransfer.cpp @@ -0,0 +1,139 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeComponentTransfer.h" +#include +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +class SVGRenderContext; + +std::vector SVGFeFunc::getTable() const { + // https://www.w3.org/TR/SVG11/filters.html#feComponentTransferTypeAttribute + const auto make_linear = [this]() -> std::vector { + std::vector tbl(256); + const float slope = this->getSlope(); + const float intercept255 = this->getIntercept() * 255; + + for (size_t i = 0; i < 256; ++i) { + tbl[i] = static_cast( + std::clamp(std::round(intercept255 + static_cast(i) * slope), 0.0f, 255.0f)); + } + + return tbl; + }; + + const auto make_gamma = [this]() -> std::vector { + std::vector tbl(256); + const float exponent = this->getExponent(); + const float offset = this->getOffset(); + + for (size_t i = 0; i < 256; ++i) { + const float component = offset + std::pow(static_cast(i) * (1 / 255.f), exponent); + tbl[i] = static_cast(std::clamp(std::round(component * 255), 0.0f, 255.0f)); + } + + return tbl; + }; + + const auto lerp_from_table_values = [this](auto lerp_func) -> std::vector { + const auto& vals = this->getTableValues(); + if (vals.size() < 2 || vals.size() > 255) { + return {}; + } + + // number of interpolation intervals + const size_t n = vals.size() - 1; + + std::vector tbl(256); + for (size_t k = 0; k < n; ++k) { + // interpolation values + const SVGNumberType v0 = std::clamp(vals[k + 0], 0.f, 1.f); + const SVGNumberType v1 = std::clamp(vals[k + 1], 0.f, 1.f); + + // start/end component table indices + const size_t c_start = k * 255 / n; + const size_t c_end = (k + 1) * 255 / n; + ASSERT(c_end <= 255); + + for (size_t ci = c_start; ci < c_end; ++ci) { + const float lerp_t = static_cast(ci - c_start) / static_cast(c_end - c_start); + const float component = lerp_func(v0, v1, lerp_t); + ASSERT(component >= 0 && component <= 1); + + tbl[ci] = static_cast(std::round(component * 255)); + } + } + + tbl.back() = static_cast(std::round(255 * std::clamp(vals.back(), 0.f, 1.f))); + + return tbl; + }; + + const auto make_table = [&]() -> std::vector { + return lerp_from_table_values([](float v0, float v1, float t) { return v0 + (v1 - v0) * t; }); + }; + + const auto make_discrete = [&]() -> std::vector { + return lerp_from_table_values([](float v0, float /*v1*/, float /*t*/) { return v0; }); + }; + + switch (this->getType()) { + case SVGFeFuncType::Identity: + return {}; + case SVGFeFuncType::Table: + return make_table(); + case SVGFeFuncType::Discrete: + return make_discrete(); + case SVGFeFuncType::Linear: + return make_linear(); + case SVGFeFuncType::Gamma: + return make_gamma(); + } +} + +bool SVGFeFunc::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setAmplitude(SVGAttributeParser::parse("amplitude", name, value)) || + this->setExponent(SVGAttributeParser::parse("exponent", name, value)) || + this->setIntercept(SVGAttributeParser::parse("intercept", name, value)) || + this->setOffset(SVGAttributeParser::parse("offset", name, value)) || + this->setSlope(SVGAttributeParser::parse("slope", name, value)) || + this->setTableValues( + SVGAttributeParser::parse>("tableValues", name, value)) || + this->setType(SVGAttributeParser::parse("type", name, value)); +} + +template <> +bool SVGAttributeParser::parse(SVGFeFuncType* type) { + static constexpr std::tuple gTypeMap[] = { + {"identity", SVGFeFuncType::Identity}, {"table", SVGFeFuncType::Table}, + {"discrete", SVGFeFuncType::Discrete}, {"linear", SVGFeFuncType::Linear}, + {"gamma", SVGFeFuncType::Gamma}, + }; + + return this->parseEnumMap(gTypeMap, type) && this->parseEOSToken(); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeComposite.cpp b/src/svg/node/SVGFeComposite.cpp new file mode 100644 index 00000000..5e3eaf28 --- /dev/null +++ b/src/svg/node/SVGFeComposite.cpp @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeComposite.h" +#include +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +class SVGRenderContext; + +bool SVGFeComposite::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setIn2(SVGAttributeParser::parse("in2", name, value)) || + this->setK1(SVGAttributeParser::parse("k1", name, value)) || + this->setK2(SVGAttributeParser::parse("k2", name, value)) || + this->setK3(SVGAttributeParser::parse("k3", name, value)) || + this->setK4(SVGAttributeParser::parse("k4", name, value)) || + this->setOperator( + SVGAttributeParser::parse("operator", name, value)); +} + +BlendMode SVGFeComposite::BlendModeForOperator(SVGFeCompositeOperator op) { + switch (op) { + case SVGFeCompositeOperator::Over: + return BlendMode::SrcOver; + case SVGFeCompositeOperator::In: + return BlendMode::SrcIn; + case SVGFeCompositeOperator::Out: + return BlendMode::SrcOut; + case SVGFeCompositeOperator::Atop: + return BlendMode::SrcATop; + case SVGFeCompositeOperator::Xor: + return BlendMode::Xor; + case SVGFeCompositeOperator::Arithmetic: + // Arithmetic is not handled with a blend + ASSERT(false); + return BlendMode::SrcOver; + } +} + +std::shared_ptr SVGFeComposite::onMakeImageFilter( + const SVGRenderContext& /*context*/, const SVGFilterContext& /*filterContext*/) const { + //TODO (YGAurora) waiting for blend and arithmetic image filter implementation + return nullptr; +} + +template <> +bool SVGAttributeParser::parse(SVGFeCompositeOperator* op) { + static constexpr std::tuple opMap[] = { + {"over", SVGFeCompositeOperator::Over}, {"in", SVGFeCompositeOperator::In}, + {"out", SVGFeCompositeOperator::Out}, {"atop", SVGFeCompositeOperator::Atop}, + {"xor", SVGFeCompositeOperator::Xor}, {"arithmetic", SVGFeCompositeOperator::Arithmetic}, + }; + + return this->parseEnumMap(opMap, op) && this->parseEOSToken(); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeDisplacementMap.cpp b/src/svg/node/SVGFeDisplacementMap.cpp new file mode 100644 index 00000000..b9975c22 --- /dev/null +++ b/src/svg/node/SVGFeDisplacementMap.cpp @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeDisplacementMap.h" +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +bool SVGFeDisplacementMap::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setIn2(SVGAttributeParser::parse("in2", name, value)) || + this->setXChannelSelector(SVGAttributeParser::parse( + "xChannelSelector", name, value)) || + this->setYChannelSelector(SVGAttributeParser::parse( + "yChannelSelector", name, value)) || + this->setScale(SVGAttributeParser::parse("scale", name, value)); +} + +std::shared_ptr SVGFeDisplacementMap::onMakeImageFilter( + const SVGRenderContext&, const SVGFilterContext&) const { + //TODO (YGAurora): waiting for displacementMap image filter. + return nullptr; +} + +SVGColorspace SVGFeDisplacementMap::resolveColorspace(const SVGRenderContext& context, + const SVGFilterContext& filterContext) const { + // According to spec https://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement, + // the 'in' source image must remain in its current colorspace, which means the colorspace of + // this FE node is the same as the input. + return filterContext.resolveInputColorspace(context, this->getIn()); +} + +template <> +bool SVGAttributeParser::parse( + SVGFeDisplacementMap::ChannelSelector* channel) { + static constexpr std::tuple gMap[] = { + {"R", SVGFeDisplacementMap::ChannelSelector::R}, + {"G", SVGFeDisplacementMap::ChannelSelector::G}, + {"B", SVGFeDisplacementMap::ChannelSelector::B}, + {"A", SVGFeDisplacementMap::ChannelSelector::A}, + }; + + return this->parseEnumMap(gMap, channel) && this->parseEOSToken(); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeGaussianBlur.cpp b/src/svg/node/SVGFeGaussianBlur.cpp new file mode 100644 index 00000000..7cd7c08f --- /dev/null +++ b/src/svg/node/SVGFeGaussianBlur.cpp @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeGaussianBlur.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Point.h" + +namespace tgfx { + +bool SVGFeGaussianBlur::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setstdDeviation(SVGAttributeParser::parse( + "stdDeviation", name, value)); +} + +std::shared_ptr SVGFeGaussianBlur::onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const { + auto scale = context.transformForCurrentBoundBox(filterContext.primitiveUnits()).scale; + const auto sigmaX = stdDeviation.X * scale.x * 4 * context.matrix().getScaleX(); + const auto sigmaY = stdDeviation.Y * scale.y * 4 * context.matrix().getScaleY(); + return ImageFilter::Blur(sigmaX, sigmaY); +} + +template <> +bool SVGAttributeParser::parse( + SVGFeGaussianBlur::StdDeviation* stdDeviation) { + std::vector values; + if (!this->parse(&values)) { + return false; + } + + stdDeviation->X = values[0]; + stdDeviation->Y = values.size() > 1 ? values[1] : values[0]; + return true; +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeImage.cpp b/src/svg/node/SVGFeImage.cpp new file mode 100644 index 00000000..264a2e1c --- /dev/null +++ b/src/svg/node/SVGFeImage.cpp @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeImage.h" +#include "svg/SVGAttributeParser.h" + +namespace tgfx { + +bool SVGFeImage::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setHref(SVGAttributeParser::parse("xlink:href", name, value)) || + this->setPreserveAspectRatio( + SVGAttributeParser::parse("preserveAspectRatio", name, value)); +} + +std::shared_ptr SVGFeImage::onMakeImageFilter(const SVGRenderContext&, + const SVGFilterContext&) const { + return nullptr; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeLightSource.cpp b/src/svg/node/SVGFeLightSource.cpp new file mode 100644 index 00000000..9ac761d9 --- /dev/null +++ b/src/svg/node/SVGFeLightSource.cpp @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeLightSource.h" +#include "svg/SVGAttributeParser.h" + +namespace tgfx { + +bool SVGFeDistantLight::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setAzimuth(SVGAttributeParser::parse("azimuth", n, v)) || + this->setElevation(SVGAttributeParser::parse("elevation", n, v)); +} + +bool SVGFePointLight::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX(SVGAttributeParser::parse("x", n, v)) || + this->setY(SVGAttributeParser::parse("y", n, v)) || + this->setZ(SVGAttributeParser::parse("z", n, v)); +} + +bool SVGFeSpotLight::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX(SVGAttributeParser::parse("x", n, v)) || + this->setY(SVGAttributeParser::parse("y", n, v)) || + this->setZ(SVGAttributeParser::parse("z", n, v)) || + this->setPointsAtX(SVGAttributeParser::parse("pointsAtX", n, v)) || + this->setPointsAtY(SVGAttributeParser::parse("pointsAtY", n, v)) || + this->setPointsAtZ(SVGAttributeParser::parse("pointsAtZ", n, v)) || + this->setSpecularExponent( + SVGAttributeParser::parse("specularExponent", n, v)) || + this->setLimitingConeAngle( + SVGAttributeParser::parse("limitingConeAngle", n, v)); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeLighting.cpp b/src/svg/node/SVGFeLighting.cpp new file mode 100644 index 00000000..54c41846 --- /dev/null +++ b/src/svg/node/SVGFeLighting.cpp @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeLighting.h" +#include "svg/SVGAttributeParser.h" + +namespace tgfx { + +bool SVGFeLighting::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setSurfaceScale( + SVGAttributeParser::parse("surfaceScale", name, value)) || + this->setUnitLength(SVGAttributeParser::parse( + "kernelUnitLength", name, value)); +} + +template <> +bool SVGAttributeParser::parse( + SVGFeLighting::KernelUnitLength* kernelUnitLength) { + std::vector values; + if (!this->parse(&values)) { + return false; + } + + kernelUnitLength->Dx = values[0]; + kernelUnitLength->Dy = values.size() > 1 ? values[1] : values[0]; + return true; +} + +bool SVGFeSpecularLighting::parseAndSetAttribute(const std::string& name, + const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setSpecularConstant( + SVGAttributeParser::parse("specularConstant", name, value)) || + this->setSpecularExponent( + SVGAttributeParser::parse("specularExponent", name, value)); +} + +bool SVGFeDiffuseLighting::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setDiffuseConstant( + SVGAttributeParser::parse("diffuseConstant", name, value)); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeMerge.cpp b/src/svg/node/SVGFeMerge.cpp new file mode 100644 index 00000000..6d07e07f --- /dev/null +++ b/src/svg/node/SVGFeMerge.cpp @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeMerge.h" +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "tgfx/core/ImageFilter.h" + +namespace tgfx { + +// class SVGRenderContext; + +bool SVGFeMergeNode::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setIn(SVGAttributeParser::parse("in", name, value)); +} + +std::shared_ptr SVGFeMerge::onMakeImageFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const { + std::vector> mergeNodeFilters(children.size()); + + this->forEachChild([&](const SVGFeMergeNode* child) { + mergeNodeFilters.push_back(filterContext.resolveInput(context, child->getIn())); + }); + return ImageFilter::Compose(mergeNodeFilters); +} + +std::vector SVGFeMerge::getInputs() const { + std::vector inputs; + inputs.reserve(children.size()); + + this->forEachChild( + [&](const SVGFeMergeNode* child) { inputs.push_back(child->getIn()); }); + + return inputs; +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeMorphology.cpp b/src/svg/node/SVGFeMorphology.cpp new file mode 100644 index 00000000..b24f5cd0 --- /dev/null +++ b/src/svg/node/SVGFeMorphology.cpp @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeMorphology.h" +#include +#include "svg/SVGAttributeParser.h" + +namespace tgfx { + +bool SVGFeMorphology::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setMorphOperator( + SVGAttributeParser::parse("operator", name, value)) || + this->setMorphRadius( + SVGAttributeParser::parse("radius", name, value)); +} + +std::shared_ptr SVGFeMorphology::onMakeImageFilter(const SVGRenderContext&, + const SVGFilterContext&) const { + //TODO (YGAurora) waiting for morphology image filter. + return nullptr; +} + +template <> +bool SVGAttributeParser::parse(SVGFeMorphology::Operator* op) { + static constexpr std::tuple gMap[] = { + {"dilate", SVGFeMorphology::Operator::kDilate}, + {"erode", SVGFeMorphology::Operator::kErode}, + }; + + return this->parseEnumMap(gMap, op) && this->parseEOSToken(); +} + +template <> +bool SVGAttributeParser::parse(SVGFeMorphology::Radius* radius) { + std::vector values; + if (!this->parse(&values)) { + return false; + } + + radius->fX = values[0]; + radius->fY = values.size() > 1 ? values[1] : values[0]; + return true; +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeOffset.cpp b/src/svg/node/SVGFeOffset.cpp new file mode 100644 index 00000000..026427ff --- /dev/null +++ b/src/svg/node/SVGFeOffset.cpp @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFeOffset.h" +#include "svg/SVGAttributeParser.h" +#include "tgfx/core/ImageFilter.h" + +namespace tgfx { + +bool SVGFeOffset::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setDx(SVGAttributeParser::parse("dx", name, value)) || + this->setDy(SVGAttributeParser::parse("dy", name, value)); +} + +std::shared_ptr SVGFeOffset::onMakeImageFilter( + const SVGRenderContext& /*context*/, const SVGFilterContext& /*filterContext*/) const { + //TODO (YGaurora) offset filter not implemented + return nullptr; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFeTurbulence.cpp b/src/svg/node/SVGFeTurbulence.cpp new file mode 100644 index 00000000..d4cd2274 --- /dev/null +++ b/src/svg/node/SVGFeTurbulence.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "tgfx/svg/node/SVGFeTurbulence.h" +#include "svg/SVGAttributeParser.h" +#include "tgfx/core/ImageFilter.h" + +namespace tgfx { + +bool SVGFeTurbulence::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setNumOctaves( + SVGAttributeParser::parse("numOctaves", name, value)) || + this->setSeed(SVGAttributeParser::parse("seed", name, value)) || + this->setBaseFrequency(SVGAttributeParser::parse( + "baseFrequency", name, value)) || + this->setTurbulenceType( + SVGAttributeParser::parse("type", name, value)); +} + +template <> +bool SVGAttributeParser::parse(SVGFeTurbulenceBaseFrequency* freq) { + SVGNumberType freqX; + if (!this->parse(&freqX)) { + return false; + } + + SVGNumberType freqY; + this->parseCommaWspToken(); + if (this->parse(&freqY)) { + *freq = SVGFeTurbulenceBaseFrequency(freqX, freqY); + } else { + *freq = SVGFeTurbulenceBaseFrequency(freqX, freqX); + } + + return this->parseEOSToken(); +} + +template <> +bool SVGAttributeParser::parse(SVGFeTurbulenceType* type) { + bool parsedValue = false; + + if (this->parseExpectedStringToken("fractalNoise")) { + *type = SVGFeTurbulenceType(SVGFeTurbulenceType::Type::FractalNoise); + parsedValue = true; + } else if (this->parseExpectedStringToken("turbulence")) { + *type = SVGFeTurbulenceType(SVGFeTurbulenceType::Type::Turbulence); + parsedValue = true; + } + + return parsedValue && this->parseEOSToken(); +} + +std::shared_ptr SVGFeTurbulence::onMakeImageFilter(const SVGRenderContext&, + const SVGFilterContext&) const { + //TODO (YGAurora) waiting for turbulence image filter. + return nullptr; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGFilter.cpp b/src/svg/node/SVGFilter.cpp new file mode 100644 index 00000000..e0b22ff2 --- /dev/null +++ b/src/svg/node/SVGFilter.cpp @@ -0,0 +1,206 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGFilter.h" +#include +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGFilterContext.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/ImageFilter.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/node/SVGFe.h" +#include "tgfx/svg/node/SVGFeBlend.h" +#include "tgfx/svg/node/SVGFeColorMatrix.h" +#include "tgfx/svg/node/SVGFeComposite.h" +#include "tgfx/svg/node/SVGFeGaussianBlur.h" +#include "tgfx/svg/node/SVGFeOffset.h" +#include "tgfx/svg/node/SVGNode.h" + +namespace tgfx { + +bool SVGFilter::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setX(SVGAttributeParser::parse("x", name, value)) || + this->setY(SVGAttributeParser::parse("y", name, value)) || + this->setWidth(SVGAttributeParser::parse("width", name, value)) || + this->setHeight(SVGAttributeParser::parse("height", name, value)) || + this->setFilterUnits( + SVGAttributeParser::parse("filterUnits", name, value)) || + this->setPrimitiveUnits( + SVGAttributeParser::parse("primitiveUnits", name, value)); +} + +void SVGFilter::applyProperties(SVGRenderContext* context) const { + this->onPrepareToRender(context); +} + +std::shared_ptr SVGFilter::buildFilterDAG(const SVGRenderContext& context) const { + std::shared_ptr filter; + SVGFilterContext filterContext(context.resolveOBBRect(X, Y, Width, Height, FilterUnits), + PrimitiveUnits); + SVGRenderContext localContext(context); + this->applyProperties(&localContext); + if (auto dropShadowFilter = buildDropShadowFilter(localContext, filterContext)) { + return dropShadowFilter; + } + if (auto innerShadowFilter = buildInnerShadowFilter(localContext, filterContext)) { + return innerShadowFilter; + } + + SVGColorspace cs = SVGColorspace::SRGB; + for (const auto& child : children) { + if (!SVGFe::IsFilterEffect(child)) { + continue; + } + + const auto& feNode = static_cast(*child); + const auto& feResultType = feNode.getResult(); + + // Propagate any inherited properties that may impact filter effect behavior (e.g. + // color-interpolation-filters). We call this explicitly here because the SVGFe + // nodes do not participate in the normal onRender path, which is when property + // propagation currently occurs. + SVGRenderContext localChildCtx(localContext); + feNode.applyProperties(&localChildCtx); + + const Rect filterSubregion = feNode.resolveFilterSubregion(localChildCtx, filterContext); + cs = feNode.resolveColorspace(localChildCtx, filterContext); + filter = feNode.makeImageFilter(localChildCtx, filterContext); + if (!filter) { + return nullptr; + } + + if (!feResultType.empty()) { + filterContext.registerResult(feResultType, filter, filterSubregion, cs); + } + + // Unspecified 'in' and 'in2' inputs implicitly resolve to the previous filter's result. + filterContext.setPreviousResult(filter, filterSubregion, cs); + } + + //TODO (YGAurora): Implement color space conversion + return filter; +} + +std::shared_ptr SVGFilter::buildDropShadowFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const { + if (children.size() < 3) { + return nullptr; + } + + size_t blurIndex = 0; + for (size_t i = 0; i < children.size(); i++) { + if (children[i]->tag() == SVGTag::FeGaussianBlur && i >= 1 && + children[i - 1]->tag() == SVGTag::FeOffset) { + blurIndex = i; + break; + } + } + + if (blurIndex < 1 || blurIndex == children.size() - 1) { + return nullptr; + } + + std::shared_ptr offsetFe = + std::static_pointer_cast(children[blurIndex - 1]); + std::shared_ptr blurFe = + std::static_pointer_cast(children[blurIndex]); + std::shared_ptr colorMatrixFe = nullptr; + + if (blurIndex + 1 < children.size() && children[blurIndex + 1]->tag() == SVGTag::FeColorMatrix) { + colorMatrixFe = std::static_pointer_cast(children[blurIndex + 1]); + } else if (blurIndex + 2 < children.size() && + children[blurIndex + 1]->tag() == SVGTag::FeComposite && + children[blurIndex + 2]->tag() == SVGTag::FeColorMatrix) { + auto compositeFe = std::static_pointer_cast(children[blurIndex + 1]); + if (compositeFe->getOperator() == SVGFeCompositeOperator::Arithmetic) { + return nullptr; + } + colorMatrixFe = std::static_pointer_cast(children[blurIndex + 2]); + } else { + return nullptr; + } + + auto scale = context.transformForCurrentBoundBox(filterContext.primitiveUnits()).scale; + auto dx = offsetFe->getDx() * scale.x * context.matrix().getScaleX(); + auto dy = offsetFe->getDy() * scale.y * context.matrix().getScaleY(); + + auto blurrinessX = blurFe->getstdDeviation().X * scale.x * 4 * context.matrix().getScaleX(); + auto blurrinessY = blurFe->getstdDeviation().Y * scale.y * 4 * context.matrix().getScaleY(); + + auto colorMatrix = colorMatrixFe->getValues(); + Color color{colorMatrix[4], colorMatrix[9], colorMatrix[14], colorMatrix[18]}; + + return ImageFilter::DropShadow(dx, dy, blurrinessX, blurrinessY, color); +} + +std::shared_ptr SVGFilter::buildInnerShadowFilter( + const SVGRenderContext& context, const SVGFilterContext& filterContext) const { + if (children.size() < 4) { + return nullptr; + } + size_t compositeIndex = 0; + for (size_t i = 0; i < children.size(); i++) { + if (children[i]->tag() == SVGTag::FeComposite) { + auto compositeFe = std::static_pointer_cast(children[i]); + if (compositeFe->getOperator() == SVGFeCompositeOperator::Arithmetic) { + compositeIndex = i; + break; + } + } + } + if (compositeIndex < 2 || compositeIndex + 1 >= children.size()) { + return nullptr; + } + if (children[compositeIndex + 1]->tag() == SVGTag::FeColorMatrix) { + std::shared_ptr blurFe; + std::shared_ptr offsetFe; + + if (children[compositeIndex - 2]->tag() == SVGTag::FeGaussianBlur && + children[compositeIndex - 1]->tag() == SVGTag::FeOffset) { + blurFe = std::static_pointer_cast(children[compositeIndex - 2]); + offsetFe = std::static_pointer_cast(children[compositeIndex - 1]); + } else if (children[compositeIndex - 2]->tag() == SVGTag::FeOffset && + children[compositeIndex - 1]->tag() == SVGTag::FeGaussianBlur) { + offsetFe = std::static_pointer_cast(children[compositeIndex - 2]); + blurFe = std::static_pointer_cast(children[compositeIndex - 1]); + } else { + return nullptr; + } + + auto scale = context.transformForCurrentBoundBox(filterContext.primitiveUnits()).scale; + auto dx = offsetFe->getDx() * scale.x * context.matrix().getScaleX(); + auto dy = offsetFe->getDy() * scale.y * context.matrix().getScaleY(); + auto blurrinessX = blurFe->getstdDeviation().X * scale.x * 4 * context.matrix().getScaleX(); + auto blurrinessY = blurFe->getstdDeviation().Y * scale.y * 4 * context.matrix().getScaleY(); + auto colorMatrixFe = std::static_pointer_cast(children[compositeIndex + 1]); + auto colorMatrix = colorMatrixFe->getValues(); + Color color{colorMatrix[4], colorMatrix[9], colorMatrix[14], colorMatrix[18]}; + + if (compositeIndex + 2 < children.size() && + children[compositeIndex + 2]->tag() == SVGTag::FeBlend) { + return ImageFilter::InnerShadow(dx, dy, blurrinessX, blurrinessY, color); + } + return ImageFilter::InnerShadowOnly(dx, dy, blurrinessX, blurrinessY, color); + } + return nullptr; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGGradient.cpp b/src/svg/node/SVGGradient.cpp new file mode 100644 index 00000000..5c54bbde --- /dev/null +++ b/src/svg/node/SVGGradient.cpp @@ -0,0 +1,117 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGGradient.h" +#include +#include +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Color.h" +#include "tgfx/core/Matrix.h" + +namespace tgfx { + +bool SVGGradient::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setGradientTransform( + SVGAttributeParser::parse("gradientTransform", name, value)) || + this->setHref(SVGAttributeParser::parse("xlink:href", name, value)) || + this->setSpreadMethod( + SVGAttributeParser::parse("spreadMethod", name, value)) || + this->setGradientUnits( + SVGAttributeParser::parse("gradientUnits", name, value)); +} + +// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute +void SVGGradient::collectColorStops(const SVGRenderContext& context, std::vector& colors, + std::vector& positions) const { + // Used to resolve percentage offsets. + const SVGLengthContext ltx(Size::Make(1, 1)); + + this->forEachChild([&](const SVGStop* stop) { + colors.push_back(this->resolveStopColor(context, *stop)); + positions.push_back( + std::clamp(ltx.resolve(stop->getOffset(), SVGLengthContext::LengthType::Other), 0.f, 1.f)); + }); + + ASSERT(colors.size() == positions.size()); + + if (positions.empty() && !Href.iri().empty()) { + const auto ref = context.findNodeById(Href); + if (ref && (ref->tag() == SVGTag::LinearGradient || ref->tag() == SVGTag::RadialGradient)) { + static_cast(ref.get())->collectColorStops(context, colors, positions); + } + } +} + +Color SVGGradient::resolveStopColor(const SVGRenderContext& context, const SVGStop& stop) const { + const auto& stopColor = stop.getStopColor(); + const auto& stopOpacity = stop.getStopOpacity(); + // Uninherited presentation attrs should have a concrete value at this point. + if (!stopColor.isValue() || !stopOpacity.isValue()) { + return Color::Black(); + } + + const auto color = context.resolveSVGColor(*stopColor); + + return {color.red, color.green, color.blue, *stopOpacity * color.alpha}; +} + +bool SVGGradient::onAsPaint(const SVGRenderContext& context, Paint* paint) const { + std::vector colors; + std::vector positions; + + this->collectColorStops(context, colors, positions); + + const auto tileMode = static_cast(SpreadMethod.type()); + + const auto transform = context.transformForCurrentBoundBox(GradientUnits); + const auto localMatrix = Matrix::MakeTrans(transform.offset.x, transform.offset.y) * + Matrix::MakeScale(transform.scale.x, transform.scale.y) * + GradientTransform; + + paint->setShader(this->onMakeShader(context, colors, positions, tileMode, localMatrix)); + return true; +} + +// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute +template <> +bool SVGAttributeParser::parse(SVGSpreadMethod* spread) { + struct TileInfo { + SVGSpreadMethod::Type type; + const char* name; + }; + static const TileInfo spreadInfoSet[] = { + {SVGSpreadMethod::Type::Pad, "pad"}, + {SVGSpreadMethod::Type::Reflect, "reflect"}, + {SVGSpreadMethod::Type::Repeat, "repeat"}, + }; + + bool parsedValue = false; + for (auto info : spreadInfoSet) { + if (this->parseExpectedStringToken(info.name)) { + *spread = SVGSpreadMethod(info.type); + parsedValue = true; + break; + } + } + + return parsedValue && this->parseEOSToken(); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGImage.cpp b/src/svg/node/SVGImage.cpp new file mode 100644 index 00000000..3e1cadc2 --- /dev/null +++ b/src/svg/node/SVGImage.cpp @@ -0,0 +1,122 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGImage.h" +#include +#include +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "svg/SVGUtils.h" +#include "tgfx/core/Data.h" +#include "tgfx/core/Image.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +bool SVGImage::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX(SVGAttributeParser::parse("x", n, v)) || + this->setY(SVGAttributeParser::parse("y", n, v)) || + this->setWidth(SVGAttributeParser::parse("width", n, v)) || + this->setHeight(SVGAttributeParser::parse("height", n, v)) || + this->setHref(SVGAttributeParser::parse("xlink:href", n, v)) || + this->setPreserveAspectRatio( + SVGAttributeParser::parse("preserveAspectRatio", n, v)); +} + +bool SVGImage::onPrepareToRender(SVGRenderContext* context) const { + // Width or height of 0 disables rendering per spec: + // https://www.w3.org/TR/SVG11/struct.html#ImageElement + return !Href.iri().empty() && Width.value() > 0 && Height.value() > 0 && + INHERITED::onPrepareToRender(context); +} + +std::shared_ptr LoadImage(const std::shared_ptr& resourceProvider, + const SVGIRI& href) { + + switch (href.type()) { + case SVGIRI::Type::DataURI: { + const auto& base64URL = href.iri(); + auto pos = base64URL.find("base64,"); + if (pos == std::string::npos) { + return nullptr; + } + std::string base64Data = base64URL.substr(pos + 7); + auto data = Base64Decode(base64Data); + return data ? Image::MakeFromEncoded(data) : nullptr; + } + case SVGIRI::Type::Nonlocal: { + std::filesystem::path path(href.iri()); + auto directory = path.parent_path().string(); + auto filename = path.filename().string(); + return resourceProvider->loadImage(directory, filename); + } + default: + return nullptr; + } +} + +SVGImage::ImageInfo SVGImage::LoadImage(const std::shared_ptr& resourceProvider, + const SVGIRI& iri, const Rect& viewPort, + SVGPreserveAspectRatio /*ratio*/) { + std::shared_ptr image = ::tgfx::LoadImage(resourceProvider, iri); + if (!image) { + return {}; + } + + // Per spec: raster content has implicit viewbox of '0 0 width height'. + const Rect viewBox = Rect::MakeWH(image->width(), image->height()); + + // Map and place at x, y specified by viewport + Rect dst = viewBox.makeOffset(viewPort.left, viewPort.top); + + return {std::move(image), dst}; +} + +void SVGImage::onRender(const SVGRenderContext& context) const { + // Per spec: x, w, width, height attributes establish the new viewport. + const SVGLengthContext& lengthContext = context.lengthContext(); + const Rect viewPort = lengthContext.resolveRect(X, Y, Width, Height); + + ImageInfo image; + const auto imgInfo = LoadImage(context.resourceProvider(), Href, viewPort, PreserveAspectRatio); + if (!imgInfo.image) { + LOGE("can't render image: load image failed\n"); + return; + } + + auto matrix = Matrix::MakeScale(viewPort.width() / imgInfo.destinationRect.width(), + viewPort.height() / imgInfo.destinationRect.height()); + matrix.preTranslate(imgInfo.destinationRect.x(), imgInfo.destinationRect.y()); + + context.canvas()->drawImage(imgInfo.image, matrix); +} + +Path SVGImage::onAsPath(const SVGRenderContext&) const { + return {}; +} + +Rect SVGImage::onObjectBoundingBox(const SVGRenderContext& context) const { + const SVGLengthContext& lengthContext = context.lengthContext(); + return lengthContext.resolveRect(X, Y, Width, Height); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGLine.cpp b/src/svg/node/SVGLine.cpp new file mode 100644 index 00000000..9b8afa93 --- /dev/null +++ b/src/svg/node/SVGLine.cpp @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGLine.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Point.h" + +namespace tgfx { + +SVGLine::SVGLine() : INHERITED(SVGTag::Line) { +} + +bool SVGLine::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX1(SVGAttributeParser::parse("x1", n, v)) || + this->setY1(SVGAttributeParser::parse("y1", n, v)) || + this->setX2(SVGAttributeParser::parse("x2", n, v)) || + this->setY2(SVGAttributeParser::parse("y2", n, v)); +} + +std::tuple SVGLine::resolve(const SVGLengthContext& lengthContext) const { + return std::make_tuple( + Point::Make(lengthContext.resolve(X1, SVGLengthContext::LengthType::Horizontal), + lengthContext.resolve(Y1, SVGLengthContext::LengthType::Vertical)), + Point::Make(lengthContext.resolve(X2, SVGLengthContext::LengthType::Horizontal), + lengthContext.resolve(Y2, SVGLengthContext::LengthType::Vertical))); +} + +void SVGLine::onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType) const { + auto [p0, p1] = this->resolve(lengthContext); + canvas->drawLine(p0, p1, paint); +} + +void SVGLine::onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType /*fillType*/, + std::shared_ptr pathEffect) const { + if (!pathEffect) { + return; + } + + auto [p0, p1] = this->resolve(lengthContext); + + Path path; + path.moveTo(p0); + path.lineTo(p1); + if (pathEffect->filterPath(&path)) { + canvas->drawPath(path, paint); + } +}; + +Path SVGLine::onAsPath(const SVGRenderContext& context) const { + auto [p0, p1] = this->resolve(context.lengthContext()); + + //TODO (YG) path add methods to support line + Path path; + path.moveTo(p0); + path.lineTo(p1); + this->mapToParent(&path); + + return path; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGLinearGradient.cpp b/src/svg/node/SVGLinearGradient.cpp new file mode 100644 index 00000000..f69eb1f0 --- /dev/null +++ b/src/svg/node/SVGLinearGradient.cpp @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGLinearGradient.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/Shader.h" +#include "tgfx/core/TileMode.h" + +namespace tgfx { + +SVGLinearGradient::SVGLinearGradient() : INHERITED(SVGTag::LinearGradient) { +} + +bool SVGLinearGradient::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setX1(SVGAttributeParser::parse("x1", name, value)) || + this->setY1(SVGAttributeParser::parse("y1", name, value)) || + this->setX2(SVGAttributeParser::parse("x2", name, value)) || + this->setY2(SVGAttributeParser::parse("y2", name, value)); +} + +std::shared_ptr SVGLinearGradient::onMakeShader(const SVGRenderContext& context, + const std::vector& colors, + const std::vector& positions, + TileMode /*tileMode*/, + const Matrix& /*localMatrix*/) const { + SVGLengthContext lengthContext = context.lengthContext(); + lengthContext.setPatternUnits(getGradientUnits()); + + auto startPoint = Point::Make(lengthContext.resolve(X1, SVGLengthContext::LengthType::Horizontal), + lengthContext.resolve(Y1, SVGLengthContext::LengthType::Vertical)); + auto endPoint = Point::Make(lengthContext.resolve(X2, SVGLengthContext::LengthType::Horizontal), + lengthContext.resolve(Y2, SVGLengthContext::LengthType::Vertical)); + + return Shader::MakeLinearGradient(startPoint, endPoint, colors, positions); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGMask.cpp b/src/svg/node/SVGMask.cpp new file mode 100644 index 00000000..799313bd --- /dev/null +++ b/src/svg/node/SVGMask.cpp @@ -0,0 +1,91 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGMask.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/ColorFilter.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Recorder.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +bool SVGMask::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX(SVGAttributeParser::parse("x", n, v)) || + this->setY(SVGAttributeParser::parse("y", n, v)) || + this->setWidth(SVGAttributeParser::parse("width", n, v)) || + this->setHeight(SVGAttributeParser::parse("height", n, v)) || + this->setMaskUnits( + SVGAttributeParser::parse("maskUnits", n, v)) || + this->setMaskContentUnits( + SVGAttributeParser::parse("maskContentUnits", n, v)) || + this->setMaskType(SVGAttributeParser::parse("mask-type", n, v)); +} + +Rect SVGMask::bounds(const SVGRenderContext& context) const { + auto lengthContext = context.lengthContext(); + lengthContext.setPatternUnits(MaskUnits); + SVGRenderContext resolveContext(context, lengthContext); + if (Width.has_value() && Height.has_value()) { + return resolveContext.resolveOBBRect(X.value_or(SVGLength(0, SVGLength::Unit::Number)), + Y.value_or(SVGLength(0, SVGLength::Unit::Number)), + Width.value(), Height.value(), MaskUnits); + } + return Rect::MakeEmpty(); +} + +/** See ITU-R Recommendation BT.709 at http://www.itu.int/rec/R-REC-BT.709/ .*/ +constexpr float LUM_COEFF_R = 0.2126f; +constexpr float LUM_COEFF_G = 0.7152f; +constexpr float LUM_COEFF_B = 0.0722f; + +constexpr std::array MakeLuminanceToAlpha() { + return std::array{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, LUM_COEFF_R, LUM_COEFF_G, LUM_COEFF_B, 0, 0}; +} + +void SVGMask::renderMask(const SVGRenderContext& context) const { + // https://www.w3.org/TR/SVG11/masking.html#Masking + // Propagate any inherited properties that may impact mask effect behavior (e.g. + // color-interpolation). We call this explicitly here because the SkSVGMask + // nodes do not participate in the normal onRender path, which is when property + // propagation currently occurs. + // The local context also restores the filter layer created below on scope exit. + + int saveCount = context.canvas()->getSaveCount(); + if (MaskType.type() != SVGMaskType::Type::Alpha) { + auto luminanceFilter = ColorFilter::Matrix(MakeLuminanceToAlpha()); + Paint luminancePaint; + luminancePaint.setColorFilter(luminanceFilter); + context.canvas()->saveLayer(&luminancePaint); + } + + { + SVGRenderContext localContext(context); + this->onPrepareToRender(&localContext); + for (const auto& child : children) { + child->render(localContext); + } + } + context.canvas()->restoreToCount(saveCount); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGNode.cpp b/src/svg/node/SVGNode.cpp new file mode 100644 index 00000000..3e194934 --- /dev/null +++ b/src/svg/node/SVGNode.cpp @@ -0,0 +1,191 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGNode.h" +#include +#include +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGNodeConstructor.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Color.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/PathTypes.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +SVGNode::SVGNode(SVGTag t) : _tag(t) { + // Uninherited presentation attributes need a non-null default value. + presentationAttributes.StopColor.set(SVGColor(Color::Black())); + presentationAttributes.StopOpacity.set(static_cast(1.0f)); + presentationAttributes.FloodColor.set(SVGColor(Color::Black())); + presentationAttributes.FloodOpacity.set(static_cast(1.0f)); + presentationAttributes.LightingColor.set(SVGColor(Color::White())); +} + +SVGNode::~SVGNode() = default; + +void SVGNode::render(const SVGRenderContext& context) const { + SVGRenderContext localContext(context, this); + + if (this->onPrepareToRender(&localContext)) { + this->onRender(localContext); + } +} + +bool SVGNode::asPaint(const SVGRenderContext& context, Paint* paint) const { + SVGRenderContext localContext(context); + + return this->onPrepareToRender(&localContext) && this->onAsPaint(localContext, paint); +} + +Path SVGNode::asPath(const SVGRenderContext& context) const { + SVGRenderContext localContext(context); + if (!this->onPrepareToRender(&localContext)) { + return Path(); + } + + Path path = this->onAsPath(localContext); + if (auto clipPath = localContext.clipPath(); !clipPath.isEmpty()) { + // There is a clip-path present on the current node. + path.addPath(clipPath, PathOp::Intersect); + } + + return path; +} + +Rect SVGNode::objectBoundingBox(const SVGRenderContext& context) const { + return this->onObjectBoundingBox(context); +} + +bool SVGNode::onPrepareToRender(SVGRenderContext* context) const { + // Apply the inheritance of presentation attributes + context->applyPresentationAttributes( + presentationAttributes, + this->hasChildren() ? 0 : static_cast(SVGRenderContext::ApplyFlags::Leaf)); + + // visibility:hidden and display:none disable rendering. + const auto visibility = context->presentationContext()._inherited.Visibility->type(); + // display is uninherited + const auto display = presentationAttributes.Display; + return visibility != SVGVisibility::Type::Hidden && + (!display.isValue() || *display != SVGDisplay::None); +} + +void SVGNode::setAttribute(SVGAttribute attribute, const SVGValue& value) { + this->onSetAttribute(attribute, value); +} + +bool SVGNode::setAttribute(const std::string& attributeName, const std::string& attributeValue) { + return SVGNodeConstructor::SetAttribute(*this, attributeName, attributeValue); +} + +template +void SetInheritedByDefault(std::optional& presentation_attribute, const T& value) { + if (value.type() != T::Type::kInherit) { + presentation_attribute.set(value); + } else { + // kInherited values are semantically equivalent to + // the absence of a local presentation attribute. + presentation_attribute.reset(); + } +} + +bool SVGNode::parseAndSetAttribute(const std::string& name, const std::string& value) { +#define PARSE_AND_SET(svgName, attrName) \ + this->set##attrName( \ + SVGAttributeParser::parseProperty(svgName, name, \ + value)) + + return PARSE_AND_SET("clip-path", ClipPath) || PARSE_AND_SET("clip-rule", ClipRule) || + PARSE_AND_SET("color", Color) || + PARSE_AND_SET("color-interpolation", ColorInterpolation) || + PARSE_AND_SET("color-interpolation-filters", ColorInterpolationFilters) || + PARSE_AND_SET("display", Display) || PARSE_AND_SET("fill", Fill) || + PARSE_AND_SET("fill-opacity", FillOpacity) || PARSE_AND_SET("fill-rule", FillRule) || + PARSE_AND_SET("filter", Filter) || PARSE_AND_SET("flood-color", FloodColor) || + PARSE_AND_SET("flood-opacity", FloodOpacity) || PARSE_AND_SET("font-family", FontFamily) || + PARSE_AND_SET("font-size", FontSize) || PARSE_AND_SET("font-style", FontStyle) || + PARSE_AND_SET("font-weight", FontWeight) || + PARSE_AND_SET("lighting-color", LightingColor) || PARSE_AND_SET("mask", Mask) || + PARSE_AND_SET("opacity", Opacity) || PARSE_AND_SET("stop-color", StopColor) || + PARSE_AND_SET("stop-opacity", StopOpacity) || PARSE_AND_SET("stroke", Stroke) || + PARSE_AND_SET("stroke-dasharray", StrokeDashArray) || + PARSE_AND_SET("stroke-dashoffset", StrokeDashOffset) || + PARSE_AND_SET("stroke-linecap", StrokeLineCap) || + PARSE_AND_SET("stroke-linejoin", StrokeLineJoin) || + PARSE_AND_SET("stroke-miterlimit", StrokeMiterLimit) || + PARSE_AND_SET("stroke-opacity", StrokeOpacity) || + PARSE_AND_SET("stroke-width", StrokeWidth) || PARSE_AND_SET("text-anchor", TextAnchor) || + PARSE_AND_SET("visibility", Visibility); + +#undef PARSE_AND_SET +} + +// https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute +Matrix SVGNode::ComputeViewboxMatrix(const Rect& viewBox, const Rect& viewPort, + SVGPreserveAspectRatio PreAspectRatio) { + if (viewBox.isEmpty() || viewPort.isEmpty()) { + return Matrix::MakeScale(0, 0); + } + + auto computeScale = [&]() -> Point { + const auto scaleX = viewPort.width() / viewBox.width(); + const auto scaleY = viewPort.height() / viewBox.height(); + + if (PreAspectRatio.align == SVGPreserveAspectRatio::Align::None) { + // none -> anisotropic scaling, regardless of fScale + return {scaleX, scaleY}; + } + + // isotropic scaling + const auto s = PreAspectRatio.scale == SVGPreserveAspectRatio::Scale::Meet + ? std::min(scaleX, scaleY) + : std::max(scaleX, scaleY); + return {s, s}; + }; + + auto computeTrans = [&](const Point& scale) -> Point { + static constexpr float alignCoeffs[] = { + 0.0f, // Min + 0.5f, // Mid + 1.0f // Max + }; + + const size_t x_coeff = static_cast(PreAspectRatio.align) >> 0 & 0x03; + const size_t y_coeff = static_cast(PreAspectRatio.align) >> 2 & 0x03; + + const auto tx = -viewBox.x() * scale.x; + const auto ty = -viewBox.y() * scale.y; + const auto dx = viewPort.width() - viewBox.width() * scale.x; + const auto dy = viewPort.height() - viewBox.height() * scale.y; + + return {tx + dx * alignCoeffs[x_coeff], ty + dy * alignCoeffs[y_coeff]}; + }; + + auto scale = computeScale(); + auto transform = computeTrans(scale); + + return Matrix::MakeTrans(transform.x, transform.y) * Matrix::MakeScale(scale.x, scale.y); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGPath.cpp b/src/svg/node/SVGPath.cpp new file mode 100644 index 00000000..140a97e7 --- /dev/null +++ b/src/svg/node/SVGPath.cpp @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGPath.h" +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "svg/SVGUtils.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +SVGPath::SVGPath() : INHERITED(SVGTag::Path) { +} + +bool SVGPath::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setShapePath(SVGAttributeParser::parse("d", n, v)); +} + +template <> +bool SVGAttributeParser::parse(Path* path) { + auto [success, parsePath] = PathMakeFromSVGString(currentPos); + if (success) { + *path = *parsePath; + } + return success; +} + +void SVGPath::onDrawFill(Canvas* canvas, const SVGLengthContext&, const Paint& paint, + PathFillType fillType) const { + // the passed fillType follows inheritance rules and needs to be applied at draw time. + Path path = ShapePath; + path.setFillType(fillType); + canvas->drawPath(path, paint); +} + +void SVGPath::onDrawStroke(Canvas* canvas, const SVGLengthContext& /*lengthContext*/, + const Paint& paint, PathFillType fillType, + std::shared_ptr pathEffect) const { + if (!pathEffect) { + return; + } + + Path path = ShapePath; + path.setFillType(fillType); + canvas->drawPath(path, paint); + if (pathEffect->filterPath(&path)) { + canvas->drawPath(path, paint); + } +}; + +Path SVGPath::onAsPath(const SVGRenderContext& context) const { + Path path = ShapePath; + // clip-rule can be inherited and needs to be applied at clip time. + path.setFillType(context.presentationContext()._inherited.ClipRule->asFillType()); + this->mapToParent(&path); + return path; +} + +Rect SVGPath::onObjectBoundingBox(const SVGRenderContext&) const { + return ShapePath.getBounds(); + //TODO (YGAurora): Implement thigh bounds computation +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGPattern.cpp b/src/svg/node/SVGPattern.cpp new file mode 100644 index 00000000..35f7382b --- /dev/null +++ b/src/svg/node/SVGPattern.cpp @@ -0,0 +1,161 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGPattern.h" +#include +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Paint.h" +#include "tgfx/core/Rect.h" +#include "tgfx/core/Shader.h" +#include "tgfx/core/Surface.h" +#include "tgfx/core/TileMode.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { + +SVGPattern::SVGPattern() : INHERITED(SVGTag::Pattern) { +} + +bool SVGPattern::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setX(SVGAttributeParser::parse("x", name, value)) || + this->setY(SVGAttributeParser::parse("y", name, value)) || + this->setWidth(SVGAttributeParser::parse("width", name, value)) || + this->setHeight(SVGAttributeParser::parse("height", name, value)) || + this->setPatternTransform( + SVGAttributeParser::parse("patternTransform", name, value)) || + this->setHref(SVGAttributeParser::parse("xlink:href", name, value)) || + this->setPatternUnits( + SVGAttributeParser::parse("patternUnits", name, value)) || + this->setContentUnits(SVGAttributeParser::parse( + "patternContentUnits", name, value)); +} + +const SVGPattern* SVGPattern::hrefTarget(const SVGRenderContext& context) const { + if (Href.iri().empty()) { + return nullptr; + } + + const auto href = context.findNodeById(Href); + if (!href || href->tag() != SVGTag::Pattern) { + return nullptr; + } + + return static_cast(href.get()); +} + +template +int inherit_if_needed(const std::optional& src, std::optional& dst) { + if (!dst.has_value()) { + dst = src; + return 1; + } + + return 0; +} + +/* https://www.w3.org/TR/SVG11/pservers.html#PatternElementHrefAttribute + * + * Any attributes which are defined on the referenced element which are not defined on this element + * are inherited by this element. If this element has no children, and the referenced element does + * (possibly due to its own ‘xlink:href’ attribute), then this element inherits the children from + * the referenced element. Inheritance can be indirect to an arbitrary level; thus, if the + * referenced element inherits attributes or children due to its own ‘xlink:href’ attribute, then + * the current element can inherit those attributes or children. + */ +const SVGPattern* SVGPattern::resolveHref(const SVGRenderContext& context, + PatternAttributes* attrs) const { + const SVGPattern* currentNode = this; + const SVGPattern* contentNode = this; + + do { + // Bitwise OR to avoid short-circuiting. + const bool didInherit = + inherit_if_needed(currentNode->X, attrs->x) | inherit_if_needed(currentNode->Y, attrs->y) | + inherit_if_needed(currentNode->Width, attrs->width) | + inherit_if_needed(currentNode->Height, attrs->height) | + inherit_if_needed(currentNode->PatternTransform, attrs->patternTransform); + + if (!contentNode->hasChildren()) { + contentNode = currentNode; + } + + if (contentNode->hasChildren() && !didInherit) { + // All attributes have been resolved, and a valid content node has been found. + // We can terminate the href chain early. + break; + } + + currentNode = currentNode->hrefTarget(context); + } while (currentNode); + + // To unify with Chrome and macOS preview, the width and height attributes here need to be + // converted to percentages, direct numbers are not supported. + if (PatternUnits.type() == SVGObjectBoundingBoxUnits::Type::UserSpaceOnUse) { + if (attrs->width.has_value() && attrs->width->unit() == SVGLength::Unit::Percentage) { + attrs->width = SVGLength(attrs->width->value() / 100.f, SVGLength::Unit::Number); + } + if (attrs->height.has_value() && attrs->height->unit() == SVGLength::Unit::Percentage) { + attrs->height = SVGLength(attrs->height->value() / 100.f, SVGLength::Unit::Number); + } + } else if (PatternUnits.type() == SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox) { + if (attrs->width.has_value() && attrs->width->unit() == SVGLength::Unit::Number) { + attrs->width = SVGLength(attrs->width->value() * 100.f, SVGLength::Unit::Percentage); + } + if (attrs->height.has_value() && attrs->height->unit() == SVGLength::Unit::Number) { + attrs->height = SVGLength(attrs->height->value() * 100.f, SVGLength::Unit::Percentage); + } + } + + return contentNode; +} + +bool SVGPattern::onAsPaint(const SVGRenderContext& context, Paint* paint) const { + PatternAttributes attrs; + const auto* contentNode = this->resolveHref(context, &attrs); + auto lengthContext = context.lengthContext(); + lengthContext.setPatternUnits(PatternUnits); + Rect tile = lengthContext.resolveRect(attrs.x.has_value() ? *attrs.x : SVGLength(0), + attrs.y.has_value() ? *attrs.y : SVGLength(0), + attrs.width.has_value() ? *attrs.width : SVGLength(0), + attrs.height.has_value() ? *attrs.height : SVGLength(0)); + + if (tile.isEmpty()) { + return false; + } + + Recorder patternRecorder; + auto* canvas = patternRecorder.beginRecording(); + auto patternMatrix = attrs.patternTransform.value_or(Matrix::I()); + canvas->concat(patternMatrix); + { + SVGRenderContext recordingContext(context, canvas, lengthContext); + contentNode->SVGContainer::onRender(recordingContext); + } + auto picture = patternRecorder.finishRecordingAsPicture(); + auto shaderImage = + Image::MakeFrom(picture, static_cast(tile.width()), static_cast(tile.height())); + auto shader = Shader::MakeImageShader(shaderImage, TileMode::Repeat, TileMode::Repeat); + paint->setShader(shader); + return true; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGPoly.cpp b/src/svg/node/SVGPoly.cpp new file mode 100644 index 00000000..0a17a4ca --- /dev/null +++ b/src/svg/node/SVGPoly.cpp @@ -0,0 +1,83 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGPoly.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Path.h" + +namespace tgfx { + +SVGPoly::SVGPoly(SVGTag t) : INHERITED(t) { +} + +bool SVGPoly::parseAndSetAttribute(const std::string& n, const std::string& v) { + if (INHERITED::parseAndSetAttribute(n, v)) { + return true; + } + + if (this->setPoints(SVGAttributeParser::parse("points", n, v))) { + if (!Points.empty()) { + path.reset(); + path.moveTo(Points[0]); + for (uint32_t i = 1; i < Points.size(); i++) { + path.lineTo(Points[i]); + } + path.close(); + } + } + return false; +} + +void SVGPoly::onDrawFill(Canvas* canvas, const SVGLengthContext&, const Paint& paint, + PathFillType fillType) const { + // the passed fillType follows inheritance rules and needs to be applied at draw time. + path.setFillType(fillType); + canvas->drawPath(path, paint); +} + +void SVGPoly::onDrawStroke(Canvas* canvas, const SVGLengthContext& /*lengthContext*/, + const Paint& paint, PathFillType fillType, + std::shared_ptr pathEffect) const { + if (!pathEffect) { + return; + } + + path.setFillType(fillType); + canvas->drawPath(path, paint); + if (pathEffect->filterPath(&path)) { + canvas->drawPath(path, paint); + } +}; + +Path SVGPoly::onAsPath(const SVGRenderContext& context) const { + Path resultPath = path; + + // clip-rule can be inherited and needs to be applied at clip time. + resultPath.setFillType(context.presentationContext()._inherited.ClipRule->asFillType()); + + this->mapToParent(&resultPath); + return resultPath; +} + +Rect SVGPoly::onObjectBoundingBox(const SVGRenderContext&) const { + return path.getBounds(); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGRadialGradient.cpp b/src/svg/node/SVGRadialGradient.cpp new file mode 100644 index 00000000..21b57052 --- /dev/null +++ b/src/svg/node/SVGRadialGradient.cpp @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGRadialGradient.h" +#include +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Color.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Shader.h" +#include "tgfx/core/TileMode.h" + +namespace tgfx { + +SVGRadialGradient::SVGRadialGradient() : INHERITED(SVGTag::RadialGradient) { +} + +bool SVGRadialGradient::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setCx(SVGAttributeParser::parse("cx", name, value)) || + this->setCy(SVGAttributeParser::parse("cy", name, value)) || + this->setR(SVGAttributeParser::parse("r", name, value)) || + this->setFx(SVGAttributeParser::parse("fx", name, value)) || + this->setFy(SVGAttributeParser::parse("fy", name, value)); +} + +std::shared_ptr SVGRadialGradient::onMakeShader(const SVGRenderContext& context, + const std::vector& colors, + const std::vector& position, + TileMode, const Matrix& matrix) const { + SVGLengthContext lengthContext = context.lengthContext(); + lengthContext.setPatternUnits(getGradientUnits()); + + auto radius = lengthContext.resolve(R, SVGLengthContext::LengthType::Other); + auto center = Point::Make(lengthContext.resolve(Cx, SVGLengthContext::LengthType::Horizontal), + lengthContext.resolve(Cy, SVGLengthContext::LengthType::Vertical)); + + // TODO(YGAurora): MakeTwoPointConical are unimplemented in tgfx + if (radius == 0) { + const auto lastColor = !colors.empty() ? *colors.end() : Color::Black(); + return Shader::MakeColorShader(lastColor); + } + matrix.mapPoints(¢er, 1); + radius *= matrix.getAxisScales().x; + return Shader::MakeRadialGradient(center, radius, colors, position); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGRect.cpp b/src/svg/node/SVGRect.cpp new file mode 100644 index 00000000..e3c2e027 --- /dev/null +++ b/src/svg/node/SVGRect.cpp @@ -0,0 +1,106 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGRect.h" +#include +// #include "SVGRectPriv.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Canvas.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Point.h" +#include "tgfx/core/RRect.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +SVGRect::SVGRect() : INHERITED(SVGTag::Rect) { +} + +bool SVGRect::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX(SVGAttributeParser::parse("x", n, v)) || + this->setY(SVGAttributeParser::parse("y", n, v)) || + this->setWidth(SVGAttributeParser::parse("width", n, v)) || + this->setHeight(SVGAttributeParser::parse("height", n, v)) || + this->setRx(SVGAttributeParser::parse("rx", n, v)) || + this->setRy(SVGAttributeParser::parse("ry", n, v)); +} + +RRect SVGRect::resolve(const SVGLengthContext& lengthContext) const { + const auto rect = lengthContext.resolveRect(X, Y, Width, Height); + const auto [rx, ry] = lengthContext.resolveOptionalRadii(Rx, Ry); + + // https://www.w3.org/TR/SVG2/shapes.html#RectElement + // ... + // 3. Finally, apply clamping to generate the used values: + // 1. If the absolute rx (after the above steps) is greater than half of the used width, + // then the used value of rx is half of the used width. + // 2. If the absolute ry (after the above steps) is greater than half of the used height, + // then the used value of ry is half of the used height. + // 3. Otherwise, the used values of rx and ry are the absolute values computed previously. + + RRect rrect; + rrect.setRectXY(rect, std::min(rx, rect.width() / 2), std::min(ry, rect.height() / 2)); + return rrect; +} + +void SVGRect::onDrawFill(Canvas* canvas, const SVGLengthContext& lengthContext, const Paint& paint, + PathFillType) const { + auto rrect = this->resolve(lengthContext); + auto offset = Point::Make(rrect.rect.left, rrect.rect.top); + rrect.rect = rrect.rect.makeOffset(-offset.x, -offset.y); + canvas->save(); + canvas->translate(offset.x, offset.y); + canvas->drawRRect(rrect, paint); + canvas->restore(); +} + +void SVGRect::onDrawStroke(Canvas* canvas, const SVGLengthContext& lengthContext, + const Paint& paint, PathFillType /*fillType*/, + std::shared_ptr pathEffect) const { + if (!pathEffect) { + return; + } + + auto rrect = this->resolve(lengthContext); + auto offset = Point::Make(rrect.rect.left, rrect.rect.top); + rrect.rect = rrect.rect.makeOffset(-offset.x, -offset.y); + Path path; + path.addRRect(rrect); + if (pathEffect->filterPath(&path)) { + canvas->save(); + canvas->translate(offset.x, offset.y); + canvas->drawPath(path, paint); + canvas->restore(); + } +}; + +Path SVGRect::onAsPath(const SVGRenderContext& context) const { + Path path; + path.addRRect(this->resolve(context.lengthContext())); + this->mapToParent(&path); + + return path; +} + +Rect SVGRect::onObjectBoundingBox(const SVGRenderContext& context) const { + return context.lengthContext().resolveRect(X, Y, Width, Height); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGRoot.cpp b/src/svg/node/SVGRoot.cpp new file mode 100644 index 00000000..0955d936 --- /dev/null +++ b/src/svg/node/SVGRoot.cpp @@ -0,0 +1,133 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGRoot.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Size.h" +#include "tgfx/svg/SVGAttribute.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/SVGValue.h" + +namespace tgfx { + +void SVGRoot::renderNode(const SVGRenderContext& context, const SVGIRI& iri) const { + SVGRenderContext localContext(context, this); + auto node = localContext.findNodeById(iri); + if (!node) { + return; + } + + if (this->onPrepareToRender(&localContext)) { + if (this == node.get()) { + this->onRender(context); + } else { + node->render(localContext); + } + } +} + +bool SVGRoot::onPrepareToRender(SVGRenderContext* context) const { + // x/y are ignored for outermost svg elements + const auto x = type == Type::kInner ? X : SVGLength(0); + const auto y = type == Type::kInner ? Y : SVGLength(0); + + auto viewPortRect = context->lengthContext().resolveRect(x, y, Width, Height); + auto contentMatrix = Matrix::MakeTrans(viewPortRect.x(), viewPortRect.y()); + auto viewPort = Size::Make(viewPortRect.width(), viewPortRect.height()); + + if (ViewBox.has_value()) { + const Rect& viewBox = *ViewBox; + + // An empty viewbox disables rendering. + if (viewBox.isEmpty()) { + return false; + } + + // A viewBox overrides the intrinsic viewport. + viewPort = Size::Make(viewBox.width(), viewBox.height()); + + contentMatrix.preConcat(ComputeViewboxMatrix(viewBox, viewPortRect, PreserveAspectRatio)); + } + + if (!contentMatrix.isIdentity()) { + context->saveOnce(); + context->canvas()->concat(contentMatrix); + } + + if (viewPort != context->lengthContext().viewPort()) { + context->writableLengthContext()->setViewPort(viewPort); + } + + return this->INHERITED::onPrepareToRender(context); +} + +// https://www.w3.org/TR/SVG11/coords.html#IntrinsicSizing +Size SVGRoot::intrinsicSize(const SVGLengthContext& lengthContext) const { + // Percentage values do not provide an intrinsic size. + if (Width.unit() == SVGLength::Unit::Percentage || Height.unit() == SVGLength::Unit::Percentage) { + return Size::Make(0, 0); + } + + return Size::Make(lengthContext.resolve(Width, SVGLengthContext::LengthType::Horizontal), + lengthContext.resolve(Height, SVGLengthContext::LengthType::Vertical)); +} + +void SVGRoot::onSetAttribute(SVGAttribute attr, const SVGValue& v) { + if (type != Type::kInner && type != Type::kRoot) return; + switch (attr) { + case SVGAttribute::X: + if (const auto* x = v.as()) { + SVGLength xValue = *x; + this->setX(xValue); + } + break; + case SVGAttribute::Y: + if (const auto* y = v.as()) { + SVGLength yValue = *y; + this->setY(yValue); + } + break; + case SVGAttribute::Width: + if (const auto* w = v.as()) { + SVGLength wValue = *w; + this->setWidth(wValue); + } + break; + case SVGAttribute::Height: + if (const auto* h = v.as()) { + SVGLength hValue = *h; + this->setHeight(hValue); + } + break; + case SVGAttribute::ViewBox: + if (const auto* vb = v.as()) { + SVGViewBoxType vbValue = *vb; + this->setViewBox(vbValue); + } + break; + case SVGAttribute::PreserveAspectRatio: + if (const auto* par = v.as()) { + SVGPreserveAspectRatio parValue = *par; + this->setPreserveAspectRatio(parValue); + } + break; + default: + this->INHERITED::onSetAttribute(attr, v); + } +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGShape.cpp b/src/svg/node/SVGShape.cpp new file mode 100644 index 00000000..6433add1 --- /dev/null +++ b/src/svg/node/SVGShape.cpp @@ -0,0 +1,59 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGShape.h" +#include +#include "core/utils/Log.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Size.h" + +namespace tgfx { + +SVGShape::SVGShape(SVGTag t) : INHERITED(t) { +} + +void SVGShape::onRender(const SVGRenderContext& context) const { + const auto fillType = context.presentationContext()._inherited.FillRule->asFillType(); + + auto selfRect = onObjectBoundingBox(context); + auto lengthContext = context.lengthContext(); + lengthContext.setViewPort(Size::Make(selfRect.width(), selfRect.height())); + auto paintContext = SVGRenderContext::CopyForPaint(context, context.canvas(), lengthContext); + + auto fillPaint = paintContext.fillPaint(); + auto strokePaint = paintContext.strokePaint(); + + if (fillPaint.has_value()) { + onDrawFill(context.canvas(), context.lengthContext(), fillPaint.value(), fillType); + } + + if (strokePaint.has_value()) { + auto strokePathEffect = context.strokePathEffect(); + if (strokePathEffect) { + onDrawStroke(context.canvas(), context.lengthContext(), strokePaint.value(), fillType, + strokePathEffect); + } else { + onDrawFill(context.canvas(), context.lengthContext(), strokePaint.value(), fillType); + } + } +} + +void SVGShape::appendChild(std::shared_ptr) { + LOGE("cannot append child nodes to an SVG shape.\n"); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGStop.cpp b/src/svg/node/SVGStop.cpp new file mode 100644 index 00000000..8cd624d5 --- /dev/null +++ b/src/svg/node/SVGStop.cpp @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGStop.h" +#include "svg/SVGAttributeParser.h" + +namespace tgfx { + +SVGStop::SVGStop() : INHERITED(SVGTag::Stop) { +} + +bool SVGStop::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setOffset(SVGAttributeParser::parse("offset", n, v)); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGText.cpp b/src/svg/node/SVGText.cpp new file mode 100644 index 00000000..85acd78c --- /dev/null +++ b/src/svg/node/SVGText.cpp @@ -0,0 +1,188 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGText.h" +#include +#include +#include +#include +#include "core/utils/Log.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Font.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/TextBlob.h" +#include "tgfx/svg/SVGTypes.h" + +namespace tgfx { +namespace { + +std::vector ResolveLengths(const SVGRenderContext& context, + const std::vector& lengths, + SVGLengthContext::LengthType lengthType) { + auto lengthContext = context.lengthContext(); + std::vector resolved; + resolved.reserve(lengths.size()); + + for (const auto& length : lengths) { + if (length.unit() == SVGLength::Unit::EMS || length.unit() == SVGLength::Unit::EXS) { + auto fontSize = context.presentationContext()._inherited.FontSize->size(); + auto resolvedLength = + lengthContext.resolve(fontSize, SVGLengthContext::LengthType::Horizontal) * + length.value(); + resolved.push_back(resolvedLength); + } else { + resolved.push_back(lengthContext.resolve(length, lengthType)); + } + } + + return resolved; +} + +} // namespace + +void SVGTextContainer::appendChild(std::shared_ptr child) { + // Only allow text content child nodes. + switch (child->tag()) { + case SVGTag::TextLiteral: + case SVGTag::TextPath: + case SVGTag::TSpan: + children.push_back(std::static_pointer_cast(child)); + break; + default: + break; + } +} + +void SVGTextFragment::renderText(const SVGRenderContext& context, + const ShapedTextCallback& function) const { + SVGRenderContext localContext(context); + if (this->onPrepareToRender(&localContext)) { + this->onShapeText(localContext, function); + } +} + +Path SVGTextFragment::onAsPath(const SVGRenderContext&) const { + // TODO (YGAurora) + return Path(); +} + +void SVGTextContainer::onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const { + + auto x = ResolveLengths(context, X, SVGLengthContext::LengthType::Horizontal); + auto y = ResolveLengths(context, Y, SVGLengthContext::LengthType::Vertical); + auto dx = ResolveLengths(context, Dx, SVGLengthContext::LengthType::Horizontal); + auto dy = ResolveLengths(context, Dy, SVGLengthContext::LengthType::Vertical); + + // TODO (YGAurora) : Handle rotate + for (uint32_t i = 0; i < children.size(); i++) { + auto child = children[i]; + context.canvas()->save(); + float offsetX = (i < x.size() ? x[i] : 0.0f) + (i < dx.size() ? dx[i] : 0.0f); + float offsetY = (i < y.size() ? y[i] : 0.0f) + (i < dy.size() ? dy[i] : 0.0f); + context.canvas()->translate(offsetX, offsetY); + child->renderText(context, function); + context.canvas()->restore(); + } +} + +bool SVGTextContainer::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setX(SVGAttributeParser::parse>("x", name, value)) || + this->setY(SVGAttributeParser::parse>("y", name, value)) || + this->setDx(SVGAttributeParser::parse>("dx", name, value)) || + this->setDy(SVGAttributeParser::parse>("dy", name, value)) || + this->setRotate( + SVGAttributeParser::parse>("rotate", name, value)); +} + +void SVGTextLiteral::onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const { + + auto font = context.resolveFont(); + if (!font.getTypeface()) { + return; + } + auto textBlob = TextBlob::MakeFrom(Text, font); + function(context, textBlob); +} + +void SVGText::onRender(const SVGRenderContext& context) const { + + auto renderer = [](const SVGRenderContext& context, + const std::shared_ptr& textBlob) -> void { + if (!textBlob) { + return; + } + + auto bound = textBlob->getBounds(); + float x = + context.presentationContext()._inherited.TextAnchor->getAlignmentFactor() * bound.width(); + float y = 0; + + auto lengthContext = context.lengthContext(); + lengthContext.setViewPort(Size::Make(bound.width(), bound.height())); + SVGRenderContext paintContext(context, context.canvas(), lengthContext); + auto fillPaint = paintContext.fillPaint(); + auto strokePaint = paintContext.strokePaint(); + + if (fillPaint.has_value()) { + context.canvas()->drawTextBlob(textBlob, x, y, fillPaint.value()); + } + if (strokePaint.has_value()) { + context.canvas()->drawTextBlob(textBlob, x, y, strokePaint.value()); + } + }; + + this->onShapeText(context, renderer); +} + +Rect SVGText::onObjectBoundingBox(const SVGRenderContext& context) const { + Rect bounds = Rect::MakeEmpty(); + + auto boundCollector = [&bounds](const SVGRenderContext&, + const std::shared_ptr& textBlob) -> void { + if (!textBlob) { + return; + } + auto textBound = textBlob->getBounds(); + bounds.join(textBound); + }; + + this->onShapeText(context, boundCollector); + return bounds; +} + +Path SVGText::onAsPath(const SVGRenderContext&) const { + // TODO (YGAurora) + return Path(); +} + +void SVGTextPath::onShapeText(const SVGRenderContext& context, + const ShapedTextCallback& function) const { + this->INHERITED::onShapeText(context, function); +} + +bool SVGTextPath::parseAndSetAttribute(const std::string& name, const std::string& value) { + return INHERITED::parseAndSetAttribute(name, value) || + this->setHref(SVGAttributeParser::parse("xlink:href", name, value)) || + this->setStartOffset(SVGAttributeParser::parse("startOffset", name, value)); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGTransformableNode.cpp b/src/svg/node/SVGTransformableNode.cpp new file mode 100644 index 00000000..7dee50a0 --- /dev/null +++ b/src/svg/node/SVGTransformableNode.cpp @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGTransformableNode.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Matrix.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" +#include "tgfx/svg/SVGAttribute.h" +#include "tgfx/svg/SVGTypes.h" +#include "tgfx/svg/SVGValue.h" + +namespace tgfx { + +SVGTransformableNode::SVGTransformableNode(SVGTag tag) : INHERITED(tag), transform(Matrix::I()) { +} + +bool SVGTransformableNode::onPrepareToRender(SVGRenderContext* context) const { + if (!transform.isIdentity()) { + auto tempTransform = transform; + if (auto unit = context->lengthContext().getPatternUnits(); + unit.has_value() && + unit.value().type() == SVGObjectBoundingBoxUnits::Type::ObjectBoundingBox) { + tempTransform.postScale(context->lengthContext().viewPort().width, + context->lengthContext().viewPort().height); + } + context->saveOnce(); + context->canvas()->concat(tempTransform); + context->concat(tempTransform); + } + + return this->INHERITED::onPrepareToRender(context); +} + +void SVGTransformableNode::onSetAttribute(SVGAttribute attr, const SVGValue& v) { + switch (attr) { + case SVGAttribute::Transform: + if (const auto* transform = v.as()) { + this->setTransform(*transform); + } + break; + default: + this->INHERITED::onSetAttribute(attr, v); + break; + } +} + +void SVGTransformableNode::mapToParent(Path* path) const { + // transforms the path to parent node coordinates. + path->transform(transform); +} + +void SVGTransformableNode::mapToParent(Rect* rect) const { + *rect = transform.mapRect(*rect); +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/node/SVGUse.cpp b/src/svg/node/SVGUse.cpp new file mode 100644 index 00000000..1758a4f5 --- /dev/null +++ b/src/svg/node/SVGUse.cpp @@ -0,0 +1,93 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/svg/node/SVGUse.h" +#include "core/utils/MathExtra.h" +#include "svg/SVGAttributeParser.h" +#include "svg/SVGRenderContext.h" +#include "tgfx/core/Path.h" +#include "tgfx/core/Rect.h" + +namespace tgfx { + +SVGUse::SVGUse() : INHERITED(SVGTag::Use) { +} + +bool SVGUse::parseAndSetAttribute(const std::string& n, const std::string& v) { + return INHERITED::parseAndSetAttribute(n, v) || + this->setX(SVGAttributeParser::parse("x", n, v)) || + this->setY(SVGAttributeParser::parse("y", n, v)) || + this->setHref(SVGAttributeParser::parse("xlink:href", n, v)); +} + +bool SVGUse::onPrepareToRender(SVGRenderContext* context) const { + if (Href.iri().empty() || !INHERITED::onPrepareToRender(context)) { + return false; + } + + if (!FloatNearlyZero(X.value()) || !FloatNearlyZero(Y.value())) { + // Restored when the local SVGRenderContext leaves scope. + context->saveOnce(); + context->canvas()->translate(X.value(), Y.value()); + } + + return true; +} + +void SVGUse::onRender(const SVGRenderContext& context) const { + const auto ref = context.findNodeById(Href); + if (!ref) { + return; + } + + auto lengthContext = context.lengthContext(); + lengthContext.clearPatternUnits(); + SVGRenderContext localContext(context, lengthContext); + ref->render(localContext); +} + +Path SVGUse::onAsPath(const SVGRenderContext& context) const { + const auto ref = context.findNodeById(Href); + if (!ref) { + return Path(); + } + + auto lengthContext = context.lengthContext(); + lengthContext.clearPatternUnits(); + SVGRenderContext localContext(context, lengthContext); + return ref->asPath(localContext); +} + +Rect SVGUse::onObjectBoundingBox(const SVGRenderContext& context) const { + const auto ref = context.findNodeById(Href); + if (!ref) { + return Rect::MakeEmpty(); + } + + auto lengthContext = context.lengthContext(); + lengthContext.clearPatternUnits(); + float x = lengthContext.resolve(X, SVGLengthContext::LengthType::Horizontal); + float y = lengthContext.resolve(Y, SVGLengthContext::LengthType::Vertical); + + Rect bounds = ref->objectBoundingBox(context); + bounds.offset(x, y); + + return bounds; +} + +} // namespace tgfx \ No newline at end of file diff --git a/src/svg/xml/XMLDOM.cpp b/src/svg/xml/XMLDOM.cpp index f53d6e25..2292e2b8 100644 --- a/src/svg/xml/XMLDOM.cpp +++ b/src/svg/xml/XMLDOM.cpp @@ -31,9 +31,9 @@ DOM::DOM(std::shared_ptr root) { DOM::~DOM() = default; -std::shared_ptr DOM::MakeFromData(const Data& data) { +std::shared_ptr DOM::Make(Stream& stream) { DOMParser parser; - if (!parser.parse(data)) { + if (!parser.parse(stream)) { return nullptr; } auto root = parser.getRoot(); diff --git a/src/svg/xml/XMLParser.cpp b/src/svg/xml/XMLParser.cpp index bf882823..a986b0c2 100644 --- a/src/svg/xml/XMLParser.cpp +++ b/src/svg/xml/XMLParser.cpp @@ -17,6 +17,7 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "XMLParser.h" +#include #include #include #include "core/utils/Log.h" @@ -51,7 +52,7 @@ class AutoTCallVProc : public std::unique_ptr> { } }; -constexpr const void* kHashSeed = &kHashSeed; +constexpr const void* HASH_SEED = &HASH_SEED; const XML_Memory_Handling_Suite XML_alloc = {malloc, realloc, free}; @@ -120,7 +121,7 @@ void XMLCALL entity_decl_handler(void* data, const XML_Char* entityName, XMLParser::XMLParser() = default; XMLParser::~XMLParser() = default; -bool XMLParser::parse(const Data& data) { +bool XMLParser::parse(Stream& stream) { ParsingContext parsingContext(this); if (!parsingContext._XMLParser) { LOGE("could not create XML parser\n"); @@ -130,7 +131,7 @@ bool XMLParser::parse(const Data& data) { // Avoid calls to rand_s if this is not set. This seed helps prevent DOS // with a known hash sequence so an address is sufficient. The provided // seed should not be zero as that results in a call to rand_s. - auto seed = static_cast(reinterpret_cast(kHashSeed) & 0xFFFFFFFF); + auto seed = static_cast(reinterpret_cast(HASH_SEED) & 0xFFFFFFFF); XML_SetHashSalt(parsingContext._XMLParser, seed ? seed : 1); XML_SetUserData(parsingContext._XMLParser, &parsingContext); @@ -140,9 +141,30 @@ bool XMLParser::parse(const Data& data) { // Disable entity processing, to inhibit internal entity expansion. See expat CVE-2013-0340. XML_SetEntityDeclHandler(parsingContext._XMLParser, entity_decl_handler); - XML_Status status = - XML_Parse(parsingContext._XMLParser, reinterpret_cast(data.bytes()), - static_cast(data.size()), true); + XML_Status status = XML_STATUS_OK; + if (stream.getMemoryBase() && stream.size() != 0) { + const char* base = reinterpret_cast(stream.getMemoryBase()); + status = XML_Parse(parsingContext._XMLParser, base, static_cast(stream.size()), true); + } else { + static constexpr int BUFFER_SIZE = 4096; + bool done = false; + size_t length = stream.size(); + size_t currentPos = 0; + do { + void* buffer = XML_GetBuffer(parsingContext._XMLParser, BUFFER_SIZE); + if (!buffer) { + return false; + } + + size_t readLength = stream.read(buffer, BUFFER_SIZE); + currentPos += readLength; + done = currentPos >= length; + status = XML_ParseBuffer(parsingContext._XMLParser, static_cast(readLength), done); + if (XML_STATUS_ERROR == status) { + break; + } + } while (!done); + } return XML_STATUS_ERROR != status; } diff --git a/src/svg/xml/XMLParser.h b/src/svg/xml/XMLParser.h index 206c4859..912031f2 100644 --- a/src/svg/xml/XMLParser.h +++ b/src/svg/xml/XMLParser.h @@ -21,6 +21,7 @@ #include #include "tgfx/core/Data.h" +#include "tgfx/core/Stream.h" namespace tgfx { class XMLParser { @@ -29,12 +30,12 @@ class XMLParser { virtual ~XMLParser(); /** - * Parses data in XML format, with the parsing results returned via callback functions. - * @param data The data to be parsed. + * Parses stream in XML format, with the parsing results returned via callback functions. + * @param stream The stream to be parsed. * @return true if parsing is successful. * @return false if parsing fails. */ - bool parse(const Data& data); + bool parse(Stream& stream); protected: /** diff --git a/test/baseline/version.json b/test/baseline/version.json index db7d2697..1002b15f 100644 --- a/test/baseline/version.json +++ b/test/baseline/version.json @@ -194,6 +194,23 @@ "WebpCodec_Encode_RGB565": "d010fb8", "WebpCodec_Encode_RGBA": "d010fb8" }, + "SVGTest": { + "blur": "b1db872", + "jpg_image": "b1db872", + "mask": "b1db872", + "path": "b1db872", + "png_image": "b1db872", + "radialGradient": "b1db872", + "text": "b1db872", + "textFont": "348f70e", + "complex1": "ba1c7c2", + "complex2": "ba1c7c2", + "complex3": "ba1c7c2", + "complex4": "ba1c7c2", + "complex5": "ba1c7c2", + "complex6": "ba1c7c2", + "complex7": "ba1c7c2" + }, "SurfaceTest": { "ImageSnapshot1": "d010fb8", "ImageSnapshot2": "d010fb8", diff --git a/test/src/SVGRenderTest.cpp b/test/src/SVGRenderTest.cpp index 74fd7811..33d02e93 100644 --- a/test/src/SVGRenderTest.cpp +++ b/test/src/SVGRenderTest.cpp @@ -16,8 +16,12 @@ // ///////////////////////////////////////////////////////////////////////////////////////////////// +#include #include "gtest/gtest.h" #include "tgfx/core/Data.h" +#include "tgfx/core/FontStyle.h" +#include "tgfx/core/Stream.h" +#include "tgfx/svg/SVGDOM.h" #include "tgfx/svg/xml/XMLDOM.h" #include "utils/TestUtils.h" @@ -34,7 +38,9 @@ TGFX_TEST(SVGRenderTest, XMLParse) { auto data = Data::MakeWithCopy(xml.data(), xml.size()); EXPECT_TRUE(data != nullptr); - auto xmlDOM = DOM::MakeFromData(*data); + auto stream = Stream::MakeFromData(data); + EXPECT_TRUE(stream != nullptr); + auto xmlDOM = DOM::Make(*stream); EXPECT_TRUE(xmlDOM != nullptr); auto rootNode = xmlDOM->getRootNode(); @@ -77,4 +83,282 @@ TGFX_TEST(SVGRenderTest, XMLParse) { EXPECT_EQ(copyDOM->getRootNode()->name, xmlDOM->getRootNode()->name); } +TGFX_TEST(SVGRenderTest, PathSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/path.svg")); + ASSERT_TRUE(stream != nullptr); + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/path")); +} + +TGFX_TEST(SVGRenderTest, PNGImageSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/png.svg")); + ASSERT_TRUE(stream != nullptr); + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/png_image")); +} + +TGFX_TEST(SVGRenderTest, JPGImageSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/jpg.svg")); + ASSERT_TRUE(stream != nullptr); + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/jpg_image")); +} + +TGFX_TEST(SVGRenderTest, MaskSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/mask.svg")); + ASSERT_TRUE(stream != nullptr); + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/mask")); +} + +TGFX_TEST(SVGRenderTest, GradientSVG) { + auto stream = + Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/radialGradient.svg")); + ASSERT_TRUE(stream != nullptr); + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/radialGradient")); +} + +TGFX_TEST(SVGRenderTest, BlurSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/blur.svg")); + ASSERT_TRUE(stream != nullptr); + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/blur")); +} + +// class FontLoaderTest : public FontManagerCustom::FontLoader { +// public: +// void loadFonts(std::vector>& families) const override { +// auto family = std::make_shared("Noto Sans SC"); +// auto typeface = MakeTypeface("resources/font/NotoSansSC-Regular.otf"); +// typeface->setFontStyle(FontStyle::Normal()); +// family->appendTypeface(typeface); +// families.push_back(family); + +// family = std::make_shared("Noto Serif SC"); +// typeface = MakeTypeface("resources/font/NotoSerifSC-Regular.otf"); +// typeface->setFontStyle(FontStyle::Normal()); +// family->appendTypeface(typeface); +// families.push_back(family); +// } +// }; + +TGFX_TEST(SVGRenderTest, TextSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/text.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/text")); +} + +TGFX_TEST(SVGRenderTest, TextFontSVG) { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/textFont.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + auto surface = Surface::Make(context, static_cast(rootNode->getWidth().value()), + static_cast(rootNode->getHeight().value())); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/textFont")); +} + +TGFX_TEST(SVGRenderTest, ComplexSVG) { + + ContextScope scope; + auto* context = scope.getContext(); + ASSERT_TRUE(context != nullptr); + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex1.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 100, 100); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex1")); + } + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex2.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 160, 160); + auto* canvas = surface->getCanvas(); + + canvas->scale(10, 10); + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex2")); + } + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex3.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 300, 300); + auto* canvas = surface->getCanvas(); + + canvas->scale(2, 2); + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex3")); + } + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex4.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 500, 400); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex4")); + } + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex5.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 1300, 1300); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex5")); + } + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex6.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 375, 812); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex6")); + } + + { + auto stream = Stream::MakeFromFile(ProjectPath::Absolute("resources/apitest/SVG/complex7.svg")); + ASSERT_TRUE(stream != nullptr); + + auto SVGDom = SVGDOM::Make(*stream); + auto rootNode = SVGDom->getRoot(); + ASSERT_TRUE(rootNode != nullptr); + + auto surface = Surface::Make(context, 1090, 2026); + auto* canvas = surface->getCanvas(); + + SVGDom->render(canvas); + EXPECT_TRUE(Baseline::Compare(surface, "SVGTest/complex7")); + } +} + } // namespace tgfx \ No newline at end of file