LibWeb: Use ErrorOr to propagate SVG attribute parsing errors

If an unexpected token is encountered when parsing an SVG attribute it
is now immediately propagated with ErrorOr. Previously, some situations
where an unexpected token was encountered could cause a crash.
This commit is contained in:
Tim Ledbetter 2024-03-19 23:45:34 +00:00 committed by Andreas Kling
parent a55394c383
commit c8b219914e
8 changed files with 405 additions and 122 deletions

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x1="0" y1="0" x2="10" y2="0" stroke="black" />
</svg>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<div>
<svg width="800" height="100">
<text x="0" y="50" font-size="1em">This should be drawn</text>
</svg>
</div>
<div>
<svg width="100" height="100">
<g stroke="black">
<path d="M 0 50 A 30 20, 0, 0 0, 90 50" />
<path stroke-width="5" d="M 50 0 V 50" />
</g>
</svg>
</div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>
<div><svg width="100" height="100"></svg></div>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<svg viewBox="0 0 10 10" width="100" height="100"></svg>
<svg viewBox="0 0 10 10" width="100" height="100"></svg>
<svg viewBox="0 0 10 10" width="100" height="100"></svg>
<svg viewBox="0 0 10 10" width="100" height="100"></svg>
<svg viewBox="0 0 10 10" width="100" height="100"></svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10 10,0" />
</svg>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<link rel="match" href="reference/svg-invalid-number-arguments-ref.html" />
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="-" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="+" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="." stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="--" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="++" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="+." stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2="-." stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2=".+" stroke="black" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<line x2="10" y2=".-" stroke="black" />
</svg>

View file

@ -0,0 +1,99 @@
<!DOCTYPE html>
<link rel="match" href="reference/svg-path-incomplete-args-ref.html" />
<div>
<svg width="800" height="100">
<g stroke="black">
<path d="invalid" />
</g>
<text x="0" y="50" font-size="1em">This should be drawn</text>
</svg>
</div>
<div>
<svg width="100" height="100">
<g stroke="black">
<path d="M 0 50 A 30 20, 0, 0 0, 90 50 this elliptical arc should be drawn" />
<path stroke-width="5" d="M 50 0 V 50 this vertical line should be drawn" />
</g>
</svg>
</div>
<div>
<svg width="100" height="100">
<!--This won't be drawn since paths should start with an M command-->
<path stroke="black" d="V10" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 VH 10" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 L 10" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" stroke-width="5" fill="transparent" d="M 10 10 C 20" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" stroke-width="5" fill="transparent" d="M 10 10 C 20 20, 40 20" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" stroke-width="5" fill="transparent" d="M 10 10 C 20 20, 40 20, 50" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" stroke-width="5" d="M 0 50 L 10" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 A" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 A 30" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 A 30 20," />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 A 30 20, 0" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path d="M 0 50 A 30 20, 0, 0" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 A 30 20, 0, 0 0" />
</svg>
</div>
<div>
<svg width="100" height="100">
<path stroke="black" d="M 0 50 A 30 20, 0, 0 0, 90" />
</svg>
</div>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<link rel="match" href="reference/svg-polygon-with-incomplete-points-ref.html" />
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points=" " />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="," />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="10" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="10," />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10 10," />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10 10" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0 0 0 10 10 10 10 invalid" />
</svg>
<svg viewBox="0 0 10 10" width="100" height="100">
<polygon points="0,0 0,10 10,10 10,0 a complete rectangle should be drawn here" />
</svg>

View file

