소스 검색

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).
MacDue 1 년 전
부모
커밋
8057542dea

+ 1 - 1
Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp

@@ -144,7 +144,7 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::fill_internal(Painter& painter, Pa
     if (m_clip.is_empty())
     if (m_clip.is_empty())
         return;
         return;
 
 
-    auto& lines = path.split_lines();
+    auto lines = path.split_lines();
     if (lines.is_empty())
     if (lines.is_empty())
         return;
         return;
 
 

+ 1 - 0
Userland/Libraries/LibGfx/Font/VectorFont.h

@@ -6,6 +6,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <AK/HashMap.h>
 #include <AK/Noncopyable.h>
 #include <AK/Noncopyable.h>
 #include <AK/RefCounted.h>
 #include <AK/RefCounted.h>
 #include <LibGfx/Font/Font.h>
 #include <LibGfx/Font/Font.h>

+ 2 - 2
Userland/Libraries/LibGfx/ImageFormats/TinyVGLoader.cpp

@@ -279,10 +279,10 @@ public:
                     path.line_to(TRY(read_point()));
                     path.line_to(TRY(read_point()));
                     break;
                     break;
                 case PathCommand::HorizontalLine:
                 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;
                     break;
                 case PathCommand::VerticalLine:
                 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;
                     break;
                 case PathCommand::CubicBezier: {
                 case PathCommand::CubicBezier: {
                     auto control_0 = TRY(read_point());
                     auto control_0 = TRY(read_point());

+ 63 - 149
Userland/Libraries/LibGfx/Path.cpp

@@ -4,10 +4,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
-#include <AK/Function.h>
-#include <AK/HashTable.h>
 #include <AK/Math.h>
 #include <AK/Math.h>
-#include <AK/QuickSort.h>
 #include <AK/StringBuilder.h>
 #include <AK/StringBuilder.h>
 #include <AK/TypeCasts.h>
 #include <AK/TypeCasts.h>
 #include <LibGfx/BoundingBox.h>
 #include <LibGfx/BoundingBox.h>
@@ -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
     // Step 1 of out-of-range radii correction
     if (rx == 0.0 || ry == 0.0) {
     if (rx == 0.0 || ry == 0.0) {
-        append_segment<LineSegment>(next_point);
+        append_segment<PathSegment::LineTo>(next_point);
         return;
         return;
     }
     }
 
 
@@ -192,7 +189,7 @@ Path Path::place_text_along(Utf8View text, Font const& font) const
         return {};
         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<FloatPoint> {
     auto next_point_for_offset = [&, line_index = 0U, distance_along_path = 0.0f, last_line_length = 0.0f](float offset) mutable -> Optional<FloatPoint> {
         while (line_index < lines.size() && offset > distance_along_path) {
         while (line_index < lines.size() && offset > distance_along_path) {
             last_line_length = lines[line_index++].length();
             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;
     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()
 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<LineSegment>(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()
 void Path::close_all_subpaths()
 {
 {
-    if (m_segments.size() <= 1)
-        return;
-
-    invalidate_split_lines();
-
-    Optional<FloatPoint> 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<MoveSegment>(cursor.value());
-            append_segment<LineSegment>(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
 ByteString Path::to_byte_string() const
 {
 {
     StringBuilder builder;
     StringBuilder builder;
     builder.append("Path { "sv);
     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);
             builder.append("MoveTo"sv);
             break;
             break;
-        case Segment::Type::LineTo:
+        case PathSegment::LineTo:
             builder.append("LineTo"sv);
             builder.append("LineTo"sv);
             break;
             break;
-        case Segment::Type::QuadraticBezierCurveTo:
+        case PathSegment::QuadraticBezierCurveTo:
             builder.append("QuadraticBezierCurveTo"sv);
             builder.append("QuadraticBezierCurveTo"sv);
             break;
             break;
-        case Segment::Type::CubicBezierCurveTo:
+        case PathSegment::CubicBezierCurveTo:
             builder.append("CubicBezierCurveTo"sv);
             builder.append("CubicBezierCurveTo"sv);
             break;
             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<QuadraticBezierCurveSegment const&>(*segment).through().to_byte_string());
+        switch (segment.command()) {
+        case PathSegment::QuadraticBezierCurveTo:
+            builder.appendff(", {}"sv, segment.through());
             break;
             break;
-        case Segment::Type::CubicBezierCurveTo:
-            builder.append(", "sv);
-            builder.append(static_cast<CubicBezierCurveSegment const&>(*segment).through_0().to_byte_string());
-            builder.append(", "sv);
-            builder.append(static_cast<CubicBezierCurveSegment const&>(*segment).through_1().to_byte_string());
+        case PathSegment::CubicBezierCurveTo:
+            builder.appendff(", {}"sv, segment.through_0());
+            builder.appendff(", {}"sv, segment.through_1());
             break;
             break;
         default:
         default:
             break;
             break;
@@ -381,88 +339,44 @@ void Path::segmentize_path()
     };
     };
 
 
     FloatPoint cursor { 0, 0 };
     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;
             break;
-        case Segment::Type::LineTo: {
-            add_line(cursor, segment->point());
-            cursor = segment->point();
+        case PathSegment::LineTo: {
+            add_line(cursor, segment.point());
             break;
             break;
         }
         }
-        case Segment::Type::QuadraticBezierCurveTo: {
-            auto control = static_cast<QuadraticBezierCurveSegment const&>(*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);
                 add_line(p0, p1);
             });
             });
-            cursor = segment->point();
             break;
             break;
         }
         }
-        case Segment::Type::CubicBezierCurveTo: {
-            auto& curve = static_cast<CubicBezierCurveSegment const&>(*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);
                 add_line(p0, p1);
             });
             });
-            cursor = segment->point();
             break;
             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 Path::copy_transformed(Gfx::AffineTransform const& transform) const
 {
 {
     Path result;
     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<QuadraticBezierCurveSegment const&>(*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<CubicBezierCurveSegment const&>(*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;
     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<typename T>
 template<typename T>
 struct RoundTrip {
 struct RoundTrip {
     RoundTrip(ReadonlySpan<T> span)
     RoundTrip(ReadonlySpan<T> span)
@@ -498,7 +412,7 @@ Path Path::stroke_to_fill(float thickness) const
 
 
     VERIFY(thickness > 0);
     VERIFY(thickness > 0);
 
 
-    auto& lines = split_lines();
+    auto lines = split_lines();
     if (lines.is_empty())
     if (lines.is_empty())
         return Path {};
         return Path {};
 
 

+ 146 - 98
Userland/Libraries/LibGfx/Path.h

@@ -7,10 +7,8 @@
 #pragma once
 #pragma once
 
 
 #include <AK/ByteString.h>
 #include <AK/ByteString.h>
-#include <AK/HashMap.h>
 #include <AK/Optional.h>
 #include <AK/Optional.h>
 #include <AK/Vector.h>
 #include <AK/Vector.h>
-#include <LibGfx/Font/Font.h>
 #include <LibGfx/Forward.h>
 #include <LibGfx/Forward.h>
 #include <LibGfx/Line.h>
 #include <LibGfx/Line.h>
 #include <LibGfx/Point.h>
 #include <LibGfx/Point.h>
@@ -18,91 +16,127 @@
 
 
 namespace Gfx {
 namespace Gfx {
 
 
-class Segment : public RefCounted<Segment> {
+class Path;
+
+class PathSegment {
 public:
 public:
-    enum class Type {
-        Invalid,
+    enum Command : u8 {
         MoveTo,
         MoveTo,
         LineTo,
         LineTo,
         QuadraticBezierCurveTo,
         QuadraticBezierCurveTo,
         CubicBezierCurveTo,
         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];
     }
     }
-
-    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)
+    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];
     }
     }
 
 
-private:
-    virtual Type type() const override { return Segment::Type::MoveTo; }
-};
-
-class LineSegment final : public Segment {
-public:
-    LineSegment(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();
     }
     }
 
 
-    virtual ~LineSegment() override = default;
+    PathSegment(Command command, ReadonlySpan<FloatPoint> points)
+        : m_command(command)
+        , m_points(points) {};
 
 
 private:
 private:
-    virtual Type type() const override { return Segment::Type::LineTo; }
+    Command m_command;
+    ReadonlySpan<FloatPoint> m_points;
 };
 };
 
 
-class QuadraticBezierCurveSegment final : public Segment {
+class PathSegmentIterator {
 public:
 public:
-    QuadraticBezierCurveSegment(FloatPoint point, FloatPoint through)
-        : Segment(point)
-        , m_through(through)
+    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; }
 
 
-    virtual ~QuadraticBezierCurveSegment() override = default;
-
-    FloatPoint through() const { return m_through; }
-
-private:
-    virtual Type type() const override { return Segment::Type::QuadraticBezierCurveTo; }
+    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;
+    }
 
 
-    FloatPoint m_through;
-};
+    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;
+    }
 
 
-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)
+    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)) };
     }
     }
 
 
