sketchmark 1.3.7 → 1.4.1
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 +21 -8
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/ast/types.d.ts +4 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/export/index.d.ts.map +1 -1
- package/dist/export/state.d.ts +8 -0
- package/dist/export/state.d.ts.map +1 -0
- package/dist/index.cjs +366 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +366 -48
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/shared.d.ts +16 -0
- package/dist/renderer/shared.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts +4 -2
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +366 -48
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { AnimationController, ANIMATION_CSS } from "./animation";
|
|
|
12
12
|
export type { AnimationEvent, AnimationEventType } from "./animation";
|
|
13
13
|
export { exportSVG, exportPNG, exportCanvasPNG, exportHTML, exportGIF, exportMP4, getSVGBlob, svgToPNGDataURL, } from "./export";
|
|
14
14
|
export type { ExportFormat, ExportOptions } from "./export";
|
|
15
|
-
export type { NodeShape, EdgeConnector, EdgeAnchor, LayoutType, AlignItems, JustifyContent, AnimationAction, AnimationTrigger, StyleProps, StepPace, ASTNode, ASTEdge, ASTGroup, ASTStep, ASTBeat, ASTStepItem, ASTChart, ASTTable, GroupChildRef, RootItemRef, ASTMarkdown, } from "./ast/types";
|
|
15
|
+
export type { NodeShape, EdgeConnector, EdgeAnchor, EdgeRoute, EdgePoint, LayoutType, AlignItems, JustifyContent, AnimationAction, AnimationTrigger, StyleProps, StepPace, ASTNode, ASTEdge, ASTGroup, ASTStep, ASTBeat, ASTStepItem, ASTChart, ASTTable, GroupChildRef, RootItemRef, ASTMarkdown, } from "./ast/types";
|
|
16
16
|
export { hashStr, clamp, lerp, parseHex, sleep, throttle, debounce, EventEmitter, } from "./utils";
|
|
17
17
|
export { render } from "./render";
|
|
18
18
|
export type { RenderOptions, DiagramInstance } from "./render";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1E,YAAY,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,EACL,SAAS,EACT,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5D,YAAY,EACV,SAAS,EACT,aAAa,EACb,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAAO,EACP,OAAO,EACP,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,EACtB,iCAAiC,GAClC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1E,YAAY,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,EACL,SAAS,EACT,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5D,YAAY,EACV,SAAS,EACT,aAAa,EACb,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAAO,EACP,OAAO,EACP,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,EACtB,iCAAiC,GAClC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -357,6 +357,39 @@ function isValueToken(t) {
|
|
|
357
357
|
function isPropKeyToken(t) {
|
|
358
358
|
return !!t && (t.type === "IDENT" || t.type === "KEYWORD");
|
|
359
359
|
}
|
|
360
|
+
const NUMBER_RE = /[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g;
|
|
361
|
+
function parseEdgeWaypoints(value, token) {
|
|
362
|
+
if (!value)
|
|
363
|
+
return undefined;
|
|
364
|
+
const numbers = (value.match(NUMBER_RE) ?? []).map((part) => Number(part));
|
|
365
|
+
if (!numbers.length)
|
|
366
|
+
return undefined;
|
|
367
|
+
if (numbers.length % 2 !== 0) {
|
|
368
|
+
throw new ParseError(`Edge via must contain x,y coordinate pairs`, token.line, token.col);
|
|
369
|
+
}
|
|
370
|
+
const points = [];
|
|
371
|
+
for (let index = 0; index < numbers.length; index += 2) {
|
|
372
|
+
const x = numbers[index];
|
|
373
|
+
const y = numbers[index + 1];
|
|
374
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
375
|
+
throw new ParseError(`Edge via contains a non-numeric coordinate`, token.line, token.col);
|
|
376
|
+
}
|
|
377
|
+
points.push([x, y]);
|
|
378
|
+
}
|
|
379
|
+
return points.length ? points : undefined;
|
|
380
|
+
}
|
|
381
|
+
function normalizeEdgeRoute(value, token) {
|
|
382
|
+
if (!value)
|
|
383
|
+
return undefined;
|
|
384
|
+
const normalized = value.toLowerCase();
|
|
385
|
+
if (normalized === "straight" || normalized === "polyline" || normalized === "orthogonal") {
|
|
386
|
+
return normalized;
|
|
387
|
+
}
|
|
388
|
+
if (normalized === "ortho" || normalized === "elbow") {
|
|
389
|
+
return "orthogonal";
|
|
390
|
+
}
|
|
391
|
+
throw new ParseError(`Unsupported edge route "${value}"; use straight, orthogonal, or polyline`, token.line, token.col);
|
|
392
|
+
}
|
|
360
393
|
function parse(src, options = {}) {
|
|
361
394
|
resetUid();
|
|
362
395
|
const preparedSource = applyPluginPreprocessors(src, options.plugins);
|
|
@@ -715,28 +748,63 @@ function parse(src, options = {}) {
|
|
|
715
748
|
height: props.height !== undefined ? parseFloat(props.height) : undefined,
|
|
716
749
|
};
|
|
717
750
|
}
|
|
718
|
-
function
|
|
719
|
-
const toTok = rest.shift();
|
|
720
|
-
if (!toTok)
|
|
721
|
-
throw new ParseError("Expected edge target", 0, 0);
|
|
751
|
+
function parseEdgeProps(toks) {
|
|
722
752
|
const props = {};
|
|
723
753
|
let j = 0;
|
|
724
|
-
while (j <
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
j += 3;
|
|
754
|
+
while (j < toks.length) {
|
|
755
|
+
const key = toks[j];
|
|
756
|
+
const eq = toks[j + 1];
|
|
757
|
+
if (!isPropKeyToken(key) || eq?.type !== "EQUALS") {
|
|
758
|
+
j++;
|
|
759
|
+
continue;
|
|
731
760
|
}
|
|
732
|
-
|
|
761
|
+
const value = toks[j + 2];
|
|
762
|
+
if (!value) {
|
|
733
763
|
j++;
|
|
764
|
+
continue;
|
|
734
765
|
}
|
|
766
|
+
if (value.type === "LBRACKET") {
|
|
767
|
+
const parts = [];
|
|
768
|
+
let depth = 1;
|
|
769
|
+
j += 3;
|
|
770
|
+
while (j < toks.length && depth > 0) {
|
|
771
|
+
const tok = toks[j];
|
|
772
|
+
if (tok.type === "LBRACKET") {
|
|
773
|
+
depth++;
|
|
774
|
+
}
|
|
775
|
+
else if (tok.type === "RBRACKET") {
|
|
776
|
+
depth--;
|
|
777
|
+
if (depth === 0) {
|
|
778
|
+
j++;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (depth > 0)
|
|
783
|
+
parts.push(tok.value);
|
|
784
|
+
j++;
|
|
785
|
+
}
|
|
786
|
+
if (depth > 0) {
|
|
787
|
+
throw new ParseError(`Unterminated edge property list; expected ']'`, key.line, key.col);
|
|
788
|
+
}
|
|
789
|
+
props[key.value] = parts.join(" ");
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
props[key.value] = value.value;
|
|
793
|
+
j += 3;
|
|
735
794
|
}
|
|
795
|
+
return props;
|
|
796
|
+
}
|
|
797
|
+
function parseEdge(fromId, connector, rest) {
|
|
798
|
+
const toTok = rest.shift();
|
|
799
|
+
if (!toTok)
|
|
800
|
+
throw new ParseError("Expected edge target", 0, 0);
|
|
801
|
+
const props = parseEdgeProps(rest);
|
|
736
802
|
const dashed = connector.includes("--") ||
|
|
737
803
|
connector.includes(".-") ||
|
|
738
804
|
connector.includes("-.");
|
|
739
805
|
const bidirectional = connector.includes("<") && connector.includes(">");
|
|
806
|
+
const via = parseEdgeWaypoints(props.via, toTok);
|
|
807
|
+
const route = normalizeEdgeRoute(props.route, toTok) ?? (via?.length ? "polyline" : undefined);
|
|
740
808
|
return {
|
|
741
809
|
kind: "edge",
|
|
742
810
|
id: uid("edge"),
|
|
@@ -748,6 +816,8 @@ function parse(src, options = {}) {
|
|
|
748
816
|
labelDy: props["label-dy"] !== undefined ? parseFloat(props["label-dy"]) : undefined,
|
|
749
817
|
fromAnchor: props["anchor-from"],
|
|
750
818
|
toAnchor: props["anchor-to"],
|
|
819
|
+
route,
|
|
820
|
+
via,
|
|
751
821
|
dashed,
|
|
752
822
|
bidirectional,
|
|
753
823
|
style: propsToStyle(props),
|
|
@@ -3681,6 +3751,8 @@ function buildSceneGraph(ast) {
|
|
|
3681
3751
|
labelDy: e.labelDy,
|
|
3682
3752
|
fromAnchor: e.fromAnchor,
|
|
3683
3753
|
toAnchor: e.toAnchor,
|
|
3754
|
+
route: e.route,
|
|
3755
|
+
via: e.via,
|
|
3684
3756
|
dashed: e.dashed ?? false,
|
|
3685
3757
|
bidirectional: e.bidirectional ?? false,
|
|
3686
3758
|
style: e.style ?? {},
|
|
@@ -4282,6 +4354,151 @@ function getConnPoint(src, dstCX, dstCY, anchor) {
|
|
|
4282
4354
|
return anchoredConnPoint(src, anchor, dstCX, dstCY);
|
|
4283
4355
|
}
|
|
4284
4356
|
// ── Group depth (for paint order) ────────────────────────────────────────
|
|
4357
|
+
function segmentLength(a, b) {
|
|
4358
|
+
return Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
4359
|
+
}
|
|
4360
|
+
function compactPolylinePoints(points) {
|
|
4361
|
+
const compacted = [];
|
|
4362
|
+
for (const point of points) {
|
|
4363
|
+
const previous = compacted[compacted.length - 1];
|
|
4364
|
+
if (!previous || segmentLength(previous, point) > 0.01) {
|
|
4365
|
+
compacted.push(point);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
return compacted;
|
|
4369
|
+
}
|
|
4370
|
+
function polylinePathData(points) {
|
|
4371
|
+
return points
|
|
4372
|
+
.map(([x, y], index) => `${index === 0 ? "M" : "L"} ${x} ${y}`)
|
|
4373
|
+
.join(" ");
|
|
4374
|
+
}
|
|
4375
|
+
function polylineEndpointDirection(points, end) {
|
|
4376
|
+
const step = end === "start" ? 1 : -1;
|
|
4377
|
+
let index = end === "start" ? 0 : points.length - 1;
|
|
4378
|
+
while (index + step >= 0 && index + step < points.length) {
|
|
4379
|
+
const from = points[index];
|
|
4380
|
+
const to = points[index + step];
|
|
4381
|
+
const dx = to[0] - from[0];
|
|
4382
|
+
const dy = to[1] - from[1];
|
|
4383
|
+
const len = Math.hypot(dx, dy);
|
|
4384
|
+
if (len > 0.01) {
|
|
4385
|
+
return end === "start" ? [dx / len, dy / len] : [-dx / len, -dy / len];
|
|
4386
|
+
}
|
|
4387
|
+
index += step;
|
|
4388
|
+
}
|
|
4389
|
+
return [1, 0];
|
|
4390
|
+
}
|
|
4391
|
+
function insetPolylineEndpoints(points, arrowAt, inset) {
|
|
4392
|
+
const next = points.map((point) => [point[0], point[1]]);
|
|
4393
|
+
if (next.length < 2)
|
|
4394
|
+
return next;
|
|
4395
|
+
if (arrowAt === "start" || arrowAt === "both") {
|
|
4396
|
+
const [dx, dy] = polylineEndpointDirection(next, "start");
|
|
4397
|
+
next[0] = [next[0][0] + dx * inset, next[0][1] + dy * inset];
|
|
4398
|
+
}
|
|
4399
|
+
if (arrowAt === "end" || arrowAt === "both") {
|
|
4400
|
+
const [dx, dy] = polylineEndpointDirection(next, "end");
|
|
4401
|
+
const last = next.length - 1;
|
|
4402
|
+
next[last] = [next[last][0] - dx * inset, next[last][1] - dy * inset];
|
|
4403
|
+
}
|
|
4404
|
+
return compactPolylinePoints(next);
|
|
4405
|
+
}
|
|
4406
|
+
function polylineLabelPosition(points, offset, dx = 0, dy = 0) {
|
|
4407
|
+
if (points.length < 2) {
|
|
4408
|
+
const [x, y] = points[0] ?? [0, 0];
|
|
4409
|
+
return { x: x + dx, y: y + dy };
|
|
4410
|
+
}
|
|
4411
|
+
const lengths = points.slice(1).map((point, index) => segmentLength(points[index], point));
|
|
4412
|
+
const total = lengths.reduce((sum, value) => sum + value, 0);
|
|
4413
|
+
if (total <= 0.01) {
|
|
4414
|
+
const [x, y] = points[0];
|
|
4415
|
+
return { x: x + dx, y: y + dy };
|
|
4416
|
+
}
|
|
4417
|
+
let travelled = 0;
|
|
4418
|
+
const target = total / 2;
|
|
4419
|
+
for (let index = 0; index < lengths.length; index += 1) {
|
|
4420
|
+
const length = lengths[index];
|
|
4421
|
+
if (travelled + length >= target) {
|
|
4422
|
+
const from = points[index];
|
|
4423
|
+
const to = points[index + 1];
|
|
4424
|
+
const t = length > 0 ? (target - travelled) / length : 0;
|
|
4425
|
+
const ux = (to[0] - from[0]) / length;
|
|
4426
|
+
const uy = (to[1] - from[1]) / length;
|
|
4427
|
+
return {
|
|
4428
|
+
x: from[0] + (to[0] - from[0]) * t - uy * offset + dx,
|
|
4429
|
+
y: from[1] + (to[1] - from[1]) * t + ux * offset + dy,
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
travelled += length;
|
|
4433
|
+
}
|
|
4434
|
+
const [x, y] = points[points.length - 1];
|
|
4435
|
+
return { x: x + dx, y: y + dy };
|
|
4436
|
+
}
|
|
4437
|
+
function rectBoundaryPoint(entity, point, direction) {
|
|
4438
|
+
const [px, py] = point;
|
|
4439
|
+
const [dx, dy] = direction;
|
|
4440
|
+
const candidates = [];
|
|
4441
|
+
const minX = entity.x;
|
|
4442
|
+
const maxX = entity.x + entity.w;
|
|
4443
|
+
const minY = entity.y;
|
|
4444
|
+
const maxY = entity.y + entity.h;
|
|
4445
|
+
const epsilon = 0.01;
|
|
4446
|
+
if (Math.abs(dx) > epsilon) {
|
|
4447
|
+
candidates.push((minX - px) / dx, (maxX - px) / dx);
|
|
4448
|
+
}
|
|
4449
|
+
if (Math.abs(dy) > epsilon) {
|
|
4450
|
+
candidates.push((minY - py) / dy, (maxY - py) / dy);
|
|
4451
|
+
}
|
|
4452
|
+
const valid = candidates
|
|
4453
|
+
.filter((t) => t >= -epsilon)
|
|
4454
|
+
.map((t) => ({
|
|
4455
|
+
t: Math.max(0, t),
|
|
4456
|
+
x: px + dx * t,
|
|
4457
|
+
y: py + dy * t,
|
|
4458
|
+
}))
|
|
4459
|
+
.filter(({ x, y }) => x >= minX - epsilon &&
|
|
4460
|
+
x <= maxX + epsilon &&
|
|
4461
|
+
y >= minY - epsilon &&
|
|
4462
|
+
y <= maxY + epsilon)
|
|
4463
|
+
.sort((a, b) => a.t - b.t);
|
|
4464
|
+
const hit = valid[0];
|
|
4465
|
+
return hit ? [hit.x, hit.y] : point;
|
|
4466
|
+
}
|
|
4467
|
+
function ellipseBoundaryPoint(entity, point, direction) {
|
|
4468
|
+
const [px, py] = point;
|
|
4469
|
+
const [dx, dy] = direction;
|
|
4470
|
+
const cx = entity.x + entity.w / 2;
|
|
4471
|
+
const cy = entity.y + entity.h / 2;
|
|
4472
|
+
const rx = Math.max(1, entity.w * 0.44);
|
|
4473
|
+
const ry = Math.max(1, entity.h * 0.44);
|
|
4474
|
+
const x0 = px - cx;
|
|
4475
|
+
const y0 = py - cy;
|
|
4476
|
+
const a = (dx * dx) / (rx * rx) + (dy * dy) / (ry * ry);
|
|
4477
|
+
const b = 2 * ((x0 * dx) / (rx * rx) + (y0 * dy) / (ry * ry));
|
|
4478
|
+
const c = (x0 * x0) / (rx * rx) + (y0 * y0) / (ry * ry) - 1;
|
|
4479
|
+
const disc = b * b - 4 * a * c;
|
|
4480
|
+
if (a <= 0 || disc < 0)
|
|
4481
|
+
return point;
|
|
4482
|
+
const sqrt = Math.sqrt(disc);
|
|
4483
|
+
const hits = [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)]
|
|
4484
|
+
.filter((t) => t >= -0.01)
|
|
4485
|
+
.sort((left, right) => left - right);
|
|
4486
|
+
const t = Math.max(0, hits[0] ?? 0);
|
|
4487
|
+
return [px + dx * t, py + dy * t];
|
|
4488
|
+
}
|
|
4489
|
+
function polylineArrowTipPoint(entity, points, end) {
|
|
4490
|
+
const point = end === "start" ? points[0] : points[points.length - 1];
|
|
4491
|
+
if (!point)
|
|
4492
|
+
return [0, 0];
|
|
4493
|
+
const [dx, dy] = polylineEndpointDirection(points, end);
|
|
4494
|
+
const outward = end === "start" ? [dx, dy] : [-dx, -dy];
|
|
4495
|
+
if (Math.hypot(outward[0], outward[1]) <= 0.01)
|
|
4496
|
+
return point;
|
|
4497
|
+
if (entity.shape === "circle") {
|
|
4498
|
+
return ellipseBoundaryPoint(entity, point, outward);
|
|
4499
|
+
}
|
|
4500
|
+
return rectBoundaryPoint(entity, point, outward);
|
|
4501
|
+
}
|
|
4285
4502
|
function groupDepth(g, gm) {
|
|
4286
4503
|
let d = 0;
|
|
4287
4504
|
let cur = g;
|
|
@@ -5242,6 +5459,31 @@ function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
|
5242
5459
|
const t = Math.min(tx, ty);
|
|
5243
5460
|
return [cx + t * dx, cy + t * dy];
|
|
5244
5461
|
}
|
|
5462
|
+
function distance$1(a, b) {
|
|
5463
|
+
return Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
5464
|
+
}
|
|
5465
|
+
function compactEdgePoints(points) {
|
|
5466
|
+
const compacted = [];
|
|
5467
|
+
for (const point of points) {
|
|
5468
|
+
const previous = compacted[compacted.length - 1];
|
|
5469
|
+
if (!previous || distance$1(previous, point) > 0.01) {
|
|
5470
|
+
compacted.push(point);
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
return compacted;
|
|
5474
|
+
}
|
|
5475
|
+
function orthogonalEdgePoints(start, end) {
|
|
5476
|
+
if (Math.abs(start[0] - end[0]) < 0.01 || Math.abs(start[1] - end[1]) < 0.01) {
|
|
5477
|
+
return [start, end];
|
|
5478
|
+
}
|
|
5479
|
+
const midX = (start[0] + end[0]) / 2;
|
|
5480
|
+
return compactEdgePoints([
|
|
5481
|
+
start,
|
|
5482
|
+
[midX, start[1]],
|
|
5483
|
+
[midX, end[1]],
|
|
5484
|
+
end,
|
|
5485
|
+
]);
|
|
5486
|
+
}
|
|
5245
5487
|
function routeEdges(sg) {
|
|
5246
5488
|
const nm = nodeMap(sg);
|
|
5247
5489
|
const tm = tableMap(sg);
|
|
@@ -5271,10 +5513,17 @@ function routeEdges(sg) {
|
|
|
5271
5513
|
}
|
|
5272
5514
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
5273
5515
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
5274
|
-
e.
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5516
|
+
const start = anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY);
|
|
5517
|
+
const end = anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY);
|
|
5518
|
+
if (e.via?.length) {
|
|
5519
|
+
e.points = compactEdgePoints([start, ...e.via, end]);
|
|
5520
|
+
}
|
|
5521
|
+
else if (e.route === "orthogonal") {
|
|
5522
|
+
e.points = orthogonalEdgePoints(start, end);
|
|
5523
|
+
}
|
|
5524
|
+
else {
|
|
5525
|
+
e.points = [start, end];
|
|
5526
|
+
}
|
|
5278
5527
|
}
|
|
5279
5528
|
}
|
|
5280
5529
|
function computeBounds(sg, margin) {
|
|
@@ -5284,6 +5533,7 @@ function computeBounds(sg, margin) {
|
|
|
5284
5533
|
...sg.tables.map((t) => t.x + t.w),
|
|
5285
5534
|
...sg.charts.map((c) => c.x + c.w),
|
|
5286
5535
|
...sg.markdowns.map((m) => m.x + m.w),
|
|
5536
|
+
...sg.edges.flatMap((e) => (e.points ?? []).map(([x]) => x)),
|
|
5287
5537
|
];
|
|
5288
5538
|
const allY = [
|
|
5289
5539
|
...sg.nodes.map((n) => n.y + n.h),
|
|
@@ -5291,6 +5541,7 @@ function computeBounds(sg, margin) {
|
|
|
5291
5541
|
...sg.tables.map((t) => t.y + t.h),
|
|
5292
5542
|
...sg.charts.map((c) => c.y + c.h),
|
|
5293
5543
|
...sg.markdowns.map((m) => m.y + m.h),
|
|
5544
|
+
...sg.edges.flatMap((e) => (e.points ?? []).map(([, y]) => y)),
|
|
5294
5545
|
];
|
|
5295
5546
|
const autoWidth = (allX.length ? Math.max(...allX) : 400) + margin;
|
|
5296
5547
|
const autoHeight = (allY.length ? Math.max(...allY) : 300) + margin;
|
|
@@ -8464,20 +8715,16 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8464
8715
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
8465
8716
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
8466
8717
|
const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
|
|
8718
|
+
const points = compactPolylinePoints(e.points?.length && e.points.length >= 2 ? e.points : [[x1, y1], [x2, y2]]);
|
|
8467
8719
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
8468
8720
|
setParentGroupData(eg, resolveEdgeParentGroupId(e.from, e.to, nm, tm, gmMap, cm, parentGroups));
|
|
8469
8721
|
if (e.style?.opacity != null)
|
|
8470
8722
|
eg.setAttribute("opacity", String(e.style.opacity));
|
|
8471
|
-
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
8472
|
-
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
8473
8723
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
8474
8724
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
8475
8725
|
const HEAD = EDGE.headInset;
|
|
8476
|
-
const
|
|
8477
|
-
const
|
|
8478
|
-
const sx2 = arrowAt === "end" || arrowAt === "both" ? x2 - nx * HEAD : x2;
|
|
8479
|
-
const sy2 = arrowAt === "end" || arrowAt === "both" ? y2 - ny * HEAD : y2;
|
|
8480
|
-
const shaft = rc.line(sx1, sy1, sx2, sy2, {
|
|
8726
|
+
const shaftPoints = insetPolylineEndpoints(points, arrowAt, HEAD);
|
|
8727
|
+
const shaft = rc.path(polylinePathData(shaftPoints), {
|
|
8481
8728
|
...BASE_ROUGH,
|
|
8482
8729
|
roughness: 0.9,
|
|
8483
8730
|
seed: hashStr$3(e.from + e.to),
|
|
@@ -8488,18 +8735,21 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8488
8735
|
shaft.setAttribute("data-edge-role", "shaft");
|
|
8489
8736
|
eg.appendChild(shaft);
|
|
8490
8737
|
if (arrowAt === "end" || arrowAt === "both") {
|
|
8491
|
-
const
|
|
8738
|
+
const [endDx, endDy] = polylineEndpointDirection(points, "end");
|
|
8739
|
+
const [endX, endY] = polylineArrowTipPoint(dst, points, "end");
|
|
8740
|
+
const endHead = arrowHead(rc, endX, endY, Math.atan2(endDy, endDx), ecol, hashStr$3(e.to));
|
|
8492
8741
|
endHead.setAttribute("data-edge-role", "head");
|
|
8493
8742
|
eg.appendChild(endHead);
|
|
8494
8743
|
}
|
|
8495
8744
|
if (arrowAt === "start" || arrowAt === "both") {
|
|
8496
|
-
const
|
|
8745
|
+
const [startDx, startDy] = polylineEndpointDirection(points, "start");
|
|
8746
|
+
const [startX, startY] = polylineArrowTipPoint(src, points, "start");
|
|
8747
|
+
const startHead = arrowHead(rc, startX, startY, Math.atan2(-startDy, -startDx), ecol, hashStr$3(e.from + "back"));
|
|
8497
8748
|
startHead.setAttribute("data-edge-role", "head");
|
|
8498
8749
|
eg.appendChild(startHead);
|
|
8499
8750
|
}
|
|
8500
8751
|
if (e.label) {
|
|
8501
|
-
const
|
|
8502
|
-
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset + (e.labelDy ?? 0);
|
|
8752
|
+
const { x: mx, y: my } = polylineLabelPosition(points, EDGE.labelOffset, e.labelDx ?? 0, e.labelDy ?? 0);
|
|
8503
8753
|
const tw = Math.max(e.label.length * 7 + 12, 36);
|
|
8504
8754
|
const bg = se("rect");
|
|
8505
8755
|
bg.setAttribute("x", String(mx - tw / 2));
|
|
@@ -9211,31 +9461,31 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9211
9461
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
9212
9462
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
9213
9463
|
const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
|
|
9464
|
+
const points = compactPolylinePoints(e.points?.length && e.points.length >= 2 ? e.points : [[x1, y1], [x2, y2]]);
|
|
9214
9465
|
if (e.style?.opacity != null)
|
|
9215
9466
|
ctx.globalAlpha = Number(e.style.opacity);
|
|
9216
9467
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
9217
9468
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
9218
|
-
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
9219
|
-
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
9220
9469
|
const HEAD = EDGE.headInset;
|
|
9221
|
-
const
|
|
9222
|
-
|
|
9223
|
-
const sx2 = arrowAt === 'end' || arrowAt === 'both' ? x2 - nx * HEAD : x2;
|
|
9224
|
-
const sy2 = arrowAt === 'end' || arrowAt === 'both' ? y2 - ny * HEAD : y2;
|
|
9225
|
-
rc.line(sx1, sy1, sx2, sy2, {
|
|
9470
|
+
const shaftPoints = insetPolylineEndpoints(points, arrowAt, HEAD);
|
|
9471
|
+
rc.path(polylinePathData(shaftPoints), {
|
|
9226
9472
|
...R, roughness: 0.9, seed: hashStr$3(e.from + e.to),
|
|
9227
9473
|
stroke: ecol,
|
|
9228
9474
|
strokeWidth: Number(e.style?.strokeWidth ?? 1.6),
|
|
9229
9475
|
...(dashed ? { strokeLineDash: EDGE.dashPattern } : {}),
|
|
9230
9476
|
});
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9477
|
+
if (arrowAt === 'end' || arrowAt === 'both') {
|
|
9478
|
+
const [endDx, endDy] = polylineEndpointDirection(points, 'end');
|
|
9479
|
+
const [endX, endY] = polylineArrowTipPoint(dst, points, 'end');
|
|
9480
|
+
drawArrowHead(rc, endX, endY, Math.atan2(endDy, endDx), ecol, hashStr$3(e.to));
|
|
9481
|
+
}
|
|
9482
|
+
if (arrowAt === 'start' || arrowAt === 'both') {
|
|
9483
|
+
const [startDx, startDy] = polylineEndpointDirection(points, 'start');
|
|
9484
|
+
const [startX, startY] = polylineArrowTipPoint(src, points, 'start');
|
|
9485
|
+
drawArrowHead(rc, startX, startY, Math.atan2(-startDy, -startDx), ecol, hashStr$3(e.from + 'back'));
|
|
9486
|
+
}
|
|
9236
9487
|
if (e.label) {
|
|
9237
|
-
const
|
|
9238
|
-
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset + (e.labelDy ?? 0);
|
|
9488
|
+
const { x: mx, y: my } = polylineLabelPosition(points, EDGE.labelOffset, e.labelDx ?? 0, e.labelDy ?? 0);
|
|
9239
9489
|
// ── Edge label: font, font-size, letter-spacing ──
|
|
9240
9490
|
// always center-anchored (single line)
|
|
9241
9491
|
const eFontSize = Number(e.style?.fontSize ?? EDGE.labelFontSize);
|
|
@@ -9932,7 +10182,7 @@ function animateShapeDraw(el, strokeDur = ANIMATION.nodeStrokeDur, stag = ANIMAT
|
|
|
9932
10182
|
}));
|
|
9933
10183
|
}
|
|
9934
10184
|
// ── Edge draw helpers ─────────────────────────────────────
|
|
9935
|
-
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path';
|
|
10185
|
+
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path, path[data-edge-role="shaft"]';
|
|
9936
10186
|
const EDGE_DECOR_SELECTOR = '[data-edge-role="head"], [data-edge-role="label"], [data-edge-role="label-bg"]';
|
|
9937
10187
|
function edgeShaftPaths(el) {
|
|
9938
10188
|
return Array.from(el.querySelectorAll(EDGE_SHAFT_SELECTOR));
|
|
@@ -11149,8 +11399,11 @@ class AnimationController {
|
|
|
11149
11399
|
* 4. After guide finishes → fade in rough.js element, remove guide
|
|
11150
11400
|
*/
|
|
11151
11401
|
_animateAnnotation(roughEl, guideD, silent) {
|
|
11152
|
-
if (silent)
|
|
11402
|
+
if (silent) {
|
|
11403
|
+
roughEl.style.opacity = "1";
|
|
11404
|
+
roughEl.style.transition = "none";
|
|
11153
11405
|
return;
|
|
11406
|
+
}
|
|
11154
11407
|
// Hide rough.js element — will be revealed after guide draws
|
|
11155
11408
|
roughEl.style.opacity = "0";
|
|
11156
11409
|
roughEl.style.transition = "none";
|
|
@@ -11440,10 +11693,74 @@ const ANIMATION_CSS = `
|
|
|
11440
11693
|
.skm-caption { pointer-events: none; user-select: none; }
|
|
11441
11694
|
`;
|
|
11442
11695
|
|
|
11696
|
+
const exportAnimationState = new WeakMap();
|
|
11697
|
+
function bindExportAnimationState(svg, state) {
|
|
11698
|
+
exportAnimationState.set(svg, state);
|
|
11699
|
+
}
|
|
11700
|
+
function getExportAnimationState(svg) {
|
|
11701
|
+
return exportAnimationState.get(svg);
|
|
11702
|
+
}
|
|
11703
|
+
|
|
11443
11704
|
// ============================================================
|
|
11444
11705
|
// sketchmark — Export System
|
|
11445
11706
|
// SVG, PNG, Canvas, GIF (stub), MP4 (stub)
|
|
11446
11707
|
// ============================================================
|
|
11708
|
+
const EXPORT_SVG_STYLE_ID = "sketchmark-export-state";
|
|
11709
|
+
const EXPORT_SVG_STATE_CSS = `
|
|
11710
|
+
.ng, .gg, .tg, .ntg, .cg, .eg, .mdg {
|
|
11711
|
+
animation: none !important;
|
|
11712
|
+
transition: none !important;
|
|
11713
|
+
}
|
|
11714
|
+
|
|
11715
|
+
.ng.hidden { opacity: 0 !important; pointer-events: none !important; }
|
|
11716
|
+
.gg.gg-hidden,
|
|
11717
|
+
.tg.gg-hidden,
|
|
11718
|
+
.ntg.gg-hidden,
|
|
11719
|
+
.cg.gg-hidden,
|
|
11720
|
+
.eg.gg-hidden,
|
|
11721
|
+
.mdg.gg-hidden { opacity: 0 !important; }
|
|
11722
|
+
|
|
11723
|
+
.ng.faded,
|
|
11724
|
+
.gg.faded,
|
|
11725
|
+
.tg.faded,
|
|
11726
|
+
.ntg.faded,
|
|
11727
|
+
.cg.faded,
|
|
11728
|
+
.eg.faded,
|
|
11729
|
+
.mdg.faded { opacity: 0.22 !important; }
|
|
11730
|
+
|
|
11731
|
+
.ng.hl path, .ng.hl rect, .ng.hl ellipse, .ng.hl polygon,
|
|
11732
|
+
.tg.hl path, .tg.hl rect,
|
|
11733
|
+
.ntg.hl path, .ntg.hl polygon,
|
|
11734
|
+
.cg.hl path, .cg.hl rect,
|
|
11735
|
+
.mdg.hl text,
|
|
11736
|
+
.eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }
|
|
11737
|
+
`;
|
|
11738
|
+
function buildExportSnapshot(svg) {
|
|
11739
|
+
const snapshot = svg.cloneNode(true);
|
|
11740
|
+
const animationState = getExportAnimationState(svg);
|
|
11741
|
+
if (animationState?.steps.length) {
|
|
11742
|
+
snapshot.querySelector("#annotation-layer")?.remove();
|
|
11743
|
+
let rc = null;
|
|
11744
|
+
try {
|
|
11745
|
+
rc = rough.svg(snapshot);
|
|
11746
|
+
}
|
|
11747
|
+
catch {
|
|
11748
|
+
rc = null;
|
|
11749
|
+
}
|
|
11750
|
+
const anim = new AnimationController(snapshot, animationState.steps, undefined, rc, animationState.config);
|
|
11751
|
+
anim.goTo(animationState.steps.length - 1);
|
|
11752
|
+
}
|
|
11753
|
+
injectExportStyles(snapshot);
|
|
11754
|
+
return snapshot;
|
|
11755
|
+
}
|
|
11756
|
+
function injectExportStyles(svg) {
|
|
11757
|
+
if (svg.querySelector(`#${EXPORT_SVG_STYLE_ID}`))
|
|
11758
|
+
return;
|
|
11759
|
+
const style = document.createElementNS(SVG_NS$1, "style");
|
|
11760
|
+
style.setAttribute("id", EXPORT_SVG_STYLE_ID);
|
|
11761
|
+
style.textContent = EXPORT_SVG_STATE_CSS;
|
|
11762
|
+
svg.insertBefore(style, svg.firstChild);
|
|
11763
|
+
}
|
|
11447
11764
|
// ── Trigger browser download ──────────────────────────────
|
|
11448
11765
|
function download(blob, filename) {
|
|
11449
11766
|
const url = URL.createObjectURL(blob);
|
|
@@ -11457,15 +11774,15 @@ function download(blob, filename) {
|
|
|
11457
11774
|
}
|
|
11458
11775
|
// ── SVG export ────────────────────────────────────────────
|
|
11459
11776
|
function exportSVG(svg, opts = {}) {
|
|
11460
|
-
const str = svgToString(svg);
|
|
11777
|
+
const str = svgToString(buildExportSnapshot(svg));
|
|
11461
11778
|
const blob = new Blob([str], { type: 'image/svg+xml;charset=utf-8' });
|
|
11462
11779
|
download(blob, opts.filename ?? 'diagram.svg');
|
|
11463
11780
|
}
|
|
11464
11781
|
function getSVGString(svg) {
|
|
11465
|
-
return svgToString(svg);
|
|
11782
|
+
return svgToString(buildExportSnapshot(svg));
|
|
11466
11783
|
}
|
|
11467
11784
|
function getSVGBlob(svg) {
|
|
11468
|
-
return new Blob([svgToString(svg)], { type: 'image/svg+xml;charset=utf-8' });
|
|
11785
|
+
return new Blob([svgToString(buildExportSnapshot(svg))], { type: 'image/svg+xml;charset=utf-8' });
|
|
11469
11786
|
}
|
|
11470
11787
|
// ── PNG export (from SVG via Canvas) ─────────────────────
|
|
11471
11788
|
async function exportPNG(svg, opts = {}) {
|
|
@@ -11491,7 +11808,7 @@ async function svgToPNGDataURL(svg, opts = {}) {
|
|
|
11491
11808
|
ctx.fillStyle = EXPORT.fallbackBg;
|
|
11492
11809
|
ctx.fillRect(0, 0, w, h);
|
|
11493
11810
|
}
|
|
11494
|
-
const svgStr = svgToString(svg);
|
|
11811
|
+
const svgStr = svgToString(buildExportSnapshot(svg));
|
|
11495
11812
|
const blob = new Blob([svgStr], { type: 'image/svg+xml;charset=utf-8' });
|
|
11496
11813
|
const url = URL.createObjectURL(blob);
|
|
11497
11814
|
await new Promise((resolve, reject) => {
|
|
@@ -11509,7 +11826,7 @@ async function exportCanvasPNG(canvas, opts = {}) {
|
|
|
11509
11826
|
}
|
|
11510
11827
|
// ── HTML export (self-contained) ──────────────────────────
|
|
11511
11828
|
function exportHTML(svg, dslSource, opts = {}) {
|
|
11512
|
-
const svgStr = svgToString(svg);
|
|
11829
|
+
const svgStr = svgToString(buildExportSnapshot(svg));
|
|
11513
11830
|
const html = `<!DOCTYPE html>
|
|
11514
11831
|
<html lang="en">
|
|
11515
11832
|
<head>
|
|
@@ -11690,6 +12007,7 @@ function render(options) {
|
|
|
11690
12007
|
}
|
|
11691
12008
|
const containerEl = el instanceof SVGSVGElement ? undefined : el;
|
|
11692
12009
|
anim = new AnimationController(svg, ast.steps, containerEl, rc, ast.config);
|
|
12010
|
+
bindExportAnimationState(svg, { steps: ast.steps, config: ast.config });
|
|
11693
12011
|
}
|
|
11694
12012
|
if (typeof tts === "boolean") {
|
|
11695
12013
|
anim.tts = tts;
|