@ -2,6 +2,7 @@
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
* Copyright (c) 2024, Tim Ledbetter <timledbetter@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -29,8 +30,11 @@ Vector<PathInstruction> AttributeParser::parse_path_data(StringView input)
{
AttributeParser parser { input };
parser.parse_whitespace();
while (!parser.done())
parser.parse_drawto();
while (!parser.done()) {
auto maybe_error = parser.parse_drawto();
if (maybe_error.is_error())
break;
}
if (!parser.m_instructions.is_empty() && parser.m_instructions[0].type != PathInstructionType::Move) {
// Invalid. "A path data segment (if there is one) must begin with a "moveto" command."
return {};
@ -42,13 +46,12 @@ Optional<float> AttributeParser::parse_coordinate(StringView input)
{
AttributeParser parser { input };
parser.parse_whitespace();
if (parser.match_coordinate()) {
float result = parser.parse_coordinate();
parser.parse_whitespace();
if (parser.done())
return result;
}
auto result_or_error = parser.parse_coordinate();
if (result_or_error.is_error())
return {};
parser.parse_whitespace();
if (parser.done())
return result_or_error.value();
return {};
}
@ -56,12 +59,12 @@ Optional<float> AttributeParser::parse_length(StringView input)
{
AttributeParser parser { input };
parser.parse_whitespace();
if (parser.match_coordinate()) {
float result = parser.parse_length();
parser.parse_whitespace();
if (parser.done())
return result;
}
auto result_or_error = parser.parse_length();
if (result_or_error.is_error())
return {};
parser.parse_whitespace();
if (parser.done())
return result_or_error.value();
return {};
}
@ -77,15 +80,16 @@ Optional<NumberPercentage> AttributeParser::parse_number_percentage(StringView i
{
AttributeParser parser { input };
parser.parse_whitespace();
if (parser.match_number()) {
float number = parser.parse_number();
bool is_percentage = parser.match('%');
if (is_percentage)
parser.consume();
parser.parse_whitespace();
if (parser.done())
return NumberPercentage(number, is_percentage);
}
auto number_or_error = parser.parse_number();
if (number_or_error.is_error())
return {};
bool is_percentage = parser.match('%');
if (is_percentage)
parser.consume();
parser.parse_whitespace();
if (parser.done())
return NumberPercentage(number_or_error.value(), is_percentage);
return {};
}
@ -106,16 +110,12 @@ Vector<Gfx::FloatPoint> AttributeParser::parse_points(StringView input)
parser.parse_whitespace();
// FIXME: "If an odd number of coordinates is provided, then the element is in error, with the same user agent behavior
// as occurs with an incorrectly specified path element. In such error cases the user agent will drop the last,
// odd coordinate and otherwise render the shape."
// The parser currently doesn't notice that there is a missing coordinate, so make it notice!
auto coordinate_pair_sequence = parser.parse_coordinate_pair_sequence();
parser.parse_whitespace();
if (!parser.done())
auto coordinate_pair_sequence_or_error = parser.parse_coordinate_pair_sequence();
if (coordinate_pair_sequence_or_error.is_error())
return {};
auto coordinate_pair_sequence = coordinate_pair_sequence_or_error.release_value();
// FIXME: This is awkward. Can we return Gfx::FloatPoints from some of these parsing methods instead of Vector<float>?
Vector<Gfx::FloatPoint> points;
points.ensure_capacity(coordinate_pair_sequence.size());
@ -126,47 +126,50 @@ Vector<Gfx::FloatPoint> AttributeParser::parse_points(StringView input)
return points;
}
void AttributeParser::parse_drawto()
ErrorOr<void> AttributeParser::parse_drawto()
{
if (match('M') || match('m')) {
parse_moveto();
return parse_moveto();
} else if (match('Z') || match('z')) {
parse_closepath();
return {};
} else if (match('L') || match('l')) {
parse_lineto();
return parse_lineto();
} else if (match('H') || match('h')) {
parse_horizontal_lineto();
return parse_horizontal_lineto();
} else if (match('V') || match('v')) {
parse_vertical_lineto();
return parse_vertical_lineto();
} else if (match('C') || match('c')) {
parse_curveto();
return parse_curveto();
} else if (match('S') || match('s')) {
parse_smooth_curveto();
return parse_smooth_curveto();
} else if (match('Q') || match('q')) {
parse_quadratic_bezier_curveto();
return parse_quadratic_bezier_curveto();
} else if (match('T') || match('t')) {
parse_smooth_quadratic_bezier_curveto();
return parse_smooth_quadratic_bezier_curveto();
} else if (match('A') || match('a')) {
parse_elliptical_arc();
} else {
dbgln("AttributeParser::parse_drawto failed to match: '{}'", ch());
TODO();
return parse_elliptical_arc();
}
dbgln("AttributeParser::parse_drawto failed to match: '{}'", ch());
return Error::from_string_literal("Invalid drawto command");
}
// https://www.w3.org/TR/SVG2/paths.html#PathDataMovetoCommands
void AttributeParser::parse_moveto()
ErrorOr<void> AttributeParser::parse_moveto()
{
bool absolute = consume() == 'M';
parse_whitespace();
bool is_first = true;
for (auto& pair : parse_coordinate_pair_sequence()) {
for (auto& pair : TRY(parse_coordinate_pair_sequence())) {
// NOTE: "M 1 2 3 4" is equivalent to "M 1 2 L 3 4".
auto type = is_first ? PathInstructionType::Move : PathInstructionType::Line;
m_instructions.append({ type, absolute, pair });
is_first = false;
}
return {};
}
void AttributeParser::parse_closepath()
@ -176,128 +179,152 @@ void AttributeParser::parse_closepath()
m_instructions.append({ PathInstructionType::ClosePath, absolute, {} });
}
void AttributeParser::parse_lineto()
ErrorOr<void> AttributeParser::parse_lineto()
{
bool absolute = consume() == 'L';
parse_whitespace();
for (auto& pair : parse_coordinate_pair_sequence())
for (auto& pair : TRY(parse_coordinate_pair_sequence()))
m_instructions.append({ PathInstructionType::Line, absolute, pair });
return {};
}
void AttributeParser::parse_horizontal_lineto()
ErrorOr<void> AttributeParser::parse_horizontal_lineto()
{
bool absolute = consume() == 'H';
parse_whitespace();
for (auto coordinate : parse_coordinate_sequence())
for (auto coordinate : TRY(parse_coordinate_sequence()))
m_instructions.append({ PathInstructionType::HorizontalLine, absolute, { coordinate } });
return {};
}
void AttributeParser::parse_vertical_lineto()
ErrorOr<void> AttributeParser::parse_vertical_lineto()
{
bool absolute = consume() == 'V';
parse_whitespace();
for (auto coordinate : parse_coordinate_sequence())
for (auto coordinate : TRY(parse_coordinate_sequence()))
m_instructions.append({ PathInstructionType::VerticalLine, absolute, { coordinate } });
return {};
}
void AttributeParser::parse_curveto()
ErrorOr<void> AttributeParser::parse_curveto()
{
bool absolute = consume() == 'C';
parse_whitespace();
while (true) {
m_instructions.append({ PathInstructionType::Curve, absolute, parse_coordinate_pair_triplet() });
m_instructions.append({ PathInstructionType::Curve, absolute, TRY(parse_coordinate_pair_triplet()) });
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_coordinate())
break;
}
return {};
}
void AttributeParser::parse_smooth_curveto()
ErrorOr<void> AttributeParser::parse_smooth_curveto()
{
bool absolute = consume() == 'S';
parse_whitespace();
while (true) {
m_instructions.append({ PathInstructionType::SmoothCurve, absolute, parse_coordinate_pair_double() });
m_instructions.append({ PathInstructionType::SmoothCurve, absolute, TRY(parse_coordinate_pair_double()) });
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_coordinate())
break;
}
return {};
}
void AttributeParser::parse_quadratic_bezier_curveto()
ErrorOr<void> AttributeParser::parse_quadratic_bezier_curveto()
{
bool absolute = consume() == 'Q';
parse_whitespace();
while (true) {
m_instructions.append({ PathInstructionType::QuadraticBezierCurve, absolute, parse_coordinate_pair_double() });
m_instructions.append({ PathInstructionType::QuadraticBezierCurve, absolute, TRY(parse_coordinate_pair_double()) });
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_coordinate())
break;
}
return {};
}
void AttributeParser::parse_smooth_quadratic_bezier_curveto()
ErrorOr<void> AttributeParser::parse_smooth_quadratic_bezier_curveto()
{
bool absolute = consume() == 'T';
parse_whitespace();
while (true) {
m_instructions.append({ PathInstructionType::SmoothQuadraticBezierCurve, absolute, parse_coordinate_pair() });
m_instructions.append({ PathInstructionType::SmoothQuadraticBezierCurve, absolute, TRY(parse_coordinate_pair()) });
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_coordinate())
break;
}
return {};
}
void AttributeParser::parse_elliptical_arc()
ErrorOr<void> AttributeParser::parse_elliptical_arc()
{
bool absolute = consume() == 'A';
parse_whitespace();
while (true) {
m_instructions.append({ PathInstructionType::EllipticalArc, absolute, parse_elliptical_arc_argument() });
auto argument = TRY(parse_elliptical_arc_argument());
m_instructions.append({ PathInstructionType::EllipticalArc, absolute, move(argument) });
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_coordinate())
break;
}
return {};
}
float AttributeParser::parse_length()
ErrorOr<float> AttributeParser::parse_length()
{
// https://www.w3.org/TR/SVG11/types.html#DataTypeLength
return parse_number();
}
float AttributeParser::parse_coordinate()
ErrorOr<float> AttributeParser::parse_coordinate()
{
// https://www.w3.org/TR/SVG11/types.html#DataTypeCoordinate
// coordinate ::= length
return parse_length();
}
Vector<float> AttributeParser::parse_coordinate_pair()
ErrorOr<Vector<float>> AttributeParser::parse_coordinate_pair()
{
Vector<float> coordinates;
coordinates.append(parse_coordinate());
coordinates.append(TRY(parse_coordinate()));
if (match_comma_whitespace())
parse_comma_whitespace();
coordinates.append(parse_coordinate());
coordinates.append(TRY(parse_coordinate()));
return coordinates;
}
Vector<float> AttributeParser::parse_coordinate_sequence()
ErrorOr<Vector<float>> AttributeParser::parse_coordinate_sequence()
{
Vector<float> sequence;
bool is_first = true;
while (true) {
sequence.append(parse_coordinate());
auto coordinate_or_error = parse_coordinate();
if (coordinate_or_error.is_error()) {
if (is_first)
return Error::from_string_literal("Expected coordinate sequence");
}
is_first = false;
sequence.append(coordinate_or_error.release_value());
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_comma_whitespace() && !match_coordinate())
@ -306,11 +333,19 @@ Vector<float> AttributeParser::parse_coordinate_sequence()
return sequence;
}
Vector<Vector<float>> AttributeParser::parse_coordinate_pair_sequence()
ErrorOr<Vector<Vector<float>>> AttributeParser::parse_coordinate_pair_sequence()
{
Vector<Vector<float>> sequence;
bool is_first = true;
while (true) {
sequence.append(parse_coordinate_pair());
auto coordinate_pair_or_error = parse_coordinate_pair();
if (coordinate_pair_or_error.is_error()) {
if (is_first)
return Error::from_string_literal("Expected coordinate pair sequence");
break;
}
is_first = false;
sequence.append(coordinate_pair_or_error.release_value());
if (match_comma_whitespace())
parse_comma_whitespace();
if (!match_comma_whitespace() && !match_coordinate())
@ -319,47 +354,48 @@ Vector<Vector<float>> AttributeParser::parse_coordinate_pair_sequence()
return sequence;
}
Vector<float> AttributeParser::parse_coordinate_pair_double()
ErrorOr<Vector<float>> AttributeParser::parse_coordinate_pair_double()
{
Vector<float> coordinates;
coordinates.extend(parse_coordinate_pair());
coordinates.extend(TRY(parse_coordinate_pair()));
if (match_comma_whitespace())
parse_comma_whitespace();
coordinates.extend(parse_coordinate_pair());
coordinates.extend(TRY(parse_coordinate_pair()));
return coordinates;
}
Vector<float> AttributeParser::parse_coordinate_pair_triplet()
ErrorOr<Vector<float>> AttributeParser::parse_coordinate_pair_triplet()
{
Vector<float> coordinates;
coordinates.extend(parse_coordinate_pair());
coordinates.extend(TRY(parse_coordinate_pair()));
if (match_comma_whitespace())
parse_comma_whitespace();
coordinates.extend(parse_coordinate_pair());
coordinates.extend(TRY(parse_coordinate_pair()));
if (match_comma_whitespace())
parse_comma_whitespace();
coordinates.extend(parse_coordinate_pair());
coordinates.extend(TRY(parse_coordinate_pair()));
return coordinates;
}
Vector<float> AttributeParser::parse_elliptical_arc_argument()
ErrorOr<Vector<float>> AttributeParser::parse_elliptical_arc_argument()
{
Vector<float> numbers;
numbers.append(parse_number());
numbers.append(TRY(parse_number()));
if (match_comma_whitespace())
parse_comma_whitespace();
numbers.append(parse_number());
numbers.append(TRY(parse_number()));
if (match_comma_whitespace())
parse_comma_whitespace();
numbers.append(parse_number());
parse_comma_whitespace();
numbers.append(parse_flag());
numbers.append(TRY(parse_number()));
if (match_comma_whitespace())
parse_comma_whitespace();
numbers.append(parse_flag());
numbers.append(TRY(parse_flag()));
if (match_comma_whitespace())
parse_comma_whitespace();
numbers.extend(parse_coordinate_pair());
numbers.append(TRY(parse_flag()));
if (match_comma_whitespace())
parse_comma_whitespace();
numbers.extend(TRY(parse_coordinate_pair()));
return numbers;
}
@ -389,18 +425,19 @@ void AttributeParser::parse_comma_whitespace()
}
// https://www.w3.org/TR/SVG11/types.html#DataTypeNumber
float AttributeParser::parse_number()
ErrorOr<float> AttributeParser::parse_number()
{
auto sign = parse_sign();
return sign * parse_nonnegative_number();
return sign * TRY(parse_nonnegative_number());
}
// https://www.w3.org/TR/SVG11/paths.html#PathDataBNF
float AttributeParser::parse_nonnegative_number()
ErrorOr<float> AttributeParser::parse_nonnegative_number()
{
// NOTE: The grammar is almost a floating point except we cannot have a sign
// at the start. That condition should have been checked by the caller.
VERIFY(!match('+') && !match('-'));
if (match('+') || match('-') || !match_number())
return Error::from_string_literal("Expected number");
auto remaining_source_text = m_lexer.remaining();
char const* start = remaining_source_text.characters_without_null_termination();
@ -412,10 +449,10 @@ float AttributeParser::parse_nonnegative_number()
return maybe_float.value;
}
float AttributeParser::parse_flag()
ErrorOr<float> AttributeParser::parse_flag()
{
if (!match('0') && !match('1'))
VERIFY_NOT_REACHED();
return Error::from_string_literal("Expected flag");
return consume() - '0';
}
@ -542,15 +579,17 @@ Optional<Vector<Transform>> AttributeParser::parse_transform()
// FIXME: This parsing is quite lenient, so will accept (with default values) some transforms that should be rejected.
auto parse_optional_number = [&](float default_value = 0.0f) {
consume_comma_whitespace();
if (match_number())
return parse_number();
return default_value;
auto number_or_error = parse_number();
if (number_or_error.is_error())
return default_value;
return number_or_error.value();
};
auto try_parse_number = [&]() -> Optional<float> {
if (match_number())
return parse_number();
return {};
auto number_or_error = parse_number();
if (number_or_error.is_error())
return {};
return number_or_error.value();
};
auto parse_function = [&](auto body) -> Optional<Transform> {
@ -691,7 +730,17 @@ bool AttributeParser::match_number() const
bool AttributeParser::match_length() const
{
return !done() && (isdigit(ch()) || ch() == '-' || ch() == '+' || ch() == '.');
if (done())
return false;
size_t offset = 0;
if (ch() == '-' || ch() == '+')
offset++;
if (ch(offset) == '.')
offset++;
return !done() && isdigit(ch(offset));
}
}

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2024, Tim Ledbetter <timledbetter@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -159,33 +160,33 @@ public:
private:
AttributeParser(StringView source);
void parse_drawto();
void parse_moveto();
ErrorOr<void> parse_drawto();
ErrorOr<void> parse_moveto();
void parse_closepath();
void parse_lineto();
void parse_horizontal_lineto();
void parse_vertical_lineto();
void parse_curveto();
void parse_smooth_curveto();
void parse_quadratic_bezier_curveto();
void parse_smooth_quadratic_bezier_curveto();
void parse_elliptical_arc();
ErrorOr<void> parse_lineto();
ErrorOr<void> parse_horizontal_lineto();
ErrorOr<void> parse_vertical_lineto();
ErrorOr<void> parse_curveto();
ErrorOr<void> parse_smooth_curveto();
ErrorOr<void> parse_quadratic_bezier_curveto();
ErrorOr<void> parse_smooth_quadratic_bezier_curveto();
ErrorOr<void> parse_elliptical_arc();
Optional<Vector<Transform>> parse_transform();
float parse_length();
float parse_coordinate();
Vector<float> parse_coordinate_pair();
Vector<float> parse_coordinate_sequence();
Vector<Vector<float>> parse_coordinate_pair_sequence();
Vector<float> parse_coordinate_pair_double();
Vector<float> parse_coordinate_pair_triplet();
Vector<float> parse_elliptical_arc_argument();
ErrorOr<float> parse_length();
ErrorOr<float> parse_coordinate();
ErrorOr<Vector<float>> parse_coordinate_pair();
ErrorOr<Vector<float>> parse_coordinate_sequence();
ErrorOr<Vector<Vector<float>>> parse_coordinate_pair_sequence();
ErrorOr<Vector<float>> parse_coordinate_pair_double();
ErrorOr<Vector<float>> parse_coordinate_pair_triplet();
ErrorOr<Vector<float>> parse_elliptical_arc_argument();
void parse_whitespace(bool must_match_once = false);
void parse_comma_whitespace();
float parse_number();
float parse_nonnegative_number();
float parse_flag();
ErrorOr<float> parse_number();
ErrorOr<float> parse_nonnegative_number();
ErrorOr<float> parse_flag();
// -1 if negative, +1 otherwise
int parse_sign();
@ -197,7 +198,7 @@ private:
bool match(char c) const { return !done() && ch() == c; }
bool done() const { return m_lexer.is_eof(); }
char ch() const { return m_lexer.peek(); }
char ch(size_t offset = 0) const { return m_lexer.peek(offset); }
char consume() { return m_lexer.consume(); }
GenericLexer m_lexer;