diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 814831566c..d5820ea9da 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/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 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 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 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; } }; } diff --git a/Userland/Libraries/LibWeb/CSS/Enums.json b/Userland/Libraries/LibWeb/CSS/Enums.json index da2c619d99..0083468226 100644 --- a/Userland/Libraries/LibWeb/CSS/Enums.json +++ b/Userland/Libraries/LibWeb/CSS/Enums.json @@ -275,6 +275,11 @@ "to-zero", "up" ], + "text-anchor": [ + "start", + "middle", + "end" + ], "text-align": [ "center", "justify", diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 906a725f1f..52dd5575fb 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/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", diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 021b49c22b..31b4f02ac4 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -589,6 +589,12 @@ bool StyleProperties::operator==(StyleProperties const& other) const return true; } +Optional StyleProperties::text_anchor() const +{ + auto value = property(CSS::PropertyID::TextAnchor); + return value_id_to_text_anchor(value->to_identifier()); +} + Optional StyleProperties::text_align() const { auto value = property(CSS::PropertyID::TextAlign); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 350550a34f..de68468ba8 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -48,6 +48,7 @@ public: Optional 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 text_anchor() const; Optional text_align() const; Optional text_justify() const; CSS::Length border_spacing_horizontal() const; diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 9067e0cf95..8ae55d8e37 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/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)); diff --git a/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp index d764c74b4f..11990063c1 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGTextPaintable.cpp +++ b/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()); + + // 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(), text_content, scaled_font, layout_node().computed_values().fill()->as_color()); } diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.h b/Userland/Libraries/LibWeb/SVG/AttributeParser.h index 45476a403b..de1c72d22f 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeParser.h +++ b/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; diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp index 6e8ad65ac6..9adb0c6596 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,22 @@ JS::ThrowCompletionOr SVGTextContentElement::initialize(JS::Realm& realm) return {}; } +Optional 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); diff --git a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h index 2fd4e4c501..5f2cfdc94b 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGTextContentElement.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -24,6 +25,8 @@ public: Gfx::FloatPoint get_offset() const; + Optional text_anchor() const; + protected: SVGTextContentElement(DOM::Document&, DOM::QualifiedName);