From 8057542deae0ec6fa02ba196f7391c40ba6957d5 Mon Sep 17 00:00:00 2001 From: MacDue Date: Sun, 17 Mar 2024 20:23:17 +0000 Subject: [PATCH] LibGfx: Simplify path storage and tidy up APIs Rather than make path segments virtual and refcounted let's store `Gfx::Path`s as a list of `FloatPoints` and a separate list of commands. This reduces the size of paths, for example, a `MoveTo` goes from 24 bytes to 9 bytes (one point + a single byte command), and removes a layer of indirection when accessing segments. A nice little bonus is transforming a path can now be done by applying the transform to all points in the path (without looking at the commands). Alongside this there's been a few minor API changes: - `path.segments()` has been removed * All current uses could be replaced by a new `path.is_empty()` API * There's also now an iterator for looping over `Gfx::Path` segments - `path.add_path(other_path)` has been removed * This was a duplicate of `path.append_path(other_path)` - `path.ensure_subpath(point)` has been removed * Had one use and is equivalent to an `is_empty()` check + `move_to()` - `path.close()` and `path.close_all_subpaths()` assume an implicit `moveto 0,0` if there's no `moveto` at the start of a path (for consistency with `path.segmentize_path()`). Only the last point could change behaviour (though in LibWeb/SVGs all paths start with a `moveto` as per the spec, it's only possible to construct a path without a starting `moveto` via LibGfx APIs). --- .../LibGfx/EdgeFlagPathRasterizer.cpp | 2 +- Userland/Libraries/LibGfx/Font/VectorFont.h | 1 + .../LibGfx/ImageFormats/TinyVGLoader.cpp | 4 +- Userland/Libraries/LibGfx/Path.cpp | 212 ++++---------- Userland/Libraries/LibGfx/Path.h | 268 +++++++++++------- Userland/Libraries/LibPDF/Renderer.cpp | 8 +- .../LibWeb/HTML/Canvas/CanvasPath.cpp | 3 +- Userland/Libraries/LibWeb/HTML/Path2D.cpp | 10 +- .../Libraries/LibWeb/SVG/SVGPathElement.cpp | 2 +- 9 files changed, 237 insertions(+), 273 deletions(-) diff --git a/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp b/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp index dc9647446f3..a7c2ecc38cb 100644 --- a/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp +++ b/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp @@ -144,7 +144,7 @@ void EdgeFlagPathRasterizer::fill_internal(Painter& painter, Pa if (m_clip.is_empty()) return; - auto& lines = path.split_lines(); + auto lines = path.split_lines(); if (lines.is_empty()) return; diff --git a/Userland/Libraries/LibGfx/Font/VectorFont.h b/Userland/Libraries/LibGfx/Font/VectorFont.h index 8d295d5af97..b93c8278d1d 100644 --- a/Userland/Libraries/LibGfx/Font/VectorFont.h +++ b/Userland/Libraries/LibGfx/Font/VectorFont.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include diff --git a/Userland/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp b/Userland/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp index ff2fe4dba51..8085ed815bf 100644 --- a/Userland/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp +++ b/Userland/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp @@ -279,10 +279,10 @@ public: path.line_to(TRY(read_point())); break; case PathCommand::HorizontalLine: - path.line_to({ TRY(read_unit()), path.segments().last()->point().y() }); + path.line_to({ TRY(read_unit()), path.last_point().y() }); break; case PathCommand::VerticalLine: - path.line_to({ path.segments().last()->point().x(), TRY(read_unit()) }); + path.line_to({ path.last_point().x(), TRY(read_unit()) }); break; case PathCommand::CubicBezier: { auto control_0 = TRY(read_point()); diff --git a/Userland/Libraries/LibGfx/Path.cpp b/Userland/Libraries/LibGfx/Path.cpp index b2e20c82aec..bfc3f00238f 100644 --- a/Userland/Libraries/LibGfx/Path.cpp +++ b/Userland/Libraries/LibGfx/Path.cpp @@ -4,10 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include #include -#include #include #include #include @@ -80,7 +77,7 @@ void Path::elliptical_arc_to(FloatPoint point, FloatSize radii, float x_axis_rot // Step 1 of out-of-range radii correction if (rx == 0.0 || ry == 0.0) { - append_segment(next_point); + append_segment(next_point); return; } @@ -192,7 +189,7 @@ Path Path::place_text_along(Utf8View text, Font const& font) const return {}; } - auto& lines = split_lines(); + auto lines = split_lines(); auto next_point_for_offset = [&, line_index = 0U, distance_along_path = 0.0f, last_line_length = 0.0f](float offset) mutable -> Optional { while (line_index < lines.size() && offset > distance_along_path) { last_line_length = lines[line_index++].length(); @@ -247,118 +244,79 @@ Path Path::place_text_along(Utf8View text, Font const& font) const return result_path; } -FloatPoint Path::last_point() -{ - FloatPoint last_point { 0, 0 }; - if (!m_segments.is_empty()) - last_point = m_segments.last()->point(); - return last_point; -} - void Path::close() { - if (m_segments.size() <= 1) - return; - - auto last_point = m_segments.last()->point(); - - for (ssize_t i = m_segments.size() - 1; i >= 0; --i) { - auto& segment = m_segments[i]; - if (segment->type() == Segment::Type::MoveTo) { - if (last_point == segment->point()) - return; - append_segment(segment->point()); - invalidate_split_lines(); - return; + // If there's no `moveto` starting this subpath assume the start is (0, 0). + FloatPoint first_point_in_subpath = { 0, 0 }; + for (auto it = end(); it-- != begin();) { + auto segment = *it; + if (segment.command() == PathSegment::MoveTo) { + first_point_in_subpath = segment.point(); + break; } } + if (first_point_in_subpath != last_point()) + line_to(first_point_in_subpath); } void Path::close_all_subpaths() { - if (m_segments.size() <= 1) - return; - - invalidate_split_lines(); - - Optional cursor, start_of_subpath; - bool is_first_point_in_subpath { false }; - - auto close_previous_subpath = [&] { - if (cursor.has_value() && !is_first_point_in_subpath) { - // This is a move from a subpath to another - // connect the two ends of this subpath before - // moving on to the next one - VERIFY(start_of_subpath.has_value()); - - append_segment(cursor.value()); - append_segment(start_of_subpath.value()); + auto it = begin(); + // Note: Get the end outside the loop as closing subpaths will move the end. + auto end = this->end(); + while (it < end) { + // If there's no `moveto` starting this subpath assume the start is (0, 0). + FloatPoint first_point_in_subpath = { 0, 0 }; + auto segment = *it; + if (segment.command() == PathSegment::MoveTo) { + first_point_in_subpath = segment.point(); + ++it; } - }; - - auto segment_count = m_segments.size(); - for (size_t i = 0; i < segment_count; i++) { - // Note: We need to use m_segments[i] as append_segment() may invalidate any references. - switch (m_segments[i]->type()) { - case Segment::Type::MoveTo: { - close_previous_subpath(); - is_first_point_in_subpath = true; - cursor = m_segments[i]->point(); - break; + // Find the end of the current subpath. + FloatPoint cursor = first_point_in_subpath; + while (it < end) { + auto segment = *it; + if (segment.command() == PathSegment::MoveTo) + break; + cursor = segment.point(); + ++it; } - case Segment::Type::LineTo: - case Segment::Type::QuadraticBezierCurveTo: - case Segment::Type::CubicBezierCurveTo: - if (is_first_point_in_subpath) { - start_of_subpath = cursor; - is_first_point_in_subpath = false; - } - cursor = m_segments[i]->point(); - break; - case Segment::Type::Invalid: - VERIFY_NOT_REACHED(); - break; + // Close the subpath. + if (first_point_in_subpath != cursor) { + move_to(cursor); + line_to(first_point_in_subpath); } } - - if (m_segments.last()->type() != Segment::Type::MoveTo) - close_previous_subpath(); } ByteString Path::to_byte_string() const { StringBuilder builder; builder.append("Path { "sv); - for (auto& segment : m_segments) { - switch (segment->type()) { - case Segment::Type::MoveTo: + for (auto segment : *this) { + switch (segment.command()) { + case PathSegment::MoveTo: builder.append("MoveTo"sv); break; - case Segment::Type::LineTo: + case PathSegment::LineTo: builder.append("LineTo"sv); break; - case Segment::Type::QuadraticBezierCurveTo: + case PathSegment::QuadraticBezierCurveTo: builder.append("QuadraticBezierCurveTo"sv); break; - case Segment::Type::CubicBezierCurveTo: + case PathSegment::CubicBezierCurveTo: builder.append("CubicBezierCurveTo"sv); break; - case Segment::Type::Invalid: - builder.append("Invalid"sv); - break; } - builder.appendff("({}", segment->point()); + builder.appendff("({}", segment.point()); - switch (segment->type()) { - case Segment::Type::QuadraticBezierCurveTo: - builder.append(", "sv); - builder.append(static_cast(*segment).through().to_byte_string()); + switch (segment.command()) { + case PathSegment::QuadraticBezierCurveTo: + builder.appendff(", {}"sv, segment.through()); break; - case Segment::Type::CubicBezierCurveTo: - builder.append(", "sv); - builder.append(static_cast(*segment).through_0().to_byte_string()); - builder.append(", "sv); - builder.append(static_cast(*segment).through_1().to_byte_string()); + case PathSegment::CubicBezierCurveTo: + builder.appendff(", {}"sv, segment.through_0()); + builder.appendff(", {}"sv, segment.through_1()); break; default: break; @@ -381,88 +339,44 @@ void Path::segmentize_path() }; FloatPoint cursor { 0, 0 }; - for (auto& segment : m_segments) { - switch (segment->type()) { - case Segment::Type::MoveTo: - bounding_box.add_point(segment->point()); - cursor = segment->point(); + for (auto segment : *this) { + switch (segment.command()) { + case PathSegment::MoveTo: + bounding_box.add_point(segment.point()); break; - case Segment::Type::LineTo: { - add_line(cursor, segment->point()); - cursor = segment->point(); + case PathSegment::LineTo: { + add_line(cursor, segment.point()); break; } - case Segment::Type::QuadraticBezierCurveTo: { - auto control = static_cast(*segment).through(); - Painter::for_each_line_segment_on_bezier_curve(control, cursor, segment->point(), [&](FloatPoint p0, FloatPoint p1) { + case PathSegment::QuadraticBezierCurveTo: { + Painter::for_each_line_segment_on_bezier_curve(segment.through(), cursor, segment.point(), [&](FloatPoint p0, FloatPoint p1) { add_line(p0, p1); }); - cursor = segment->point(); break; } - case Segment::Type::CubicBezierCurveTo: { - auto& curve = static_cast(*segment); - auto control_0 = curve.through_0(); - auto control_1 = curve.through_1(); - Painter::for_each_line_segment_on_cubic_bezier_curve(control_0, control_1, cursor, segment->point(), [&](FloatPoint p0, FloatPoint p1) { + case PathSegment::CubicBezierCurveTo: { + Painter::for_each_line_segment_on_cubic_bezier_curve(segment.through_0(), segment.through_1(), cursor, segment.point(), [&](FloatPoint p0, FloatPoint p1) { add_line(p0, p1); }); - cursor = segment->point(); break; } - case Segment::Type::Invalid: - VERIFY_NOT_REACHED(); } + cursor = segment.point(); } - m_split_lines = move(segments); - m_bounding_box = bounding_box; + m_split_lines = SplitLines { move(segments), bounding_box }; } Path Path::copy_transformed(Gfx::AffineTransform const& transform) const { Path result; - - for (auto const& segment : m_segments) { - switch (segment->type()) { - case Segment::Type::MoveTo: - result.move_to(transform.map(segment->point())); - break; - case Segment::Type::LineTo: { - result.line_to(transform.map(segment->point())); - break; - } - case Segment::Type::QuadraticBezierCurveTo: { - auto const& quadratic_segment = static_cast(*segment); - result.quadratic_bezier_curve_to(transform.map(quadratic_segment.through()), transform.map(segment->point())); - break; - } - case Segment::Type::CubicBezierCurveTo: { - auto const& cubic_segment = static_cast(*segment); - result.cubic_bezier_curve_to(transform.map(cubic_segment.through_0()), transform.map(cubic_segment.through_1()), transform.map(segment->point())); - break; - } - case Segment::Type::Invalid: - VERIFY_NOT_REACHED(); - } - } - + result.m_commands = m_commands; + result.m_points.ensure_capacity(m_points.size()); + for (auto point : m_points) + result.m_points.unchecked_append(transform.map(point)); return result; } -void Path::add_path(Path const& other) -{ - m_segments.extend(other.m_segments); - invalidate_split_lines(); -} - -void Path::ensure_subpath(FloatPoint point) -{ - if (m_need_new_subpath && m_segments.is_empty()) { - move_to(point); - m_need_new_subpath = false; - } -} template struct RoundTrip { RoundTrip(ReadonlySpan span) @@ -498,7 +412,7 @@ Path Path::stroke_to_fill(float thickness) const VERIFY(thickness > 0); - auto& lines = split_lines(); + auto lines = split_lines(); if (lines.is_empty()) return Path {}; diff --git a/Userland/Libraries/LibGfx/Path.h b/Userland/Libraries/LibGfx/Path.h index 59585acccba..71bbd701e1d 100644 --- a/Userland/Libraries/LibGfx/Path.h +++ b/Userland/Libraries/LibGfx/Path.h @@ -7,10 +7,8 @@ #pragma once #include -#include #include #include -#include #include #include #include @@ -18,91 +16,127 @@ namespace Gfx { -class Segment : public RefCounted { +class Path; + +class PathSegment { public: - enum class Type { - Invalid, + enum Command : u8 { MoveTo, LineTo, QuadraticBezierCurveTo, CubicBezierCurveTo, }; - Segment(FloatPoint point) - : m_point(point) + ALWAYS_INLINE Command command() const { return m_command; } + ALWAYS_INLINE FloatPoint point() const { return m_points.last(); } + ALWAYS_INLINE FloatPoint through() const { + VERIFY(m_command == Command::QuadraticBezierCurveTo); + return m_points[0]; + } + ALWAYS_INLINE FloatPoint through_0() const + { + VERIFY(m_command == Command::CubicBezierCurveTo); + return m_points[0]; + } + ALWAYS_INLINE FloatPoint through_1() const + { + VERIFY(m_command == Command::CubicBezierCurveTo); + return m_points[1]; } - virtual ~Segment() = default; - - FloatPoint point() const { return m_point; } - virtual Type type() const = 0; - -protected: - FloatPoint m_point; -}; - -class MoveSegment final : public Segment { -public: - MoveSegment(FloatPoint point) - : Segment(point) + static constexpr int points_per_command(Command command) { + switch (command) { + case Command::MoveTo: + case Command::LineTo: + return 1; // Single point. + case Command::QuadraticBezierCurveTo: + return 2; // Control point + point. + case Command::CubicBezierCurveTo: + return 3; // Two control points + point. + } + VERIFY_NOT_REACHED(); } + PathSegment(Command command, ReadonlySpan points) + : m_command(command) + , m_points(points) {}; + private: - virtual Type type() const override { return Segment::Type::MoveTo; } + Command m_command; + ReadonlySpan m_points; }; -class LineSegment final : public Segment { +class PathSegmentIterator { public: - LineSegment(FloatPoint point) - : Segment(point) + int operator<=>(PathSegmentIterator other) const + { + if (m_command_index > other.m_command_index) + return 1; + if (m_command_index < other.m_command_index) + return -1; + return 0; + } + bool operator==(PathSegmentIterator other) const { return m_command_index == other.m_command_index; } + bool operator!=(PathSegmentIterator other) const { return m_command_index != other.m_command_index; } + + PathSegmentIterator operator++() + { + if (m_command_index < m_commands.size()) + m_point_index += PathSegment::points_per_command(m_commands[m_command_index++]); + return *this; + } + PathSegmentIterator operator++(int) + { + PathSegmentIterator old(*this); + ++*this; + return old; + } + + PathSegmentIterator operator--() + { + if (m_command_index > 0) + m_point_index -= PathSegment::points_per_command(m_commands[--m_command_index]); + return *this; + } + PathSegmentIterator operator--(int) + { + PathSegmentIterator old(*this); + --*this; + return old; + } + + PathSegment operator*() const + { + auto command = m_commands[m_command_index]; + return PathSegment { command, m_points.span().slice(m_point_index, PathSegment::points_per_command(command)) }; + } + + PathSegmentIterator& operator=(PathSegmentIterator const& other) + { + m_point_index = other.m_point_index; + m_command_index = other.m_command_index; + return *this; + } + PathSegmentIterator(PathSegmentIterator const&) = default; + + friend Path; + +private: + PathSegmentIterator(Vector const& points, Vector const& commands, size_t point_index = 0, size_t command_index = 0) + : m_points(points) + , m_commands(commands) + , m_point_index(point_index) + , m_command_index(command_index) { } - virtual ~LineSegment() override = default; - -private: - virtual Type type() const override { return Segment::Type::LineTo; } -}; - -class QuadraticBezierCurveSegment final : public Segment { -public: - QuadraticBezierCurveSegment(FloatPoint point, FloatPoint through) - : Segment(point) - , m_through(through) - { - } - - virtual ~QuadraticBezierCurveSegment() override = default; - - FloatPoint through() const { return m_through; } - -private: - virtual Type type() const override { return Segment::Type::QuadraticBezierCurveTo; } - - FloatPoint m_through; -}; - -class CubicBezierCurveSegment final : public Segment { -public: - CubicBezierCurveSegment(FloatPoint point, FloatPoint through_0, FloatPoint through_1) - : Segment(point) - , m_through_0(through_0) - , m_through_1(through_1) - { - } - - virtual ~CubicBezierCurveSegment() override = default; - - FloatPoint through_0() const { return m_through_0; } - FloatPoint through_1() const { return m_through_1; } - -private: - virtual Type type() const override { return Segment::Type::CubicBezierCurveTo; } - - FloatPoint m_through_0; - FloatPoint m_through_1; + // Note: Store reference to vectors from Gfx::Path so appending segments does not invalidate iterators. + Vector const& m_points; + Vector const& m_commands; + size_t m_point_index { 0 }; + size_t m_command_index { 0 }; }; class Path { @@ -111,40 +145,34 @@ public: void move_to(FloatPoint point) { - append_segment(point); + append_segment(point); } void line_to(FloatPoint point) { - append_segment(point); + append_segment(point); invalidate_split_lines(); } void horizontal_line_to(float x) { - float previous_y = 0; - if (!m_segments.is_empty()) - previous_y = m_segments.last()->point().y(); - line_to({ x, previous_y }); + line_to({ x, last_point().y() }); } void vertical_line_to(float y) { - float previous_x = 0; - if (!m_segments.is_empty()) - previous_x = m_segments.last()->point().x(); - line_to({ previous_x, y }); + line_to({ last_point().x(), y }); } void quadratic_bezier_curve_to(FloatPoint through, FloatPoint point) { - append_segment(point, through); + append_segment(point, through); invalidate_split_lines(); } void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) { - append_segment(p2, c1, c2); + append_segment(p2, c1, c2); invalidate_split_lines(); } @@ -156,75 +184,95 @@ public: void text(Utf8View, Font const&); - FloatPoint last_point(); + FloatPoint last_point() + { + if (!m_points.is_empty()) + return m_points.last(); + return {}; + } void close(); void close_all_subpaths(); - Vector> const& segments() const { return m_segments; } + Path stroke_to_fill(float thickness) const; - auto& split_lines() const + Path place_text_along(Utf8View text, Font const&) const; + + Path copy_transformed(AffineTransform const&) const; + + ReadonlySpan split_lines() const { if (!m_split_lines.has_value()) { const_cast(this)->segmentize_path(); VERIFY(m_split_lines.has_value()); } - return m_split_lines.value(); - } - - void clear() - { - m_segments.clear(); - m_split_lines.clear(); + return m_split_lines->lines; } Gfx::FloatRect const& bounding_box() const { - if (!m_bounding_box.has_value()) { - const_cast(this)->segmentize_path(); - VERIFY(m_bounding_box.has_value()); - } - return m_bounding_box.value(); + (void)split_lines(); + return m_split_lines->bounding_box; } void append_path(Path const& path) { - m_segments.ensure_capacity(m_segments.size() + path.m_segments.size()); - for (auto const& segment : path.m_segments) - m_segments.unchecked_append(segment); + m_commands.extend(path.m_commands); + m_points.extend(path.m_points); invalidate_split_lines(); } - Path copy_transformed(AffineTransform const&) const; - void add_path(Path const&); - void ensure_subpath(FloatPoint point); ByteString to_byte_string() const; - Path stroke_to_fill(float thickness) const; + PathSegmentIterator begin() const + { + return PathSegmentIterator(m_points, m_commands); + } - Path place_text_along(Utf8View text, Font const&) const; + PathSegmentIterator end() const + { + return PathSegmentIterator(m_points, m_commands, m_points.size(), m_commands.size()); + } + + bool is_empty() const + { + return m_commands.is_empty(); + } + + void clear() + { + *this = Path {}; + } private: void approximate_elliptical_arc_with_cubic_beziers(FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta); void invalidate_split_lines() { - m_bounding_box.clear(); m_split_lines.clear(); } void segmentize_path(); - template - void append_segment(Args&&... args) + template + void append_segment(FloatPoint point, Args&&... args) { - m_segments.append(adopt_ref(*new T(forward(args)...))); + constexpr auto point_count = sizeof...(Args) + 1; + static_assert(point_count == PathSegment::points_per_command(command)); + m_commands.append(command); + // Place the current path point after any extra control points so `m_points.last()` is always the last point. + FloatPoint points[] { args..., point }; + m_points.append(points, point_count); } - Vector> m_segments {}; + Vector m_points {}; + Vector m_commands {}; - Optional> m_split_lines {}; - Optional m_bounding_box; - bool m_need_new_subpath = { true }; + struct SplitLines { + Vector lines; + Gfx::FloatRect bounding_box; + }; + + Optional m_split_lines {}; }; } diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index 1c46b3af864..4fd2e4589ed 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -247,7 +247,7 @@ RENDERER_HANDLER(path_move) RENDERER_HANDLER(path_line) { - VERIFY(!m_current_path.segments().is_empty()); + VERIFY(!m_current_path.is_empty()); m_current_path.line_to(map(args[0].to_float(), args[1].to_float())); return {}; } @@ -265,8 +265,8 @@ RENDERER_HANDLER(path_cubic_bezier_curve) RENDERER_HANDLER(path_cubic_bezier_curve_no_first_control) { VERIFY(args.size() == 4); - VERIFY(!m_current_path.segments().is_empty()); - auto current_point = (*m_current_path.segments().rbegin())->point(); + VERIFY(!m_current_path.is_empty()); + auto current_point = m_current_path.last_point(); m_current_path.cubic_bezier_curve_to( current_point, map(args[0].to_float(), args[1].to_float()), @@ -277,7 +277,7 @@ RENDERER_HANDLER(path_cubic_bezier_curve_no_first_control) RENDERER_HANDLER(path_cubic_bezier_curve_no_second_control) { VERIFY(args.size() == 4); - VERIFY(!m_current_path.segments().is_empty()); + VERIFY(!m_current_path.is_empty()); auto first_control_point = map(args[0].to_float(), args[1].to_float()); auto second_control_point = map(args[2].to_float(), args[3].to_float()); m_current_path.cubic_bezier_curve_to( diff --git a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp index 283570ff5cd..d540fae4f0a 100644 --- a/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp +++ b/Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp @@ -129,7 +129,8 @@ WebIDL::ExceptionOr CanvasPath::arc_to(double x1, double y1, double x2, do // 2. Ensure there is a subpath for (x1, y1). auto transform = active_transform(); - m_path.ensure_subpath(transform.map(Gfx::FloatPoint { x1, y1 })); + if (m_path.is_empty()) + m_path.move_to(transform.map(Gfx::FloatPoint { x1, y1 })); // 3. If radius is negative, then throw an "IndexSizeError" DOMException. if (radius < 0) diff --git a/Userland/Libraries/LibWeb/HTML/Path2D.cpp b/Userland/Libraries/LibWeb/HTML/Path2D.cpp index d27cce58b0b..5f5a9b621d4 100644 --- a/Userland/Libraries/LibWeb/HTML/Path2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/Path2D.cpp @@ -42,9 +42,9 @@ Path2D::Path2D(JS::Realm& realm, Optional, String>> c auto path_instructions = SVG::AttributeParser::parse_path_data(path->get()); auto svg_path = SVG::path_from_path_instructions(path_instructions); - if (!svg_path.segments().is_empty()) { + if (!svg_path.is_empty()) { // 5. Let (x, y) be the last point in svgPath. - auto xy = svg_path.segments().last()->point(); + auto xy = svg_path.last_point(); // 6. Add all the subpaths, if any, from svgPath to output. this->path() = move(svg_path); @@ -70,7 +70,7 @@ WebIDL::ExceptionOr Path2D::add_path(JS::NonnullGCPtr path, Geomet // The addPath(path, transform) method, when invoked on a Path2D object a, must run these steps: // 1. If the Path2D object path has no subpaths, then return. - if (path->path().segments().is_empty()) + if (path->path().is_empty()) return {}; // 2. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform. @@ -85,11 +85,11 @@ WebIDL::ExceptionOr Path2D::add_path(JS::NonnullGCPtr path, Geomet auto copy = path->path().copy_transformed(Gfx::AffineTransform { static_cast(matrix->m11()), static_cast(matrix->m12()), static_cast(matrix->m21()), static_cast(matrix->m22()), static_cast(matrix->m41()), static_cast(matrix->m42()) }); // 6. Let (x, y) be the last point in the last subpath of c. - auto xy = copy.segments().last()->point(); + auto xy = copy.last_point(); // 7. Add all the subpaths in c to a. // FIXME: Is this correct? - this->path().add_path(copy); + this->path().append_path(copy); // 8. Create a new subpath in a with (x, y) as the only point in the subpath. this->move_to(xy.x(), xy.y()); diff --git a/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp index 1b462e27ff8..75584f6fbce 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp @@ -111,7 +111,7 @@ Gfx::Path path_from_path_instructions(ReadonlySpan instructions for (auto& instruction : instructions) { // If the first path element uses relative coordinates, we treat them as absolute by making them relative to (0, 0). - auto last_point = path.segments().is_empty() ? Gfx::FloatPoint { 0, 0 } : path.segments().last()->point(); + auto last_point = path.last_point(); auto& absolute = instruction.absolute; auto& data = instruction.data;