mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
Previously, we only supported very basic numbers and a single level of
text positioning support in the `x`, `y`, `dx` and `dy` attributes in
`<text>` and `<tspan>` SVG elements.
This improves our support for them in the following ways:
* Any `length-percentage` or `number` type value is accepted;
* Nested `<text>` and `<tspan>` use the 'current text position'
concept to determine where the next text run should go;
* We expose the attributes' values through the API.
Though we still do not support:
* Applying the `rotate` attribute;
* Applying transformations on a per-character basis.
* Proper horizontal and vertical glyph advancing (we just use the path
bounding box for now).
81 lines
2.8 KiB
C++
81 lines
2.8 KiB
C++
/*
|
|
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
|
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <LibWeb/SVG/SVGTextContentElement.h>
|
|
|
|
namespace Web::SVG {
|
|
|
|
// https://svgwg.org/svg2-draft/text.html#TSpanNotes
|
|
// https://svgwg.org/svg2-draft/text.html#TSpanAttributes
|
|
struct TextPositioning {
|
|
using Position = Variant<CSS::LengthPercentage, CSS::Number>;
|
|
|
|
Vector<Position> x;
|
|
Vector<Position> y;
|
|
Vector<Position> dx;
|
|
Vector<Position> dy;
|
|
Vector<float> rotate;
|
|
|
|
void apply_to_text_position(Layout::Node const& node, CSSPixelSize viewport, Gfx::FloatPoint& current_text_position,
|
|
size_t character_index) const
|
|
{
|
|
auto value_for_character = [&](Vector<Position> const& values) -> float {
|
|
if (values.is_empty())
|
|
return 0.f;
|
|
|
|
auto position = character_index < values.size() ? values[character_index] : values.last();
|
|
return position.visit(
|
|
[](CSS::Number const& number) { return static_cast<float>(number.value()); },
|
|
[&](CSS::LengthPercentage const& length_percentage) {
|
|
auto reference = &values == &x || &values == &dx ? viewport.width() : viewport.height();
|
|
return length_percentage.to_px(node, reference).to_float();
|
|
});
|
|
};
|
|
|
|
if (!x.is_empty())
|
|
current_text_position.set_x(value_for_character(x));
|
|
if (!y.is_empty())
|
|
current_text_position.set_y(value_for_character(y));
|
|
|
|
current_text_position.translate_by(value_for_character(dx), value_for_character(dy));
|
|
}
|
|
};
|
|
|
|
// https://svgwg.org/svg2-draft/text.html#InterfaceSVGTextPositioningElement
|
|
class SVGTextPositioningElement : public SVGTextContentElement {
|
|
WEB_PLATFORM_OBJECT(SVGTextPositioningElement, SVGTextContentElement);
|
|
|
|
public:
|
|
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
|
|
|
|
TextPositioning text_positioning() const;
|
|
|
|
GC::Ref<SVGAnimatedLengthList> x();
|
|
GC::Ref<SVGAnimatedLengthList> y();
|
|
GC::Ref<SVGAnimatedLengthList> dx();
|
|
GC::Ref<SVGAnimatedLengthList> dy();
|
|
GC::Ref<SVGAnimatedNumberList> rotate();
|
|
|
|
protected:
|
|
SVGTextPositioningElement(DOM::Document&, DOM::QualifiedName);
|
|
|
|
virtual void initialize(JS::Realm&) override;
|
|
virtual void visit_edges(Visitor&) override;
|
|
|
|
private:
|
|
GC::Ref<SVGAnimatedLengthList> ensure_length_list(GC::Ptr<SVGAnimatedLengthList>&, FlyString const& attribute_name) const;
|
|
|
|
GC::Ptr<SVGAnimatedLengthList> m_x;
|
|
GC::Ptr<SVGAnimatedLengthList> m_y;
|
|
GC::Ptr<SVGAnimatedLengthList> m_dx;
|
|
GC::Ptr<SVGAnimatedLengthList> m_dy;
|
|
GC::Ptr<SVGAnimatedNumberList> m_rotate;
|
|
};
|
|
|
|
}
|