소스 검색

LibWeb: Add an initial implementation of SVG `text-anchor`

This only handles very simple <text> elements, but this is enough (with
the font-size changes) to improve the badges on the GitHub README a fair
bit.
MacDue 2 년 전
부모
커밋
30277f385c

+ 4 - 0
Userland/Libraries/LibWeb/CSS/ComputedValues.h

@@ -83,6 +83,7 @@ public:
     static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; }
     static float stroke_opacity() { return 1.0f; }
     static float stop_opacity() { return 1.0f; }
+    static CSS::TextAnchor text_anchor() { return CSS::TextAnchor::Start; }
     static CSS::Length border_radius() { return Length::make_px(0); }
     static Variant<CSS::VerticalAlign, CSS::LengthPercentage> vertical_align() { return CSS::VerticalAlign::Baseline; }
     static CSS::LengthBox inset() { return { CSS::Length::make_auto(), CSS::Length::make_auto(), CSS::Length::make_auto(), CSS::Length::make_auto() }; }
@@ -312,6 +313,7 @@ public:
     LengthPercentage const& stroke_width() const { return m_inherited.stroke_width; }
     Color stop_color() const { return m_noninherited.stop_color; }
     float stop_opacity() const { return m_noninherited.stop_opacity; }
+    CSS::TextAnchor text_anchor() const { return m_inherited.text_anchor; }
 
     Vector<CSS::Transformation> const& transformations() const { return m_noninherited.transformations; }
     CSS::TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; }
@@ -357,6 +359,7 @@ protected:
         float fill_opacity { InitialValues::fill_opacity() };
         float stroke_opacity { InitialValues::stroke_opacity() };
         LengthPercentage stroke_width { Length::make_px(1) };
+        CSS::TextAnchor text_anchor { InitialValues::text_anchor() };
 
         Vector<ShadowData> text_shadow;
     } m_inherited;
@@ -538,6 +541,7 @@ public:
     void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }
     void set_stop_color(Color value) { m_noninherited.stop_color = value; }
     void set_stop_opacity(float value) { m_noninherited.stop_opacity = value; }
+    void set_text_anchor(CSS::TextAnchor value) { m_inherited.text_anchor = value; }
 };
 
 }

+ 5 - 0
Userland/Libraries/LibWeb/CSS/Enums.json

@@ -275,6 +275,11 @@
         "to-zero",
         "up"
     ],
+    "text-anchor": [
+        "start",
+        "middle",
+        "end"
+    ],
     "text-align": [
         "center",
         "justify",

+ 7 - 0
Userland/Libraries/LibWeb/CSS/Properties.json

@@ -1862,6 +1862,13 @@
     ],
     "percentages-resolve-to": "length"
   },
+  "text-anchor": {
+    "inherited": true,
+    "initial": "start",
+    "valid-types": [
+      "text-anchor"
+    ]
+  },
   "text-align": {
     "inherited": true,
     "initial": "left",

+ 6 - 0
Userland/Libraries/LibWeb/CSS/StyleProperties.cpp

@@ -589,6 +589,12 @@ bool StyleProperties::operator==(StyleProperties const& other) const
     return true;
 }
 
+Optional<CSS::TextAnchor> StyleProperties::text_anchor() const
+{
+    auto value = property(CSS::PropertyID::TextAnchor);
+    return value_id_to_text_anchor(value->to_identifier());
+}
+
 Optional<CSS::TextAlign> StyleProperties::text_align() const
 {
     auto value = property(CSS::PropertyID::TextAlign);

+ 1 - 0
Userland/Libraries/LibWeb/CSS/StyleProperties.h

@@ -48,6 +48,7 @@ public:
     Optional<LengthPercentage> length_percentage(CSS::PropertyID) const;
     LengthBox length_box(CSS::PropertyID left_id, CSS::PropertyID top_id, CSS::PropertyID right_id, CSS::PropertyID bottom_id, const CSS::Length& default_value) const;
     Color color_or_fallback(CSS::PropertyID, Layout::NodeWithStyle const&, Color fallback) const;
+    Optional<CSS::TextAnchor> text_anchor() const;
     Optional<CSS::TextAlign> text_align() const;
     Optional<CSS::TextJustify> text_justify() const;
     CSS::Length border_spacing_horizontal() const;

+ 3 - 0
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -724,6 +724,9 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
     computed_values.set_stroke_opacity(computed_style.stroke_opacity());
     computed_values.set_stop_opacity(computed_style.stop_opacity());
 
+    if (auto text_anchor = computed_style.text_anchor(); text_anchor.has_value())
+        computed_values.set_text_anchor(*text_anchor);
+
     computed_values.set_column_gap(computed_style.size_value(CSS::PropertyID::ColumnGap));
     computed_values.set_row_gap(computed_style.size_value(CSS::PropertyID::RowGap));
 

+ 25 - 0
Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp

@@ -68,6 +68,31 @@ void SVGTextPaintable::paint(PaintContext& context, PaintPhase phase) const
 
     Utf8View text_content { child_text_content };
     auto text_offset = context.floored_device_point(dom_node.get_offset().transformed(*transform).to_type<CSSPixels>());
+
+    // FIXME: Once SVGFormattingContext does text layout this logic should move there.
+    // https://svgwg.org/svg2-draft/text.html#TextAnchoringProperties
+    switch (text_element.text_anchor().value_or(SVG::TextAnchor::Start)) {
+    case SVG::TextAnchor::Start:
+        // The rendered characters are aligned such that the start of the resulting rendered text is at the initial
+        // current text position.
+        break;
+    case SVG::TextAnchor::Middle: {
+        // The rendered characters are shifted such that the geometric middle of the resulting rendered text
+        // (determined from the initial and final current text position before applying the text-anchor property)
+        // is at the initial current text position.
+        text_offset.translate_by(-scaled_font.width(text_content) / 2, 0);
+        break;
+    }
+    case SVG::TextAnchor::End: {
+        // The rendered characters are shifted such that the end of the resulting rendered text (final current text
+        // position before applying the text-anchor property) is at the initial current text position.
+        text_offset.translate_by(-scaled_font.width(text_content), 0);
+        break;
+    }
+    default:
+        VERIFY_NOT_REACHED();
+    }
+
     painter.draw_text_run(text_offset.to_type<int>(), text_content, scaled_font, layout_node().computed_values().fill()->as_color());
 }
 

+ 6 - 0
Userland/Libraries/LibWeb/SVG/AttributeParser.h

@@ -125,6 +125,12 @@ enum class FillRule {
     Evenodd
 };
 
+enum class TextAnchor {
+    Start,
+    Middle,
+    End
+};
+
 class AttributeParser final {
 public:
     ~AttributeParser() = default;

+ 17 - 0
Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp

@@ -8,6 +8,7 @@
 #include <AK/Utf16View.h>
 #include <LibJS/Runtime/Completion.h>
 #include <LibJS/Runtime/Utf16String.h>
+#include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/Layout/SVGTextBox.h>
 #include <LibWeb/SVG/AttributeNames.h>
@@ -30,6 +31,22 @@ JS::ThrowCompletionOr<void> SVGTextContentElement::initialize(JS::Realm& realm)
     return {};
 }
 
+Optional<TextAnchor> SVGTextContentElement::text_anchor() const
+{
+    if (!layout_node())
+        return {};
+    switch (layout_node()->computed_values().text_anchor()) {
+    case CSS::TextAnchor::Start:
+        return TextAnchor::Start;
+    case CSS::TextAnchor::Middle:
+        return TextAnchor::Middle;
+    case CSS::TextAnchor::End:
+        return TextAnchor::End;
+    default:
+        VERIFY_NOT_REACHED();
+    }
+}
+
 void SVGTextContentElement::attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value)
 {
     SVGGraphicsElement::attribute_changed(name, value);

+ 3 - 0
Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h

@@ -6,6 +6,7 @@
 
 #pragma once
 
+#include <LibWeb/SVG/AttributeParser.h>
 #include <LibWeb/SVG/SVGGraphicsElement.h>
 #include <LibWeb/WebIDL/ExceptionOr.h>
 
@@ -24,6 +25,8 @@ public:
 
     Gfx::FloatPoint get_offset() const;
 
+    Optional<TextAnchor> text_anchor() const;
+
 protected:
     SVGTextContentElement(DOM::Document&, DOM::QualifiedName);