diff options
author | MacDue <macdue@dueutil.tech> | 2023-04-10 12:22:06 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2023-04-12 07:40:22 +0200 |
commit | 62f087bd56ab2ae4edd8b71f7f871d34837bc6c3 (patch) | |
tree | 79b2c732c3ecefecc1fa3f7c373ee4dad21dd429 /Userland/Libraries | |
parent | b8d1fae31f080391ccb0cc3f2992af51e8a38f73 (diff) | |
download | serenity-62f087bd56ab2ae4edd8b71f7f871d34837bc6c3.zip |
LibWeb: Add SVG transform parsing
This parses SVG transforms using the syntax from CSS Transforms
Module Level 1. Note: This looks very similar to CSS tranforms, but
the syntax is not compatible. For example, SVG rotate() is
rotate(<a> <x> <y>) where all parameters are unitless numbers whereas
CSS rotate() is rotate(<angle> unit) along with separate rotateX/Y/Z().
(At the same time AttributeParser is updated to use GenericLexer which
makes for easier string matching).
There is work needed for error handling (which AttributeParser does not
deal with very gracefully right now).
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibWeb/SVG/AttributeParser.cpp | 117 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/SVG/AttributeParser.h | 47 |
2 files changed, 156 insertions, 8 deletions
diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp b/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp index 236f707b7a..05ca7537f4 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp +++ b/Userland/Libraries/LibWeb/SVG/AttributeParser.cpp @@ -1,22 +1,30 @@ /* * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> + * Copyright (c) 2023, MacDue <macdue@dueutil.tech> * * SPDX-License-Identifier: BSD-2-Clause */ #include "AttributeParser.h" #include <AK/FloatingPointStringConversions.h> +#include <AK/GenericShorthands.h> #include <AK/StringBuilder.h> #include <ctype.h> namespace Web::SVG { AttributeParser::AttributeParser(StringView source) - : m_source(move(source)) + : m_lexer(source) { } +Optional<Vector<Transform>> AttributeParser::parse_transform(StringView input) +{ + AttributeParser parser { input }; + return parser.parse_transform(); +} + Vector<PathInstruction> AttributeParser::parse_path_data(StringView input) { AttributeParser parser { input }; @@ -361,12 +369,12 @@ float AttributeParser::parse_nonnegative_number() // at the start. That condition should have been checked by the caller. VERIFY(!match('+') && !match('-')); - auto remaining_source_text = m_source.substring_view(m_cursor); + auto remaining_source_text = m_lexer.remaining(); char const* start = remaining_source_text.characters_without_null_termination(); auto maybe_float = parse_first_floating_point<float>(start, start + remaining_source_text.length()); VERIFY(maybe_float.parsed_value()); - m_cursor += maybe_float.end_ptr - start; + m_lexer.ignore(maybe_float.end_ptr - start); return maybe_float.value; } @@ -389,6 +397,109 @@ int AttributeParser::parse_sign() return 1; } +// https://drafts.csswg.org/css-transforms/#svg-syntax +Optional<Vector<Transform>> AttributeParser::parse_transform() +{ + // wsp: + // Either a U+000A LINE FEED, U+000D CARRIAGE RETURN, U+0009 CHARACTER TABULATION, or U+0020 SPACE. + auto wsp = [](char c) { + return AK::first_is_one_of(c, '\n', '\r', '\t', '\f', ' '); + }; + auto consume_whitespace = [&] { + m_lexer.consume_while(wsp); + }; + + auto consume_comma_whitespace = [&] { + consume_whitespace(); + m_lexer.consume_specific(','); + consume_whitespace(); + }; + + // FIXME: AttributeParser currently does not handle invalid parses in most cases (e.g. parse_number()) and just crashes. + auto parse_optional_number = [&](float default_value = 0.0f) { + consume_comma_whitespace(); + if (m_lexer.next_is(isdigit)) + return parse_number(); + return default_value; + }; + + auto parse_function = [&](auto body) -> Optional<Transform> { + consume_whitespace(); + if (!m_lexer.consume_specific('(')) + return {}; + consume_whitespace(); + Transform transform { .operation = Transform::Operation { body() } }; + consume_whitespace(); + if (m_lexer.consume_specific(')')) + return transform; + return {}; + }; + + // NOTE: This looks very similar to the CSS transform but the syntax is not compatible. + Vector<Transform> transform_list; + consume_whitespace(); + while (!done()) { + Optional<Transform> maybe_transform; + if (m_lexer.consume_specific("translate"sv)) { + maybe_transform = parse_function([&] { + Transform::Translate translate {}; + translate.x = parse_number(); + translate.y = parse_optional_number(); + return translate; + }); + } else if (m_lexer.consume_specific("scale"sv)) { + maybe_transform = parse_function([&] { + Transform::Scale scale {}; + scale.x = parse_number(); + scale.y = parse_optional_number(scale.x); + return scale; + }); + } else if (m_lexer.consume_specific("rotate"sv)) { + maybe_transform = parse_function([&] { + Transform::Rotate rotate {}; + rotate.a = parse_number(); + rotate.x = parse_optional_number(); + rotate.y = parse_optional_number(); + return rotate; + }); + } else if (m_lexer.consume_specific("skewX"sv)) { + maybe_transform = parse_function([&] { + Transform::SkewX skew_x {}; + skew_x.a = parse_number(); + return skew_x; + }); + } else if (m_lexer.consume_specific("skewY"sv)) { + maybe_transform = parse_function([&] { + Transform::SkewY skew_y {}; + skew_y.a = parse_number(); + return skew_y; + }); + } else if (m_lexer.consume_specific("matrix"sv)) { + maybe_transform = parse_function([&] { + Transform::Matrix matrix; + matrix.a = parse_number(); + consume_comma_whitespace(); + matrix.b = parse_number(); + consume_comma_whitespace(); + matrix.c = parse_number(); + consume_comma_whitespace(); + matrix.d = parse_number(); + consume_comma_whitespace(); + matrix.e = parse_number(); + consume_comma_whitespace(); + matrix.f = parse_number(); + return matrix; + }); + } + if (maybe_transform.has_value()) + transform_list.append(*maybe_transform); + else + return {}; + consume_comma_whitespace(); + } + return transform_list; +} + bool AttributeParser::match_whitespace() const { if (done()) diff --git a/Userland/Libraries/LibWeb/SVG/AttributeParser.h b/Userland/Libraries/LibWeb/SVG/AttributeParser.h index 618406554a..e7b5e075c5 100644 --- a/Userland/Libraries/LibWeb/SVG/AttributeParser.h +++ b/Userland/Libraries/LibWeb/SVG/AttributeParser.h @@ -7,6 +7,8 @@ #pragma once +#include <AK/GenericLexer.h> +#include <AK/Variant.h> #include <AK/Vector.h> #include <LibGfx/Point.h> @@ -32,6 +34,39 @@ struct PathInstruction { Vector<float> data; }; +struct Transform { + struct Translate { + float x; + float y; + }; + struct Scale { + float x; + float y; + }; + struct Rotate { + float a; + float x; + float y; + }; + struct SkewX { + float a; + }; + struct SkewY { + float a; + }; + struct Matrix { + float a; + float b; + float c; + float d; + float e; + float f; + }; + + using Operation = Variant<Translate, Scale, Rotate, SkewX, SkewY, Matrix>; + Operation operation; +}; + class AttributeParser final { public: ~AttributeParser() = default; @@ -41,6 +76,7 @@ public: static Optional<float> parse_positive_length(StringView input); static Vector<Gfx::FloatPoint> parse_points(StringView input); static Vector<PathInstruction> parse_path_data(StringView input); + static Optional<Vector<Transform>> parse_transform(StringView input); private: AttributeParser(StringView source); @@ -57,6 +93,8 @@ private: void parse_smooth_quadratic_bezier_curveto(); void parse_elliptical_arc(); + Optional<Vector<Transform>> parse_transform(); + float parse_length(); float parse_coordinate(); Vector<float> parse_coordinate_pair(); @@ -79,12 +117,11 @@ private: bool match_length() const; bool match(char c) const { return !done() && ch() == c; } - bool done() const { return m_cursor >= m_source.length(); } - char ch() const { return m_source[m_cursor]; } - char consume() { return m_source[m_cursor++]; } + bool done() const { return m_lexer.is_eof(); } + char ch() const { return m_lexer.peek(); } + char consume() { return m_lexer.consume(); } - StringView m_source; - size_t m_cursor { 0 }; + GenericLexer m_lexer; Vector<PathInstruction> m_instructions; }; |