@phin-tech/dottie 0.5.4 → 0.6.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/PARITY.md +3 -0
- package/dist/compile.js +41 -4
- package/dist/dottie-compile.mjs +42 -5
- package/dist/dottie-player.js +103 -12
- package/dist/dottie-player.min.js +52 -50
- package/dist/dottie.js +102 -12
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/clicks.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model.d.ts +26 -0
- package/dist/types/svg.d.ts +4 -2
- package/package.json +1 -1
package/dist/dottie.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.6.1 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
4
4
|
var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
@@ -33,6 +33,7 @@ var KNOWN_FIELDS = {
|
|
|
33
33
|
"evenOdd",
|
|
34
34
|
"rotation",
|
|
35
35
|
"link",
|
|
36
|
+
"url",
|
|
36
37
|
"anchor",
|
|
37
38
|
"click",
|
|
38
39
|
"hover",
|
|
@@ -41,12 +42,18 @@ var KNOWN_FIELDS = {
|
|
|
41
42
|
"offset",
|
|
42
43
|
"args",
|
|
43
44
|
"image",
|
|
44
|
-
"reveal"
|
|
45
|
+
"reveal",
|
|
46
|
+
"ports",
|
|
47
|
+
"labelRect",
|
|
48
|
+
"extension"
|
|
45
49
|
],
|
|
46
50
|
strokePoint: ["x", "y"],
|
|
47
51
|
subpath: ["closed", "coords"],
|
|
48
52
|
inlineImage: ["data", "format"],
|
|
49
|
-
endpoint: ["elementID", "port", "x", "y"],
|
|
53
|
+
endpoint: ["elementID", "port", "portIndex", "x", "y"],
|
|
54
|
+
extensionRef: ["id", "shape", "version", "params"],
|
|
55
|
+
normalizedPoint: ["x", "y"],
|
|
56
|
+
normalizedRect: ["x", "y", "width", "height"],
|
|
50
57
|
connector: ["start", "end", "routing", "startHead", "endHead"],
|
|
51
58
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
52
59
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -85,6 +92,9 @@ function allowedURL(url) {
|
|
|
85
92
|
if (!/^(https?:\/\/|mailto:)/i.test(u) || [...u].length <= 7 || /[\u0000-\u001f\u007f-\u009f]/.test(u)) return null;
|
|
86
93
|
return u;
|
|
87
94
|
}
|
|
95
|
+
function webLink(url) {
|
|
96
|
+
return url ? allowedURL(url) : null;
|
|
97
|
+
}
|
|
88
98
|
function rgba(v) {
|
|
89
99
|
if (!isObj(v)) return void 0;
|
|
90
100
|
const r = num(v.r), g = num(v.g), b = num(v.b);
|
|
@@ -131,9 +141,11 @@ var Reader = class {
|
|
|
131
141
|
endpoint(v) {
|
|
132
142
|
const e = isObj(v) ? v : {};
|
|
133
143
|
const port = str(e.port);
|
|
144
|
+
const portIndex = num(e.portIndex);
|
|
134
145
|
return {
|
|
135
146
|
elementID: uuid(e.elementID) ?? null,
|
|
136
147
|
port: port && ["top", "right", "bottom", "left"].includes(port) ? port : null,
|
|
148
|
+
portIndex: portIndex !== void 0 && Number.isInteger(portIndex) && portIndex >= 0 ? portIndex : null,
|
|
137
149
|
x: num(e.x) ?? 0,
|
|
138
150
|
y: num(e.y) ?? 0
|
|
139
151
|
};
|
|
@@ -198,6 +210,7 @@ var Reader = class {
|
|
|
198
210
|
evenOdd: bool(v.evenOdd) ?? null,
|
|
199
211
|
rotation: num(v.rotation) ?? null,
|
|
200
212
|
link: uuid(v.link) ?? null,
|
|
213
|
+
url: str(v.url) ?? null,
|
|
201
214
|
anchor: str(v.anchor) ?? null,
|
|
202
215
|
click: this.click(v.click),
|
|
203
216
|
hover: this.hover(v.hover),
|
|
@@ -206,9 +219,36 @@ var Reader = class {
|
|
|
206
219
|
offset: num(v.offset) ?? null,
|
|
207
220
|
args: strings(v.args),
|
|
208
221
|
image: this.image(v.image, images),
|
|
209
|
-
reveal: num(v.reveal) ?? null
|
|
222
|
+
reveal: num(v.reveal) ?? null,
|
|
223
|
+
ports: Array.isArray(v.ports) ? v.ports.map((p) => {
|
|
224
|
+
const o = isObj(p) ? p : {};
|
|
225
|
+
return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
|
|
226
|
+
}) : null,
|
|
227
|
+
labelRect: this.labelRect(v.labelRect),
|
|
228
|
+
extension: this.extension(v.extension)
|
|
210
229
|
};
|
|
211
230
|
}
|
|
231
|
+
labelRect(v) {
|
|
232
|
+
if (!isObj(v)) return null;
|
|
233
|
+
this.fields(v, "normalizedRect");
|
|
234
|
+
return { x: num(v.x) ?? 0, y: num(v.y) ?? 0, width: num(v.width) ?? 0, height: num(v.height) ?? 0 };
|
|
235
|
+
}
|
|
236
|
+
extension(v) {
|
|
237
|
+
if (!isObj(v)) return null;
|
|
238
|
+
this.fields(v, "extensionRef");
|
|
239
|
+
const id = str(v.id), shape = str(v.shape), version = str(v.version);
|
|
240
|
+
if (id === void 0 || shape === void 0 || version === void 0) {
|
|
241
|
+
this.warn("Skipped an extension without an id, shape or version");
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const params = {};
|
|
245
|
+
if (isObj(v.params)) {
|
|
246
|
+
for (const [k, p] of Object.entries(v.params)) {
|
|
247
|
+
if (typeof p === "number" || typeof p === "boolean" || typeof p === "string") params[k] = p;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return { id, shape, version, params };
|
|
251
|
+
}
|
|
212
252
|
click(v) {
|
|
213
253
|
if (!isObj(v)) return null;
|
|
214
254
|
this.fields(v, "click");
|
|
@@ -714,12 +754,45 @@ function portPoint(shape, port) {
|
|
|
714
754
|
const p = port === "top" ? { x: midX(r), y: r.y } : port === "right" ? { x: maxX(r), y: midY(r) } : port === "bottom" ? { x: midX(r), y: maxY(r) } : { x: r.x, y: midY(r) };
|
|
715
755
|
return rotated(shape, p);
|
|
716
756
|
}
|
|
757
|
+
function flattenContour(cp) {
|
|
758
|
+
const out = [];
|
|
759
|
+
for (let s = 0; s + 3 < cp.length; s += 3) {
|
|
760
|
+
const seg = cp.slice(s, s + 4);
|
|
761
|
+
for (let k = 0; k < 16; k++) out.push(bezierPoint(seg, k / 16));
|
|
762
|
+
}
|
|
763
|
+
out.push(cp[cp.length - 1]);
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
766
|
+
function outlineCrossing(shape, c, d) {
|
|
767
|
+
let nearest2 = null, farthest = null;
|
|
768
|
+
for (const contour of shapeContours(shape)) {
|
|
769
|
+
const pts = flattenContour(contour.points);
|
|
770
|
+
const n = pts.length;
|
|
771
|
+
const edges = contour.closed ? n : n - 1;
|
|
772
|
+
for (let i = 0; i < edges; i++) {
|
|
773
|
+
const a = pts[i], b = pts[(i + 1) % n];
|
|
774
|
+
const e = { x: b.x - a.x, y: b.y - a.y }, w = { x: a.x - c.x, y: a.y - c.y };
|
|
775
|
+
const det = e.x * d.y - d.x * e.y;
|
|
776
|
+
if (det === 0) continue;
|
|
777
|
+
const t = (e.x * w.y - e.y * w.x) / det, s = (d.x * w.y - d.y * w.x) / det;
|
|
778
|
+
if (!(t > 0 && s >= 0 && s <= 1)) continue;
|
|
779
|
+
if (nearest2 === null || t < nearest2) nearest2 = t;
|
|
780
|
+
if (t <= 1 && (farthest === null || t > farthest)) farthest = t;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const best = farthest ?? nearest2;
|
|
784
|
+
return best === null ? null : { x: c.x + d.x * best, y: c.y + d.y * best };
|
|
785
|
+
}
|
|
717
786
|
function edgePoint(shape, toward) {
|
|
718
787
|
const r = frame(shape);
|
|
719
788
|
const c = center(shape);
|
|
720
789
|
const target = unrotated(shape, toward);
|
|
721
790
|
const dx = target.x - c.x, dy = target.y - c.y;
|
|
722
791
|
if (!((dx !== 0 || dy !== 0) && r.width > 0 && r.height > 0)) return c;
|
|
792
|
+
if (shape.kind === "path") {
|
|
793
|
+
const hit = outlineCrossing(shape, c, { x: dx, y: dy });
|
|
794
|
+
if (hit) return rotated(shape, hit);
|
|
795
|
+
}
|
|
723
796
|
const hw = r.width / 2, hh = r.height / 2;
|
|
724
797
|
let t;
|
|
725
798
|
if (shape.kind === "ellipse") t = 1 / Math.sqrt(dx * dx / (hw * hw) + dy * dy / (hh * hh));
|
|
@@ -731,9 +804,15 @@ function reference(ep, index) {
|
|
|
731
804
|
const s = ep.elementID ? index.get(ep.elementID) : void 0;
|
|
732
805
|
return s ? center(s) : { x: ep.x, y: ep.y };
|
|
733
806
|
}
|
|
807
|
+
function normalizedPoint(shape, p) {
|
|
808
|
+
const r = frame(shape);
|
|
809
|
+
return rotated(shape, { x: r.x + p.x * r.width, y: r.y + p.y * r.height });
|
|
810
|
+
}
|
|
734
811
|
function resolve(ep, other, index) {
|
|
735
812
|
const s = ep.elementID ? index.get(ep.elementID) : void 0;
|
|
736
813
|
if (!s) return { x: ep.x, y: ep.y };
|
|
814
|
+
const i = ep.portIndex, ports = s.ports ?? [];
|
|
815
|
+
if (typeof i === "number" && Number.isInteger(i) && i >= 0 && i < ports.length) return normalizedPoint(s, ports[i]);
|
|
737
816
|
return ep.port ? portPoint(s, ep.port) : edgePoint(s, other);
|
|
738
817
|
}
|
|
739
818
|
function endpoints(e, index) {
|
|
@@ -1917,7 +1996,7 @@ function clickTargets(page, file) {
|
|
|
1917
1996
|
continue;
|
|
1918
1997
|
}
|
|
1919
1998
|
}
|
|
1920
|
-
const url = c?.url ? allowedURL(c.url) : null;
|
|
1999
|
+
const url = (c?.url ? allowedURL(c.url) : null) ?? webLink(e.url);
|
|
1921
2000
|
if (url) {
|
|
1922
2001
|
out.set(e.id, { url });
|
|
1923
2002
|
continue;
|
|
@@ -2717,8 +2796,8 @@ function svgOpen(b, background, paint) {
|
|
|
2717
2796
|
}
|
|
2718
2797
|
return out;
|
|
2719
2798
|
}
|
|
2720
|
-
function svgParts(page, background, fixed) {
|
|
2721
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed);
|
|
2799
|
+
function svgParts(page, background, fixed, links = false) {
|
|
2800
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
2722
2801
|
const theme = themeNamed(page.theme);
|
|
2723
2802
|
const bg = background ? theme?.background ?? background : null;
|
|
2724
2803
|
const ink = theme?.ink ?? black;
|
|
@@ -2742,14 +2821,19 @@ function svgParts(page, background, fixed) {
|
|
|
2742
2821
|
${body}</g>
|
|
2743
2822
|
`;
|
|
2744
2823
|
}
|
|
2745
|
-
|
|
2824
|
+
if (alpha < 1) body = `<g opacity="${fmt(alpha)}">
|
|
2746
2825
|
${body}</g>
|
|
2747
|
-
|
|
2826
|
+
`;
|
|
2827
|
+
const href = links ? (e.click?.url ? allowedURL(e.click.url) : null) ?? webLink(e.url) : null;
|
|
2828
|
+
if (href) body = `<a href="${escapeXML(href).replace(/"/g, """)}" target="_blank">
|
|
2829
|
+
${body}</a>
|
|
2830
|
+
`;
|
|
2831
|
+
parts.push({ key: e.id, markup: body });
|
|
2748
2832
|
}
|
|
2749
2833
|
return { bounds: b, parts };
|
|
2750
2834
|
}
|
|
2751
2835
|
function svg(page, background = { r: 1, g: 1, b: 1, a: 1 }, fixed) {
|
|
2752
|
-
const { bounds: b, parts } = svgParts(page, background, fixed);
|
|
2836
|
+
const { bounds: b, parts } = svgParts(page, background, fixed, true);
|
|
2753
2837
|
return svgOpen(b, themeNamed(page.theme)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
|
|
2754
2838
|
}
|
|
2755
2839
|
function labelRect(e, index) {
|
|
@@ -2844,7 +2928,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
2844
2928
|
}
|
|
2845
2929
|
s += `<path d="${outline(e).data()}" fill="none" ${strokeAttrs}/>
|
|
2846
2930
|
`;
|
|
2847
|
-
if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, frame(e), e, true);
|
|
2931
|
+
if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" ? pathLabelBox(e) : frame(e), e, true);
|
|
2848
2932
|
return s;
|
|
2849
2933
|
}
|
|
2850
2934
|
default:
|
|
@@ -2857,6 +2941,11 @@ function placeholder(e, strokeAttrs) {
|
|
|
2857
2941
|
return `<path d="${p.data()}" fill="none" ${strokeAttrs} stroke-opacity="0.5" stroke-dasharray="6 4"/>
|
|
2858
2942
|
` + textElement(`${e.kind}${e.text ? `: ${e.text}` : ""}`, f, { ...e, style: { ...e.style, fontSize: Math.min(e.style.fontSize, 12) } }, true);
|
|
2859
2943
|
}
|
|
2944
|
+
function pathLabelBox(e) {
|
|
2945
|
+
const f = frame(e), r = e.labelRect;
|
|
2946
|
+
if (!r) return f;
|
|
2947
|
+
return { x: f.x + r.x * f.width, y: f.y + r.y * f.height, width: r.width * f.width, height: r.height * f.height };
|
|
2948
|
+
}
|
|
2860
2949
|
function textElement(text, f, e, centered) {
|
|
2861
2950
|
const size = e.style.fontSize;
|
|
2862
2951
|
const lines = text.split("\n");
|
|
@@ -3555,7 +3644,7 @@ function render(source, options = {}) {
|
|
|
3555
3644
|
function isFile(v) {
|
|
3556
3645
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3557
3646
|
}
|
|
3558
|
-
var VERSION = "0.
|
|
3647
|
+
var VERSION = "0.6.1";
|
|
3559
3648
|
export {
|
|
3560
3649
|
BOIL_DRAWINGS,
|
|
3561
3650
|
DottieFormatError,
|
|
@@ -3603,5 +3692,6 @@ export {
|
|
|
3603
3692
|
themeNamed,
|
|
3604
3693
|
themes,
|
|
3605
3694
|
transientElements,
|
|
3695
|
+
webLink,
|
|
3606
3696
|
withRoughness
|
|
3607
3697
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/clicks.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type Click = {
|
|
|
11
11
|
/** The marker called `name`: an exact match, else one that differs only in case. */
|
|
12
12
|
export declare function sceneNamed(name: string, markers: Marker[]): number;
|
|
13
13
|
/** The page's clickable elements and what each click does, by element id. A scene named by no marker, or a link
|
|
14
|
-
* that isn't allowed, makes nothing clickable. Given the whole file, an element whose `link` points at an element on
|
|
14
|
+
* that isn't allowed, makes nothing clickable; an element with no `click` opens its `url` when that is a web link. Given the whole file, an element whose `link` points at an element on
|
|
15
15
|
* another page shows that page (the HTML export holds one page, so it can't). */
|
|
16
16
|
export declare function clickTargets(page: Page, file?: DottieFile): Map<string, Click>;
|
|
17
17
|
/** `e` as it looks hovered (HoverLook.apply). */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export interface RenderOptions {
|
|
|
18
18
|
/** One page of a drawing as an SVG document, as Dottie's own SVG export draws it. */
|
|
19
19
|
export declare function render(source: string | DottieFile | unknown, options?: RenderOptions): string;
|
|
20
20
|
/** The library's version. */
|
|
21
|
-
export declare const VERSION = "0.
|
|
21
|
+
export declare const VERSION = "0.6.1";
|
package/dist/types/model.d.ts
CHANGED
|
@@ -26,12 +26,21 @@ export interface Style {
|
|
|
26
26
|
/** How far into its dash pattern a dashed or dotted line starts; marching ants set it on every frame. */
|
|
27
27
|
dashOffset: number | null;
|
|
28
28
|
}
|
|
29
|
+
/** `portIndex` pins the end to one of the bound element's `ports`, and wins over `port`. */
|
|
29
30
|
export interface Endpoint {
|
|
30
31
|
elementID: string | null;
|
|
31
32
|
port: Port | null;
|
|
33
|
+
portIndex?: number | null;
|
|
32
34
|
x: number;
|
|
33
35
|
y: number;
|
|
34
36
|
}
|
|
37
|
+
/** The shape an extension generated a path with. Read-only here: nothing runs in dottie.js. */
|
|
38
|
+
export interface ExtensionRef {
|
|
39
|
+
id: string;
|
|
40
|
+
shape: string;
|
|
41
|
+
version: string;
|
|
42
|
+
params: Record<string, number | boolean | string>;
|
|
43
|
+
}
|
|
35
44
|
export interface ConnectorData {
|
|
36
45
|
start: Endpoint;
|
|
37
46
|
end: Endpoint;
|
|
@@ -82,6 +91,9 @@ export interface Element {
|
|
|
82
91
|
evenOdd: boolean | null;
|
|
83
92
|
rotation: number | null;
|
|
84
93
|
link: string | null;
|
|
94
|
+
/** Where the element links outside the drawing (Element.url): a web address, or another drawing. Only web
|
|
95
|
+
* addresses are followed here (`webLink`). */
|
|
96
|
+
url: string | null;
|
|
85
97
|
anchor: string | null;
|
|
86
98
|
click: ClickAction | null;
|
|
87
99
|
hover: HoverLook | null;
|
|
@@ -94,6 +106,18 @@ export interface Element {
|
|
|
94
106
|
/** Base64 bytes; an empty `data` is a picture that couldn't be found (drawn crossed out). */
|
|
95
107
|
image: EmbeddedImage | null;
|
|
96
108
|
reveal: number | null;
|
|
109
|
+
/** For an extension shape: connection points and label box, normalized to the frame. */
|
|
110
|
+
ports?: {
|
|
111
|
+
x: number;
|
|
112
|
+
y: number;
|
|
113
|
+
}[] | null;
|
|
114
|
+
labelRect?: {
|
|
115
|
+
x: number;
|
|
116
|
+
y: number;
|
|
117
|
+
width: number;
|
|
118
|
+
height: number;
|
|
119
|
+
} | null;
|
|
120
|
+
extension?: ExtensionRef | null;
|
|
97
121
|
}
|
|
98
122
|
export interface Layer {
|
|
99
123
|
id: string;
|
|
@@ -211,6 +235,8 @@ export declare function clampFPS(f: number): number;
|
|
|
211
235
|
/** `url` trimmed, if it is a link a page may open: http, https or mailto, as ClickAction.allowed. A drawing can
|
|
212
236
|
* play on someone else's site, so nothing else (javascript:, data:) is ever followed. */
|
|
213
237
|
export declare function allowedURL(url: string): string | null;
|
|
238
|
+
/** An element's `url`, if it is a web address (OutsideLink.webAddress); a link to another drawing gives null. */
|
|
239
|
+
export declare function webLink(url: string | null | undefined): string | null;
|
|
214
240
|
export declare function repeatIsValid(r: Repeat): boolean;
|
|
215
241
|
/** Sorts in place by `key`, keeping equal keys in their original order (as Swift's sort happens to for these). */
|
|
216
242
|
export declare function stableSortBy<T>(a: T[], key: (v: T) => number): void;
|
package/dist/types/svg.d.ts
CHANGED
|
@@ -23,8 +23,10 @@ export interface Part {
|
|
|
23
23
|
key: string;
|
|
24
24
|
markup: string;
|
|
25
25
|
}
|
|
26
|
-
/** The SVG for each thing drawn, back to front, keyed by element id (rock hatching as "rock").
|
|
27
|
-
|
|
26
|
+
/** The SVG for each thing drawn, back to front, keyed by element id (rock hatching as "rock"). With `links`, an
|
|
27
|
+
* element whose click or `url` is a web address is wrapped in an `<a>` that opens it, the click's first (the players
|
|
28
|
+
* make their own clickable groups). */
|
|
29
|
+
export declare function svgParts(page: Page, background: RGBA | null, fixed?: Rect, links?: boolean): {
|
|
28
30
|
bounds: Rect;
|
|
29
31
|
parts: Part[];
|
|
30
32
|
};
|