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).
This commit is contained in:
MacDue 2024-03-17 20:23:17 +00:00 committed by Andreas Kling
commit 8057542dea
Notes: sideshowbarker 2024-07-17 10:54:57 +09:00
9 changed files with 237 additions and 273 deletions

View file

@ -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 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<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:
// 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<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()) });
// 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());