@grida/svg-editor 1.0.0-alpha.14 → 1.0.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/dist/{dom-Dz_V6q0Y.d.mts → dom-98AUOfsP.d.mts} +44 -2
- package/dist/{dom-D4dy6kq5.d.ts → dom-BO2-E9oK.d.ts} +44 -2
- package/dist/{dom-DSjfCllZ.mjs → dom-DOvcMvl4.mjs} +295 -176
- package/dist/{dom-BuD8TKmL.js → dom-U6ae5fQF.js} +300 -175
- package/dist/dom.d.mts +3 -3
- package/dist/dom.d.ts +3 -3
- package/dist/dom.js +2 -1
- package/dist/dom.mjs +2 -2
- package/dist/{editor-BHHU_Nvz.js → editor-C6Lj1In-.js} +433 -582
- package/dist/{editor-CJ2KuRh5.d.ts → editor-CYoGJ3Hf.d.ts} +500 -24
- package/dist/{editor-YQwdWHBb.d.mts → editor-D2eQe8lB.d.mts} +500 -24
- package/dist/{editor-B6pchGYk.mjs → editor-DKQOIKuU.mjs} +432 -582
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{model-DqGqV1H4.js → model-D0nU_EkL.js} +1245 -79
- package/dist/{model-DIzZmeyf.mjs → model-L3t9ixT_.mjs} +1240 -80
- package/dist/presets.d.mts +2 -2
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +2 -2
- package/dist/presets.mjs +1 -1
- package/dist/react.d.mts +20 -3
- package/dist/react.d.ts +20 -3
- package/dist/react.js +25 -2
- package/dist/react.mjs +25 -3
- package/package.json +29 -5
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const require_model = require("./model-
|
|
1
|
+
const require_model = require("./model-D0nU_EkL.js");
|
|
2
2
|
let _grida_cmath = require("@grida/cmath");
|
|
3
3
|
_grida_cmath = require_model.__toESM(_grida_cmath);
|
|
4
4
|
let _grida_svg_parse = require("@grida/svg/parse");
|
|
5
|
+
let _grida_svg_pathdata = require("@grida/svg/pathdata");
|
|
5
6
|
let _grida_vn = require("@grida/vn");
|
|
6
7
|
_grida_vn = require_model.__toESM(_grida_vn);
|
|
7
8
|
let _grida_text_editor_dom = require("@grida/text-editor/dom");
|
|
@@ -344,6 +345,12 @@ var MemoizedGeometryProvider = class {
|
|
|
344
345
|
node_at_point(p) {
|
|
345
346
|
return this.driver.node_at_point(p);
|
|
346
347
|
}
|
|
348
|
+
/** Pass-through. Frame projection depends on live layout, not on the
|
|
349
|
+
* bounds cache, so there is nothing to memoize. Falls back to the raw
|
|
350
|
+
* delta when the driver can't resolve a frame. */
|
|
351
|
+
world_delta_to_local(id, delta) {
|
|
352
|
+
return this.driver.world_delta_to_local?.(id, delta) ?? delta;
|
|
353
|
+
}
|
|
347
354
|
/** Unsubscribe from both signals. Call on surface detach. */
|
|
348
355
|
dispose() {
|
|
349
356
|
for (const unsub of this.unsubscribers) unsub();
|
|
@@ -1206,7 +1213,8 @@ function sub_selection_equal(a, b) {
|
|
|
1206
1213
|
var VectorEditSession = class {
|
|
1207
1214
|
constructor(node_id, source, session_d) {
|
|
1208
1215
|
this.node_id = node_id;
|
|
1209
|
-
this.
|
|
1216
|
+
this._source = source;
|
|
1217
|
+
this._source_before_promotion = null;
|
|
1210
1218
|
this._session_d = session_d;
|
|
1211
1219
|
this._last_seen_d = session_d;
|
|
1212
1220
|
this._selected_vertices = [];
|
|
@@ -1214,6 +1222,51 @@ var VectorEditSession = class {
|
|
|
1214
1222
|
this._selected_tangents = [];
|
|
1215
1223
|
this._hovered_control = null;
|
|
1216
1224
|
}
|
|
1225
|
+
/** Source tag the session currently projects through. See `_source`. */
|
|
1226
|
+
get source() {
|
|
1227
|
+
return this._source;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Flip the source to `path` after the underlying element was promoted
|
|
1231
|
+
* (rect / circle / ellipse → `<path>`). Idempotent: a second call while
|
|
1232
|
+
* already promoted does nothing, so the pre-promotion source captured by
|
|
1233
|
+
* the first flip is never clobbered.
|
|
1234
|
+
*/
|
|
1235
|
+
promote_source_to_path() {
|
|
1236
|
+
if (this._source_before_promotion !== null) return;
|
|
1237
|
+
if (this._source.kind === "path") return;
|
|
1238
|
+
this._source_before_promotion = this._source;
|
|
1239
|
+
this._source = {
|
|
1240
|
+
kind: "path",
|
|
1241
|
+
d: this._session_d
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
/** Reverse a {@link promote_source_to_path} (gesture undo). No-op if the
|
|
1245
|
+
* source was never promoted. */
|
|
1246
|
+
restore_source() {
|
|
1247
|
+
if (this._source_before_promotion === null) return;
|
|
1248
|
+
this._source = this._source_before_promotion;
|
|
1249
|
+
this._source_before_promotion = null;
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Re-sync the source to the document's current tag, outright. Unlike
|
|
1253
|
+
* {@link promote_source_to_path} / {@link restore_source} (which manage a
|
|
1254
|
+
* single primitive→path flip within one gesture), this sets the source to
|
|
1255
|
+
* an authoritative value derived from the live document and clears the
|
|
1256
|
+
* promotion bookkeeping.
|
|
1257
|
+
*
|
|
1258
|
+
* The host calls this when an undo/redo re-types the node out from under a
|
|
1259
|
+
* *different* live session object than the one that performed the original
|
|
1260
|
+
* flip (exit + undo-exit creates a fresh session; the captured session's
|
|
1261
|
+
* `restore_source` then no-ops). Without it the live session could keep
|
|
1262
|
+
* `source.kind === "path"` while the node is back to a primitive, and the
|
|
1263
|
+
* next gesture would write a stray `d` onto the native tag. Re-deriving
|
|
1264
|
+
* from the document keeps the live session authoritative.
|
|
1265
|
+
*/
|
|
1266
|
+
sync_source(source) {
|
|
1267
|
+
this._source = source;
|
|
1268
|
+
this._source_before_promotion = null;
|
|
1269
|
+
}
|
|
1217
1270
|
/** The session's current PathModel-form `d`. Gesture handlers read
|
|
1218
1271
|
* this instead of `doc.get_attr(node_id, "d")` so they stay tag-
|
|
1219
1272
|
* oblivious (non-path sources have no `d` on the document). */
|
|
@@ -1378,26 +1431,39 @@ var VectorEditSession = class {
|
|
|
1378
1431
|
function source_to_session_d(source) {
|
|
1379
1432
|
switch (source.kind) {
|
|
1380
1433
|
case "path": return source.d;
|
|
1434
|
+
case "line": return _grida_vn.default.toSVGPathData(_grida_vn.default.fromPolyline([[source.x1, source.y1], [source.x2, source.y2]]));
|
|
1381
1435
|
case "polyline": return _grida_vn.default.toSVGPathData(_grida_vn.default.fromPolyline(source.points.map((p) => [p[0], p[1]])));
|
|
1382
1436
|
case "polygon": return _grida_vn.default.toSVGPathData(_grida_vn.default.fromPolygon(source.points.map((p) => [p[0], p[1]])));
|
|
1437
|
+
case "circle": return _grida_vn.default.toSVGPathData(_grida_vn.default.fromEllipse({
|
|
1438
|
+
x: source.cx - source.r,
|
|
1439
|
+
y: source.cy - source.r,
|
|
1440
|
+
width: source.r * 2,
|
|
1441
|
+
height: source.r * 2
|
|
1442
|
+
}));
|
|
1443
|
+
case "ellipse": return _grida_vn.default.toSVGPathData(_grida_vn.default.fromEllipse({
|
|
1444
|
+
x: source.cx - source.rx,
|
|
1445
|
+
y: source.cy - source.ry,
|
|
1446
|
+
width: source.rx * 2,
|
|
1447
|
+
height: source.ry * 2
|
|
1448
|
+
}));
|
|
1449
|
+
case "rect": return _grida_svg_pathdata.SVGShapes.createRect(source.x, source.y, source.width, source.height, source.rx, source.ry).encode();
|
|
1383
1450
|
}
|
|
1384
1451
|
}
|
|
1385
1452
|
/**
|
|
1386
|
-
*
|
|
1453
|
+
* Native-attribute writeback. Given a new path-data `d` from a gesture,
|
|
1387
1454
|
* project it back into the source tag's native attrs and write those —
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1455
|
+
* `<path>` takes `d` directly; `<line>` takes `x1/y1/x2/y2`;
|
|
1456
|
+
* `<polyline>` / `<polygon>` take `points`.
|
|
1390
1457
|
*
|
|
1391
|
-
* Returns `true`
|
|
1392
|
-
* the
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1458
|
+
* Returns `true` if the geometry was written natively. Returns `false`
|
|
1459
|
+
* when the source tag cannot express the geometry — a curve was introduced
|
|
1460
|
+
* or the topology left the tag's canonical form, OR the source is a
|
|
1461
|
+
* geometry primitive (rect / circle / ellipse) which has no native vector
|
|
1462
|
+
* form at all. A `false` return is the re-type-to-`<path>` signal; the
|
|
1463
|
+
* caller ({@link vector_apply}) handles it. This function never re-types.
|
|
1396
1464
|
*
|
|
1397
|
-
* Symmetric across apply / revert:
|
|
1398
|
-
*
|
|
1399
|
-
* geometry to this d"), so apply/revert stay consistent even when one
|
|
1400
|
-
* writes native attrs and the other would.
|
|
1465
|
+
* Symmetric across apply / revert: callers use it for both the in-flight
|
|
1466
|
+
* write and the undo-revert (both are just "set the geometry to this d").
|
|
1401
1467
|
*/
|
|
1402
1468
|
function apply_session_d(doc, node_id, source, d) {
|
|
1403
1469
|
if (source.kind === "path") {
|
|
@@ -1406,10 +1472,62 @@ function apply_session_d(doc, node_id, source, d) {
|
|
|
1406
1472
|
}
|
|
1407
1473
|
const native = require_model.PathModel.fromSvgPathD(d).toNativeAttrs(source.kind);
|
|
1408
1474
|
if (native === null) return false;
|
|
1475
|
+
if (native.kind === "line") {
|
|
1476
|
+
doc.set_attr(node_id, "x1", String(native.x1));
|
|
1477
|
+
doc.set_attr(node_id, "y1", String(native.y1));
|
|
1478
|
+
doc.set_attr(node_id, "x2", String(native.x2));
|
|
1479
|
+
doc.set_attr(node_id, "y2", String(native.y2));
|
|
1480
|
+
return true;
|
|
1481
|
+
}
|
|
1409
1482
|
const points = native.points.map((p) => `${p[0]},${p[1]}`).join(" ");
|
|
1410
1483
|
doc.set_attr(node_id, "points", points);
|
|
1411
1484
|
return true;
|
|
1412
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Session-aware geometry write — the single commit chokepoint the DOM
|
|
1488
|
+
* gesture handlers call so re-typing stays in one place rather than being
|
|
1489
|
+
* reimplemented per gesture. One uniform rule across every source:
|
|
1490
|
+
*
|
|
1491
|
+
* 1. Try native writeback ({@link apply_session_d}). For `<path>` and for
|
|
1492
|
+
* a vertex tag (`line` / `polyline` / `polygon`) whose edit still fits
|
|
1493
|
+
* its native form, this writes and we're done — the element keeps its
|
|
1494
|
+
* tag.
|
|
1495
|
+
* 2. If native writeback refused (a curve was introduced, the topology
|
|
1496
|
+
* escaped the canonical chain, or the source is a geometry primitive
|
|
1497
|
+
* with no native form), re-type the element to `<path>` via
|
|
1498
|
+
* {@link SvgDocument.retype_to_path} and flip the session source to
|
|
1499
|
+
* `path` (so every downstream reader — overlay, gates, the
|
|
1500
|
+
* external-mutation reconciler — behaves correctly).
|
|
1501
|
+
*
|
|
1502
|
+
* Returns the {@link RetypeRecord} token iff this call performed a re-type
|
|
1503
|
+
* (so the caller can pair it with the edit in one history bracket and hand
|
|
1504
|
+
* it to {@link vector_revert} on undo); otherwise `null`.
|
|
1505
|
+
*/
|
|
1506
|
+
function vector_apply(doc, session, d) {
|
|
1507
|
+
if (apply_session_d(doc, session.node_id, session.source, d)) return null;
|
|
1508
|
+
const token = doc.retype_to_path(session.node_id, d);
|
|
1509
|
+
if (token) {
|
|
1510
|
+
session.promote_source_to_path();
|
|
1511
|
+
return token;
|
|
1512
|
+
}
|
|
1513
|
+
doc.set_attr(session.node_id, "d", d);
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Counterpart to {@link vector_apply}. If this gesture re-typed the element
|
|
1518
|
+
* (a non-null `promotion` token), restore the original tag/attrs and the
|
|
1519
|
+
* session source — the re-type and the edit undo as one step. Otherwise
|
|
1520
|
+
* re-write the baseline geometry natively; for a geometry primitive that
|
|
1521
|
+
* never re-typed, {@link apply_session_d} writes nothing (a correct no-op).
|
|
1522
|
+
*/
|
|
1523
|
+
function vector_revert(doc, session, baseline_d, promotion) {
|
|
1524
|
+
if (promotion) {
|
|
1525
|
+
doc.revert_retype(session.node_id, promotion);
|
|
1526
|
+
session.restore_source();
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
apply_session_d(doc, session.node_id, session.source, baseline_d);
|
|
1530
|
+
}
|
|
1413
1531
|
//#endregion
|
|
1414
1532
|
//#region src/core/vector-edit/marquee.ts
|
|
1415
1533
|
let marquee;
|
|
@@ -1521,11 +1639,48 @@ const IS_MODIFIER_KEY = {
|
|
|
1521
1639
|
* surface skips render() during the in-flight mount and doesn't yank the
|
|
1522
1640
|
* live `<text>` element out from under the about-to-mount text surface. */
|
|
1523
1641
|
const TEXT_EDIT_PENDING = { __pending: true };
|
|
1524
|
-
/**
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
1528
|
-
|
|
1642
|
+
/**
|
|
1643
|
+
* Wire a web-font settle source to the editor's geometry channel.
|
|
1644
|
+
*
|
|
1645
|
+
* The DOM surface re-serializes the `<svg>` on every editor tick, but a
|
|
1646
|
+
* `<text>` / `<tspan>` bbox can change with NO attribute write: a web font
|
|
1647
|
+
* finishing load AFTER its `font-family` / `font-size` was already written.
|
|
1648
|
+
* The IR never sees that reflow, so nothing bumps `geometry_version` and
|
|
1649
|
+
* every bounds-keyed consumer (snap, HUD chrome, size meter) stays stuck at
|
|
1650
|
+
* the fallback-face metrics until the next real edit.
|
|
1651
|
+
*
|
|
1652
|
+
* Listens for `loadingdone` on `source` (a `FontFaceSet`, or any injected
|
|
1653
|
+
* `EventTarget` in tests) and calls `bump` once per settle. COARSE on
|
|
1654
|
+
* purpose: one bump clears the WHOLE bounds cache, not just text nodes —
|
|
1655
|
+
* consistent with the package's pessimistic-invalidation stance, and far
|
|
1656
|
+
* cheaper than scoping the bump to the (possibly many) reflowed runs.
|
|
1657
|
+
*
|
|
1658
|
+
* Also bumps once when `source.ready` resolves (when present): fonts that
|
|
1659
|
+
* settled before attach — a cache hit, or `font-display` resolving the same
|
|
1660
|
+
* tick the surface mounts — never re-fire `loadingdone`, so a document
|
|
1661
|
+
* mounted post-settle still needs one bump to re-read at the real metrics.
|
|
1662
|
+
*
|
|
1663
|
+
* Returns a teardown that removes the listener and neutralizes the pending
|
|
1664
|
+
* `ready` bump (leak guard) — call it on surface detach.
|
|
1665
|
+
*
|
|
1666
|
+
* Factored out of the surface so it can be unit-tested with a fake
|
|
1667
|
+
* `EventTarget` in the node-only test env (jsdom's `FontFaceSet` is
|
|
1668
|
+
* incomplete); never a real font / network.
|
|
1669
|
+
*/
|
|
1670
|
+
function install_font_load_geometry_bump(source, bump) {
|
|
1671
|
+
if (!source) return () => {};
|
|
1672
|
+
const on_fonts_settled = () => bump();
|
|
1673
|
+
source.addEventListener("loadingdone", on_fonts_settled);
|
|
1674
|
+
let alive = true;
|
|
1675
|
+
const ready = source.ready;
|
|
1676
|
+
if (ready && typeof ready.then === "function") ready.then(() => {
|
|
1677
|
+
if (alive) bump();
|
|
1678
|
+
});
|
|
1679
|
+
return () => {
|
|
1680
|
+
alive = false;
|
|
1681
|
+
source.removeEventListener("loadingdone", on_fonts_settled);
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1529
1684
|
/**
|
|
1530
1685
|
* Attach a DOM surface to a headless editor. Returns a `DomSurfaceHandle`
|
|
1531
1686
|
* whose `detach()` is the inverse — DOM cleared, listeners removed,
|
|
@@ -1580,6 +1735,7 @@ var DomSurface = class DomSurface {
|
|
|
1580
1735
|
this.teardown.push(() => this.attention.dispose());
|
|
1581
1736
|
if (process.env.NODE_ENV !== "production" && container.children.length > 0) console.warn("@grida/svg-editor: surface container is not empty at attach time. Render chrome (toolbars, layer lists, inspectors) as siblings of the container, not children — otherwise clicks on those children will silently break. See README §Surface.");
|
|
1582
1737
|
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
1738
|
+
container.style.overflow = "hidden";
|
|
1583
1739
|
container.style.userSelect = "none";
|
|
1584
1740
|
container.style.webkitUserSelect = "none";
|
|
1585
1741
|
const translate_options = () => {
|
|
@@ -1596,7 +1752,8 @@ var DomSurface = class DomSurface {
|
|
|
1596
1752
|
emit: () => this.editor_internal().emit(),
|
|
1597
1753
|
open_preview: (label) => this.editor_internal().history.preview(label),
|
|
1598
1754
|
open_snap: (ids) => this.open_snap_session_for(ids),
|
|
1599
|
-
options: translate_options
|
|
1755
|
+
options: translate_options,
|
|
1756
|
+
project_delta: (id, d) => this._geometry_provider?.world_delta_to_local(id, d) ?? d
|
|
1600
1757
|
});
|
|
1601
1758
|
const resize_options = () => {
|
|
1602
1759
|
const style = this.editor.style;
|
|
@@ -1664,6 +1821,7 @@ var DomSurface = class DomSurface {
|
|
|
1664
1821
|
shapeOf: (id) => this.shape_of(id),
|
|
1665
1822
|
vectorOf: (id) => this.vector_of(id),
|
|
1666
1823
|
onIntent: (i) => this.commit_intent(i),
|
|
1824
|
+
onTap: (t) => this.handle_tap(t),
|
|
1667
1825
|
style: {
|
|
1668
1826
|
chromeColor: editor.style.chrome_color,
|
|
1669
1827
|
showRotationHandles: true
|
|
@@ -1770,6 +1928,8 @@ var DomSurface = class DomSurface {
|
|
|
1770
1928
|
win.addEventListener("resize", fn);
|
|
1771
1929
|
this.teardown.push(() => win.removeEventListener("resize", fn));
|
|
1772
1930
|
}
|
|
1931
|
+
const detach_font_listener = install_font_load_geometry_bump(options.font_load_source ?? container.ownerDocument.fonts ?? null, () => editor._internal.bump_geometry());
|
|
1932
|
+
this.teardown.push(detach_font_listener);
|
|
1773
1933
|
this.wire_events();
|
|
1774
1934
|
const internal = editor._internal;
|
|
1775
1935
|
this.editor_hover_internal = internal;
|
|
@@ -2336,14 +2496,15 @@ var DomSurface = class DomSurface {
|
|
|
2336
2496
|
const neighbor_ids = compute_neighborhood(doc, ids);
|
|
2337
2497
|
const agent_id_set = /* @__PURE__ */ new Set();
|
|
2338
2498
|
for (const id of ids) for (const inner of snap_descent(doc, id)) agent_id_set.add(inner);
|
|
2499
|
+
const bounds_of = (id) => this._geometry_provider?.bounds_of(id) ?? null;
|
|
2339
2500
|
const agents = [];
|
|
2340
2501
|
for (const id of agent_id_set) {
|
|
2341
|
-
const r =
|
|
2502
|
+
const r = bounds_of(id);
|
|
2342
2503
|
if (r) agents.push(r);
|
|
2343
2504
|
}
|
|
2344
2505
|
const neighbors = [];
|
|
2345
2506
|
for (const id of neighbor_ids) {
|
|
2346
|
-
const r =
|
|
2507
|
+
const r = bounds_of(id);
|
|
2347
2508
|
if (r) neighbors.push(r);
|
|
2348
2509
|
}
|
|
2349
2510
|
return new SnapSession({
|
|
@@ -2852,6 +3013,26 @@ var DomSurface = class DomSurface {
|
|
|
2852
3013
|
if (this.editor.keymap.claims(e)) e.preventDefault();
|
|
2853
3014
|
this.editor.keymap.dispatch(e);
|
|
2854
3015
|
}
|
|
3016
|
+
/**
|
|
3017
|
+
* Re-express a HUD tap as an editor {@link PickEvent} and fan it out on the
|
|
3018
|
+
* editor's pick channel. The HUD already resolved everything that matters —
|
|
3019
|
+
* the pointer-down point, the hit node, and click-vs-drag — so this is a
|
|
3020
|
+
* pure translation (HUD `[x, y]` tuple → editor `{ x, y }` doc-space point)
|
|
3021
|
+
* with NO re-hit-testing. Taking the hit from the HUD (not a fresh
|
|
3022
|
+
* `node_at_point`) guarantees the pick and the selection it accompanies can
|
|
3023
|
+
* never disagree. Observe-only: this mutates no editor state.
|
|
3024
|
+
*/
|
|
3025
|
+
handle_tap(tap) {
|
|
3026
|
+
this.editor._internal.push_pick({
|
|
3027
|
+
point: {
|
|
3028
|
+
x: tap.point[0],
|
|
3029
|
+
y: tap.point[1]
|
|
3030
|
+
},
|
|
3031
|
+
node_id: tap.hit,
|
|
3032
|
+
button: tap.button,
|
|
3033
|
+
mods: tap.mods
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
2855
3036
|
commit_intent(intent) {
|
|
2856
3037
|
switch (intent.kind) {
|
|
2857
3038
|
case "select":
|
|
@@ -3261,7 +3442,7 @@ var DomSurface = class DomSurface {
|
|
|
3261
3442
|
if (this.text_edit || this.vector_edit) return false;
|
|
3262
3443
|
const tag = this.tag_of(id);
|
|
3263
3444
|
if (tag === "text" || tag === "tspan") return this.enter_text_edit(id);
|
|
3264
|
-
if (
|
|
3445
|
+
if (this.editor_internal().doc.is_vector_edit_target(id) !== null) return this.enter_vector_edit(id);
|
|
3265
3446
|
return false;
|
|
3266
3447
|
}
|
|
3267
3448
|
/**
|
|
@@ -3544,11 +3725,11 @@ var DomSurface = class DomSurface {
|
|
|
3544
3725
|
b_control: project_point_through_ctm(b_ctrl_local[0], b_ctrl_local[1], ctm, offset)
|
|
3545
3726
|
};
|
|
3546
3727
|
}),
|
|
3547
|
-
neighbours:
|
|
3728
|
+
neighbours: model.neighbouringVertices({
|
|
3548
3729
|
vertices: this.vector_edit.selected_vertices,
|
|
3549
3730
|
segments: this.vector_edit.selected_segments,
|
|
3550
3731
|
tangents: this.vector_edit.selected_tangents
|
|
3551
|
-
})
|
|
3732
|
+
}),
|
|
3552
3733
|
origin: [0, 0]
|
|
3553
3734
|
};
|
|
3554
3735
|
}
|
|
@@ -3613,11 +3794,54 @@ var DomSurface = class DomSurface {
|
|
|
3613
3794
|
replay_vector_session_state(target_node_id, d, selection) {
|
|
3614
3795
|
const cur = this.vector_edit;
|
|
3615
3796
|
if (!cur || cur.node_id !== target_node_id) return;
|
|
3616
|
-
if (d !== null)
|
|
3797
|
+
if (d !== null) {
|
|
3798
|
+
cur.mark_seen(d);
|
|
3799
|
+
const live_source = this.editor_internal().doc.is_vector_edit_target(cur.node_id);
|
|
3800
|
+
if (live_source) cur.sync_source(live_source);
|
|
3801
|
+
}
|
|
3617
3802
|
cur.restore_selection(selection);
|
|
3618
3803
|
this.sync_selection_mirror();
|
|
3619
3804
|
}
|
|
3620
3805
|
/**
|
|
3806
|
+
* Build the `{ apply, revert }` history step for a vector-edit geometry
|
|
3807
|
+
* delta — the single chokepoint that routes the write through
|
|
3808
|
+
* {@link vector_apply} / {@link vector_revert} so promote-to-path of a
|
|
3809
|
+
* primitive source (rect / circle / ellipse) is handled in one place
|
|
3810
|
+
* rather than per gesture.
|
|
3811
|
+
*
|
|
3812
|
+
* `promo` is the per-gesture token holder (shared by reference across an
|
|
3813
|
+
* `active_preview`'s preview frames and its committed step) so the
|
|
3814
|
+
* promotion that fires on the first frame is the one undo reverses —
|
|
3815
|
+
* promotion + first edit collapse into a single undo step. On redo after
|
|
3816
|
+
* an undo-demote, `apply` re-promotes and refreshes the token.
|
|
3817
|
+
*
|
|
3818
|
+
* `after_selection` / `before_selection` drive sub-selection replay;
|
|
3819
|
+
* pass `null` (preview frames) to skip it.
|
|
3820
|
+
*/
|
|
3821
|
+
vector_geometry_step(node_id, target_d, baseline_d, promo, after_selection, before_selection) {
|
|
3822
|
+
const internal = this.editor_internal();
|
|
3823
|
+
const doc = internal.doc;
|
|
3824
|
+
const emit = internal.emit;
|
|
3825
|
+
const session = this.vector_edit;
|
|
3826
|
+
return {
|
|
3827
|
+
providerId: "svg-editor",
|
|
3828
|
+
apply: () => {
|
|
3829
|
+
if (session) {
|
|
3830
|
+
const tok = vector_apply(doc, session, target_d);
|
|
3831
|
+
if (tok) promo.token = tok;
|
|
3832
|
+
}
|
|
3833
|
+
if (after_selection) this.replay_vector_session_state(node_id, target_d, after_selection);
|
|
3834
|
+
emit();
|
|
3835
|
+
},
|
|
3836
|
+
revert: () => {
|
|
3837
|
+
if (session) vector_revert(doc, session, baseline_d, promo.token);
|
|
3838
|
+
promo.token = null;
|
|
3839
|
+
if (before_selection) this.replay_vector_session_state(node_id, baseline_d, before_selection);
|
|
3840
|
+
emit();
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
/**
|
|
3621
3845
|
* Push a standalone vector sub-selection change as one history entry.
|
|
3622
3846
|
*
|
|
3623
3847
|
* Called by selection-only handlers (vertex / segment / tangent click,
|
|
@@ -3709,11 +3933,7 @@ var DomSurface = class DomSurface {
|
|
|
3709
3933
|
handle_translate_vertices(intent) {
|
|
3710
3934
|
if (!this.vector_edit || this.vector_edit.node_id !== intent.node_id) return;
|
|
3711
3935
|
const internal = this.editor_internal();
|
|
3712
|
-
const doc = internal.doc;
|
|
3713
|
-
const emit = internal.emit;
|
|
3714
3936
|
const node_id = intent.node_id;
|
|
3715
|
-
const source = this.vector_edit.source;
|
|
3716
|
-
const commit = (d) => apply_session_d(doc, node_id, source, d);
|
|
3717
3937
|
if (!this.active_preview || this.active_preview.kind !== "vector_vertex_translate" || this.active_preview.node_id !== node_id || !require_model.array_shallow_equal(this.active_preview.indices, intent.indices)) {
|
|
3718
3938
|
if (this.active_preview) {
|
|
3719
3939
|
if ("session" in this.active_preview) this.active_preview.session.discard();
|
|
@@ -3724,6 +3944,7 @@ var DomSurface = class DomSurface {
|
|
|
3724
3944
|
this.active_preview = {
|
|
3725
3945
|
kind: "vector_vertex_translate",
|
|
3726
3946
|
node_id,
|
|
3947
|
+
promo: { token: null },
|
|
3727
3948
|
indices: [...intent.indices],
|
|
3728
3949
|
initial_d,
|
|
3729
3950
|
baseline_model,
|
|
@@ -3749,32 +3970,10 @@ var DomSurface = class DomSurface {
|
|
|
3749
3970
|
if (intent.phase === "commit") {
|
|
3750
3971
|
const before_selection = this.active_preview.before_selection;
|
|
3751
3972
|
const after_selection = this.vector_edit.snapshot_selection();
|
|
3752
|
-
this.active_preview.session.set(
|
|
3753
|
-
providerId: "svg-editor",
|
|
3754
|
-
apply: () => {
|
|
3755
|
-
commit(target_d);
|
|
3756
|
-
this.replay_vector_session_state(node_id, target_d, after_selection);
|
|
3757
|
-
emit();
|
|
3758
|
-
},
|
|
3759
|
-
revert: () => {
|
|
3760
|
-
commit(baseline_d);
|
|
3761
|
-
this.replay_vector_session_state(node_id, baseline_d, before_selection);
|
|
3762
|
-
emit();
|
|
3763
|
-
}
|
|
3764
|
-
});
|
|
3973
|
+
this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, after_selection, before_selection));
|
|
3765
3974
|
this.active_preview.session.commit();
|
|
3766
3975
|
this.active_preview = null;
|
|
3767
|
-
} else this.active_preview.session.set(
|
|
3768
|
-
providerId: "svg-editor",
|
|
3769
|
-
apply: () => {
|
|
3770
|
-
commit(target_d);
|
|
3771
|
-
emit();
|
|
3772
|
-
},
|
|
3773
|
-
revert: () => {
|
|
3774
|
-
commit(baseline_d);
|
|
3775
|
-
emit();
|
|
3776
|
-
}
|
|
3777
|
-
});
|
|
3976
|
+
} else this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, null, null));
|
|
3778
3977
|
}
|
|
3779
3978
|
/**
|
|
3780
3979
|
* `translate_vector_selection` — the sub-selection-aware delta-translate.
|
|
@@ -3806,11 +4005,7 @@ var DomSurface = class DomSurface {
|
|
|
3806
4005
|
handle_translate_vector_selection(intent) {
|
|
3807
4006
|
if (!this.vector_edit || this.vector_edit.node_id !== intent.node_id) return;
|
|
3808
4007
|
const internal = this.editor_internal();
|
|
3809
|
-
const doc = internal.doc;
|
|
3810
|
-
const emit = internal.emit;
|
|
3811
4008
|
const node_id = intent.node_id;
|
|
3812
|
-
const source = this.vector_edit.source;
|
|
3813
|
-
const commit = (d) => apply_session_d(doc, node_id, source, d);
|
|
3814
4009
|
const ses = this.vector_edit;
|
|
3815
4010
|
const current_d = this.read_session_d();
|
|
3816
4011
|
if (current_d === null) return;
|
|
@@ -3847,6 +4042,7 @@ var DomSurface = class DomSurface {
|
|
|
3847
4042
|
this.active_preview = {
|
|
3848
4043
|
kind: "vector_translate_selection",
|
|
3849
4044
|
node_id,
|
|
4045
|
+
promo: { token: null },
|
|
3850
4046
|
indices: [...indices],
|
|
3851
4047
|
tangent_refs: [...tangent_refs],
|
|
3852
4048
|
initial_d,
|
|
@@ -3880,32 +4076,10 @@ var DomSurface = class DomSurface {
|
|
|
3880
4076
|
if (intent.phase === "commit") {
|
|
3881
4077
|
const before_selection = this.active_preview.before_selection;
|
|
3882
4078
|
const after_selection = this.vector_edit.snapshot_selection();
|
|
3883
|
-
this.active_preview.session.set(
|
|
3884
|
-
providerId: "svg-editor",
|
|
3885
|
-
apply: () => {
|
|
3886
|
-
commit(target_d);
|
|
3887
|
-
this.replay_vector_session_state(node_id, target_d, after_selection);
|
|
3888
|
-
emit();
|
|
3889
|
-
},
|
|
3890
|
-
revert: () => {
|
|
3891
|
-
commit(baseline_d);
|
|
3892
|
-
this.replay_vector_session_state(node_id, baseline_d, before_selection);
|
|
3893
|
-
emit();
|
|
3894
|
-
}
|
|
3895
|
-
});
|
|
4079
|
+
this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, after_selection, before_selection));
|
|
3896
4080
|
this.active_preview.session.commit();
|
|
3897
4081
|
this.active_preview = null;
|
|
3898
|
-
} else this.active_preview.session.set(
|
|
3899
|
-
providerId: "svg-editor",
|
|
3900
|
-
apply: () => {
|
|
3901
|
-
commit(target_d);
|
|
3902
|
-
emit();
|
|
3903
|
-
},
|
|
3904
|
-
revert: () => {
|
|
3905
|
-
commit(baseline_d);
|
|
3906
|
-
emit();
|
|
3907
|
-
}
|
|
3908
|
-
});
|
|
4082
|
+
} else this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, null, null));
|
|
3909
4083
|
}
|
|
3910
4084
|
/** Mirror handler for `select_tangent` (analogous to `select_vertex`). */
|
|
3911
4085
|
handle_select_tangent(intent) {
|
|
@@ -3930,13 +4104,8 @@ var DomSurface = class DomSurface {
|
|
|
3930
4104
|
*/
|
|
3931
4105
|
handle_set_tangent_intent(intent) {
|
|
3932
4106
|
if (!this.vector_edit || this.vector_edit.node_id !== intent.node_id) return;
|
|
3933
|
-
if (this.vector_edit.source.kind !== "path") return;
|
|
3934
4107
|
const internal = this.editor_internal();
|
|
3935
|
-
const doc = internal.doc;
|
|
3936
|
-
const emit = internal.emit;
|
|
3937
4108
|
const node_id = intent.node_id;
|
|
3938
|
-
const source = this.vector_edit.source;
|
|
3939
|
-
const commit = (d) => apply_session_d(doc, node_id, source, d);
|
|
3940
4109
|
if (!this.active_preview || this.active_preview.kind !== "vector_set_tangent" || this.active_preview.node_id !== node_id || this.active_preview.tangent[0] !== intent.tangent[0] || this.active_preview.tangent[1] !== intent.tangent[1]) {
|
|
3941
4110
|
if (this.active_preview && "session" in this.active_preview) this.active_preview.session.discard();
|
|
3942
4111
|
const initial_d = this.read_session_d();
|
|
@@ -3945,6 +4114,7 @@ var DomSurface = class DomSurface {
|
|
|
3945
4114
|
this.active_preview = {
|
|
3946
4115
|
kind: "vector_set_tangent",
|
|
3947
4116
|
node_id,
|
|
4117
|
+
promo: { token: null },
|
|
3948
4118
|
tangent: [intent.tangent[0], intent.tangent[1]],
|
|
3949
4119
|
initial_d,
|
|
3950
4120
|
baseline_model,
|
|
@@ -3962,32 +4132,10 @@ var DomSurface = class DomSurface {
|
|
|
3962
4132
|
if (intent.phase === "commit") {
|
|
3963
4133
|
const before_selection = this.active_preview.before_selection;
|
|
3964
4134
|
const after_selection = this.vector_edit.snapshot_selection();
|
|
3965
|
-
this.active_preview.session.set(
|
|
3966
|
-
providerId: "svg-editor",
|
|
3967
|
-
apply: () => {
|
|
3968
|
-
commit(target_d);
|
|
3969
|
-
this.replay_vector_session_state(node_id, target_d, after_selection);
|
|
3970
|
-
emit();
|
|
3971
|
-
},
|
|
3972
|
-
revert: () => {
|
|
3973
|
-
commit(baseline_d);
|
|
3974
|
-
this.replay_vector_session_state(node_id, baseline_d, before_selection);
|
|
3975
|
-
emit();
|
|
3976
|
-
}
|
|
3977
|
-
});
|
|
4135
|
+
this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, after_selection, before_selection));
|
|
3978
4136
|
this.active_preview.session.commit();
|
|
3979
4137
|
this.active_preview = null;
|
|
3980
|
-
} else this.active_preview.session.set(
|
|
3981
|
-
providerId: "svg-editor",
|
|
3982
|
-
apply: () => {
|
|
3983
|
-
commit(target_d);
|
|
3984
|
-
emit();
|
|
3985
|
-
},
|
|
3986
|
-
revert: () => {
|
|
3987
|
-
commit(baseline_d);
|
|
3988
|
-
emit();
|
|
3989
|
-
}
|
|
3990
|
-
});
|
|
4138
|
+
} else this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, null, null));
|
|
3991
4139
|
}
|
|
3992
4140
|
/**
|
|
3993
4141
|
* Split a segment at parametric position `t`. One-shot atomic edit; no
|
|
@@ -3998,35 +4146,20 @@ var DomSurface = class DomSurface {
|
|
|
3998
4146
|
handle_split_segment(intent) {
|
|
3999
4147
|
if (!this.vector_edit || this.vector_edit.node_id !== intent.node_id) return;
|
|
4000
4148
|
const node_id = intent.node_id;
|
|
4001
|
-
const source = this.vector_edit.source;
|
|
4002
|
-
const commit = (d) => apply_session_d(doc, node_id, source, d);
|
|
4003
4149
|
const baseline_d = this.read_session_d();
|
|
4004
4150
|
if (baseline_d === null) return;
|
|
4005
4151
|
const { model: next_model, new_vertex } = require_model.PathModel.fromSvgPathD(baseline_d).splitSegment(intent.segment, intent.t);
|
|
4006
4152
|
const target_d = next_model.toSvgPathD();
|
|
4007
4153
|
const internal = this.editor_internal();
|
|
4008
|
-
const doc = internal.doc;
|
|
4009
|
-
const emit = internal.emit;
|
|
4010
4154
|
const before_selection = this.vector_edit.snapshot_selection();
|
|
4011
4155
|
const after_selection = Object.freeze({
|
|
4012
4156
|
vertices: Object.freeze([new_vertex]),
|
|
4013
4157
|
segments: Object.freeze([]),
|
|
4014
4158
|
tangents: Object.freeze([])
|
|
4015
4159
|
});
|
|
4160
|
+
const promo = { token: null };
|
|
4016
4161
|
const split_session = internal.history.preview("vector/split-segment");
|
|
4017
|
-
split_session.set(
|
|
4018
|
-
providerId: "svg-editor",
|
|
4019
|
-
apply: () => {
|
|
4020
|
-
commit(target_d);
|
|
4021
|
-
this.replay_vector_session_state(node_id, target_d, after_selection);
|
|
4022
|
-
emit();
|
|
4023
|
-
},
|
|
4024
|
-
revert: () => {
|
|
4025
|
-
commit(baseline_d);
|
|
4026
|
-
this.replay_vector_session_state(node_id, baseline_d, before_selection);
|
|
4027
|
-
emit();
|
|
4028
|
-
}
|
|
4029
|
-
});
|
|
4162
|
+
split_session.set(this.vector_geometry_step(node_id, target_d, baseline_d, promo, after_selection, before_selection));
|
|
4030
4163
|
split_session.commit();
|
|
4031
4164
|
this.redraw();
|
|
4032
4165
|
}
|
|
@@ -4041,13 +4174,8 @@ var DomSurface = class DomSurface {
|
|
|
4041
4174
|
*/
|
|
4042
4175
|
handle_bend_segment(intent) {
|
|
4043
4176
|
if (!this.vector_edit || this.vector_edit.node_id !== intent.node_id) return;
|
|
4044
|
-
if (this.vector_edit.source.kind !== "path") return;
|
|
4045
4177
|
const internal = this.editor_internal();
|
|
4046
|
-
const doc = internal.doc;
|
|
4047
|
-
const emit = internal.emit;
|
|
4048
4178
|
const node_id = intent.node_id;
|
|
4049
|
-
const source = this.vector_edit.source;
|
|
4050
|
-
const commit = (d) => apply_session_d(doc, node_id, source, d);
|
|
4051
4179
|
if (!this.active_preview || this.active_preview.kind !== "vector_bend_segment" || this.active_preview.node_id !== node_id || this.active_preview.segment !== intent.segment || this.active_preview.ca !== intent.ca) {
|
|
4052
4180
|
if (this.active_preview && "session" in this.active_preview) this.active_preview.session.discard();
|
|
4053
4181
|
const initial_d = this.read_session_d();
|
|
@@ -4061,6 +4189,7 @@ var DomSurface = class DomSurface {
|
|
|
4061
4189
|
this.active_preview = {
|
|
4062
4190
|
kind: "vector_bend_segment",
|
|
4063
4191
|
node_id,
|
|
4192
|
+
promo: { token: null },
|
|
4064
4193
|
segment: intent.segment,
|
|
4065
4194
|
ca: intent.ca,
|
|
4066
4195
|
frozen: {
|
|
@@ -4086,32 +4215,10 @@ var DomSurface = class DomSurface {
|
|
|
4086
4215
|
if (intent.phase === "commit") {
|
|
4087
4216
|
const before_selection = this.active_preview.before_selection;
|
|
4088
4217
|
const after_selection = this.vector_edit.snapshot_selection();
|
|
4089
|
-
this.active_preview.session.set(
|
|
4090
|
-
providerId: "svg-editor",
|
|
4091
|
-
apply: () => {
|
|
4092
|
-
commit(target_d);
|
|
4093
|
-
this.replay_vector_session_state(node_id, target_d, after_selection);
|
|
4094
|
-
emit();
|
|
4095
|
-
},
|
|
4096
|
-
revert: () => {
|
|
4097
|
-
commit(baseline_d);
|
|
4098
|
-
this.replay_vector_session_state(node_id, baseline_d, before_selection);
|
|
4099
|
-
emit();
|
|
4100
|
-
}
|
|
4101
|
-
});
|
|
4218
|
+
this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, after_selection, before_selection));
|
|
4102
4219
|
this.active_preview.session.commit();
|
|
4103
4220
|
this.active_preview = null;
|
|
4104
|
-
} else this.active_preview.session.set(
|
|
4105
|
-
providerId: "svg-editor",
|
|
4106
|
-
apply: () => {
|
|
4107
|
-
commit(target_d);
|
|
4108
|
-
emit();
|
|
4109
|
-
},
|
|
4110
|
-
revert: () => {
|
|
4111
|
-
commit(baseline_d);
|
|
4112
|
-
emit();
|
|
4113
|
-
}
|
|
4114
|
-
});
|
|
4221
|
+
} else this.active_preview.session.set(this.vector_geometry_step(node_id, target_d, baseline_d, this.active_preview.promo, null, null));
|
|
4115
4222
|
}
|
|
4116
4223
|
/**
|
|
4117
4224
|
* Project a doc-space point (HUD's container CSS-px frame) back to the
|
|
@@ -4168,24 +4275,6 @@ var DomSurface = class DomSurface {
|
|
|
4168
4275
|
const transform_str = this.editor.document.get_attr(id, "transform");
|
|
4169
4276
|
return require_model.transform.project(local, transform_str);
|
|
4170
4277
|
}
|
|
4171
|
-
/** World-space rect for snap purposes. Differs from `bbox_world` for
|
|
4172
|
-
* `<svg>` viewport-establishing elements: `getBBox()` on an `<svg>`
|
|
4173
|
-
* reports the union of descendant geometry (SVG 2 §4.6.4), which —
|
|
4174
|
-
* when the dragged element is a descendant — silently turns the
|
|
4175
|
-
* dragged element's own pre-gesture position into a snap target via
|
|
4176
|
-
* the parent's edges. Use the viewport extent instead so the root
|
|
4177
|
-
* SVG's snap edges represent the canvas boundary, not "wherever the
|
|
4178
|
-
* children happen to be right now". */
|
|
4179
|
-
bbox_world_for_snap(id) {
|
|
4180
|
-
if (this.tag_of(id) === "svg") {
|
|
4181
|
-
const el = this.element_index.get(id);
|
|
4182
|
-
if (el instanceof SVGSVGElement) {
|
|
4183
|
-
const vp = svg_viewport_bounds(el);
|
|
4184
|
-
if (vp) return vp;
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
return this.bbox_world(id);
|
|
4188
|
-
}
|
|
4189
4278
|
editor_internal() {
|
|
4190
4279
|
return this.editor._internal;
|
|
4191
4280
|
}
|
|
@@ -4440,6 +4529,36 @@ var SvgGeometryDriver = class {
|
|
|
4440
4529
|
node_at_point(p) {
|
|
4441
4530
|
return this.accessors.pick_at_world(p, true);
|
|
4442
4531
|
}
|
|
4532
|
+
/** World→local delta projection. The frame an element's position is
|
|
4533
|
+
* written in is its PARENT user-space: a `<rect>`'s `x`/`y` and the
|
|
4534
|
+
* leading `translate(...)` composed onto a `<g>`/transformed node are
|
|
4535
|
+
* both interpreted there. We take the parent element's frame (not the
|
|
4536
|
+
* element's own) so that translating a node whose OWN transform has a
|
|
4537
|
+
* scale/rotation is not double-counted.
|
|
4538
|
+
*
|
|
4539
|
+
* Camera-free: `inv(root.getScreenCTM) ∘ parent.getScreenCTM` maps
|
|
4540
|
+
* parent user-space → root world-space, cancelling the shared CSS /
|
|
4541
|
+
* camera transform. Inverting its linear part turns a world delta into
|
|
4542
|
+
* the local delta. Identity (→ delta unchanged) for flat frames,
|
|
4543
|
+
* top-level nodes, and any degenerate / unavailable matrix. */
|
|
4544
|
+
world_delta_to_local(id, delta) {
|
|
4545
|
+
const parent = this.accessors.element_for(id)?.parentNode;
|
|
4546
|
+
const root = this.accessors.root();
|
|
4547
|
+
if (!(parent instanceof SVGGraphicsElement) || !root) return delta;
|
|
4548
|
+
if (parent === root) return delta;
|
|
4549
|
+
if (typeof parent.getScreenCTM !== "function" || typeof root.getScreenCTM !== "function") return delta;
|
|
4550
|
+
const parent_ctm = parent.getScreenCTM();
|
|
4551
|
+
const root_ctm = root.getScreenCTM();
|
|
4552
|
+
if (!parent_ctm || !root_ctm) return delta;
|
|
4553
|
+
const m = root_ctm.inverse().multiply(parent_ctm);
|
|
4554
|
+
const det = m.a * m.d - m.c * m.b;
|
|
4555
|
+
if (!Number.isFinite(det) || det === 0) return delta;
|
|
4556
|
+
const [x, y] = project_delta_inverse_ctm(delta.x, delta.y, m);
|
|
4557
|
+
return {
|
|
4558
|
+
x,
|
|
4559
|
+
y
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4443
4562
|
};
|
|
4444
4563
|
var SvgHitShapeDriver = class {
|
|
4445
4564
|
constructor(accessors) {
|
|
@@ -4493,6 +4612,12 @@ Object.defineProperty(exports, "attach_dom_surface", {
|
|
|
4493
4612
|
return attach_dom_surface;
|
|
4494
4613
|
}
|
|
4495
4614
|
});
|
|
4615
|
+
Object.defineProperty(exports, "install_font_load_geometry_bump", {
|
|
4616
|
+
enumerable: true,
|
|
4617
|
+
get: function() {
|
|
4618
|
+
return install_font_load_geometry_bump;
|
|
4619
|
+
}
|
|
4620
|
+
});
|
|
4496
4621
|
Object.defineProperty(exports, "inverse_project_rect", {
|
|
4497
4622
|
enumerable: true,
|
|
4498
4623
|
get: function() {
|