mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Add DOMMatrix and DOMMatrixReadOnly
These are needed for apple.com, completely broken without. Obviously Claude wrote most of this code. The test that it wrote _does_ pass in FireFox and a reasonable number of WPT *DOMMatrix* test pass. A relatively small subset of this was needed for Apple, but there was plenty of low-hanging fruit in the WPT tests. Also, some of these WPT tests uncovered a name collision in our WPT reporter: https://github.com/lightpanda-io/wpt/pull/70
This commit is contained in:
@@ -822,6 +822,8 @@ pub const PageJsApis = flattenTypes(&.{
|
||||
@import("../webapi/DOMTreeWalker.zig"),
|
||||
@import("../webapi/DOMNodeIterator.zig"),
|
||||
@import("../webapi/DOMRect.zig"),
|
||||
@import("../webapi/DOMMatrixReadOnly.zig"),
|
||||
@import("../webapi/DOMMatrix.zig"),
|
||||
@import("../webapi/DOMParser.zig"),
|
||||
@import("../webapi/XMLSerializer.zig"),
|
||||
@import("../webapi/AbstractRange.zig"),
|
||||
@@ -1008,6 +1010,8 @@ pub const WorkerJsApis = flattenTypes(&.{
|
||||
@import("../webapi/event/PromiseRejectionEvent.zig"),
|
||||
@import("../webapi/event/CloseEvent.zig"),
|
||||
@import("../webapi/DOMException.zig"),
|
||||
@import("../webapi/DOMMatrixReadOnly.zig"),
|
||||
@import("../webapi/DOMMatrix.zig"),
|
||||
@import("../webapi/net/URLSearchParams.zig"),
|
||||
@import("../webapi/encoding/TextEncoder.zig"),
|
||||
@import("../webapi/encoding/TextDecoder.zig"),
|
||||
|
||||
401
src/browser/tests/dommatrix.html
Normal file
401
src/browser/tests/dommatrix.html
Normal file
@@ -0,0 +1,401 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>DOMMatrix Test</title>
|
||||
<script src="testing.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
<script id=identity_no_args>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
testing.expectEqual(1, m.a);
|
||||
testing.expectEqual(0, m.b);
|
||||
testing.expectEqual(0, m.c);
|
||||
testing.expectEqual(1, m.d);
|
||||
testing.expectEqual(0, m.e);
|
||||
testing.expectEqual(0, m.f);
|
||||
testing.expectTrue(m.is2D);
|
||||
testing.expectTrue(m.isIdentity);
|
||||
testing.expectEqual(1, m.m11);
|
||||
testing.expectEqual(1, m.m22);
|
||||
testing.expectEqual(1, m.m33);
|
||||
testing.expectEqual(1, m.m44);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_6_element_array>
|
||||
{
|
||||
const m = new DOMMatrix([1, 2, 3, 4, 5, 6]);
|
||||
testing.expectEqual(1, m.a);
|
||||
testing.expectEqual(2, m.b);
|
||||
testing.expectEqual(3, m.c);
|
||||
testing.expectEqual(4, m.d);
|
||||
testing.expectEqual(5, m.e);
|
||||
testing.expectEqual(6, m.f);
|
||||
testing.expectTrue(m.is2D);
|
||||
testing.expectFalse(m.isIdentity);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_16_element_array>
|
||||
{
|
||||
const m = new DOMMatrix([
|
||||
1, 2, 3, 4,
|
||||
5, 6, 7, 8,
|
||||
9, 10, 11, 12,
|
||||
13, 14, 15, 16,
|
||||
]);
|
||||
testing.expectEqual(1, m.m11);
|
||||
testing.expectEqual(6, m.m22);
|
||||
testing.expectEqual(11, m.m33);
|
||||
testing.expectEqual(16, m.m44);
|
||||
testing.expectEqual(13, m.m41);
|
||||
testing.expectFalse(m.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_matrix_string>
|
||||
{
|
||||
const m = new DOMMatrix('matrix(1, 0, 0, 1, 10, 20)');
|
||||
testing.expectEqual(1, m.a);
|
||||
testing.expectEqual(0, m.b);
|
||||
testing.expectEqual(10, m.e);
|
||||
testing.expectEqual(20, m.f);
|
||||
testing.expectTrue(m.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_none_and_empty>
|
||||
{
|
||||
const none = new DOMMatrix('none');
|
||||
testing.expectTrue(none.isIdentity);
|
||||
const empty = new DOMMatrix('');
|
||||
testing.expectTrue(empty.isIdentity);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_translate_string>
|
||||
{
|
||||
const m = new DOMMatrix('translate(40px, 50px)');
|
||||
testing.expectEqual(40, m.e);
|
||||
testing.expectEqual(50, m.f);
|
||||
testing.expectTrue(m.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_scale_string>
|
||||
{
|
||||
const m = new DOMMatrix('scale(2)');
|
||||
testing.expectEqual(2, m.a);
|
||||
testing.expectEqual(2, m.d);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_matrix3d_string>
|
||||
{
|
||||
const m = new DOMMatrix('matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, 5,6,7,1)');
|
||||
testing.expectEqual(5, m.m41);
|
||||
testing.expectEqual(6, m.m42);
|
||||
testing.expectEqual(7, m.m43);
|
||||
testing.expectFalse(m.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=rotate_90>
|
||||
{
|
||||
const m = new DOMMatrix('rotate(90deg)');
|
||||
// cos(90)=0, sin(90)=1
|
||||
testing.expectTrue(Math.abs(m.a - 0) < 1e-9);
|
||||
testing.expectTrue(Math.abs(m.b - 1) < 1e-9);
|
||||
testing.expectTrue(Math.abs(m.c - -1) < 1e-9);
|
||||
testing.expectTrue(Math.abs(m.d - 0) < 1e-9);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=constructor_copies_matrix_via_string>
|
||||
{
|
||||
// Per WebIDL the init arg is (string or sequence); a matrix is not a
|
||||
// sequence, so it is stringified and re-parsed — round-tripping a copy.
|
||||
const src = new DOMMatrix([
|
||||
2, 1, 0, 0,
|
||||
1, 2, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
10, 10, 0, 1,
|
||||
]);
|
||||
const m = new DOMMatrix(src);
|
||||
testing.expectEqual(2, m.m11);
|
||||
testing.expectEqual(1, m.m21);
|
||||
testing.expectEqual(10, m.m41);
|
||||
testing.expectFalse(m.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=constructor_invalid_string_throws_syntaxerror>
|
||||
{
|
||||
let name = null;
|
||||
try { new DOMMatrix('not a transform'); } catch (e) { name = e.name; }
|
||||
testing.expectEqual('SyntaxError', name);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_matrix_dict>
|
||||
{
|
||||
const m = DOMMatrix.fromMatrix({ a: 2, d: 3, e: 10, f: 20 });
|
||||
testing.expectEqual(2, m.a);
|
||||
testing.expectEqual(3, m.d);
|
||||
testing.expectEqual(10, m.e);
|
||||
testing.expectEqual(20, m.f);
|
||||
testing.expectTrue(m.is2D);
|
||||
|
||||
const ro = DOMMatrixReadOnly.fromMatrix({ m11: 1, m22: 1, m33: 1, m44: 1, is2D: false });
|
||||
testing.expectTrue(ro instanceof DOMMatrixReadOnly);
|
||||
testing.expectFalse(ro.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_matrix_alias_conflict_throws>
|
||||
{
|
||||
// a and m11 both present but unequal -> TypeError
|
||||
let threw = false;
|
||||
try { DOMMatrix.fromMatrix({ a: 1, m11: 2 }); } catch (e) { threw = (e instanceof TypeError); }
|
||||
testing.expectTrue(threw);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=from_float_arrays>
|
||||
{
|
||||
const m6 = DOMMatrix.fromFloat64Array(new Float64Array([1, 0, 0, 1, 7, 8]));
|
||||
testing.expectEqual(7, m6.e);
|
||||
testing.expectEqual(8, m6.f);
|
||||
testing.expectTrue(m6.is2D);
|
||||
|
||||
const m16 = DOMMatrix.fromFloat32Array(new Float32Array([
|
||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
||||
]));
|
||||
testing.expectFalse(m16.is2D);
|
||||
testing.expectTrue(m16.isIdentity);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=multiply_translate>
|
||||
{
|
||||
const a = new DOMMatrix().translate(10, 0);
|
||||
const b = new DOMMatrix().translate(0, 20);
|
||||
const m = a.multiply(b);
|
||||
testing.expectEqual(10, m.e);
|
||||
testing.expectEqual(20, m.f);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=scale_self>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
const r = m.scaleSelf(3);
|
||||
testing.expectEqual(3, m.a);
|
||||
testing.expectEqual(3, m.d);
|
||||
// self methods return the same object
|
||||
testing.expectTrue(r === m);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=inverse_translate>
|
||||
{
|
||||
const m = new DOMMatrix().translate(10, 20);
|
||||
const inv = m.inverse();
|
||||
testing.expectEqual(-10, inv.e);
|
||||
testing.expectEqual(-20, inv.f);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=set_a_mutates>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
m.a = 5;
|
||||
testing.expectEqual(5, m.m11);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=to_string_throws_on_non_finite>
|
||||
{
|
||||
for (const num of [NaN, Infinity, -Infinity]) {
|
||||
let name = null;
|
||||
const m = new DOMMatrix([1, 0, 0, 1, 0, num]);
|
||||
try { String(m); } catch (e) { name = e.name; }
|
||||
testing.expectEqual('InvalidStateError', name);
|
||||
}
|
||||
// finite matrix stringifies fine
|
||||
testing.expectEqual('matrix(1, 0, 0, 1, 0, 0)', String(new DOMMatrix()));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=to_string_2d>
|
||||
{
|
||||
const m = new DOMMatrix([1, 0, 0, 1, 10, 20]);
|
||||
testing.expectEqual('matrix(1, 0, 0, 1, 10, 20)', m.toString());
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=to_float32_array>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
const arr = m.toFloat32Array();
|
||||
testing.expectTrue(arr instanceof Float32Array);
|
||||
testing.expectEqual(16, arr.length);
|
||||
testing.expectEqual(1, arr[0]);
|
||||
testing.expectEqual(1, arr[15]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=set_3d_element_zero_preserves_is2d>
|
||||
{
|
||||
// Setting a z/w element to 0 (or -0) must preserve is2D; only a non-zero
|
||||
// value clears it.
|
||||
for (const attr of ['m13', 'm14', 'm23', 'm24', 'm31', 'm32', 'm34', 'm43']) {
|
||||
const m = new DOMMatrix();
|
||||
m[attr] = 0;
|
||||
testing.expectTrue(m.is2D);
|
||||
m[attr] = -0;
|
||||
testing.expectTrue(m.is2D);
|
||||
m[attr] = 42;
|
||||
testing.expectFalse(m.is2D);
|
||||
}
|
||||
// m33/m44 identity is 1: setting to 1 preserves, anything else clears.
|
||||
const a = new DOMMatrix();
|
||||
a.m33 = 1;
|
||||
testing.expectTrue(a.is2D);
|
||||
a.m33 = 2;
|
||||
testing.expectFalse(a.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=multiply_no_args_is_copy>
|
||||
{
|
||||
const m = new DOMMatrix([1, 2, 3, 4, 5, 6]);
|
||||
const r = m.multiply();
|
||||
testing.expectEqual(1, r.a);
|
||||
testing.expectEqual(6, r.f);
|
||||
testing.expectTrue(r !== m);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=new_object_methods>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
for (const method of ['scale3d', 'rotateAxisAngle', 'rotateFromVector', 'skewX', 'skewY', 'flipX', 'flipY', 'multiply']) {
|
||||
const r = m[method]();
|
||||
testing.expectTrue(r instanceof DOMMatrix);
|
||||
testing.expectTrue(r !== m);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=skew_keeps_2d>
|
||||
{
|
||||
const m = new DOMMatrix().skewX(30);
|
||||
testing.expectTrue(m.is2D);
|
||||
// tan(30deg) ~= 0.5774 lands in c (m21)
|
||||
testing.expectTrue(Math.abs(m.c - Math.tan(30 * Math.PI / 180)) < 1e-9);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=readonly_exists_and_inheritance>
|
||||
{
|
||||
const ro = new DOMMatrixReadOnly([1, 0, 0, 1, 10, 20]);
|
||||
testing.expectEqual(10, ro.e);
|
||||
testing.expectEqual(20, ro.f);
|
||||
testing.expectTrue(ro instanceof DOMMatrixReadOnly);
|
||||
// DOMMatrix extends DOMMatrixReadOnly
|
||||
const m = new DOMMatrix();
|
||||
testing.expectTrue(m instanceof DOMMatrix);
|
||||
testing.expectTrue(m instanceof DOMMatrixReadOnly);
|
||||
testing.expectFalse(ro instanceof DOMMatrix);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=readonly_is_immutable>
|
||||
{
|
||||
const ro = new DOMMatrixReadOnly([1, 0, 0, 1, 10, 20]);
|
||||
// Setting a component on a read-only matrix must not change it (the
|
||||
// accessor has no setter).
|
||||
try { ro.a = 5; } catch (e) {}
|
||||
testing.expectEqual(1, ro.a);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=readonly_methods_return_mutable>
|
||||
{
|
||||
const ro = new DOMMatrixReadOnly();
|
||||
const t = ro.translate(10, 20);
|
||||
// DOMMatrixReadOnly methods return a (new, mutable) DOMMatrix
|
||||
testing.expectTrue(t instanceof DOMMatrix);
|
||||
testing.expectEqual(10, t.e);
|
||||
testing.expectEqual(20, t.f);
|
||||
// original is untouched
|
||||
testing.expectEqual(0, ro.e);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=mutable_self_methods_chain>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
const r = m.translateSelf(5, 6).scaleSelf(2);
|
||||
testing.expectTrue(r === m);
|
||||
testing.expectEqual(2, m.a);
|
||||
testing.expectEqual(5, m.e);
|
||||
testing.expectEqual(6, m.f);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=multiply_accepts_dict>
|
||||
{
|
||||
const m = new DOMMatrix();
|
||||
const r = m.multiply({ m11: 1, m12: 2, m21: 0, m22: 1, m41: 0, m42: 0 });
|
||||
testing.expectEqual(2, r.b);
|
||||
// original not mutated
|
||||
testing.expectEqual(0, m.b);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=multiply_accepts_matrix_instance>
|
||||
{
|
||||
const a = new DOMMatrix().translate(10, 0);
|
||||
const b = new DOMMatrix().translate(0, 20);
|
||||
const m = a.multiply(b);
|
||||
testing.expectEqual(10, m.e);
|
||||
testing.expectEqual(20, m.f);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=invert_self_identity_and_singular>
|
||||
{
|
||||
// returns self
|
||||
const m = new DOMMatrix().translate(10, -20.5);
|
||||
const ret = m.invertSelf();
|
||||
testing.expectTrue(ret === m);
|
||||
testing.expectEqual(-10, m.e);
|
||||
testing.expectEqual(20.5, m.f);
|
||||
|
||||
// singular matrix inverts to all-NaN, is2D false
|
||||
const s = new DOMMatrix([0, 0, 0, 0, 0, 0]);
|
||||
s.invertSelf();
|
||||
testing.expectTrue(Number.isNaN(s.m11));
|
||||
testing.expectTrue(Number.isNaN(s.m44));
|
||||
testing.expectFalse(s.is2D);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=read_all_m_components>
|
||||
{
|
||||
// framer-motion reads m11..m44 off a matrix built from a transform string
|
||||
const m = new DOMMatrix('matrix(1, 0, 0, 1, 0, 0)');
|
||||
let sum = 0;
|
||||
for (let r = 1; r < 5; r++) {
|
||||
for (let c = 1; c < 5; c++) {
|
||||
sum += m[`m${r}${c}`];
|
||||
}
|
||||
}
|
||||
testing.expectEqual(4, sum); // identity has four 1s on the diagonal
|
||||
}
|
||||
</script>
|
||||
301
src/browser/webapi/DOMMatrix.zig
Normal file
301
src/browser/webapi/DOMMatrix.zig
Normal file
@@ -0,0 +1,301 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const RO = @import("DOMMatrixReadOnly.zig");
|
||||
|
||||
const DOMMatrix = @This();
|
||||
|
||||
_proto: *RO,
|
||||
|
||||
pub fn init(init_: ?js.Value, exec: *const js.Execution) !*DOMMatrix {
|
||||
const parsed = try RO.Parsed.init(init_, exec);
|
||||
return create(parsed.m, parsed.is_2d, exec.page);
|
||||
}
|
||||
|
||||
// Builds the [DOMMatrixReadOnly, DOMMatrix] prototype chain on a single arena
|
||||
// (owned by the base) and cross-links them, the same way File wraps Blob.
|
||||
pub fn create(m: [16]f64, is_2d: bool, page: *Page) !*DOMMatrix {
|
||||
const proto = try RO.createBare(m, is_2d, page);
|
||||
errdefer proto.deinit(page);
|
||||
|
||||
const self = try proto._arena.create(DOMMatrix);
|
||||
self.* = .{ ._proto = proto };
|
||||
proto._type = .{ .mutable = self };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn fromMatrix(other_: ?RO.DOMMatrixInit, page: *Page) !*DOMMatrix {
|
||||
const parsed = try RO.fixupDict(other_ orelse .{});
|
||||
return create(parsed.m, parsed.is_2d, page);
|
||||
}
|
||||
|
||||
pub fn fromFloat32Array(array: js.TypedArray(f32), page: *Page) !*DOMMatrix {
|
||||
const parsed = try RO.floatsToParsed(f32, array.values);
|
||||
return create(parsed.m, parsed.is_2d, page);
|
||||
}
|
||||
|
||||
pub fn fromFloat64Array(array: js.TypedArray(f64), page: *Page) !*DOMMatrix {
|
||||
const parsed = try RO.floatsToParsed(f64, array.values);
|
||||
return create(parsed.m, parsed.is_2d, page);
|
||||
}
|
||||
|
||||
// The base already exposes read-only getters, but a redeclared accessor's
|
||||
// getter must be typed to this (owner) struct, so we provide DOMMatrix-typed
|
||||
// getters that read through `_proto`.
|
||||
|
||||
pub fn getA(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[0];
|
||||
}
|
||||
pub fn getB(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[1];
|
||||
}
|
||||
pub fn getC(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[4];
|
||||
}
|
||||
pub fn getD(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[5];
|
||||
}
|
||||
pub fn getE(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[12];
|
||||
}
|
||||
pub fn getF(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[13];
|
||||
}
|
||||
|
||||
pub fn setA(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[0] = v;
|
||||
}
|
||||
pub fn setB(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[1] = v;
|
||||
}
|
||||
pub fn setC(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[4] = v;
|
||||
}
|
||||
pub fn setD(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[5] = v;
|
||||
}
|
||||
pub fn setE(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[12] = v;
|
||||
}
|
||||
pub fn setF(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[13] = v;
|
||||
}
|
||||
|
||||
pub fn translateSelf(self: *DOMMatrix, tx_: ?f64, ty_: ?f64, tz_: ?f64) *DOMMatrix {
|
||||
const tz = tz_ orelse 0;
|
||||
const p = self._proto;
|
||||
p._m = RO.multiplyMatrix(p._m, RO.translationMatrix(tx_ orelse 0, ty_ orelse 0, tz));
|
||||
if (tz != 0) p._is_2d = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn scaleSelf(self: *DOMMatrix, sx_: ?f64, sy_: ?f64, sz_: ?f64, ox_: ?f64, oy_: ?f64, oz_: ?f64) *DOMMatrix {
|
||||
const sx = sx_ orelse 1;
|
||||
const sy = sy_ orelse sx;
|
||||
const sz = sz_ orelse 1;
|
||||
const ox = ox_ orelse 0;
|
||||
const oy = oy_ orelse 0;
|
||||
const oz = oz_ orelse 0;
|
||||
const p = self._proto;
|
||||
var m = RO.multiplyMatrix(p._m, RO.translationMatrix(ox, oy, oz));
|
||||
m = RO.multiplyMatrix(m, RO.scaleMatrix(sx, sy, sz));
|
||||
m = RO.multiplyMatrix(m, RO.translationMatrix(-ox, -oy, -oz));
|
||||
p._m = m;
|
||||
if (sz != 1 or oz != 0) p._is_2d = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn scale3dSelf(self: *DOMMatrix, scale_: ?f64, ox_: ?f64, oy_: ?f64, oz_: ?f64) *DOMMatrix {
|
||||
const s = scale_ orelse 1;
|
||||
const ox = ox_ orelse 0;
|
||||
const oy = oy_ orelse 0;
|
||||
const oz = oz_ orelse 0;
|
||||
const p = self._proto;
|
||||
var m = RO.multiplyMatrix(p._m, RO.translationMatrix(ox, oy, oz));
|
||||
m = RO.multiplyMatrix(m, RO.scaleMatrix(s, s, s));
|
||||
m = RO.multiplyMatrix(m, RO.translationMatrix(-ox, -oy, -oz));
|
||||
p._m = m;
|
||||
if (s != 1) p._is_2d = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn rotateSelf(self: *DOMMatrix, rx_: ?f64, ry_: ?f64, rz_: ?f64) *DOMMatrix {
|
||||
const p = self._proto;
|
||||
if (ry_ == null and rz_ == null) {
|
||||
p._m = RO.multiplyMatrix(p._m, RO.rotateZMatrix(RO.toRadians(rx_ orelse 0, .deg)));
|
||||
} else {
|
||||
p._m = RO.multiplyMatrix(p._m, RO.rotateXMatrix(RO.toRadians(rx_ orelse 0, .deg)));
|
||||
p._m = RO.multiplyMatrix(p._m, RO.rotateYMatrix(RO.toRadians(ry_ orelse 0, .deg)));
|
||||
p._m = RO.multiplyMatrix(p._m, RO.rotateZMatrix(RO.toRadians(rz_ orelse 0, .deg)));
|
||||
p._is_2d = false;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn rotateFromVectorSelf(self: *DOMMatrix, x_: ?f64, y_: ?f64) *DOMMatrix {
|
||||
const x = x_ orelse 0;
|
||||
const y = y_ orelse 0;
|
||||
const rad = if (x == 0 and y == 0) 0 else std.math.atan2(y, x);
|
||||
const p = self._proto;
|
||||
p._m = RO.multiplyMatrix(p._m, RO.rotateZMatrix(rad));
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn rotateAxisAngleSelf(self: *DOMMatrix, x_: ?f64, y_: ?f64, z_: ?f64, angle_: ?f64) *DOMMatrix {
|
||||
const p = self._proto;
|
||||
p._m = RO.multiplyMatrix(p._m, RO.axisAngleMatrix(x_ orelse 0, y_ orelse 0, z_ orelse 0, RO.toRadians(angle_ orelse 0, .deg)));
|
||||
if ((x_ orelse 0) != 0 or (y_ orelse 0) != 0) p._is_2d = false;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn skewXSelf(self: *DOMMatrix, sx_: ?f64) *DOMMatrix {
|
||||
const p = self._proto;
|
||||
p._m = RO.multiplyMatrix(p._m, RO.skewMatrix(RO.toRadians(sx_ orelse 0, .deg), 0));
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn skewYSelf(self: *DOMMatrix, sy_: ?f64) *DOMMatrix {
|
||||
const p = self._proto;
|
||||
p._m = RO.multiplyMatrix(p._m, RO.skewMatrix(0, RO.toRadians(sy_ orelse 0, .deg)));
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn multiplySelf(self: *DOMMatrix, other_: ?RO.DOMMatrixInit) !*DOMMatrix {
|
||||
const p = self._proto;
|
||||
const other = try RO.fixupDict(other_ orelse .{});
|
||||
p._m = RO.multiplyMatrix(p._m, other.m);
|
||||
p._is_2d = p._is_2d and other.is_2d;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn preMultiplySelf(self: *DOMMatrix, other_: ?RO.DOMMatrixInit) !*DOMMatrix {
|
||||
const p = self._proto;
|
||||
const other = try RO.fixupDict(other_ orelse .{});
|
||||
p._m = RO.multiplyMatrix(other.m, p._m);
|
||||
p._is_2d = p._is_2d and other.is_2d;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn invertSelf(self: *DOMMatrix) *DOMMatrix {
|
||||
const p = self._proto;
|
||||
if (RO.invertMatrix(p._m)) |v| {
|
||||
p._m = v;
|
||||
} else {
|
||||
p._m = .{std.math.nan(f64)} ** 16;
|
||||
p._is_2d = false;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn setMatrixValue(self: *DOMMatrix, transform: []const u8) !*DOMMatrix {
|
||||
var m = RO.identity();
|
||||
var is_2d = true;
|
||||
try RO.parseTransformList(transform, &m, &is_2d);
|
||||
self._proto._m = m;
|
||||
self._proto._is_2d = is_2d;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(DOMMatrix);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "DOMMatrix";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(DOMMatrix.init, .{ .dom_exception = true });
|
||||
|
||||
pub const fromMatrix = bridge.function(DOMMatrix.fromMatrix, .{ .static = true });
|
||||
pub const fromFloat32Array = bridge.function(DOMMatrix.fromFloat32Array, .{ .static = true });
|
||||
pub const fromFloat64Array = bridge.function(DOMMatrix.fromFloat64Array, .{ .static = true });
|
||||
|
||||
// Make the components writable (the read-only getters are reused from the
|
||||
// base; the setters are ours).
|
||||
pub const a = bridge.accessor(DOMMatrix.getA, DOMMatrix.setA, .{});
|
||||
pub const b = bridge.accessor(DOMMatrix.getB, DOMMatrix.setB, .{});
|
||||
pub const c = bridge.accessor(DOMMatrix.getC, DOMMatrix.setC, .{});
|
||||
pub const d = bridge.accessor(DOMMatrix.getD, DOMMatrix.setD, .{});
|
||||
pub const e = bridge.accessor(DOMMatrix.getE, DOMMatrix.setE, .{});
|
||||
pub const f = bridge.accessor(DOMMatrix.getF, DOMMatrix.setF, .{});
|
||||
|
||||
pub const m11 = bridge.accessor(getM(0), setM(0), .{});
|
||||
pub const m12 = bridge.accessor(getM(1), setM(1), .{});
|
||||
pub const m13 = bridge.accessor(getM(2), setM(2), .{});
|
||||
pub const m14 = bridge.accessor(getM(3), setM(3), .{});
|
||||
pub const m21 = bridge.accessor(getM(4), setM(4), .{});
|
||||
pub const m22 = bridge.accessor(getM(5), setM(5), .{});
|
||||
pub const m23 = bridge.accessor(getM(6), setM(6), .{});
|
||||
pub const m24 = bridge.accessor(getM(7), setM(7), .{});
|
||||
pub const m31 = bridge.accessor(getM(8), setM(8), .{});
|
||||
pub const m32 = bridge.accessor(getM(9), setM(9), .{});
|
||||
pub const m33 = bridge.accessor(getM(10), setM(10), .{});
|
||||
pub const m34 = bridge.accessor(getM(11), setM(11), .{});
|
||||
pub const m41 = bridge.accessor(getM(12), setM(12), .{});
|
||||
pub const m42 = bridge.accessor(getM(13), setM(13), .{});
|
||||
pub const m43 = bridge.accessor(getM(14), setM(14), .{});
|
||||
pub const m44 = bridge.accessor(getM(15), setM(15), .{});
|
||||
|
||||
pub const translateSelf = bridge.function(DOMMatrix.translateSelf, .{});
|
||||
pub const scaleSelf = bridge.function(DOMMatrix.scaleSelf, .{});
|
||||
pub const scale3dSelf = bridge.function(DOMMatrix.scale3dSelf, .{});
|
||||
pub const rotateSelf = bridge.function(DOMMatrix.rotateSelf, .{});
|
||||
pub const rotateFromVectorSelf = bridge.function(DOMMatrix.rotateFromVectorSelf, .{});
|
||||
pub const rotateAxisAngleSelf = bridge.function(DOMMatrix.rotateAxisAngleSelf, .{});
|
||||
pub const skewXSelf = bridge.function(DOMMatrix.skewXSelf, .{});
|
||||
pub const skewYSelf = bridge.function(DOMMatrix.skewYSelf, .{});
|
||||
pub const multiplySelf = bridge.function(DOMMatrix.multiplySelf, .{});
|
||||
pub const preMultiplySelf = bridge.function(DOMMatrix.preMultiplySelf, .{});
|
||||
pub const invertSelf = bridge.function(DOMMatrix.invertSelf, .{});
|
||||
// setMatrixValue parses a CSS transform string; Window-only.
|
||||
pub const setMatrixValue = bridge.function(DOMMatrix.setMatrixValue, .{ .dom_exception = true, .exposed = .window });
|
||||
|
||||
fn getM(comptime idx: usize) fn (*const DOMMatrix) f64 {
|
||||
return struct {
|
||||
fn get(self: *const DOMMatrix) f64 {
|
||||
return self._proto._m[idx];
|
||||
}
|
||||
}.get;
|
||||
}
|
||||
|
||||
fn setM(comptime idx: usize) fn (*DOMMatrix, f64) void {
|
||||
return struct {
|
||||
fn set(self: *DOMMatrix, v: f64) void {
|
||||
self._proto._m[idx] = v;
|
||||
// Assigning a z/w element a value other than its identity drops the
|
||||
// 2D flag. Setting it back to the identity value (0 for the
|
||||
// off-diagonal elements, 1 for m33/m44) preserves is2D. Note `-0`
|
||||
// compares equal to `0`, so it preserves it too, per spec.
|
||||
switch (idx) {
|
||||
2, 3, 6, 7, 8, 9, 11, 14 => if (v != 0) {
|
||||
self._proto._is_2d = false;
|
||||
},
|
||||
10, 15 => if (v != 1) {
|
||||
self._proto._is_2d = false;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}.set;
|
||||
}
|
||||
};
|
||||
870
src/browser/webapi/DOMMatrixReadOnly.zig
Normal file
870
src/browser/webapi/DOMMatrixReadOnly.zig
Normal file
@@ -0,0 +1,870 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const DOMMatrix = @import("DOMMatrix.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const DOMMatrixReadOnly = @This();
|
||||
|
||||
pub const _prototype_root = true;
|
||||
|
||||
_type: Type,
|
||||
_rc: lp.RC(u8),
|
||||
_arena: Allocator,
|
||||
|
||||
// Stored column-major, matching the spec's mAB naming where A is the column
|
||||
// and B is the row:
|
||||
// _m[0.._4] = m11, m12, m13, m14 (first column)
|
||||
// _m[4.._8] = m21, m22, m23, m24
|
||||
// _m[8..12] = m31, m32, m33, m34
|
||||
// _m[12..16] = m41, m42, m43, m44
|
||||
//
|
||||
// A point (x, y, z, w) is transformed as:
|
||||
// out[row] = sum_col _m[col*4 + row] * in[col]
|
||||
_m: [16]f64,
|
||||
_is_2d: bool,
|
||||
|
||||
pub const Type = union(enum) {
|
||||
generic,
|
||||
mutable: *DOMMatrix,
|
||||
};
|
||||
|
||||
pub fn init(init_: ?js.Value, exec: *const js.Execution) !*DOMMatrixReadOnly {
|
||||
const parsed = try Parsed.init(init_, exec);
|
||||
return createBare(parsed.m, parsed.is_2d, exec.page);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DOMMatrixReadOnly, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *DOMMatrixReadOnly) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *DOMMatrixReadOnly, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn createBare(m: [16]f64, is_2d: bool, page: *Page) !*DOMMatrixReadOnly {
|
||||
const arena = try page.getArena(.tiny, "DOMMatrix");
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const self = try arena.create(DOMMatrixReadOnly);
|
||||
self.* = .{
|
||||
._rc = .{},
|
||||
._arena = arena,
|
||||
._type = .generic,
|
||||
._m = m,
|
||||
._is_2d = is_2d,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub const DOMMatrixInit = struct {
|
||||
a: ?f64 = null,
|
||||
b: ?f64 = null,
|
||||
c: ?f64 = null,
|
||||
d: ?f64 = null,
|
||||
e: ?f64 = null,
|
||||
f: ?f64 = null,
|
||||
m11: ?f64 = null,
|
||||
m12: ?f64 = null,
|
||||
m13: ?f64 = null,
|
||||
m14: ?f64 = null,
|
||||
m21: ?f64 = null,
|
||||
m22: ?f64 = null,
|
||||
m23: ?f64 = null,
|
||||
m24: ?f64 = null,
|
||||
m31: ?f64 = null,
|
||||
m32: ?f64 = null,
|
||||
m33: ?f64 = null,
|
||||
m34: ?f64 = null,
|
||||
m41: ?f64 = null,
|
||||
m42: ?f64 = null,
|
||||
m43: ?f64 = null,
|
||||
m44: ?f64 = null,
|
||||
is2D: ?bool = null,
|
||||
};
|
||||
|
||||
// Implements "validate and fixup a DOMMatrixInit dictionary".
|
||||
pub fn fixupDict(d: DOMMatrixInit) !Parsed {
|
||||
if (aliasConflict(d.m11, d.a) or aliasConflict(d.m12, d.b) or
|
||||
aliasConflict(d.m21, d.c) or aliasConflict(d.m22, d.d) or
|
||||
aliasConflict(d.m41, d.e) or aliasConflict(d.m42, d.f))
|
||||
{
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
// An explicit is2D:true is incompatible with any 3D member being set.
|
||||
if (d.is2D) |is_2d| {
|
||||
if (is_2d and has3dMembers(d)) {
|
||||
return error.TypeError;
|
||||
}
|
||||
}
|
||||
|
||||
const m: [16]f64 = .{
|
||||
d.m11 orelse d.a orelse 1, d.m12 orelse d.b orelse 0, d.m13 orelse 0, d.m14 orelse 0,
|
||||
d.m21 orelse d.c orelse 0, d.m22 orelse d.d orelse 1, d.m23 orelse 0, d.m24 orelse 0,
|
||||
d.m31 orelse 0, d.m32 orelse 0, d.m33 orelse 1, d.m34 orelse 0,
|
||||
d.m41 orelse d.e orelse 0, d.m42 orelse d.f orelse 0, d.m43 orelse 0, d.m44 orelse 1,
|
||||
};
|
||||
|
||||
const is_2d = d.is2D orelse !has3dMembers(d);
|
||||
return .{ .m = m, .is_2d = is_2d };
|
||||
}
|
||||
|
||||
// Builds a matrix from a 6- or 16-element float sequence (toFloat*Array order).
|
||||
pub fn floatsToParsed(comptime T: type, values: []const T) !Parsed {
|
||||
var m = identity();
|
||||
if (values.len == 6) {
|
||||
m = .{
|
||||
values[0], values[1], 0, 0,
|
||||
values[2], values[3], 0, 0,
|
||||
0, 0, 1, 0,
|
||||
values[4], values[5], 0, 1,
|
||||
};
|
||||
return .{ .m = m, .is_2d = true };
|
||||
}
|
||||
|
||||
if (values.len == 16) {
|
||||
for (0..16) |i| m[i] = values[i];
|
||||
return .{ .m = m, .is_2d = false };
|
||||
}
|
||||
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
pub fn fromMatrix(other_: ?DOMMatrixInit, page: *Page) !*DOMMatrixReadOnly {
|
||||
const parsed = try fixupDict(other_ orelse .{});
|
||||
return createBare(parsed.m, parsed.is_2d, page);
|
||||
}
|
||||
|
||||
pub fn fromFloat32Array(array: js.TypedArray(f32), page: *Page) !*DOMMatrixReadOnly {
|
||||
const parsed = try floatsToParsed(f32, array.values);
|
||||
return createBare(parsed.m, parsed.is_2d, page);
|
||||
}
|
||||
|
||||
pub fn fromFloat64Array(array: js.TypedArray(f64), page: *Page) !*DOMMatrixReadOnly {
|
||||
const parsed = try floatsToParsed(f64, array.values);
|
||||
return createBare(parsed.m, parsed.is_2d, page);
|
||||
}
|
||||
|
||||
pub fn identity() [16]f64 {
|
||||
return .{
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Returns lhs * rhs (composition: applying the result is lhs(rhs(point))).
|
||||
pub fn multiplyMatrix(lhs: [16]f64, rhs: [16]f64) [16]f64 {
|
||||
var out: [16]f64 = undefined;
|
||||
for (0..4) |col| {
|
||||
for (0..4) |row| {
|
||||
var sum: f64 = 0;
|
||||
for (0..4) |k| {
|
||||
sum += lhs[k * 4 + row] * rhs[col * 4 + k];
|
||||
}
|
||||
out[col * 4 + row] = sum;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
pub fn translationMatrix(tx: f64, ty: f64, tz: f64) [16]f64 {
|
||||
return .{
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
tx, ty, tz, 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn scaleMatrix(sx: f64, sy: f64, sz: f64) [16]f64 {
|
||||
return .{
|
||||
sx, 0, 0, 0,
|
||||
0, sy, 0, 0,
|
||||
0, 0, sz, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rotateZMatrix(rad: f64) [16]f64 {
|
||||
const c = @cos(rad);
|
||||
const s = @sin(rad);
|
||||
return .{
|
||||
c, s, 0, 0,
|
||||
-s, c, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rotateXMatrix(rad: f64) [16]f64 {
|
||||
const c = @cos(rad);
|
||||
const s = @sin(rad);
|
||||
return .{
|
||||
1, 0, 0, 0,
|
||||
0, c, s, 0,
|
||||
0, -s, c, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rotateYMatrix(rad: f64) [16]f64 {
|
||||
const c = @cos(rad);
|
||||
const s = @sin(rad);
|
||||
return .{
|
||||
c, 0, -s, 0,
|
||||
0, 1, 0, 0,
|
||||
s, 0, c, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Rotation by `rad` about the (possibly unnormalised) axis (x, y, z).
|
||||
pub fn axisAngleMatrix(x_in: f64, y_in: f64, z_in: f64, rad: f64) [16]f64 {
|
||||
var x = x_in;
|
||||
var y = y_in;
|
||||
var z = z_in;
|
||||
const len = @sqrt(x * x + y * y + z * z);
|
||||
if (len == 0) {
|
||||
return identity();
|
||||
}
|
||||
|
||||
x /= len;
|
||||
y /= len;
|
||||
z /= len;
|
||||
const c = @cos(rad);
|
||||
const s = @sin(rad);
|
||||
const t = 1 - c;
|
||||
return .{
|
||||
t * x * x + c, t * x * y + s * z, t * x * z - s * y, 0,
|
||||
t * x * y - s * z, t * y * y + c, t * y * z + s * x, 0,
|
||||
t * x * z + s * y, t * y * z - s * x, t * z * z + c, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn skewMatrix(ax_rad: f64, ay_rad: f64) [16]f64 {
|
||||
return .{
|
||||
1, @tan(ay_rad), 0, 0,
|
||||
@tan(ax_rad), 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Inverse of a 4x4 matrix; returns null if non-invertible.
|
||||
pub fn invertMatrix(m: [16]f64) ?[16]f64 {
|
||||
var inv: [16]f64 = undefined;
|
||||
inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10];
|
||||
inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10];
|
||||
inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9];
|
||||
inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9];
|
||||
inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10];
|
||||
inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10];
|
||||
inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9];
|
||||
inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9];
|
||||
inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6];
|
||||
inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6];
|
||||
inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5];
|
||||
inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5];
|
||||
inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6];
|
||||
inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6];
|
||||
inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5];
|
||||
inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5];
|
||||
|
||||
var det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
|
||||
if (det == 0) {
|
||||
return null;
|
||||
}
|
||||
det = 1.0 / det;
|
||||
|
||||
var out: [16]f64 = undefined;
|
||||
for (0..16) |i| {
|
||||
out[i] = inv[i] * det;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Parses a CSS <transform-list> (e.g. "matrix(1,0,0,1,10,20) scale(2)") and
|
||||
// accumulates it into `m`. "none"/empty leave the matrix as identity.
|
||||
pub fn parseTransformList(input: []const u8, m: *[16]f64, is_2d: *bool) !void {
|
||||
const trimmed = std.mem.trim(u8, input, " \t\r\n");
|
||||
if (trimmed.len == 0 or std.mem.eql(u8, trimmed, "none")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < trimmed.len) {
|
||||
// skip whitespace and separating commas
|
||||
while (i < trimmed.len and (std.ascii.isWhitespace(trimmed[i]) or trimmed[i] == ',')) : (i += 1) {}
|
||||
if (i >= trimmed.len) {
|
||||
break;
|
||||
}
|
||||
|
||||
const name_start = i;
|
||||
while (i < trimmed.len and trimmed[i] != '(') : (i += 1) {}
|
||||
if (i >= trimmed.len) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
const name = std.mem.trim(u8, trimmed[name_start..i], " \t\r\n");
|
||||
|
||||
i += 1; // consume '('
|
||||
const args_start = i;
|
||||
while (i < trimmed.len and trimmed[i] != ')') : (i += 1) {}
|
||||
if (i >= trimmed.len) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
const args = trimmed[args_start..i];
|
||||
i += 1; // consume ')'
|
||||
|
||||
const func = try parseFunction(name, args, is_2d);
|
||||
m.* = multiplyMatrix(m.*, func);
|
||||
}
|
||||
}
|
||||
|
||||
fn parseFunction(name: []const u8, args: []const u8, is_2d: *bool) ![16]f64 {
|
||||
var nums: [16]f64 = undefined;
|
||||
var units: [16]ParsedValue.Unit = undefined;
|
||||
var count: usize = 0;
|
||||
|
||||
var it = std.mem.splitScalar(u8, args, ',');
|
||||
while (it.next()) |raw| {
|
||||
const tok = std.mem.trim(u8, raw, " \t\r\n");
|
||||
if (tok.len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (count >= 16) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
const parsed = try ParsedValue.parse(tok);
|
||||
nums[count] = parsed.value;
|
||||
units[count] = parsed.unit;
|
||||
count += 1;
|
||||
}
|
||||
|
||||
const Eql = std.mem.eql;
|
||||
if (Eql(u8, name, "matrix")) {
|
||||
if (count != 6) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
return .{
|
||||
nums[0], nums[1], 0, 0,
|
||||
nums[2], nums[3], 0, 0,
|
||||
0, 0, 1, 0,
|
||||
nums[4], nums[5], 0, 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "matrix3d")) {
|
||||
if (count != 16) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
is_2d.* = false;
|
||||
return nums;
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "translate")) {
|
||||
const tx = nums[0];
|
||||
const ty = if (count > 1) nums[1] else 0;
|
||||
return translationMatrix(tx, ty, 0);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "translateX")) {
|
||||
return translationMatrix(nums[0], 0, 0);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "translateY")) {
|
||||
return translationMatrix(0, nums[0], 0);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "translateZ")) {
|
||||
is_2d.* = false;
|
||||
return translationMatrix(0, 0, nums[0]);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "translate3d")) {
|
||||
is_2d.* = false;
|
||||
return translationMatrix(nums[0], nums[1], nums[2]);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "scale")) {
|
||||
const sx = nums[0];
|
||||
const sy = if (count > 1) nums[1] else sx;
|
||||
return scaleMatrix(sx, sy, 1);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "scaleX")) {
|
||||
return scaleMatrix(nums[0], 1, 1);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "scaleY")) {
|
||||
return scaleMatrix(1, nums[0], 1);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "scaleZ")) {
|
||||
is_2d.* = false;
|
||||
return scaleMatrix(1, 1, nums[0]);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "scale3d")) {
|
||||
is_2d.* = false;
|
||||
return scaleMatrix(nums[0], nums[1], nums[2]);
|
||||
}
|
||||
if (Eql(u8, name, "rotate") or Eql(u8, name, "rotateZ")) {
|
||||
if (Eql(u8, name, "rotateZ")) is_2d.* = false;
|
||||
return rotateZMatrix(toRadians(nums[0], units[0]));
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "rotateX")) {
|
||||
is_2d.* = false;
|
||||
return rotateXMatrix(toRadians(nums[0], units[0]));
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "rotateY")) {
|
||||
is_2d.* = false;
|
||||
return rotateYMatrix(toRadians(nums[0], units[0]));
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "rotate3d")) {
|
||||
is_2d.* = false;
|
||||
if (count != 4) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
return axisAngleMatrix(nums[0], nums[1], nums[2], toRadians(nums[3], units[3]));
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "skew")) {
|
||||
const ax = toRadians(nums[0], units[0]);
|
||||
const ay = if (count > 1) toRadians(nums[1], units[1]) else 0;
|
||||
return skewMatrix(ax, ay);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "skewX")) {
|
||||
return skewMatrix(toRadians(nums[0], units[0]), 0);
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "skewY")) {
|
||||
return skewMatrix(0, toRadians(nums[0], units[0]));
|
||||
}
|
||||
|
||||
if (Eql(u8, name, "perspective")) {
|
||||
is_2d.* = false;
|
||||
var out = identity();
|
||||
if (nums[0] != 0) out[11] = -1.0 / nums[0];
|
||||
return out;
|
||||
}
|
||||
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
pub fn toRadians(value: f64, unit: ParsedValue.Unit) f64 {
|
||||
return switch (unit) {
|
||||
.rad => value,
|
||||
.grad => value * std.math.pi / 200.0,
|
||||
.turn => value * std.math.tau,
|
||||
// bare numbers in rotate()/skew() are interpreted as degrees
|
||||
.deg, .none => value * std.math.pi / 180.0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getA(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[0];
|
||||
}
|
||||
pub fn getB(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[1];
|
||||
}
|
||||
pub fn getC(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[4];
|
||||
}
|
||||
pub fn getD(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[5];
|
||||
}
|
||||
pub fn getE(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[12];
|
||||
}
|
||||
pub fn getF(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[13];
|
||||
}
|
||||
|
||||
pub fn getIs2D(self: *const DOMMatrixReadOnly) bool {
|
||||
return self._is_2d;
|
||||
}
|
||||
|
||||
pub fn getIsIdentity(self: *const DOMMatrixReadOnly) bool {
|
||||
const id = identity();
|
||||
for (0..16) |i| {
|
||||
if (self._m[i] != id[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn translate(self: *const DOMMatrixReadOnly, tx_: ?f64, ty_: ?f64, tz_: ?f64, page: *Page) !*DOMMatrix {
|
||||
const tz = tz_ orelse 0;
|
||||
return DOMMatrix.create(
|
||||
multiplyMatrix(self._m, translationMatrix(tx_ orelse 0, ty_ orelse 0, tz)),
|
||||
self._is_2d and tz == 0,
|
||||
page,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn scale(self: *const DOMMatrixReadOnly, sx_: ?f64, sy_: ?f64, sz_: ?f64, ox_: ?f64, oy_: ?f64, oz_: ?f64, page: *Page) !*DOMMatrix {
|
||||
const sx = sx_ orelse 1;
|
||||
const sy = sy_ orelse sx;
|
||||
const sz = sz_ orelse 1;
|
||||
const ox = ox_ orelse 0;
|
||||
const oy = oy_ orelse 0;
|
||||
const oz = oz_ orelse 0;
|
||||
var m = multiplyMatrix(self._m, translationMatrix(ox, oy, oz));
|
||||
m = multiplyMatrix(m, scaleMatrix(sx, sy, sz));
|
||||
m = multiplyMatrix(m, translationMatrix(-ox, -oy, -oz));
|
||||
return DOMMatrix.create(m, self._is_2d and sz == 1 and oz == 0, page);
|
||||
}
|
||||
|
||||
pub fn scaleNonUniform(self: *const DOMMatrixReadOnly, sx_: ?f64, sy_: ?f64, page: *Page) !*DOMMatrix {
|
||||
const sx = sx_ orelse 1;
|
||||
const sy = sy_ orelse 1;
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, scaleMatrix(sx, sy, 1)), self._is_2d, page);
|
||||
}
|
||||
|
||||
pub fn scale3d(self: *const DOMMatrixReadOnly, scale_: ?f64, ox_: ?f64, oy_: ?f64, oz_: ?f64, page: *Page) !*DOMMatrix {
|
||||
const s = scale_ orelse 1;
|
||||
const ox = ox_ orelse 0;
|
||||
const oy = oy_ orelse 0;
|
||||
const oz = oz_ orelse 0;
|
||||
var m = multiplyMatrix(self._m, translationMatrix(ox, oy, oz));
|
||||
m = multiplyMatrix(m, scaleMatrix(s, s, s));
|
||||
m = multiplyMatrix(m, translationMatrix(-ox, -oy, -oz));
|
||||
return DOMMatrix.create(m, self._is_2d and s == 1, page);
|
||||
}
|
||||
|
||||
pub fn rotate(self: *const DOMMatrixReadOnly, rx_: ?f64, ry_: ?f64, rz_: ?f64, page: *Page) !*DOMMatrix {
|
||||
var out = self._m;
|
||||
var is_2d = self._is_2d;
|
||||
// With a single argument, it is the Z rotation.
|
||||
if (ry_ == null and rz_ == null) {
|
||||
out = multiplyMatrix(out, rotateZMatrix(toRadians(rx_ orelse 0, .deg)));
|
||||
} else {
|
||||
out = multiplyMatrix(out, rotateXMatrix(toRadians(rx_ orelse 0, .deg)));
|
||||
out = multiplyMatrix(out, rotateYMatrix(toRadians(ry_ orelse 0, .deg)));
|
||||
out = multiplyMatrix(out, rotateZMatrix(toRadians(rz_ orelse 0, .deg)));
|
||||
is_2d = false;
|
||||
}
|
||||
return DOMMatrix.create(out, is_2d, page);
|
||||
}
|
||||
|
||||
pub fn rotateFromVector(self: *const DOMMatrixReadOnly, x_: ?f64, y_: ?f64, page: *Page) !*DOMMatrix {
|
||||
const x = x_ orelse 0;
|
||||
const y = y_ orelse 0;
|
||||
const rad = if (x == 0 and y == 0) 0 else std.math.atan2(y, x);
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, rotateZMatrix(rad)), self._is_2d, page);
|
||||
}
|
||||
|
||||
pub fn rotateAxisAngle(self: *const DOMMatrixReadOnly, x_: ?f64, y_: ?f64, z_: ?f64, angle_: ?f64, page: *Page) !*DOMMatrix {
|
||||
return DOMMatrix.create(
|
||||
multiplyMatrix(self._m, axisAngleMatrix(x_ orelse 0, y_ orelse 0, z_ orelse 0, toRadians(angle_ orelse 0, .deg))),
|
||||
// Only a rotation purely about the z axis stays 2D.
|
||||
self._is_2d and (x_ orelse 0) == 0 and (y_ orelse 0) == 0,
|
||||
page,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn skewX(self: *const DOMMatrixReadOnly, sx_: ?f64, page: *Page) !*DOMMatrix {
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, skewMatrix(toRadians(sx_ orelse 0, .deg), 0)), self._is_2d, page);
|
||||
}
|
||||
|
||||
pub fn skewY(self: *const DOMMatrixReadOnly, sy_: ?f64, page: *Page) !*DOMMatrix {
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, skewMatrix(0, toRadians(sy_ orelse 0, .deg))), self._is_2d, page);
|
||||
}
|
||||
|
||||
pub fn multiply(self: *const DOMMatrixReadOnly, other_: ?DOMMatrixInit, page: *Page) !*DOMMatrix {
|
||||
const other = try fixupDict(other_ orelse .{});
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, other.m), self._is_2d and other.is_2d, page);
|
||||
}
|
||||
|
||||
pub fn flipX(self: *const DOMMatrixReadOnly, page: *Page) !*DOMMatrix {
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, scaleMatrix(-1, 1, 1)), self._is_2d, page);
|
||||
}
|
||||
|
||||
pub fn flipY(self: *const DOMMatrixReadOnly, page: *Page) !*DOMMatrix {
|
||||
return DOMMatrix.create(multiplyMatrix(self._m, scaleMatrix(1, -1, 1)), self._is_2d, page);
|
||||
}
|
||||
|
||||
pub fn inverse(self: *const DOMMatrixReadOnly, page: *Page) !*DOMMatrix {
|
||||
if (invertMatrix(self._m)) |v| {
|
||||
return DOMMatrix.create(v, self._is_2d, page);
|
||||
}
|
||||
// Non-invertible matrices become all-NaN with is2D = false.
|
||||
return DOMMatrix.create(.{std.math.nan(f64)} ** 16, false, page);
|
||||
}
|
||||
|
||||
pub fn toFloat32Array(self: *const DOMMatrixReadOnly, exec: *const js.Execution) !js.TypedArray(f32) {
|
||||
const out = try exec.call_arena.alloc(f32, 16);
|
||||
for (0..16) |i| {
|
||||
out[i] = @floatCast(self._m[i]);
|
||||
}
|
||||
return .{ .values = out };
|
||||
}
|
||||
|
||||
pub fn toFloat64Array(self: *const DOMMatrixReadOnly, exec: *const js.Execution) !js.TypedArray(f64) {
|
||||
const out = try exec.call_arena.dupe(f64, &self._m);
|
||||
return .{ .values = out };
|
||||
}
|
||||
|
||||
pub fn toString(self: *const DOMMatrixReadOnly, exec: *const js.Execution) ![]const u8 {
|
||||
const m = self._m;
|
||||
if (self._is_2d) {
|
||||
// Per the stringifier: throw if any serialized component is non-finite.
|
||||
for ([_]f64{ m[0], m[1], m[4], m[5], m[12], m[13] }) |v| {
|
||||
if (!std.math.isFinite(v)) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
}
|
||||
return std.fmt.allocPrint(exec.call_arena, "matrix({d}, {d}, {d}, {d}, {d}, {d})", .{
|
||||
m[0], m[1], m[4], m[5], m[12], m[13],
|
||||
});
|
||||
}
|
||||
for (m) |v| {
|
||||
if (!std.math.isFinite(v)) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
}
|
||||
return std.fmt.allocPrint(exec.call_arena, "matrix3d({d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d}, {d})", .{
|
||||
m[0], m[1], m[2], m[3],
|
||||
m[4], m[5], m[6], m[7],
|
||||
m[8], m[9], m[10], m[11],
|
||||
m[12], m[13], m[14], m[15],
|
||||
});
|
||||
}
|
||||
|
||||
fn aliasConflict(x: ?f64, y: ?f64) bool {
|
||||
const a = x orelse return false;
|
||||
const b = y orelse return false;
|
||||
if (std.math.isNan(a) and std.math.isNan(b)) {
|
||||
return false;
|
||||
}
|
||||
return a != b;
|
||||
}
|
||||
|
||||
// True when the dict specifies any 3D-only member away from its identity value.
|
||||
fn has3dMembers(d: DOMMatrixInit) bool {
|
||||
return (d.m13 orelse 0) != 0 or (d.m14 orelse 0) != 0 or
|
||||
(d.m23 orelse 0) != 0 or (d.m24 orelse 0) != 0 or
|
||||
(d.m31 orelse 0) != 0 or (d.m32 orelse 0) != 0 or
|
||||
(d.m34 orelse 0) != 0 or (d.m43 orelse 0) != 0 or
|
||||
(d.m33 orelse 1) != 1 or (d.m44 orelse 1) != 1;
|
||||
}
|
||||
|
||||
pub const Parsed = struct {
|
||||
m: [16]f64,
|
||||
is_2d: bool,
|
||||
|
||||
pub fn init(init_: ?js.Value, exec: *const js.Execution) !Parsed {
|
||||
var m: [16]f64 = identity();
|
||||
var is_2d = true;
|
||||
|
||||
if (init_) |in| {
|
||||
if (!in.isUndefined()) {
|
||||
if (in.isArray()) {
|
||||
try sequenceToMatrix(in.toArray(), &m, &is_2d);
|
||||
} else {
|
||||
// Per WebIDL the union is `(DOMString or sequence)`: a value
|
||||
// that isn't a sequence is converted to a DOMString. So a
|
||||
// string parses directly, and any other value (a number, null,
|
||||
// or another matrix) is stringified first — which is how
|
||||
// `new DOMMatrix(otherMatrix)` round-trips via its
|
||||
// matrix()/matrix3d() serialization.
|
||||
if (exec.js.global == .worker) {
|
||||
return error.TypeError;
|
||||
}
|
||||
const str = try in.toStringSmart();
|
||||
try parseTransformList(str, &m, &is_2d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .m = m, .is_2d = is_2d };
|
||||
}
|
||||
};
|
||||
|
||||
const ParsedValue = struct {
|
||||
value: f64,
|
||||
unit: Unit,
|
||||
|
||||
const Unit = enum {
|
||||
none,
|
||||
deg,
|
||||
rad,
|
||||
grad,
|
||||
turn,
|
||||
};
|
||||
|
||||
// Parses a single CSS dimension token: a number with an optional unit suffix.
|
||||
// Length units are ignored (we don't resolve layout), so the numeric part is
|
||||
// taken verbatim; angle units are recorded so they can be normalised.
|
||||
fn parse(tok: []const u8) !ParsedValue {
|
||||
var end: usize = 0;
|
||||
while (end < tok.len) : (end += 1) {
|
||||
const c = tok[end];
|
||||
if ((c >= '0' and c <= '9') or c == '.' or c == '+' or c == '-' or c == 'e' or c == 'E') {
|
||||
// 'e'/'E' is ambiguous with exponents; only treat as exponent when
|
||||
// followed by a digit/sign.
|
||||
if ((c == 'e' or c == 'E') and end > 0) {
|
||||
if (end + 1 >= tok.len) break;
|
||||
const nx = tok[end + 1];
|
||||
if (!((nx >= '0' and nx <= '9') or nx == '+' or nx == '-')) break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (end == 0) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
const value = try std.fmt.parseFloat(f64, tok[0..end]);
|
||||
const suffix = tok[end..];
|
||||
|
||||
var unit: Unit = .none;
|
||||
if (std.ascii.eqlIgnoreCase(suffix, "deg")) {
|
||||
unit = .deg;
|
||||
} else if (std.ascii.eqlIgnoreCase(suffix, "rad")) {
|
||||
unit = .rad;
|
||||
} else if (std.ascii.eqlIgnoreCase(suffix, "grad")) {
|
||||
unit = .grad;
|
||||
} else if (std.ascii.eqlIgnoreCase(suffix, "turn")) {
|
||||
unit = .turn;
|
||||
}
|
||||
return .{ .value = value, .unit = unit };
|
||||
}
|
||||
};
|
||||
|
||||
fn sequenceToMatrix(arr: js.Array, m: *[16]f64, is_2d: *bool) !void {
|
||||
const n = arr.len();
|
||||
if (n == 6) {
|
||||
// matrix(a, b, c, d, e, f)
|
||||
var v: [6]f64 = undefined;
|
||||
for (0..6) |i| {
|
||||
v[i] = try (try arr.get(@intCast(i))).toF64();
|
||||
}
|
||||
m.* = .{
|
||||
v[0], v[1], 0, 0,
|
||||
v[2], v[3], 0, 0,
|
||||
0, 0, 1, 0,
|
||||
v[4], v[5], 0, 1,
|
||||
};
|
||||
is_2d.* = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 16) {
|
||||
for (0..16) |i| {
|
||||
m[i] = try (try arr.get(@intCast(i))).toF64();
|
||||
}
|
||||
is_2d.* = false;
|
||||
return;
|
||||
}
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(DOMMatrixReadOnly);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "DOMMatrixReadOnly";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(DOMMatrixReadOnly.init, .{ .dom_exception = true });
|
||||
|
||||
pub const fromMatrix = bridge.function(DOMMatrixReadOnly.fromMatrix, .{ .static = true });
|
||||
pub const fromFloat32Array = bridge.function(DOMMatrixReadOnly.fromFloat32Array, .{ .static = true });
|
||||
pub const fromFloat64Array = bridge.function(DOMMatrixReadOnly.fromFloat64Array, .{ .static = true });
|
||||
|
||||
pub const a = bridge.accessor(DOMMatrixReadOnly.getA, null, .{});
|
||||
pub const b = bridge.accessor(DOMMatrixReadOnly.getB, null, .{});
|
||||
pub const c = bridge.accessor(DOMMatrixReadOnly.getC, null, .{});
|
||||
pub const d = bridge.accessor(DOMMatrixReadOnly.getD, null, .{});
|
||||
pub const e = bridge.accessor(DOMMatrixReadOnly.getE, null, .{});
|
||||
pub const f = bridge.accessor(DOMMatrixReadOnly.getF, null, .{});
|
||||
|
||||
pub const m11 = bridge.accessor(getM(0), null, .{});
|
||||
pub const m12 = bridge.accessor(getM(1), null, .{});
|
||||
pub const m13 = bridge.accessor(getM(2), null, .{});
|
||||
pub const m14 = bridge.accessor(getM(3), null, .{});
|
||||
pub const m21 = bridge.accessor(getM(4), null, .{});
|
||||
pub const m22 = bridge.accessor(getM(5), null, .{});
|
||||
pub const m23 = bridge.accessor(getM(6), null, .{});
|
||||
pub const m24 = bridge.accessor(getM(7), null, .{});
|
||||
pub const m31 = bridge.accessor(getM(8), null, .{});
|
||||
pub const m32 = bridge.accessor(getM(9), null, .{});
|
||||
pub const m33 = bridge.accessor(getM(10), null, .{});
|
||||
pub const m34 = bridge.accessor(getM(11), null, .{});
|
||||
pub const m41 = bridge.accessor(getM(12), null, .{});
|
||||
pub const m42 = bridge.accessor(getM(13), null, .{});
|
||||
pub const m43 = bridge.accessor(getM(14), null, .{});
|
||||
pub const m44 = bridge.accessor(getM(15), null, .{});
|
||||
|
||||
pub const is2D = bridge.accessor(DOMMatrixReadOnly.getIs2D, null, .{});
|
||||
pub const isIdentity = bridge.accessor(DOMMatrixReadOnly.getIsIdentity, null, .{});
|
||||
|
||||
pub const translate = bridge.function(DOMMatrixReadOnly.translate, .{});
|
||||
pub const scale = bridge.function(DOMMatrixReadOnly.scale, .{});
|
||||
pub const scaleNonUniform = bridge.function(DOMMatrixReadOnly.scaleNonUniform, .{});
|
||||
pub const scale3d = bridge.function(DOMMatrixReadOnly.scale3d, .{});
|
||||
pub const rotate = bridge.function(DOMMatrixReadOnly.rotate, .{});
|
||||
pub const rotateFromVector = bridge.function(DOMMatrixReadOnly.rotateFromVector, .{});
|
||||
pub const rotateAxisAngle = bridge.function(DOMMatrixReadOnly.rotateAxisAngle, .{});
|
||||
pub const skewX = bridge.function(DOMMatrixReadOnly.skewX, .{});
|
||||
pub const skewY = bridge.function(DOMMatrixReadOnly.skewY, .{});
|
||||
pub const multiply = bridge.function(DOMMatrixReadOnly.multiply, .{});
|
||||
pub const flipX = bridge.function(DOMMatrixReadOnly.flipX, .{});
|
||||
pub const flipY = bridge.function(DOMMatrixReadOnly.flipY, .{});
|
||||
pub const inverse = bridge.function(DOMMatrixReadOnly.inverse, .{});
|
||||
pub const toFloat32Array = bridge.function(DOMMatrixReadOnly.toFloat32Array, .{});
|
||||
pub const toFloat64Array = bridge.function(DOMMatrixReadOnly.toFloat64Array, .{});
|
||||
// The stringifier depends on CSS serialization and is Window-only.
|
||||
pub const toString = bridge.function(DOMMatrixReadOnly.toString, .{ .dom_exception = true, .exposed = .window });
|
||||
|
||||
// m11..m44 getters are generated from the storage index.
|
||||
fn getM(comptime idx: usize) fn (*const DOMMatrixReadOnly) f64 {
|
||||
return struct {
|
||||
fn get(self: *const DOMMatrixReadOnly) f64 {
|
||||
return self._m[idx];
|
||||
}
|
||||
}.get;
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "WebApi: DOMMatrixReadOnly" {
|
||||
try testing.htmlRunner("dommatrix.html", .{});
|
||||
}
|
||||
Reference in New Issue
Block a user