LibWeb: Support percentage attributes on SVG rect element

This makes the IMDb logo have a yellow background as expected.
This commit is contained in:
Andreas Kling 2025-09-28 12:40:14 +02:00 committed by Andreas Kling
commit d36e5098f8
Notes: github-actions[bot] 2025-09-28 17:26:28 +00:00
6 changed files with 58 additions and 41 deletions

View file

@ -33,33 +33,33 @@ void SVGRectElement::attribute_changed(FlyString const& name, Optional<String> c
Base::attribute_changed(name, old_value, value, namespace_); Base::attribute_changed(name, old_value, value, namespace_);
if (name == SVG::AttributeNames::x) { if (name == SVG::AttributeNames::x) {
m_x = AttributeParser::parse_coordinate(value.value_or(String {})); m_x = AttributeParser::parse_number_percentage(value.value_or(String {}));
} else if (name == SVG::AttributeNames::y) { } else if (name == SVG::AttributeNames::y) {
m_y = AttributeParser::parse_coordinate(value.value_or(String {})); m_y = AttributeParser::parse_number_percentage(value.value_or(String {}));
} else if (name == SVG::AttributeNames::width) { } else if (name == SVG::AttributeNames::width) {
m_width = AttributeParser::parse_positive_length(value.value_or(String {})); m_width = AttributeParser::parse_number_percentage(value.value_or(String {}));
} else if (name == SVG::AttributeNames::height) { } else if (name == SVG::AttributeNames::height) {
m_height = AttributeParser::parse_positive_length(value.value_or(String {})); m_height = AttributeParser::parse_number_percentage(value.value_or(String {}));
} else if (name == SVG::AttributeNames::rx) { } else if (name == SVG::AttributeNames::rx) {
m_radius_x = AttributeParser::parse_length(value.value_or(String {})); m_radius_x = AttributeParser::parse_number_percentage(value.value_or(String {}));
} else if (name == SVG::AttributeNames::ry) { } else if (name == SVG::AttributeNames::ry) {
m_radius_y = AttributeParser::parse_length(value.value_or(String {})); m_radius_y = AttributeParser::parse_number_percentage(value.value_or(String {}));
} }
} }
Gfx::Path SVGRectElement::get_path(CSSPixelSize) Gfx::Path SVGRectElement::get_path(CSSPixelSize viewport_size)
{ {
float width = m_width.value_or(0); float width = m_width.value_or(NumberPercentage::create_number(0)).resolve_relative_to(viewport_size.width().to_float());
float height = m_height.value_or(0); float height = m_height.value_or(NumberPercentage::create_number(0)).resolve_relative_to(viewport_size.height().to_float());
float x = m_x.value_or(0); float x = m_x.value_or(NumberPercentage::create_number(0)).resolve_relative_to(viewport_size.width().to_float());
float y = m_y.value_or(0); float y = m_y.value_or(NumberPercentage::create_number(0)).resolve_relative_to(viewport_size.height().to_float());
Gfx::Path path; Gfx::Path path;
// If width or height is zero, rendering is disabled. // If width or height is zero, rendering is disabled.
if (width == 0 || height == 0) if (width == 0 || height == 0)
return path; return path;
auto corner_radii = calculate_used_corner_radius_values(); auto corner_radii = calculate_used_corner_radius_values(viewport_size);
float rx = corner_radii.width(); float rx = corner_radii.width();
float ry = corner_radii.height(); float ry = corner_radii.height();
@ -112,7 +112,7 @@ Gfx::Path SVGRectElement::get_path(CSSPixelSize)
return path; return path;
} }
Gfx::FloatSize SVGRectElement::calculate_used_corner_radius_values() const Gfx::FloatSize SVGRectElement::calculate_used_corner_radius_values(CSSPixelSize viewport_size) const
{ {
// 1. Let rx and ry be length values. // 1. Let rx and ry be length values.
float rx = 0; float rx = 0;
@ -125,27 +125,27 @@ Gfx::FloatSize SVGRectElement::calculate_used_corner_radius_values() const
} }
// 3. Otherwise, if a properly specified value is provided for rx, but not for ry, then set both rx and ry to the value of rx. // 3. Otherwise, if a properly specified value is provided for rx, but not for ry, then set both rx and ry to the value of rx.
else if (m_radius_x.has_value() && !m_radius_y.has_value()) { else if (m_radius_x.has_value() && !m_radius_y.has_value()) {
rx = m_radius_x.value(); rx = m_radius_x.value().resolve_relative_to(viewport_size.width().to_float());
ry = m_radius_x.value(); ry = m_radius_x.value().resolve_relative_to(viewport_size.width().to_float());
} }
// 4. Otherwise, if a properly specified value is provided for ry, but not for rx, then set both rx and ry to the value of ry. // 4. Otherwise, if a properly specified value is provided for ry, but not for rx, then set both rx and ry to the value of ry.
else if (m_radius_y.has_value() && !m_radius_x.has_value()) { else if (m_radius_y.has_value() && !m_radius_x.has_value()) {
rx = m_radius_y.value(); rx = m_radius_y.value().resolve_relative_to(viewport_size.height().to_float());
ry = m_radius_y.value(); ry = m_radius_y.value().resolve_relative_to(viewport_size.height().to_float());
} }
// 5. Otherwise, both rx and ry were specified properly. Set rx to the value of rx and ry to the value of ry. // 5. Otherwise, both rx and ry were specified properly. Set rx to the value of rx and ry to the value of ry.
else { else {
rx = m_radius_x.value(); rx = m_radius_x.value().resolve_relative_to(viewport_size.width().to_float());
ry = m_radius_y.value(); ry = m_radius_y.value().resolve_relative_to(viewport_size.height().to_float());
} }
// 6. If rx is greater than half of width, then set rx to half of width. // 6. If rx is greater than half of width, then set rx to half of width.
auto half_width = m_width.value_or(0) / 2; auto half_width = m_width.value_or(NumberPercentage::create_number(0)).resolve_relative_to(viewport_size.width().to_float()) / 2;
if (rx > half_width) if (rx > half_width)
rx = half_width; rx = half_width;
// 7. If ry is greater than half of height, then set ry to half of height. // 7. If ry is greater than half of height, then set ry to half of height.
auto half_height = m_height.value_or(0) / 2; auto half_height = m_height.value_or(NumberPercentage::create_number(0)).resolve_relative_to(viewport_size.height().to_float()) / 2;
if (ry > half_height) if (ry > half_height)
ry = half_height; ry = half_height;
@ -158,8 +158,8 @@ GC::Ref<SVGAnimatedLength> SVGRectElement::x() const
{ {
// FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported. // FIXME: Create a proper animated value when animations are supported.
auto base_length = SVGLength::create(realm(), 0, m_x.value_or(0), SVGLength::ReadOnly::No); auto base_length = SVGLength::create(realm(), 0, m_x.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::No);
auto anim_length = SVGLength::create(realm(), 0, m_x.value_or(0), SVGLength::ReadOnly::Yes); auto anim_length = SVGLength::create(realm(), 0, m_x.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::Yes);
return SVGAnimatedLength::create(realm(), base_length, anim_length); return SVGAnimatedLength::create(realm(), base_length, anim_length);
} }
@ -168,8 +168,8 @@ GC::Ref<SVGAnimatedLength> SVGRectElement::y() const
{ {
// FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported. // FIXME: Create a proper animated value when animations are supported.
auto base_length = SVGLength::create(realm(), 0, m_y.value_or(0), SVGLength::ReadOnly::No); auto base_length = SVGLength::create(realm(), 0, m_y.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::No);
auto anim_length = SVGLength::create(realm(), 0, m_y.value_or(0), SVGLength::ReadOnly::Yes); auto anim_length = SVGLength::create(realm(), 0, m_y.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::Yes);
return SVGAnimatedLength::create(realm(), base_length, anim_length); return SVGAnimatedLength::create(realm(), base_length, anim_length);
} }
@ -178,8 +178,8 @@ GC::Ref<SVGAnimatedLength> SVGRectElement::width() const
{ {
// FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported. // FIXME: Create a proper animated value when animations are supported.
auto base_length = SVGLength::create(realm(), 0, m_width.value_or(0), SVGLength::ReadOnly::No); auto base_length = SVGLength::create(realm(), 0, m_width.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::No);
auto anim_length = SVGLength::create(realm(), 0, m_width.value_or(0), SVGLength::ReadOnly::Yes); auto anim_length = SVGLength::create(realm(), 0, m_width.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::Yes);
return SVGAnimatedLength::create(realm(), base_length, anim_length); return SVGAnimatedLength::create(realm(), base_length, anim_length);
} }
@ -188,8 +188,8 @@ GC::Ref<SVGAnimatedLength> SVGRectElement::height() const
{ {
// FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported. // FIXME: Create a proper animated value when animations are supported.
auto base_length = SVGLength::create(realm(), 0, m_height.value_or(0), SVGLength::ReadOnly::No); auto base_length = SVGLength::create(realm(), 0, m_height.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::No);
auto anim_length = SVGLength::create(realm(), 0, m_height.value_or(0), SVGLength::ReadOnly::Yes); auto anim_length = SVGLength::create(realm(), 0, m_height.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::Yes);
return SVGAnimatedLength::create(realm(), base_length, anim_length); return SVGAnimatedLength::create(realm(), base_length, anim_length);
} }
@ -198,8 +198,8 @@ GC::Ref<SVGAnimatedLength> SVGRectElement::rx() const
{ {
// FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported. // FIXME: Create a proper animated value when animations are supported.
auto base_length = SVGLength::create(realm(), 0, m_radius_x.value_or(0), SVGLength::ReadOnly::No); auto base_length = SVGLength::create(realm(), 0, m_radius_x.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::No);
auto anim_length = SVGLength::create(realm(), 0, m_radius_x.value_or(0), SVGLength::ReadOnly::Yes); auto anim_length = SVGLength::create(realm(), 0, m_radius_x.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::Yes);
return SVGAnimatedLength::create(realm(), base_length, anim_length); return SVGAnimatedLength::create(realm(), base_length, anim_length);
} }
@ -208,8 +208,8 @@ GC::Ref<SVGAnimatedLength> SVGRectElement::ry() const
{ {
// FIXME: Populate the unit type when it is parsed (0 here is "unknown"). // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported. // FIXME: Create a proper animated value when animations are supported.
auto base_length = SVGLength::create(realm(), 0, m_radius_y.value_or(0), SVGLength::ReadOnly::No); auto base_length = SVGLength::create(realm(), 0, m_radius_y.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::No);
auto anim_length = SVGLength::create(realm(), 0, m_radius_y.value_or(0), SVGLength::ReadOnly::Yes); auto anim_length = SVGLength::create(realm(), 0, m_radius_y.value_or(NumberPercentage::create_number(0)).value(), SVGLength::ReadOnly::Yes);
return SVGAnimatedLength::create(realm(), base_length, anim_length); return SVGAnimatedLength::create(realm(), base_length, anim_length);
} }