-    virtual ~CubicBezierCurveSegment() override = default;
+    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;
 
 
-    FloatPoint through_0() const { return m_through_0; }
-    FloatPoint through_1() const { return m_through_1; }
+    friend Path;
 
 
 private:
 private:
-    virtual Type type() const override { return Segment::Type::CubicBezierCurveTo; }
+    PathSegmentIterator(Vector<FloatPoint> const& points, Vector<PathSegment::Command> 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)
+    {
+    }
 
 
-    FloatPoint m_through_0;
-    FloatPoint m_through_1;
+    // Note: Store reference to vectors from Gfx::Path so appending segments does not invalidate iterators.
+    Vector<FloatPoint> const& m_points;
+    Vector<PathSegment::Command> const& m_commands;
+    size_t m_point_index { 0 };
+    size_t m_command_index { 0 };
 };
 };
 
 
 class Path {
 class Path {
@@ -111,40 +145,34 @@ public:
 
 
     void move_to(FloatPoint point)
     void move_to(FloatPoint point)
     {
     {
-        append_segment<MoveSegment>(point);
+        append_segment<PathSegment::MoveTo>(point);
     }
     }
 
 
     void line_to(FloatPoint point)
     void line_to(FloatPoint point)
     {
     {
-        append_segment<LineSegment>(point);
+        append_segment<PathSegment::LineTo>(point);
         invalidate_split_lines();
         invalidate_split_lines();
     }
     }
 
 
     void horizontal_line_to(float x)
     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)
     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)
     void quadratic_bezier_curve_to(FloatPoint through, FloatPoint point)
     {
     {
-        append_segment<QuadraticBezierCurveSegment>(point, through);
+        append_segment<PathSegment::QuadraticBezierCurveTo>(point, through);
         invalidate_split_lines();
         invalidate_split_lines();
     }
     }
 
 
     void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2)
     void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2)
     {
     {
-        append_segment<CubicBezierCurveSegment>(p2, c1, c2);
+        append_segment<PathSegment::CubicBezierCurveTo>(p2, c1, c2);
         invalidate_split_lines();
         invalidate_split_lines();
     }
     }
 
 
