NovaSVG 0.10.0
A header-only SVG library
Loading...
Searching...
No Matches
svgelement.h
Go to the documentation of this file.
1#ifndef NOVASVG_SVGELEMENT_H
2#define NOVASVG_SVGELEMENT_H
3
4#include <string>
5#include <forward_list>
6#include <list>
7#include <map>
8#include <unordered_map>
9#include <optional>
10#include <vector>
11#include <cmath>
12#include <cctype>
13#include <set>
14#include <cassert>
15#include "svgparserutils.h"
16
17// ---------------------------------------------------------------
18// SVGElement and its family (SVGPaintElement, SVGGeometryElement,
19// SVGTextElement, SVGLayoutState) used to be 5 declaration/
20// implementation pairs (10 files). They're merged into this one
21// file because they're circularly dependent -- SVGElement's own
22// implementation needs the derived classes fully declared, and the
23// derived classes need SVGElement fully declared to inherit from it
24// -- so a clean 1:1 .h/.hpp split was never actually possible for
25// this group; only a shared declare-everything-then-define-
26// everything file is. Order matters: declarations below follow the
27// dependency order (base class first), implementations follow
28// (order doesn't matter there since all declarations above are
29// already visible).
30// ---------------------------------------------------------------
31
32namespace novasvg {
33using namespace render; // render/ layer (novasvg::render), the former plutovg
34
35// ---- svgproperty ----
36enum class PropertyID : uint8_t {
37 Unknown = 0,
41 Class,
45 Color,
46 Cx,
47 Cy,
48 D,
50 Display,
52 Dx,
53 Dy,
54 Fill,
57 Filter,
64 Fx,
65 Fy,
68 Height,
69 Href,
70 Id,
71 In,
72 In2,
73 K1,
74 K2,
75 K3,
76 K4,
85 Mask,
90 Offset,
91 Opacity,
93 Orient,
99 Points,
101 R,
102 RefX,
103 RefY,
105 Result,
106 Rotate,
107 Rx,
108 Ry,
113 Stroke,
121 Style,
126 Transform,
129 ViewBox,
132 Width,
135 X,
136 X1,
137 X2,
138 Y,
139 Y1,
140 Y2
141};
142
143PropertyID propertyid(std::string_view name);
144PropertyID csspropertyid(std::string_view name);
145
146class SVGElement;
147
149public:
151 virtual ~SVGProperty() = default;
152 PropertyID id() const { return m_id; }
153
154 virtual bool parse(std::string_view input) = 0;
155
156private:
157 SVGProperty(const SVGProperty&) = delete;
158 SVGProperty& operator=(const SVGProperty&) = delete;
159 PropertyID m_id;
160};
161
162class SVGString final : public SVGProperty {
163public:
165 : SVGProperty(id)
166 {}
167
168 const std::string& value() const { return m_value; }
169 bool parse(std::string_view input) final;
170
171private:
172 std::string m_value;
173};
174
175class Paint {
176public:
177 Paint() = default;
178 explicit Paint(const Color& color) : m_color(color) {}
179 Paint(const std::string& id, const Color& color)
180 : m_id(id), m_color(color)
181 {}
182
183 const Color& color() const { return m_color; }
184 const std::string& id() const { return m_id; }
185 bool isNone() const { return m_id.empty() && !m_color.isVisible(); }
186
187private:
188 std::string m_id;
189 Color m_color = Color::Transparent;
190};
191
192enum class Display : uint8_t {
193 Inline,
194 None
195};
196
197enum class Visibility : uint8_t {
198 Visible,
199 Hidden,
201};
202
203enum class Overflow : uint8_t {
204 Visible,
205 Hidden
206};
207
208// CSS mix-blend-mode. Normal = ordinary painting. Of the other keywords the separable modes below are
209// supported; overlay, color-dodge, color-burn, hard-light, soft-light and the non-separable ones (hue,
210// saturation, color, luminosity) are not yet and paint as Normal, like a value this library doesn't know.
211enum class MixBlendMode : uint8_t {
212 Normal,
213 Multiply,
214 Screen,
215 Darken,
216 Lighten,
217 Difference,
218 Exclusion
219};
220
221enum class PointerEvents : uint8_t {
222 None,
223 Auto,
224 Stroke,
225 Fill,
226 Painted,
227 Visible,
232 All
233};
234
235enum class FontStyle : uint8_t {
236 Normal,
237 Italic
238};
239
240enum class FontWeight : uint8_t {
241 Normal,
242 Bold
243};
244
245enum class AlignmentBaseline : uint8_t {
246 Auto,
247 Baseline,
250 Middle,
251 Central,
252 AfterEdge,
256 Hanging,
258};
259
260enum class DominantBaseline : uint8_t {
261 Auto,
262 UseScript,
263 NoChange,
264 ResetSize,
267 Hanging,
269 Central,
270 Middle,
273};
274
275enum class TextAnchor : uint8_t {
276 Start,
277 Middle,
278 End
279};
280
281enum class WhiteSpace : uint8_t {
282 Default,
284};
285
286enum class WritingMode : uint8_t {
289};
290
291enum class TextOrientation : uint8_t {
292 Mixed,
293 Upright
294};
295
296enum class Direction : uint8_t {
297 Ltr,
298 Rtl
299};
300
301enum class MaskType : uint8_t {
302 Luminance,
303 Alpha
304};
305
306enum class Units : uint8_t {
309};
310
311enum class MarkerUnits : uint8_t {
314};
315
316enum class LengthAdjust : uint8_t {
317 Spacing,
319};
320
321template<typename Enum>
322using SVGEnumerationEntry = std::pair<Enum, std::string_view>;
323
324template<typename Enum>
325class SVGEnumeration final : public SVGProperty {
326public:
327 explicit SVGEnumeration(PropertyID id, Enum value)
328 : SVGProperty(id)
329 , m_value(value)
330 {}
331
332 Enum value() const { return m_value; }
333 bool parse(std::string_view input) final;
334
335private:
336 template<unsigned int N>
337 bool parseEnum(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N]);
338 Enum m_value;
339};
340
341class SVGAngle final : public SVGProperty {
342public:
343 enum class OrientType {
344 Auto,
346 Angle
347 };
348
349 explicit SVGAngle(PropertyID id)
350 : SVGProperty(id)
351 {}
352
353 float value() const { return m_value; }
354 OrientType orientType() const { return m_orientType; }
355 bool parse(std::string_view input) final;
356
357private:
358 float m_value = 0;
359 OrientType m_orientType = OrientType::Angle;
360};
361
362enum class LengthUnits : uint8_t {
363 None,
364 Percent,
365 Px,
366 Em,
367 Ex
368};
369
370enum class LengthDirection : uint8_t {
372 Vertical,
374};
375
376enum class LengthNegativeMode : uint8_t {
377 Allow,
378 Forbid
379};
380
381class Length {
382public:
383 Length() = default;
385 : m_value(value), m_units(units)
386 {}
387
388 float value() const { return m_value; }
389 LengthUnits units() const { return m_units; }
390
391 bool parse(std::string_view input, LengthNegativeMode mode);
392
393private:
394 float m_value = 0.f;
396};
397
398class SVGLength final : public SVGProperty {
399public:
401 : SVGProperty(id)
402 , m_direction(direction)
403 , m_negativeMode(negativeMode)
404 , m_value(value, units)
405 {}
406
407 bool isPercent() const { return m_value.units() == LengthUnits::Percent; }
408
409 LengthDirection direction() const { return m_direction; }
410 LengthNegativeMode negativeMode() const { return m_negativeMode; }
411 const Length& value() const { return m_value; }
412 bool parse(std::string_view input) final;
413
414private:
415 const LengthDirection m_direction;
416 const LengthNegativeMode m_negativeMode;
417 Length m_value;
418};
419
421public:
423 : m_element(element), m_units(units)
424 {}
425
426 float valueForLength(const Length& length, LengthDirection direction) const;
427 float valueForLength(const SVGLength& length) const { return valueForLength(length.value(), length.direction()); }
428
429private:
430 float viewportDimension(LengthDirection direction) const;
431 const SVGElement* m_element;
432 const Units m_units;
433};
434
435using LengthList = std::vector<Length>;
436
437class SVGLengthList final : public SVGProperty {
438public:
444
445 LengthDirection direction() const { return m_direction; }
446 LengthNegativeMode negativeMode() const { return m_negativeMode; }
447 const LengthList& values() const { return m_values; }
448 bool parse(std::string_view input) final;
449
450private:
451 const LengthDirection m_direction;
452 const LengthNegativeMode m_negativeMode;
453 LengthList m_values;
454};
455
457public:
458 enum class Type {
459 Baseline,
460 Sub,
461 Super,
462 Length
463 };
464
465 BaselineShift() = default;
467 BaselineShift(const Length& length) : m_type(Type::Length), m_length(length) {}
468
469 Type type() const { return m_type; }
470 const Length& length() const { return m_length; }
471
472private:
473 Type m_type{Type::Baseline};
474 Length m_length;
475};
476
477class SVGNumber : public SVGProperty {
478public:
480 : SVGProperty(id)
481 , m_value(value)
482 {}
483
484 float value() const { return m_value; }
485 bool parse(std::string_view input) override;
486
487private:
488 float m_value;
489};
490
491class SVGNumberPercentage final : public SVGProperty {
492public:
494 : SVGProperty(id)
495 , m_value(value)
496 {}
497
498 float value() const { return m_value; }
499 bool parse(std::string_view input) final;
500
501private:
502 float m_value;
503};
504
505using NumberList = std::vector<float>;
506
507class SVGNumberList final : public SVGProperty {
508public:
510 : SVGProperty(id)
511 {}
512
513 const NumberList& values() const { return m_values; }
514 bool parse(std::string_view input) final;
515
516private:
517 NumberList m_values;
518};
519
520class SVGPath final : public SVGProperty {
521public:
522 explicit SVGPath(PropertyID id)
523 : SVGProperty(id)
524 {}
525
526 const Path& value() const { return m_value; }
527 bool parse(std::string_view input) final;
528
529private:
530 Path m_value;
531};
532
533class SVGPoint final : public SVGProperty {
534public:
535 explicit SVGPoint(PropertyID id)
536 : SVGProperty(id)
537 {}
538
539 const Point& value() const { return m_value; }
540 bool parse(std::string_view input) final;
541
542private:
543 Point m_value;
544};
545
546using PointList = std::vector<Point>;
547
548class SVGPointList final : public SVGProperty {
549public:
551 : SVGProperty(id)
552 {}
553
554 const PointList& values() const { return m_values; }
555 bool parse(std::string_view input) final;
556
557private:
558 PointList m_values;
559};
560
561class SVGRect final : public SVGProperty {
562public:
563 explicit SVGRect(PropertyID id)
564 : SVGProperty(id)
565 , m_value(Rect::Invalid)
566 {}
567
568 const Rect& value() const { return m_value; }
569 bool parse(std::string_view input) final;
570
571private:
572 Rect m_value;
573};
574
575class SVGTransform final : public SVGProperty {
576public:
578 : SVGProperty(id)
579 {}
580
581 const Transform& value() const { return m_value; }
582 bool parse(std::string_view input) final;
583
584private:
585 Transform m_value;
586};
587
588enum class TransformBoxType : uint8_t {
589 ViewBox,
590 FillBox
591};
592
593// CSS `transform-box`. Only distinguishes fill-box (and content-box, treated
594// the same here) from the default view-box -- stroke-box and the various
595// *-box refinements aren't worth the extra bookkeeping for what this is
596// used for (resolving `transform-origin`'s reference box).
597class SVGTransformBox final : public SVGProperty {
598public:
600 : SVGProperty(id)
601 {}
602
603 TransformBoxType value() const { return m_value; }
604 bool specified() const { return m_specified; }
605 bool parse(std::string_view input) final;
606
607private:
609 bool m_specified = false;
610};
611
612// CSS `transform-origin`, 2-value form (x y; an optional 3rd z-offset token
613// is skipped if present, this renderer is 2D only). Supports the keywords
614// (left/center/right/top/bottom), percentages, and plain numbers (treated
615// as user-unit lengths) -- covers the common cases without pulling in the
616// full CSS <length-percentage> grammar.
617class SVGTransformOrigin final : public SVGProperty {
618public:
620 : SVGProperty(id)
621 {}
622
623 bool specified() const { return m_specified; }
624 Point resolve(const Rect& box) const
625 {
626 auto ox = m_xIsPercent ? box.x + box.w * m_x : box.x + m_x;
627 auto oy = m_yIsPercent ? box.y + box.h * m_y : box.y + m_y;
628 return Point(ox, oy);
629 }
630 bool parse(std::string_view input) final;
631
632private:
633 float m_x = 0.5f;
634 float m_y = 0.5f;
635 bool m_xIsPercent = true;
636 bool m_yIsPercent = true;
637 bool m_specified = false;
638};
639
641public:
642 enum class AlignType {
643 None,
644 xMinYMin,
645 xMidYMin,
646 xMaxYMin,
647 xMinYMid,
648 xMidYMid,
649 xMaxYMid,
650 xMinYMax,
651 xMidYMax,
653 };
654
655 enum class MeetOrSlice {
656 Meet,
657 Slice
658 };
659
663
664 AlignType alignType() const { return m_alignType; }
665 MeetOrSlice meetOrSlice() const { return m_meetOrSlice; }
666 bool parse(std::string_view input) final;
667
668 Rect getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const;
669 Transform getTransform(const Rect& viewBoxRect, const Size& viewportSize) const;
670 void transformRect(Rect& dstRect, Rect& srcRect) const;
671
672private:
673 AlignType m_alignType = AlignType::xMidYMid;
674 MeetOrSlice m_meetOrSlice = MeetOrSlice::Meet;
675};
676
677#include <cassert>
678
679// ---- svgelement ----
680
681class Document;
682class SVGElement;
683class SVGRootElement;
684
685class SVGNode {
686public:
688 : m_document(document)
689 {}
690
691 virtual ~SVGNode() = default;
692 virtual bool isTextNode() const { return false; }
693 virtual bool isElement() const { return false; }
694 virtual bool isPaintElement() const { return false; }
695 virtual bool isGraphicsElement() const { return false; }
696 virtual bool isGeometryElement() const { return false; }
697 virtual bool isTextPositioningElement() const { return false; }
698
699 Document* document() const { return m_document; }
700 SVGRootElement* rootElement() const { return m_document->rootElement(); }
701
702 SVGElement* parentElement() const { return m_parentElement; }
703 void setParentElement(SVGElement* parent) { m_parentElement = parent; }
704
705 bool isRootElement() const { return m_parentElement == nullptr; }
706
707 virtual std::unique_ptr<SVGNode> clone(bool deep) const = 0;
708
709private:
710 SVGNode(const SVGNode&) = delete;
711 SVGNode& operator=(const SVGNode&) = delete;
712 Document* m_document;
713 SVGElement* m_parentElement = nullptr;
714};
715
716class SVGTextNode final : public SVGNode {
717public:
719
720 bool isTextNode() const final { return true; }
721
722 const std::string& data() const { return m_data; }
723 void setData(const std::string& data);
724
725 std::unique_ptr<SVGNode> clone(bool deep) const final;
726
727private:
728 std::string m_data;
729};
730
738namespace Specificity {
739constexpr int PresentationAttribute = 0x1;
740constexpr int Stylesheet = 0x10;
741constexpr int InlineStyle = 0x100;
742constexpr int StylesheetImportant = 0x1000;
743constexpr int InlineImportant = 0x2000;
744constexpr int Api = 0x4000;
745} // namespace Specificity
746
748public:
749 Attribute() = default;
750 Attribute(int specificity, PropertyID id, std::string value)
751 : m_specificity(specificity), m_id(id), m_value(std::move(value))
752 {}
753
754 int specificity() const { return m_specificity; }
755 PropertyID id() const { return m_id; }
756 const std::string& value() const { return m_value; }
757
758private:
759 int m_specificity;
760 PropertyID m_id;
761 std::string m_value;
762};
763
764using AttributeList = std::forward_list<Attribute>;
765
766enum class ElementID : uint8_t {
767 Unknown = 0,
768 Star,
769 Circle,
770 ClipPath,
771 Defs,
772 Ellipse,
775 FeFlood,
777 FeMerge,
779 FeOffset,
780 Filter,
782 G,
783 Image,
784 Line,
786 Marker,
787 Mask,
788 Path,
789 Pattern,
790 Polygon,
791 Polyline,
793 Rect,
794 Stop,
795 Style,
796 Svg,
797 Switch,
798 Symbol,
799 Text,
800 Tspan,
801 Use
802};
803
804ElementID elementid(std::string_view name);
805
806using SVGNodeList = std::list<std::unique_ptr<SVGNode>>;
807using SVGPropertyList = std::forward_list<SVGProperty*>;
808
809class SVGMarkerElement;
811class SVGMaskElement;
812class SVGFilterElement;
813class SVGFilterContext;
814class SVGPaintElement;
815class SVGLayoutState;
816class SVGRenderState;
817
818extern const std::string emptyString;
819
820class SVGElement : public SVGNode {
821public:
822 static std::unique_ptr<SVGElement> create(Document* document, ElementID id);
823
825 virtual ~SVGElement() = default;
826
827 bool hasAttribute(std::string_view name) const;
828 const std::string& getAttribute(std::string_view name) const;
829 bool setAttribute(std::string_view name, const std::string& value);
830
831 const Attribute* findAttribute(PropertyID id) const;
832 bool hasAttribute(PropertyID id) const;
833 const std::string& getAttribute(PropertyID id) const;
834 bool setAttribute(int specificity, PropertyID id, const std::string& value);
836 bool setAttribute(const Attribute& attribute);
837
838 virtual void parseAttribute(PropertyID id, const std::string& value);
839
841 SVGElement* nextElement() const;
842
843 SVGNode* addChild(std::unique_ptr<SVGNode> child);
844 SVGNode* firstChild() const;
845 SVGNode* lastChild() const;
846
847 ElementID id() const { return m_id; }
848 const AttributeList& attributes() const { return m_attributes; }
849 const SVGPropertyList& properties() const { return m_properties; }
850 const SVGNodeList& children() const { return m_children; }
851
852 virtual Transform localTransform() const { return Transform::Identity; }
853 virtual Rect fillBoundingBox() const;
854 virtual Rect strokeBoundingBox() const;
855 virtual Rect paintBoundingBox() const;
856
857 SVGMarkerElement* getMarker(std::string_view id) const;
858 SVGClipPathElement* getClipper(std::string_view id) const;
859 SVGMaskElement* getMasker(std::string_view id) const;
860 SVGFilterElement* getFilterElement(std::string_view id) const;
861 SVGPaintElement* getPainter(std::string_view id) const;
862
863 SVGElement* elementFromPoint(float x, float y);
864
865 template<typename T>
866 void transverse(T callback);
867
868 void addProperty(SVGProperty& value);
871 float font_size() const { return m_font_size; }
872
874 std::unique_ptr<SVGNode> clone(bool deep) const final;
875
876 virtual void build();
877
878 virtual void layoutElement(const SVGLayoutState& state);
880 virtual void layout(SVGLayoutState& state);
881
883 virtual void render(SVGRenderState& state) const;
884
885 // Template Method hook for filter-primitive elements (feGaussianBlur,
886 // feOffset, feFlood, feComposite, feMerge, feDropShadow, ...): given
887 // the running filter pipeline context, compute and return this
888 // primitive's output. Default (every non-primitive element,
889 // including feMergeNode which only makes sense as feMerge's child)
890 // is "not a primitive" -- returns nullptr, which SVGFilterElement's
891 // pipeline simply skips over.
892 virtual std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const { return nullptr; }
893
894 bool isDisplayNone() const { return m_display == Display::None; }
895 bool isOverflowHidden() const { return m_overflow == Overflow::Hidden; }
896 bool isVisibilityHidden() const { return m_visibility != Visibility::Visible; }
897
898 bool isHiddenElement() const;
899 bool isPointableElement() const;
900
901 const SVGClipPathElement* clipper() const { return m_clipper; }
902 const SVGMaskElement* masker() const { return m_masker; }
903 const SVGFilterElement* filterElement() const { return m_filter; }
904 float opacity() const { return m_opacity; }
905 MixBlendMode mixBlendMode() const { return m_mix_blend_mode; }
906
907 bool isElement() const final { return true; }
908
909private:
910 mutable Rect m_paintBoundingBox = Rect::Invalid;
911 const SVGClipPathElement* m_clipper = nullptr;
912 const SVGMaskElement* m_masker = nullptr;
913 const SVGFilterElement* m_filter = nullptr;
914 float m_opacity = 1.f;
915 MixBlendMode m_mix_blend_mode = MixBlendMode::Normal;
916
917 float m_font_size = 12.f;
918 Display m_display = Display::Inline;
919 Overflow m_overflow = Overflow::Visible;
920 Visibility m_visibility = Visibility::Visible;
921 PointerEvents m_pointer_events = PointerEvents::Auto;
922
923 ElementID m_id;
924 AttributeList m_attributes;
925 SVGPropertyList m_properties;
926 SVGNodeList m_children;
927};
928
929inline const SVGElement* toSVGElement(const SVGNode* node)
930{
931 if(node && node->isElement())
932 return static_cast<const SVGElement*>(node);
933 return nullptr;
934}
935
937{
938 if(node && node->isElement())
939 return static_cast<SVGElement*>(node);
940 return nullptr;
941}
942
943inline SVGElement* toSVGElement(const std::unique_ptr<SVGNode>& node)
944{
945 return toSVGElement(node.get());
946}
947
948template<typename T>
949inline void SVGElement::transverse(T callback)
950{
951 callback(this);
952 for(const auto& child : m_children) {
953 if(auto element = toSVGElement(child)) {
954 element->transverse(callback);
955 }
956 }
957}
958
959class SVGStyleElement final : public SVGElement {
960public:
962};
963
965public:
966 SVGFitToViewBox(SVGElement* element);
967
968 const SVGRect& viewBox() const { return m_viewBox; }
969 const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
970 Transform viewBoxToViewTransform(const Size& viewportSize) const;
971 Rect getClipRect(const Size& viewportSize) const;
972
973private:
974 SVGRect m_viewBox;
975 SVGPreserveAspectRatio m_preserveAspectRatio;
976};
977
979public:
980 SVGURIReference(SVGElement* element);
981
982 const SVGString& href() const { return m_href; }
983 const std::string& hrefString() const { return m_href.value(); }
984 SVGElement* getTargetElement(const Document* document) const;
985
986private:
987 SVGString m_href;
988};
989
991public:
992 SVGPaintServer() = default;
994 : m_element(element), m_color(color), m_opacity(opacity)
995 {}
996
997 bool isRenderable() const { return m_opacity > 0.f && (m_element || m_color.getA() > 0); }
998
999 const SVGPaintElement* element() const { return m_element; }
1000 const Color& color() const { return m_color; }
1001 float opacity() const { return m_opacity; }
1002
1003 bool applyPaint(SVGRenderState& state) const;
1004
1005private:
1006 const SVGPaintElement* m_element = nullptr;
1007 Color m_color = Color::Transparent;
1008 float m_opacity = 0.f;
1009};
1010
1012public:
1014
1015 bool isGraphicsElement() const final { return true; }
1016
1017 const SVGTransform& transform() const { return m_transform; }
1018 Transform localTransform() const override;
1019
1020 SVGPaintServer getPaintServer(const Paint& paint, float opacity) const;
1021 StrokeData getStrokeData(const SVGLayoutState& state) const;
1022
1023private:
1024 SVGTransform m_transform;
1025 SVGTransformOrigin m_transformOrigin;
1026 SVGTransformBox m_transformBox;
1027};
1028
1030public:
1032
1033 const SVGLength& x() const { return m_x; }
1034 const SVGLength& y() const { return m_y; }
1035 const SVGLength& width() const { return m_width; }
1036 const SVGLength& height() const { return m_height; }
1037
1038 Transform localTransform() const override;
1039 void render(SVGRenderState& state) const override;
1040
1041private:
1042 SVGLength m_x;
1043 SVGLength m_y;
1044 SVGLength m_width;
1045 SVGLength m_height;
1046};
1047
1048class SVGRootElement final : public SVGSVGElement {
1049public:
1051
1052 float intrinsicWidth() const { return m_intrinsicWidth; }
1053 float intrinsicHeight() const { return m_intrinsicHeight; }
1054
1055 void setNeedsLayout() { m_intrinsicWidth = -1.f; }
1056 bool needsLayout() const { return m_intrinsicWidth == -1.f; }
1057
1059
1060 SVGElement* getElementById(std::string_view id) const;
1061 void addElementById(const std::string& id, SVGElement* element);
1062 void layout(SVGLayoutState& state) final;
1063
1064 void forceLayout();
1065
1066 // Every stylesheet applied to the document -- the <style> text found while parsing, then anything
1067 // passed to Document::applyStyleSheet() -- kept verbatim and in order. The HTML inside a
1068 // <foreignObject> sits outside the SVG element tree and so never goes through the resolver that sets
1069 // SVG presentation properties; it is styled from these (see resolveForeignObjectStyle()).
1070 const std::vector<std::string>& styleSheets() const { return m_styleSheets; }
1071 void addStyleSheet(std::string text) { m_styleSheets.push_back(std::move(text)); }
1072
1073private:
1074 std::map<std::string, SVGElement*, std::less<>> m_idCache;
1075 float m_intrinsicWidth{-1.f};
1076 float m_intrinsicHeight{-1.f};
1077 std::vector<std::string> m_styleSheets;
1078};
1079
1081public:
1083
1084 const SVGLength& x() const { return m_x; }
1085 const SVGLength& y() const { return m_y; }
1086 const SVGLength& width() const { return m_width; }
1087 const SVGLength& height() const { return m_height; }
1088
1090 void render(SVGRenderState& state) const final;
1091 void build() final;
1092
1093private:
1094 std::unique_ptr<SVGElement> cloneTargetElement(SVGElement* targetElement);
1095 SVGLength m_x;
1096 SVGLength m_y;
1097 SVGLength m_width;
1098 SVGLength m_height;
1099};
1100
1102public:
1104
1105 const SVGLength& x() const { return m_x; }
1106 const SVGLength& y() const { return m_y; }
1107 const SVGLength& width() const { return m_width; }
1108 const SVGLength& height() const { return m_height; }
1109 const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio; }
1110 const Bitmap& image() const { return m_image; }
1111
1114 void render(SVGRenderState& state) const final;
1115 void parseAttribute(PropertyID id, const std::string& value) final;
1116
1117private:
1118 SVGLength m_x;
1119 SVGLength m_y;
1120 SVGLength m_width;
1121 SVGLength m_height;
1122 SVGPreserveAspectRatio m_preserveAspectRatio;
1123 Bitmap m_image;
1124};
1125
1127
1146public:
1147 virtual ~ForeignObjectRenderer() = default;
1148 virtual void render(const SVGForeignObjectElement* element, SVGRenderState& state) const = 0;
1149};
1150
1155public:
1156 static ForeignObjectSimple* instance();
1157
1158 void render(const SVGForeignObjectElement* element, SVGRenderState& state) const final;
1159};
1160
1172 static constexpr unsigned char Bold = 1;
1173 static constexpr unsigned char Italic = 2;
1174
1175 Font face[4];
1176 const Font& get(unsigned char style) const { return face[style & 3]; }
1177};
1178
1180public:
1182
1183 const SVGLength& x() const { return m_x; }
1184 const SVGLength& y() const { return m_y; }
1185 const SVGLength& width() const { return m_width; }
1186 const SVGLength& height() const { return m_height; }
1187 const Font& font() const { return m_font; }
1188 const SVGPaintServer& fill() const { return m_fill; }
1189
1191 unsigned char baseFontStyle() const { return m_baseFontStyle; }
1194 ForeignObjectFonts fontsFor(unsigned usedStyles) const;
1195
1202 const std::string& rawContent() const { return m_rawContent; }
1203 void setRawContent(std::string content) { m_rawContent = std::move(content); }
1204
1205 ForeignObjectRenderer* renderer() const { return m_renderer; }
1206 void setRenderer(ForeignObjectRenderer* renderer) { m_renderer = renderer; }
1207
1213 const std::optional<Color>& htmlTextColor() const { return m_htmlTextColor; }
1214 const std::optional<Color>& htmlBackgroundColor() const { return m_htmlBackgroundColor; }
1215 void setHtmlStyle(std::optional<Color> text, std::optional<Color> background)
1216 {
1217 m_htmlTextColor = text;
1218 m_htmlBackgroundColor = background;
1219 }
1220
1221 Rect fillBoundingBox() const final;
1222 Rect strokeBoundingBox() const final;
1223 void layoutElement(const SVGLayoutState& state) final;
1224 void render(SVGRenderState& state) const final;
1225
1226private:
1227 SVGLength m_x;
1228 SVGLength m_y;
1229 SVGLength m_width;
1230 SVGLength m_height;
1231 Font m_font;
1232 std::string m_fontFamily;
1233 unsigned char m_baseFontStyle = 0;
1234 SVGPaintServer m_fill;
1235 std::string m_rawContent;
1236 ForeignObjectRenderer* m_renderer = nullptr;
1237 std::optional<Color> m_htmlTextColor;
1238 std::optional<Color> m_htmlBackgroundColor;
1239};
1240
1242public:
1243 SVGSymbolElement(Document* document);
1244};
1245
1246class SVGGElement final : public SVGGraphicsElement {
1247public:
1248 SVGGElement(Document* document);
1249
1250 void render(SVGRenderState& state) const final;
1251};
1252
1253// <switch>: renders ONLY the first direct child whose conditional-processing attributes all
1254// evaluate true (a child with none always does), exactly as a browser does. mermaid's journey
1255// diagram writes every label as `<switch><foreignObject>...</foreignObject><text>...</text></switch>`:
1256// the foreignObject (which this library paints) wins and the native <text> is only the fallback
1257// for renderers without foreignObject support. Before this element existed, `switch` was an
1258// unknown element and its whole subtree -- label included -- was skipped.
1260public:
1261 SVGSwitchElement(Document* document);
1262
1263 void render(SVGRenderState& state) const final;
1264
1265private:
1266 const SVGElement* selectedChild() const;
1267};
1268
1270public:
1271 SVGDefsElement(Document* document);
1272};
1273
1274class SVGMarkerElement final : public SVGElement, public SVGFitToViewBox {
1275public:
1276 SVGMarkerElement(Document* document);
1277
1278 const SVGLength& refX() const { return m_refX; }
1279 const SVGLength& refY() const { return m_refY; }
1280 const SVGLength& markerWidth() const { return m_markerWidth; }
1281 const SVGLength& markerHeight() const { return m_markerHeight; }
1282 const SVGEnumeration<MarkerUnits>& markerUnits() const { return m_markerUnits; }
1283 const SVGAngle& orient() const { return m_orient; }
1284
1285 Point refPoint() const;
1286 Size markerSize() const;
1287
1288 Transform markerTransform(const Point& origin, float angle, float strokeWidth) const;
1289 Rect markerBoundingBox(const Point& origin, float angle, float strokeWidth) const;
1290 void renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const;
1291
1292 Transform localTransform() const final;
1293
1294private:
1295 SVGLength m_refX;
1296 SVGLength m_refY;
1297 SVGLength m_markerWidth;
1298 SVGLength m_markerHeight;
1299 SVGEnumeration<MarkerUnits> m_markerUnits;
1300 SVGAngle m_orient;
1301};
1302
1304public:
1305 SVGClipPathElement(Document* document);
1306
1307 const SVGEnumeration<Units>& clipPathUnits() const { return m_clipPathUnits; }
1308 Rect clipBoundingBox(const SVGElement* element) const;
1309
1310 void applyClipMask(SVGRenderState& state) const;
1311 void applyClipPath(SVGRenderState& state) const;
1312
1313 bool requiresMasking() const;
1314
1315private:
1316 SVGEnumeration<Units> m_clipPathUnits;
1317};
1318
1319class SVGMaskElement final : public SVGElement {
1320public:
1321 SVGMaskElement(Document* document);
1322
1323 const SVGLength& x() const { return m_x; }
1324 const SVGLength& y() const { return m_y; }
1325 const SVGLength& width() const { return m_width; }
1326 const SVGLength& height() const { return m_height; }
1327 const SVGEnumeration<Units>& maskUnits() const { return m_maskUnits; }
1328 const SVGEnumeration<Units>& maskContentUnits() const { return m_maskContentUnits; }
1329
1330 Rect maskRect(const SVGElement* element) const;
1331 Rect maskBoundingBox(const SVGElement* element) const;
1332 void applyMask(SVGRenderState& state) const;
1333
1334 void layoutElement(const SVGLayoutState& state) final;
1335
1336private:
1337 SVGLength m_x;
1338 SVGLength m_y;
1339 SVGLength m_width;
1340 SVGLength m_height;
1341 SVGEnumeration<Units> m_maskUnits;
1342 SVGEnumeration<Units> m_maskContentUnits;
1343 MaskType m_mask_type = MaskType::Luminance;
1344};
1345
1346// ---- svgfilterelement ----
1347// Real SVG filter-primitive pipeline: SVGFilterContext carries the
1348// running state (SourceGraphic, named `result=` buffers, implicit
1349// chaining, device scale); each primitive is its own SVGElement subclass
1350// overriding the applyFilterPrimitive() Template Method; SVGFilterElement
1351// itself is just a pipeline executor with no per-type switch statement --
1352// it asks each child to compute its own output and threads the result
1353// through. Adding a new primitive later means adding one small class, not
1354// touching the executor.
1355static float parseFirstFloat(std::string_view input, float fallback)
1356{
1357 if(input.empty())
1358 return fallback;
1359 return std::strtof(std::string(input).c_str(), nullptr);
1360}
1361
1363public:
1364 SVGFilterContext(const std::shared_ptr<Canvas>& sourceGraphic, float scale)
1365 : m_sourceGraphic(sourceGraphic), m_lastResult(sourceGraphic), m_scale(scale)
1366 {}
1367
1368 float scale() const { return m_scale; }
1369
1370 // A blank transparent canvas matching SourceGraphic's position/size --
1371 // every primitive's output shares this geometry (ponytail: real UAs
1372 // allow per-primitive filter regions; one shared region for the whole
1373 // chain is a deliberate simplification, see checklist.md).
1374 std::shared_ptr<Canvas> newCanvas() const
1375 {
1376 return Canvas::create(float(m_sourceGraphic->x()), float(m_sourceGraphic->y()), float(m_sourceGraphic->width()), float(m_sourceGraphic->height()));
1377 }
1378
1379 std::shared_ptr<Canvas> cloneCanvas(const Canvas& source) const
1380 {
1381 auto canvas = newCanvas();
1382 canvas->compositeOver(source);
1383 return canvas;
1384 }
1385
1386 // Resolves an `in=`/`in2=` reference: empty -> implicit chain (result
1387 // of the previous primitive, or SourceGraphic for the first one);
1388 // "SourceGraphic"/"SourceAlpha" -> the original rendered element (full
1389 // or alpha-only); otherwise a named `result=` from an earlier
1390 // primitive, falling back to the implicit chain if the name is
1391 // unresolvable (spec says this is undefined behavior; failing soft
1392 // beats crashing).
1393 std::shared_ptr<Canvas> resolveInput(std::string_view in) const
1394 {
1395 if(in.empty())
1396 return m_lastResult;
1397 if(in == "SourceGraphic")
1398 return m_sourceGraphic;
1399 if(in == "SourceAlpha") {
1400 auto alpha = cloneCanvas(*m_sourceGraphic);
1401 alpha->tintToFloodColor(Color(0, 0, 0), 1.f);
1402 return alpha;
1403 }
1404 auto it = m_namedResults.find(std::string(in));
1405 if(it != m_namedResults.end())
1406 return it->second;
1407 return m_lastResult;
1408 }
1409
1410 // Records a primitive's output as the new implicit chain head, and
1411 // additionally under `name` if it declared `result=`.
1412 void setResult(std::string_view name, std::shared_ptr<Canvas> canvas)
1413 {
1414 if(!name.empty())
1415 m_namedResults.emplace(std::string(name), canvas);
1416 m_lastResult = std::move(canvas);
1417 }
1418
1419 const std::shared_ptr<Canvas>& lastResult() const { return m_lastResult; }
1420
1421private:
1422 std::shared_ptr<Canvas> m_sourceGraphic;
1423 std::shared_ptr<Canvas> m_lastResult;
1424 std::unordered_map<std::string, std::shared_ptr<Canvas>> m_namedResults;
1425 float m_scale;
1426};
1427
1429public:
1431
1432 std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const final
1433 {
1434 auto sigma = parseFirstFloat(getAttribute(PropertyID::StdDeviation), 0.f) * context.scale();
1435 auto output = context.cloneCanvas(*context.resolveInput(getAttribute(PropertyID::In)));
1436 output->boxBlur(sigma, sigma);
1437 return output;
1438 }
1439};
1440
1441class SVGFEOffsetElement final : public SVGElement {
1442public:
1444
1445 std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const final
1446 {
1447 auto dx = parseFirstFloat(getAttribute(PropertyID::Dx), 0.f) * context.scale();
1448 auto dy = parseFirstFloat(getAttribute(PropertyID::Dy), 0.f) * context.scale();
1449 auto output = context.cloneCanvas(*context.resolveInput(getAttribute(PropertyID::In)));
1450 output->shift(dx, dy);
1451 return output;
1452 }
1453};
1454
1455class SVGFEFloodElement final : public SVGElement {
1456public:
1458
1459 std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const final
1460 {
1461 auto floodColorAttr = getAttribute(PropertyID::Flood_Color);
1462 Color floodColor = floodColorAttr.empty() ? Color(0, 0, 0) : Color(floodColorAttr);
1463 auto floodOpacity = parseFirstFloat(getAttribute(PropertyID::Flood_Opacity), 1.f);
1464
1465 auto output = context.newCanvas();
1466 output->fillOpaqueWhite();
1467 output->tintToFloodColor(floodColor, floodOpacity);
1468 return output;
1469 }
1470};
1471
1472class SVGFEMergeElement final : public SVGElement {
1473public:
1475
1476 std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const final
1477 {
1478 auto output = context.newCanvas();
1479 for(const auto& child : children()) {
1480 auto element = toSVGElement(child);
1481 if(element && element->id() == ElementID::FeMergeNode)
1482 output->compositeOver(*context.resolveInput(element->getAttribute(PropertyID::In)));
1483 }
1484
1485 return output;
1486 }
1487};
1488
1489class SVGFECompositeElement final : public SVGElement {
1490public:
1492
1493 std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const final
1494 {
1495 auto input = context.resolveInput(getAttribute(PropertyID::In));
1496 auto input2 = context.resolveInput(getAttribute(PropertyID::In2));
1497 auto output = context.cloneCanvas(*input2);
1498
1499 auto op = getAttribute(PropertyID::Operator);
1500 if(op == "arithmetic") {
1501 auto k1 = parseFirstFloat(getAttribute(PropertyID::K1), 0.f);
1502 auto k2 = parseFirstFloat(getAttribute(PropertyID::K2), 0.f);
1503 auto k3 = parseFirstFloat(getAttribute(PropertyID::K3), 0.f);
1504 auto k4 = parseFirstFloat(getAttribute(PropertyID::K4), 0.f);
1505 output->compositeArithmetic(*input, k1, k2, k3, k4);
1506 return output;
1507 }
1508
1509 auto mode = Canvas::PorterDuff::Over;
1510 if(op == "in") mode = Canvas::PorterDuff::In;
1511 else if(op == "out") mode = Canvas::PorterDuff::Out;
1512 else if(op == "atop") mode = Canvas::PorterDuff::Atop;
1513 else if(op == "xor") mode = Canvas::PorterDuff::Xor;
1514
1515 output->compositeWith(*input, mode);
1516 return output;
1517 }
1518};
1519
1521public:
1523
1524 std::shared_ptr<Canvas> applyFilterPrimitive(SVGFilterContext& context) const final
1525 {
1526 auto sigma = parseFirstFloat(getAttribute(PropertyID::StdDeviation), 2.f) * context.scale();
1527 auto dx = parseFirstFloat(getAttribute(PropertyID::Dx), 2.f) * context.scale();
1528 auto dy = parseFirstFloat(getAttribute(PropertyID::Dy), 2.f) * context.scale();
1529 auto floodColorAttr = getAttribute(PropertyID::Flood_Color);
1530 Color floodColor = floodColorAttr.empty() ? Color(0, 0, 0) : Color(floodColorAttr);
1531 auto floodOpacity = parseFirstFloat(getAttribute(PropertyID::Flood_Opacity), 1.f);
1532
1533 auto input = context.resolveInput(getAttribute(PropertyID::In));
1534 auto shadow = context.cloneCanvas(*input);
1535 shadow->tintToFloodColor(floodColor, floodOpacity);
1536 shadow->boxBlur(sigma, sigma);
1537 shadow->shift(dx, dy);
1538 shadow->compositeOver(*input);
1539 return shadow;
1540 }
1541};
1542
1543class SVGFilterElement final : public SVGElement {
1544public:
1545 SVGFilterElement(Document* document);
1546
1547 // Extra space (in the *filtered element's own* user-space units,
1548 // unscaled) the offscreen buffer needs so blur/shadow spread doesn't
1549 // get clipped at the element's plain bounding box.
1550 float pixelMargin() const;
1551
1552 // Runs every child primitive in document order, threading each
1553 // result through SVGFilterContext, then writes the final result back
1554 // into `state`'s current (already-rendered) canvas. `scale` converts
1555 // user-space stdDeviation/dx/dy to device pixels.
1556 void applyFilter(SVGRenderState& state, float scale) const;
1557};
1558
1559// ---- svgrenderstate ----
1560enum class SVGRenderMode {
1561 Painting,
1562 Clipping
1563};
1564
1566public:
1567 explicit SVGBlendInfo(const SVGElement* element);
1568 SVGBlendInfo(const SVGClipPathElement* clipper, const SVGMaskElement* masker, const SVGFilterElement* filter, float opacity,
1569 MixBlendMode mixBlendMode = MixBlendMode::Normal)
1570 : m_clipper(clipper), m_masker(masker), m_filter(filter), m_opacity(opacity), m_mixBlendMode(mixBlendMode)
1571 {}
1572
1573 bool requiresCompositing(SVGRenderMode mode) const;
1574 const SVGClipPathElement* clipper() const { return m_clipper; }
1575 const SVGMaskElement* masker() const { return m_masker; }
1576 const SVGFilterElement* filterElement() const { return m_filter; }
1577 float opacity() const { return m_opacity; }
1578 MixBlendMode mixBlendMode() const { return m_mixBlendMode; }
1579
1580private:
1581 const SVGClipPathElement* m_clipper;
1582 const SVGMaskElement* m_masker;
1583 const SVGFilterElement* m_filter;
1584 const float m_opacity;
1585 const MixBlendMode m_mixBlendMode;
1586};
1587
1589public:
1590 SVGRenderState(const SVGElement* element, const SVGRenderState& parent, const Transform& localTransform)
1591 : m_element(element), m_parent(&parent), m_currentTransform(parent.currentTransform() * localTransform)
1592 , m_mode(parent.mode()), m_canvas(parent.canvas())
1593 {}
1594
1595 SVGRenderState(const SVGElement* element, const SVGRenderState* parent, const Transform& currentTransform, SVGRenderMode mode, std::shared_ptr<Canvas> canvas)
1596 : m_element(element), m_parent(parent), m_currentTransform(currentTransform), m_mode(mode), m_canvas(std::move(canvas))
1597 {}
1598
1599 Canvas& operator*() const { return *m_canvas; }
1600 Canvas* operator->() const { return &*m_canvas; }
1601
1602 const SVGElement* element() const { return m_element; }
1603 const SVGRenderState* parent() const { return m_parent; }
1604 const Transform& currentTransform() const { return m_currentTransform; }
1605 const SVGRenderMode mode() const { return m_mode; }
1606 const std::shared_ptr<Canvas>& canvas() const { return m_canvas; }
1607
1608 Rect fillBoundingBox() const { return m_element->fillBoundingBox(); }
1609 Rect paintBoundingBox() const { return m_element->paintBoundingBox(); }
1610
1611 bool hasCycleReference(const SVGElement* element) const;
1612
1613 void beginGroup(const SVGBlendInfo& blendInfo);
1614 void endGroup(const SVGBlendInfo& blendInfo);
1615
1616private:
1617 const SVGElement* m_element;
1618 const SVGRenderState* m_parent;
1619 const Transform m_currentTransform;
1620 const SVGRenderMode m_mode;
1621 std::shared_ptr<Canvas> m_canvas;
1622};
1623
1624// ---- svgpaintelement ----
1625
1627public:
1628 SVGPaintElement(Document* document, ElementID id);
1629
1630 bool isPaintElement() const final { return true; }
1631
1632 virtual bool applyPaint(SVGRenderState& state, float opacity) const = 0;
1633};
1634
1635class SVGStopElement final : public SVGElement {
1636public:
1637 SVGStopElement(Document* document);
1638
1639 void layoutElement(const SVGLayoutState& state) final;
1640 const SVGNumberPercentage& offset() const { return m_offset; }
1641 GradientStop gradientStop(float opacity) const;
1642
1643private:
1644 SVGNumberPercentage m_offset;
1645 Color m_stop_color = Color::Black;
1646 float m_stop_opacity = 1.f;
1647};
1648
1649class SVGGradientAttributes;
1650
1652public:
1653 SVGGradientElement(Document* document, ElementID id);
1654
1655 const SVGTransform& gradientTransform() const { return m_gradientTransform; }
1656 const SVGEnumeration<Units>& gradientUnits() const { return m_gradientUnits; }
1657 const SVGEnumeration<SpreadMethod>& spreadMethod() const { return m_spreadMethod; }
1658 void collectGradientAttributes(SVGGradientAttributes& attributes) const;
1659
1660private:
1661 SVGTransform m_gradientTransform;
1662 SVGEnumeration<Units> m_gradientUnits;
1663 SVGEnumeration<SpreadMethod> m_spreadMethod;
1664};
1665
1667public:
1669
1670 const Transform& gradientTransform() const { return m_gradientTransform->gradientTransform().value(); }
1671 SpreadMethod spreadMethod() const { return m_spreadMethod->spreadMethod().value(); }
1672 Units gradientUnits() const { return m_gradientUnits->gradientUnits().value(); }
1673 const SVGGradientElement* gradientContentElement() const { return m_gradientContentElement; }
1674
1675 bool hasGradientTransform() const { return m_gradientTransform; }
1676 bool hasSpreadMethod() const { return m_spreadMethod; }
1677 bool hasGradientUnits() const { return m_gradientUnits; }
1678 bool hasGradientContentElement() const { return m_gradientContentElement; }
1679
1680 void setGradientTransform(const SVGGradientElement* value) { m_gradientTransform = value; }
1681 void setSpreadMethod(const SVGGradientElement* value) { m_spreadMethod = value; }
1682 void setGradientUnits(const SVGGradientElement* value) { m_gradientUnits = value; }
1683 void setGradientContentElement(const SVGGradientElement* value) { m_gradientContentElement = value; }
1684
1686 if(!m_gradientTransform) { m_gradientTransform = element; }
1687 if(!m_spreadMethod) { m_spreadMethod = element; }
1688 if(!m_gradientUnits) { m_gradientUnits = element; }
1689 if(!m_gradientContentElement) { m_gradientContentElement = element; }
1690 }
1691
1692private:
1693 const SVGGradientElement* m_gradientTransform{nullptr};
1694 const SVGGradientElement* m_spreadMethod{nullptr};
1695 const SVGGradientElement* m_gradientUnits{nullptr};
1696 const SVGGradientElement* m_gradientContentElement{nullptr};
1697};
1698
1699class SVGLinearGradientAttributes;
1700
1702public:
1704
1705 const SVGLength& x1() const { return m_x1; }
1706 const SVGLength& y1() const { return m_y1; }
1707 const SVGLength& x2() const { return m_x2; }
1708 const SVGLength& y2() const { return m_y2; }
1709
1710 bool applyPaint(SVGRenderState& state, float opacity) const final;
1711
1712private:
1713 SVGLinearGradientAttributes collectGradientAttributes() const;
1714 SVGLength m_x1;
1715 SVGLength m_y1;
1716 SVGLength m_x2;
1717 SVGLength m_y2;
1718};
1719
1721public:
1723
1724 const SVGLength& x1() const { return m_x1->x1(); }
1725 const SVGLength& y1() const { return m_y1->y1(); }
1726 const SVGLength& x2() const { return m_x2->x2(); }
1727 const SVGLength& y2() const { return m_y2->y2(); }
1728
1729 bool hasX1() const { return m_x1; }
1730 bool hasY1() const { return m_y1; }
1731 bool hasX2() const { return m_x2; }
1732 bool hasY2() const { return m_y2; }
1733
1734 void setX1(const SVGLinearGradientElement* value) { m_x1 = value; }
1735 void setY1(const SVGLinearGradientElement* value) { m_y1 = value; }
1736 void setX2(const SVGLinearGradientElement* value) { m_x2 = value; }
1737 void setY2(const SVGLinearGradientElement* value) { m_y2 = value; }
1738
1740 SVGGradientAttributes::setDefaultValues(element);
1741 if(!m_x1) { m_x1 = element; }
1742 if(!m_y1) { m_y1 = element; }
1743 if(!m_x2) { m_x2 = element; }
1744 if(!m_y2) { m_y2 = element; }
1745 }
1746
1747private:
1748 const SVGLinearGradientElement* m_x1{nullptr};
1749 const SVGLinearGradientElement* m_y1{nullptr};
1750 const SVGLinearGradientElement* m_x2{nullptr};
1751 const SVGLinearGradientElement* m_y2{nullptr};
1752};
1753
1754class SVGRadialGradientAttributes;
1755
1757public:
1759
1760 const SVGLength& cx() const { return m_cx; }
1761 const SVGLength& cy() const { return m_cy; }
1762 const SVGLength& r() const { return m_r; }
1763 const SVGLength& fx() const { return m_fx; }
1764 const SVGLength& fy() const { return m_fy; }
1765
1766 bool applyPaint(SVGRenderState& state, float opacity) const final;
1767
1768private:
1769 SVGRadialGradientAttributes collectGradientAttributes() const;
1770 SVGLength m_cx;
1771 SVGLength m_cy;
1772 SVGLength m_r;
1773 SVGLength m_fx;
1774 SVGLength m_fy;
1775};
1776
1778public:
1780
1781 const SVGLength& cx() const { return m_cx->cx(); }
1782 const SVGLength& cy() const { return m_cy->cy(); }
1783 const SVGLength& r() const { return m_r->r(); }
1784 const SVGLength& fx() const { return m_fx ? m_fx->fx() : m_cx->cx(); }
1785 const SVGLength& fy() const { return m_fy ? m_fy->fy() : m_cy->cy(); }
1786
1787 bool hasCx() const { return m_cx; }
1788 bool hasCy() const { return m_cy; }
1789 bool hasR() const { return m_r; }
1790 bool hasFx() const { return m_fx; }
1791 bool hasFy() const { return m_fy; }
1792
1793 void setCx(const SVGRadialGradientElement* value) { m_cx = value; }
1794 void setCy(const SVGRadialGradientElement* value) { m_cy = value; }
1795 void setR(const SVGRadialGradientElement* value) { m_r = value; }
1796 void setFx(const SVGRadialGradientElement* value) { m_fx = value; }
1797 void setFy(const SVGRadialGradientElement* value) { m_fy = value; }
1798
1800 SVGGradientAttributes::setDefaultValues(element);
1801 if(!m_cx) { m_cx = element; }
1802 if(!m_cy) { m_cy = element; }
1803 if(!m_r) { m_r = element; }
1804 }
1805
1806private:
1807 const SVGRadialGradientElement* m_cx{nullptr};
1808 const SVGRadialGradientElement* m_cy{nullptr};
1809 const SVGRadialGradientElement* m_r{nullptr};
1810 const SVGRadialGradientElement* m_fx{nullptr};
1811 const SVGRadialGradientElement* m_fy{nullptr};
1812};
1813
1814class SVGPatternAttributes;
1815
1817public:
1818 SVGPatternElement(Document* document);
1819
1820 const SVGLength& x() const { return m_x; }
1821 const SVGLength& y() const { return m_y; }
1822 const SVGLength& width() const { return m_width; }
1823 const SVGLength& height() const { return m_height; }
1824 const SVGTransform& patternTransform() const { return m_patternTransform; }
1825 const SVGEnumeration<Units>& patternUnits() const { return m_patternUnits; }
1826 const SVGEnumeration<Units>& patternContentUnits() const { return m_patternContentUnits; }
1827
1828 bool applyPaint(SVGRenderState& state, float opacity) const final;
1829
1830private:
1831 SVGPatternAttributes collectPatternAttributes() const;
1832 SVGLength m_x;
1833 SVGLength m_y;
1834 SVGLength m_width;
1835 SVGLength m_height;
1836 SVGTransform m_patternTransform;
1837 SVGEnumeration<Units> m_patternUnits;
1838 SVGEnumeration<Units> m_patternContentUnits;
1839};
1840
1842public:
1844
1845 const SVGLength& x() const { return m_x->x(); }
1846 const SVGLength& y() const { return m_y->y(); }
1847 const SVGLength& width() const { return m_width->width(); }
1848 const SVGLength& height() const { return m_height->height(); }
1849 const Transform& patternTransform() const { return m_patternTransform->patternTransform().value(); }
1850 Units patternUnits() const { return m_patternUnits->patternUnits().value(); }
1851 Units patternContentUnits() const { return m_patternContentUnits->patternContentUnits().value(); }
1852 const Rect& viewBox() const { return m_viewBox->viewBox().value(); }
1853 const SVGPreserveAspectRatio& preserveAspectRatio() const { return m_preserveAspectRatio->preserveAspectRatio(); }
1854 const SVGPatternElement* patternContentElement() const { return m_patternContentElement; }
1855
1856 bool hasX() const { return m_x; }
1857 bool hasY() const { return m_y; }
1858 bool hasWidth() const { return m_width; }
1859 bool hasHeight() const { return m_height; }
1860 bool hasPatternTransform() const { return m_patternTransform; }
1861 bool hasPatternUnits() const { return m_patternUnits; }
1862 bool hasPatternContentUnits() const { return m_patternContentUnits; }
1863 bool hasViewBox() const { return m_viewBox; }
1864 bool hasPreserveAspectRatio() const { return m_preserveAspectRatio; }
1865 bool hasPatternContentElement() const { return m_patternContentElement; }
1866
1867 void setX(const SVGPatternElement* value) { m_x = value; }
1868 void setY(const SVGPatternElement* value) { m_y = value; }
1869 void setWidth(const SVGPatternElement* value) { m_width = value; }
1870 void setHeight(const SVGPatternElement* value) { m_height = value; }
1871 void setPatternTransform(const SVGPatternElement* value) { m_patternTransform = value; }
1872 void setPatternUnits(const SVGPatternElement* value) { m_patternUnits = value; }
1873 void setPatternContentUnits(const SVGPatternElement* value) { m_patternContentUnits = value; }
1874 void setViewBox(const SVGPatternElement* value) { m_viewBox = value; }
1875 void setPreserveAspectRatio(const SVGPatternElement* value) { m_preserveAspectRatio = value; }
1876 void setPatternContentElement(const SVGPatternElement* value) { m_patternContentElement = value; }
1877
1879 if(!m_x) { m_x = element; }
1880 if(!m_y) { m_y = element; }
1881 if(!m_width) { m_width = element; }
1882 if(!m_height) { m_height = element; }
1883 if(!m_patternTransform) { m_patternTransform = element; }
1884 if(!m_patternUnits) { m_patternUnits = element; }
1885 if(!m_patternContentUnits) { m_patternContentUnits = element; }
1886 if(!m_viewBox) { m_viewBox = element; }
1887 if(!m_preserveAspectRatio) { m_preserveAspectRatio = element; }
1888 if(!m_patternContentElement) { m_patternContentElement = element; }
1889 }
1890
1891private:
1892 const SVGPatternElement* m_x{nullptr};
1893 const SVGPatternElement* m_y{nullptr};
1894 const SVGPatternElement* m_width{nullptr};
1895 const SVGPatternElement* m_height{nullptr};
1896 const SVGPatternElement* m_patternTransform{nullptr};
1897 const SVGPatternElement* m_patternUnits{nullptr};
1898 const SVGPatternElement* m_patternContentUnits{nullptr};
1899 const SVGPatternElement* m_viewBox{nullptr};
1900 const SVGPatternElement* m_preserveAspectRatio{nullptr};
1901 const SVGPatternElement* m_patternContentElement{nullptr};
1902};
1903
1904// ---- svggeometryelement ----
1905
1907public:
1908 SVGMarkerPosition(const SVGMarkerElement* element, const Point& origin, float angle)
1909 : m_element(element), m_origin(origin), m_angle(angle)
1910 {}
1911
1912 const SVGMarkerElement* element() const { return m_element; }
1913 const Point& origin() const { return m_origin; }
1914 float angle() const { return m_angle; }
1915
1916 Rect markerBoundingBox(float strokeWidth) const;
1917 void renderMarker(SVGRenderState& state, float strokeWidth) const;
1918
1919private:
1920 const SVGMarkerElement* m_element;
1921 Point m_origin;
1922 float m_angle;
1923};
1924
1925using SVGMarkerPositionList = std::vector<SVGMarkerPosition>;
1926
1928public:
1929 SVGGeometryElement(Document* document, ElementID id);
1930
1931 bool isGeometryElement() const final { return true; }
1932
1933 Rect fillBoundingBox() const override { return m_fillBoundingBox; }
1934 Rect strokeBoundingBox() const override;
1935 void layoutElement(const SVGLayoutState& state) override;
1936
1937 bool isRenderable() const { return !m_path.isNull() && !isDisplayNone() && !isVisibilityHidden(); }
1938
1939 FillRule fill_rule() const { return m_fill_rule; }
1940 FillRule clip_rule() const { return m_clip_rule; }
1941
1942 virtual Rect updateShape(Path& path) = 0;
1943
1944 void updateMarkerPositions(SVGMarkerPositionList& positions, const SVGLayoutState& state);
1945 void render(SVGRenderState& state) const override;
1946
1947 const Path& path() const { return m_path; }
1948
1949private:
1950 Path m_path;
1951 Rect m_fillBoundingBox;
1952 StrokeData m_strokeData;
1953
1954 SVGPaintServer m_fill;
1955 SVGPaintServer m_stroke;
1956 SVGMarkerPositionList m_markerPositions;
1957
1958 FillRule m_fill_rule = FillRule::NonZero;
1959 FillRule m_clip_rule = FillRule::NonZero;
1960};
1961
1963public:
1964 SVGLineElement(Document* document);
1965
1966 Rect updateShape(Path& path) final;
1967
1968private:
1969 SVGLength m_x1;
1970 SVGLength m_y1;
1971 SVGLength m_x2;
1972 SVGLength m_y2;
1973};
1974
1976public:
1977 SVGRectElement(Document* document);
1978
1979 Rect updateShape(Path& path) final;
1980
1981private:
1982 SVGLength m_x;
1983 SVGLength m_y;
1984 SVGLength m_width;
1985 SVGLength m_height;
1986 SVGLength m_rx;
1987 SVGLength m_ry;
1988};
1989
1991public:
1992 SVGEllipseElement(Document* document);
1993
1994 Rect updateShape(Path& path) final;
1995
1996private:
1997 SVGLength m_cx;
1998 SVGLength m_cy;
1999 SVGLength m_rx;
2000 SVGLength m_ry;
2001};
2002
2004public:
2005 SVGCircleElement(Document* document);
2006
2007 Rect updateShape(Path& path) final;
2008
2009private:
2010 SVGLength m_cx;
2011 SVGLength m_cy;
2012 SVGLength m_r;
2013};
2014
2016public:
2017 SVGPolyElement(Document* document, ElementID id);
2018
2019 Rect updateShape(Path& path) final;
2020
2021private:
2022 SVGPointList m_points;
2023};
2024
2026public:
2027 SVGPathElement(Document* document);
2028
2029 Rect updateShape(Path& path) final;
2030
2031private:
2032 SVGPath m_d;
2033};
2034
2035// ---- svgtextelement ----
2036
2038class SVGTextElement;
2039
2041 std::optional<float> x;
2042 std::optional<float> y;
2043 std::optional<float> dx;
2044 std::optional<float> dy;
2045 std::optional<float> rotate;
2046};
2047
2048using SVGCharacterPositions = std::map<size_t, SVGCharacterPosition>;
2049
2051 SVGTextPosition(const SVGNode* node, size_t startOffset, size_t endOffset)
2052 : node(node), startOffset(startOffset), endOffset(endOffset)
2053 {}
2054
2058};
2059
2060using SVGTextPositionList = std::vector<SVGTextPosition>;
2061
2063 explicit SVGTextFragment(const SVGTextPositioningElement* element) : element(element) {}
2066 size_t offset = 0;
2067 size_t length = 0;
2068 bool startsNewTextChunk = false;
2069 float x = 0;
2070 float y = 0;
2071 float width = 0;
2072 float height = 0;
2073 float angle = 0;
2074};
2075
2076using SVGTextFragmentList = std::vector<SVGTextFragment>;
2077
2079public:
2080 SVGTextFragmentsBuilder(std::u32string& text, SVGTextFragmentList& fragments);
2081
2082 void build(const SVGTextElement* textElement);
2083
2084private:
2085 void handleText(const SVGTextNode* node);
2086 void handleElement(const SVGTextPositioningElement* element);
2087 void fillCharacterPositions(const SVGTextPosition& position);
2088 std::u32string& m_text;
2089 SVGTextFragmentList& m_fragments;
2090 SVGCharacterPositions m_characterPositions;
2091 SVGTextPositionList m_textPositions;
2092 size_t m_characterOffset = 0;
2093 float m_x = 0;
2094 float m_y = 0;
2095};
2096
2098public:
2100
2101 bool isTextPositioningElement() const final { return true; }
2102
2103 const LengthList& x() const { return m_x.values(); }
2104 const LengthList& y() const { return m_y.values(); }
2105 const LengthList& dx() const { return m_dx.values(); }
2106 const LengthList& dy() const { return m_dy.values(); }
2107 const NumberList& rotate() const { return m_rotate.values(); }
2108
2109 const SVGLength& textLength() const { return m_textLength; }
2110 LengthAdjust lengthAdjust() const { return m_lengthAdjust.value(); }
2111
2112 const Font& font() const { return m_font; }
2113 const SVGPaintServer& fill() const { return m_fill; }
2114 const SVGPaintServer& stroke() const { return m_stroke; }
2115
2116 bool isVerticalWritingMode() const { return m_writing_mode == WritingMode::Vertical; }
2117 bool isUprightTextOrientation() const { return m_text_orientation == TextOrientation::Upright; }
2118
2119 float stroke_width() const { return m_stroke_width; }
2120 float letter_spacing() const { return m_letter_spacing; }
2121 float word_spacing() const { return m_word_spacing; }
2122 float baseline_offset() const { return m_baseline_offset; }
2123 AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; }
2124 DominantBaseline dominant_baseline() const { return m_dominant_baseline; }
2125 TextAnchor text_anchor() const { return m_text_anchor; }
2126 WhiteSpace white_space() const { return m_white_space; }
2127 Direction direction() const { return m_direction; }
2128
2129 void layoutElement(const SVGLayoutState& state) override;
2130
2131private:
2132 float convertBaselineOffset(const BaselineShift& baselineShift) const;
2133 SVGLengthList m_x;
2134 SVGLengthList m_y;
2135 SVGLengthList m_dx;
2136 SVGLengthList m_dy;
2137 SVGNumberList m_rotate;
2138
2139 SVGLength m_textLength;
2140 SVGEnumeration<LengthAdjust> m_lengthAdjust;
2141
2142 Font m_font;
2143 SVGPaintServer m_fill;
2144 SVGPaintServer m_stroke;
2145
2146 float m_stroke_width = 1.f;
2147 float m_letter_spacing = 0.f;
2148 float m_word_spacing = 0.f;
2149 float m_baseline_offset = 0.f;
2150 AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto;
2151 DominantBaseline m_dominant_baseline = DominantBaseline::Auto;
2152 TextAnchor m_text_anchor = TextAnchor::Start;
2153 WhiteSpace m_white_space = WhiteSpace::Default;
2154 WritingMode m_writing_mode = WritingMode::Horizontal;
2155 TextOrientation m_text_orientation = TextOrientation::Mixed;
2156 Direction m_direction = Direction::Ltr;
2157};
2158
2160public:
2161 SVGTSpanElement(Document* document);
2162};
2163
2165public:
2166 SVGTextElement(Document* document);
2167
2168 Rect fillBoundingBox() const final { return boundingBox(false); }
2169 Rect strokeBoundingBox() const final { return boundingBox(true); }
2170
2171 void layout(SVGLayoutState& state) final;
2172 void render(SVGRenderState& state) const final;
2173
2174private:
2175 Rect boundingBox(bool includeStroke) const;
2176 SVGTextFragmentList m_fragments;
2177 std::u32string m_text;
2178};
2179
2180// ---- svglayoutstate ----
2181
2183public:
2184 SVGLayoutState() = default;
2185 SVGLayoutState(const SVGLayoutState& parent, const SVGElement* element);
2186
2187 const SVGLayoutState* parent() const { return m_parent; }
2188 const SVGElement* element() const { return m_element; }
2189
2190 const Paint& fill() const { return m_fill; }
2191 const Paint& stroke() const { return m_stroke; }
2192
2193 const Color& color() const { return m_color; }
2194 const Color& stop_color() const { return m_stop_color; }
2195
2196 float opacity() const { return m_opacity; }
2197 MixBlendMode mix_blend_mode() const { return m_mix_blend_mode; }
2198 float stop_opacity() const { return m_stop_opacity; }
2199 float fill_opacity() const { return m_fill_opacity; }
2200 float stroke_opacity() const { return m_stroke_opacity; }
2201 float stroke_miterlimit() const { return m_stroke_miterlimit; }
2202 float font_size() const { return m_font_size; }
2203
2204 const Length& letter_spacing() const { return m_letter_spacing; }
2205 const Length& word_spacing() const { return m_word_spacing; }
2206
2207 const BaselineShift& baseline_shift() const { return m_baseline_shift; }
2208 const Length& stroke_width() const { return m_stroke_width; }
2209 const Length& stroke_dashoffset() const { return m_stroke_dashoffset; }
2210 const LengthList& stroke_dasharray() const { return m_stroke_dasharray; }
2211
2212 LineCap stroke_linecap() const { return m_stroke_linecap; }
2213 LineJoin stroke_linejoin() const { return m_stroke_linejoin; }
2214
2215 FillRule fill_rule() const { return m_fill_rule; }
2216 FillRule clip_rule() const { return m_clip_rule; }
2217
2218 FontWeight font_weight() const { return m_font_weight; }
2219 FontStyle font_style() const { return m_font_style; }
2220
2221 AlignmentBaseline alignment_baseline() const { return m_alignment_baseline; }
2222 DominantBaseline dominant_baseline() const { return m_dominant_baseline; }
2223
2224 TextAnchor text_anchor() const { return m_text_anchor; }
2225 WhiteSpace white_space() const { return m_white_space; }
2226 WritingMode writing_mode() const { return m_writing_mode; }
2227 TextOrientation text_orientation() const { return m_text_orientation; }
2228 Direction direction() const { return m_direction; }
2229
2230 Display display() const { return m_display; }
2231 Visibility visibility() const { return m_visibility; }
2232 Overflow overflow() const { return m_overflow; }
2233 PointerEvents pointer_events() const { return m_pointer_events; }
2234 MaskType mask_type() const { return m_mask_type; }
2235
2236 const std::string& mask() const { return m_mask; }
2237 const std::string& clip_path() const { return m_clip_path; }
2238 const std::string& filter() const { return m_filter; }
2239 const std::string& marker_start() const { return m_marker_start; }
2240 const std::string& marker_mid() const { return m_marker_mid; }
2241 const std::string& marker_end() const { return m_marker_end; }
2242 const std::string& font_family() const { return m_font_family; }
2243
2244 Font font() const;
2245
2246private:
2247 const SVGLayoutState* m_parent = nullptr;
2248 const SVGElement* m_element = nullptr;
2249
2250 Paint m_fill{Color::Black};
2251 Paint m_stroke{Color::Transparent};
2252
2253 Color m_color = Color::Black;
2254 Color m_stop_color = Color::Black;
2255
2256 float m_opacity = 1.f;
2257 MixBlendMode m_mix_blend_mode = MixBlendMode::Normal; // like opacity: not inherited
2258 float m_fill_opacity = 1.f;
2259 float m_stroke_opacity = 1.f;
2260 float m_stop_opacity = 1.f;
2261 float m_stroke_miterlimit = 4.f;
2262 float m_font_size = 12.f;
2263
2264 Length m_letter_spacing{0.f, LengthUnits::None};
2265 Length m_word_spacing{0.f, LengthUnits::None};
2266
2267 BaselineShift m_baseline_shift;
2268 Length m_stroke_width{1.f, LengthUnits::None};
2269 Length m_stroke_dashoffset{0.f, LengthUnits::None};
2270 LengthList m_stroke_dasharray;
2271
2272 LineCap m_stroke_linecap = LineCap::Butt;
2273 LineJoin m_stroke_linejoin = LineJoin::Miter;
2274
2275 FillRule m_fill_rule = FillRule::NonZero;
2276 FillRule m_clip_rule = FillRule::NonZero;
2277
2278 FontWeight m_font_weight = FontWeight::Normal;
2279 FontStyle m_font_style = FontStyle::Normal;
2280
2281 AlignmentBaseline m_alignment_baseline = AlignmentBaseline::Auto;
2282 DominantBaseline m_dominant_baseline = DominantBaseline::Auto;
2283
2284 TextAnchor m_text_anchor = TextAnchor::Start;
2285 WhiteSpace m_white_space = WhiteSpace::Default;
2286 WritingMode m_writing_mode = WritingMode::Horizontal;
2287 TextOrientation m_text_orientation = TextOrientation::Mixed;
2288 Direction m_direction = Direction::Ltr;
2289
2290 Display m_display = Display::Inline;
2291 Visibility m_visibility = Visibility::Visible;
2292 Overflow m_overflow = Overflow::Visible;
2293 PointerEvents m_pointer_events = PointerEvents::Auto;
2294 MaskType m_mask_type = MaskType::Luminance;
2295
2296 std::string m_mask;
2297 std::string m_clip_path;
2298 std::string m_filter;
2299 std::string m_marker_start;
2300 std::string m_marker_mid;
2301 std::string m_marker_end;
2302 std::string m_font_family;
2303};
2304
2305// ================================================================
2306// Implementation
2307// ================================================================
2308
2309// ---- svgproperty (impl) ----
2310using namespace render; // render/ layer (novasvg::render), the former plutovg
2311
2313{
2314 static const struct {
2315 std::string_view name;
2316 PropertyID value;
2317 } table[] = {
2318 {"class", PropertyID::Class},
2319 {"clipPathUnits", PropertyID::ClipPathUnits},
2320 {"cx", PropertyID::Cx},
2321 {"cy", PropertyID::Cy},
2322 {"d", PropertyID::D},
2323 {"dx", PropertyID::Dx},
2324 {"dy", PropertyID::Dy},
2325 {"fx", PropertyID::Fx},
2326 {"fy", PropertyID::Fy},
2327 {"gradientTransform", PropertyID::GradientTransform},
2328 {"gradientUnits", PropertyID::GradientUnits},
2329 {"height", PropertyID::Height},
2330 {"href", PropertyID::Href},
2331 {"id", PropertyID::Id},
2332 {"in", PropertyID::In},
2333 {"in2", PropertyID::In2},
2334 {"k1", PropertyID::K1},
2335 {"k2", PropertyID::K2},
2336 {"k3", PropertyID::K3},
2337 {"k4", PropertyID::K4},
2338 {"lengthAdjust", PropertyID::LengthAdjust},
2339 {"markerHeight", PropertyID::MarkerHeight},
2340 {"markerUnits", PropertyID::MarkerUnits},
2341 {"markerWidth", PropertyID::MarkerWidth},
2342 {"maskContentUnits", PropertyID::MaskContentUnits},
2343 {"maskUnits", PropertyID::MaskUnits},
2344 {"offset", PropertyID::Offset},
2345 {"operator", PropertyID::Operator},
2346 {"orient", PropertyID::Orient},
2347 {"patternContentUnits", PropertyID::PatternContentUnits},
2348 {"patternTransform", PropertyID::PatternTransform},
2349 {"patternUnits", PropertyID::PatternUnits},
2350 {"points", PropertyID::Points},
2351 {"preserveAspectRatio", PropertyID::PreserveAspectRatio},
2352 {"r", PropertyID::R},
2353 {"refX", PropertyID::RefX},
2354 {"refY", PropertyID::RefY},
2355 {"requiredExtensions", PropertyID::RequiredExtensions},
2356 {"result", PropertyID::Result},
2357 {"rotate", PropertyID::Rotate},
2358 {"rx", PropertyID::Rx},
2359 {"ry", PropertyID::Ry},
2360 {"spreadMethod", PropertyID::SpreadMethod},
2361 {"stdDeviation", PropertyID::StdDeviation},
2362 {"style", PropertyID::Style},
2363 {"systemLanguage", PropertyID::SystemLanguage},
2364 {"textLength", PropertyID::TextLength},
2365 {"transform", PropertyID::Transform},
2366 {"viewBox", PropertyID::ViewBox},
2367 {"width", PropertyID::Width},
2368 {"x", PropertyID::X},
2369 {"x1", PropertyID::X1},
2370 {"x2", PropertyID::X2},
2371 {"xlink:href", PropertyID::Href},
2372 {"xml:space", PropertyID::White_Space},
2373 {"y", PropertyID::Y},
2374 {"y1", PropertyID::Y1},
2375 {"y2", PropertyID::Y2}
2376 };
2377
2378 auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
2379 if(it == std::end(table) || it->name != name)
2380 return csspropertyid(name);
2381 return it->value;
2382}
2383
2385{
2386 static const struct {
2387 std::string_view name;
2388 PropertyID value;
2389 } table[] = {
2390 {"alignment-baseline", PropertyID::Alignment_Baseline},
2391 {"background-color", PropertyID::Background_Color},
2392 {"baseline-shift", PropertyID::Baseline_Shift},
2393 {"clip-path", PropertyID::Clip_Path},
2394 {"clip-rule", PropertyID::Clip_Rule},
2395 {"color", PropertyID::Color},
2396 {"direction", PropertyID::Direction},
2397 {"display", PropertyID::Display},
2398 {"dominant-baseline", PropertyID::Dominant_Baseline},
2399 {"fill", PropertyID::Fill},
2400 {"fill-opacity", PropertyID::Fill_Opacity},
2401 {"fill-rule", PropertyID::Fill_Rule},
2402 {"filter", PropertyID::Filter},
2403 {"flood-color", PropertyID::Flood_Color},
2404 {"flood-opacity", PropertyID::Flood_Opacity},
2405 {"font-family", PropertyID::Font_Family},
2406 {"font-size", PropertyID::Font_Size},
2407 {"font-style", PropertyID::Font_Style},
2408 {"font-weight", PropertyID::Font_Weight},
2409 {"letter-spacing", PropertyID::Letter_Spacing},
2410 {"marker-end", PropertyID::Marker_End},
2411 {"marker-mid", PropertyID::Marker_Mid},
2412 {"marker-start", PropertyID::Marker_Start},
2413 {"mask", PropertyID::Mask},
2414 {"mask-type", PropertyID::Mask_Type},
2415 {"mix-blend-mode", PropertyID::Mix_Blend_Mode},
2416 {"opacity", PropertyID::Opacity},
2417 {"overflow", PropertyID::Overflow},
2418 {"pointer-events", PropertyID::Pointer_Events},
2419 {"stop-color", PropertyID::Stop_Color},
2420 {"stop-opacity", PropertyID::Stop_Opacity},
2421 {"stroke", PropertyID::Stroke},
2422 {"stroke-dasharray", PropertyID::Stroke_Dasharray},
2423 {"stroke-dashoffset", PropertyID::Stroke_Dashoffset},
2424 {"stroke-linecap", PropertyID::Stroke_Linecap},
2425 {"stroke-linejoin", PropertyID::Stroke_Linejoin},
2426 {"stroke-miterlimit", PropertyID::Stroke_Miterlimit},
2427 {"stroke-opacity", PropertyID::Stroke_Opacity},
2428 {"stroke-width", PropertyID::Stroke_Width},
2429 {"text-anchor", PropertyID::Text_Anchor},
2430 {"text-orientation", PropertyID::Text_Orientation},
2431 {"transform-box", PropertyID::Transform_Box},
2432 {"transform-origin", PropertyID::Transform_Origin},
2433 {"visibility", PropertyID::Visibility},
2434 {"white-space", PropertyID::White_Space},
2435 {"word-spacing", PropertyID::Word_Spacing},
2436 {"writing-mode", PropertyID::Writing_Mode}
2437 };
2438
2439 auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
2440 if(it == std::end(table) || it->name != name)
2441 return PropertyID::Unknown;
2442 return it->value;
2443}
2444
2445NOVASVG_INLINE SVGProperty::SVGProperty(PropertyID id)
2446 : m_id(id)
2447{
2448}
2449
2450NOVASVG_INLINE bool SVGString::parse(std::string_view input)
2451{
2453 m_value.assign(input);
2454 return true;
2455}
2456
2457template<>
2459{
2460 static const SVGEnumerationEntry<SpreadMethod> entries[] = {
2461 {SpreadMethod::Pad, "pad"},
2462 {SpreadMethod::Reflect, "reflect"},
2463 {SpreadMethod::Repeat, "repeat"}
2464 };
2465
2466 return parseEnum(input, entries);
2467}
2468
2469template<>
2471{
2472 static const SVGEnumerationEntry<Units> entries[] = {
2473 {Units::UserSpaceOnUse, "userSpaceOnUse"},
2474 {Units::ObjectBoundingBox, "objectBoundingBox"}
2475 };
2476
2477 return parseEnum(input, entries);
2478}
2479
2480template<>
2482{
2483 static const SVGEnumerationEntry<MarkerUnits> entries[] = {
2484 {MarkerUnits::StrokeWidth, "strokeWidth"},
2485 {MarkerUnits::UserSpaceOnUse, "userSpaceOnUse"}
2486 };
2487
2488 return parseEnum(input, entries);
2489}
2490
2491template<>
2493{
2494 static const SVGEnumerationEntry<LengthAdjust> entries[] = {
2495 {LengthAdjust::Spacing, "spacing"},
2496 {LengthAdjust::SpacingAndGlyphs, "spacingAndGlyphs"}
2497 };
2498
2499 return parseEnum(input, entries);
2500}
2501
2502template<typename Enum>
2503template<unsigned int N>
2504NOVASVG_INLINE bool SVGEnumeration<Enum>::parseEnum(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N])
2505{
2507 for(const auto& entry : entries) {
2508 if(input == entry.second) {
2509 m_value = entry.first;
2510 return true;
2511 }
2512 }
2513
2514 return false;
2515}
2516
2517NOVASVG_INLINE bool SVGAngle::parse(std::string_view input)
2518{
2520 if(input == "auto") {
2521 m_value = 0.f;
2522 m_orientType = OrientType::Auto;
2523 return true;
2524 }
2525
2526 if(input == "auto-start-reverse") {
2527 m_value = 0.f;
2528 m_orientType = OrientType::AutoStartReverse;
2529 return true;
2530 }
2531
2532 float value = 0.f;
2533 if(!parseNumber(input, value))
2534 return false;
2535 if(!input.empty()) {
2536 if(input == "rad")
2537 value *= 180.f / NOVASVG_PI;
2538 else if(input == "grad")
2539 value *= 360.f / 400.f;
2540 else if(input == "turn")
2541 value *= 360.f;
2542 else if(input != "deg") {
2543 return false;
2544 }
2545 }
2546
2547 m_value = value;
2548 m_orientType = OrientType::Angle;
2549 return true;
2550}
2551
2552NOVASVG_INLINE bool Length::parse(std::string_view input, LengthNegativeMode mode)
2553{
2554 float value = 0.f;
2556 if(!parseNumber(input, value))
2557 return false;
2558 if(value < 0.f && mode == LengthNegativeMode::Forbid)
2559 return false;
2560 if(input.empty()) {
2561 m_value = value;
2562 m_units = LengthUnits::None;
2563 return true;
2564 }
2565
2566 constexpr auto dpi = 96.f;
2567 switch(input.front()) {
2568 case '%':
2569 m_value = value;
2570 m_units = LengthUnits::Percent;
2571 input.remove_prefix(1);
2572 break;
2573 case 'p':
2574 input.remove_prefix(1);
2575 if(input.empty())
2576 return false;
2577 else if(input.front() == 'x')
2578 m_value = value;
2579 else if(input.front() == 'c')
2580 m_value = value * dpi / 6.f;
2581 else if(input.front() == 't')
2582 m_value = value * dpi / 72.f;
2583 else
2584 return false;
2585 m_units = LengthUnits::Px;
2586 input.remove_prefix(1);
2587 break;
2588 case 'i':
2589 input.remove_prefix(1);
2590 if(input.empty())
2591 return false;
2592 else if(input.front() == 'n')
2593 m_value = value * dpi;
2594 else
2595 return false;
2596 m_units = LengthUnits::Px;
2597 input.remove_prefix(1);
2598 break;
2599 case 'c':
2600 input.remove_prefix(1);
2601 if(input.empty())
2602 return false;
2603 else if(input.front() == 'm')
2604 m_value = value * dpi / 2.54f;
2605 else
2606 return false;
2607 m_units = LengthUnits::Px;
2608 input.remove_prefix(1);
2609 break;
2610 case 'm':
2611 input.remove_prefix(1);
2612 if(input.empty())
2613 return false;
2614 else if(input.front() == 'm')
2615 m_value = value * dpi / 25.4f;
2616 else
2617 return false;
2618 m_units = LengthUnits::Px;
2619 input.remove_prefix(1);
2620 break;
2621 case 'e':
2622 input.remove_prefix(1);
2623 if(input.empty())
2624 return false;
2625 else if(input.front() == 'm')
2626 m_units = LengthUnits::Em;
2627 else if(input.front() == 'x')
2628 m_units = LengthUnits::Ex;
2629 else
2630 return false;
2631 m_value = value;
2632 input.remove_prefix(1);
2633 break;
2634 default:
2635 return false;
2636 }
2637
2638 return input.empty();
2639}
2640
2642{
2643 if(length.units() == LengthUnits::Percent) {
2644 if(m_units == Units::UserSpaceOnUse)
2645 return length.value() * viewportDimension(direction) / 100.f;
2646 return length.value() / 100.f;
2647 }
2648
2649 if(length.units() == LengthUnits::Ex)
2650 return length.value() * m_element->font_size() / 2.f;
2651 if(length.units() == LengthUnits::Em)
2652 return length.value() * m_element->font_size();
2653 return length.value();
2654}
2655
2656NOVASVG_INLINE float LengthContext::viewportDimension(LengthDirection direction) const
2657{
2658 auto viewportSize = m_element->currentViewportSize();
2659 switch(direction) {
2661 return viewportSize.w;
2663 return viewportSize.h;
2664 default:
2665 return std::sqrt(viewportSize.w * viewportSize.w + viewportSize.h * viewportSize.h) / NOVASVG_SQRT2;
2666 }
2667}
2668
2669NOVASVG_INLINE bool SVGLength::parse(std::string_view input)
2670{
2671 return m_value.parse(input, m_negativeMode);
2672}
2673
2674NOVASVG_INLINE bool SVGLengthList::parse(std::string_view input)
2675{
2676 m_values.clear();
2677 while(!input.empty()) {
2678 size_t count = 0;
2679 while(count < input.length() && input[count] != ',' && !IS_WS(input[count]))
2680 ++count;
2681 if(count == 0)
2682 break;
2683 Length value(0, LengthUnits::None);
2684 if(!value.parse(input.substr(0, count), m_negativeMode))
2685 return false;
2686 input.remove_prefix(count);
2688 m_values.push_back(value);
2689 }
2690
2691 return true;
2692}
2693
2694NOVASVG_INLINE bool SVGNumber::parse(std::string_view input)
2695{
2696 float value = 0.f;
2698 if(!parseNumber(input, value))
2699 return false;
2700 if(!input.empty())
2701 return false;
2702 m_value = value;
2703 return true;
2704}
2705
2706NOVASVG_INLINE bool SVGNumberPercentage::parse(std::string_view input)
2707{
2708 float value = 0.f;
2710 if(!parseNumber(input, value))
2711 return false;
2712 if(!input.empty() && input.front() == '%') {
2713 value /= 100.f;
2714 input.remove_prefix(1);
2715 }
2716
2717 if(!input.empty())
2718 return false;
2719 m_value = std::clamp(value, 0.f, 1.f);
2720 return true;
2721}
2722
2723NOVASVG_INLINE bool SVGNumberList::parse(std::string_view input)
2724{
2725 m_values.clear();
2726 stripLeadingSpaces(input);
2727 while(!input.empty()) {
2728 float value = 0.f;
2729 if(!parseNumber(input, value))
2730 return false;
2732 m_values.push_back(value);
2733 }
2734
2735 return true;
2736}
2737
2738NOVASVG_INLINE bool SVGPath::parse(std::string_view input)
2739{
2740 return m_value.parse(input.data(), input.length());
2741}
2742
2743NOVASVG_INLINE bool SVGPoint::parse(std::string_view input)
2744{
2745 Point value;
2747 if(!parseNumber(input, value.x)
2748 || !skipOptionalSpaces(input)
2749 || !parseNumber(input, value.y)
2750 || !input.empty()) {
2751 return false;
2752 }
2753
2754 m_value = value;
2755 return true;
2756}
2757
2758NOVASVG_INLINE bool SVGPointList::parse(std::string_view input)
2759{
2760 m_values.clear();
2761 stripLeadingSpaces(input);
2762 while(!input.empty()) {
2763 Point value;
2764 if(!parseNumber(input, value.x)
2765 || !skipOptionalSpacesOrComma(input)
2766 || !parseNumber(input, value.y)) {
2767 return false;
2768 }
2769
2770 m_values.push_back(value);
2772 }
2773
2774 return true;
2775}
2776
2777NOVASVG_INLINE bool SVGRect::parse(std::string_view input)
2778{
2779 Rect value;
2781 if(!parseNumber(input, value.x)
2782 || !skipOptionalSpacesOrComma(input)
2783 || !parseNumber(input, value.y)
2784 || !skipOptionalSpacesOrComma(input)
2785 || !parseNumber(input, value.w)
2786 || !skipOptionalSpacesOrComma(input)
2787 || !parseNumber(input, value.h)
2788 || !input.empty()) {
2789 return false;
2790 }
2791
2792 if(value.w < 0.f || value.h < 0.f)
2793 return false;
2794 m_value = value;
2795 return true;
2796}
2797
2798NOVASVG_INLINE bool SVGTransform::parse(std::string_view input)
2799{
2800 return m_value.parse(input.data(), input.length());
2801}
2802
2803NOVASVG_INLINE bool SVGTransformBox::parse(std::string_view input)
2804{
2806 if(input == "fill-box" || input == "content-box") {
2807 m_value = TransformBoxType::FillBox;
2808 } else if(input == "view-box" || input == "border-box" || input == "stroke-box") {
2809 // border-box/stroke-box aren't distinguished from view-box here (see
2810 // the class comment) -- treated as "not fill-box" for origin purposes.
2811 m_value = TransformBoxType::ViewBox;
2812 } else {
2813 return false;
2814 }
2815
2816 m_specified = true;
2817 return true;
2818}
2819
2820NOVASVG_INLINE bool SVGTransformOrigin::parse(std::string_view input)
2821{
2822 auto parseComponent = [](std::string_view token, float& value, bool& isPercent) {
2823 if(token == "left" || token == "top") { value = 0.f; isPercent = true; return true; }
2824 if(token == "center") { value = 0.5f; isPercent = true; return true; }
2825 if(token == "right" || token == "bottom") { value = 1.f; isPercent = true; return true; }
2826 float number = 0.f;
2827 if(!parseNumber(token, number))
2828 return false;
2829 if(!token.empty() && token.front() == '%') {
2830 value = number / 100.f;
2831 isPercent = true;
2832 token.remove_prefix(1);
2833 } else {
2834 value = number;
2835 isPercent = false;
2836 }
2837 return token.empty();
2838 };
2839
2840 std::vector<std::string_view> tokens;
2842 while(!input.empty()) {
2843 size_t count = 0;
2844 while(count < input.length() && !IS_WS(input[count]))
2845 ++count;
2846 tokens.push_back(input.substr(0, count));
2847 input.remove_prefix(count);
2848 skipOptionalSpaces(input);
2849 }
2850
2851 if(tokens.empty() || tokens.size() > 2)
2852 return false;
2853
2854 float x = 0.5f, y = 0.5f;
2855 bool xIsPercent = true, yIsPercent = true;
2856 if(tokens.size() == 1) {
2857 if(tokens[0] == "top" || tokens[0] == "bottom") {
2858 if(!parseComponent(tokens[0], y, yIsPercent))
2859 return false;
2860 } else if(!parseComponent(tokens[0], x, xIsPercent)) {
2861 return false;
2862 }
2863 } else {
2864 if(!parseComponent(tokens[0], x, xIsPercent) || !parseComponent(tokens[1], y, yIsPercent))
2865 return false;
2866 }
2867
2868 m_x = x;
2869 m_y = y;
2870 m_xIsPercent = xIsPercent;
2871 m_yIsPercent = yIsPercent;
2872 m_specified = true;
2873 return true;
2874}
2875
2877{
2879 stripLeadingSpaces(input);
2880 if(skipString(input, "none"))
2882 else if(skipString(input, "xMinYMin"))
2884 else if(skipString(input, "xMidYMin"))
2886 else if(skipString(input, "xMaxYMin"))
2888 else if(skipString(input, "xMinYMid"))
2890 else if(skipString(input, "xMidYMid"))
2892 else if(skipString(input, "xMaxYMid"))
2894 else if(skipString(input, "xMinYMax"))
2896 else if(skipString(input, "xMidYMax"))
2898 else if(skipString(input, "xMaxYMax"))
2900 else {
2901 return false;
2902 }
2903
2905 skipOptionalSpaces(input);
2906 if(skipString(input, "meet")) {
2908 } else if(skipString(input, "slice")) {
2910 }
2911
2914 skipOptionalSpaces(input);
2915 if(!input.empty())
2916 return false;
2917 m_alignType = alignType;
2918 m_meetOrSlice = meetOrSlice;
2919 return true;
2920}
2921
2922NOVASVG_INLINE Rect SVGPreserveAspectRatio::getClipRect(const Rect& viewBoxRect, const Size& viewportSize) const
2923{
2924 assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty());
2925 auto xScale = viewportSize.w / viewBoxRect.w;
2926 auto yScale = viewportSize.h / viewBoxRect.h;
2927 if(m_alignType == AlignType::None) {
2928 return Rect(viewBoxRect.x, viewBoxRect.y, viewportSize.w / xScale, viewportSize.h / yScale);
2929 }
2930
2931 auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? NOVASVG_MIN(xScale, yScale) : NOVASVG_MAX(xScale, yScale);
2932 auto xOffset = -viewBoxRect.x * scale;
2933 auto yOffset = -viewBoxRect.y * scale;
2934 auto viewWidth = viewBoxRect.w * scale;
2935 auto viewHeight = viewBoxRect.h * scale;
2936 switch(m_alignType) {
2940 xOffset += (viewportSize.w - viewWidth) * 0.5f;
2941 break;
2945 xOffset += (viewportSize.w - viewWidth);
2946 break;
2947 default:
2948 break;
2949 }
2950
2951 switch(m_alignType) {
2955 yOffset += (viewportSize.h - viewHeight) * 0.5f;
2956 break;
2960 yOffset += (viewportSize.h - viewHeight);
2961 break;
2962 default:
2963 break;
2964 }
2965
2966 return Rect(-xOffset / scale, -yOffset / scale, viewportSize.w / scale, viewportSize.h / scale);
2967}
2968
2969NOVASVG_INLINE Transform SVGPreserveAspectRatio::getTransform(const Rect& viewBoxRect, const Size& viewportSize) const
2970{
2971 assert(!viewBoxRect.isEmpty() && !viewportSize.isEmpty());
2972 auto xScale = viewportSize.w / viewBoxRect.w;
2973 auto yScale = viewportSize.h / viewBoxRect.h;
2974 if(m_alignType == AlignType::None) {
2975 return Transform(xScale, 0, 0, yScale, -viewBoxRect.x * xScale, -viewBoxRect.y * yScale);
2976 }
2977
2978 auto scale = (m_meetOrSlice == MeetOrSlice::Meet) ? NOVASVG_MIN(xScale, yScale) : NOVASVG_MAX(xScale, yScale);
2979 auto xOffset = -viewBoxRect.x * scale;
2980 auto yOffset = -viewBoxRect.y * scale;
2981 auto viewWidth = viewBoxRect.w * scale;
2982 auto viewHeight = viewBoxRect.h * scale;
2983 switch(m_alignType) {
2987 xOffset += (viewportSize.w - viewWidth) * 0.5f;
2988 break;
2992 xOffset += (viewportSize.w - viewWidth);
2993 break;
2994 default:
2995 break;
2996 }
2997
2998 switch(m_alignType) {
3002 yOffset += (viewportSize.h - viewHeight) * 0.5f;
3003 break;
3007 yOffset += (viewportSize.h - viewHeight);
3008 break;
3009 default:
3010 break;
3011 }
3012
3013 return Transform(scale, 0, 0, scale, xOffset, yOffset);
3014}
3015
3017{
3018 if(m_alignType == AlignType::None)
3019 return;
3020 auto viewSize = dstRect.size();
3021 auto imageSize = srcRect.size();
3022 if(m_meetOrSlice == MeetOrSlice::Meet) {
3023 auto scale = imageSize.h / imageSize.w;
3024 if(viewSize.h > viewSize.w * scale) {
3025 dstRect.h = viewSize.w * scale;
3026 switch(m_alignType) {
3030 dstRect.y += (viewSize.h - dstRect.h) * 0.5f;
3031 break;
3035 dstRect.y += viewSize.h - dstRect.h;
3036 break;
3037 default:
3038 break;
3039 }
3040 }
3041
3042 if(viewSize.w > viewSize.h / scale) {
3043 dstRect.w = viewSize.h / scale;
3044 switch(m_alignType) {
3048 dstRect.x += (viewSize.w - dstRect.w) * 0.5f;
3049 break;
3053 dstRect.x += viewSize.w - dstRect.w;
3054 break;
3055 default:
3056 break;
3057 }
3058 }
3059 } else if(m_meetOrSlice == MeetOrSlice::Slice) {
3060 auto scale = imageSize.h / imageSize.w;
3061 if(viewSize.h < viewSize.w * scale) {
3062 srcRect.h = viewSize.h * (imageSize.w / viewSize.w);
3063 switch(m_alignType) {
3067 srcRect.y += (imageSize.h - srcRect.h) * 0.5f;
3068 break;
3072 srcRect.y += imageSize.h - srcRect.h;
3073 break;
3074 default:
3075 break;
3076 }
3077 }
3078
3079 if(viewSize.w < viewSize.h / scale) {
3080 srcRect.w = viewSize.w * (imageSize.h / viewSize.h);
3081 switch(m_alignType) {
3085 srcRect.x += (imageSize.w - srcRect.w) * 0.5f;
3086 break;
3090 srcRect.x += imageSize.w - srcRect.w;
3091 break;
3092 default:
3093 break;
3094 }
3095 }
3096 }
3097}
3098
3099// ---- svgrenderstate (impl) ----
3101 : m_clipper(element->clipper())
3102 , m_masker(element->masker())
3103 , m_filter(element->filterElement())
3104 , m_opacity(element->opacity())
3105 , m_mixBlendMode(element->mixBlendMode())
3106{
3107}
3108
3110{
3111 return (m_clipper && m_clipper->requiresMasking()) || (mode == SVGRenderMode::Painting && (m_masker || m_filter || m_opacity < 1.f || m_mixBlendMode != MixBlendMode::Normal));
3112}
3113
3115{
3116 auto current = this;
3117 do {
3118 if(element == current->element())
3119 return true;
3120 current = current->parent();
3121 } while(current);
3122 return false;
3123}
3124
3126{
3127 auto requiresCompositing = blendInfo.requiresCompositing(m_mode);
3128 if(requiresCompositing) {
3129 auto boundingBox = m_currentTransform.mapRect(m_element->paintBoundingBox());
3130 if(m_mode == SVGRenderMode::Painting && blendInfo.filterElement()) {
3131 boundingBox.inflate(blendInfo.filterElement()->pixelMargin() * m_currentTransform.xScale());
3132 }
3133 boundingBox.intersect(m_canvas->extents());
3134 m_canvas = Canvas::create(boundingBox);
3135 } else {
3136 m_canvas->save();
3137 }
3138
3139 if(!requiresCompositing && blendInfo.clipper()) {
3140 blendInfo.clipper()->applyClipPath(*this);
3141 }
3142}
3143
3145{
3146 if(m_canvas == m_parent->canvas()) {
3147 m_canvas->restore();
3148 return;
3149 }
3150
3151 auto opacity = m_mode == SVGRenderMode::Clipping ? 1.f : blendInfo.opacity();
3152 if(blendInfo.clipper())
3153 blendInfo.clipper()->applyClipMask(*this);
3154 if(m_mode == SVGRenderMode::Painting && blendInfo.masker()) {
3155 blendInfo.masker()->applyMask(*this);
3156 }
3157 if(m_mode == SVGRenderMode::Painting && blendInfo.filterElement()) {
3158 blendInfo.filterElement()->applyFilter(*this, m_currentTransform.xScale());
3159 }
3160
3161 // the finished group is blended into what is already painted under it, with its mix-blend-mode
3162 BlendMode blendMode = BlendMode::Src_Over;
3163 if(m_mode == SVGRenderMode::Painting) {
3164 switch(blendInfo.mixBlendMode()) {
3165 case MixBlendMode::Multiply: blendMode = BlendMode::Multiply; break;
3166 case MixBlendMode::Screen: blendMode = BlendMode::Screen; break;
3167 case MixBlendMode::Darken: blendMode = BlendMode::Darken; break;
3168 case MixBlendMode::Lighten: blendMode = BlendMode::Lighten; break;
3169 case MixBlendMode::Difference: blendMode = BlendMode::Difference; break;
3170 case MixBlendMode::Exclusion: blendMode = BlendMode::Exclusion; break;
3171 case MixBlendMode::Normal: break;
3172 }
3173 }
3174
3175 m_parent->m_canvas->blendCanvas(*m_canvas, blendMode, opacity);
3176}
3177
3178// ---- svgpaintelement (impl) ----
3179
3181 : SVGElement(document, id)
3182{
3183}
3184
3186 : SVGElement(document, ElementID::Stop)
3187 , m_offset(PropertyID::Offset, 0.f)
3188{
3189 addProperty(m_offset);
3190}
3191
3193{
3194 m_stop_color = state.stop_color();
3195 m_stop_opacity = state.stop_opacity();
3197}
3198
3200{
3201 Color stopColor = m_stop_color.colorWithAlpha(m_stop_opacity * opacity);
3203 m_offset.value(), { stopColor.getRF(), stopColor.getGF(), stopColor.getBF(), stopColor.getAF() }
3204 };
3205
3206 return gradientStop;
3207}
3208
3210 : SVGPaintElement(document, id)
3211 , SVGURIReference(this)
3212 , m_gradientTransform(PropertyID::GradientTransform)
3213 , m_gradientUnits(PropertyID::GradientUnits, Units::ObjectBoundingBox)
3214 , m_spreadMethod(PropertyID::SpreadMethod, SpreadMethod::Pad)
3215{
3216 addProperty(m_gradientTransform);
3217 addProperty(m_gradientUnits);
3218 addProperty(m_spreadMethod);
3219}
3220
3222{
3223 if(!attributes.hasGradientTransform() && hasAttribute(PropertyID::GradientTransform))
3224 attributes.setGradientTransform(this);
3225 if(!attributes.hasSpreadMethod() && hasAttribute(PropertyID::SpreadMethod))
3226 attributes.setSpreadMethod(this);
3227 if(!attributes.hasGradientUnits() && hasAttribute(PropertyID::GradientUnits))
3228 attributes.setGradientUnits(this);
3229 if(!attributes.hasGradientContentElement()) {
3230 for(const auto& child : children()) {
3231 if(auto element = toSVGElement(child); element && element->id() == ElementID::Stop) {
3232 attributes.setGradientContentElement(this);
3233 break;
3234 }
3235 }
3236 }
3237}
3238
3251
3252NOVASVG_INLINE SVGLinearGradientAttributes SVGLinearGradientElement::collectGradientAttributes() const
3253{
3255 std::set<const SVGGradientElement*> processedGradients;
3256 const SVGGradientElement* current = this;
3257 while(true) {
3259 if(current->id() == ElementID::LinearGradient) {
3260 auto element = static_cast<const SVGLinearGradientElement*>(current);
3261 if(!attributes.hasX1() && element->hasAttribute(PropertyID::X1))
3262 attributes.setX1(element);
3263 if(!attributes.hasY1() && element->hasAttribute(PropertyID::Y1))
3264 attributes.setY1(element);
3265 if(!attributes.hasX2() && element->hasAttribute(PropertyID::X2))
3266 attributes.setX2(element);
3267 if(!attributes.hasY2() && element->hasAttribute(PropertyID::Y2)) {
3268 attributes.setY2(element);
3269 }
3270 }
3271
3272 auto targetElement = current->getTargetElement(document());
3273 if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
3274 break;
3275 processedGradients.insert(current);
3276 current = static_cast<const SVGGradientElement*>(targetElement);
3277 if(processedGradients.count(current) > 0) {
3278 break;
3279 }
3280 }
3281
3282 attributes.setDefaultValues(this);
3283 return attributes;
3284}
3285
3286NOVASVG_INLINE GradientStops buildGradientStops(const SVGGradientElement* element, float opacity)
3287{
3288 GradientStops gradientStops;
3289
3290 const auto& children = element->children();
3291 gradientStops.reserve(children.size());
3292 for(const auto& child : children) {
3293 auto childElement = toSVGElement(child);
3294 if(childElement && childElement->id() == ElementID::Stop) {
3295 auto stopElement = static_cast<SVGStopElement*>(childElement);
3296 gradientStops.push_back(stopElement->gradientStop(opacity));
3297 }
3298 }
3299
3300 return gradientStops;
3301}
3302
3304{
3305 auto attributes = collectGradientAttributes();
3306 auto gradientContentElement = attributes.gradientContentElement();
3307 auto gradientStops = buildGradientStops(gradientContentElement, opacity);
3308 if(gradientStops.empty())
3309 return false;
3310 LengthContext lengthContext(this, attributes.gradientUnits());
3311 auto x1 = lengthContext.valueForLength(attributes.x1());
3312 auto y1 = lengthContext.valueForLength(attributes.y1());
3313 auto x2 = lengthContext.valueForLength(attributes.x2());
3314 auto y2 = lengthContext.valueForLength(attributes.y2());
3315 if(gradientStops.size() == 1 || (x1 == x2 && y1 == y2)) {
3316 const auto& lastStop = gradientStops.back();
3317 state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
3318 return true;
3319 }
3320
3321 auto spreadMethod = attributes.spreadMethod();
3322 auto gradientUnits = attributes.gradientUnits();
3323 auto gradientTransform = attributes.gradientTransform();
3325 auto bbox = state.fillBoundingBox();
3326 gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
3327 }
3328
3329 state->setLinearGradient(x1, y1, x2, y2, spreadMethod, gradientStops, gradientTransform);
3330 return true;
3331}
3332
3347
3349{
3350 auto attributes = collectGradientAttributes();
3351 auto gradientContentElement = attributes.gradientContentElement();
3352 auto gradientStops = buildGradientStops(gradientContentElement, opacity);
3353 if(gradientStops.empty())
3354 return false;
3355 LengthContext lengthContext(this, attributes.gradientUnits());
3356 auto r = lengthContext.valueForLength(attributes.r());
3357 if(r == 0.f || gradientStops.size() == 1) {
3358 const auto& lastStop = gradientStops.back();
3359 state->setColor(lastStop.color.r, lastStop.color.g, lastStop.color.b, lastStop.color.a);
3360 return true;
3361 }
3362
3363 auto fx = lengthContext.valueForLength(attributes.fx());
3364 auto fy = lengthContext.valueForLength(attributes.fy());
3365 auto cx = lengthContext.valueForLength(attributes.cx());
3366 auto cy = lengthContext.valueForLength(attributes.cy());
3367
3368 auto spreadMethod = attributes.spreadMethod();
3369 auto gradientUnits = attributes.gradientUnits();
3370 auto gradientTransform = attributes.gradientTransform();
3372 auto bbox = state.fillBoundingBox();
3373 gradientTransform.postMultiply(Transform(bbox.w, 0, 0, bbox.h, bbox.x, bbox.y));
3374 }
3375
3376 state->setRadialGradient(cx, cy, r, fx, fy, spreadMethod, gradientStops, gradientTransform);
3377 return true;
3378}
3379
3380NOVASVG_INLINE SVGRadialGradientAttributes SVGRadialGradientElement::collectGradientAttributes() const
3381{
3383 std::set<const SVGGradientElement*> processedGradients;
3384 const SVGGradientElement* current = this;
3385 while(true) {
3387 if(current->id() == ElementID::RadialGradient) {
3388 auto element = static_cast<const SVGRadialGradientElement*>(current);
3389 if(!attributes.hasCx() && element->hasAttribute(PropertyID::Cx))
3390 attributes.setCx(element);
3391 if(!attributes.hasCy() && element->hasAttribute(PropertyID::Cy))
3392 attributes.setCy(element);
3393 if(!attributes.hasR() && element->hasAttribute(PropertyID::R))
3394 attributes.setR(element);
3395 if(!attributes.hasFx() && element->hasAttribute(PropertyID::Fx))
3396 attributes.setFx(element);
3397 if(!attributes.hasFy() && element->hasAttribute(PropertyID::Fy)) {
3398 attributes.setFy(element);
3399 }
3400 }
3401
3402 auto targetElement = current->getTargetElement(document());
3403 if(!targetElement || !(targetElement->id() == ElementID::LinearGradient || targetElement->id() == ElementID::RadialGradient))
3404 break;
3405 processedGradients.insert(current);
3406 current = static_cast<const SVGGradientElement*>(targetElement);
3407 if(processedGradients.count(current) > 0) {
3408 break;
3409 }
3410 }
3411
3412 attributes.setDefaultValues(this);
3413 return attributes;
3414}
3415
3417 : SVGPaintElement(document, ElementID::Pattern)
3418 , SVGURIReference(this)
3419 , SVGFitToViewBox(this)
3424 , m_patternTransform(PropertyID::PatternTransform)
3425 , m_patternUnits(PropertyID::PatternUnits, Units::ObjectBoundingBox)
3426 , m_patternContentUnits(PropertyID::PatternContentUnits, Units::UserSpaceOnUse)
3427{
3428 addProperty(m_x);
3429 addProperty(m_y);
3430 addProperty(m_width);
3431 addProperty(m_height);
3432 addProperty(m_patternTransform);
3433 addProperty(m_patternUnits);
3434 addProperty(m_patternContentUnits);
3435}
3436
3438{
3439 if(state.hasCycleReference(this))
3440 return false;
3441 auto attributes = collectPatternAttributes();
3442 auto patternContentElement = attributes.patternContentElement();
3443 if(patternContentElement == nullptr)
3444 return false;
3445 LengthContext lengthContext(this, attributes.patternUnits());
3446 Rect patternRect = {
3447 lengthContext.valueForLength(attributes.x()),
3448 lengthContext.valueForLength(attributes.y()),
3449 lengthContext.valueForLength(attributes.width()),
3450 lengthContext.valueForLength(attributes.height())
3451 };
3452
3453 if(attributes.patternUnits() == Units::ObjectBoundingBox) {
3454 auto bbox = state.fillBoundingBox();
3455 patternRect.x = patternRect.x * bbox.w + bbox.x;
3456 patternRect.y = patternRect.y * bbox.h + bbox.y;
3457 patternRect.w = patternRect.w * bbox.w;
3458 patternRect.h = patternRect.h * bbox.h;
3459 }
3460
3461 auto currentTransform = attributes.patternTransform() * state.currentTransform();
3462 auto xScale = currentTransform.xScale();
3463 auto yScale = currentTransform.yScale();
3464
3465 auto patternImage = Canvas::create(0, 0, patternRect.w * xScale, patternRect.h * yScale);
3466 auto patternImageTransform = Transform::scaled(xScale, yScale);
3467
3468 const auto& viewBoxRect = attributes.viewBox();
3469 if(viewBoxRect.isValid()) {
3470 const auto& preserveAspectRatio = attributes.preserveAspectRatio();
3472 } else if(attributes.patternContentUnits() == Units::ObjectBoundingBox) {
3473 auto bbox = state.fillBoundingBox();
3474 patternImageTransform.scale(bbox.w, bbox.h);
3475 }
3476
3478 patternContentElement->renderChildren(newState);
3479
3480 auto patternTransform = attributes.patternTransform();
3482 patternTransform.scale(patternRect.w / patternImage->width(), patternRect.h / patternImage->height());
3483 state->setTexture(*patternImage, TextureType::Tiled, opacity, patternTransform);
3484 return true;
3485}
3486
3487NOVASVG_INLINE SVGPatternAttributes SVGPatternElement::collectPatternAttributes() const
3488{
3490 std::set<const SVGPatternElement*> processedPatterns;
3491 const SVGPatternElement* current = this;
3492 while(true) {
3493 if(!attributes.hasX() && current->hasAttribute(PropertyID::X))
3494 attributes.setX(current);
3495 if(!attributes.hasY() && current->hasAttribute(PropertyID::Y))
3496 attributes.setY(current);
3497 if(!attributes.hasWidth() && current->hasAttribute(PropertyID::Width))
3498 attributes.setWidth(current);
3499 if(!attributes.hasHeight() && current->hasAttribute(PropertyID::Height))
3500 attributes.setHeight(current);
3501 if(!attributes.hasPatternTransform() && current->hasAttribute(PropertyID::PatternTransform))
3502 attributes.setPatternTransform(current);
3503 if(!attributes.hasPatternUnits() && current->hasAttribute(PropertyID::PatternUnits))
3504 attributes.setPatternUnits(current);
3505 if(!attributes.hasPatternContentUnits() && current->hasAttribute(PropertyID::PatternContentUnits))
3506 attributes.setPatternContentUnits(current);
3507 if(!attributes.hasViewBox() && current->hasAttribute(PropertyID::ViewBox))
3508 attributes.setViewBox(current);
3509 if(!attributes.hasPreserveAspectRatio() && current->hasAttribute(PropertyID::PreserveAspectRatio))
3510 attributes.setPreserveAspectRatio(current);
3511 if(!attributes.hasPatternContentElement()) {
3512 for(const auto& child : current->children()) {
3513 if(child->isElement()) {
3514 attributes.setPatternContentElement(current);
3515 break;
3516 }
3517 }
3518 }
3519
3520 auto targetElement = current->getTargetElement(document());
3522 break;
3523 processedPatterns.insert(current);
3524 current = static_cast<const SVGPatternElement*>(targetElement);
3525 if(processedPatterns.count(current) > 0) {
3526 break;
3527 }
3528 }
3529
3530 attributes.setDefaultValues(this);
3531 return attributes;
3532}
3533
3534// ---- svggeometryelement (impl) ----
3535using namespace render; // render/ layer (novasvg::render), the former plutovg
3536
3538{
3539 return m_element->markerBoundingBox(m_origin, m_angle, strokeWidth);
3540}
3541
3543{
3544 m_element->renderMarker(state, m_origin, m_angle, strokeWidth);
3545}
3546
3551
3553{
3555 if(m_stroke.isRenderable()) {
3556 float capLimit = m_strokeData.lineWidth() / 2.f;
3557 if(m_strokeData.lineCap() == LineCap::Square)
3558 capLimit *= NOVASVG_SQRT2;
3559 float joinLimit = m_strokeData.lineWidth() / 2.f;
3560 if(m_strokeData.lineJoin() == LineJoin::Miter) {
3561 joinLimit *= m_strokeData.miterLimit();
3562 }
3563
3564 strokeBoundingBox.inflate(NOVASVG_MAX(capLimit, joinLimit));
3565 }
3566
3567 for(const auto& markerPosition : m_markerPositions)
3568 strokeBoundingBox.unite(markerPosition.markerBoundingBox(m_strokeData.lineWidth()));
3569 return strokeBoundingBox;
3570}
3571
3573{
3574 m_fill_rule = state.fill_rule();
3575 m_clip_rule = state.clip_rule();
3576 m_fill = getPaintServer(state.fill(), state.fill_opacity());
3577 m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
3578 m_strokeData = getStrokeData(state);
3580
3581 m_path.reset();
3582 m_markerPositions.clear();
3583 m_fillBoundingBox = updateShape(m_path);
3584 updateMarkerPositions(m_markerPositions, state);
3585}
3586
3588{
3589 if(m_path.isEmpty())
3590 return;
3591 auto markerStart = getMarker(state.marker_start());
3592 auto markerMid = getMarker(state.marker_mid());
3593 auto markerEnd = getMarker(state.marker_end());
3594 if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr) {
3595 return;
3596 }
3597
3598 Point origin;
3599 Point startPoint;
3600 Point inslopePoints[2];
3601 Point outslopePoints[2];
3602
3603 int index = 0;
3604 std::array<Point, 3> points;
3605 PathIterator it(m_path);
3606 while(!it.isDone()) {
3607 switch(it.currentSegment(points)) {
3609 startPoint = points[0];
3610 inslopePoints[0] = origin;
3611 inslopePoints[1] = points[0];
3612 origin = points[0];
3613 break;
3615 inslopePoints[0] = origin;
3616 inslopePoints[1] = points[0];
3617 origin = points[0];
3618 break;
3620 inslopePoints[0] = points[1];
3621 inslopePoints[1] = points[2];
3622 origin = points[2];
3623 break;
3624 case PathCommand::Close:
3625 inslopePoints[0] = origin;
3626 inslopePoints[1] = points[0];
3627 origin = startPoint;
3628 startPoint = Point();
3629 break;
3630 }
3631
3632 it.next();
3633
3634 if(!it.isDone() && (markerStart || markerMid)) {
3635 it.currentSegment(points);
3636 outslopePoints[0] = origin;
3637 outslopePoints[1] = points[0];
3638 if(index == 0 && markerStart) {
3639 auto slope = outslopePoints[1] - outslopePoints[0];
3640 auto angle = 180.f * std::atan2(slope.y, slope.x) / NOVASVG_PI;
3641 const auto& orient = markerStart->orient();
3642 if(orient.orientType() == SVGAngle::OrientType::AutoStartReverse)
3643 angle -= 180.f;
3644 positions.emplace_back(markerStart, origin, angle);
3645 }
3646
3647 if(index > 0 && markerMid) {
3648 auto inslope = inslopePoints[1] - inslopePoints[0];
3649 auto outslope = outslopePoints[1] - outslopePoints[0];
3650 auto inangle = 180.f * std::atan2(inslope.y, inslope.x) / NOVASVG_PI;
3651 auto outangle = 180.f * std::atan2(outslope.y, outslope.x) / NOVASVG_PI;
3652 if(std::abs(inangle - outangle) > 180.f)
3653 inangle += 360.f;
3654 auto angle = (inangle + outangle) * 0.5f;
3655 positions.emplace_back(markerMid, origin, angle);
3656 }
3657 }
3658
3659 if(markerEnd && it.isDone()) {
3660 auto slope = inslopePoints[1] - inslopePoints[0];
3661 auto angle = 180.f * std::atan2(slope.y, slope.x) / NOVASVG_PI;
3662 positions.emplace_back(markerEnd, origin, angle);
3663 }
3664
3665 index += 1;
3666 }
3667}
3668
3670{
3671 if(!isRenderable())
3672 return;
3673 SVGBlendInfo blendInfo(this);
3674 SVGRenderState newState(this, state, localTransform());
3675 newState.beginGroup(blendInfo);
3676 if(newState.mode() == SVGRenderMode::Clipping) {
3677 newState->setColor(Color::White);
3678 newState->fillPath(m_path, m_clip_rule, newState.currentTransform());
3679 } else {
3680 if(m_fill.applyPaint(newState))
3681 newState->fillPath(m_path, m_fill_rule, newState.currentTransform());
3682 if(m_stroke.applyPaint(newState)) {
3683 newState->strokePath(m_path, m_strokeData, newState.currentTransform());
3684 }
3685
3686 for(const auto& markerPosition : m_markerPositions) {
3687 markerPosition.renderMarker(newState, m_strokeData.lineWidth());
3688 }
3689 }
3690
3691 newState.endGroup(blendInfo);
3692}
3693
3706
3708{
3709 LengthContext lengthContext(this);
3710 auto x1 = lengthContext.valueForLength(m_x1);
3711 auto y1 = lengthContext.valueForLength(m_y1);
3712 auto x2 = lengthContext.valueForLength(m_x2);
3713 auto y2 = lengthContext.valueForLength(m_y2);
3714
3715 path.moveTo(x1, y1);
3716 path.lineTo(x2, y2);
3717 return Rect(x1, y1, x2 - x1, y2 - y1);
3718}
3719
3736
3738{
3739 LengthContext lengthContext(this);
3740 auto width = lengthContext.valueForLength(m_width);
3741 auto height = lengthContext.valueForLength(m_height);
3742 if(width <= 0.f || height <= 0.f) {
3743 return Rect::Empty;
3744 }
3745
3746 auto x = lengthContext.valueForLength(m_x);
3747 auto y = lengthContext.valueForLength(m_y);
3748
3749 auto rx = lengthContext.valueForLength(m_rx);
3750 auto ry = lengthContext.valueForLength(m_ry);
3751
3752 if(rx <= 0.f) rx = ry;
3753 if(ry <= 0.f) ry = rx;
3754
3755 rx = NOVASVG_MIN(rx, width / 2.f);
3756 ry = NOVASVG_MIN(ry, height / 2.f);
3757
3758 path.addRoundRect(x, y, width, height, rx, ry);
3759 return Rect(x, y, width, height);
3760}
3761
3774
3776{
3777 LengthContext lengthContext(this);
3778 auto rx = lengthContext.valueForLength(m_rx);
3779 auto ry = lengthContext.valueForLength(m_ry);
3780 if(rx <= 0.f || ry <= 0.f) {
3781 return Rect::Empty;
3782 }
3783
3784 auto cx = lengthContext.valueForLength(m_cx);
3785 auto cy = lengthContext.valueForLength(m_cy);
3786 path.addEllipse(cx, cy, rx, ry);
3787 return Rect(cx - rx, cy - ry, rx + rx, ry + ry);
3788}
3789
3800
3802{
3803 LengthContext lengthContext(this);
3804 auto r = lengthContext.valueForLength(m_r);
3805 if(r <= 0.f) {
3806 return Rect::Empty;
3807 }
3808
3809 auto cx = lengthContext.valueForLength(m_cx);
3810 auto cy = lengthContext.valueForLength(m_cy);
3811 path.addEllipse(cx, cy, r, r);
3812 return Rect(cx - r, cy - r, r + r, r + r);
3813}
3814
3816 : SVGGeometryElement(document, id)
3817 , m_points(PropertyID::Points)
3818{
3819 addProperty(m_points);
3820}
3821
3823{
3824 const auto& points = m_points.values();
3825 if(points.empty()) {
3826 return Rect::Empty;
3827 }
3828
3829 path.moveTo(points[0].x, points[0].y);
3830 for(size_t i = 1; i < points.size(); i++) {
3831 path.lineTo(points[i].x, points[i].y);
3832 }
3833
3834 if(id() == ElementID::Polygon)
3835 path.close();
3836 return path.boundingRect();
3837}
3838
3840 : SVGGeometryElement(document, ElementID::Path)
3841 , m_d(PropertyID::D)
3842{
3843 addProperty(m_d);
3844}
3845
3847{
3848 path = m_d.value();
3849 return path.boundingRect();
3850}
3851
3852// ---- svgtextelement (impl) ----
3853using namespace render; // render/ layer (novasvg::render), the former plutovg
3854
3855inline const SVGTextNode* toSVGTextNode(const SVGNode* node)
3856{
3857 assert(node && node->isTextNode());
3858 return static_cast<const SVGTextNode*>(node);
3859}
3860
3862{
3863 assert(node && node->isTextPositioningElement());
3864 return static_cast<const SVGTextPositioningElement*>(node);
3865}
3866
3900
3902{
3903 auto offset = element->baseline_offset();
3904 auto parent = element->parentElement();
3905 while(parent->isTextPositioningElement()) {
3906 offset += toSVGTextPositioningElement(parent)->baseline_offset();
3907 parent = parent->parentElement();
3908 }
3909
3910 auto baseline = element->alignment_baseline();
3911 if(baseline == AlignmentBaseline::Auto || baseline == AlignmentBaseline::Baseline) {
3912 baseline = resolveDominantBaseline(element);
3913 }
3914
3915 const auto& font = element->font();
3916 switch(baseline) {
3919 offset -= font.ascent();
3920 break;
3922 offset -= font.xHeight() / 2.f;
3923 break;
3925 offset -= (font.ascent() + font.descent()) / 2.f;
3926 break;
3930 offset -= font.descent();
3931 break;
3933 offset -= font.ascent() * 8.f / 10.f;
3934 break;
3936 offset -= font.ascent() / 2.f;
3937 break;
3938 default:
3939 break;
3940 }
3941
3942 return offset;
3943}
3944
3946{
3947 auto direction = element->direction();
3948 switch(element->text_anchor()) {
3949 case TextAnchor::Start:
3950 return direction == Direction::Rtl;
3951 case TextAnchor::Middle:
3952 return true;
3953 case TextAnchor::End:
3954 return direction == Direction::Ltr;
3955 default:
3956 assert(false);
3957 }
3958
3959 return false;
3960}
3961
3963{
3964 auto direction = element->direction();
3965 switch(element->text_anchor()) {
3966 case TextAnchor::Start:
3967 if(direction == Direction::Ltr)
3968 return 0.f;
3969 return -width;
3970 case TextAnchor::Middle:
3971 return -width / 2.f;
3972 case TextAnchor::End:
3973 if(direction == Direction::Ltr)
3974 return -width;
3975 return 0.f;
3976 default:
3977 assert(false);
3978 }
3979
3980 return 0.f;
3981}
3982
3983using SVGTextFragmentIterator = SVGTextFragmentList::iterator;
3984
3986{
3987 float chunkLength = 0;
3988 const SVGTextFragment* lastFragment = nullptr;
3989 for(auto it = begin; it != end; ++it) {
3990 const SVGTextFragment& fragment = *it;
3991 chunkLength += isVerticalText ? fragment.height : fragment.width;
3992 if(!lastFragment) {
3993 lastFragment = &fragment;
3994 continue;
3995 }
3996
3997 if(isVerticalText) {
3998 chunkLength += fragment.y - (lastFragment->y + lastFragment->height);
3999 } else {
4000 chunkLength += fragment.x - (lastFragment->x + lastFragment->width);
4001 }
4002
4003 lastFragment = &fragment;
4004 }
4005
4006 return chunkLength;
4007}
4008
4010{
4011 const SVGTextFragment& firstFragment = *begin;
4012 const auto isVerticalText = firstFragment.element->isVerticalWritingMode();
4013 if(firstFragment.element->hasAttribute(PropertyID::TextLength)) {
4014 LengthContext lengthContext(firstFragment.element);
4015 auto textLength = lengthContext.valueForLength(firstFragment.element->textLength());
4016 auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText);
4017 if(textLength > 0.f && chunkLength > 0.f) {
4018 size_t numCharacters = 0;
4019 for(auto it = begin; it != end; ++it) {
4020 const SVGTextFragment& fragment = *it;
4021 numCharacters += fragment.length;
4022 }
4023
4024 if(firstFragment.element->lengthAdjust() == LengthAdjust::SpacingAndGlyphs) {
4025 auto textLengthScale = textLength / chunkLength;
4026 auto lengthAdjustTransform = Transform::translated(firstFragment.x, firstFragment.y);
4027 if(isVerticalText) {
4028 lengthAdjustTransform.scale(1.f, textLengthScale);
4029 } else {
4030 lengthAdjustTransform.scale(textLengthScale, 1.f);
4031 }
4032
4033 lengthAdjustTransform.translate(-firstFragment.x, -firstFragment.y);
4034 for(auto it = begin; it != end; ++it) {
4035 SVGTextFragment& fragment = *it;
4036 fragment.lengthAdjustTransform = lengthAdjustTransform;
4037 }
4038 } else if(numCharacters > 1) {
4039 assert(firstFragment.element->lengthAdjust() == LengthAdjust::Spacing);
4040 size_t characterOffset = 0;
4041 auto textLengthShift = (textLength - chunkLength) / (numCharacters - 1);
4042 for(auto it = begin; it != end; ++it) {
4043 SVGTextFragment& fragment = *it;
4044 if(isVerticalText) {
4045 fragment.y += textLengthShift * characterOffset;
4046 } else {
4047 fragment.x += textLengthShift * characterOffset;
4048 }
4049
4050 characterOffset += fragment.length;
4051 }
4052 }
4053 }
4054 }
4055
4056 if(needsTextAnchorAdjustment(firstFragment.element)) {
4057 auto chunkLength = calculateTextChunkLength(begin, end, isVerticalText);
4058 auto chunkOffset = calculateTextAnchorOffset(firstFragment.element, chunkLength);
4059 for(auto it = begin; it != end; ++it) {
4060 SVGTextFragment& fragment = *it;
4061 if(isVerticalText) {
4062 fragment.y += chunkOffset;
4063 } else {
4064 fragment.x += chunkOffset;
4065 }
4066 }
4067 }
4068}
4069
4071 : m_text(text), m_fragments(fragments)
4072{
4073 m_text.clear();
4074 m_fragments.clear();
4075}
4076
4078{
4079 handleElement(textElement);
4080 for(const auto& position : m_textPositions) {
4081 fillCharacterPositions(position);
4082 }
4083
4084 std::u32string_view wholeText(m_text);
4085 for(const auto& textPosition : m_textPositions) {
4086 if(!textPosition.node->isTextNode())
4087 continue;
4088 auto element = toSVGTextPositioningElement(textPosition.node->parentElement());
4089 const auto isVerticalText = element->isVerticalWritingMode();
4090 const auto isUprightText = element->isUprightTextOrientation();
4091 const auto& font = element->font();
4092
4093 SVGTextFragment fragment(element);
4094 auto recordTextFragment = [&](auto startOffset, auto endOffset) {
4095 auto text = wholeText.substr(startOffset, endOffset - startOffset);
4096 fragment.offset = startOffset;
4097 fragment.length = text.length();
4098 fragment.width = font.measureText(text);
4099 fragment.height = font.height() + font.lineGap();
4100 if(isVerticalText) {
4101 m_y += isUprightText ? fragment.height : fragment.width;
4102 } else {
4103 m_x += fragment.width;
4104 }
4105
4106 m_fragments.push_back(fragment);
4107 };
4108
4109 auto needsTextLengthSpacing = element->lengthAdjust() == LengthAdjust::Spacing && element->hasAttribute(PropertyID::TextLength);
4110 auto baselineOffset = calculateBaselineOffset(element);
4111 auto startOffset = textPosition.startOffset;
4112 auto textOffset = textPosition.startOffset;
4113 auto didStartTextFragment = false;
4114 auto applySpacingToNextCharacter = false;
4115 auto lastCharacter = 0u;
4116 auto lastAngle = 0.f;
4117 while(textOffset < textPosition.endOffset) {
4118 SVGCharacterPosition characterPosition;
4119 if(auto it = m_characterPositions.find(m_characterOffset); it != m_characterPositions.end()) {
4120 characterPosition = it->second;
4121 }
4122
4123 auto currentCharacter = wholeText.at(textOffset);
4124 auto angle = characterPosition.rotate.value_or(0);
4125 auto dx = characterPosition.dx.value_or(0);
4126 auto dy = characterPosition.dy.value_or(0);
4127
4128 auto shouldStartNewFragment = needsTextLengthSpacing || isVerticalText || applySpacingToNextCharacter
4129 || characterPosition.x || characterPosition.y || dx || dy || angle || angle != lastAngle;
4130 if(shouldStartNewFragment && didStartTextFragment) {
4131 recordTextFragment(startOffset, textOffset);
4132 applySpacingToNextCharacter = false;
4133 startOffset = textOffset;
4134 }
4135
4136 auto startsNewTextChunk = (characterPosition.x || characterPosition.y) && textOffset == textPosition.startOffset;
4137 if(startsNewTextChunk || shouldStartNewFragment || !didStartTextFragment) {
4138 m_x = dx + characterPosition.x.value_or(m_x);
4139 m_y = dy + characterPosition.y.value_or(m_y);
4140 fragment.x = isVerticalText ? m_x + baselineOffset : m_x;
4141 fragment.y = isVerticalText ? m_y : m_y - baselineOffset;
4142 fragment.angle = angle;
4143 if(isVerticalText) {
4144 if(isUprightText) {
4145 fragment.y += font.height();
4146 } else {
4147 fragment.angle += 90.f;
4148 }
4149 }
4150
4151 fragment.startsNewTextChunk = startsNewTextChunk;
4152 didStartTextFragment = true;
4153 }
4154
4155 auto spacing = element->letter_spacing();
4156 if(currentCharacter && lastCharacter && element->word_spacing()) {
4157 if(currentCharacter == ' ' && lastCharacter != ' ') {
4158 spacing += element->word_spacing();
4159 }
4160 }
4161
4162 if(spacing) {
4163 applySpacingToNextCharacter = true;
4164 if(isVerticalText) {
4165 m_y += spacing;
4166 } else {
4167 m_x += spacing;
4168 }
4169 }
4170
4171 lastAngle = angle;
4172 lastCharacter = currentCharacter;
4173 ++textOffset;
4174 ++m_characterOffset;
4175 }
4176
4177 recordTextFragment(startOffset, textOffset);
4178 }
4179
4180 if(m_fragments.empty())
4181 return;
4182 auto it = m_fragments.begin();
4183 auto begin = m_fragments.begin();
4184 auto end = m_fragments.end();
4185 for(++it; it != end; ++it) {
4186 const SVGTextFragment& fragment = *it;
4187 if(!fragment.startsNewTextChunk)
4188 continue;
4189 handleTextChunk(begin, it);
4190 begin = it;
4191 }
4192
4193 handleTextChunk(begin, it);
4194}
4195
4196NOVASVG_INLINE void SVGTextFragmentsBuilder::handleText(const SVGTextNode* node)
4197{
4198 const auto& text = node->data();
4199 if(text.empty())
4200 return;
4201 auto element = toSVGTextPositioningElement(node->parentElement());
4202 const auto startOffset = m_text.length();
4203 uint32_t lastCharacter = ' ';
4204 if(!m_text.empty()) {
4205 lastCharacter = m_text.back();
4206 }
4207
4208 text_iterator_t it;
4210 while(text_iterator_has_next(&it)) {
4211 auto currentCharacter = text_iterator_next(&it);
4212 if(currentCharacter == '\t' || currentCharacter == '\n' || currentCharacter == '\r')
4213 currentCharacter = ' ';
4214 if(currentCharacter == ' ' && lastCharacter == ' ' && element->white_space() == WhiteSpace::Default)
4215 continue;
4216 m_text.push_back(currentCharacter);
4217 lastCharacter = currentCharacter;
4218 }
4219
4220 if(startOffset < m_text.length()) {
4221 m_textPositions.emplace_back(node, startOffset, m_text.length());
4222 }
4223}
4224
4225NOVASVG_INLINE void SVGTextFragmentsBuilder::handleElement(const SVGTextPositioningElement* element)
4226{
4227 if(element->isDisplayNone())
4228 return;
4229 const auto itemIndex = m_textPositions.size();
4230 m_textPositions.emplace_back(element, m_text.length(), m_text.length());
4231 for(const auto& child : element->children()) {
4232 if(child->isTextNode()) {
4233 handleText(toSVGTextNode(child.get()));
4234 } else if(child->isTextPositioningElement()) {
4235 handleElement(toSVGTextPositioningElement(child.get()));
4236 }
4237 }
4238
4239 auto& position = m_textPositions[itemIndex];
4240 assert(position.node == element);
4241 position.endOffset = m_text.length();
4242}
4243
4244NOVASVG_INLINE void SVGTextFragmentsBuilder::fillCharacterPositions(const SVGTextPosition& position)
4245{
4246 if(!position.node->isTextPositioningElement())
4247 return;
4248 auto element = toSVGTextPositioningElement(position.node);
4249 const auto& xList = element->x();
4250 const auto& yList = element->y();
4251 const auto& dxList = element->dx();
4252 const auto& dyList = element->dy();
4253 const auto& rotateList = element->rotate();
4254
4255 auto xListSize = xList.size();
4256 auto yListSize = yList.size();
4257 auto dxListSize = dxList.size();
4258 auto dyListSize = dyList.size();
4259 auto rotateListSize = rotateList.size();
4260 if(!xListSize && !yListSize && !dxListSize && !dyListSize && !rotateListSize) {
4261 return;
4262 }
4263
4264 LengthContext lengthContext(element);
4265 std::optional<float> lastRotation;
4266 for(auto offset = position.startOffset; offset < position.endOffset; ++offset) {
4267 auto index = offset - position.startOffset;
4268 if(index >= xListSize && index >= yListSize && index >= dxListSize && index >= dyListSize && index >= rotateListSize)
4269 break;
4270 auto& characterPosition = m_characterPositions[offset];
4271 if(index < xListSize)
4272 characterPosition.x = lengthContext.valueForLength(xList[index], LengthDirection::Horizontal);
4273 if(index < yListSize)
4274 characterPosition.y = lengthContext.valueForLength(yList[index], LengthDirection::Vertical);
4275 if(index < dxListSize)
4276 characterPosition.dx = lengthContext.valueForLength(dxList[index], LengthDirection::Horizontal);
4277 if(index < dyListSize)
4278 characterPosition.dy = lengthContext.valueForLength(dyList[index], LengthDirection::Vertical);
4279 if(index < rotateListSize) {
4280 characterPosition.rotate = rotateList[index];
4281 lastRotation = characterPosition.rotate;
4282 }
4283 }
4284
4285 if(lastRotation == std::nullopt)
4286 return;
4287 auto offset = position.startOffset + rotateList.size();
4288 while(offset < position.endOffset) {
4289 m_characterPositions[offset++].rotate = lastRotation;
4290 }
4291}
4292
4311
4313{
4314 m_font = state.font();
4315 m_fill = getPaintServer(state.fill(), state.fill_opacity());
4316 m_stroke = getPaintServer(state.stroke(), state.stroke_opacity());
4318
4319 LengthContext lengthContext(this);
4320 m_stroke_width = lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal);
4321 m_letter_spacing = lengthContext.valueForLength(state.letter_spacing(), LengthDirection::Diagonal);
4322 m_word_spacing = lengthContext.valueForLength(state.word_spacing(), LengthDirection::Diagonal);
4323
4324 m_baseline_offset = convertBaselineOffset(state.baseline_shift());
4325 m_alignment_baseline = state.alignment_baseline();
4326 m_dominant_baseline = state.dominant_baseline();
4327 m_text_anchor = state.text_anchor();
4328 m_white_space = state.white_space();
4329 m_writing_mode = state.writing_mode();
4330 m_text_orientation = state.text_orientation();
4331 m_direction = state.direction();
4332}
4333
4334NOVASVG_INLINE float SVGTextPositioningElement::convertBaselineOffset(const BaselineShift& baselineShift) const
4335{
4336 if(baselineShift.type() == BaselineShift::Type::Baseline)
4337 return 0.f;
4338 if(baselineShift.type() == BaselineShift::Type::Sub)
4339 return -m_font.height() / 2.f;
4340 if(baselineShift.type() == BaselineShift::Type::Super) {
4341 return m_font.height() / 2.f;
4342 }
4343
4344 const auto& length = baselineShift.length();
4345 if(length.units() == LengthUnits::Percent)
4346 return length.value() * m_font.size() / 100.f;
4347 if(length.units() == LengthUnits::Ex)
4348 return length.value() * m_font.size() / 2.f;
4349 if(length.units() == LengthUnits::Em)
4350 return length.value() * m_font.size();
4351 return length.value();
4352}
4353
4358
4363
4369
4371{
4372 if(m_fragments.empty() || isVisibilityHidden() || isDisplayNone())
4373 return;
4374 SVGBlendInfo blendInfo(this);
4375 SVGRenderState newState(this, state, localTransform());
4376 newState.beginGroup(blendInfo);
4377 if(newState.mode() == SVGRenderMode::Clipping) {
4378 newState->setColor(Color::White);
4379 }
4380
4381 std::u32string_view wholeText(m_text);
4382 for(const auto& fragment : m_fragments) {
4383 if(fragment.element->isVisibilityHidden())
4384 continue;
4385 auto transform = newState.currentTransform() * Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform;
4386 auto text = wholeText.substr(fragment.offset, fragment.length);
4387 auto origin = Point(fragment.x, fragment.y);
4388
4389 const auto& font = fragment.element->font();
4390 if(newState.mode() == SVGRenderMode::Clipping) {
4391 newState->fillText(text, font, origin, transform);
4392 } else {
4393 const auto& fill = fragment.element->fill();
4394 const auto& stroke = fragment.element->stroke();
4395 auto stroke_width = fragment.element->stroke_width();
4396 if(fill.applyPaint(newState))
4397 newState->fillText(text, font, origin, transform);
4398 if(stroke.applyPaint(newState)) {
4399 newState->strokeText(text, stroke_width, font, origin, transform);
4400 }
4401 }
4402 }
4403
4404 newState.endGroup(blendInfo);
4405}
4406
4407NOVASVG_INLINE Rect SVGTextElement::boundingBox(bool includeStroke) const
4408{
4409 auto boundingBox = Rect::Invalid;
4410 for(const auto& fragment : m_fragments) {
4411 const auto& font = fragment.element->font();
4412 const auto& stroke = fragment.element->stroke();
4413 auto fragmentTranform = Transform::rotated(fragment.angle, fragment.x, fragment.y) * fragment.lengthAdjustTransform;
4414 auto fragmentRect = Rect(fragment.x, fragment.y - font.ascent(), fragment.width, fragment.height);
4415 if(includeStroke && stroke.isRenderable())
4416 fragmentRect.inflate(fragment.element->stroke_width() / 2.f);
4417 boundingBox.unite(fragmentTranform.mapRect(fragmentRect));
4418 }
4419
4420 if(!boundingBox.isValid())
4421 boundingBox = Rect::Empty;
4422 return boundingBox;
4423}
4424
4425// ---- svglayoutstate (impl) ----
4426using namespace render; // render/ layer (novasvg::render), the former plutovg
4427
4428NOVASVG_INLINE std::optional<Color> parseColorValue(std::string_view& input, const SVGLayoutState* state)
4429{
4430 if(skipString(input, "currentColor")) {
4431 return state->color();
4432 }
4433
4434 color_t color;
4435 int length = color_parse(&color, input.data(), input.length());
4436 if(length == 0)
4437 return std::nullopt;
4438 input.remove_prefix(length);
4439 // color_to_argb32() packs as 0xAARRGGBB (alpha high byte) -- a
4440 // different layout than Color::value()'s 0xRRGGBBAA, so this goes
4441 // through components, never through the raw packed value.
4442 auto argb = color_to_argb32(&color);
4443 return Color((argb >> 16) & 0xff, (argb >> 8) & 0xff, argb & 0xff, (argb >> 24) & 0xff);
4444}
4445
4446NOVASVG_INLINE Color parseColor(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
4447{
4448 auto color = parseColorValue(input, state);
4449 if(!color || !input.empty())
4450 color = defaultValue;
4451 return color.value();
4452}
4453
4454NOVASVG_INLINE Color parseColorOrNone(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
4455{
4456 if(input.compare("none") == 0)
4457 return Color::Transparent;
4458 return parseColor(input, state, defaultValue);
4459}
4460
4461NOVASVG_INLINE bool parseUrlValue(std::string_view& input, std::string& value)
4462{
4463 if(!skipString(input, "url")
4464 || !skipOptionalSpaces(input)
4465 || !skipDelimiter(input, '(')
4466 || !skipOptionalSpaces(input)) {
4467 return false;
4468 }
4469
4470 switch(input.front()) {
4471 case '\'':
4472 case '\"': {
4473 auto delim = input.front();
4474 input.remove_prefix(1);
4475 skipOptionalSpaces(input);
4476 if(!skipDelimiter(input, '#'))
4477 return false;
4478 while(!input.empty() && input.front() != delim) {
4479 value += input.front();
4480 input.remove_prefix(1);
4481 }
4482
4483 skipOptionalSpaces(input);
4484 if(!skipDelimiter(input, delim))
4485 return false;
4486 break;
4487 } case '#': {
4488 input.remove_prefix(1);
4489 while(!input.empty() && input.front() != ')') {
4490 value += input.front();
4491 input.remove_prefix(1);
4492 }
4493
4494 break;
4495 } default:
4496 return false;
4497 }
4498
4499 return skipOptionalSpaces(input) && skipDelimiter(input, ')');
4500}
4501
4502NOVASVG_INLINE std::string parseUrl(std::string_view input)
4503{
4504 std::string value;
4505 if(!parseUrlValue(input, value) || !input.empty())
4506 value.clear();
4507 return value;
4508}
4509
4510NOVASVG_INLINE Paint parsePaint(std::string_view input, const SVGLayoutState* state, const Color& defaultValue)
4511{
4512 std::string id;
4513 if(!parseUrlValue(input, id))
4514 return Paint(parseColorOrNone(input, state, defaultValue));
4515 if(skipOptionalSpaces(input))
4516 return Paint(id, parseColorOrNone(input, state, defaultValue));
4517 return Paint(id, Color::Transparent);
4518}
4519
4520NOVASVG_INLINE float parseNumberOrPercentage(std::string_view input, bool allowPercentage, float defaultValue)
4521{
4522 float value;
4523 if(!parseNumber(input, value))
4524 return defaultValue;
4525 if(allowPercentage) {
4526 if(skipDelimiter(input, '%'))
4527 value /= 100.f;
4528 value = std::clamp(value, 0.f, 1.f);
4529 }
4530
4531 if(!input.empty())
4532 return defaultValue;
4533 return value;
4534}
4535
4536NOVASVG_INLINE Length parseLength(std::string_view input, LengthNegativeMode mode, const Length& defaultValue)
4537{
4538 Length value;
4539 if(!value.parse(input, mode))
4540 value = defaultValue;
4541 return value;
4542}
4543
4545{
4546 if(input.compare("baseline") == 0)
4548 if(input.compare("sub") == 0)
4550 if(input.compare("super") == 0)
4553}
4554
4556{
4557 if(input.compare("none") == 0)
4558 return LengthList();
4559 LengthList values;
4560 do {
4561 size_t count = 0;
4562 while(count < input.length() && input[count] != ',' && !IS_WS(input[count]))
4563 ++count;
4564 Length value(0, LengthUnits::None);
4565 if(!value.parse(input.substr(0, count), LengthNegativeMode::Forbid))
4566 return LengthList();
4567 input.remove_prefix(count);
4568 values.push_back(std::move(value));
4569 } while(skipOptionalSpacesOrComma(input));
4570 return values;
4571}
4572
4574{
4575 if(input.compare("normal") == 0)
4576 return Length(0, LengthUnits::None);
4578}
4579
4580NOVASVG_INLINE float parseFontSize(std::string_view input, const SVGLayoutState* state)
4581{
4583 if(length.units() == LengthUnits::Percent)
4584 return length.value() * state->font_size() / 100.f;
4585 if(length.units() == LengthUnits::Ex)
4586 return length.value() * state->font_size() / 2.f;
4587 if(length.units() == LengthUnits::Em)
4588 return length.value() * state->font_size();
4589 return length.value();
4590}
4591
4592template<typename Enum, unsigned int N>
4593NOVASVG_INLINE Enum parseEnumValue(std::string_view input, const SVGEnumerationEntry<Enum>(&entries)[N], Enum defaultValue)
4594{
4595 for(const auto& entry : entries) {
4596 if(input == entry.second) {
4597 return entry.first;
4598 }
4599 }
4600
4601 return defaultValue;
4602}
4603
4604NOVASVG_INLINE Display parseDisplay(std::string_view input)
4605{
4606 static const SVGEnumerationEntry<Display> entries[] = {
4607 {Display::Inline, "inline"},
4608 {Display::None, "none"}
4609 };
4610
4611 return parseEnumValue(input, entries, Display::Inline);
4612}
4613
4615{
4616 static const SVGEnumerationEntry<Visibility> entries[] = {
4617 {Visibility::Visible, "visible"},
4618 {Visibility::Hidden, "hidden"},
4619 {Visibility::Collapse, "collapse"}
4620 };
4621
4622 return parseEnumValue(input, entries, Visibility::Visible);
4623}
4624
4626{
4627 static const SVGEnumerationEntry<Overflow> entries[] = {
4628 {Overflow::Visible, "visible"},
4629 {Overflow::Hidden, "hidden"}
4630 };
4631
4632 return parseEnumValue(input, entries, Overflow::Visible);
4633}
4634
4636{
4637 static const SVGEnumerationEntry<PointerEvents> entries[] = {
4638 {PointerEvents::None,"none"},
4639 {PointerEvents::Auto,"auto"},
4640 {PointerEvents::Stroke,"stroke"},
4641 {PointerEvents::Fill,"fill"},
4642 {PointerEvents::Painted,"painted"},
4643 {PointerEvents::Visible,"visible"},
4644 {PointerEvents::VisibleStroke,"visibleStroke"},
4645 {PointerEvents::VisibleFill,"visibleFill"},
4646 {PointerEvents::VisiblePainted,"visiblePainted"},
4647 {PointerEvents::BoundingBox,"bounding-box"},
4648 {PointerEvents::All,"all"},
4649 };
4650
4651 return parseEnumValue(input, entries, PointerEvents::Auto);
4652}
4653
4655{
4656 static const SVGEnumerationEntry<FontWeight> entries[] = {
4657 {FontWeight::Normal, "normal"},
4658 {FontWeight::Bold, "bold"},
4659 {FontWeight::Bold, "bolder"},
4660 {FontWeight::Normal, "lighter"},
4661 {FontWeight::Normal, "100"},
4662 {FontWeight::Normal, "200"},
4663 {FontWeight::Normal, "300"},
4664 {FontWeight::Normal, "400"},
4665 {FontWeight::Normal, "500"},
4666 {FontWeight::Bold, "600"},
4667 {FontWeight::Bold, "700"},
4668 {FontWeight::Bold, "800"},
4669 {FontWeight::Bold, "900"}
4670 };
4671
4672 return parseEnumValue(input, entries, FontWeight::Normal);
4673}
4674
4676{
4677 static const SVGEnumerationEntry<FontStyle> entries[] = {
4678 {FontStyle::Normal, "normal"},
4679 {FontStyle::Italic, "italic"},
4680 {FontStyle::Italic, "oblique"}
4681 };
4682
4683 return parseEnumValue(input, entries, FontStyle::Normal);
4684}
4685
4687{
4688 static const SVGEnumerationEntry<AlignmentBaseline> entries[] = {
4689 {AlignmentBaseline::Auto, "auto"},
4690 {AlignmentBaseline::Baseline, "baseline"},
4691 {AlignmentBaseline::BeforeEdge, "before-edge"},
4692 {AlignmentBaseline::TextBeforeEdge, "text-before-edge"},
4693 {AlignmentBaseline::Middle, "middle"},
4694 {AlignmentBaseline::Central, "central"},
4695 {AlignmentBaseline::AfterEdge, "after-edge"},
4696 {AlignmentBaseline::TextAfterEdge, "text-after-edge"},
4697 {AlignmentBaseline::Ideographic, "ideographic"},
4698 {AlignmentBaseline::Alphabetic, "alphabetic"},
4699 {AlignmentBaseline::Hanging, "hanging"},
4700 {AlignmentBaseline::Mathematical, "mathematical"}
4701 };
4702
4703 return parseEnumValue(input, entries, AlignmentBaseline::Auto);
4704}
4705
4707{
4708 static const SVGEnumerationEntry<DominantBaseline> entries[] = {
4709 {DominantBaseline::Auto, "auto"},
4710 {DominantBaseline::UseScript, "use-script"},
4711 {DominantBaseline::NoChange, "no-change"},
4712 {DominantBaseline::ResetSize, "reset-size"},
4713 {DominantBaseline::Ideographic, "ideographic"},
4714 {DominantBaseline::Alphabetic, "alphabetic"},
4715 {DominantBaseline::Hanging, "hanging"},
4716 {DominantBaseline::Mathematical, "mathematical"},
4717 {DominantBaseline::Central, "central"},
4718 {DominantBaseline::Middle, "middle"},
4719 {DominantBaseline::TextAfterEdge, "text-after-edge"},
4720 {DominantBaseline::TextBeforeEdge, "text-before-edge"}
4721 };
4722
4723 return parseEnumValue(input, entries, DominantBaseline::Auto);
4724}
4725
4727{
4728 static const SVGEnumerationEntry<Direction> entries[] = {
4729 {Direction::Ltr, "ltr"},
4730 {Direction::Rtl, "rtl"}
4731 };
4732
4733 return parseEnumValue(input, entries, Direction::Ltr);
4734}
4735
4737{
4738 static const SVGEnumerationEntry<WritingMode> entries[] = {
4739 {WritingMode::Horizontal, "horizontal-tb"},
4740 {WritingMode::Vertical, "vertical-rl"},
4741 {WritingMode::Vertical, "vertical-lr"},
4742 {WritingMode::Horizontal, "lr-tb"},
4744 {WritingMode::Horizontal, "rl-tb"},
4746 {WritingMode::Vertical, "tb-rl"},
4747 {WritingMode::Vertical, "tb"}
4748 };
4749
4750 return parseEnumValue(input, entries, WritingMode::Horizontal);
4751}
4752
4754{
4755 static const SVGEnumerationEntry<TextOrientation> entries[] = {
4756 {TextOrientation::Mixed, "mixed"},
4757 {TextOrientation::Upright, "upright"}
4758 };
4759
4760 return parseEnumValue(input, entries, TextOrientation::Mixed);
4761}
4762
4764{
4765 static const SVGEnumerationEntry<TextAnchor> entries[] = {
4766 {TextAnchor::Start, "start"},
4767 {TextAnchor::Middle, "middle"},
4768 {TextAnchor::End, "end"}
4769 };
4770
4771 return parseEnumValue(input, entries, TextAnchor::Start);
4772}
4773
4775{
4776 static const SVGEnumerationEntry<WhiteSpace> entries[] = {
4777 {WhiteSpace::Default, "default"},
4778 {WhiteSpace::Preserve, "preserve"},
4779 {WhiteSpace::Default, "normal"},
4780 {WhiteSpace::Default, "nowrap"},
4781 {WhiteSpace::Default, "pre-line"},
4782 {WhiteSpace::Preserve, "pre-wrap"},
4783 {WhiteSpace::Preserve, "pre"}
4784 };
4785
4786 return parseEnumValue(input, entries, WhiteSpace::Default);
4787}
4788
4790{
4791 static const SVGEnumerationEntry<MixBlendMode> entries[] = {
4792 {MixBlendMode::Normal, "normal"},
4793 {MixBlendMode::Multiply, "multiply"},
4794 {MixBlendMode::Screen, "screen"},
4795 {MixBlendMode::Darken, "darken"},
4796 {MixBlendMode::Lighten, "lighten"},
4797 {MixBlendMode::Difference, "difference"},
4798 {MixBlendMode::Exclusion, "exclusion"}
4799 };
4800
4801 // CSS keywords are ASCII case-insensitive
4802 std::string keyword;
4803 for(char ch : input)
4804 keyword.push_back(char(std::tolower(static_cast<unsigned char>(ch))));
4805 return parseEnumValue(keyword, entries, MixBlendMode::Normal);
4806}
4807
4809{
4810 static const SVGEnumerationEntry<MaskType> entries[] = {
4811 {MaskType::Luminance, "luminance"},
4812 {MaskType::Alpha, "alpha"}
4813 };
4814
4815 return parseEnumValue(input, entries, MaskType::Luminance);
4816}
4817
4819{
4820 static const SVGEnumerationEntry<FillRule> entries[] = {
4821 {FillRule::NonZero, "nonzero"},
4822 {FillRule::EvenOdd, "evenodd"}
4823 };
4824
4825 return parseEnumValue(input, entries, FillRule::NonZero);
4826}
4827
4828NOVASVG_INLINE LineCap parseLineCap(std::string_view input)
4829{
4830 static const SVGEnumerationEntry<LineCap> entries[] = {
4831 {LineCap::Butt, "butt"},
4832 {LineCap::Round, "round"},
4833 {LineCap::Square, "square"}
4834 };
4835
4836 return parseEnumValue(input, entries, LineCap::Butt);
4837}
4838
4840{
4841 static const SVGEnumerationEntry<LineJoin> entries[] = {
4842 {LineJoin::Miter, "miter"},
4843 {LineJoin::Round, "round"},
4844 {LineJoin::Bevel, "bevel"}
4845 };
4846
4847 return parseEnumValue(input, entries, LineJoin::Miter);
4848}
4849
4851 : m_parent(&parent)
4852 , m_element(element)
4853 , m_fill(parent.fill())
4854 , m_stroke(parent.stroke())
4855 , m_color(parent.color())
4856 , m_fill_opacity(parent.fill_opacity())
4857 , m_stroke_opacity(parent.stroke_opacity())
4858 , m_stroke_miterlimit(parent.stroke_miterlimit())
4859 , m_font_size(parent.font_size())
4860 , m_letter_spacing(parent.letter_spacing())
4861 , m_word_spacing(parent.word_spacing())
4862 , m_stroke_width(parent.stroke_width())
4863 , m_stroke_dashoffset(parent.stroke_dashoffset())
4864 , m_stroke_dasharray(parent.stroke_dasharray())
4865 , m_stroke_linecap(parent.stroke_linecap())
4866 , m_stroke_linejoin(parent.stroke_linejoin())
4867 , m_fill_rule(parent.fill_rule())
4868 , m_clip_rule(parent.clip_rule())
4869 , m_font_weight(parent.font_weight())
4870 , m_font_style(parent.font_style())
4871 , m_dominant_baseline(parent.dominant_baseline())
4872 , m_text_anchor(parent.text_anchor())
4873 , m_white_space(parent.white_space())
4874 , m_writing_mode(parent.writing_mode())
4875 , m_text_orientation(parent.text_orientation())
4876 , m_direction(parent.direction())
4877 , m_visibility(parent.visibility())
4878 , m_overflow(element->isRootElement() ? Overflow::Visible : Overflow::Hidden)
4879 , m_pointer_events(parent.pointer_events())
4880 , m_marker_start(parent.marker_start())
4881 , m_marker_mid(parent.marker_mid())
4882 , m_marker_end(parent.marker_end())
4883 , m_font_family(parent.font_family())
4884{
4885 for(const auto& attribute : element->attributes()) {
4886 std::string_view input(attribute.value());
4888 if(input.empty() || input.compare("inherit") == 0)
4889 continue;
4890 switch(attribute.id()) {
4891 case PropertyID::Fill:
4892 m_fill = parsePaint(input, this, Color::Black);
4893 break;
4894 case PropertyID::Stroke:
4895 m_stroke = parsePaint(input, this, Color::Transparent);
4896 break;
4897 case PropertyID::Color:
4898 m_color = parseColor(input, this, Color::Black);
4899 break;
4901 m_stop_color = parseColor(input, this, Color::Black);
4902 break;
4904 m_opacity = parseNumberOrPercentage(input, true, 1.f);
4905 break;
4907 m_mix_blend_mode = parseMixBlendMode(input);
4908 break;
4910 m_fill_opacity = parseNumberOrPercentage(input, true, 1.f);
4911 break;
4913 m_stroke_opacity = parseNumberOrPercentage(input, true, 1.f);
4914 break;
4916 m_stop_opacity = parseNumberOrPercentage(input, true, 1.f);
4917 break;
4919 m_stroke_miterlimit = parseNumberOrPercentage(input, false, 4.f);
4920 break;
4922 m_font_size = parseFontSize(input, this);
4923 break;
4925 m_letter_spacing = parseLengthOrNormal(input);
4926 break;
4928 m_word_spacing = parseLengthOrNormal(input);
4929 break;
4931 m_baseline_shift = parseBaselineShift(input);
4932 break;
4934 m_stroke_width = parseLength(input, LengthNegativeMode::Forbid, Length(1.f, LengthUnits::None));
4935 break;
4937 m_stroke_dashoffset = parseLength(input, LengthNegativeMode::Allow, Length(0.f, LengthUnits::None));
4938 break;
4940 m_stroke_dasharray = parseDashArray(input);
4941 break;
4943 m_stroke_linecap = parseLineCap(input);
4944 break;
4946 m_stroke_linejoin = parseLineJoin(input);
4947 break;
4949 m_fill_rule = parseFillRule(input);
4950 break;
4952 m_clip_rule = parseFillRule(input);
4953 break;
4955 m_font_weight = parseFontWeight(input);
4956 break;
4958 m_font_style = parseFontStyle(input);
4959 break;
4961 m_alignment_baseline = parseAlignmentBaseline(input);
4962 break;
4964 m_dominant_baseline = parseDominantBaseline(input);
4965 break;
4967 m_direction = parseDirection(input);
4968 break;
4970 m_text_anchor = parseTextAnchor(input);
4971 break;
4973 m_white_space = parseWhiteSpace(input);
4974 break;
4976 m_writing_mode = parseWritingMode(input);
4977 break;
4979 m_text_orientation = parseTextOrientation(input);
4980 break;
4982 m_display = parseDisplay(input);
4983 break;
4985 m_visibility = parseVisibility(input);
4986 break;
4988 m_overflow = parseOverflow(input);
4989 break;
4991 m_pointer_events = parsePointerEvents(input);
4992 break;
4994 m_mask_type = parseMaskType(input);
4995 break;
4996 case PropertyID::Mask:
4997 m_mask = parseUrl(input);
4998 break;
5000 m_clip_path = parseUrl(input);
5001 break;
5002 case PropertyID::Filter:
5003 m_filter = parseUrl(input);
5004 break;
5006 m_marker_start = parseUrl(input);
5007 break;
5009 m_marker_mid = parseUrl(input);
5010 break;
5012 m_marker_end = parseUrl(input);
5013 break;
5015 m_font_family.assign(input);
5016 break;
5017 default:
5018 break;
5019 }
5020 }
5021}
5022
5023// The one place a (font-family stack, weight, style) triple becomes a Font, shared by SVG <text>
5024// (SVGLayoutState::font()) and by the bold/italic runs inside a <foreignObject>, so both always
5025// pick the very same face for the same request.
5026NOVASVG_INLINE Font makeFont(const std::string& family, float size, bool bold, bool italic)
5027{
5028 // Walk the font-family stack name by name (see
5029 // FontFaceCache::getFontFaceForFamilyStack()): an @font-face-embedded
5030 // font wins under its exact declared name, an uninstalled name falls
5031 // back to a metric-compatible substitute, unresolvable names are skipped.
5032 FontFace face = fontFaceCache()->getFontFaceForFamilyStack(family, bold, italic);
5033
5034 // Absolute last resort: the hardcoded generic-family table.
5035 if(face.isNull())
5036 face = fontFaceCache()->getFontFace(emptyString, bold, italic);
5037 return Font(face, size);
5038}
5039
5041{
5042 return makeFont(m_font_family, m_font_size, m_font_weight == FontWeight::Bold, m_font_style == FontStyle::Italic);
5043}
5044
5045// ---- svgelement (impl) ----
5046using namespace render; // render/ layer (novasvg::render), the former plutovg
5047
5048NOVASVG_INLINE ElementID elementid(std::string_view name)
5049{
5050 static const struct {
5051 std::string_view name;
5052 ElementID value;
5053 } table[] = {
5054 {"a", ElementID::G},
5055 {"circle", ElementID::Circle},
5056 {"clipPath", ElementID::ClipPath},
5057 {"defs", ElementID::Defs},
5058 {"ellipse", ElementID::Ellipse},
5059 {"feComposite", ElementID::FeComposite},
5060 {"feDropShadow", ElementID::FeDropShadow},
5061 {"feFlood", ElementID::FeFlood},
5062 {"feGaussianBlur", ElementID::FeGaussianBlur},
5063 {"feMerge", ElementID::FeMerge},
5064 {"feMergeNode", ElementID::FeMergeNode},
5065 {"feOffset", ElementID::FeOffset},
5066 {"filter", ElementID::Filter},
5067 {"foreignObject", ElementID::ForeignObject},
5068 {"g", ElementID::G},
5069 {"image", ElementID::Image},
5070 {"line", ElementID::Line},
5071 {"linearGradient", ElementID::LinearGradient},
5072 {"marker", ElementID::Marker},
5073 {"mask", ElementID::Mask},
5074 {"path", ElementID::Path},
5075 {"pattern", ElementID::Pattern},
5076 {"polygon", ElementID::Polygon},
5077 {"polyline", ElementID::Polyline},
5078 {"radialGradient", ElementID::RadialGradient},
5079 {"rect", ElementID::Rect},
5080 {"stop", ElementID::Stop},
5081 {"style", ElementID::Style},
5082 {"svg", ElementID::Svg},
5083 {"switch", ElementID::Switch},
5084 {"symbol", ElementID::Symbol},
5085 {"text", ElementID::Text},
5086 {"tspan", ElementID::Tspan},
5087 {"use", ElementID::Use}
5088 };
5089
5090 auto it = std::lower_bound(table, std::end(table), name, [](const auto& item, const auto& name) { return item.name < name; });
5091 if(it == std::end(table) || it->name != name)
5092 return ElementID::Unknown;
5093 return it->value;
5094}
5095
5097 : SVGNode(document)
5098{
5099}
5100
5101NOVASVG_INLINE void SVGTextNode::setData(const std::string& data)
5102{
5104 m_data.assign(data);
5105}
5106
5107NOVASVG_INLINE std::unique_ptr<SVGNode> SVGTextNode::clone(bool deep) const
5108{
5109 auto node = std::make_unique<SVGTextNode>(document());
5110 node->setData(m_data);
5111 return node;
5112}
5113
5114NOVASVG_INLINE const std::string emptyString;
5115
5116NOVASVG_INLINE std::unique_ptr<SVGElement> SVGElement::create(Document* document, ElementID id)
5117{
5118 switch(id) {
5119 case ElementID::Svg:
5120 return std::make_unique<SVGSVGElement>(document);
5121 case ElementID::Path:
5122 return std::make_unique<SVGPathElement>(document);
5123 case ElementID::G:
5124 return std::make_unique<SVGGElement>(document);
5125 case ElementID::Switch:
5126 return std::make_unique<SVGSwitchElement>(document);
5127 case ElementID::Rect:
5128 return std::make_unique<SVGRectElement>(document);
5129 case ElementID::Circle:
5130 return std::make_unique<SVGCircleElement>(document);
5131 case ElementID::Ellipse:
5132 return std::make_unique<SVGEllipseElement>(document);
5133 case ElementID::Line:
5134 return std::make_unique<SVGLineElement>(document);
5135 case ElementID::Defs:
5136 return std::make_unique<SVGDefsElement>(document);
5137 case ElementID::Polygon:
5139 return std::make_unique<SVGPolyElement>(document, id);
5140 case ElementID::Stop:
5141 return std::make_unique<SVGStopElement>(document);
5143 return std::make_unique<SVGLinearGradientElement>(document);
5145 return std::make_unique<SVGRadialGradientElement>(document);
5146 case ElementID::Symbol:
5147 return std::make_unique<SVGSymbolElement>(document);
5148 case ElementID::Use:
5149 return std::make_unique<SVGUseElement>(document);
5150 case ElementID::Pattern:
5151 return std::make_unique<SVGPatternElement>(document);
5152 case ElementID::Mask:
5153 return std::make_unique<SVGMaskElement>(document);
5155 return std::make_unique<SVGClipPathElement>(document);
5156 case ElementID::Filter:
5157 return std::make_unique<SVGFilterElement>(document);
5159 return std::make_unique<SVGFEGaussianBlurElement>(document);
5161 return std::make_unique<SVGFEDropShadowElement>(document);
5163 return std::make_unique<SVGFEOffsetElement>(document);
5164 case ElementID::FeFlood:
5165 return std::make_unique<SVGFEFloodElement>(document);
5167 return std::make_unique<SVGFECompositeElement>(document);
5168 case ElementID::FeMerge:
5169 return std::make_unique<SVGFEMergeElement>(document);
5171 return std::make_unique<SVGElement>(document, id);
5172 case ElementID::Marker:
5173 return std::make_unique<SVGMarkerElement>(document);
5174 case ElementID::Image:
5175 return std::make_unique<SVGImageElement>(document);
5177 return std::make_unique<SVGForeignObjectElement>(document);
5178 case ElementID::Style:
5179 return std::make_unique<SVGStyleElement>(document);
5180 case ElementID::Text:
5181 return std::make_unique<SVGTextElement>(document);
5182 case ElementID::Tspan:
5183 return std::make_unique<SVGTSpanElement>(document);
5184 default:
5185 assert(false);
5186 }
5187
5188 return nullptr;
5189}
5190
5192 : SVGNode(document)
5193 , m_id(id)
5194{
5195}
5196
5197NOVASVG_INLINE bool SVGElement::hasAttribute(std::string_view name) const
5198{
5199 auto id = propertyid(name);
5200 if(id == PropertyID::Unknown)
5201 return false;
5202 return hasAttribute(id);
5203}
5204
5205NOVASVG_INLINE const std::string& SVGElement::getAttribute(std::string_view name) const
5206{
5207 auto id = propertyid(name);
5208 if(id == PropertyID::Unknown)
5209 return emptyString;
5210 return getAttribute(id);
5211}
5212
5213NOVASVG_INLINE bool SVGElement::setAttribute(std::string_view name, const std::string& value)
5214{
5215 auto id = propertyid(name);
5216 if(id == PropertyID::Unknown)
5217 return false;
5218 return setAttribute(Specificity::Api, id, value);
5219}
5220
5222{
5223 for(const auto& attribute : m_attributes) {
5224 if(id == attribute.id()) {
5225 return &attribute;
5226 }
5227 }
5228
5229 return nullptr;
5230}
5231
5233{
5234 for(const auto& attribute : m_attributes) {
5235 if(id == attribute.id()) {
5236 return true;
5237 }
5238 }
5239
5240 return false;
5241}
5242
5244{
5245 for(const auto& attribute : m_attributes) {
5246 if(id == attribute.id()) {
5247 return attribute.value();
5248 }
5249 }
5250
5251 return emptyString;
5252}
5253
5254NOVASVG_INLINE bool SVGElement::setAttribute(int specificity, PropertyID id, const std::string& value)
5255{
5256 for(auto& attribute : m_attributes) {
5257 if(id == attribute.id()) {
5258 if(specificity < attribute.specificity())
5259 return false;
5260 parseAttribute(id, value);
5261 attribute = Attribute(specificity, id, value);
5262 return true;
5263 }
5264 }
5265
5266 parseAttribute(id, value);
5267 m_attributes.emplace_front(specificity, id, value);
5268 return true;
5269}
5270
5272{
5273 for(const auto& attribute : attributes) {
5274 setAttribute(attribute);
5275 }
5276}
5277
5279{
5280 return setAttribute(attribute.specificity(), attribute.id(), attribute.value());
5281}
5282
5283NOVASVG_INLINE void SVGElement::parseAttribute(PropertyID id, const std::string& value)
5284{
5286 if(auto property = getProperty(id)) {
5287 property->parse(value);
5288 }
5289}
5290
5292{
5293 auto parent = parentElement();
5294 if(parent == nullptr)
5295 return nullptr;
5296 const auto& children = parent->children();
5297 auto it = children.begin();
5298 auto end = children.end();
5299 SVGElement* element = nullptr;
5300 for(; it != end; ++it) {
5301 SVGNode* node = &**it;
5302 if(node->isTextNode())
5303 continue;
5304 if(node == this)
5305 return element;
5306 element = static_cast<SVGElement*>(node);
5307 }
5308
5309 return nullptr;
5310}
5311
5313{
5314 auto parent = parentElement();
5315 if(parent == nullptr)
5316 return nullptr;
5317 const auto& children = parent->children();
5318 auto it = children.rbegin();
5319 auto end = children.rend();
5320 SVGElement* element = nullptr;
5321 for(; it != end; ++it) {
5322 SVGNode* node = &**it;
5323 if(node->isTextNode())
5324 continue;
5325 if(node == this)
5326 return element;
5327 element = static_cast<SVGElement*>(node);
5328 }
5329
5330 return nullptr;
5331}
5332
5333NOVASVG_INLINE SVGNode* SVGElement::addChild(std::unique_ptr<SVGNode> child)
5334{
5335 child->setParentElement(this);
5336 m_children.push_back(std::move(child));
5337 return &*m_children.back();
5338}
5339
5341{
5342 if(m_children.empty())
5343 return nullptr;
5344 return &*m_children.front();
5345}
5346
5348{
5349 if(m_children.empty())
5350 return nullptr;
5351 return &*m_children.back();
5352}
5353
5355{
5357 for(const auto& child : m_children) {
5358 if(auto element = toSVGElement(child); element && !element->isHiddenElement()) {
5359 fillBoundingBox.unite(element->localTransform().mapRect(element->fillBoundingBox()));
5360 }
5361 }
5362
5365 return fillBoundingBox;
5366}
5367
5369{
5371 for(const auto& child : m_children) {
5372 if(auto element = toSVGElement(child); element && !element->isHiddenElement()) {
5373 strokeBoundingBox.unite(element->localTransform().mapRect(element->strokeBoundingBox()));
5374 }
5375 }
5376
5379 return strokeBoundingBox;
5380}
5381
5383{
5384 if(m_paintBoundingBox.isValid())
5385 return m_paintBoundingBox;
5386 m_paintBoundingBox = Rect::Empty;
5387 m_paintBoundingBox = strokeBoundingBox();
5388 assert(m_paintBoundingBox.isValid());
5389 if(m_clipper) m_paintBoundingBox.intersect(m_clipper->clipBoundingBox(this));
5390 if(m_masker) m_paintBoundingBox.intersect(m_masker->maskBoundingBox(this));
5391 return m_paintBoundingBox;
5392}
5393
5395{
5396 auto element = rootElement()->getElementById(id);
5397 if(element && element->id() == ElementID::Marker)
5398 return static_cast<SVGMarkerElement*>(element);
5399 return nullptr;
5400}
5401
5403{
5404 auto element = rootElement()->getElementById(id);
5405 if(element && element->id() == ElementID::ClipPath)
5406 return static_cast<SVGClipPathElement*>(element);
5407 return nullptr;
5408}
5409
5411{
5412 auto element = rootElement()->getElementById(id);
5413 if(element && element->id() == ElementID::Mask)
5414 return static_cast<SVGMaskElement*>(element);
5415 return nullptr;
5416}
5417
5419{
5420 auto element = rootElement()->getElementById(id);
5421 if(element && element->id() == ElementID::Filter)
5422 return static_cast<SVGFilterElement*>(element);
5423 return nullptr;
5424}
5425
5427{
5428 auto element = rootElement()->getElementById(id);
5429 if(element && element->isPaintElement())
5430 return static_cast<SVGPaintElement*>(element);
5431 return nullptr;
5432}
5433
5435{
5436 auto it = m_children.rbegin();
5437 auto end = m_children.rend();
5438 for(; it != end; ++it) {
5439 auto child = toSVGElement(*it);
5440 if(child && !child->isHiddenElement()) {
5441 if(auto element = child->elementFromPoint(x, y)) {
5442 return element;
5443 }
5444 }
5445 }
5446
5447 if(isPointableElement()) {
5448 auto transform = localTransform();
5449 for(auto parent = parentElement(); parent; parent = parent->parentElement())
5450 transform.postMultiply(parent->localTransform());
5451 auto bbox = transform.mapRect(paintBoundingBox());
5452 if(bbox.contains(x, y)) {
5453 return this;
5454 }
5455 }
5456
5457 return nullptr;
5458}
5459
5461{
5462 m_properties.push_front(&value);
5463}
5464
5466{
5467 for(auto property : m_properties) {
5468 if(id == property->id()) {
5469 return property;
5470 }
5471 }
5472
5473 return nullptr;
5474}
5475
5477{
5478 auto parent = parentElement();
5479 if(parent == nullptr) {
5480 auto element = static_cast<const SVGSVGElement*>(this);
5481 const auto& viewBox = element->viewBox();
5482 if(viewBox.value().isValid())
5483 return viewBox.value().size();
5484 return Size(300, 150);
5485 }
5486
5487 if(parent->id() == ElementID::Svg) {
5488 auto element = static_cast<const SVGSVGElement*>(parent);
5489 const auto& viewBox = element->viewBox();
5490 if(viewBox.value().isValid())
5491 return viewBox.value().size();
5492 LengthContext lengthContext(element);
5493 auto width = lengthContext.valueForLength(element->width());
5494 auto height = lengthContext.valueForLength(element->height());
5495 return Size(width, height);
5496 }
5497
5498 return parent->currentViewportSize();
5499}
5500
5502{
5503 for(const auto& child : m_children) {
5504 parentElement->addChild(child->clone(true));
5505 }
5506}
5507
5508NOVASVG_INLINE std::unique_ptr<SVGNode> SVGElement::clone(bool deep) const
5509{
5510 auto element = SVGElement::create(document(), m_id);
5511 element->setAttributes(m_attributes);
5512 if(deep) { cloneChildren(element.get()); }
5513 return element;
5514}
5515
5517{
5518 for(const auto& child : m_children) {
5519 if(auto element = toSVGElement(child)) {
5520 element->build();
5521 }
5522 }
5523}
5524
5526{
5527 m_paintBoundingBox = Rect::Invalid;
5528 m_clipper = getClipper(state.clip_path());
5529 m_masker = getMasker(state.mask());
5530 m_filter = getFilterElement(state.filter());
5531 m_opacity = state.opacity();
5532 m_mix_blend_mode = state.mix_blend_mode();
5533
5534 m_font_size = state.font_size();
5535 m_display = state.display();
5536 m_overflow = state.overflow();
5537 m_visibility = state.visibility();
5538 m_pointer_events = state.pointer_events();
5539}
5540
5542{
5543 for(const auto& child : m_children) {
5544 if(auto element = toSVGElement(child)) {
5545 element->layout(state);
5546 }
5547 }
5548}
5549
5551{
5552 SVGLayoutState newState(state, this);
5553 layoutElement(newState);
5554 layoutChildren(newState);
5555}
5556
5558{
5559 for(const auto& child : m_children) {
5560 if(auto element = toSVGElement(child)) {
5561 element->render(state);
5562 }
5563 }
5564}
5565
5569
5571{
5572 if(isDisplayNone())
5573 return true;
5574 switch(m_id) {
5575 case ElementID::Defs:
5576 case ElementID::Symbol:
5577 case ElementID::Marker:
5579 case ElementID::Mask:
5580 case ElementID::Filter:
5584 case ElementID::FeFlood:
5586 case ElementID::FeMerge:
5590 case ElementID::Pattern:
5591 case ElementID::Stop:
5592 return true;
5593 default:
5594 return false;
5595 }
5596}
5597
5599{
5600 if(m_pointer_events != PointerEvents::None
5601 && m_visibility != Visibility::Hidden
5602 && m_display != Display::None
5603 && m_opacity != 0.f) {
5604 switch(m_id) {
5605 case ElementID::Line:
5606 case ElementID::Rect:
5607 case ElementID::Ellipse:
5608 case ElementID::Circle:
5610 case ElementID::Polygon:
5611 case ElementID::Path:
5612 case ElementID::Text:
5613 case ElementID::Image:
5615 return true;
5616 default:
5617 break;
5618 }
5619 }
5620
5621 return false;
5622}
5623
5628
5630 : m_viewBox(PropertyID::ViewBox)
5631 , m_preserveAspectRatio(PropertyID::PreserveAspectRatio)
5632{
5633 element->addProperty(m_viewBox);
5634 element->addProperty(m_preserveAspectRatio);
5635}
5636
5638{
5639 const auto& viewBoxRect = m_viewBox.value();
5640 if(viewBoxRect.isEmpty() || viewportSize.isEmpty())
5641 return Transform::Identity;
5642 return m_preserveAspectRatio.getTransform(viewBoxRect, viewportSize);
5643}
5644
5646{
5647 const auto& viewBoxRect = m_viewBox.value();
5648 if(viewBoxRect.isEmpty() || viewportSize.isEmpty())
5649 return Rect(0, 0, viewportSize.w, viewportSize.h);
5650 return m_preserveAspectRatio.getClipRect(viewBoxRect, viewportSize);
5651}
5652
5654 : m_href(PropertyID::Href)
5655{
5656 element->addProperty(m_href);
5657}
5658
5660{
5661 std::string_view value(m_href.value());
5662 if(value.empty() || value.front() != '#')
5663 return nullptr;
5664 return document->rootElement()->getElementById(value.substr(1));
5665}
5666
5668{
5669 if(!isRenderable())
5670 return false;
5671 if(m_element) return m_element->applyPaint(state, m_opacity);
5672 state->setColor(m_color.colorWithAlpha(m_opacity));
5673 return true;
5674}
5675
5677 : SVGElement(document, id)
5678 , m_transform(PropertyID::Transform)
5679 , m_transformOrigin(PropertyID::Transform_Origin)
5680 , m_transformBox(PropertyID::Transform_Box)
5681{
5682 addProperty(m_transform);
5683 addProperty(m_transformOrigin);
5684 addProperty(m_transformBox);
5685}
5686
5688{
5689 // Only shift the pivot when transform-origin was actually specified --
5690 // matches legacy behavior (origin at the current user-space (0,0)) for
5691 // every element that doesn't use it, i.e. everything except elements
5692 // that explicitly opt in the way feature-css-use-symbol.svg's `.rot`
5693 // class does.
5694 if(!m_transformOrigin.specified())
5695 return m_transform.value();
5696
5697 Rect box;
5698 if(m_transformBox.specified() && m_transformBox.value() == TransformBoxType::FillBox) {
5699 box = fillBoundingBox();
5700 } else {
5701 auto size = currentViewportSize();
5702 box = Rect(0.f, 0.f, size.w, size.h);
5703 }
5704
5705 if(box.isEmpty())
5706 return m_transform.value();
5707
5708 auto origin = m_transformOrigin.resolve(box);
5709 return Transform::translated(origin.x, origin.y) * m_transform.value() * Transform::translated(-origin.x, -origin.y);
5710}
5711
5713{
5714 if(paint.isNone())
5715 return SVGPaintServer();
5716 if(auto element = getPainter(paint.id()))
5717 return SVGPaintServer(element, paint.color(), opacity);
5718 return SVGPaintServer(nullptr, paint.color(), opacity);
5719}
5720
5722{
5723 LengthContext lengthContext(this);
5724 StrokeData strokeData(lengthContext.valueForLength(state.stroke_width(), LengthDirection::Diagonal));
5725 strokeData.setMiterLimit(state.stroke_miterlimit());
5726 strokeData.setLineCap(state.stroke_linecap());
5727 strokeData.setLineJoin(state.stroke_linejoin());
5728 strokeData.setDashOffset(lengthContext.valueForLength(state.stroke_dashoffset(), LengthDirection::Diagonal));
5729
5730 DashArray dashArray;
5731 for(const auto& dash : state.stroke_dasharray())
5732 dashArray.push_back(lengthContext.valueForLength(dash, LengthDirection::Diagonal));
5733 strokeData.setDashArray(std::move(dashArray));
5734 return strokeData;
5735}
5736
5750
5752{
5754 const Rect viewportRect = {
5755 lengthContext.valueForLength(m_x),
5756 lengthContext.valueForLength(m_y),
5757 lengthContext.valueForLength(m_width),
5758 lengthContext.valueForLength(m_height)
5759 };
5760
5761 if(isRootElement())
5762 return viewBoxToViewTransform(viewportRect.size());
5764}
5765
5767{
5768 if(isDisplayNone())
5769 return;
5771 const Size viewportSize = {
5772 lengthContext.valueForLength(m_width),
5773 lengthContext.valueForLength(m_height)
5774 };
5775
5776 if(viewportSize.isEmpty())
5777 return;
5778 SVGBlendInfo blendInfo(this);
5780 newState.beginGroup(blendInfo);
5781 if(isOverflowHidden())
5782 newState->clipRect(getClipRect(viewportSize), FillRule::NonZero, newState.currentTransform());
5784 newState.endGroup(blendInfo);
5785}
5786
5791
5793{
5794 if(needsLayout())
5795 forceLayout();
5796 return this;
5797}
5798
5800{
5801 auto it = m_idCache.find(id);
5802 if(it == m_idCache.end())
5803 return nullptr;
5804 return it->second;
5805}
5806
5807NOVASVG_INLINE void SVGRootElement::addElementById(const std::string& id, SVGElement* element)
5808{
5809 m_idCache.emplace(id, element);
5810}
5811
5813{
5815
5817 if(!width().isPercent()) {
5818 m_intrinsicWidth = lengthContext.valueForLength(width());
5819 } else {
5820 m_intrinsicWidth = 0.f;
5821 }
5822
5823 if(!height().isPercent()) {
5824 m_intrinsicHeight = lengthContext.valueForLength(height());
5825 } else {
5826 m_intrinsicHeight = 0.f;
5827 }
5828
5829 const auto& viewBoxRect = viewBox().value();
5830 if(!viewBoxRect.isEmpty() && (!m_intrinsicWidth || !m_intrinsicHeight)) {
5832 if(!m_intrinsicWidth && m_intrinsicHeight)
5833 m_intrinsicWidth = m_intrinsicHeight * intrinsicRatio;
5834 else if(m_intrinsicWidth && !m_intrinsicHeight) {
5835 m_intrinsicHeight = m_intrinsicWidth / intrinsicRatio;
5836 }
5837 }
5838
5839 if(viewBoxRect.isValid() && (!m_intrinsicWidth || !m_intrinsicHeight)) {
5840 m_intrinsicWidth = viewBoxRect.w;
5841 m_intrinsicHeight = viewBoxRect.h;
5842 }
5843
5844 if(!m_intrinsicWidth || !m_intrinsicHeight) {
5845 auto boundingBox = paintBoundingBox();
5846 if(!m_intrinsicWidth)
5847 m_intrinsicWidth = boundingBox.right();
5848 if(!m_intrinsicHeight) {
5849 m_intrinsicHeight = boundingBox.bottom();
5850 }
5851 }
5852}
5853
5859
5873
5875{
5877 const Point translation = {
5878 lengthContext.valueForLength(m_x),
5879 lengthContext.valueForLength(m_y)
5880 };
5881
5883}
5884
5886{
5887 if(isDisplayNone())
5888 return;
5889 SVGBlendInfo blendInfo(this);
5891 newState.beginGroup(blendInfo);
5893 newState.endGroup(blendInfo);
5894}
5895
5897{
5899 if(auto newElement = cloneTargetElement(targetElement)) {
5900 addChild(std::move(newElement));
5901 }
5902 }
5903
5905}
5906
5907inline bool isDisallowedElement(const SVGElement* element)
5908{
5909 switch(element->id()) {
5910 case ElementID::Circle:
5911 case ElementID::Ellipse:
5913 case ElementID::G:
5914 case ElementID::Image:
5915 case ElementID::Line:
5916 case ElementID::Path:
5917 case ElementID::Polygon:
5919 case ElementID::Rect:
5920 case ElementID::Svg:
5921 case ElementID::Switch:
5922 case ElementID::Symbol:
5923 case ElementID::Text:
5924 case ElementID::Tspan:
5925 case ElementID::Use:
5926 return false;
5927 default:
5928 return true;
5929 }
5930}
5931
5932NOVASVG_INLINE std::unique_ptr<SVGElement> SVGUseElement::cloneTargetElement(SVGElement* targetElement)
5933{
5935 return nullptr;
5936 const auto& idAttr = targetElement->getAttribute(PropertyID::Id);
5937 auto parent = parentElement();
5938 while(parent) {
5939 auto attribute = parent->findAttribute(PropertyID::Id);
5940 if(attribute && idAttr == attribute->value())
5941 return nullptr;
5942 parent = parent->parentElement();
5943 }
5944
5945 auto tagId = targetElement->id();
5946 if(tagId == ElementID::Symbol) {
5948 }
5949
5951 newElement->setAttributes(targetElement->attributes());
5952 if(newElement->id() == ElementID::Svg) {
5953 for(const auto& attribute : attributes()) {
5955 newElement->setAttribute(attribute);
5956 }
5957 }
5958 }
5959
5960 if(newElement->id() != ElementID::Use)
5961 targetElement->cloneChildren(newElement.get());
5962 return newElement;
5963}
5964
5979
5981{
5983 const Rect viewportRect = {
5984 lengthContext.valueForLength(m_x),
5985 lengthContext.valueForLength(m_y),
5986 lengthContext.valueForLength(m_width),
5987 lengthContext.valueForLength(m_height)
5988 };
5989
5990 return viewportRect;
5991}
5992
5997
5999{
6000 if(m_image.isNull() || isDisplayNone() || isVisibilityHidden())
6001 return;
6003 Rect srcRect(0, 0, m_image.width(), m_image.height());
6004 if(dstRect.isEmpty() || srcRect.isEmpty())
6005 return;
6006 m_preserveAspectRatio.transformRect(dstRect, srcRect);
6007
6008 SVGBlendInfo blendInfo(this);
6010 newState.beginGroup(blendInfo);
6011 newState->drawImage(m_image, dstRect, srcRect, newState.currentTransform());
6012 newState.endGroup(blendInfo);
6013}
6014
6016{
6017 if(href.compare(0, 5, "data:") == 0) {
6018 std::string_view input(href);
6019 auto index = input.find(',', 5);
6020 if(index == std::string_view::npos)
6021 return Bitmap();
6022 input.remove_prefix(index + 1);
6023 return surface_load_from_image_base64(input.data(), input.length());
6024 }
6025
6026 return surface_load_from_image_file(href.data());
6027}
6028
6030{
6031 if(id == PropertyID::Href) {
6032 m_image = loadImageResource(value);
6033 } else {
6035 }
6036}
6037
6038// ---------------------------------------------------------------
6039// SVGForeignObjectElement / ForeignObjectSimple
6040// ---------------------------------------------------------------
6041
6054
6056{
6058 return Rect(
6059 lengthContext.valueForLength(m_x),
6060 lengthContext.valueForLength(m_y),
6061 lengthContext.valueForLength(m_width),
6062 lengthContext.valueForLength(m_height)
6063 );
6064}
6065
6070
6072{
6074 for(unsigned style = 0; style < 4; ++style) {
6075 if(!(usedStyles & (1u << style)))
6076 continue;
6077 if(style == m_baseFontStyle) {
6078 fonts.face[style] = m_font;
6079 continue;
6080 }
6081 auto variant = makeFont(m_fontFamily, m_font.size(), (style & ForeignObjectFonts::Bold) != 0, (style & ForeignObjectFonts::Italic) != 0);
6082 // A variant the machine can't provide at all must not blank the text: use the element's own face.
6083 fonts.face[style] = variant.isNull() ? m_font : variant;
6084 }
6085 return fonts;
6086}
6087
6089{
6090 m_font = state.font();
6091 m_fontFamily = state.font_family();
6092 m_baseFontStyle = (unsigned char)((state.font_weight() == FontWeight::Bold ? ForeignObjectFonts::Bold : 0)
6093 | (state.font_style() == FontStyle::Italic ? ForeignObjectFonts::Italic : 0));
6094 m_fill = getPaintServer(state.fill(), state.fill_opacity());
6096}
6097
6099{
6100 if(m_rawContent.empty() || isDisplayNone() || isVisibilityHidden())
6101 return;
6102 SVGBlendInfo blendInfo(this);
6104 newState.beginGroup(blendInfo);
6105 auto renderer = m_renderer ? m_renderer : ForeignObjectSimple::instance();
6106 renderer->render(this, newState);
6107 newState.endGroup(blendInfo);
6108}
6109
6111{
6112 static ForeignObjectSimple s_instance;
6113 return &s_instance;
6114}
6115
6116namespace {
6117
6118inline std::u32string utf8ToU32(const std::string& text)
6119{
6120 std::u32string result;
6121 result.reserve(text.size());
6122 text_iterator_t it;
6123 text_iterator_init(&it, text.data(), (int)text.length(), NOVASVG_TEXT_ENCODING_UTF8);
6124 while(text_iterator_has_next(&it))
6125 result.push_back(text_iterator_next(&it));
6126 return result;
6127}
6128
6129// Pulls a "<property>: <value>" declaration's raw value text out of a CSS
6130// declaration block (the inside of a style="..." attribute, or the
6131// inside of a rule's { ... }). Not a general CSS-value parser -- just
6132// finds and trims the text between the property name and the next
6133// `;`/`}`/quote. `property` match is boundary-checked (not
6134// preceded/followed by identifier characters) so searching for "color"
6135// doesn't false-match inside "background-color", or "height" inside
6136// "line-height". Used by parseNumericDeclaration() below and by the
6137// white-space lookup.
6138inline std::optional<std::string_view> findDeclarationValue(std::string_view block, std::string_view property)
6139{
6140 size_t pos = 0;
6141 while((pos = block.find(property, pos)) != std::string_view::npos) {
6142 auto matchStart = pos;
6143 auto after = pos + property.size();
6144 pos = after;
6145 if(matchStart > 0) {
6146 auto prev = block[matchStart - 1];
6147 if(IS_ALPHA(prev) || IS_NUM(prev) || prev == '-' || prev == '_')
6148 continue; // part of a longer property name
6149 }
6150 if(after < block.size() && (IS_ALPHA(block[after]) || IS_NUM(block[after]) || block[after] == '-'))
6151 continue; // property name continues past what we matched, e.g. "line-height" vs "height"
6152
6153 while(after < block.size() && IS_WS(block[after]))
6154 ++after;
6155 if(after >= block.size() || block[after] != ':')
6156 continue;
6157 ++after;
6158 while(after < block.size() && IS_WS(block[after]))
6159 ++after;
6160 auto end = block.find_first_of(";}\"'", after);
6161 auto value = block.substr(after, (end == std::string_view::npos ? block.size() : end) - after);
6162 while(!value.empty() && IS_WS(value.back()))
6163 value.remove_suffix(1);
6164 if(!value.empty())
6165 return value;
6166 }
6167
6168 return std::nullopt;
6169}
6170
6171// Same idea as findDeclarationValue(), but scans to the next *top-level*
6172// `;`/`}` instead of stopping at the first quote -- needed for
6173// `@font-face`'s `src:` value, which legitimately contains both quotes
6174// and commas (`url(...) format("truetype"), url(...) format("woff2")`).
6175// Parens are tracked so a `;`/`}` inside a url()/format() call (neither
6176// occurs in valid CSS, but malformed input shouldn't cut the scan short)
6177// doesn't end the value early either.
6178inline std::optional<std::string_view> findRawDeclarationValue(std::string_view block, std::string_view property)
6179{
6180 size_t pos = 0;
6181 while((pos = block.find(property, pos)) != std::string_view::npos) {
6182 auto matchStart = pos;
6183 auto after = pos + property.size();
6184 pos = after;
6185 if(matchStart > 0) {
6186 auto prev = block[matchStart - 1];
6187 if(IS_ALPHA(prev) || IS_NUM(prev) || prev == '-' || prev == '_')
6188 continue;
6189 }
6190 if(after < block.size() && (IS_ALPHA(block[after]) || IS_NUM(block[after]) || block[after] == '-'))
6191 continue;
6192
6193 while(after < block.size() && IS_WS(block[after]))
6194 ++after;
6195 if(after >= block.size() || block[after] != ':')
6196 continue;
6197 ++after;
6198 while(after < block.size() && IS_WS(block[after]))
6199 ++after;
6200
6201 auto valueStart = after;
6202 auto end = after;
6203 int parenDepth = 0;
6204 while(end < block.size()) {
6205 auto ch = block[end];
6206 if(ch == '(') {
6207 // `url(data:...)` holds the whole embedded font. Nothing inside it ends the value, so when
6208 // the run up to the next `)` has no `(` in it, jump over it in one go.
6209 if(parenDepth == 0) {
6210 const char* base = block.data() + end + 1;
6211 const size_t remaining = block.size() - end - 1;
6212 if(auto* close = static_cast<const char*>(std::memchr(base, ')', remaining))) {
6213 if(std::memchr(base, '(', size_t(close - base)) == nullptr) {
6214 end = size_t(close - block.data()) + 1;
6215 continue;
6216 }
6217 }
6218 }
6219 ++parenDepth;
6220 } else if(ch == ')') {
6221 if(parenDepth > 0)
6222 --parenDepth;
6223 } else if((ch == ';' || ch == '}') && parenDepth == 0) {
6224 break;
6225 }
6226 ++end;
6227 }
6228
6229 auto value = block.substr(valueStart, end - valueStart);
6230 while(!value.empty() && IS_WS(value.back()))
6231 value.remove_suffix(1);
6232 if(!value.empty())
6233 return value;
6234 }
6235
6236 return std::nullopt;
6237}
6238
6239// A CSS colour value (`#f9f`, `rgb(..)`, `hsl(..)`, `rgba(..)`, a name) -> Color; nullopt if it isn't one.
6240inline std::optional<Color> parseCssColor(std::string_view value)
6241{
6242 color_t color;
6243 int length = color_parse(&color, value.data(), (int)value.length());
6244 if(length == 0)
6245 return std::nullopt;
6246 auto argb = color_to_argb32(&color);
6247 return Color((argb >> 16) & 0xff, (argb >> 8) & 0xff, argb & 0xff, (argb >> 24) & 0xff);
6248}
6249
6250// A parsed numeric CSS value: `raw` is the number itself, `isPixels`
6251// says whether it carried a "px" suffix (an absolute length) as opposed
6252// to a bare multiplier (e.g. line-height's "1.5"). Anything else
6253// (%, em, "normal", ...) isn't handled -- callers fall back to their own
6254// default for those, same as when the property is missing entirely.
6255struct NumericDeclaration {
6256 float raw;
6258};
6259
6260inline std::optional<NumericDeclaration> parseNumericDeclaration(std::string_view block, std::string_view property)
6261{
6262 auto value = findDeclarationValue(block, property);
6263 if(!value)
6264 return std::nullopt;
6265
6266 char* end = nullptr;
6267 std::string valueStr(*value);
6268 auto number = std::strtof(valueStr.c_str(), &end);
6269 if(end == valueStr.c_str())
6270 return std::nullopt; // no leading number at all, e.g. "normal"
6271
6272 auto unit = valueStr.c_str() + (end - valueStr.c_str());
6273 while(*unit == ' ')
6274 ++unit;
6275 return NumericDeclaration{number, std::string_view(unit) == "px"};
6276}
6277
6278inline std::optional<std::string_view> htmlAttribute(std::string_view tag, std::string_view name)
6279{
6280 auto pos = tag.find(name);
6281 if(pos == std::string_view::npos)
6282 return std::nullopt;
6283 auto valueStart = pos + name.size();
6284 if(valueStart >= tag.size() || (tag[valueStart] != '"' && tag[valueStart] != '\''))
6285 return std::nullopt;
6286 auto quote = tag[valueStart];
6287 ++valueStart;
6288 auto valueEnd = tag.find(quote, valueStart);
6289 if(valueEnd == std::string_view::npos)
6290 return std::nullopt;
6291 return tag.substr(valueStart, valueEnd - valueStart);
6292}
6293
6294// ---------------------------------------------------------------------------------------------------
6295// <foreignObject> text as runs of one style each.
6296//
6297// Roughly "element.innerText" for the tiny subset of HTML this cares about, but keeping what a reader
6298// can SEE of the markup: which stretches are bold and which are italic. Deliberately not a real HTML
6299// engine -- no box model, no per-tag display, margins or sizes -- because mermaid (and most
6300// SVG-generating tools) only put one short run of text, perhaps with <br>, <b>/<strong> and <i>/<em>
6301// in it.
6302//
6303// Style bits (ForeignObjectFonts::Bold / Italic) come from, in this order:
6304// * the style the foreignObject itself inherits from the SVG around it (the `baseStyle` argument);
6305// * the tag: <b> <strong> = bold, <i> <em> <cite> <var> <dfn> = italic;
6306// * an inline style="font-weight: ..." / style="font-style: ...", which wins over the tag and may
6307// also turn a style OFF again (`font-weight: normal` inside a bold stretch).
6308// Styles nest and are inherited by everything inside the element, and end with its closing tag.
6309// ---------------------------------------------------------------------------------------------------
6310struct ForeignObjectSegment {
6311 std::string text;
6312 unsigned char style = 0;
6313};
6314
6315inline bool foreignObjectIsVoidTag(const std::string& tag)
6316{
6317 static const std::set<std::string_view> tags = {"area", "base", "br", "col", "embed", "hr", "img",
6318 "input", "link", "meta", "source", "track", "wbr"};
6319 return tags.count(tag) > 0;
6320}
6321
6322// Tags that sit INSIDE a line of text, so a boundary with them is not a boundary between words:
6323// "un<b>believ</b>able" is one word. Every tag NOT in this set, and not a block tag, still counts as
6324// a word boundary (a space), exactly as before styles existed -- see foreignObjectStyledText().
6325inline bool foreignObjectIsInlineTag(const std::string& tag)
6326{
6327 static const std::set<std::string_view> tags = {"a", "abbr", "b", "bdi", "bdo", "big", "cite", "code", "data", "del",
6328 "dfn", "em", "font", "i", "ins", "kbd", "label", "mark", "q", "s",
6329 "samp", "small", "span", "strike", "strong", "sub", "sup", "time",
6330 "tt", "u", "var"};
6331 return tags.count(tag) > 0;
6332}
6333
6334// The value of the style="..." attribute inside a tag's text (after its name), or empty.
6335inline std::string_view foreignObjectStyleAttribute(std::string_view attributes)
6336{
6337 size_t pos = 0;
6338 while(pos < attributes.size()) {
6339 while(pos < attributes.size() && (std::isspace(static_cast<unsigned char>(attributes[pos])) || attributes[pos] == '/'))
6340 ++pos;
6341 auto nameStart = pos;
6342 while(pos < attributes.size() && !std::isspace(static_cast<unsigned char>(attributes[pos])) && attributes[pos] != '=' && attributes[pos] != '/')
6343 ++pos;
6344 auto name = attributes.substr(nameStart, pos - nameStart);
6345 while(pos < attributes.size() && std::isspace(static_cast<unsigned char>(attributes[pos])))
6346 ++pos;
6347 std::string_view value;
6348 if(pos < attributes.size() && attributes[pos] == '=') {
6349 ++pos;
6350 while(pos < attributes.size() && std::isspace(static_cast<unsigned char>(attributes[pos])))
6351 ++pos;
6352 if(pos < attributes.size() && (attributes[pos] == '"' || attributes[pos] == '\'')) {
6353 auto quote = attributes[pos++];
6354 auto end = attributes.find(quote, pos);
6355 value = attributes.substr(pos, end == std::string_view::npos ? std::string_view::npos : end - pos);
6356 pos = end == std::string_view::npos ? attributes.size() : end + 1;
6357 } else {
6358 auto valueStart = pos;
6359 while(pos < attributes.size() && !std::isspace(static_cast<unsigned char>(attributes[pos])))
6360 ++pos;
6361 value = attributes.substr(valueStart, pos - valueStart);
6362 }
6363 }
6364 if(name.size() == 5 && (name[0] | 0x20) == 's' && (name[1] | 0x20) == 't' && (name[2] | 0x20) == 'y' && (name[3] | 0x20) == 'l' && (name[4] | 0x20) == 'e')
6365 return value;
6366 }
6367 return {};
6368}
6369
6370// The lower-cased value of one declaration, without a trailing `!important`; empty if it is absent.
6371inline std::string foreignObjectDeclaration(std::string_view block, std::string_view property)
6372{
6373 auto value = findDeclarationValue(block, property);
6374 if(!value)
6375 return {};
6376 std::string result;
6377 for(char c : *value)
6378 result += char(std::tolower(static_cast<unsigned char>(c)));
6379 if(auto bang = result.find('!'); bang != std::string::npos)
6380 result.erase(bang);
6381 while(!result.empty() && std::isspace(static_cast<unsigned char>(result.back())))
6382 result.pop_back();
6383 return result;
6384}
6385
6386// The style in effect inside an element that is being opened.
6387inline unsigned char foreignObjectStyleOfOpening(const std::string& tag, std::string_view attributes, unsigned char inherited)
6388{
6389 unsigned char style = inherited;
6390 if(tag == "b" || tag == "strong")
6392 else if(tag == "i" || tag == "em" || tag == "cite" || tag == "var" || tag == "dfn")
6394
6395 auto inlineStyle = foreignObjectStyleAttribute(attributes);
6396 if(!inlineStyle.empty()) {
6397 if(auto weight = foreignObjectDeclaration(inlineStyle, "font-weight"); !weight.empty()) {
6398 style &= (unsigned char)~ForeignObjectFonts::Bold;
6399 if(parseFontWeight(weight) == FontWeight::Bold)
6401 }
6402 if(auto fontStyle = foreignObjectDeclaration(inlineStyle, "font-style"); !fontStyle.empty()) {
6403 style &= (unsigned char)~ForeignObjectFonts::Italic;
6404 if(parseFontStyle(fontStyle) == FontStyle::Italic)
6406 }
6407 }
6408 return style;
6409}
6410
6411inline std::vector<ForeignObjectSegment> foreignObjectStyledText(std::string_view html, unsigned char baseStyle = 0)
6412{
6413 // Tag names that force a line break, matching their real-browser default of `display: block` (so a
6414 // `<br>`/`<p>`/`<div>` boundary becomes '\n', preserved through the whitespace-collapse pass below,
6415 // instead of collapsing into an ordinary space like other tags do). Mermaid's own labels are
6416 // `white-space: nowrap` by design (each is laid out to fit on one line at the font it expects) and
6417 // only ever span multiple lines where it inserted an explicit `<br>` itself at a deliberate break
6418 // point -- so honoring just these forced breaks reproduces real multi-line labels without needing
6419 // general width-based word-wrap.
6420 static const std::set<std::string_view> block_tags = {"br", "p", "div", "tr", "li"};
6421
6422 std::vector<ForeignObjectSegment> raw;
6423 auto put = [&](char c, unsigned char style) {
6424 if(raw.empty() || raw.back().style != style)
6425 raw.push_back({std::string(), style});
6426 raw.back().text += c;
6427 };
6428
6429 struct OpenElement {
6430 std::string tag;
6431 unsigned char style;
6432 };
6433 std::vector<OpenElement> open;
6434 unsigned char style = baseStyle;
6435
6436 for(size_t i = 0; i < html.size();) {
6437 char c = html[i];
6438
6439 if(c == '<') {
6440 if(html.compare(i, 4, "<!--") == 0) {
6441 auto end = html.find("-->", i + 4);
6442 i = end == std::string_view::npos ? html.size() : end + 3;
6443 put(' ', style);
6444 continue;
6445 }
6446
6447 // the end of the tag: the first '>' that is not inside a quoted attribute value
6448 size_t gt = i + 1;
6449 char quote = 0;
6450 while(gt < html.size() && (quote || html[gt] != '>')) {
6451 if(quote) {
6452 if(html[gt] == quote)
6453 quote = 0;
6454 } else if(html[gt] == '"' || html[gt] == '\'') {
6455 quote = html[gt];
6456 }
6457 ++gt;
6458 }
6459 auto inside = html.substr(i + 1, gt - i - 1);
6460 i = NOVASVG_MIN(gt + 1, html.size());
6461
6462 const bool closing = !inside.empty() && inside.front() == '/';
6463 const bool selfClosing = !inside.empty() && inside.back() == '/';
6464 auto rest = closing ? inside.substr(1) : inside;
6465 size_t nameLength = 0;
6466 while(nameLength < rest.size() && std::isalnum(static_cast<unsigned char>(rest[nameLength])))
6467 ++nameLength;
6468 std::string name(rest.substr(0, nameLength));
6469 for(auto& ch : name)
6470 ch = char(std::tolower(static_cast<unsigned char>(ch)));
6471
6472 // Every tag boundary is a word boundary (a space), so "Hello<br/>world" and "a</td><td>b"
6473 // never glue together; block tags force a line break instead; and the tags that sit inside
6474 // a line of text (<b>, <span>, ...) are no boundary at all.
6475 auto boundary = [&](unsigned char at) {
6476 if(block_tags.count(name))
6477 put('\n', at);
6478 else if(!foreignObjectIsInlineTag(name))
6479 put(' ', at);
6480 };
6481
6482 if(closing) {
6483 for(size_t k = open.size(); k-- > 0;) {
6484 if(open[k].tag == name) {
6485 open.resize(k);
6486 break;
6487 }
6488 }
6489 style = open.empty() ? baseStyle : open.back().style;
6490 boundary(style);
6491 } else {
6492 boundary(style);
6493 if(!selfClosing && !foreignObjectIsVoidTag(name)) {
6494 style = foreignObjectStyleOfOpening(name, rest.substr(nameLength), style);
6495 open.push_back({name, style});
6496 }
6497 }
6498 continue;
6499 }
6500
6501 if(c == '&') {
6502 if(html.compare(i, 5, "&amp;") == 0) { put('&', style); i += 5; continue; }
6503 if(html.compare(i, 4, "&lt;") == 0) { put('<', style); i += 4; continue; }
6504 if(html.compare(i, 4, "&gt;") == 0) { put('>', style); i += 4; continue; }
6505 if(html.compare(i, 6, "&quot;") == 0) { put('"', style); i += 6; continue; }
6506 if(html.compare(i, 6, "&apos;") == 0) { put('\'', style); i += 6; continue; }
6507 if(html.compare(i, 6, "&nbsp;") == 0) { put(' ', style); i += 6; continue; }
6508 }
6509
6510 put(c, style);
6511 ++i;
6512 }
6513
6514 // Collapse runs of ordinary whitespace into a single space (trimming leading/trailing) -- across
6515 // run boundaries, so "<b>a </b> b" has one space, not two -- but '\n' is preserved as its own
6516 // line-break marker rather than being folded into that, and spaces immediately touching a '\n' are
6517 // dropped rather than kept as a redundant space before/after the break.
6518 std::vector<ForeignObjectSegment> collapsed;
6519 auto append = [&](char c, unsigned char at) {
6520 if(collapsed.empty() || collapsed.back().style != at)
6521 collapsed.push_back({std::string(), at});
6522 collapsed.back().text += c;
6523 };
6524 auto dropLast = [&]() {
6525 collapsed.back().text.pop_back();
6526 if(collapsed.back().text.empty())
6527 collapsed.pop_back();
6528 };
6529 bool lastWasSpace = true; // trims leading whitespace too
6530 for(const auto& segment : raw) {
6531 for(char c : segment.text) {
6532 if(c == '\t' || c == '\r')
6533 c = ' ';
6534 if(c == '\n') {
6535 while(!collapsed.empty() && collapsed.back().text.back() == ' ')
6536 dropLast();
6537 append('\n', segment.style);
6538 lastWasSpace = true;
6539 continue;
6540 }
6541 if(c == ' ') {
6542 if(lastWasSpace)
6543 continue;
6544 lastWasSpace = true;
6545 } else {
6546 lastWasSpace = false;
6547 }
6548 append(c, segment.style);
6549 }
6550 }
6551 while(!collapsed.empty() && (collapsed.back().text.back() == ' ' || collapsed.back().text.back() == '\n'))
6552 dropLast();
6553 return collapsed;
6554}
6555
6556// The same text with every style dropped: the plain "innerText" of the HTML ('\n' at forced breaks).
6557inline std::string foreignObjectPlainText(std::string_view html)
6558{
6559 std::string text;
6560 for(const auto& segment : foreignObjectStyledText(html))
6561 text += segment.text;
6562 return text;
6563}
6564
6565// Real line-height for foreignObject text, read from the HTML's own
6566// inline style="line-height:..." (mermaid always sets this directly on
6567// the wrapping <div> -- never via a class the way it sometimes does for
6568// color/background-color -- so this only needs to check inline style=,
6569// not walk the stylesheet the way resolveForeignObjectStyle() does).
6570// Handles a bare multiplier ("1.5", the common case, including
6571// mermaid's own default) or an absolute "Npx" length; anything else
6572// (%, em, "normal", ...) or no line-height at all falls back to the
6573// caller's own default.
6574inline std::optional<float> foreignObjectLineHeight(std::string_view rawHtml, float fontSize)
6575{
6576 size_t pos = 0;
6577 while(pos < rawHtml.size()) {
6578 auto tagStart = rawHtml.find('<', pos);
6579 if(tagStart == std::string_view::npos)
6580 break;
6581 if(tagStart + 1 < rawHtml.size() && rawHtml[tagStart + 1] == '/') {
6582 pos = tagStart + 2;
6583 continue;
6584 }
6585 auto tagEnd = rawHtml.find('>', tagStart);
6586 if(tagEnd == std::string_view::npos)
6587 break;
6588 auto tag = rawHtml.substr(tagStart, tagEnd - tagStart);
6589 if(auto style = htmlAttribute(tag, "style=")) {
6590 if(auto declaration = parseNumericDeclaration(*style, "line-height"))
6591 return declaration->isPixels ? declaration->raw : declaration->raw * fontSize;
6592 }
6593 pos = tagEnd + 1;
6594 }
6595
6596 return std::nullopt;
6597}
6598
6599// Whether the HTML's own CSS lets its text wrap at the box edge. Decided
6600// exactly like a browser would: the OUTERMOST element that declares
6601// `white-space` wins, and `nowrap` / `pre` mean "never soft-wrap" -- that is
6602// what mermaid's default labels say (`display:table-cell; white-space:nowrap`),
6603// so those keep laying out on one line (an overflowing line is handled by
6604// render()'s own horizontal condense, unchanged). Anything else --
6605// `normal`, `break-spaces` (mermaid's wrap mode: `display:table;
6606// white-space:break-spaces; width:200px`), `pre-wrap`, `pre-line` -- may
6607// wrap. No `white-space` declared at all is CSS's initial value, `normal`.
6608inline bool foreignObjectAllowsWrap(std::string_view rawHtml)
6609{
6610 size_t pos = 0;
6611 while(pos < rawHtml.size()) {
6612 auto tagStart = rawHtml.find('<', pos);
6613 if(tagStart == std::string_view::npos)
6614 break;
6615 if(tagStart + 1 < rawHtml.size() && rawHtml[tagStart + 1] == '/') {
6616 pos = tagStart + 2;
6617 continue;
6618 }
6619 auto tagEnd = rawHtml.find('>', tagStart);
6620 if(tagEnd == std::string_view::npos)
6621 break;
6622 auto tag = rawHtml.substr(tagStart, tagEnd - tagStart);
6623 if(auto style = htmlAttribute(tag, "style=")) {
6624 if(auto value = findDeclarationValue(*style, "white-space"))
6625 return !(*value == "nowrap" || *value == "pre");
6626 }
6627 pos = tagEnd + 1;
6628 }
6629 return true;
6630}
6631
6632// The px width the HTML itself constrains its text to, when it says so:
6633// the outermost element declaring `width` and/or `max-width` in px (both ->
6634// the smaller, as in CSS). Only px is understood; anything else (%, em, auto)
6635// means "no declared width" and the caller falls back to the box it has.
6636inline std::optional<float> foreignObjectDeclaredWidth(std::string_view rawHtml)
6637{
6638 size_t pos = 0;
6639 while(pos < rawHtml.size()) {
6640 auto tagStart = rawHtml.find('<', pos);
6641 if(tagStart == std::string_view::npos)
6642 break;
6643 if(tagStart + 1 < rawHtml.size() && rawHtml[tagStart + 1] == '/') {
6644 pos = tagStart + 2;
6645 continue;
6646 }
6647 auto tagEnd = rawHtml.find('>', tagStart);
6648 if(tagEnd == std::string_view::npos)
6649 break;
6650 auto tag = rawHtml.substr(tagStart, tagEnd - tagStart);
6651 if(auto style = htmlAttribute(tag, "style=")) {
6652 std::optional<float> width;
6653 if(auto d = parseNumericDeclaration(*style, "width"); d && d->isPixels)
6654 width = d->raw;
6655 if(auto d = parseNumericDeclaration(*style, "max-width"); d && d->isPixels)
6656 width = width ? NOVASVG_MIN(*width, d->raw) : d->raw;
6657 if(width)
6658 return width;
6659 }
6660 pos = tagEnd + 1;
6661 }
6662 return std::nullopt;
6663}
6664
6665// Greedy word-wrap of ONE paragraph to `maxWidth`: a line takes the next word
6666// while the whole candidate line ("a b c", measured as one string so kerning
6667// across the space counts, like a browser's shaping) still fits. A single
6668// word wider than maxWidth stays alone on an overflowing line -- CSS never
6669// breaks inside a word by default; render() condenses that one line.
6670inline void wrapForeignObjectParagraph(const std::u32string& paragraph, const Font& font, float maxWidth, std::vector<std::u32string>& lines)
6671{
6672 // Browsers lay out in 1/64px units, so "fits" tolerates that much slack.
6673 const float limit = maxWidth + 1.f / 128.f;
6674 std::u32string current;
6675 size_t i = 0;
6676 while(i < paragraph.size()) {
6677 while(i < paragraph.size() && (paragraph[i] == U' ' || paragraph[i] == U'\t'))
6678 ++i;
6679 auto wordEnd = i;
6680 while(wordEnd < paragraph.size() && paragraph[wordEnd] != U' ' && paragraph[wordEnd] != U'\t')
6681 ++wordEnd;
6682 if(wordEnd == i)
6683 break;
6684 auto word = paragraph.substr(i, wordEnd - i);
6685 i = wordEnd;
6686
6687 if(current.empty()) {
6688 current = std::move(word);
6689 continue;
6690 }
6691 auto candidate = current;
6692 candidate += U' ';
6693 candidate += word;
6694 if(font.measureText(std::u32string_view(candidate)) <= limit) {
6695 current = std::move(candidate);
6696 } else {
6697 lines.push_back(std::move(current));
6698 current = std::move(word);
6699 }
6700 }
6701 if(!current.empty())
6702 lines.push_back(std::move(current));
6703}
6704
6705// Splits `text` (already using '\n' for the forced breaks
6706// foreignObjectPlainText() inserts at <br>/<p>/<div> boundaries) into lines.
6707// With maxWidth <= 0 (the caller's HTML is `white-space: nowrap`/`pre`, or
6708// there is no width to wrap at) it only splits on those forced breaks, as
6709// mermaid's nowrap labels want. With a positive maxWidth each paragraph is
6710// additionally greedy word-wrapped to it, using the same Font::measureText()
6711// the paint path uses.
6712inline std::vector<std::u32string> wrapForeignObjectText(const std::string& text, const Font* font = nullptr, float maxWidth = 0.f)
6713{
6714 std::vector<std::u32string> lines;
6715 const bool softWrap = font != nullptr && !font->isNull() && maxWidth > 0.f;
6716 size_t pos = 0;
6717 while(pos <= text.size()) {
6718 auto nl = text.find('\n', pos);
6719 auto paragraph = text.substr(pos, nl == std::string::npos ? std::string::npos : nl - pos);
6720 pos = (nl == std::string::npos) ? text.size() + 1 : nl + 1;
6721 if(paragraph.empty())
6722 continue;
6723 if(softWrap)
6724 wrapForeignObjectParagraph(utf8ToU32(paragraph), *font, maxWidth, lines);
6725 else
6726 lines.push_back(utf8ToU32(paragraph));
6727 }
6728
6729 if(lines.empty())
6730 lines.emplace_back();
6731 return lines;
6732}
6733
6734// One stretch of a line in a single style; a line is a few of them in a row.
6735struct ForeignObjectRun {
6736 std::u32string text;
6737 unsigned char style = 0;
6738};
6739using ForeignObjectLine = std::vector<ForeignObjectRun>;
6740
6741// The text of a segment list split at its forced breaks ('\n'), each paragraph as segments again.
6742inline std::vector<std::vector<ForeignObjectSegment>> splitForeignObjectParagraphs(const std::vector<ForeignObjectSegment>& segments)
6743{
6744 std::vector<std::vector<ForeignObjectSegment>> paragraphs(1);
6745 for(const auto& segment : segments) {
6746 size_t pos = 0;
6747 while(pos <= segment.text.size()) {
6748 auto newline = segment.text.find('\n', pos);
6749 auto piece = segment.text.substr(pos, newline == std::string::npos ? std::string::npos : newline - pos);
6750 if(!piece.empty())
6751 paragraphs.back().push_back({std::move(piece), segment.style});
6752 if(newline == std::string::npos)
6753 break;
6754 paragraphs.emplace_back();
6755 pos = newline + 1;
6756 }
6757 }
6758 return paragraphs;
6759}
6760
6761// Width of text whose characters each have a style: every stretch of one style is measured on its own
6762// with that style's face (kerning is not applied ACROSS a style change, which no face could do anyway).
6763inline float measureForeignObjectCells(const std::u32string& chars, const std::vector<unsigned char>& styles, const ForeignObjectFonts& fonts)
6764{
6765 float width = 0.f;
6766 size_t begin = 0;
6767 while(begin < chars.size()) {
6768 size_t end = begin + 1;
6769 while(end < chars.size() && styles[end] == styles[begin])
6770 ++end;
6771 width += fonts.get(styles[begin]).measureText(std::u32string_view(chars).substr(begin, end - begin));
6772 begin = end;
6773 }
6774 return width;
6775}
6776
6777inline ForeignObjectLine makeForeignObjectLine(const std::u32string& chars, const std::vector<unsigned char>& styles)
6778{
6779 ForeignObjectLine line;
6780 for(size_t i = 0; i < chars.size(); ++i) {
6781 if(line.empty() || line.back().style != styles[i])
6782 line.push_back({std::u32string(), styles[i]});
6783 line.back().text += chars[i];
6784 }
6785 return line;
6786}
6787
6788// Lays ONE paragraph out into lines (appended to `lines`). With maxWidth <= 0 it never soft-wraps.
6789inline void wrapForeignObjectStyledParagraph(const std::vector<ForeignObjectSegment>& paragraph, const ForeignObjectFonts& fonts, float maxWidth, std::vector<ForeignObjectLine>& lines)
6790{
6791 bool uniform = true;
6792 for(const auto& segment : paragraph)
6793 uniform = uniform && segment.style == paragraph.front().style;
6794
6795 if(uniform) {
6796 // One style all the way: exactly the layout text had before it could carry styles (a candidate
6797 // line is measured as ONE string, so kerning across the space counts).
6798 const auto style = paragraph.front().style;
6799 std::string text;
6800 for(const auto& segment : paragraph)
6801 text += segment.text;
6802 auto chars = utf8ToU32(text);
6803 const auto& font = fonts.get(style);
6804 if(maxWidth > 0.f && !font.isNull()) {
6805 std::vector<std::u32string> wrapped;
6806 wrapForeignObjectParagraph(chars, font, maxWidth, wrapped);
6807 for(auto& line : wrapped)
6808 lines.push_back(ForeignObjectLine{ForeignObjectRun{std::move(line), style}});
6809 } else {
6810 lines.push_back(ForeignObjectLine{ForeignObjectRun{std::move(chars), style}});
6811 }
6812 return;
6813 }
6814
6815 std::u32string chars;
6816 std::vector<unsigned char> styles;
6817 for(const auto& segment : paragraph) {
6818 auto piece = utf8ToU32(segment.text);
6819 chars += piece;
6820 styles.insert(styles.end(), piece.size(), segment.style);
6821 }
6822 if(!(maxWidth > 0.f)) {
6823 lines.push_back(makeForeignObjectLine(chars, styles));
6824 return;
6825 }
6826
6827 // Same greedy word-wrap as wrapForeignObjectParagraph(), a word at a time, but a candidate line is
6828 // measured style stretch by style stretch. A space takes the style it has in the source.
6829 const float limit = maxWidth + 1.f / 128.f;
6830 std::u32string current;
6831 std::vector<unsigned char> currentStyles;
6832 size_t i = 0;
6833 while(i < chars.size()) {
6834 unsigned char spaceStyle = 0;
6835 while(i < chars.size() && (chars[i] == U' ' || chars[i] == U'\t')) {
6836 spaceStyle = styles[i];
6837 ++i;
6838 }
6839 auto wordEnd = i;
6840 while(wordEnd < chars.size() && chars[wordEnd] != U' ' && chars[wordEnd] != U'\t')
6841 ++wordEnd;
6842 if(wordEnd == i)
6843 break;
6844 auto word = chars.substr(i, wordEnd - i);
6845 std::vector<unsigned char> wordStyles(styles.begin() + long(i), styles.begin() + long(wordEnd));
6846 i = wordEnd;
6847
6848 if(current.empty()) {
6849 current = std::move(word);
6850 currentStyles = std::move(wordStyles);
6851 continue;
6852 }
6853 auto candidate = current;
6854 auto candidateStyles = currentStyles;
6855 candidate += U' ';
6856 candidateStyles.push_back(spaceStyle);
6857 candidate += word;
6858 candidateStyles.insert(candidateStyles.end(), wordStyles.begin(), wordStyles.end());
6859 if(measureForeignObjectCells(candidate, candidateStyles, fonts) <= limit) {
6860 current = std::move(candidate);
6861 currentStyles = std::move(candidateStyles);
6862 } else {
6863 lines.push_back(makeForeignObjectLine(current, currentStyles));
6864 current = std::move(word);
6865 currentStyles = std::move(wordStyles);
6866 }
6867 }
6868 if(!current.empty())
6869 lines.push_back(makeForeignObjectLine(current, currentStyles));
6870}
6871
6872// Lines of a styled text, honouring its forced breaks; wrapWidth <= 0 = no soft wrapping. Never empty.
6873inline std::vector<ForeignObjectLine> layoutForeignObjectText(const std::vector<ForeignObjectSegment>& segments, const ForeignObjectFonts& fonts, float wrapWidth)
6874{
6875 std::vector<ForeignObjectLine> lines;
6876 for(const auto& paragraph : splitForeignObjectParagraphs(segments)) {
6877 if(!paragraph.empty())
6878 wrapForeignObjectStyledParagraph(paragraph, fonts, wrapWidth, lines);
6879 }
6880 if(lines.empty())
6881 lines.emplace_back();
6882 return lines;
6883}
6884
6885inline float measureForeignObjectLine(const ForeignObjectLine& line, const ForeignObjectFonts& fonts)
6886{
6887 float width = 0.f;
6888 for(const auto& run : line)
6889 width += fonts.get(run.style).measureText(std::u32string_view(run.text));
6890 return width;
6891}
6892
6893// Bit n set = style n appears somewhere in the text.
6894inline unsigned foreignObjectStylesUsed(const std::vector<ForeignObjectSegment>& segments)
6895{
6896 unsigned used = 0;
6897 for(const auto& segment : segments)
6898 used |= 1u << (segment.style & 3);
6899 return used;
6900}
6901
6902} // namespace
6903
6918 float height = 0.f;
6919 float width = 0.f;
6920 int lineCount = 0;
6921 float lineHeight = 0.f;
6922};
6923
6947inline ForeignObjectMetrics measureForeignObjectSegments(std::string_view rawHtml, const std::vector<ForeignObjectSegment>& segments, const ForeignObjectFonts& fonts, unsigned char baseStyle, float maxWidth)
6948{
6949 ForeignObjectMetrics metrics;
6950 const auto& font = fonts.get(baseStyle);
6951 if(font.isNull())
6952 return metrics;
6953 // Same rule render() applies, so layout and paint agree: soft-wrap only if
6954 // the HTML's own `white-space` allows it, at the explicit maxWidth if one
6955 // was given, else at the width the HTML itself declares (px).
6956 float wrapWidth = 0.f;
6957 if(foreignObjectAllowsWrap(rawHtml)) {
6958 if(maxWidth > 0.f)
6959 wrapWidth = maxWidth;
6960 else if(auto declared = foreignObjectDeclaredWidth(rawHtml))
6961 wrapWidth = *declared;
6962 }
6963 auto lines = layoutForeignObjectText(segments, fonts, wrapWidth);
6964 metrics.lineCount = int(lines.size());
6965 metrics.lineHeight = foreignObjectLineHeight(rawHtml, font.size()).value_or(font.height() * 1.2f);
6966 metrics.height = metrics.lineHeight * float(metrics.lineCount);
6967 for(const auto& line : lines)
6968 // NOVASVG_MAX(), not std::max(): see novasvg.h's own
6969 // comment on why this codebase avoids std::max/std::min entirely
6970 // rather than special-casing each call site against the
6971 // Windows.h max/min macro collision.
6972 metrics.width = NOVASVG_MAX(metrics.width, measureForeignObjectLine(line, fonts));
6973 return metrics;
6974}
6975
6982inline ForeignObjectMetrics measureForeignObjectContent(std::string_view rawHtml, const ForeignObjectFonts& fonts, float maxWidth = -1.f, unsigned char baseStyle = 0)
6983{
6984 return measureForeignObjectSegments(rawHtml, foreignObjectStyledText(rawHtml, baseStyle), fonts, baseStyle, maxWidth);
6985}
6986
6993inline ForeignObjectMetrics measureForeignObjectContent(std::string_view rawHtml, const Font& font, float maxWidth = -1.f)
6994{
6995 auto segments = foreignObjectStyledText(rawHtml);
6996 for(auto& segment : segments)
6997 segment.style = 0; // one face for everything: measured exactly like plain text
6998 ForeignObjectFonts fonts;
6999 fonts.face[0] = font;
7000 return measureForeignObjectSegments(rawHtml, segments, fonts, 0, maxWidth);
7001}
7002
7003NOVASVG_INLINE void ForeignObjectSimple::render(const SVGForeignObjectElement* element, SVGRenderState& state) const
7004{
7005 auto segments = foreignObjectStyledText(element->rawContent(), element->baseFontStyle());
7006 if(segments.empty())
7007 return;
7008
7009 const auto& font = element->font();
7010 if(font.isNull())
7011 return;
7012
7013 // One face per style that actually occurs (just font() for unstyled text, nothing else is looked up).
7014 const auto fonts = element->fontsFor(foreignObjectStylesUsed(segments));
7015
7016 auto box = element->fillBoundingBox();
7017 // A browser wraps HTML text at its box's edge unless CSS says nowrap/pre.
7018 auto lines = layoutForeignObjectText(segments, fonts, foreignObjectAllowsWrap(element->rawContent()) ? box.w : 0.f);
7019
7020 // Real line-height from the HTML's own `line-height:` (mermaid's
7021 // content usually says 1.5) when present and parseable; falls back
7022 // to a 1.2x default (roughly a browser's own "normal") otherwise --
7023 // e.g. for %/em values or "normal" itself, which aren't handled.
7024 auto lineHeight = foreignObjectLineHeight(element->rawContent(), font.size()).value_or(font.height() * 1.2f);
7025 auto totalHeight = lineHeight * float(lines.size());
7026 auto topY = box.y + (box.h - totalHeight) / 2.f;
7027
7028 if(auto backgroundColor = element->htmlBackgroundColor()) {
7029 state->setColor(*backgroundColor);
7030 Path backgroundPath;
7031 backgroundPath.addRect(box);
7032 state->fillPath(backgroundPath, FillRule::NonZero, state.currentTransform());
7033 }
7034
7035 state->setColor(element->htmlTextColor().value_or(Color(0, 0, 0)));
7036
7037 for(size_t i = 0; i < lines.size(); ++i) {
7038 const auto& line = lines[i];
7039 auto lineWidth = measureForeignObjectLine(line, fonts);
7040
7041 // A line is as tall as its tallest face (bold or italic can differ a little from the regular one);
7042 // a line of one style, the usual case, is simply that style's face.
7043 float ascent = font.ascent();
7044 float fontHeight = font.height();
7045 if(!line.empty()) {
7046 ascent = 0.f;
7047 fontHeight = 0.f;
7048 for(const auto& run : line) {
7049 ascent = NOVASVG_MAX(ascent, fonts.get(run.style).ascent());
7050 fontHeight = NOVASVG_MAX(fontHeight, fonts.get(run.style).height());
7051 }
7052 }
7053
7054 // A real Trebuchet-MS-metrics render always fits each wrapped
7055 // line with room to spare -- so overflow here only ever means
7056 // OUR substitute font is wider than the one mermaid measured
7057 // with (this environment lacks Trebuchet MS). There's no
7058 // reliable way to know how much wider without the real font, so
7059 // rather than truncating (tried before, still didn't match a
7060 // real Chromium render) we condense that one line horizontally.
7061 float scale = 1.f;
7062 if(lineWidth > box.w && lineWidth > 0.f)
7063 scale = box.w / lineWidth;
7064
7065 Point origin(
7066 box.x + (box.w - lineWidth * scale) / 2.f,
7067 // Baseline for this line = top of the text block + however
7068 // many line-heights precede it + half the leading (the extra
7069 // space line-height adds beyond the font's own ascent+descent,
7070 // split evenly above and below -- standard CSS line-box
7071 // behavior) + this font's ascent. Without the half-leading
7072 // term, text sits noticeably above center (font.height() is
7073 // usually well under a 1.5x line-height, so the omitted term
7074 // isn't a rounding-sized error).
7075 topY + float(i) * lineHeight + (lineHeight - fontHeight) / 2.f + ascent
7076 );
7077
7078 auto textTransform = state.currentTransform();
7079 if(scale < 1.f) {
7080 // Condense horizontally about the line's own left edge so it
7081 // still lands at `origin`; only x is scaled, so glyph height
7082 // (and therefore this line's vertical position) is untouched.
7083 textTransform = textTransform * Transform::translated(origin.x, 0.f) * Transform::scaled(scale, 1.f) * Transform::translated(-origin.x, 0.f);
7084 }
7085
7086 // The runs of the line side by side on the one baseline, each in its own face.
7087 float x = origin.x;
7088 for(const auto& run : line) {
7089 const std::u32string_view runView(run.text);
7090 const auto& runFont = fonts.get(run.style);
7091 state->fillText(runView, runFont, Point(x, origin.y), textTransform);
7092 x += runFont.measureText(runView);
7093 }
7094 }
7095}
7096
7097NOVASVG_INLINE SVGSymbolElement::SVGSymbolElement(Document* document)
7098 : SVGGraphicsElement(document, ElementID::Symbol)
7099 , SVGFitToViewBox(this)
7100{
7101}
7102
7107
7109{
7110 if(isDisplayNone())
7111 return;
7112 SVGBlendInfo blendInfo(this);
7113 SVGRenderState newState(this, state, localTransform());
7114 newState.beginGroup(blendInfo);
7115 renderChildren(newState);
7116 newState.endGroup(blendInfo);
7117}
7118
7123
7124// SVG conditional processing for one switch child. `requiredExtensions` must be non-empty
7125// and name an extension we implement (the XHTML namespace -- <foreignObject> content);
7126// `systemLanguage` must list a tag matching the user language ("en"); no attribute -> true.
7127// (requiredFeatures was removed in SVG 2 and browsers treat it as always true.)
7128inline bool switchChildPasses(const SVGElement* child)
7129{
7130 auto trim = [](std::string_view v) {
7131 while(!v.empty() && std::isspace(static_cast<unsigned char>(v.front())))
7132 v.remove_prefix(1);
7133 while(!v.empty() && std::isspace(static_cast<unsigned char>(v.back())))
7134 v.remove_suffix(1);
7135 return v;
7136 };
7138 std::string_view value = trim(child->getAttribute(PropertyID::RequiredExtensions));
7139 bool supported = false;
7140 size_t pos = 0;
7141 while(pos < value.size() && !supported) {
7142 while(pos < value.size() && std::isspace(static_cast<unsigned char>(value[pos])))
7143 ++pos;
7144 auto end = pos;
7145 while(end < value.size() && !std::isspace(static_cast<unsigned char>(value[end])))
7146 ++end;
7147 supported = value.substr(pos, end - pos) == "http://www.w3.org/1999/xhtml";
7148 pos = end;
7149 }
7150 if(!supported)
7151 return false;
7152 }
7154 std::string_view value = child->getAttribute(PropertyID::SystemLanguage);
7155 bool matches = false;
7156 size_t pos = 0;
7157 while(pos <= value.size() && !matches) {
7158 auto comma = value.find(',', pos);
7159 auto tag = trim(value.substr(pos, comma == std::string_view::npos ? std::string_view::npos : comma - pos));
7160 std::string lower(tag);
7161 for(auto& c : lower)
7162 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
7163 matches = lower == "en" || lower.rfind("en-", 0) == 0;
7164 if(comma == std::string_view::npos)
7165 break;
7166 pos = comma + 1;
7167 }
7168 if(!matches)
7169 return false;
7170 }
7171 return true;
7172}
7173
7174NOVASVG_INLINE const SVGElement* SVGSwitchElement::selectedChild() const
7175{
7176 for(const auto& child : children()) {
7177 auto element = toSVGElement(child);
7178 // only graphics elements take part; <title>, <desc>, <defs>... are skipped
7179 if(!element || isDisallowedElement(element))
7180 continue;
7181 if(switchChildPasses(element))
7182 return element;
7183 }
7184 return nullptr;
7185}
7186
7188{
7189 if(isDisplayNone())
7190 return;
7191 SVGBlendInfo blendInfo(this);
7192 SVGRenderState newState(this, state, localTransform());
7193 newState.beginGroup(blendInfo);
7194 if(auto child = selectedChild())
7195 child->render(newState);
7196 newState.endGroup(blendInfo);
7197}
7198
7203
7205 : SVGElement(document, ElementID::Marker)
7206 , SVGFitToViewBox(this)
7211 , m_markerUnits(PropertyID::MarkerUnits, MarkerUnits::StrokeWidth)
7212 , m_orient(PropertyID::Orient)
7213{
7214 addProperty(m_refX);
7215 addProperty(m_refY);
7216 addProperty(m_markerWidth);
7217 addProperty(m_markerHeight);
7218 addProperty(m_markerUnits);
7219 addProperty(m_orient);
7220}
7221
7223{
7224 LengthContext lengthContext(this);
7225 const Point refPoint = {
7226 lengthContext.valueForLength(m_refX),
7227 lengthContext.valueForLength(m_refY)
7228 };
7229
7230 return refPoint;
7231}
7232
7234{
7235 LengthContext lengthContext(this);
7236 const Size markerSize = {
7237 lengthContext.valueForLength(m_markerWidth),
7238 lengthContext.valueForLength(m_markerHeight)
7239 };
7240
7241 return markerSize;
7242}
7243
7244NOVASVG_INLINE Transform SVGMarkerElement::markerTransform(const Point& origin, float angle, float strokeWidth) const
7245{
7246 auto transform = Transform::translated(origin.x, origin.y);
7247 if(m_orient.orientType() == SVGAngle::OrientType::Angle) {
7248 transform.rotate(m_orient.value());
7249 } else {
7250 transform.rotate(angle);
7251 }
7252
7253 auto viewTransform = viewBoxToViewTransform(markerSize());
7254 auto refOrigin = viewTransform.mapPoint(refPoint());
7255 if(m_markerUnits.value() == MarkerUnits::StrokeWidth)
7256 transform.scale(strokeWidth, strokeWidth);
7257 transform.translate(-refOrigin.x, -refOrigin.y);
7258 return transform * viewTransform;
7259}
7260
7261NOVASVG_INLINE Rect SVGMarkerElement::markerBoundingBox(const Point& origin, float angle, float strokeWidth) const
7262{
7263 return markerTransform(origin, angle, strokeWidth).mapRect(paintBoundingBox());
7264}
7265
7266NOVASVG_INLINE void SVGMarkerElement::renderMarker(SVGRenderState& state, const Point& origin, float angle, float strokeWidth) const
7267{
7268 if(state.hasCycleReference(this))
7269 return;
7270 SVGBlendInfo blendInfo(this);
7271 SVGRenderState newState(this, state, markerTransform(origin, angle, strokeWidth));
7272 newState.beginGroup(blendInfo);
7273 if(isOverflowHidden())
7274 newState->clipRect(getClipRect(markerSize()), FillRule::NonZero, newState.currentTransform());
7275 renderChildren(newState);
7276 newState.endGroup(blendInfo);
7277}
7278
7283
7286 , m_clipPathUnits(PropertyID::ClipPathUnits, Units::UserSpaceOnUse)
7287{
7288 addProperty(m_clipPathUnits);
7289}
7290
7292{
7294 if(m_clipPathUnits.value() == Units::ObjectBoundingBox) {
7295 auto bbox = element->fillBoundingBox();
7296 clipBoundingBox.x = clipBoundingBox.x * bbox.w + bbox.x;
7297 clipBoundingBox.y = clipBoundingBox.y * bbox.h + bbox.y;
7300 }
7301
7303}
7304
7306{
7307 if(state.hasCycleReference(this))
7308 return;
7309 auto maskImage = Canvas::create(state.currentTransform().mapRect(state.paintBoundingBox()));
7310 auto currentTransform = state.currentTransform() * localTransform();
7311 if(m_clipPathUnits.value() == Units::ObjectBoundingBox) {
7312 auto bbox = state.fillBoundingBox();
7313 currentTransform.translate(bbox.x, bbox.y);
7314 currentTransform.scale(bbox.w, bbox.h);
7315 }
7316
7317 SVGRenderState newState(this, &state, currentTransform, SVGRenderMode::Clipping, maskImage);
7318 renderChildren(newState);
7319 if(clipper()) {
7320 clipper()->applyClipMask(newState);
7321 }
7322
7323 state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f);
7324}
7325
7327{
7328 if(node && node->isGeometryElement())
7329 return static_cast<const SVGGeometryElement*>(node);
7330 return nullptr;
7331}
7332
7334{
7335 auto currentTransform = state.currentTransform() * localTransform();
7336 if(m_clipPathUnits.value() == Units::ObjectBoundingBox) {
7337 auto bbox = state.fillBoundingBox();
7338 currentTransform.translate(bbox.x, bbox.y);
7339 currentTransform.scale(bbox.w, bbox.h);
7340 }
7341
7342 for(const auto& child : children()) {
7343 auto element = toSVGElement(child);
7344 if(element == nullptr || element->isDisplayNone())
7345 continue;
7346 Transform clipTransform(currentTransform);
7347 auto shapeElement = toSVGGeometryElement(element);
7348 if(shapeElement == nullptr) {
7349 if(element->id() != ElementID::Use)
7350 continue;
7351 clipTransform.multiply(element->localTransform());
7352 shapeElement = toSVGGeometryElement(element->firstChild());
7353 }
7354
7355 if(shapeElement == nullptr || !shapeElement->isRenderable())
7356 continue;
7357 state->clipPath(shapeElement->path(), shapeElement->clip_rule(), clipTransform * shapeElement->localTransform());
7358 return;
7359 }
7360
7362}
7363
7365{
7366 if(clipper())
7367 return true;
7368 const SVGGeometryElement* prevShapeElement = nullptr;
7369 for(const auto& child : children()) {
7370 auto element = toSVGElement(child);
7371 if(element == nullptr || element->isDisplayNone())
7372 continue;
7373 auto shapeElement = toSVGGeometryElement(element);
7374 if(shapeElement == nullptr) {
7375 if(element->isTextPositioningElement())
7376 return true;
7377 if(element->id() != ElementID::Use)
7378 continue;
7379 if(element->clipper())
7380 return true;
7381 shapeElement = toSVGGeometryElement(element->firstChild());
7382 }
7383
7384 if(shapeElement == nullptr || !shapeElement->isRenderable())
7385 continue;
7386 if(prevShapeElement || shapeElement->clipper())
7387 return true;
7388 prevShapeElement = shapeElement;
7389 }
7390
7391 return false;
7392}
7393
7410
7412{
7413 LengthContext lengthContext(this, m_maskUnits.value());
7414 Rect maskRect = {
7415 lengthContext.valueForLength(m_x),
7416 lengthContext.valueForLength(m_y),
7417 lengthContext.valueForLength(m_width),
7418 lengthContext.valueForLength(m_height)
7419 };
7420
7421 if(m_maskUnits.value() == Units::ObjectBoundingBox) {
7422 auto bbox = element->fillBoundingBox();
7423 maskRect.x = maskRect.x * bbox.w + bbox.x;
7424 maskRect.y = maskRect.y * bbox.h + bbox.y;
7425 maskRect.w = maskRect.w * bbox.w;
7426 maskRect.h = maskRect.h * bbox.h;
7427 }
7428
7429 return maskRect;
7430}
7431
7433{
7435 if(m_maskContentUnits.value() == Units::ObjectBoundingBox) {
7436 auto bbox = element->fillBoundingBox();
7441 }
7442
7443 return maskBoundingBox.intersected(maskRect(element));
7444}
7445
7447{
7448 if(state.hasCycleReference(this))
7449 return;
7450 auto maskImage = Canvas::create(state.currentTransform().mapRect(state.paintBoundingBox()));
7451 maskImage->clipRect(maskRect(state.element()), FillRule::NonZero, state.currentTransform());
7452
7453 auto currentTransform = state.currentTransform();
7454 if(m_maskContentUnits.value() == Units::ObjectBoundingBox) {
7455 auto bbox = state.fillBoundingBox();
7456 currentTransform.translate(bbox.x, bbox.y);
7457 currentTransform.scale(bbox.w, bbox.h);
7458 }
7459
7462 if(clipper())
7464 if(masker()) {
7466 }
7467
7468 if(m_mask_type == MaskType::Luminance)
7469 maskImage->convertToLuminanceMask();
7470 state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f);
7471}
7472
7473// ---- svgfilterelement (impl) ----
7478
7480{
7481 float margin = 0.f;
7482 for(const auto& child : children()) {
7483 auto element = toSVGElement(child);
7484 if(!element)
7485 continue;
7486 if(element->id() == ElementID::FeGaussianBlur) {
7487 auto sigma = parseFirstFloat(element->getAttribute(PropertyID::StdDeviation), 0.f);
7488 margin = NOVASVG_MAX(margin, sigma * 3.f);
7489 } else if(element->id() == ElementID::FeDropShadow) {
7490 auto sigma = parseFirstFloat(element->getAttribute(PropertyID::StdDeviation), 2.f);
7491 auto dx = parseFirstFloat(element->getAttribute(PropertyID::Dx), 2.f);
7492 auto dy = parseFirstFloat(element->getAttribute(PropertyID::Dy), 2.f);
7493 margin = NOVASVG_MAX(margin, sigma * 3.f + NOVASVG_MAX(std::abs(dx), std::abs(dy)));
7494 } else if(element->id() == ElementID::FeOffset) {
7495 auto dx = parseFirstFloat(element->getAttribute(PropertyID::Dx), 0.f);
7496 auto dy = parseFirstFloat(element->getAttribute(PropertyID::Dy), 0.f);
7497 margin = NOVASVG_MAX(margin, NOVASVG_MAX(std::abs(dx), std::abs(dy)));
7498 }
7499 // feFlood/feComposite/feMerge don't themselves need extra margin;
7500 // if they combine an already-blurred/offset named result, that
7501 // primitive already grew the margin.
7502 }
7503
7504 return margin;
7505}
7506
7508{
7509 SVGFilterContext context(state.canvas(), scale);
7510 for(const auto& child : children()) {
7511 auto element = toSVGElement(child);
7512 if(!element)
7513 continue;
7514 auto output = element->applyFilterPrimitive(context);
7515 if(!output)
7516 continue; // not a primitive (e.g. a stray feMergeNode outside feMerge)
7517 context.setResult(element->getAttribute(PropertyID::Result), std::move(output));
7518 }
7519
7520 state.canvas()->copyPixelsFrom(*context.lastResult());
7521}
7522
7524{
7525 m_mask_type = state.mask_type();
7527}
7528
7529} // namespace novasvg
7530
7531#endif // NOVASVG_SVGELEMENT_H
Attribute(int specificity, PropertyID id, std::string value)
Definition svgelement.h:750
int specificity() const
Definition svgelement.h:754
const std::string & value() const
Definition svgelement.h:756
PropertyID id() const
Definition svgelement.h:755
const Length & length() const
Definition svgelement.h:470
BaselineShift(const Length &length)
Definition svgelement.h:467
bool isNull() const
Checks if the bitmap is null.
Definition bitmap.h:132
int height() const
Gets the height of the bitmap.
Definition bitmap.h:1688
int width() const
Gets the width of the bitmap.
Definition bitmap.h:1681
Represents a color in RGBA format (0xRRGGBBAA).
Definition color.h:85
float getGF() const
Green component as a 0..1 float.
Definition color.h:267
float getBF() const
Blue component as a 0..1 float.
Definition color.h:269
Color colorWithAlpha(float opacity) const
This color with its alpha scaled by opacity (0..1).
Definition color.h:282
bool isVisible() const
True if not fully transparent (alpha > 0).
Definition color.h:276
static const Color Transparent
Definition color.h:827
float getRF() const
Red component as a 0..1 float.
Definition color.h:265
uint8_t getA() const
Get the alpha component of the color.
Definition color.h:232
float getAF() const
Alpha component as a 0..1 float.
Definition color.h:271
static const Color Black
Definition color.h:825
static const Color White
Definition color.h:826
FontFace getFontFace(const std::string &family, bool bold, bool italic) const
Definition font.h:334
FontFace getFontFaceForFamilyStack(const std::string &familyStack, bool bold, bool italic) const
Definition font.h:370
bool isNull() const
Definition font.h:37
float height() const
Definition font.h:147
float ascent() const
Definition font.h:145
float size() const
Definition font.h:172
Renders the (HTML-ish) content captured inside a <foreignObject>.
virtual ~ForeignObjectRenderer()=default
virtual void render(const SVGForeignObjectElement *element, SVGRenderState &state) const =0
Default ForeignObjectRenderer: tag-stripped plain text, centered.
static ForeignObjectSimple * instance()
LengthContext(const SVGElement *element, Units units=Units::UserSpaceOnUse)
Definition svgelement.h:422
float valueForLength(const SVGLength &length) const
Definition svgelement.h:427
float valueForLength(const Length &length, LengthDirection direction) const
Length()=default
Length(float value, LengthUnits units)
Definition svgelement.h:384
float value() const
Definition svgelement.h:388
bool parse(std::string_view input, LengthNegativeMode mode)
LengthUnits units() const
Definition svgelement.h:389
Paint()=default
bool isNone() const
Definition svgelement.h:185
const std::string & id() const
Definition svgelement.h:184
Paint(const Color &color)
Definition svgelement.h:178
Paint(const std::string &id, const Color &color)
Definition svgelement.h:179
const Color & color() const
Definition svgelement.h:183
bool isDone() const
Definition canvas.h:371
PathCommand currentSegment(std::array< Point, 3 > &points) const
Definition canvas.h:866
void addRect(float x, float y, float w, float h)
Definition canvas.h:789
bool isEmpty() const
Definition canvas.h:830
bool parse(const char *data, size_t length)
Definition canvas.h:842
void reset()
Definition canvas.h:809
constexpr bool isValid() const
Definition canvas.h:201
constexpr Rect & unite(const Rect &o)
Definition canvas.h:248
static const Rect Empty
Definition canvas.h:203
constexpr Rect intersected(const Rect &rect) const
Definition canvas.h:214
constexpr Size size() const
Definition canvas.h:194
constexpr Rect & intersect(const Rect &o)
Definition canvas.h:242
constexpr void inflate(float dx, float dy)
Definition canvas.h:181
constexpr bool isEmpty() const
Definition canvas.h:199
static const Rect Invalid
Definition canvas.h:204
float value() const
Definition svgelement.h:353
OrientType orientType() const
Definition svgelement.h:354
bool parse(std::string_view input) final
SVGAngle(PropertyID id)
Definition svgelement.h:349
SVGBlendInfo(const SVGClipPathElement *clipper, const SVGMaskElement *masker, const SVGFilterElement *filter, float opacity, MixBlendMode mixBlendMode=MixBlendMode::Normal)
const SVGFilterElement * filterElement() const
MixBlendMode mixBlendMode() const
bool requiresCompositing(SVGRenderMode mode) const
const SVGMaskElement * masker() const
const SVGClipPathElement * clipper() const
SVGBlendInfo(const SVGElement *element)
float opacity() const
Rect updateShape(Path &path) final
SVGCircleElement(Document *document)
void applyClipPath(SVGRenderState &state) const
void applyClipMask(SVGRenderState &state) const
SVGClipPathElement(Document *document)
const SVGEnumeration< Units > & clipPathUnits() const
Rect clipBoundingBox(const SVGElement *element) const
SVGDefsElement(Document *document)
SVGNode * lastChild() const
bool isOverflowHidden() const
Definition svgelement.h:895
SVGFilterElement * getFilterElement(std::string_view id) const
const std::string & getAttribute(std::string_view name) const
virtual Rect paintBoundingBox() const
MixBlendMode mixBlendMode() const
Definition svgelement.h:905
Size currentViewportSize() const
virtual Transform localTransform() const
Definition svgelement.h:852
void layoutChildren(SVGLayoutState &state)
bool isHiddenElement() const
const SVGClipPathElement * clipper() const
Definition svgelement.h:901
float font_size() const
Definition svgelement.h:871
bool hasAttribute(std::string_view name) const
const Attribute * findAttribute(PropertyID id) const
const SVGNodeList & children() const
Definition svgelement.h:850
SVGPaintElement * getPainter(std::string_view id) const
virtual ~SVGElement()=default
SVGNode * addChild(std::unique_ptr< SVGNode > child)
std::unique_ptr< SVGNode > clone(bool deep) const final
SVGClipPathElement * getClipper(std::string_view id) const
virtual Rect strokeBoundingBox() const
SVGMarkerElement * getMarker(std::string_view id) const
virtual Rect fillBoundingBox() const
void renderChildren(SVGRenderState &state) const
void transverse(T callback)
Definition svgelement.h:949
bool isVisibilityHidden() const
Definition svgelement.h:896
const SVGPropertyList & properties() const
Definition svgelement.h:849
const AttributeList & attributes() const
Definition svgelement.h:848
bool isElement() const final
Definition svgelement.h:907
ElementID id() const
Definition svgelement.h:847
static std::unique_ptr< SVGElement > create(Document *document, ElementID id)
virtual void render(SVGRenderState &state) const
void addProperty(SVGProperty &value)
SVGMaskElement * getMasker(std::string_view id) const
virtual void layout(SVGLayoutState &state)
SVGElement * previousElement() const
SVGElement * nextElement() const
void cloneChildren(SVGElement *parentElement) const
bool setAttribute(std::string_view name, const std::string &value)
SVGProperty * getProperty(PropertyID id) const
bool isPointableElement() const
virtual void parseAttribute(PropertyID id, const std::string &value)
virtual std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const
Definition svgelement.h:892
SVGNode * firstChild() const
void setAttributes(const AttributeList &attributes)
virtual void layoutElement(const SVGLayoutState &state)
bool isDisplayNone() const
Definition svgelement.h:894
SVGElement(Document *document, ElementID id)
SVGElement * elementFromPoint(float x, float y)
const SVGFilterElement * filterElement() const
Definition svgelement.h:903
float opacity() const
Definition svgelement.h:904
virtual void build()
const SVGMaskElement * masker() const
Definition svgelement.h:902
SVGEllipseElement(Document *document)
Rect updateShape(Path &path) final
SVGEnumeration(PropertyID id, Enum value)
Definition svgelement.h:327
bool parse(std::string_view input) final
std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const final
SVGFECompositeElement(Document *document)
SVGFEDropShadowElement(Document *document)
std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const final
std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const final
SVGFEFloodElement(Document *document)
std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const final
SVGFEGaussianBlurElement(Document *document)
std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const final
SVGFEMergeElement(Document *document)
std::shared_ptr< Canvas > applyFilterPrimitive(SVGFilterContext &context) const final
SVGFEOffsetElement(Document *document)
void setResult(std::string_view name, std::shared_ptr< Canvas > canvas)
SVGFilterContext(const std::shared_ptr< Canvas > &sourceGraphic, float scale)
std::shared_ptr< Canvas > resolveInput(std::string_view in) const
std::shared_ptr< Canvas > cloneCanvas(const Canvas &source) const
std::shared_ptr< Canvas > newCanvas() const
const std::shared_ptr< Canvas > & lastResult() const
SVGFilterElement(Document *document)
void applyFilter(SVGRenderState &state, float scale) const
const SVGPreserveAspectRatio & preserveAspectRatio() const
Definition svgelement.h:969
Transform viewBoxToViewTransform(const Size &viewportSize) const
Rect getClipRect(const Size &viewportSize) const
const SVGRect & viewBox() const
Definition svgelement.h:968
SVGFitToViewBox(SVGElement *element)
Rect fillBoundingBox() const final
const std::optional< Color > & htmlTextColor() const
void layoutElement(const SVGLayoutState &state) final
SVGForeignObjectElement(Document *document)
const std::string & rawContent() const
Raw markup captured between <foreignObject ...> and </foreignObject> (whatever HTML/XML the document ...
const SVGLength & y() const
void render(SVGRenderState &state) const final
const SVGLength & width() const
const SVGPaintServer & fill() const
const std::optional< Color > & htmlBackgroundColor() const
void setRenderer(ForeignObjectRenderer *renderer)
void setRawContent(std::string content)
unsigned char baseFontStyle() const
Bold/italic bits (ForeignObjectFonts::Bold/Italic) the element itself inherits, i....
ForeignObjectFonts fontsFor(unsigned usedStyles) const
void setHtmlStyle(std::optional< Color > text, std::optional< Color > background)
const SVGLength & x() const
Rect strokeBoundingBox() const final
ForeignObjectRenderer * renderer() const
const SVGLength & height() const
SVGGElement(Document *document)
void render(SVGRenderState &state) const final
void render(SVGRenderState &state) const override
bool isGeometryElement() const final
FillRule clip_rule() const
void updateMarkerPositions(SVGMarkerPositionList &positions, const SVGLayoutState &state)
SVGGeometryElement(Document *document, ElementID id)
virtual Rect updateShape(Path &path)=0
const Path & path() const
Rect fillBoundingBox() const override
FillRule fill_rule() const
Rect strokeBoundingBox() const override
void layoutElement(const SVGLayoutState &state) override
const Transform & gradientTransform() const
void setSpreadMethod(const SVGGradientElement *value)
void setGradientTransform(const SVGGradientElement *value)
const SVGGradientElement * gradientContentElement() const
SpreadMethod spreadMethod() const
void setGradientContentElement(const SVGGradientElement *value)
void setGradientUnits(const SVGGradientElement *value)
void setDefaultValues(const SVGGradientElement *element)
const SVGTransform & gradientTransform() const
void collectGradientAttributes(SVGGradientAttributes &attributes) const
SVGGradientElement(Document *document, ElementID id)
const SVGEnumeration< Units > & gradientUnits() const
const SVGEnumeration< SpreadMethod > & spreadMethod() const
Transform localTransform() const override
StrokeData getStrokeData(const SVGLayoutState &state) const
SVGPaintServer getPaintServer(const Paint &paint, float opacity) const
SVGGraphicsElement(Document *document, ElementID id)
bool isGraphicsElement() const final
const SVGTransform & transform() const
Rect fillBoundingBox() const final
const Bitmap & image() const
Rect strokeBoundingBox() const final
const SVGLength & height() const
void parseAttribute(PropertyID id, const std::string &value) final
const SVGLength & x() const
void render(SVGRenderState &state) const final
SVGImageElement(Document *document)
const SVGLength & y() const
const SVGLength & width() const
const SVGPreserveAspectRatio & preserveAspectRatio() const
DominantBaseline dominant_baseline() const
TextAnchor text_anchor() const
AlignmentBaseline alignment_baseline() const
FontStyle font_style() const
const std::string & marker_start() const
float stroke_miterlimit() const
LineJoin stroke_linejoin() const
const std::string & filter() const
float stop_opacity() const
Overflow overflow() const
const BaselineShift & baseline_shift() const
PointerEvents pointer_events() const
float font_size() const
const Length & letter_spacing() const
const Paint & stroke() const
MaskType mask_type() const
FillRule fill_rule() const
Visibility visibility() const
const std::string & font_family() const
MixBlendMode mix_blend_mode() const
const SVGLayoutState * parent() const
const SVGElement * element() const
const std::string & clip_path() const
float fill_opacity() const
const Length & stroke_width() const
const Color & color() const
const std::string & mask() const
const Paint & fill() const
WhiteSpace white_space() const
Direction direction() const
LineCap stroke_linecap() const
const LengthList & stroke_dasharray() const
const std::string & marker_mid() const
Display display() const
const Length & word_spacing() const
WritingMode writing_mode() const
FillRule clip_rule() const
const Color & stop_color() const
float stroke_opacity() const
const std::string & marker_end() const
const Length & stroke_dashoffset() const
TextOrientation text_orientation() const
FontWeight font_weight() const
bool parse(std::string_view input) final
LengthNegativeMode negativeMode() const
Definition svgelement.h:446
const LengthList & values() const
Definition svgelement.h:447
SVGLengthList(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode)
Definition svgelement.h:439
LengthDirection direction() const
Definition svgelement.h:445
SVGLength(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode, float value=0, LengthUnits units=LengthUnits::None)
Definition svgelement.h:400
bool parse(std::string_view input) final
bool isPercent() const
Definition svgelement.h:407
LengthNegativeMode negativeMode() const
Definition svgelement.h:410
LengthDirection direction() const
Definition svgelement.h:409
const Length & value() const
Definition svgelement.h:411
SVGLineElement(Document *document)
Rect updateShape(Path &path) final
const SVGLength & y1() const
const SVGLength & x2() const
void setX1(const SVGLinearGradientElement *value)
void setY1(const SVGLinearGradientElement *value)
void setX2(const SVGLinearGradientElement *value)
const SVGLength & x1() const
const SVGLength & y2() const
void setY2(const SVGLinearGradientElement *value)
void setDefaultValues(const SVGLinearGradientElement *element)
const SVGLength & x1() const
const SVGLength & x2() const
const SVGLength & y1() const
bool applyPaint(SVGRenderState &state, float opacity) const final
const SVGLength & y2() const
SVGLinearGradientElement(Document *document)
Rect markerBoundingBox(const Point &origin, float angle, float strokeWidth) const
void renderMarker(SVGRenderState &state, const Point &origin, float angle, float strokeWidth) const
const SVGLength & refX() const
const SVGLength & markerWidth() const
const SVGLength & markerHeight() const
Transform localTransform() const final
SVGMarkerElement(Document *document)
const SVGEnumeration< MarkerUnits > & markerUnits() const
const SVGLength & refY() const
const SVGAngle & orient() const
Transform markerTransform(const Point &origin, float angle, float strokeWidth) const
Rect markerBoundingBox(float strokeWidth) const
SVGMarkerPosition(const SVGMarkerElement *element, const Point &origin, float angle)
void renderMarker(SVGRenderState &state, float strokeWidth) const
const SVGMarkerElement * element() const
const Point & origin() const
const SVGLength & y() const
const SVGEnumeration< Units > & maskUnits() const
void layoutElement(const SVGLayoutState &state) final
Rect maskRect(const SVGElement *element) const
const SVGEnumeration< Units > & maskContentUnits() const
const SVGLength & width() const
void applyMask(SVGRenderState &state) const
SVGMaskElement(Document *document)
Rect maskBoundingBox(const SVGElement *element) const
const SVGLength & height() const
const SVGLength & x() const
virtual bool isTextPositioningElement() const
Definition svgelement.h:697
SVGElement * parentElement() const
Definition svgelement.h:702
virtual bool isPaintElement() const
Definition svgelement.h:694
Document * document() const
Definition svgelement.h:699
bool isRootElement() const
Definition svgelement.h:705
SVGRootElement * rootElement() const
Definition svgelement.h:700
virtual bool isTextNode() const
Definition svgelement.h:692
virtual bool isElement() const
Definition svgelement.h:693
virtual bool isGeometryElement() const
Definition svgelement.h:696
void setParentElement(SVGElement *parent)
Definition svgelement.h:703
virtual bool isGraphicsElement() const
Definition svgelement.h:695
SVGNode(Document *document)
Definition svgelement.h:687
virtual std::unique_ptr< SVGNode > clone(bool deep) const =0
virtual ~SVGNode()=default
bool parse(std::string_view input) final
const NumberList & values() const
Definition svgelement.h:513
SVGNumberList(PropertyID id)
Definition svgelement.h:509
bool parse(std::string_view input) final
SVGNumberPercentage(PropertyID id, float value)
Definition svgelement.h:493
bool parse(std::string_view input) override
SVGNumber(PropertyID id, float value)
Definition svgelement.h:479
float value() const
Definition svgelement.h:484
virtual bool applyPaint(SVGRenderState &state, float opacity) const =0
bool isPaintElement() const final
SVGPaintElement(Document *document, ElementID id)
bool isRenderable() const
Definition svgelement.h:997
bool applyPaint(SVGRenderState &state) const
const SVGPaintElement * element() const
Definition svgelement.h:999
SVGPaintServer(const SVGPaintElement *element, const Color &color, float opacity)
Definition svgelement.h:993
const Color & color() const
Rect updateShape(Path &path) final
SVGPathElement(Document *document)
bool parse(std::string_view input) final
SVGPath(PropertyID id)
Definition svgelement.h:522
const Path & value() const
Definition svgelement.h:526
const SVGPatternElement * patternContentElement() const
void setPatternTransform(const SVGPatternElement *value)
void setPatternContentUnits(const SVGPatternElement *value)
const Transform & patternTransform() const
void setY(const SVGPatternElement *value)
const SVGLength & y() const
void setPatternUnits(const SVGPatternElement *value)
const Rect & viewBox() const
const SVGPreserveAspectRatio & preserveAspectRatio() const
void setWidth(const SVGPatternElement *value)
void setViewBox(const SVGPatternElement *value)
void setPreserveAspectRatio(const SVGPatternElement *value)
const SVGLength & height() const
void setPatternContentElement(const SVGPatternElement *value)
bool hasPatternContentElement() const
const SVGLength & width() const
const SVGLength & x() const
void setHeight(const SVGPatternElement *value)
void setDefaultValues(const SVGPatternElement *element)
void setX(const SVGPatternElement *value)
bool applyPaint(SVGRenderState &state, float opacity) const final
const SVGTransform & patternTransform() const
SVGPatternElement(Document *document)
const SVGLength & x() const
const SVGEnumeration< Units > & patternUnits() const
const SVGLength & height() const
const SVGEnumeration< Units > & patternContentUnits() const
const SVGLength & y() const
const SVGLength & width() const
bool parse(std::string_view input) final
const PointList & values() const
Definition svgelement.h:554
SVGPointList(PropertyID id)
Definition svgelement.h:550
bool parse(std::string_view input) final
const Point & value() const
Definition svgelement.h:539
SVGPoint(PropertyID id)
Definition svgelement.h:535
Rect updateShape(Path &path) final
SVGPolyElement(Document *document, ElementID id)
void transformRect(Rect &dstRect, Rect &srcRect) const
Rect getClipRect(const Rect &viewBoxRect, const Size &viewportSize) const
Transform getTransform(const Rect &viewBoxRect, const Size &viewportSize) const
bool parse(std::string_view input) final
MeetOrSlice meetOrSlice() const
Definition svgelement.h:665
SVGPreserveAspectRatio(PropertyID id)
Definition svgelement.h:660
PropertyID id() const
Definition svgelement.h:152
virtual ~SVGProperty()=default
virtual bool parse(std::string_view input)=0
const SVGLength & r() const
void setFx(const SVGRadialGradientElement *value)
void setFy(const SVGRadialGradientElement *value)
const SVGLength & cx() const
const SVGLength & fx() const
void setCx(const SVGRadialGradientElement *value)
void setR(const SVGRadialGradientElement *value)
const SVGLength & fy() const
void setDefaultValues(const SVGRadialGradientElement *element)
void setCy(const SVGRadialGradientElement *value)
const SVGLength & cy() const
SVGRadialGradientElement(Document *document)
const SVGLength & r() const
bool applyPaint(SVGRenderState &state, float opacity) const final
const SVGLength & fy() const
const SVGLength & cx() const
const SVGLength & cy() const
const SVGLength & fx() const
SVGRectElement(Document *document)
Rect updateShape(Path &path) final
bool parse(std::string_view input) final
SVGRect(PropertyID id)
Definition svgelement.h:563
const Rect & value() const
Definition svgelement.h:568
SVGRenderState(const SVGElement *element, const SVGRenderState &parent, const Transform &localTransform)
const SVGElement * element() const
Rect paintBoundingBox() const
const SVGRenderMode mode() const
bool hasCycleReference(const SVGElement *element) const
const std::shared_ptr< Canvas > & canvas() const
Rect fillBoundingBox() const
void endGroup(const SVGBlendInfo &blendInfo)
SVGRenderState(const SVGElement *element, const SVGRenderState *parent, const Transform &currentTransform, SVGRenderMode mode, std::shared_ptr< Canvas > canvas)
Canvas * operator->() const
Canvas & operator*() const
const SVGRenderState * parent() const
const Transform & currentTransform() const
void beginGroup(const SVGBlendInfo &blendInfo)
float intrinsicHeight() const
const std::vector< std::string > & styleSheets() const
SVGElement * getElementById(std::string_view id) const
float intrinsicWidth() const
void layout(SVGLayoutState &state) final
void addElementById(const std::string &id, SVGElement *element)
SVGRootElement(Document *document)
SVGRootElement * layoutIfNeeded()
void addStyleSheet(std::string text)
const SVGLength & x() const
SVGSVGElement(Document *document)
const SVGLength & width() const
Transform localTransform() const override
const SVGLength & height() const
void render(SVGRenderState &state) const override
const SVGLength & y() const
SVGStopElement(Document *document)
const SVGNumberPercentage & offset() const
void layoutElement(const SVGLayoutState &state) final
GradientStop gradientStop(float opacity) const
SVGString(PropertyID id)
Definition svgelement.h:164
bool parse(std::string_view input) final
const std::string & value() const
Definition svgelement.h:168
SVGStyleElement(Document *document)
SVGSwitchElement(Document *document)
void render(SVGRenderState &state) const final
SVGTSpanElement(Document *document)
void layout(SVGLayoutState &state) final
Rect strokeBoundingBox() const final
SVGTextElement(Document *document)
void render(SVGRenderState &state) const final
Rect fillBoundingBox() const final
void build(const SVGTextElement *textElement)
SVGTextFragmentsBuilder(std::u32string &text, SVGTextFragmentList &fragments)
bool isTextNode() const final
Definition svgelement.h:720
const std::string & data() const
Definition svgelement.h:722
std::unique_ptr< SVGNode > clone(bool deep) const final
SVGTextNode(Document *document)
void setData(const std::string &data)
SVGTextPositioningElement(Document *document, ElementID id)
DominantBaseline dominant_baseline() const
const SVGPaintServer & fill() const
bool isTextPositioningElement() const final
const LengthList & dy() const
void layoutElement(const SVGLayoutState &state) override
AlignmentBaseline alignment_baseline() const
const LengthList & y() const
const SVGPaintServer & stroke() const
LengthAdjust lengthAdjust() const
const LengthList & dx() const
const SVGLength & textLength() const
const LengthList & x() const
const NumberList & rotate() const
SVGTransformBox(PropertyID id)
Definition svgelement.h:599
TransformBoxType value() const
Definition svgelement.h:603
bool parse(std::string_view input) final
SVGTransformOrigin(PropertyID id)
Definition svgelement.h:619
Point resolve(const Rect &box) const
Definition svgelement.h:624
bool parse(std::string_view input) final
bool parse(std::string_view input) final
const Transform & value() const
Definition svgelement.h:581
SVGTransform(PropertyID id)
Definition svgelement.h:577
const SVGString & href() const
Definition svgelement.h:982
const std::string & hrefString() const
Definition svgelement.h:983
SVGURIReference(SVGElement *element)
SVGElement * getTargetElement(const Document *document) const
void render(SVGRenderState &state) const final
Transform localTransform() const final
const SVGLength & x() const
const SVGLength & width() const
const SVGLength & y() const
SVGUseElement(Document *document)
const SVGLength & height() const
constexpr bool isEmpty() const
Definition canvas.h:125
bool parse(const char *data, size_t length)
Definition canvas.h:682
static const Transform Identity
Definition canvas.h:300
Transform & multiply(const Transform &transform)
Definition canvas.h:582
static Transform translated(float tx, float ty)
Definition canvas.h:715
float xScale() const
Definition canvas.h:672
static Transform scaled(float sx, float sy)
Definition canvas.h:701
Rect mapRect(const Rect &rect) const
Definition canvas.h:661
static Transform rotated(float angle, float cx, float cy)
Definition canvas.h:687
#define NOVASVG_MAX(a, b)
Definition color.h:11
#define NOVASVG_MIN(a, b)
Definition color.h:14
NOVASVG_INLINE unsigned int color_to_argb32(const color_t *color)
Converts a color to a 32-bit unsigned ARGB value.
Definition paint.h:84
struct novasvg::render::text_iterator text_iterator_t
Iterator for traversing code points in text data.
surface_t * surface_load_from_image_file(const char *filename)
Loads an image surface from a file.
Definition surface.h:7781
NOVASVG_INLINE codepoint_t text_iterator_next(text_iterator_t *it)
Retrieves the next code point and advances the iterator.
Definition font.h:5046
@ NOVASVG_TEXT_ENCODING_UTF8
UTF-8 encoding.
Definition render.h:750
NOVASVG_INLINE int color_parse(color_t *color, const char *data, int length)
Parses a color from a string using CSS color syntax.
Definition paint.h:144
NOVASVG_INLINE bool text_iterator_has_next(const text_iterator_t *it)
Checks if there are more code points to iterate.
Definition font.h:5041
struct novasvg::render::color color_t
Represents a color with red, green, blue, and alpha components.
NOVASVG_INLINE void text_iterator_init(text_iterator_t *it, const void *text, int length, text_encoding_t encoding)
Initializes a text iterator.
Definition font.h:5031
surface_t * surface_load_from_image_base64(const char *data, int length)
Loads an image surface from base64-encoded data.
Definition surface.h:7909
Main namespace for the NovaSVG library.
Definition bitmap.h:23
ForeignObjectMetrics measureForeignObjectContent(std::string_view rawHtml, const ForeignObjectFonts &fonts, float maxWidth=-1.f, unsigned char baseStyle=0)
measureForeignObjectContent() for text that may carry bold and italic: fonts supplies the face for ea...
constexpr bool IS_WS(int cc)
NOVASVG_INLINE LineCap parseLineCap(std::string_view input)
std::forward_list< Attribute > AttributeList
Definition svgelement.h:764
NOVASVG_INLINE FontWeight parseFontWeight(std::string_view input)
NOVASVG_INLINE FontStyle parseFontStyle(std::string_view input)
NOVASVG_INLINE LineJoin parseLineJoin(std::string_view input)
bool parseNumber(std::string_view &input, T &number)
bool switchChildPasses(const SVGElement *child)
NOVASVG_INLINE Display parseDisplay(std::string_view input)
LineJoin
Definition canvas.h:40
FontFaceCache * fontFaceCache()
Definition font.h:449
std::vector< SVGTextPosition > SVGTextPositionList
SVGTextFragmentList::iterator SVGTextFragmentIterator
std::forward_list< SVGProperty * > SVGPropertyList
Definition svgelement.h:807
constexpr bool skipDelimiter(std::string_view &input, char delimiter)
std::vector< SVGTextFragment > SVGTextFragmentList
std::vector< float > NumberList
Definition svgelement.h:505
std::vector< SVGMarkerPosition > SVGMarkerPositionList
NOVASVG_INLINE Bitmap loadImageResource(const std::string &href)
PropertyID propertyid(std::string_view name)
constexpr bool skipString(std::string_view &input, std::string_view value)
const SVGGeometryElement * toSVGGeometryElement(const SVGNode *node)
NOVASVG_INLINE WritingMode parseWritingMode(std::string_view input)
ElementID elementid(std::string_view name)
const SVGTextPositioningElement * toSVGTextPositioningElement(const SVGNode *node)
std::vector< Length > LengthList
Definition svgelement.h:435
constexpr bool skipOptionalSpacesOrComma(std::string_view &input)
NOVASVG_INLINE Length parseLengthOrNormal(std::string_view input)
NOVASVG_INLINE float calculateTextAnchorOffset(const SVGTextPositioningElement *element, float width)
LengthNegativeMode
Definition svgelement.h:376
constexpr bool IS_NUM(int cc)
NOVASVG_INLINE Visibility parseVisibility(std::string_view input)
std::list< std::unique_ptr< SVGNode > > SVGNodeList
Definition svgelement.h:806
NOVASVG_INLINE std::optional< Color > parseColorValue(std::string_view &input, const SVGLayoutState *state)
NOVASVG_INLINE float calculateTextChunkLength(SVGTextFragmentIterator begin, SVGTextFragmentIterator end, bool isVerticalText)
constexpr void stripLeadingAndTrailingSpaces(std::string_view &input)
NOVASVG_INLINE BaselineShift parseBaselineShift(std::string_view input)
NOVASVG_INLINE float parseFontSize(std::string_view input, const SVGLayoutState *state)
NOVASVG_INLINE Color parseColorOrNone(std::string_view input, const SVGLayoutState *state, const Color &defaultValue)
PropertyID csspropertyid(std::string_view name)
NOVASVG_INLINE void handleTextChunk(SVGTextFragmentIterator begin, SVGTextFragmentIterator end)
constexpr bool IS_ALPHA(int cc)
NOVASVG_INLINE FillRule parseFillRule(std::string_view input)
NOVASVG_INLINE bool parseUrlValue(std::string_view &input, std::string &value)
const SVGElement * toSVGElement(const SVGNode *node)
Definition svgelement.h:929
NOVASVG_INLINE Direction parseDirection(std::string_view input)
bool isDisallowedElement(const SVGElement *element)
NOVASVG_INLINE Length parseLength(std::string_view input, LengthNegativeMode mode, const Length &defaultValue)
NOVASVG_INLINE PointerEvents parsePointerEvents(std::string_view input)
NOVASVG_INLINE GradientStops buildGradientStops(const SVGGradientElement *element, float opacity)
NOVASVG_INLINE bool needsTextAnchorAdjustment(const SVGTextPositioningElement *element)
constexpr void stripLeadingSpaces(std::string_view &input)
NOVASVG_INLINE DominantBaseline parseDominantBaseline(std::string_view input)
NOVASVG_INLINE AlignmentBaseline parseAlignmentBaseline(std::string_view input)
NOVASVG_INLINE float parseNumberOrPercentage(std::string_view input, bool allowPercentage, float defaultValue)
NOVASVG_INLINE Paint parsePaint(std::string_view input, const SVGLayoutState *state, const Color &defaultValue)
std::map< size_t, SVGCharacterPosition > SVGCharacterPositions
NOVASVG_INLINE AlignmentBaseline resolveDominantBaseline(const SVGTextPositioningElement *element)
constexpr bool skipOptionalSpaces(std::string_view &input)
SpreadMethod
Definition canvas.h:51
const SVGTextNode * toSVGTextNode(const SVGNode *node)
ForeignObjectMetrics measureForeignObjectSegments(std::string_view rawHtml, const std::vector< ForeignObjectSegment > &segments, const ForeignObjectFonts &fonts, unsigned char baseStyle, float maxWidth)
Measures what ForeignObjectMetrics::height (etc.) will be for rawHtml painted with font,...
NOVASVG_INLINE LengthList parseDashArray(std::string_view input)
NOVASVG_INLINE Color parseColor(std::string_view input, const SVGLayoutState *state, const Color &defaultValue)
NOVASVG_INLINE TextAnchor parseTextAnchor(std::string_view input)
FillRule
Definition canvas.h:46
const std::string emptyString
std::vector< Point > PointList
Definition svgelement.h:546
LineCap
Definition canvas.h:34
NOVASVG_INLINE TextOrientation parseTextOrientation(std::string_view input)
NOVASVG_INLINE Overflow parseOverflow(std::string_view input)
NOVASVG_INLINE std::string parseUrl(std::string_view input)
NOVASVG_INLINE Font makeFont(const std::string &family, float size, bool bold, bool italic)
NOVASVG_INLINE Enum parseEnumValue(std::string_view input, const SVGEnumerationEntry< Enum >(&entries)[N], Enum defaultValue)
NOVASVG_INLINE WhiteSpace parseWhiteSpace(std::string_view input)
NOVASVG_INLINE MixBlendMode parseMixBlendMode(std::string_view input)
std::pair< Enum, std::string_view > SVGEnumerationEntry
Definition svgelement.h:322
NOVASVG_INLINE MaskType parseMaskType(std::string_view input)
NOVASVG_INLINE float calculateBaselineOffset(const SVGTextPositioningElement *element)
AlignmentBaseline
Definition svgelement.h:245
#define NOVASVG_INLINE
Definition novasvg.h:42
#define NOVASVG_PI
Definition render.h:102
#define NOVASVG_SQRT2
Definition render.h:105
The faces a <foreignObject> can paint with: its own font plus the bold / italic variants of it that ,...
const Font & get(unsigned char style) const
static constexpr unsigned char Bold
static constexpr unsigned char Italic
Metrics for how ForeignObjectSimple::render() will lay out and size a <foreignObject>'s content,...
float width
Widest line, per Font::measureText() – see this struct's own field-level note above measureForeignObj...
int lineCount
Number of lines wrapForeignObjectText() splits this content into.
float lineHeight
Per-line height (from the content's own inline line-height:, or font.height() * 1....
float height
Total height ForeignObjectSimple::render() will use for this content, in pixels.
std::optional< float > rotate
std::optional< float > x
std::optional< float > dx
std::optional< float > y
std::optional< float > dy
SVGTextFragment(const SVGTextPositioningElement *element)
const SVGTextPositioningElement * element
SVGTextPosition(const SVGNode *node, size_t startOffset, size_t endOffset)
Represents a color with red, green, blue, and alpha components.
Definition render.h:1019
Represents a gradient stop.
Definition render.h:1311
stroke_data_t stroke
Definition private.h:158
Iterator for traversing code points in text data.
Definition render.h:758
bool isPixels
unsigned char style
float raw
std::string text
UTF-8; ' ' marks a forced line break ( ,.