View file

@ -34,14 +34,14 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
Gfx::FloatSize calculate_used_corner_radius_values() const; Gfx::FloatSize calculate_used_corner_radius_values(CSSPixelSize viewport_size) const;
Optional<float> m_x; Optional<NumberPercentage> m_x;
Optional<float> m_y; Optional<NumberPercentage> m_y;
Optional<float> m_width; Optional<NumberPercentage> m_width;
Optional<float> m_height; Optional<NumberPercentage> m_height;
Optional<float> m_radius_x; Optional<NumberPercentage> m_radius_x;
Optional<float> m_radius_y; Optional<NumberPercentage> m_radius_y;
}; };
} }

View file

@ -0,0 +1,16 @@
Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] children: not-inline
BlockContainer <html> at [0,0] [0+0+0 800 0+0+0] [0+0+0 216 0+0+0] [BFC] children: not-inline
BlockContainer <body> at [8,8] [8+0+0 784 0+0+8] [8+0+0 200 0+0+8] children: inline
frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 300x200] baseline: 200
SVGSVGBox <svg> at [8,8] [0+0+0 300 0+0+0] [0+0+0 200 0+0+0] [SVG] children: not-inline
SVGGeometryBox <rect> at [83,108] [0+0+0 99 0+0+0] [0+0+0 99 0+0+0] children: not-inline
TextNode <#text> (not painted)
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x216]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x200]
SVGSVGPaintable (SVGSVGBox<svg>) [8,8 300x200]
SVGPathPaintable (SVGGeometryBox<rect>) [83,108 99x99]
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x216] [children: 0] (z-index: auto)

View file

@ -0,0 +1 @@
<!doctype html><svg xmlns="http://www.w3.org/2000/svg" width="300" height="200" viewBox="0 0 64 32" version="1.1"><rect x="25%" y="50%" width="33%" height="66%" rx="5%" ry="10%" fill="red"></rect></svg>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

View file

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-33"> <meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-200">
<svg width="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <svg width="200" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<link rel="match" href="../expected/svg-maskContentUnits-ref.html" /> <link rel="match" href="../expected/svg-maskContentUnits-ref.html" />
<mask id="myMask1" maskContentUnits="userSpaceOnUse"> <mask id="myMask1" maskContentUnits="userSpaceOnUse">