@@ -156,75 +184,95 @@ public:
 
 
     void text(Utf8View, Font const&);
     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();
     void close_all_subpaths();
     void close_all_subpaths();
 
 
-    Vector<NonnullRefPtr<Segment const>> const& segments() const { return m_segments; }
+    Path stroke_to_fill(float thickness) const;
+
+    Path place_text_along(Utf8View text, Font const&) const;
+
+    Path copy_transformed(AffineTransform const&) const;
 
 
-    auto& split_lines() const
+    ReadonlySpan<FloatLine> split_lines() const
     {
     {
         if (!m_split_lines.has_value()) {
         if (!m_split_lines.has_value()) {
             const_cast<Path*>(this)->segmentize_path();
             const_cast<Path*>(this)->segmentize_path();
             VERIFY(m_split_lines.has_value());
             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
     Gfx::FloatRect const& bounding_box() const
     {
     {
-        if (!m_bounding_box.has_value()) {
-            const_cast<Path*>(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)
     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();
         invalidate_split_lines();
     }
     }
 
 
-    Path copy_transformed(AffineTransform const&) const;
-    void add_path(Path const&);
-    void ensure_subpath(FloatPoint point);
     ByteString to_byte_string() const;
     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:
 private:
     void approximate_elliptical_arc_with_cubic_beziers(FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta);
     void approximate_elliptical_arc_with_cubic_beziers(FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta);
 
 
     void invalidate_split_lines()
     void invalidate_split_lines()
     {
     {
-        m_bounding_box.clear();
         m_split_lines.clear();
         m_split_lines.clear();
     }
     }
     void segmentize_path();
     void segmentize_path();
 
 
-    template<typename T, typename... Args>
-    void append_segment(Args&&... args)
+    template<PathSegment::Command command, typename... Args>
+    void append_segment(FloatPoint point, Args&&... args)
     {
     {
-        m_segments.append(adopt_ref(*new T(forward<Args>(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<NonnullRefPtr<Segment const>> m_segments {};
+    Vector<FloatPoint> m_points {};
+    Vector<PathSegment::Command> m_commands {};
+
+    struct SplitLines {
+        Vector<FloatLine> lines;
+        Gfx::FloatRect bounding_box;
+    };
 
 
-    Optional<Vector<FloatLine>> m_split_lines {};
-    Optional<Gfx::FloatRect> m_bounding_box;
-    bool m_need_new_subpath = { true };
+    Optional<SplitLines> m_split_lines {};
 };
 };
 
 
 }
 }

+ 4 - 4
Userland/Libraries/LibPDF/Renderer.cpp

@@ -247,7 +247,7 @@ RENDERER_HANDLER(path_move)
 
 
 RENDERER_HANDLER(path_line)
 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()));
     m_current_path.line_to(map(args[0].to_float(), args[1].to_float()));
     return {};
     return {};
 }
 }
@@ -265,8 +265,8 @@ RENDERER_HANDLER(path_cubic_bezier_curve)
 RENDERER_HANDLER(path_cubic_bezier_curve_no_first_control)
 RENDERER_HANDLER(path_cubic_bezier_curve_no_first_control)
 {
 {
     VERIFY(args.size() == 4);
     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(
     m_current_path.cubic_bezier_curve_to(
         current_point,
         current_point,
         map(args[0].to_float(), args[1].to_float()),
         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)
 RENDERER_HANDLER(path_cubic_bezier_curve_no_second_control)
 {
 {
     VERIFY(args.size() == 4);
     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 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());
     auto second_control_point = map(args[2].to_float(), args[3].to_float());
     m_current_path.cubic_bezier_curve_to(
     m_current_path.cubic_bezier_curve_to(

+ 2 - 1
Userland/Libraries/LibWeb/HTML/Canvas/CanvasPath.cpp

@@ -129,7 +129,8 @@ WebIDL::ExceptionOr<void> CanvasPath::arc_to(double x1, double y1, double x2, do
 
 
     // 2. Ensure there is a subpath for (x1, y1).
     // 2. Ensure there is a subpath for (x1, y1).
     auto transform = active_transform();
     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.
     // 3. If radius is negative, then throw an "IndexSizeError" DOMException.
     if (radius < 0)
     if (radius < 0)

+ 5 - 5
Userland/Libraries/LibWeb/HTML/Path2D.cpp

@@ -42,9 +42,9 @@ Path2D::Path2D(JS::Realm& realm, Optional<Variant<JS::Handle<Path2D>, String>> c
     auto path_instructions = SVG::AttributeParser::parse_path_data(path->get<String>());
     auto path_instructions = SVG::AttributeParser::parse_path_data(path->get<String>());
     auto svg_path = SVG::path_from_path_instructions(path_instructions);
     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.
         // 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.
         // 6. Add all the subpaths, if any, from svgPath to output.
         this->path() = move(svg_path);
         this->path() = move(svg_path);
@@ -70,7 +70,7 @@ WebIDL::ExceptionOr<void> Path2D::add_path(JS::NonnullGCPtr<Path2D> path, Geomet
     // The addPath(path, transform) method, when invoked on a Path2D object a, must run these steps:
     // 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.
     // 1. If the Path2D object path has no subpaths, then return.
-    if (path->path().segments().is_empty())
+    if (path->path().is_empty())
         return {};
         return {};
 
 
     // 2. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform.
     // 2. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform.
@@ -85,11 +85,11 @@ WebIDL::ExceptionOr<void> Path2D::add_path(JS::NonnullGCPtr<Path2D> path, Geomet
     auto copy = path->path().copy_transformed(Gfx::AffineTransform { static_cast<float>(matrix->m11()), static_cast<float>(matrix->m12()), static_cast<float>(matrix->m21()), static_cast<float>(matrix->m22()), static_cast<float>(matrix->m41()), static_cast<float>(matrix->m42()) });
     auto copy = path->path().copy_transformed(Gfx::AffineTransform { static_cast<float>(matrix->m11()), static_cast<float>(matrix->m12()), static_cast<float>(matrix->m21()), static_cast<float>(matrix->m22()), static_cast<float>(matrix->m41()), static_cast<float>(matrix->m42()) });
 
 
     // 6. Let (x, y) be the last point in the last subpath of c.
     // 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.
     // 7. Add all the subpaths in c to a.
     // FIXME: Is this correct?
     // 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.
     // 8. Create a new subpath in a with (x, y) as the only point in the subpath.
     this->move_to(xy.x(), xy.y());
     this->move_to(xy.x(), xy.y());

+ 1 - 1
Userland/Libraries/LibWeb/SVG/SVGPathElement.cpp

@@ -111,7 +111,7 @@ Gfx::Path path_from_path_instructions(ReadonlySpan<PathInstruction> instructions
 
 
     for (auto& instruction : 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).
         // 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& absolute = instruction.absolute;
         auto& data = instruction.data;
         auto& data = instruction.data;