mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 19:37:35 +00:00
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.
This commit is contained in:
parent
4cdb4de049
commit
30277f385c
10 changed files with 77 additions and 0 deletions
|
@ -83,6 +83,7 @@ public:
|
||||||
static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; }
|
static CSS::FillRule fill_rule() { return CSS::FillRule::Nonzero; }
|
||||||
static float stroke_opacity() { return 1.0f; }
|
static float stroke_opacity() { return 1.0f; }
|
||||||
static float stop_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 CSS::Length border_radius() { return Length::make_px(0); }
|
||||||
static Variant<CSS::VerticalAlign, CSS::LengthPercentage> vertical_align() { return CSS::VerticalAlign::Baseline; }
|
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() }; }
|
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; }
|
LengthPercentage const& stroke_width() const { return m_inherited.stroke_width; }
|
||||||
Color stop_color() const { return m_noninherited.stop_color; }
|
Color stop_color() const { return m_noninherited.stop_color; }
|
||||||
float stop_opacity() const { return m_noninherited.stop_opacity; }
|
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; }
|
Vector<CSS::Transformation> const& transformations() const { return m_noninherited.transformations; }
|
||||||
CSS::TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; }
|
CSS::TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; }
|
||||||
|
@ -357,6 +359,7 @@ protected:
|
||||||
float fill_opacity { InitialValues::fill_opacity() };
|
float fill_opacity { InitialValues::fill_opacity() };
|
||||||
float stroke_opacity { InitialValues::stroke_opacity() };
|
float stroke_opacity { InitialValues::stroke_opacity() };
|
||||||
LengthPercentage stroke_width { Length::make_px(1) };
|
LengthPercentage stroke_width { Length::make_px(1) };
|
||||||
|
CSS::TextAnchor text_anchor { InitialValues::text_anchor() };
|
||||||
|
|
||||||
Vector<ShadowData> text_shadow;
|
Vector<ShadowData> text_shadow;
|
||||||
} m_inherited;
|
} m_inherited;
|
||||||
|
@ -538,6 +541,7 @@ public:
|
||||||
void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }
|
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_color(Color value) { m_noninherited.stop_color = value; }
|
||||||
void set_stop_opacity(float value) { m_noninherited.stop_opacity = value; }
|
void set_stop_opacity(float value) { m_noninherited.stop_opacity = value; }
|
||||||
|
void set_text_anchor(CSS::TextAnchor value) { m_inherited.text_anchor = value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,6 +275,11 @@
|
||||||
"to-zero",
|
"to-zero",
|
||||||
"up"
|
"up"
|
||||||
],
|
],
|
||||||
|
"text-anchor": [
|
||||||
|
"start",
|
||||||
|
"middle",
|
||||||
|
"end"
|
||||||
|
],
|
||||||
"text-align": [
|
"text-align": [
|
||||||
"center",
|
"center",
|
||||||
"justify",
|
"justify",
|
||||||
|
|
|
@ -1862,6 +1862,13 @@
|
||||||
],
|
],
|
||||||
"percentages-resolve-to": "length"
|
"percentages-resolve-to": "length"
|
||||||
},
|
},
|
||||||
|
"text-anchor": {
|
||||||
|
"inherited": true,
|
||||||
|
"initial": "start",
|
||||||
|
"valid-types": [
|
||||||
|
"text-anchor"
|
||||||
|
]
|
||||||
|
},
|
||||||
"text-align": {
|
"text-align": {
|
||||||
"inherited": true,
|
"inherited": true,
|
||||||
"initial": "left",
|
"initial": "left",
|
||||||
|
|
|
@ -589,6 +589,12 @@ bool StyleProperties::operator==(StyleProperties const& other) const
|
||||||
return true;
|
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
|
Optional<CSS::TextAlign> StyleProperties::text_align() const
|
||||||
{
|
{
|
||||||
auto value = property(CSS::PropertyID::TextAlign);
|
auto value = property(CSS::PropertyID::TextAlign);
|
||||||
|
|
|
@ -48,6 +48,7 @@ public:
|
||||||
Optional<LengthPercentage> length_percentage(CSS::PropertyID) const;
|
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;
|
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;
|
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::TextAlign> text_align() const;
|
||||||
Optional<CSS::TextJustify> text_justify() const;
|
Optional<CSS::TextJustify> text_justify() const;
|
||||||
CSS::Length border_spacing_horizontal() const;
|
CSS::Length border_spacing_horizontal() const;
|
||||||
|
|
|
@ -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_stroke_opacity(computed_style.stroke_opacity());
|
||||||
computed_values.set_stop_opacity(computed_style.stop_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_column_gap(computed_style.size_value(CSS::PropertyID::ColumnGap));
|
||||||
computed_values.set_row_gap(computed_style.size_value(CSS::PropertyID::RowGap));
|
computed_values.set_row_gap(computed_style.size_value(CSS::PropertyID::RowGap));
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,31 @@ void SVGTextPaintable::paint(PaintContext& context, PaintPhase phase) const
|
||||||
|
|
||||||
Utf8View text_content { child_text_content };
|
Utf8View text_content { child_text_content };
|
||||||
auto text_offset = context.floored_device_point(dom_node.get_offset().transformed(*transform).to_type<CSSPixels>());
|
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());
|
painter.draw_text_run(text_offset.to_type<int>(), text_content, scaled_font, layout_node().computed_values().fill()->as_color());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,12 @@ enum class FillRule {
|
||||||
Evenodd
|
Evenodd
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class TextAnchor {
|
||||||
|
Start,
|
||||||
|
Middle,
|
||||||
|
End
|
||||||
|
};
|
||||||
|
|
||||||
class AttributeParser final {
|
class AttributeParser final {
|
||||||
public:
|
public:
|
||||||
~AttributeParser() = default;
|
~AttributeParser() = default;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <AK/Utf16View.h>
|
#include <AK/Utf16View.h>
|
||||||
#include <LibJS/Runtime/Completion.h>
|
#include <LibJS/Runtime/Completion.h>
|
||||||
#include <LibJS/Runtime/Utf16String.h>
|
#include <LibJS/Runtime/Utf16String.h>
|
||||||
|
#include <LibWeb/CSS/Parser/Parser.h>
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
#include <LibWeb/Layout/SVGTextBox.h>
|
#include <LibWeb/Layout/SVGTextBox.h>
|
||||||
#include <LibWeb/SVG/AttributeNames.h>
|
#include <LibWeb/SVG/AttributeNames.h>
|
||||||
|
@ -30,6 +31,22 @@ JS::ThrowCompletionOr<void> SVGTextContentElement::initialize(JS::Realm& realm)
|
||||||
return {};
|
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)
|
void SVGTextContentElement::attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value)
|
||||||
{
|
{
|
||||||
SVGGraphicsElement::attribute_changed(name, value);
|
SVGGraphicsElement::attribute_changed(name, value);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/SVG/AttributeParser.h>
|
||||||
#include <LibWeb/SVG/SVGGraphicsElement.h>
|
#include <LibWeb/SVG/SVGGraphicsElement.h>
|
||||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||||
|
|
||||||
|
@ -24,6 +25,8 @@ public:
|
||||||
|
|
||||||
Gfx::FloatPoint get_offset() const;
|
Gfx::FloatPoint get_offset() const;
|
||||||
|
|
||||||
|
Optional<TextAnchor> text_anchor() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SVGTextContentElement(DOM::Document&, DOM::QualifiedName);
|
SVGTextContentElement(DOM::Document&, DOM::QualifiedName);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue