mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-21 08:48:57 +00:00
LibWeb/SVG: Parse comma-separated SVG viewBox
From the SVG spec The value of the ‘viewBox’ attribute is a list of four numbers <min-x>, <min-y>, <width> and <height>, separated by whitespace and/or a comma... Currently try_parse_view_box will fail to parse the attribute if the values are separated by commas. This change replaces try_parse_view_box with a more correct implementation. It will reside in the AttributeParser.cpp. This new implementation correctly handles comma-separated viewBox values, and is also more robust against invalid inputs. Additionally, it adds a new test case to ensure viewBox values with various syntax are parsed correctly and invalid values are rejected.
This commit is contained in:
parent
c3aa8f0c8d
commit
21ff66c6cb
Notes:
github-actions[bot]
2025-08-30 13:50:27 +00:00
Author: https://github.com/erik-kz
Commit: 21ff66c6cb
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6018
Reviewed-by: https://github.com/AtkinsSJ
Reviewed-by: https://github.com/kalenikaliaksandr ✅
12 changed files with 104 additions and 91 deletions
|
@ -921,7 +921,6 @@ set(SOURCES
|
||||||
SVG/SVGUseElement.cpp
|
SVG/SVGUseElement.cpp
|
||||||
SVG/SVGViewElement.cpp
|
SVG/SVGViewElement.cpp
|
||||||
SVG/TagNames.cpp
|
SVG/TagNames.cpp
|
||||||
SVG/ViewBox.cpp
|
|
||||||
TrustedTypes/TrustedHTML.cpp
|
TrustedTypes/TrustedHTML.cpp
|
||||||
TrustedTypes/TrustedScript.cpp
|
TrustedTypes/TrustedScript.cpp
|
||||||
TrustedTypes/TrustedScriptURL.cpp
|
TrustedTypes/TrustedScriptURL.cpp
|
||||||
|
|
|
@ -702,6 +702,48 @@ Optional<Vector<Transform>> AttributeParser::parse_transform()
|
||||||
return transform_list;
|
return transform_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<ViewBox> AttributeParser::parse_viewbox(StringView input)
|
||||||
|
{
|
||||||
|
AttributeParser parser { input };
|
||||||
|
ViewBox viewbox;
|
||||||
|
|
||||||
|
parser.parse_whitespace();
|
||||||
|
auto maybe_min_x = parser.parse_coordinate();
|
||||||
|
if (maybe_min_x.is_error())
|
||||||
|
return {};
|
||||||
|
viewbox.min_x = maybe_min_x.value();
|
||||||
|
|
||||||
|
if (!parser.match_comma_whitespace())
|
||||||
|
return {};
|
||||||
|
parser.parse_comma_whitespace();
|
||||||
|
auto maybe_min_y = parser.parse_coordinate();
|
||||||
|
if (maybe_min_y.is_error())
|
||||||
|
return {};
|
||||||
|
viewbox.min_y = maybe_min_y.value();
|
||||||
|
|
||||||
|
if (!parser.match_comma_whitespace())
|
||||||
|
return {};
|
||||||
|
parser.parse_comma_whitespace();
|
||||||
|
auto maybe_width = parser.parse_length();
|
||||||
|
if (maybe_width.is_error())
|
||||||
|
return {};
|
||||||
|
viewbox.width = maybe_width.value();
|
||||||
|
|
||||||
|
if (!parser.match_comma_whitespace())
|
||||||
|
return {};
|
||||||
|
parser.parse_comma_whitespace();
|
||||||
|
auto maybe_height = parser.parse_length();
|
||||||
|
if (maybe_height.is_error())
|
||||||
|
return {};
|
||||||
|
viewbox.height = maybe_height.value();
|
||||||
|
|
||||||
|
parser.parse_whitespace();
|
||||||
|
if (!parser.done())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return viewbox;
|
||||||
|
}
|
||||||
|
|
||||||
bool AttributeParser::match_whitespace() const
|
bool AttributeParser::match_whitespace() const
|
||||||
{
|
{
|
||||||
if (done())
|
if (done())
|
||||||
|
|
|
@ -76,6 +76,13 @@ enum class SVGUnits {
|
||||||
UserSpaceOnUse
|
UserSpaceOnUse
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ViewBox {
|
||||||
|
double min_x { 0 };
|
||||||
|
double min_y { 0 };
|
||||||
|
double width { 0 };
|
||||||
|
double height { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
using GradientUnits = SVGUnits;
|
using GradientUnits = SVGUnits;
|
||||||
using MaskUnits = SVGUnits;
|
using MaskUnits = SVGUnits;
|
||||||
using MaskContentUnits = SVGUnits;
|
using MaskContentUnits = SVGUnits;
|
||||||
|
@ -141,6 +148,7 @@ public:
|
||||||
static Optional<PreserveAspectRatio> parse_preserve_aspect_ratio(StringView input);
|
static Optional<PreserveAspectRatio> parse_preserve_aspect_ratio(StringView input);
|
||||||
static Optional<SVGUnits> parse_units(StringView input);
|
static Optional<SVGUnits> parse_units(StringView input);
|
||||||
static Optional<SpreadMethod> parse_spread_method(StringView input);
|
static Optional<SpreadMethod> parse_spread_method(StringView input);
|
||||||
|
static Optional<ViewBox> parse_viewbox(StringView input);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AttributeParser(StringView source);
|
AttributeParser(StringView source);
|
||||||
|
|
|
@ -28,7 +28,7 @@ void SVGFitToViewBox::attribute_changed(DOM::Element& element, FlyString const&
|
||||||
if (!value.has_value()) {
|
if (!value.has_value()) {
|
||||||
m_view_box_for_bindings->set_nulled(true);
|
m_view_box_for_bindings->set_nulled(true);
|
||||||
} else {
|
} else {
|
||||||
m_view_box = try_parse_view_box(value.value_or(String {}));
|
m_view_box = AttributeParser::parse_viewbox(value.value_or(String {}));
|
||||||
m_view_box_for_bindings->set_nulled(!m_view_box.has_value());
|
m_view_box_for_bindings->set_nulled(!m_view_box.has_value());
|
||||||
if (m_view_box.has_value()) {
|
if (m_view_box.has_value()) {
|
||||||
m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
|
m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height });
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
#include <LibJS/Heap/Cell.h>
|
#include <LibJS/Heap/Cell.h>
|
||||||
#include <LibWeb/SVG/AttributeParser.h>
|
#include <LibWeb/SVG/AttributeParser.h>
|
||||||
#include <LibWeb/SVG/SVGAnimatedString.h>
|
#include <LibWeb/SVG/SVGAnimatedString.h>
|
||||||
#include <LibWeb/SVG/ViewBox.h>
|
|
||||||
|
|
||||||
namespace Web::SVG {
|
namespace Web::SVG {
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include <LibWeb/SVG/SVGFitToViewBox.h>
|
#include <LibWeb/SVG/SVGFitToViewBox.h>
|
||||||
#include <LibWeb/SVG/SVGGradientElement.h>
|
#include <LibWeb/SVG/SVGGradientElement.h>
|
||||||
#include <LibWeb/SVG/TagNames.h>
|
#include <LibWeb/SVG/TagNames.h>
|
||||||
#include <LibWeb/SVG/ViewBox.h>
|
|
||||||
|
|
||||||
namespace Web::SVG {
|
namespace Web::SVG {
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
#include <LibWeb/SVG/SVGGraphicsElement.h>
|
#include <LibWeb/SVG/SVGGraphicsElement.h>
|
||||||
#include <LibWeb/SVG/SVGLength.h>
|
#include <LibWeb/SVG/SVGLength.h>
|
||||||
#include <LibWeb/SVG/SVGTransform.h>
|
#include <LibWeb/SVG/SVGTransform.h>
|
||||||
#include <LibWeb/SVG/ViewBox.h>
|
|
||||||
#include <LibWeb/WebIDL/Types.h>
|
#include <LibWeb/WebIDL/Types.h>
|
||||||
|
|
||||||
namespace Web::SVG {
|
namespace Web::SVG {
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AK/CharacterTypes.h>
|
|
||||||
#include <AK/GenericLexer.h>
|
|
||||||
#include <AK/Optional.h>
|
|
||||||
#include <AK/StringView.h>
|
|
||||||
#include <LibWeb/SVG/ViewBox.h>
|
|
||||||
|
|
||||||
namespace Web::SVG {
|
|
||||||
|
|
||||||
Optional<ViewBox> try_parse_view_box(StringView string)
|
|
||||||
{
|
|
||||||
// FIXME: This should handle all valid viewBox values.
|
|
||||||
|
|
||||||
GenericLexer lexer(string);
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
MinX,
|
|
||||||
MinY,
|
|
||||||
Width,
|
|
||||||
Height,
|
|
||||||
};
|
|
||||||
int state { State::MinX };
|
|
||||||
ViewBox view_box;
|
|
||||||
|
|
||||||
while (!lexer.is_eof()) {
|
|
||||||
lexer.consume_while([](auto ch) { return is_ascii_space(ch); });
|
|
||||||
auto token = lexer.consume_until([](auto ch) { return is_ascii_space(ch) && ch != ','; });
|
|
||||||
auto maybe_number = token.to_number<float>();
|
|
||||||
if (!maybe_number.has_value())
|
|
||||||
return {};
|
|
||||||
switch (state) {
|
|
||||||
case State::MinX:
|
|
||||||
view_box.min_x = maybe_number.value();
|
|
||||||
break;
|
|
||||||
case State::MinY:
|
|
||||||
view_box.min_y = maybe_number.value();
|
|
||||||
break;
|
|
||||||
case State::Width:
|
|
||||||
if (*maybe_number < 0)
|
|
||||||
return {};
|
|
||||||
view_box.width = maybe_number.value();
|
|
||||||
break;
|
|
||||||
case State::Height:
|
|
||||||
if (*maybe_number < 0)
|
|
||||||
return {};
|
|
||||||
view_box.height = maybe_number.value();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
state += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return view_box;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AK/Forward.h>
|
|
||||||
#include <LibWeb/Forward.h>
|
|
||||||
|
|
||||||
namespace Web::SVG {
|
|
||||||
|
|
||||||
struct ViewBox {
|
|
||||||
double min_x { 0 };
|
|
||||||
double min_y { 0 };
|
|
||||||
double width { 0 };
|
|
||||||
double height { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
Optional<ViewBox> try_parse_view_box(StringView);
|
|
||||||
|
|
||||||
}
|
|
|
@ -48,6 +48,5 @@ source_set("SVG") {
|
||||||
"SVGTransformList.cpp",
|
"SVGTransformList.cpp",
|
||||||
"SVGUseElement.cpp",
|
"SVGUseElement.cpp",
|
||||||
"TagNames.cpp",
|
"TagNames.cpp",
|
||||||
"ViewBox.cpp",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
18
Tests/LibWeb/Text/expected/SVG/svg-viewbox-syntax.txt
Normal file
18
Tests/LibWeb/Text/expected/SVG/svg-viewbox-syntax.txt
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
null
|
||||||
|
null
|
||||||
|
null
|
||||||
|
null
|
||||||
|
null
|
||||||
|
null
|
35
Tests/LibWeb/Text/input/SVG/svg-viewbox-syntax.html
Normal file
35
Tests/LibWeb/Text/input/SVG/svg-viewbox-syntax.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="display: none" id="svg-element"></svg>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const svgElement = document.getElementById("svg-element");
|
||||||
|
svgElement.setAttribute("viewBox", "0,1,2,3");
|
||||||
|
println(svgElement.viewBox.baseVal.x);
|
||||||
|
println(svgElement.viewBox.baseVal.y);
|
||||||
|
println(svgElement.viewBox.baseVal.width);
|
||||||
|
println(svgElement.viewBox.baseVal.height);
|
||||||
|
svgElement.setAttribute("viewBox", " 4 , 5 , 6 , 7 ");
|
||||||
|
println(svgElement.viewBox.baseVal.x);
|
||||||
|
println(svgElement.viewBox.baseVal.y);
|
||||||
|
println(svgElement.viewBox.baseVal.width);
|
||||||
|
println(svgElement.viewBox.baseVal.height);
|
||||||
|
svgElement.setAttribute("viewBox", "8 9,10 11");
|
||||||
|
println(svgElement.viewBox.baseVal.x);
|
||||||
|
println(svgElement.viewBox.baseVal.y);
|
||||||
|
println(svgElement.viewBox.baseVal.width);
|
||||||
|
println(svgElement.viewBox.baseVal.height);
|
||||||
|
svgElement.setAttribute("viewBox", "");
|
||||||
|
println(svgElement.viewBox.baseVal);
|
||||||
|
svgElement.setAttribute("viewBox", " , , , ");
|
||||||
|
println(svgElement.viewBox.baseVal);
|
||||||
|
svgElement.setAttribute("viewBox", "12");
|
||||||
|
println(svgElement.viewBox.baseVal);
|
||||||
|
svgElement.setAttribute("viewBox", "13,");
|
||||||
|
println(svgElement.viewBox.baseVal);
|
||||||
|
svgElement.setAttribute("viewBox", ",14");
|
||||||
|
println(svgElement.viewBox.baseVal);
|
||||||
|
svgElement.setAttribute("viewBox", "15,16,17,18,");
|
||||||
|
println(svgElement.viewBox.baseVal);
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue