| /* | |
| * Copyright 2026 Rive | |
| */ | |
| #ifndef _RIVE_SVG_RENDERER_HPP_ | |
| #define _RIVE_SVG_RENDERER_HPP_ | |
| #include "rive/math/mat2d.hpp" | |
| #include "rive/renderer.hpp" | |
| #include "utils/svg_factory.hpp" | |
| #include <iomanip> | |
| #include <sstream> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <vector> | |
| namespace rive | |
| { | |
| class SVGRenderer : public Renderer | |
| { | |
| public: | |
| static constexpr int kDefaultFloatPrecision = 4; | |
| explicit SVGRenderer(int floatPrecision = kDefaultFloatPrecision) : | |
| m_floatPrecision(floatPrecision) | |
| { | |
| m_stack.emplace_back(); | |
| m_defs << std::setprecision(floatPrecision); | |
| } | |
| void save() override; | |
| void restore() override; | |
| void transform(const Mat2D& transform) override; | |
| void drawPath(RenderPath* path, RenderPaint* paint) override; | |
| void clipPath(RenderPath* path) override; | |
| void drawImage(const RenderImage* image, | |
| ImageSampler sampler, | |
| BlendMode blendMode, | |
| float opacity) override; | |
| void drawImageMesh(const RenderImage* image, | |
| ImageSampler sampler, | |
| rcp<RenderBuffer> vertices, | |
| rcp<RenderBuffer> uvCoords, | |
| rcp<RenderBuffer> indices, | |
| uint32_t vertexCount, | |
| uint32_t indexCount, | |
| BlendMode blendMode, | |
| float opacity) override; | |
| void modulateOpacity(float opacity) override; | |
| std::string finalize(int width, int height) const; | |
| private: | |
| // A child of a save block. Either a real draw (a single self-closing | |
| // <path>/<image> element whose blend is *not* yet inlined as a style), | |
| // or a pass-through chunk of markup such as a clip-path open/close that | |
| // should be flushed verbatim and never wrapped by blend grouping. | |
| struct Item | |
| { | |
| std::string body; // the element markup (without blend style for draws) | |
| BlendMode blend = BlendMode::srcOver; | |
| bool isDraw = false; | |
| // clipPath() open items: id of the clip this <g> references. | |
| int clipOpenId = -1; | |
| // Restored chunks that are exactly one top-level <g clip-path>: the | |
| // clip id and the byte length of the opening tag, so writeEntry can | |
| // merge adjacent chunks sharing a clip into a single group. | |
| int singleClipId = -1; | |
| size_t clipOpenLen = 0; | |
| }; | |
| struct StackEntry | |
| { | |
| // Accumulated state. Inherited from the parent on save(). | |
| Mat2D matrix; | |
| float opacity = 1.0f; | |
| // Open <g clip-path> wrappers that need closing on restore(). | |
| int openClipGroups = 0; | |
| std::vector<Item> items; | |
| }; | |
| int m_floatPrecision; | |
| std::vector<StackEntry> m_stack; | |
| std::ostringstream m_defs; | |
| // Serialized clip geometry -> clip id, so identical clips share one def. | |
| std::unordered_map<std::string, int> m_clipDefIds; | |
| int m_clipIdCounter = 0; | |
| int m_gradientIdCounter = 0; | |
| void emitPaintAttributes(std::ostream& out, | |
| SVGRenderPaint* paint, | |
| SVGRenderPath* path, | |
| float extraOpacity); | |
| static void writeTransform(std::ostream& out, const Mat2D& m); | |
| static void writeColor(std::ostream& out, | |
| unsigned r, | |
| unsigned g, | |
| unsigned b); | |
| // Append an entry's items into `dst`, applying blend grouping rules and | |
| // merging adjacent single-clip chunks. | |
| static void writeEntry(std::ostream& dst, const StackEntry& entry); | |
| // True when writeEntry will wrap the entry in a shared-blend <g>. | |
| static bool wantsBlendWrap(const StackEntry& entry); | |
| }; | |
| } // namespace rive | |
| #endif |