1#ifndef NOVASVG_SVGELEMENT_H
2#define NOVASVG_SVGELEMENT_H
8#include <unordered_map>
33using namespace render;
154 virtual bool parse(std::string_view input) = 0;
168 const std::string&
value()
const {
return m_value; }
169 bool parse(std::string_view input)
final;
184 const std::string&
id()
const {
return m_id; }
321template<
typename Enum>
324template<
typename Enum>
332 Enum
value()
const {
return m_value; }
333 bool parse(std::string_view input)
final;
336 template<
unsigned int N>
353 float value()
const {
return m_value; }
355 bool parse(std::string_view input)
final;
388 float value()
const {
return m_value; }
404 , m_value(
value, units)
412 bool parse(std::string_view input)
final;
423 : m_element(element), m_units(units)
448 bool parse(std::string_view input)
final;
484 float value()
const {
return m_value; }
485 bool parse(std::string_view input)
override;
498 float value()
const {
return m_value; }
499 bool parse(std::string_view input)
final;
514 bool parse(std::string_view input)
final;
527 bool parse(std::string_view input)
final;
540 bool parse(std::string_view input)
final;
555 bool parse(std::string_view input)
final;
565 , m_value(
Rect::Invalid)
569 bool parse(std::string_view input)
final;
582 bool parse(std::string_view input)
final;
605 bool parse(std::string_view input)
final;
609 bool m_specified =
false;
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);
630 bool parse(std::string_view input)
final;
635 bool m_xIsPercent =
true;
636 bool m_yIsPercent =
true;
637 bool m_specified =
false;
666 bool parse(std::string_view input)
final;
707 virtual std::unique_ptr<SVGNode>
clone(
bool deep)
const = 0;
722 const std::string&
data()
const {
return m_data; }
725 std::unique_ptr<SVGNode>
clone(
bool deep)
const final;
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;
756 const std::string&
value()
const {
return m_value; }
828 const std::string&
getAttribute(std::string_view name)
const;
829 bool setAttribute(std::string_view name,
const std::string& value);
874 std::unique_ptr<SVGNode>
clone(
bool deep)
const final;
876 virtual void build();
914 float m_opacity = 1.f;
917 float m_font_size = 12.f;
952 for(
const auto& child : m_children) {
954 element->transverse(callback);
1008 float m_opacity = 0.f;
1070 const std::vector<std::string>&
styleSheets()
const {
return m_styleSheets; }
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;
1172 static constexpr unsigned char Bold = 1;
1217 m_htmlTextColor =
text;
1218 m_htmlBackgroundColor = background;
1221 Rect fillBoundingBox() const final;
1222 Rect strokeBoundingBox() const final;
1232 std::
string m_fontFamily;
1233 unsigned char m_baseFontStyle = 0;
1235 std::
string m_rawContent;
1237 std::optional<
Color> m_htmlTextColor;
1238 std::optional<
Color> m_htmlBackgroundColor;
1285 Point refPoint()
const;
1286 Size markerSize()
const;
1288 Transform markerTransform(
const Point& origin,
float angle,
float strokeWidth)
const;
1289 Rect markerBoundingBox(
const Point& origin,
float angle,
float strokeWidth)
const;
1313 bool requiresMasking()
const;
1343 MaskType m_mask_type = MaskType::Luminance;
1355static float parseFirstFloat(std::string_view input,
float fallback)
1359 return std::strtof(std::string(input).c_str(),
nullptr);
1365 : m_sourceGraphic(sourceGraphic), m_lastResult(sourceGraphic), m_scale(scale)
1376 return Canvas::create(
float(m_sourceGraphic->x()),
float(m_sourceGraphic->y()),
float(m_sourceGraphic->width()),
float(m_sourceGraphic->height()));
1381 auto canvas = newCanvas();
1382 canvas->compositeOver(source);
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);
1404 auto it = m_namedResults.find(std::string(in));
1405 if(it != m_namedResults.end())
1407 return m_lastResult;
1415 m_namedResults.emplace(std::string(name),
canvas);
1416 m_lastResult = std::move(
canvas);
1419 const std::shared_ptr<Canvas>&
lastResult()
const {
return m_lastResult; }
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;
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);
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);
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);
1465 auto output = context.newCanvas();
1466 output->fillOpaqueWhite();
1467 output->tintToFloodColor(floodColor, floodOpacity);
1478 auto output = context.newCanvas();
1479 for(
const auto& child : children()) {
1481 if(element && element->id() == ElementID::FeMergeNode)
1482 output->compositeOver(*context.resolveInput(element->getAttribute(PropertyID::In)));
1495 auto input = context.resolveInput(getAttribute(PropertyID::In));
1496 auto input2 = context.resolveInput(getAttribute(PropertyID::In2));
1497 auto output = context.cloneCanvas(*input2);
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);
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;
1515 output->compositeWith(*input, mode);
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);
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);
1550 float pixelMargin()
const;
1570 : m_clipper(clipper), m_masker(masker), m_filter(filter), m_opacity(opacity), m_mixBlendMode(mixBlendMode)
1584 const float m_opacity;
1591 : m_element(element), m_parent(&parent), m_currentTransform(parent.currentTransform() * localTransform)
1592 , m_mode(parent.mode()), m_canvas(parent.
canvas())
1596 : m_element(element), m_parent(parent), m_currentTransform(currentTransform), m_mode(mode), m_canvas(std::move(
canvas))
1606 const std::shared_ptr<Canvas>&
canvas()
const {
return m_canvas; }
1611 bool hasCycleReference(
const SVGElement* element)
const;
1621 std::shared_ptr<Canvas> m_canvas;
1645 Color m_stop_color = Color::Black;
1646 float m_stop_opacity = 1.f;
1649class SVGGradientAttributes;
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; }
1694 const SVGGradientElement* m_spreadMethod{
nullptr};
1695 const SVGGradientElement* m_gradientUnits{
nullptr};
1696 const SVGGradientElement* m_gradientContentElement{
nullptr};
1699class SVGLinearGradientAttributes;
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; }
1749 const SVGLinearGradientElement* m_y1{
nullptr};
1750 const SVGLinearGradientElement* m_x2{
nullptr};
1751 const SVGLinearGradientElement* m_y2{
nullptr};
1754class SVGRadialGradientAttributes;
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(); }
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; }
1808 const SVGRadialGradientElement* m_cy{
nullptr};
1809 const SVGRadialGradientElement* m_r{
nullptr};
1810 const SVGRadialGradientElement* m_fx{
nullptr};
1811 const SVGRadialGradientElement* m_fy{
nullptr};
1814class SVGPatternAttributes;
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; }
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};
1909 : m_element(element), m_origin(origin), m_angle(angle)
1916 Rect markerBoundingBox(
float strokeWidth)
const;
1934 Rect strokeBoundingBox()
const override;
1937 bool isRenderable()
const {
return !m_path.isNull() && !isDisplayNone() && !isVisibilityHidden(); }
1951 Rect m_fillBoundingBox;
1952 StrokeData m_strokeData;
1958 FillRule m_fill_rule = FillRule::NonZero;
1959 FillRule m_clip_rule = FillRule::NonZero;
2041 std::optional<float>
x;
2042 std::optional<float>
y;
2052 : node(node), startOffset(startOffset), endOffset(endOffset)
2068 bool startsNewTextChunk =
false;
2088 std::u32string& m_text;
2092 size_t m_characterOffset = 0;
2132 float convertBaselineOffset(
const BaselineShift& baselineShift)
const;
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;
2152 TextAnchor m_text_anchor = TextAnchor::Start;
2153 WhiteSpace m_white_space = WhiteSpace::Default;
2154 WritingMode m_writing_mode = WritingMode::Horizontal;
2175 Rect boundingBox(
bool includeStroke)
const;
2177 std::u32string m_text;
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; }
2250 Paint m_fill{Color::Black};
2251 Paint m_stroke{Color::Transparent};
2253 Color m_color = Color::Black;
2254 Color m_stop_color = Color::Black;
2256 float m_opacity = 1.f;
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;
2264 Length m_letter_spacing{0.f, LengthUnits::None};
2265 Length m_word_spacing{0.f, LengthUnits::None};
2267 BaselineShift m_baseline_shift;
2268 Length m_stroke_width{1.f, LengthUnits::None};
2269 Length m_stroke_dashoffset{0.f, LengthUnits::None};
2272 LineCap m_stroke_linecap = LineCap::Butt;
2273 LineJoin m_stroke_linejoin = LineJoin::Miter;
2275 FillRule m_fill_rule = FillRule::NonZero;
2276 FillRule m_clip_rule = FillRule::NonZero;
2278 FontWeight m_font_weight = FontWeight::Normal;
2279 FontStyle m_font_style = FontStyle::Normal;
2284 TextAnchor m_text_anchor = TextAnchor::Start;
2285 WhiteSpace m_white_space = WhiteSpace::Default;
2286 WritingMode m_writing_mode = WritingMode::Horizontal;
2290 Display m_display = Display::Inline;
2291 Visibility m_visibility = Visibility::Visible;
2292 Overflow m_overflow = Overflow::Visible;
2294 MaskType m_mask_type = MaskType::Luminance;
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;
2310using namespace render;
2314 static const struct {
2315 std::string_view name;
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}
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)
2386 static const struct {
2387 std::string_view name;
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}
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;
2453 m_value.assign(input);
2466 return parseEnum(input, entries);
2477 return parseEnum(input, entries);
2488 return parseEnum(input, entries);
2499 return parseEnum(input, entries);
2502template<
typename Enum>
2503template<
unsigned int N>
2507 for(
const auto& entry : entries) {
2508 if(input == entry.second) {
2509 m_value = entry.first;
2520 if(input ==
"auto") {
2526 if(input ==
"auto-start-reverse") {
2535 if(!input.empty()) {
2538 else if(input ==
"grad")
2539 value *= 360.f / 400.f;
2540 else if(input ==
"turn")
2542 else if(input !=
"deg") {
2566 constexpr auto dpi = 96.f;
2567 switch(input.front()) {
2571 input.remove_prefix(1);
2574 input.remove_prefix(1);
2577 else if(input.front() ==
'x')
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;
2586 input.remove_prefix(1);
2589 input.remove_prefix(1);
2592 else if(input.front() ==
'n')
2593 m_value =
value * dpi;
2597 input.remove_prefix(1);
2600 input.remove_prefix(1);
2603 else if(input.front() ==
'm')
2604 m_value =
value * dpi / 2.54f;
2608 input.remove_prefix(1);
2611 input.remove_prefix(1);
2614 else if(input.front() ==
'm')
2615 m_value =
value * dpi / 25.4f;
2619 input.remove_prefix(1);
2622 input.remove_prefix(1);
2625 else if(input.front() ==
'm')
2627 else if(input.front() ==
'x')
2632 input.remove_prefix(1);
2638 return input.empty();
2645 return length.
value() * viewportDimension(direction) / 100.f;
2646 return length.
value() / 100.f;
2653 return length.
value();
2661 return viewportSize.
w;
2663 return viewportSize.h;
2665 return std::sqrt(viewportSize.w * viewportSize.w + viewportSize.h * viewportSize.h) /
NOVASVG_SQRT2;
2671 return m_value.
parse(input, m_negativeMode);
2677 while(!input.empty()) {
2679 while(count < input.length() && input[count] !=
',' && !
IS_WS(input[count]))
2684 if(!value.
parse(input.substr(0, count), m_negativeMode))
2686 input.remove_prefix(count);
2688 m_values.push_back(value);
2712 if(!input.empty() && input.front() ==
'%') {
2714 input.remove_prefix(1);
2719 m_value = std::clamp(
value, 0.f, 1.f);
2727 while(!input.empty()) {
2732 m_values.push_back(value);
2740 return m_value.
parse(input.data(), input.length());
2750 || !input.empty()) {
2762 while(!input.empty()) {
2770 m_values.push_back(value);
2788 || !input.empty()) {
2800 return m_value.
parse(input.data(), input.length());
2806 if(input ==
"fill-box" || input ==
"content-box") {
2808 }
else if(input ==
"view-box" || input ==
"border-box" || input ==
"stroke-box") {
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; }
2829 if(!token.empty() && token.front() ==
'%') {
2830 value = number / 100.f;
2832 token.remove_prefix(1);
2837 return token.empty();
2840 std::vector<std::string_view> tokens;
2842 while(!input.empty()) {
2844 while(count < input.length() && !
IS_WS(input[count]))
2846 tokens.push_back(input.substr(0, count));
2847 input.remove_prefix(count);
2851 if(tokens.empty() || tokens.size() > 2)
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))
2860 }
else if(!parseComponent(tokens[0], x, xIsPercent)) {
2864 if(!parseComponent(tokens[0], x, xIsPercent) || !parseComponent(tokens[1], y, yIsPercent))
2870 m_xIsPercent = xIsPercent;
2871 m_yIsPercent = yIsPercent;
2925 auto xScale = viewportSize.
w / viewBoxRect.
w;
2926 auto yScale = viewportSize.
h / viewBoxRect.
h;
2928 return Rect(viewBoxRect.
x, viewBoxRect.
y, viewportSize.
w / xScale, viewportSize.
h / 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;
2945 xOffset += (viewportSize.
w - viewWidth);
2951 switch(m_alignType) {
2955 yOffset += (viewportSize.
h - viewHeight) * 0.5f;
2960 yOffset += (viewportSize.
h - viewHeight);
2966 return Rect(-xOffset / scale, -yOffset / scale, viewportSize.
w / scale, viewportSize.
h / scale);
2972 auto xScale = viewportSize.
w / viewBoxRect.
w;
2973 auto yScale = viewportSize.
h / viewBoxRect.
h;
2975 return Transform(xScale, 0, 0, yScale, -viewBoxRect.
x * xScale, -viewBoxRect.
y * 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;
2992 xOffset += (viewportSize.
w - viewWidth);
2998 switch(m_alignType) {
3002 yOffset += (viewportSize.
h - viewHeight) * 0.5f;
3007 yOffset += (viewportSize.
h - viewHeight);
3013 return Transform(scale, 0, 0, scale, xOffset, yOffset);
3020 auto viewSize = dstRect.
size();
3021 auto imageSize = srcRect.
size();
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;
3035 dstRect.
y += viewSize.h - dstRect.
h;
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;
3053 dstRect.
x += viewSize.w - dstRect.
w;
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;
3072 srcRect.
y += imageSize.h - srcRect.
h;
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;
3090 srcRect.
x += imageSize.w - srcRect.
w;
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())
3116 auto current =
this;
3118 if(
element == current->element())
3120 current = current->parent();
3128 if(requiresCompositing) {
3133 boundingBox.intersect(m_canvas->extents());
3134 m_canvas = Canvas::create(boundingBox);
3139 if(!requiresCompositing && blendInfo.
clipper()) {
3146 if(m_canvas == m_parent->
canvas()) {
3147 m_canvas->restore();
3162 BlendMode blendMode = BlendMode::Src_Over;
3175 m_parent->m_canvas->blendCanvas(*m_canvas, blendMode, opacity);
3194 m_stop_color =
state.stop_color();
3195 m_stop_opacity =
state.stop_opacity();
3229 if(!
attributes.hasGradientContentElement()) {
3230 for(
const auto& child :
children()) {
3255 std::set<const SVGGradientElement*> processedGradients;
3275 processedGradients.insert(current);
3277 if(processedGradients.count(current) > 0) {
3288 GradientStops gradientStops;
3290 const auto& children = element->
children();
3291 gradientStops.reserve(children.size());
3292 for(
const auto& child : children) {
3296 gradientStops.push_back(stopElement->gradientStop(opacity));
3300 return gradientStops;
3305 auto attributes = collectGradientAttributes();
3306 auto gradientContentElement =
attributes.gradientContentElement();
3308 if(gradientStops.empty())
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);
3325 auto bbox =
state.fillBoundingBox();
3350 auto attributes = collectGradientAttributes();
3351 auto gradientContentElement =
attributes.gradientContentElement();
3353 if(gradientStops.empty())
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);
3372 auto bbox =
state.fillBoundingBox();
3383 std::set<const SVGGradientElement*> processedGradients;
3405 processedGradients.insert(current);
3407 if(processedGradients.count(current) > 0) {
3439 if(
state.hasCycleReference(
this))
3441 auto attributes = collectPatternAttributes();
3442 auto patternContentElement =
attributes.patternContentElement();
3443 if(patternContentElement ==
nullptr)
3461 auto currentTransform =
attributes.patternTransform() *
state.currentTransform();
3462 auto xScale = currentTransform.xScale();
3463 auto yScale = currentTransform.yScale();
3478 patternContentElement->renderChildren(
newState);
3513 if(
child->isElement()) {
3535using namespace render;
3556 float capLimit = m_strokeData.lineWidth() / 2.f;
3559 float joinLimit = m_strokeData.lineWidth() / 2.f;
3561 joinLimit *= m_strokeData.miterLimit();
3567 for(
const auto& markerPosition : m_markerPositions)
3574 m_fill_rule =
state.fill_rule();
3575 m_clip_rule =
state.clip_rule();
3582 m_markerPositions.clear();
3594 if(markerStart ==
nullptr && markerMid ==
nullptr && markerEnd ==
nullptr) {
3600 Point inslopePoints[2];
3601 Point outslopePoints[2];
3604 std::array<Point, 3> points;
3609 startPoint = points[0];
3610 inslopePoints[0] = origin;
3611 inslopePoints[1] = points[0];
3615 inslopePoints[0] = origin;
3616 inslopePoints[1] = points[0];
3620 inslopePoints[0] = points[1];
3621 inslopePoints[1] = points[2];
3625 inslopePoints[0] = origin;
3626 inslopePoints[1] = points[0];
3627 origin = startPoint;
3628 startPoint =
Point();
3634 if(!it.
isDone() && (markerStart || markerMid)) {
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();
3644 positions.emplace_back(markerStart, origin, angle);
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)
3654 auto angle = (inangle + outangle) * 0.5f;
3655 positions.emplace_back(markerMid, origin, angle);
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);
3686 for(
const auto& markerPosition : m_markerPositions) {
3687 markerPosition.renderMarker(newState, m_strokeData.lineWidth());
3715 path.moveTo(x1, y1);
3716 path.lineTo(x2, y2);
3717 return Rect(x1, y1, x2 - x1, y2 - y1);
3742 if(width <= 0.f || height <= 0.f) {
3752 if(rx <= 0.f) rx = ry;
3753 if(ry <= 0.f) ry = rx;
3758 path.addRoundRect(x, y, width, height, rx, ry);
3759 return Rect(x, y, width, height);
3780 if(rx <= 0.f || ry <= 0.f) {
3786 path.addEllipse(cx, cy, rx, ry);
3787 return Rect(cx - rx, cy - ry, rx + rx, ry + ry);
3811 path.addEllipse(cx, cy, r, r);
3812 return Rect(cx - r, cy - r, r + r, r + r);
3824 const auto& points = m_points.
values();
3825 if(points.empty()) {
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);
3836 return path.boundingRect();
3849 return path.boundingRect();
3853using namespace render;
3905 while(parent->isTextPositioningElement()) {
3907 parent = parent->parentElement();
3915 const auto& font = element->
font();
3922 offset -= font.xHeight() / 2.f;
3925 offset -= (font.ascent() + font.descent()) / 2.f;
3930 offset -= font.descent();
3933 offset -= font.ascent() * 8.f / 10.f;
3936 offset -= font.ascent() / 2.f;
3971 return -width / 2.f;
3987 float chunkLength = 0;
3989 for(
auto it = begin; it != end; ++it) {
3991 chunkLength += isVerticalText ? fragment.
height : fragment.
width;
3993 lastFragment = &fragment;
3997 if(isVerticalText) {
3998 chunkLength += fragment.
y - (lastFragment->
y + lastFragment->
height);
4000 chunkLength += fragment.
x - (lastFragment->
x + lastFragment->
width);
4003 lastFragment = &fragment;
4017 if(textLength > 0.f && chunkLength > 0.f) {
4018 size_t numCharacters = 0;
4019 for(
auto it = begin; it != end; ++it) {
4021 numCharacters += fragment.
length;
4025 auto textLengthScale = textLength / chunkLength;
4027 if(isVerticalText) {
4028 lengthAdjustTransform.scale(1.f, textLengthScale);
4030 lengthAdjustTransform.scale(textLengthScale, 1.f);
4033 lengthAdjustTransform.translate(-firstFragment.
x, -firstFragment.
y);
4034 for(
auto it = begin; it != end; ++it) {
4038 }
else if(numCharacters > 1) {
4040 size_t characterOffset = 0;
4041 auto textLengthShift = (textLength - chunkLength) / (numCharacters - 1);
4042 for(
auto it = begin; it != end; ++it) {
4044 if(isVerticalText) {
4045 fragment.
y += textLengthShift * characterOffset;
4047 fragment.
x += textLengthShift * characterOffset;
4050 characterOffset += fragment.
length;
4059 for(
auto it = begin; it != end; ++it) {
4061 if(isVerticalText) {
4062 fragment.
y += chunkOffset;
4064 fragment.
x += chunkOffset;
4071 : m_text(
text), m_fragments(fragments)
4074 m_fragments.clear();
4079 handleElement(textElement);
4080 for(
const auto& position : m_textPositions) {
4081 fillCharacterPositions(position);
4084 std::u32string_view wholeText(m_text);
4085 for(
const auto& textPosition : m_textPositions) {
4086 if(!textPosition.node->isTextNode())
4089 const auto isVerticalText = element->isVerticalWritingMode();
4090 const auto isUprightText = element->isUprightTextOrientation();
4091 const auto& font = element->font();
4094 auto recordTextFragment = [&](
auto startOffset,
auto endOffset) {
4095 auto text = wholeText.substr(startOffset, endOffset - startOffset);
4096 fragment.
offset = startOffset;
4098 fragment.
width = font.measureText(
text);
4099 fragment.
height = font.height() + font.lineGap();
4100 if(isVerticalText) {
4101 m_y += isUprightText ? fragment.
height : fragment.
width;
4103 m_x += fragment.
width;
4106 m_fragments.push_back(fragment);
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) {
4119 if(
auto it = m_characterPositions.find(m_characterOffset); it != m_characterPositions.end()) {
4120 characterPosition = it->second;
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);
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;
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) {
4145 fragment.
y += font.height();
4147 fragment.
angle += 90.f;
4152 didStartTextFragment =
true;
4155 auto spacing = element->letter_spacing();
4156 if(currentCharacter && lastCharacter && element->word_spacing()) {
4157 if(currentCharacter ==
' ' && lastCharacter !=
' ') {
4158 spacing += element->word_spacing();
4163 applySpacingToNextCharacter =
true;
4164 if(isVerticalText) {
4172 lastCharacter = currentCharacter;
4174 ++m_characterOffset;
4177 recordTextFragment(startOffset, textOffset);
4180 if(m_fragments.empty())
4182 auto it = m_fragments.begin();
4183 auto begin = m_fragments.begin();
4184 auto end = m_fragments.end();
4185 for(++it; it != end; ++it) {
4202 const auto startOffset = m_text.length();
4203 uint32_t lastCharacter =
' ';
4204 if(!m_text.empty()) {
4205 lastCharacter = m_text.back();
4212 if(currentCharacter ==
'\t' || currentCharacter ==
'\n' || currentCharacter ==
'\r')
4213 currentCharacter =
' ';
4214 if(currentCharacter ==
' ' && lastCharacter ==
' ' && element->white_space() ==
WhiteSpace::Default)
4216 m_text.push_back(currentCharacter);
4217 lastCharacter = currentCharacter;
4220 if(startOffset < m_text.length()) {
4221 m_textPositions.emplace_back(node, startOffset, m_text.length());
4225NOVASVG_INLINE void SVGTextFragmentsBuilder::handleElement(
const SVGTextPositioningElement* element)
4227 if(element->isDisplayNone())
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()) {
4234 }
else if(child->isTextPositioningElement()) {
4239 auto& position = m_textPositions[itemIndex];
4240 assert(position.node == element);
4241 position.endOffset = m_text.length();
4244NOVASVG_INLINE void SVGTextFragmentsBuilder::fillCharacterPositions(
const SVGTextPosition& position)
4246 if(!position.node->isTextPositioningElement())
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();
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) {
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)
4270 auto& characterPosition = m_characterPositions[offset];
4271 if(index < xListSize)
4273 if(index < yListSize)
4275 if(index < dxListSize)
4277 if(index < dyListSize)
4279 if(index < rotateListSize) {
4280 characterPosition.rotate = rotateList[index];
4281 lastRotation = characterPosition.rotate;
4285 if(lastRotation == std::nullopt)
4287 auto offset = position.startOffset + rotateList.size();
4288 while(offset < position.endOffset) {
4289 m_characterPositions[offset++].rotate = lastRotation;
4314 m_font =
state.font();
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();
4339 return -m_font.
height() / 2.f;
4341 return m_font.
height() / 2.f;
4344 const auto& length = baselineShift.
length();
4346 return length.
value() * m_font.
size() / 100.f;
4348 return length.value() * m_font.
size() / 2.f;
4350 return length.value() * m_font.
size();
4351 return length.value();
4381 std::u32string_view wholeText(m_text);
4382 for(
const auto& fragment : m_fragments) {
4383 if(fragment.element->isVisibilityHidden())
4386 auto text = wholeText.substr(fragment.offset, fragment.length);
4387 auto origin =
Point(fragment.x, fragment.y);
4389 const auto&
font = fragment.element->font();
4410 for(
const auto& fragment : m_fragments) {
4411 const auto&
font = fragment.element->font();
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);
4416 fragmentRect.inflate(fragment.element->stroke_width() / 2.f);
4417 boundingBox.
unite(fragmentTranform.mapRect(fragmentRect));
4426using namespace render;
4437 return std::nullopt;
4438 input.remove_prefix(length);
4443 return Color((argb >> 16) & 0xff, (argb >> 8) & 0xff, argb & 0xff, (argb >> 24) & 0xff);
4449 if(!
color || !input.empty())
4450 color = defaultValue;
4451 return color.value();
4456 if(input.compare(
"none") == 0)
4470 switch(input.front()) {
4473 auto delim = input.front();
4474 input.remove_prefix(1);
4478 while(!input.empty() && input.front() != delim) {
4479 value += input.front();
4480 input.remove_prefix(1);
4488 input.remove_prefix(1);
4489 while(!input.empty() && input.front() !=
')') {
4490 value += input.front();
4491 input.remove_prefix(1);
4524 return defaultValue;
4525 if(allowPercentage) {
4528 value = std::clamp(value, 0.f, 1.f);
4532 return defaultValue;
4539 if(!value.
parse(input, mode))
4540 value = defaultValue;
4546 if(input.compare(
"baseline") == 0)
4548 if(input.compare(
"sub") == 0)
4550 if(input.compare(
"super") == 0)
4557 if(input.compare(
"none") == 0)
4562 while(count < input.length() && input[count] !=
',' && !
IS_WS(input[count]))
4567 input.remove_prefix(count);
4568 values.push_back(std::move(value));
4575 if(input.compare(
"normal") == 0)
4589 return length.value();
4592template<
typename Enum,
unsigned int N>
4595 for(
const auto& entry : entries) {
4596 if(input == entry.second) {
4601 return defaultValue;
4802 std::string keyword;
4803 for(
char ch : input)
4804 keyword.push_back(
char(std::tolower(
static_cast<unsigned char>(ch))));
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())
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())
4886 std::string_view input(attribute.value());
4888 if(input.empty() || input.compare(
"inherit") == 0)
4890 switch(attribute.id()) {
5015 m_font_family.assign(input);
5037 return Font(face, size);
5046using namespace render;
5050 static const struct {
5051 std::string_view name;
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)
5104 m_data.assign(
data);
5109 auto node = std::make_unique<SVGTextNode>(
document());
5120 return std::make_unique<SVGSVGElement>(
document);
5122 return std::make_unique<SVGPathElement>(
document);
5124 return std::make_unique<SVGGElement>(
document);
5126 return std::make_unique<SVGSwitchElement>(
document);
5128 return std::make_unique<SVGRectElement>(
document);
5130 return std::make_unique<SVGCircleElement>(
document);
5132 return std::make_unique<SVGEllipseElement>(
document);
5134 return std::make_unique<SVGLineElement>(
document);
5136 return std::make_unique<SVGDefsElement>(
document);
5139 return std::make_unique<SVGPolyElement>(
document,
id);
5141 return std::make_unique<SVGStopElement>(
document);
5143 return std::make_unique<SVGLinearGradientElement>(
document);
5145 return std::make_unique<SVGRadialGradientElement>(
document);
5147 return std::make_unique<SVGSymbolElement>(
document);
5149 return std::make_unique<SVGUseElement>(
document);
5151 return std::make_unique<SVGPatternElement>(
document);
5153 return std::make_unique<SVGMaskElement>(
document);
5155 return std::make_unique<SVGClipPathElement>(
document);
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);
5165 return std::make_unique<SVGFEFloodElement>(
document);
5167 return std::make_unique<SVGFECompositeElement>(
document);
5169 return std::make_unique<SVGFEMergeElement>(
document);
5171 return std::make_unique<SVGElement>(
document,
id);
5173 return std::make_unique<SVGMarkerElement>(
document);
5175 return std::make_unique<SVGImageElement>(
document);
5177 return std::make_unique<SVGForeignObjectElement>(
document);
5179 return std::make_unique<SVGStyleElement>(
document);
5181 return std::make_unique<SVGTextElement>(
document);
5183 return std::make_unique<SVGTSpanElement>(
document);
5223 for(
const auto& attribute : m_attributes) {
5224 if(
id == attribute.id()) {
5234 for(
const auto& attribute : m_attributes) {
5235 if(
id == attribute.id()) {
5245 for(
const auto& attribute : m_attributes) {
5246 if(
id == attribute.id()) {
5247 return attribute.
value();
5256 for(
auto& attribute : m_attributes) {
5257 if(
id == attribute.id()) {
5258 if(specificity < attribute.specificity())
5261 attribute =
Attribute(specificity,
id, value);
5267 m_attributes.emplace_front(specificity,
id, value);
5287 property->parse(value);
5294 if(parent ==
nullptr)
5296 const auto&
children = parent->children();
5300 for(; it != end; ++it) {
5315 if(parent ==
nullptr)
5317 const auto&
children = parent->children();
5321 for(; it != end; ++it) {
5336 m_children.push_back(std::move(child));
5337 return &*m_children.back();
5342 if(m_children.empty())
5344 return &*m_children.front();
5349 if(m_children.empty())
5351 return &*m_children.back();
5357 for(
const auto& child : m_children) {
5371 for(
const auto& child : m_children) {
5384 if(m_paintBoundingBox.
isValid())
5385 return m_paintBoundingBox;
5388 assert(m_paintBoundingBox.
isValid());
5391 return m_paintBoundingBox;
5429 if(element && element->isPaintElement())
5436 auto it = m_children.rbegin();
5437 auto end = m_children.rend();
5438 for(; it != end; ++it) {
5440 if(child && !child->isHiddenElement()) {
5441 if(
auto element = child->elementFromPoint(x, y)) {
5450 transform.postMultiply(parent->localTransform());
5452 if(bbox.contains(x, y)) {
5462 m_properties.push_front(&value);
5467 for(
auto property : m_properties) {
5468 if(
id == property->id()) {
5479 if(parent ==
nullptr) {
5481 const auto& viewBox = element->
viewBox();
5482 if(viewBox.value().isValid())
5484 return Size(300, 150);
5489 const auto& viewBox = element->
viewBox();
5490 if(viewBox.value().isValid())
5495 return Size(width, height);
5498 return parent->currentViewportSize();
5503 for(
const auto& child : m_children) {
5511 element->setAttributes(m_attributes);
5518 for(
const auto& child : m_children) {
5532 m_mix_blend_mode =
state.mix_blend_mode();
5535 m_display =
state.display();
5536 m_overflow =
state.overflow();
5537 m_visibility =
state.visibility();
5538 m_pointer_events =
state.pointer_events();
5543 for(
const auto& child : m_children) {
5545 element->layout(
state);
5559 for(
const auto& child : m_children) {
5561 element->render(
state);
5603 && m_opacity != 0.f) {
5639 const auto& viewBoxRect = m_viewBox.
value();
5640 if(viewBoxRect.isEmpty() || viewportSize.
isEmpty())
5642 return m_preserveAspectRatio.
getTransform(viewBoxRect, viewportSize);
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);
5661 std::string_view value(m_href.
value());
5662 if(value.empty() || value.front() !=
'#')
5695 return m_transform.
value();
5702 box =
Rect(0.f, 0.f, size.w, size.h);
5706 return m_transform.
value();
5708 auto origin = m_transformOrigin.
resolve(box);
5725 strokeData.setMiterLimit(
state.stroke_miterlimit());
5726 strokeData.setLineCap(
state.stroke_linecap());
5727 strokeData.setLineJoin(
state.stroke_linejoin());
5730 DashArray dashArray;
5731 for(
const auto& dash :
state.stroke_dasharray())
5733 strokeData.setDashArray(std::move(dashArray));
5801 auto it = m_idCache.find(
id);
5802 if(
it == m_idCache.end())
5809 m_idCache.emplace(
id, element);
5817 if(!
width().isPercent()) {
5820 m_intrinsicWidth = 0.f;
5823 if(!
height().isPercent()) {
5826 m_intrinsicHeight = 0.f;
5830 if(!
viewBoxRect.isEmpty() && (!m_intrinsicWidth || !m_intrinsicHeight)) {
5832 if(!m_intrinsicWidth && m_intrinsicHeight)
5834 else if(m_intrinsicWidth && !m_intrinsicHeight) {
5839 if(
viewBoxRect.isValid() && (!m_intrinsicWidth || !m_intrinsicHeight)) {
5844 if(!m_intrinsicWidth || !m_intrinsicHeight) {
5846 if(!m_intrinsicWidth)
5847 m_intrinsicWidth = boundingBox.right();
5848 if(!m_intrinsicHeight) {
5849 m_intrinsicHeight = boundingBox.bottom();
5909 switch(element->
id()) {
5932NOVASVG_INLINE std::unique_ptr<SVGElement> SVGUseElement::cloneTargetElement(SVGElement* targetElement)
5942 parent = parent->parentElement();
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)
6022 input.remove_prefix(index + 1);
6077 if(
style == m_baseFontStyle) {
6090 m_font =
state.font();
6091 m_fontFamily =
state.font_family();
6118inline std::u32string utf8ToU32(
const std::string&
text)
6120 std::u32string result;
6121 result.reserve(
text.size());
6138inline std::optional<std::string_view> findDeclarationValue(std::string_view block, std::string_view property)
6141 while((pos = block.find(property, pos)) != std::string_view::npos) {
6142 auto matchStart = pos;
6143 auto after = pos +
property.size();
6145 if(matchStart > 0) {
6146 auto prev = block[matchStart - 1];
6150 if(after < block.size() && (
IS_ALPHA(block[after]) ||
IS_NUM(block[after]) || block[after] ==
'-'))
6153 while(after < block.size() &&
IS_WS(block[after]))
6155 if(after >= block.size() || block[after] !=
':')
6158 while(after < block.size() &&
IS_WS(block[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);
6168 return std::nullopt;
6178inline std::optional<std::string_view> findRawDeclarationValue(std::string_view block, std::string_view property)
6181 while((pos = block.find(property, pos)) != std::string_view::npos) {
6182 auto matchStart = pos;
6183 auto after = pos +
property.size();
6185 if(matchStart > 0) {
6186 auto prev = block[matchStart - 1];
6190 if(after < block.size() && (
IS_ALPHA(block[after]) ||
IS_NUM(block[after]) || block[after] ==
'-'))
6193 while(after < block.size() &&
IS_WS(block[after]))
6195 if(after >= block.size() || block[after] !=
':')
6198 while(after < block.size() &&
IS_WS(block[after]))
6201 auto valueStart = after;
6204 while(end < block.size()) {
6205 auto ch = block[end];
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;
6220 }
else if(ch ==
')') {
6223 }
else if((ch ==
';' || ch ==
'}') && parenDepth == 0) {
6229 auto value = block.substr(valueStart, end - valueStart);
6230 while(!value.empty() &&
IS_WS(value.back()))
6231 value.remove_suffix(1);
6236 return std::nullopt;
6240inline std::optional<Color> parseCssColor(std::string_view value)
6243 int length =
color_parse(&color, value.data(), (
int)value.length());
6245 return std::nullopt;
6247 return Color((argb >> 16) & 0xff, (argb >> 8) & 0xff, argb & 0xff, (argb >> 24) & 0xff);
6255struct NumericDeclaration {
6260inline std::optional<NumericDeclaration> parseNumericDeclaration(std::string_view block, std::string_view property)
6262 auto value = findDeclarationValue(block, property);
6264 return std::nullopt;
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;
6272 auto unit = valueStr.c_str() + (end - valueStr.c_str());
6275 return NumericDeclaration{number, std::string_view(unit) ==
"px"};
6278inline std::optional<std::string_view> htmlAttribute(std::string_view tag, std::string_view name)
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];
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);
6310struct ForeignObjectSegment {
6315inline bool foreignObjectIsVoidTag(
const std::string& tag)
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;
6325inline bool foreignObjectIsInlineTag(
const std::string& tag)
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",
6331 return tags.count(tag) > 0;
6335inline std::string_view foreignObjectStyleAttribute(std::string_view attributes)
6338 while(pos < attributes.size()) {
6339 while(pos < attributes.size() && (std::isspace(
static_cast<unsigned char>(attributes[pos])) || attributes[pos] ==
'/'))
6341 auto nameStart = pos;
6342 while(pos < attributes.size() && !std::isspace(
static_cast<unsigned char>(attributes[pos])) && attributes[pos] !=
'=' && attributes[pos] !=
'/')
6344 auto name = attributes.substr(nameStart, pos - nameStart);
6345 while(pos < attributes.size() && std::isspace(
static_cast<unsigned char>(attributes[pos])))
6347 std::string_view value;
6348 if(pos < attributes.size() && attributes[pos] ==
'=') {
6350 while(pos < attributes.size() && std::isspace(
static_cast<unsigned char>(attributes[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;
6358 auto valueStart = pos;
6359 while(pos < attributes.size() && !std::isspace(
static_cast<unsigned char>(attributes[pos])))
6361 value = attributes.substr(valueStart, pos - valueStart);
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')
6371inline std::string foreignObjectDeclaration(std::string_view block, std::string_view property)
6373 auto value = findDeclarationValue(block, property);
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)
6381 while(!result.empty() && std::isspace(
static_cast<unsigned char>(result.back())))
6387inline unsigned char foreignObjectStyleOfOpening(
const std::string& tag, std::string_view attributes,
unsigned char inherited)
6389 unsigned char style = inherited;
6390 if(tag ==
"b" || tag ==
"strong")
6392 else if(tag ==
"i" || tag ==
"em" || tag ==
"cite" || tag ==
"var" || tag ==
"dfn")
6395 auto inlineStyle = foreignObjectStyleAttribute(attributes);
6396 if(!inlineStyle.empty()) {
6397 if(
auto weight = foreignObjectDeclaration(inlineStyle,
"font-weight"); !weight.empty()) {
6402 if(
auto fontStyle = foreignObjectDeclaration(inlineStyle,
"font-style"); !fontStyle.empty()) {
6411inline std::vector<ForeignObjectSegment> foreignObjectStyledText(std::string_view html,
unsigned char baseStyle = 0)
6420 static const std::set<std::string_view> block_tags = {
"br",
"p",
"div",
"tr",
"li"};
6422 std::vector<ForeignObjectSegment>
raw;
6423 auto put = [&](
char c,
unsigned char style) {
6425 raw.push_back({std::string(),
style});
6426 raw.back().text += c;
6429 struct OpenElement {
6431 unsigned char style;
6433 std::vector<OpenElement> open;
6434 unsigned char style = baseStyle;
6436 for(
size_t i = 0; i < html.size();) {
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;
6450 while(gt < html.size() && (quote || html[gt] !=
'>')) {
6452 if(html[gt] == quote)
6454 }
else if(html[gt] ==
'"' || html[gt] ==
'\'') {
6459 auto inside = html.substr(i + 1, gt - i - 1);
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])))
6468 std::string name(rest.substr(0, nameLength));
6469 for(
auto& ch : name)
6470 ch = char(std::tolower(static_cast<unsigned char>(ch)));
6475 auto boundary = [&](
unsigned char at) {
6476 if(block_tags.count(name))
6478 else if(!foreignObjectIsInlineTag(name))
6483 for(
size_t k = open.size(); k-- > 0;) {
6484 if(open[k].tag == name) {
6489 style = open.empty() ? baseStyle : open.back().style;
6493 if(!selfClosing && !foreignObjectIsVoidTag(name)) {
6494 style = foreignObjectStyleOfOpening(name, rest.substr(nameLength),
style);
6495 open.push_back({name,
style});
6502 if(html.compare(i, 5,
"&") == 0) { put(
'&',
style); i += 5;
continue; }
6503 if(html.compare(i, 4,
"<") == 0) { put(
'<',
style); i += 4;
continue; }
6504 if(html.compare(i, 4,
">") == 0) { put(
'>',
style); i += 4;
continue; }
6505 if(html.compare(i, 6,
""") == 0) { put(
'"',
style); i += 6;
continue; }
6506 if(html.compare(i, 6,
"'") == 0) { put(
'\'',
style); i += 6;
continue; }
6507 if(html.compare(i, 6,
" ") == 0) { put(
' ',
style); i += 6;
continue; }
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;
6524 auto dropLast = [&]() {
6525 collapsed.back().text.pop_back();
6526 if(collapsed.back().text.empty())
6527 collapsed.pop_back();
6529 bool lastWasSpace =
true;
6530 for(
const auto& segment :
raw) {
6531 for(
char c : segment.
text) {
6532 if(c ==
'\t' || c ==
'\r')
6535 while(!collapsed.empty() && collapsed.back().text.back() ==
' ')
6537 append(
'\n', segment.style);
6538 lastWasSpace =
true;
6544 lastWasSpace =
true;
6546 lastWasSpace =
false;
6548 append(c, segment.style);
6551 while(!collapsed.empty() && (collapsed.back().text.back() ==
' ' || collapsed.back().text.back() ==
'\n'))
6557inline std::string foreignObjectPlainText(std::string_view html)
6560 for(
const auto& segment : foreignObjectStyledText(html))
6574inline std::optional<float> foreignObjectLineHeight(std::string_view rawHtml,
float fontSize)
6577 while(pos < rawHtml.size()) {
6578 auto tagStart = rawHtml.find(
'<', pos);
6579 if(tagStart == std::string_view::npos)
6581 if(tagStart + 1 < rawHtml.size() && rawHtml[tagStart + 1] ==
'/') {
6585 auto tagEnd = rawHtml.find(
'>', tagStart);
6586 if(tagEnd == std::string_view::npos)
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;
6596 return std::nullopt;
6608inline bool foreignObjectAllowsWrap(std::string_view rawHtml)
6611 while(pos < rawHtml.size()) {
6612 auto tagStart = rawHtml.find(
'<', pos);
6613 if(tagStart == std::string_view::npos)
6615 if(tagStart + 1 < rawHtml.size() && rawHtml[tagStart + 1] ==
'/') {
6619 auto tagEnd = rawHtml.find(
'>', tagStart);
6620 if(tagEnd == std::string_view::npos)
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");
6636inline std::optional<float> foreignObjectDeclaredWidth(std::string_view rawHtml)
6639 while(pos < rawHtml.size()) {
6640 auto tagStart = rawHtml.find(
'<', pos);
6641 if(tagStart == std::string_view::npos)
6643 if(tagStart + 1 < rawHtml.size() && rawHtml[tagStart + 1] ==
'/') {
6647 auto tagEnd = rawHtml.find(
'>', tagStart);
6648 if(tagEnd == std::string_view::npos)
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)
6655 if(
auto d = parseNumericDeclaration(*
style,
"max-width"); d && d->isPixels)
6656 width = width ?
NOVASVG_MIN(*width, d->raw) : d->raw;
6662 return std::nullopt;
6670inline void wrapForeignObjectParagraph(
const std::u32string& paragraph,
const Font& font,
float maxWidth, std::vector<std::u32string>& lines)
6673 const float limit = maxWidth + 1.f / 128.f;
6674 std::u32string current;
6676 while(i < paragraph.size()) {
6677 while(i < paragraph.size() && (paragraph[i] == U
' ' || paragraph[i] == U
'\t'))
6680 while(wordEnd < paragraph.size() && paragraph[wordEnd] != U
' ' && paragraph[wordEnd] != U
'\t')
6684 auto word = paragraph.substr(i, wordEnd - i);
6687 if(current.empty()) {
6688 current = std::move(word);
6691 auto candidate = current;
6694 if(font.measureText(std::u32string_view(candidate)) <= limit) {
6695 current = std::move(candidate);
6697 lines.push_back(std::move(current));
6698 current = std::move(word);
6701 if(!current.empty())
6702 lines.push_back(std::move(current));
6712inline std::vector<std::u32string> wrapForeignObjectText(
const std::string&
text,
const Font* font =
nullptr,
float maxWidth = 0.f)
6714 std::vector<std::u32string> lines;
6715 const bool softWrap = font !=
nullptr && !font->isNull() && maxWidth > 0.f;
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())
6724 wrapForeignObjectParagraph(utf8ToU32(paragraph), *font, maxWidth, lines);
6726 lines.push_back(utf8ToU32(paragraph));
6730 lines.emplace_back();
6735struct ForeignObjectRun {
6736 std::u32string
text;
6737 unsigned char style = 0;
6739using ForeignObjectLine = std::vector<ForeignObjectRun>;
6742inline std::vector<std::vector<ForeignObjectSegment>> splitForeignObjectParagraphs(
const std::vector<ForeignObjectSegment>& segments)
6744 std::vector<std::vector<ForeignObjectSegment>> paragraphs(1);
6745 for(
const auto& segment : segments) {
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);
6751 paragraphs.back().push_back({std::move(piece), segment.style});
6752 if(newline == std::string::npos)
6754 paragraphs.emplace_back();
6763inline float measureForeignObjectCells(
const std::u32string& chars,
const std::vector<unsigned char>& styles,
const ForeignObjectFonts& fonts)
6767 while(begin < chars.size()) {
6768 size_t end = begin + 1;
6769 while(end < chars.size() && styles[end] == styles[begin])
6771 width += fonts.get(styles[begin]).measureText(std::u32string_view(chars).substr(begin, end - begin));
6777inline ForeignObjectLine makeForeignObjectLine(
const std::u32string& chars,
const std::vector<unsigned char>& styles)
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];
6789inline void wrapForeignObjectStyledParagraph(
const std::vector<ForeignObjectSegment>& paragraph,
const ForeignObjectFonts& fonts,
float maxWidth, std::vector<ForeignObjectLine>& lines)
6791 bool uniform =
true;
6792 for(
const auto& segment : paragraph)
6793 uniform = uniform && segment.
style == paragraph.front().
style;
6798 const auto style = paragraph.front().style;
6800 for(
const auto& segment : paragraph)
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}});
6810 lines.push_back(ForeignObjectLine{ForeignObjectRun{std::move(chars),
style}});
6815 std::u32string chars;
6816 std::vector<unsigned char> styles;
6817 for(
const auto& segment : paragraph) {
6818 auto piece = utf8ToU32(segment.text);
6820 styles.insert(styles.end(), piece.size(), segment.style);
6822 if(!(maxWidth > 0.f)) {
6823 lines.push_back(makeForeignObjectLine(chars, styles));
6829 const float limit = maxWidth + 1.f / 128.f;
6830 std::u32string current;
6831 std::vector<unsigned char> currentStyles;
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];
6840 while(wordEnd < chars.size() && chars[wordEnd] != U
' ' && chars[wordEnd] != U
'\t')
6844 auto word = chars.substr(i, wordEnd - i);
6845 std::vector<unsigned char> wordStyles(styles.begin() +
long(i), styles.begin() +
long(wordEnd));
6848 if(current.empty()) {
6849 current = std::move(word);
6850 currentStyles = std::move(wordStyles);
6853 auto candidate = current;
6854 auto candidateStyles = currentStyles;
6856 candidateStyles.push_back(spaceStyle);
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);
6863 lines.push_back(makeForeignObjectLine(current, currentStyles));
6864 current = std::move(word);
6865 currentStyles = std::move(wordStyles);
6868 if(!current.empty())
6869 lines.push_back(makeForeignObjectLine(current, currentStyles));
6873inline std::vector<ForeignObjectLine> layoutForeignObjectText(
const std::vector<ForeignObjectSegment>& segments,
const ForeignObjectFonts& fonts,
float wrapWidth)
6875 std::vector<ForeignObjectLine> lines;
6876 for(
const auto& paragraph : splitForeignObjectParagraphs(segments)) {
6877 if(!paragraph.empty())
6878 wrapForeignObjectStyledParagraph(paragraph, fonts, wrapWidth, lines);
6881 lines.emplace_back();
6885inline float measureForeignObjectLine(
const ForeignObjectLine& line,
const ForeignObjectFonts& fonts)
6888 for(
const auto& run : line)
6889 width += fonts.get(run.
style).measureText(std::u32string_view(run.
text));
6894inline unsigned foreignObjectStylesUsed(
const std::vector<ForeignObjectSegment>& segments)
6897 for(
const auto& segment : segments)
6898 used |= 1u << (segment.
style & 3);
6921 float lineHeight = 0.f;
6950 const auto& font = fonts.
get(baseStyle);
6956 float wrapWidth = 0.f;
6957 if(foreignObjectAllowsWrap(rawHtml)) {
6959 wrapWidth = maxWidth;
6960 else if(
auto declared = foreignObjectDeclaredWidth(rawHtml))
6961 wrapWidth = *declared;
6963 auto lines = layoutForeignObjectText(segments, fonts, wrapWidth);
6965 metrics.
lineHeight = foreignObjectLineHeight(rawHtml, font.size()).value_or(font.height() * 1.2f);
6967 for(
const auto& line : lines)
6995 auto segments = foreignObjectStyledText(rawHtml);
6996 for(
auto& segment : segments)
6999 fonts.
face[0] = font;
7006 if(segments.empty())
7009 const auto& font = element->
font();
7014 const auto fonts = element->
fontsFor(foreignObjectStylesUsed(segments));
7018 auto lines = layoutForeignObjectText(segments, fonts, foreignObjectAllowsWrap(element->
rawContent()) ? box.w : 0.f);
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;
7029 state->setColor(*backgroundColor);
7030 Path backgroundPath;
7032 state->fillPath(backgroundPath, FillRule::NonZero,
state.currentTransform());
7037 for(
size_t i = 0; i < lines.size(); ++i) {
7038 const auto& line = lines[i];
7039 auto lineWidth = measureForeignObjectLine(line, fonts);
7043 float ascent = font.ascent();
7044 float fontHeight = font.height();
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());
7062 if(lineWidth > box.w && lineWidth > 0.f)
7063 scale = box.w / lineWidth;
7066 box.x + (box.w - lineWidth * scale) / 2.f,
7075 topY +
float(i) * lineHeight + (lineHeight - fontHeight) / 2.f + ascent
7078 auto textTransform =
state.currentTransform();
7083 textTransform = textTransform * Transform::translated(origin.
x, 0.f) * Transform::scaled(scale, 1.f) * Transform::translated(-origin.
x, 0.f);
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);
7130 auto trim = [](std::string_view v) {
7131 while(!v.empty() && std::isspace(
static_cast<unsigned char>(v.front())))
7133 while(!v.empty() && std::isspace(
static_cast<unsigned char>(v.back())))
7139 bool supported =
false;
7141 while(pos < value.size() && !supported) {
7142 while(pos < value.size() && std::isspace(
static_cast<unsigned char>(value[pos])))
7145 while(end < value.size() && !std::isspace(
static_cast<unsigned char>(value[end])))
7147 supported = value.substr(pos, end - pos) ==
"http://www.w3.org/1999/xhtml";
7155 bool matches =
false;
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)
7174NOVASVG_INLINE const SVGElement* SVGSwitchElement::selectedChild()
const
7176 for(
const auto& child :
children()) {
7194 if(
auto child = selectedChild())
7195 child->render(newState);
7248 transform.rotate(m_orient.
value());
7250 transform.rotate(angle);
7254 auto refOrigin = viewTransform.mapPoint(
refPoint());
7256 transform.scale(strokeWidth, strokeWidth);
7257 transform.translate(-refOrigin.x, -refOrigin.y);
7258 return transform * viewTransform;
7268 if(
state.hasCycleReference(
this))
7307 if(
state.hasCycleReference(
this))
7309 auto maskImage = Canvas::create(
state.currentTransform().mapRect(
state.paintBoundingBox()));
7312 auto bbox =
state.fillBoundingBox();
7313 currentTransform.translate(bbox.x, bbox.y);
7314 currentTransform.scale(bbox.w, bbox.h);
7323 state->blendCanvas(*maskImage, BlendMode::Dst_In, 1.f);
7337 auto bbox =
state.fillBoundingBox();
7338 currentTransform.translate(bbox.x, bbox.y);
7339 currentTransform.scale(bbox.w, bbox.h);
7342 for(
const auto& child :
children()) {
7344 if(element ==
nullptr || element->isDisplayNone())
7346 Transform clipTransform(currentTransform);
7348 if(shapeElement ==
nullptr) {
7351 clipTransform.
multiply(element->localTransform());
7355 if(shapeElement ==
nullptr || !shapeElement->isRenderable())
7357 state->clipPath(shapeElement->path(), shapeElement->clip_rule(), clipTransform * shapeElement->localTransform());
7369 for(
const auto& child :
children()) {
7371 if(element ==
nullptr || element->isDisplayNone())
7374 if(shapeElement ==
nullptr) {
7375 if(element->isTextPositioningElement())
7379 if(element->clipper())
7384 if(shapeElement ==
nullptr || !shapeElement->isRenderable())
7386 if(prevShapeElement || shapeElement->
clipper())
7388 prevShapeElement = shapeElement;
7448 if(
state.hasCycleReference(
this))
7450 auto maskImage = Canvas::create(
state.currentTransform().mapRect(
state.paintBoundingBox()));
7453 auto currentTransform =
state.currentTransform();
7456 currentTransform.translate(
bbox.x,
bbox.y);
7457 currentTransform.scale(
bbox.w,
bbox.h);
7482 for(
const auto& child :
children()) {
7491 auto dx = parseFirstFloat(element->getAttribute(
PropertyID::Dx), 2.f);
7492 auto dy = parseFirstFloat(element->getAttribute(
PropertyID::Dy), 2.f);
7495 auto dx = parseFirstFloat(element->getAttribute(
PropertyID::Dx), 0.f);
7496 auto dy = parseFirstFloat(element->getAttribute(
PropertyID::Dy), 0.f);
7510 for(
const auto& child :
children()) {
7514 auto output = element->applyFilterPrimitive(context);
7525 m_mask_type =
state.mask_type();
Attribute(int specificity, PropertyID id, std::string value)
const std::string & value() const
const Length & length() const
BaselineShift(const Length &length)
bool isNull() const
Checks if the bitmap is null.
int height() const
Gets the height of the bitmap.
int width() const
Gets the width of the bitmap.
Represents a color in RGBA format (0xRRGGBBAA).
float getGF() const
Green component as a 0..1 float.
float getBF() const
Blue component as a 0..1 float.
Color colorWithAlpha(float opacity) const
This color with its alpha scaled by opacity (0..1).
bool isVisible() const
True if not fully transparent (alpha > 0).
static const Color Transparent
float getRF() const
Red component as a 0..1 float.
uint8_t getA() const
Get the alpha component of the color.
float getAF() const
Alpha component as a 0..1 float.
FontFace getFontFace(const std::string &family, bool bold, bool italic) const
FontFace getFontFaceForFamilyStack(const std::string &familyStack, bool bold, bool italic) const
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)
float valueForLength(const SVGLength &length) const
float valueForLength(const Length &length, LengthDirection direction) const
Length(float value, LengthUnits units)
bool parse(std::string_view input, LengthNegativeMode mode)
LengthUnits units() const
const std::string & id() const
Paint(const Color &color)
Paint(const std::string &id, const Color &color)
const Color & color() const
PathCommand currentSegment(std::array< Point, 3 > &points) const
void addRect(float x, float y, float w, float h)
bool parse(const char *data, size_t length)
constexpr bool isValid() const
constexpr Rect & unite(const Rect &o)
constexpr Rect intersected(const Rect &rect) const
constexpr Size size() const
constexpr Rect & intersect(const Rect &o)
constexpr void inflate(float dx, float dy)
constexpr bool isEmpty() const
static const Rect Invalid
OrientType orientType() const
bool parse(std::string_view input) final
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)
Rect updateShape(Path &path) final
SVGCircleElement(Document *document)
void applyClipPath(SVGRenderState &state) const
void applyClipMask(SVGRenderState &state) const
bool requiresMasking() const
SVGClipPathElement(Document *document)
const SVGEnumeration< Units > & clipPathUnits() const
Rect clipBoundingBox(const SVGElement *element) const
SVGDefsElement(Document *document)
SVGNode * lastChild() const
bool isOverflowHidden() const
SVGFilterElement * getFilterElement(std::string_view id) const
const std::string & getAttribute(std::string_view name) const
virtual Rect paintBoundingBox() const
MixBlendMode mixBlendMode() const
Size currentViewportSize() const
virtual Transform localTransform() const
void layoutChildren(SVGLayoutState &state)
bool isHiddenElement() const
const SVGClipPathElement * clipper() const
bool hasAttribute(std::string_view name) const
const Attribute * findAttribute(PropertyID id) const
const SVGNodeList & children() const
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)
bool isVisibilityHidden() const
const SVGPropertyList & properties() const
const AttributeList & attributes() const
bool isElement() const final
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
SVGNode * firstChild() const
void setAttributes(const AttributeList &attributes)
virtual void layoutElement(const SVGLayoutState &state)
bool isDisplayNone() const
SVGElement(Document *document, ElementID id)
SVGElement * elementFromPoint(float x, float y)
const SVGFilterElement * filterElement() const
const SVGMaskElement * masker() const
SVGEllipseElement(Document *document)
Rect updateShape(Path &path) final
SVGEnumeration(PropertyID id, Enum value)
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
float pixelMargin() const
const SVGPreserveAspectRatio & preserveAspectRatio() const
Transform viewBoxToViewTransform(const Size &viewportSize) const
Rect getClipRect(const Size &viewportSize) const
const SVGRect & viewBox() const
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
const Font & font() 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 isRenderable() const
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)
SVGGradientAttributes()=default
bool hasSpreadMethod() const
bool hasGradientContentElement() const
void setGradientTransform(const SVGGradientElement *value)
bool hasGradientTransform() const
const SVGGradientElement * gradientContentElement() const
Units gradientUnits() const
SpreadMethod spreadMethod() const
bool hasGradientUnits() 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
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
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
const LengthList & values() const
SVGLengthList(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode)
LengthDirection direction() const
SVGLength(PropertyID id, LengthDirection direction, LengthNegativeMode negativeMode, float value=0, LengthUnits units=LengthUnits::None)
bool parse(std::string_view input) final
LengthNegativeMode negativeMode() const
LengthDirection direction() const
const Length & value() const
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)
SVGLinearGradientAttributes()=default
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
SVGElement * parentElement() const
virtual bool isPaintElement() const
Document * document() const
bool isRootElement() const
SVGRootElement * rootElement() const
virtual bool isTextNode() const
virtual bool isElement() const
virtual bool isGeometryElement() const
void setParentElement(SVGElement *parent)
virtual bool isGraphicsElement() const
SVGNode(Document *document)
virtual std::unique_ptr< SVGNode > clone(bool deep) const =0
virtual ~SVGNode()=default
bool parse(std::string_view input) final
const NumberList & values() const
SVGNumberList(PropertyID id)
bool parse(std::string_view input) final
SVGNumberPercentage(PropertyID id, float value)
bool parse(std::string_view input) override
SVGNumber(PropertyID id, float value)
virtual bool applyPaint(SVGRenderState &state, float opacity) const =0
bool isPaintElement() const final
SVGPaintElement(Document *document, ElementID id)
bool isRenderable() const
bool applyPaint(SVGRenderState &state) const
const SVGPaintElement * element() const
SVGPaintServer(const SVGPaintElement *element, const Color &color, float opacity)
const Color & color() const
Rect updateShape(Path &path) final
SVGPathElement(Document *document)
bool parse(std::string_view input) final
const Path & value() const
const SVGPatternElement * patternContentElement() const
void setPatternTransform(const SVGPatternElement *value)
void setPatternContentUnits(const SVGPatternElement *value)
const Transform & patternTransform() const
SVGPatternAttributes()=default
void setY(const SVGPatternElement *value)
const SVGLength & y() const
void setPatternUnits(const SVGPatternElement *value)
Units patternContentUnits() const
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 hasPatternTransform() const
bool hasPatternContentElement() const
Units patternUnits() const
const SVGLength & width() const
const SVGLength & x() const
bool hasPatternContentUnits() const
void setHeight(const SVGPatternElement *value)
bool hasPreserveAspectRatio() const
void setDefaultValues(const SVGPatternElement *element)
bool hasPatternUnits() const
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
SVGPointList(PropertyID id)
bool parse(std::string_view input) final
const Point & value() const
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
SVGPreserveAspectRatio(PropertyID id)
AlignType alignType() const
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
SVGRadialGradientAttributes()=default
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
const Rect & value() const
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 ¤tTransform, 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
bool parse(std::string_view input) final
const std::string & value() const
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
const std::string & data() const
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
float baseline_offset() const
bool isTextPositioningElement() const final
const LengthList & dy() const
bool isUprightTextOrientation() const
Direction direction() const
void layoutElement(const SVGLayoutState &state) override
const Font & font() const
bool isVerticalWritingMode() const
AlignmentBaseline alignment_baseline() const
const LengthList & y() const
const SVGPaintServer & stroke() const
LengthAdjust lengthAdjust() const
WhiteSpace white_space() const
float word_spacing() const
float letter_spacing() const
TextAnchor text_anchor() const
const LengthList & dx() const
const SVGLength & textLength() const
float stroke_width() const
const LengthList & x() const
const NumberList & rotate() const
const SVGString & href() const
const std::string & hrefString() const
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
#define NOVASVG_MAX(a, b)
#define NOVASVG_MIN(a, b)
NOVASVG_INLINE unsigned int color_to_argb32(const color_t *color)
Converts a color to a 32-bit unsigned ARGB value.
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.
NOVASVG_INLINE codepoint_t text_iterator_next(text_iterator_t *it)
Retrieves the next code point and advances the iterator.
@ NOVASVG_TEXT_ENCODING_UTF8
UTF-8 encoding.
NOVASVG_INLINE int color_parse(color_t *color, const char *data, int length)
Parses a color from a string using CSS color syntax.
NOVASVG_INLINE bool text_iterator_has_next(const text_iterator_t *it)
Checks if there are more code points to iterate.
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.
surface_t * surface_load_from_image_base64(const char *data, int length)
Loads an image surface from base64-encoded data.
Main namespace for the NovaSVG library.
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
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)
FontFaceCache * fontFaceCache()
std::vector< SVGTextPosition > SVGTextPositionList
SVGTextFragmentList::iterator SVGTextFragmentIterator
std::forward_list< SVGProperty * > SVGPropertyList
constexpr bool skipDelimiter(std::string_view &input, char delimiter)
std::vector< SVGTextFragment > SVGTextFragmentList
std::vector< float > NumberList
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
constexpr bool skipOptionalSpacesOrComma(std::string_view &input)
NOVASVG_INLINE Length parseLengthOrNormal(std::string_view input)
NOVASVG_INLINE float calculateTextAnchorOffset(const SVGTextPositioningElement *element, float width)
constexpr bool IS_NUM(int cc)
NOVASVG_INLINE Visibility parseVisibility(std::string_view input)
std::list< std::unique_ptr< SVGNode > > SVGNodeList
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)
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)
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)
const std::string emptyString
std::vector< Point > PointList
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
NOVASVG_INLINE MaskType parseMaskType(std::string_view input)
NOVASVG_INLINE float calculateBaselineOffset(const SVGTextPositioningElement *element)
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 > dx
std::optional< float > dy
SVGTextFragment(const SVGTextPositioningElement *element)
Transform lengthAdjustTransform
const SVGTextPositioningElement * element
SVGTextPosition(const SVGNode *node, size_t startOffset, size_t endOffset)
Represents a color with red, green, blue, and alpha components.
Represents a gradient stop.
Iterator for traversing code points in text data.
std::string text
UTF-8; ' ' marks a forced line break ( ,.