/src/serenity/Userland/Libraries/LibWeb/HTML/Path2D.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> |
3 | | * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> |
4 | | * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org> |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | */ |
8 | | |
9 | | #include <LibWeb/Bindings/Intrinsics.h> |
10 | | #include <LibWeb/Bindings/Path2DPrototype.h> |
11 | | #include <LibWeb/Geometry/DOMMatrix.h> |
12 | | #include <LibWeb/HTML/Path2D.h> |
13 | | #include <LibWeb/SVG/AttributeParser.h> |
14 | | #include <LibWeb/SVG/SVGPathElement.h> |
15 | | |
16 | | namespace Web::HTML { |
17 | | |
18 | | JS_DEFINE_ALLOCATOR(Path2D); |
19 | | |
20 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Path2D>> Path2D::construct_impl(JS::Realm& realm, Optional<Variant<JS::Handle<Path2D>, String>> const& path) |
21 | 0 | { |
22 | 0 | return realm.heap().allocate<Path2D>(realm, realm, path); |
23 | 0 | } |
24 | | |
25 | | // https://html.spec.whatwg.org/multipage/canvas.html#dom-path2d |
26 | | Path2D::Path2D(JS::Realm& realm, Optional<Variant<JS::Handle<Path2D>, String>> const& path) |
27 | 0 | : PlatformObject(realm) |
28 | 0 | , CanvasPath(static_cast<Bindings::PlatformObject&>(*this)) |
29 | 0 | { |
30 | | // 1. Let output be a new Path2D object. |
31 | | // 2. If path is not given, then return output. |
32 | 0 | if (!path.has_value()) |
33 | 0 | return; |
34 | | |
35 | | // 3. If path is a Path2D object, then add all subpaths of path to output and return output. |
36 | | // (In other words, it returns a copy of the argument.) |
37 | 0 | if (path->has<JS::Handle<Path2D>>()) { |
38 | 0 | this->path() = path->get<JS::Handle<Path2D>>()->path(); |
39 | 0 | return; |
40 | 0 | } |
41 | | |
42 | | // 4. Let svgPath be the result of parsing and interpreting path according to SVG 2's rules for path data. [SVG] |
43 | 0 | auto path_instructions = SVG::AttributeParser::parse_path_data(path->get<String>()); |
44 | 0 | auto svg_path = SVG::path_from_path_instructions(path_instructions); |
45 | |
|
46 | 0 | if (!svg_path.is_empty()) { |
47 | | // 5. Let (x, y) be the last point in svgPath. |
48 | 0 | auto xy = svg_path.last_point(); |
49 | | |
50 | | // 6. Add all the subpaths, if any, from svgPath to output. |
51 | 0 | this->path() = move(svg_path); |
52 | | |
53 | | // 7. Create a new subpath in output with (x, y) as the only point in the subpath. |
54 | 0 | this->move_to(xy.x(), xy.y()); |
55 | 0 | } |
56 | | |
57 | | // 8. Return output. |
58 | 0 | } |
59 | | |
60 | 0 | Path2D::~Path2D() = default; |
61 | | |
62 | | void Path2D::initialize(JS::Realm& realm) |
63 | 0 | { |
64 | 0 | Base::initialize(realm); |
65 | 0 | set_prototype(&Bindings::ensure_web_prototype<Bindings::Path2DPrototype>(realm, "Path2D"_fly_string)); |
66 | 0 | } |
67 | | |
68 | | // https://html.spec.whatwg.org/multipage/canvas.html#dom-path2d-addpath |
69 | | WebIDL::ExceptionOr<void> Path2D::add_path(JS::NonnullGCPtr<Path2D> path, Geometry::DOMMatrix2DInit& transform) |
70 | 0 | { |
71 | | // The addPath(path, transform) method, when invoked on a Path2D object a, must run these steps: |
72 | | |
73 | | // 1. If the Path2D object path has no subpaths, then return. |
74 | 0 | if (path->path().is_empty()) |
75 | 0 | return {}; |
76 | | |
77 | | // 2. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform. |
78 | 0 | auto matrix = TRY(Geometry::DOMMatrix::create_from_dom_matrix_2d_init(realm(), transform)); |
79 | | |
80 | | // 3. If one or more of matrix's m11 element, m12 element, m21 element, m22 element, m41 element, or m42 element are infinite or NaN, then return. |
81 | 0 | if (!isfinite(matrix->m11()) || !isfinite(matrix->m12()) || !isfinite(matrix->m21()) || !isfinite(matrix->m22()) || !isfinite(matrix->m41()) || !isfinite(matrix->m42())) |
82 | 0 | return {}; |
83 | | |
84 | | // 4. Create a copy of all the subpaths in path. Let this copy be known as c. |
85 | | // 5. Transform all the coordinates and lines in c by the transform matrix matrix. |
86 | 0 | 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()) }); |
87 | | |
88 | | // 6. Let (x, y) be the last point in the last subpath of c. |
89 | 0 | auto xy = copy.last_point(); |
90 | | |
91 | | // 7. Add all the subpaths in c to a. |
92 | | // FIXME: Is this correct? |
93 | 0 | this->path().append_path(copy); |
94 | | |
95 | | // 8. Create a new subpath in a with (x, y) as the only point in the subpath. |
96 | 0 | this->move_to(xy.x(), xy.y()); |
97 | |
|
98 | 0 | return {}; |
99 | 0 | } |
100 | | |
101 | | } |