@phin-tech/dottie 0.6.2 → 0.6.3
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 +2 -0
- package/dist/compile.js +12 -5
- package/dist/dottie-compile.mjs +13 -6
- package/dist/dottie-player.js +270 -42
- package/dist/dottie-player.min.js +52 -52
- package/dist/dottie.js +270 -42
- package/dist/types/connector.d.ts +6 -6
- package/dist/types/element.d.ts +3 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model.d.ts +6 -0
- package/dist/types/shapes.d.ts +17 -4
- package/package.json +1 -1
package/dist/dottie.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.6.
|
|
1
|
+
/*! dottie.js 0.6.3 | 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);
|
|
@@ -13,7 +13,7 @@ var KNOWN_FIELDS = {
|
|
|
13
13
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
14
14
|
layer: ["id", "name", "visible", "locked"],
|
|
15
15
|
rgba: ["r", "g", "b", "a"],
|
|
16
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset"],
|
|
16
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
17
17
|
element: [
|
|
18
18
|
"id",
|
|
19
19
|
"kind",
|
|
@@ -54,7 +54,8 @@ var KNOWN_FIELDS = {
|
|
|
54
54
|
extensionRef: ["id", "shape", "version", "params"],
|
|
55
55
|
normalizedPoint: ["x", "y"],
|
|
56
56
|
normalizedRect: ["x", "y", "width", "height"],
|
|
57
|
-
connector: ["start", "end", "routing", "startHead", "endHead"],
|
|
57
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
|
|
58
|
+
waypoint: ["x", "y"],
|
|
58
59
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
59
60
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
60
61
|
keyframe: ["t", "x", "y", "width", "height", "rotation", "strokeWidth", "stroke", "fill", "visible", "reveal", "opacity", "ease"],
|
|
@@ -136,7 +137,8 @@ var Reader = class {
|
|
|
136
137
|
font: str(s.font) || null,
|
|
137
138
|
gridSize: num(s.gridSize) ?? null,
|
|
138
139
|
opacity: num(s.opacity) ?? null,
|
|
139
|
-
dashOffset: num(s.dashOffset) ?? null
|
|
140
|
+
dashOffset: num(s.dashOffset) ?? null,
|
|
141
|
+
textColor: rgba(s.textColor) ?? null
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
144
|
endpoint(v) {
|
|
@@ -164,7 +166,12 @@ var Reader = class {
|
|
|
164
166
|
end: this.endpoint(v.end),
|
|
165
167
|
routing: routing && ["straight", "curved", "elbow"].includes(routing) ? routing : "straight",
|
|
166
168
|
startHead: head(v.startHead, "none"),
|
|
167
|
-
endHead: head(v.endHead, "arrow")
|
|
169
|
+
endHead: head(v.endHead, "arrow"),
|
|
170
|
+
waypoints: Array.isArray(v.waypoints) ? v.waypoints.filter(isObj).map((w) => {
|
|
171
|
+
this.fields(w, "waypoint");
|
|
172
|
+
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
173
|
+
}) : null,
|
|
174
|
+
hops: typeof v.hops === "boolean" ? v.hops : null
|
|
168
175
|
};
|
|
169
176
|
}
|
|
170
177
|
image(v, table) {
|
|
@@ -441,8 +448,18 @@ function isAutomaticInk(c) {
|
|
|
441
448
|
function cloneElement(e) {
|
|
442
449
|
return {
|
|
443
450
|
...e,
|
|
444
|
-
style: {
|
|
445
|
-
|
|
451
|
+
style: {
|
|
452
|
+
...e.style,
|
|
453
|
+
stroke: { ...e.style.stroke },
|
|
454
|
+
fill: { ...e.style.fill },
|
|
455
|
+
textColor: e.style.textColor ? { ...e.style.textColor } : null
|
|
456
|
+
},
|
|
457
|
+
connector: e.connector ? {
|
|
458
|
+
...e.connector,
|
|
459
|
+
start: { ...e.connector.start },
|
|
460
|
+
end: { ...e.connector.end },
|
|
461
|
+
waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
|
|
462
|
+
} : null
|
|
446
463
|
};
|
|
447
464
|
}
|
|
448
465
|
function orderedElements(page) {
|
|
@@ -872,13 +889,19 @@ function rotatedBounds(e) {
|
|
|
872
889
|
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
873
890
|
}
|
|
874
891
|
function resolvingInk(e, ink) {
|
|
875
|
-
|
|
892
|
+
const text = e.style.textColor !== null && isAutomaticInk(e.style.textColor);
|
|
893
|
+
if (!isAutomaticInk(e.style.stroke) && !text) return e;
|
|
876
894
|
const copy = cloneElement(e);
|
|
877
895
|
const covers = e.style.fillStyle === "solid" || e.style.fillStyle === "grid";
|
|
878
896
|
const onLightFill = covers && e.style.fill.a >= 0.5 && luminance(e.style.fill) > 0.5;
|
|
879
|
-
|
|
897
|
+
const resolved2 = luminance(ink) > 0.5 && onLightFill ? black : ink;
|
|
898
|
+
if (isAutomaticInk(e.style.stroke)) copy.style.stroke = { ...resolved2 };
|
|
899
|
+
if (text) copy.style.textColor = { ...resolved2 };
|
|
880
900
|
return copy;
|
|
881
901
|
}
|
|
902
|
+
function labelColor(e) {
|
|
903
|
+
return e.kind === "text" || e.kind === "note" ? e.style.stroke : e.style.textColor ?? e.style.stroke;
|
|
904
|
+
}
|
|
882
905
|
function shapeContours(e) {
|
|
883
906
|
if (e.kind !== "path" || !e.subpaths) return [];
|
|
884
907
|
const out = [];
|
|
@@ -972,34 +995,93 @@ function resolve(ep, other, index) {
|
|
|
972
995
|
function endpoints(e, index) {
|
|
973
996
|
const c = e.connector;
|
|
974
997
|
if (!c) return [{ x: 0, y: 0 }, { x: 0, y: 0 }];
|
|
975
|
-
|
|
998
|
+
const w = c.waypoints ?? [];
|
|
999
|
+
return [
|
|
1000
|
+
resolve(c.start, w.length > 0 ? w[0] : reference(c.end, index), index),
|
|
1001
|
+
resolve(c.end, w.length > 0 ? w[w.length - 1] : reference(c.start, index), index)
|
|
1002
|
+
];
|
|
976
1003
|
}
|
|
977
1004
|
function routePoints(r) {
|
|
978
1005
|
if (r.kind === "polyline") return r.points;
|
|
979
|
-
const out = [];
|
|
980
|
-
for (let
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1006
|
+
const out = [], p = r.points;
|
|
1007
|
+
for (let s = 0; s < Math.floor((p.length - 1) / 3); s++) {
|
|
1008
|
+
const a = p[3 * s], b = p[3 * s + 1], c = p[3 * s + 2], d = p[3 * s + 3];
|
|
1009
|
+
for (let i = s === 0 ? 0 : 1; i <= 24; i++) {
|
|
1010
|
+
const t = i / 24, u = 1 - t;
|
|
1011
|
+
out.push({
|
|
1012
|
+
x: u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x,
|
|
1013
|
+
y: u * u * u * a.y + 3 * u * u * t * b.y + 3 * u * t * t * c.y + t * t * t * d.y
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
986
1016
|
}
|
|
987
1017
|
return out;
|
|
988
1018
|
}
|
|
1019
|
+
var vertical = (p) => p === null ? null : p === "top" || p === "bottom";
|
|
1020
|
+
function simplified(pts) {
|
|
1021
|
+
const out = [];
|
|
1022
|
+
for (const q of pts) {
|
|
1023
|
+
const l = out[out.length - 1];
|
|
1024
|
+
if (l && Math.abs(l.x - q.x) < 0.5 && Math.abs(l.y - q.y) < 0.5) continue;
|
|
1025
|
+
if (out.length >= 2) {
|
|
1026
|
+
const p = out[out.length - 2], m = out[out.length - 1];
|
|
1027
|
+
if (Math.abs(p.x - m.x) < 0.5 && Math.abs(m.x - q.x) < 0.5 || Math.abs(p.y - m.y) < 0.5 && Math.abs(m.y - q.y) < 0.5) {
|
|
1028
|
+
out[out.length - 1] = q;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
out.push(q);
|
|
1033
|
+
}
|
|
1034
|
+
return out.length >= 2 ? out : pts;
|
|
1035
|
+
}
|
|
1036
|
+
function routeThrough(p, c) {
|
|
1037
|
+
switch (c.routing) {
|
|
1038
|
+
case "straight":
|
|
1039
|
+
return { kind: "polyline", points: p };
|
|
1040
|
+
case "curved": {
|
|
1041
|
+
const out = [p[0]];
|
|
1042
|
+
for (let i = 0; i < p.length - 1; i++) {
|
|
1043
|
+
const p0 = p[Math.max(i - 1, 0)], p1 = p[i], p2 = p[i + 1], p3 = p[Math.min(i + 2, p.length - 1)];
|
|
1044
|
+
out.push({ x: p1.x + (p2.x - p0.x) / 6, y: p1.y + (p2.y - p0.y) / 6 });
|
|
1045
|
+
out.push({ x: p2.x - (p3.x - p1.x) / 6, y: p2.y - (p3.y - p1.y) / 6 });
|
|
1046
|
+
out.push(p2);
|
|
1047
|
+
}
|
|
1048
|
+
return { kind: "curve", points: out };
|
|
1049
|
+
}
|
|
1050
|
+
case "elbow": {
|
|
1051
|
+
const a = p[0], first = p[1];
|
|
1052
|
+
let goVertical = vertical(c.start.port) ?? Math.abs(first.y - a.y) > Math.abs(first.x - a.x);
|
|
1053
|
+
const endV = vertical(c.end.port);
|
|
1054
|
+
const out = [a];
|
|
1055
|
+
for (let i = 1; i < p.length; i++) {
|
|
1056
|
+
const q = p[i], cur = out[out.length - 1];
|
|
1057
|
+
const alignedX = Math.abs(cur.x - q.x) < 0.5, alignedY = Math.abs(cur.y - q.y) < 0.5;
|
|
1058
|
+
if (alignedX || alignedY) {
|
|
1059
|
+
out.push(q);
|
|
1060
|
+
if (alignedX !== alignedY) goVertical = !alignedX;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
const up = i === p.length - 1 && endV !== null ? !endV : goVertical;
|
|
1064
|
+
out.push(up ? { x: cur.x, y: q.y } : { x: q.x, y: cur.y }, q);
|
|
1065
|
+
goVertical = up;
|
|
1066
|
+
}
|
|
1067
|
+
return { kind: "polyline", points: simplified(out) };
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
989
1071
|
function route(e, index) {
|
|
990
1072
|
const c = e.connector;
|
|
991
1073
|
if (!c) return { kind: "polyline", points: [] };
|
|
992
1074
|
const [a, b] = endpoints(e, index);
|
|
1075
|
+
if (c.waypoints && c.waypoints.length > 0) return routeThrough([a, ...c.waypoints.map((w) => ({ x: w.x, y: w.y })), b], c);
|
|
993
1076
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
994
1077
|
const horizontal = Math.abs(dx) >= Math.abs(dy);
|
|
995
1078
|
switch (c.routing) {
|
|
996
1079
|
case "straight":
|
|
997
1080
|
return { kind: "polyline", points: [a, b] };
|
|
998
1081
|
case "curved":
|
|
999
|
-
if (horizontal) return { kind: "
|
|
1000
|
-
return { kind: "
|
|
1082
|
+
if (horizontal) return { kind: "curve", points: [a, { x: a.x + dx / 2, y: a.y }, { x: b.x - dx / 2, y: b.y }, b] };
|
|
1083
|
+
return { kind: "curve", points: [a, { x: a.x, y: a.y + dy / 2 }, { x: b.x, y: b.y - dy / 2 }, b] };
|
|
1001
1084
|
case "elbow": {
|
|
1002
|
-
const vertical = (p) => p === null ? null : p === "top" || p === "bottom";
|
|
1003
1085
|
const startV = vertical(c.start.port) ?? !horizontal;
|
|
1004
1086
|
const endV = vertical(c.end.port) ?? !horizontal;
|
|
1005
1087
|
if (startV && endV) {
|
|
@@ -1579,10 +1661,12 @@ function dashPattern(dash, width) {
|
|
|
1579
1661
|
return [0.01, Math.max(5, width * 2.6)];
|
|
1580
1662
|
}
|
|
1581
1663
|
}
|
|
1582
|
-
function connectorParts(e, index) {
|
|
1664
|
+
function connectorParts(e, index, page) {
|
|
1583
1665
|
const c = e.connector;
|
|
1584
1666
|
if (!c) return [new Path(), new Path()];
|
|
1585
1667
|
const [a, b] = endpoints(e, index);
|
|
1668
|
+
const r = route(e, index);
|
|
1669
|
+
const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
|
|
1586
1670
|
const key = [
|
|
1587
1671
|
e.seed,
|
|
1588
1672
|
e.style.roughness,
|
|
@@ -1596,28 +1680,166 @@ function connectorParts(e, index) {
|
|
|
1596
1680
|
a.x,
|
|
1597
1681
|
a.y,
|
|
1598
1682
|
b.x,
|
|
1599
|
-
b.y
|
|
1683
|
+
b.y,
|
|
1684
|
+
JSON.stringify(c.waypoints),
|
|
1685
|
+
JSON.stringify(hops)
|
|
1600
1686
|
].join("|");
|
|
1601
|
-
return connectors.get(key, () => computeConnector(e,
|
|
1687
|
+
return connectors.get(key, () => computeConnector(e, r, hops));
|
|
1688
|
+
}
|
|
1689
|
+
function hopRadius(strokeWidth) {
|
|
1690
|
+
return Math.max(8, strokeWidth * 4);
|
|
1691
|
+
}
|
|
1692
|
+
function hopSamples(r) {
|
|
1693
|
+
const pts = r.points, out = [];
|
|
1694
|
+
if (r.kind === "polyline") {
|
|
1695
|
+
for (let i = 0; i < pts.length - 1; i++) out.push([pts[i], pts[i + 1]]);
|
|
1696
|
+
return out;
|
|
1697
|
+
}
|
|
1698
|
+
for (let s = 0; s < Math.floor((pts.length - 1) / 3); s++) {
|
|
1699
|
+
const seg = pts.slice(3 * s, 3 * s + 4), samples = [];
|
|
1700
|
+
for (let i = 0; i <= 24; i++) samples.push(cubicPoint(seg, i / 24));
|
|
1701
|
+
out.push(samples);
|
|
1702
|
+
}
|
|
1703
|
+
return out;
|
|
1704
|
+
}
|
|
1705
|
+
function runningLengths(samples) {
|
|
1706
|
+
const out = [0];
|
|
1707
|
+
for (let i = 1; i < samples.length; i++) out.push(out[i - 1] + Math.hypot(samples[i].x - samples[i - 1].x, samples[i].y - samples[i - 1].y));
|
|
1708
|
+
return out;
|
|
1709
|
+
}
|
|
1710
|
+
function cubicPoint(p, t) {
|
|
1711
|
+
const u = 1 - t;
|
|
1712
|
+
return {
|
|
1713
|
+
x: u * u * u * p[0].x + 3 * u * u * t * p[1].x + 3 * u * t * t * p[2].x + t * t * t * p[3].x,
|
|
1714
|
+
y: u * u * u * p[0].y + 3 * u * u * t * p[1].y + 3 * u * t * t * p[2].y + t * t * t * p[3].y
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function subCubic(p, t0, t1) {
|
|
1718
|
+
const split = (q, t) => {
|
|
1719
|
+
const lerp = (a, b) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
|
|
1720
|
+
const ab = lerp(q[0], q[1]), bc = lerp(q[1], q[2]), cd = lerp(q[2], q[3]);
|
|
1721
|
+
const abc = lerp(ab, bc), bcd = lerp(bc, cd), m = lerp(abc, bcd);
|
|
1722
|
+
return [[q[0], ab, abc, m], [m, bcd, cd, q[3]]];
|
|
1723
|
+
};
|
|
1724
|
+
const left = split(p, t1)[0];
|
|
1725
|
+
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
1726
|
+
}
|
|
1727
|
+
function hopCrossings(e, r, page, index) {
|
|
1728
|
+
const segments = hopSamples(r);
|
|
1729
|
+
if (segments.length === 0) return [];
|
|
1730
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
1731
|
+
const beneath = [];
|
|
1732
|
+
for (const o of orderedElements(page)) {
|
|
1733
|
+
if (o.id === e.id) break;
|
|
1734
|
+
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
1735
|
+
beneath.push(routePoints(route(o, index)));
|
|
1736
|
+
}
|
|
1737
|
+
const rad = hopRadius(e.style.strokeWidth);
|
|
1738
|
+
const out = [];
|
|
1739
|
+
for (const samples of segments) {
|
|
1740
|
+
const along = runningLengths(samples);
|
|
1741
|
+
const length = along[along.length - 1];
|
|
1742
|
+
const found = [];
|
|
1743
|
+
for (let i = 0; i < samples.length - 1; i++) {
|
|
1744
|
+
const p = samples[i], q = samples[i + 1];
|
|
1745
|
+
const dx = q.x - p.x, dy = q.y - p.y;
|
|
1746
|
+
for (const line2 of beneath) {
|
|
1747
|
+
for (let k = 0; k < line2.length - 1; k++) {
|
|
1748
|
+
const a = line2[k], b = line2[k + 1];
|
|
1749
|
+
const ex = b.x - a.x, ey = b.y - a.y;
|
|
1750
|
+
const det = dx * ey - dy * ex;
|
|
1751
|
+
if (Math.abs(det) < 1e-9) continue;
|
|
1752
|
+
const wx = a.x - p.x, wy = a.y - p.y;
|
|
1753
|
+
const t = (wx * ey - wy * ex) / det, s = (wx * dy - wy * dx) / det;
|
|
1754
|
+
if (t > 0 && t < 1 && s >= 0 && s <= 1) found.push(along[i] + t * (along[i + 1] - along[i]));
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
const kept = [];
|
|
1759
|
+
for (const at of found.sort((x, y) => x - y)) {
|
|
1760
|
+
if (at < rad + 2 || at > length - rad - 2) continue;
|
|
1761
|
+
if (kept.length > 0 && at - kept[kept.length - 1] < 2 * rad + 2) continue;
|
|
1762
|
+
kept.push(at);
|
|
1763
|
+
}
|
|
1764
|
+
out.push(kept);
|
|
1765
|
+
}
|
|
1766
|
+
return out;
|
|
1602
1767
|
}
|
|
1603
|
-
function computeConnector(e,
|
|
1768
|
+
function computeConnector(e, r, hops) {
|
|
1604
1769
|
const c = e.connector;
|
|
1605
1770
|
const rng = new SeededRNG(e.seed);
|
|
1606
1771
|
const rough = e.style.roughness, bow = e.style.bowing;
|
|
1607
1772
|
const path = new Path(), solid = new Path();
|
|
1608
|
-
const
|
|
1773
|
+
const w = rough * 3;
|
|
1774
|
+
const j = () => rng.signed() * w;
|
|
1775
|
+
const hop = (s, t, u, rad2) => {
|
|
1776
|
+
let n = { x: u.y, y: -u.x };
|
|
1777
|
+
if (n.y > 1e-9 || Math.abs(n.y) <= 1e-9 && n.x > 0) n = { x: -n.x, y: -n.y };
|
|
1778
|
+
const k = rad2 * 0.5523;
|
|
1779
|
+
const m = { x: (s.x + t.x) / 2, y: (s.y + t.y) / 2 };
|
|
1780
|
+
const top = { x: m.x + n.x * rad2, y: m.y + n.y * rad2 };
|
|
1781
|
+
path.moveTo(s);
|
|
1782
|
+
const a1x = s.x + n.x * k + j(), a1y = s.y + n.y * k + j(), a2x = top.x - u.x * k + j(), a2y = top.y - u.y * k + j();
|
|
1783
|
+
path.curveTo(top, { x: a1x, y: a1y }, { x: a2x, y: a2y });
|
|
1784
|
+
const b1x = top.x + u.x * k + j(), b1y = top.y + u.y * k + j(), b2x = t.x + n.x * k + j(), b2y = t.y + n.y * k + j();
|
|
1785
|
+
path.curveTo(t, { x: b1x, y: b1y }, { x: b2x, y: b2y });
|
|
1786
|
+
};
|
|
1787
|
+
const rad = hopRadius(e.style.strokeWidth);
|
|
1609
1788
|
if (r.kind === "polyline") {
|
|
1610
1789
|
const pts2 = r.points;
|
|
1790
|
+
const straight = (p, q) => {
|
|
1791
|
+
if (rough <= 0) path.moveTo(p).lineTo(q);
|
|
1792
|
+
else line(p, q, rng, rough, bow, true, path);
|
|
1793
|
+
};
|
|
1611
1794
|
for (let i = 0; i < Math.max(pts2.length - 1, 0); i++) {
|
|
1612
|
-
|
|
1613
|
-
|
|
1795
|
+
const p = pts2[i], q = pts2[i + 1];
|
|
1796
|
+
const length = Math.hypot(q.x - p.x, q.y - p.y);
|
|
1797
|
+
const u = length > 0 ? { x: (q.x - p.x) / length, y: (q.y - p.y) / length } : { x: 0, y: 0 };
|
|
1798
|
+
const at = (d) => ({ x: p.x + u.x * d, y: p.y + u.y * d });
|
|
1799
|
+
let from = p;
|
|
1800
|
+
for (const d of i < hops.length ? hops[i] : []) {
|
|
1801
|
+
const s = at(d - rad), t = at(d + rad);
|
|
1802
|
+
straight(from, s);
|
|
1803
|
+
hop(s, t, u, rad);
|
|
1804
|
+
from = t;
|
|
1805
|
+
}
|
|
1806
|
+
straight(from, q);
|
|
1614
1807
|
}
|
|
1615
1808
|
} else {
|
|
1616
|
-
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1809
|
+
const pts2 = r.points;
|
|
1810
|
+
const samples = hopSamples(r);
|
|
1811
|
+
path.moveTo(pts2[0]);
|
|
1812
|
+
for (let s = 0; s < Math.floor((pts2.length - 1) / 3); s++) {
|
|
1813
|
+
const b = pts2[3 * s + 1], c2 = pts2[3 * s + 2], d = pts2[3 * s + 3];
|
|
1814
|
+
const c1x = b.x + j(), c1y = b.y + j(), c2x = c2.x + j(), c2y = c2.y + j();
|
|
1815
|
+
const here = s < hops.length ? hops[s] : [];
|
|
1816
|
+
if (here.length === 0) {
|
|
1817
|
+
path.curveTo(d, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
const cubic = [pts2[3 * s], { x: c1x, y: c1y }, { x: c2x, y: c2y }, d];
|
|
1821
|
+
const along = runningLengths(samples[s]);
|
|
1822
|
+
const param = (at) => {
|
|
1823
|
+
let i = 0;
|
|
1824
|
+
while (i < along.length - 2 && along[i + 1] < at) i++;
|
|
1825
|
+
const span = along[i + 1] - along[i];
|
|
1826
|
+
return (i + (span > 0 ? (at - along[i]) / span : 0)) / 24;
|
|
1827
|
+
};
|
|
1828
|
+
let from = 0;
|
|
1829
|
+
for (const at of here) {
|
|
1830
|
+
const t0 = param(at - rad), t1 = param(at + rad);
|
|
1831
|
+
const piece = subCubic(cubic, from, t0);
|
|
1832
|
+
if (s > 0 || from > 0) path.moveTo(piece[0]);
|
|
1833
|
+
path.curveTo(piece[3], piece[1], piece[2]);
|
|
1834
|
+
const a = cubicPoint(cubic, t0), z = cubicPoint(cubic, t1);
|
|
1835
|
+
const width = Math.hypot(z.x - a.x, z.y - a.y);
|
|
1836
|
+
const u = width > 0 ? { x: (z.x - a.x) / width, y: (z.y - a.y) / width } : { x: 0, y: 0 };
|
|
1837
|
+
hop(a, z, u, width / 2);
|
|
1838
|
+
from = t1;
|
|
1839
|
+
}
|
|
1840
|
+
const rest = subCubic(cubic, from, 1);
|
|
1841
|
+
path.moveTo(rest[0]).curveTo(rest[3], rest[1], rest[2]);
|
|
1842
|
+
}
|
|
1621
1843
|
}
|
|
1622
1844
|
const pts = routePoints(r);
|
|
1623
1845
|
const size = Math.max(10, e.style.strokeWidth * 5);
|
|
@@ -1639,10 +1861,10 @@ function computeConnector(e, index) {
|
|
|
1639
1861
|
solid.moveTo(tip).lineTo(w1).lineTo(w2).close();
|
|
1640
1862
|
break;
|
|
1641
1863
|
case "dot": {
|
|
1642
|
-
const
|
|
1864
|
+
const rad2 = size * 0.32;
|
|
1643
1865
|
const ang = Math.atan2(tip.y - from.y, tip.x - from.x);
|
|
1644
|
-
const cx = tip.x -
|
|
1645
|
-
solid.addEllipse({ x: cx -
|
|
1866
|
+
const cx = tip.x - rad2 * Math.cos(ang), cy = tip.y - rad2 * Math.sin(ang);
|
|
1867
|
+
solid.addEllipse({ x: cx - rad2, y: cy - rad2, width: 2 * rad2, height: 2 * rad2 });
|
|
1646
1868
|
break;
|
|
1647
1869
|
}
|
|
1648
1870
|
case "bar": {
|
|
@@ -2109,7 +2331,12 @@ function placed(inner, instance, symbol) {
|
|
|
2109
2331
|
const q = point(p);
|
|
2110
2332
|
return { elementID: null, port: p.port, x: q.x, y: q.y };
|
|
2111
2333
|
};
|
|
2112
|
-
e.connector = {
|
|
2334
|
+
e.connector = {
|
|
2335
|
+
...inner.connector,
|
|
2336
|
+
start: end(inner.connector.start),
|
|
2337
|
+
end: end(inner.connector.end),
|
|
2338
|
+
waypoints: inner.connector.waypoints?.map(point) ?? null
|
|
2339
|
+
};
|
|
2113
2340
|
}
|
|
2114
2341
|
return e;
|
|
2115
2342
|
}
|
|
@@ -2885,7 +3112,7 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
2885
3112
|
if (!visible.has(e.layerID)) continue;
|
|
2886
3113
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
2887
3114
|
if (alpha <= 0) continue;
|
|
2888
|
-
let body = svgElement(e, index, bg);
|
|
3115
|
+
let body = svgElement(e, index, bg, d);
|
|
2889
3116
|
if (angle(e) !== 0 && e.kind !== "connector") {
|
|
2890
3117
|
body = `<g transform="rotate(${fmt(angle(e) * 180 / Math.PI)} ${fmt(midX(frame(e)))} ${fmt(midY(frame(e)))})">
|
|
2891
3118
|
${body}</g>
|
|
@@ -2920,7 +3147,7 @@ function typed(s, progress) {
|
|
|
2920
3147
|
function revealProgress(e) {
|
|
2921
3148
|
return e.reveal !== null && e.reveal < 1 ? Math.max(0, e.reveal) : null;
|
|
2922
3149
|
}
|
|
2923
|
-
function svgElement(e, index, labelBackground) {
|
|
3150
|
+
function svgElement(e, index, labelBackground, page) {
|
|
2924
3151
|
const stroke = color(e.style.stroke);
|
|
2925
3152
|
let strokeAttrs = `stroke="${stroke}" stroke-width="${fmt(e.style.strokeWidth)}" stroke-linecap="round" stroke-linejoin="round"`;
|
|
2926
3153
|
const dash = dashPattern(e.style.dash, e.style.strokeWidth);
|
|
@@ -2931,7 +3158,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
2931
3158
|
if (r !== null) {
|
|
2932
3159
|
if (e.kind === "text") text = typed(text ?? "", r);
|
|
2933
3160
|
else if (e.kind === "connector") {
|
|
2934
|
-
return `<path d="${connectorParts(e, index)[0].trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
3161
|
+
return `<path d="${connectorParts(e, index, page)[0].trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
2935
3162
|
`;
|
|
2936
3163
|
} else {
|
|
2937
3164
|
return `<path d="${outline(e).trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
@@ -2940,7 +3167,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
2940
3167
|
}
|
|
2941
3168
|
switch (e.kind) {
|
|
2942
3169
|
case "connector": {
|
|
2943
|
-
const [line2, filled2] = connectorParts(e, index);
|
|
3170
|
+
const [line2, filled2] = connectorParts(e, index, page);
|
|
2944
3171
|
let s = `<path d="${line2.data()}" fill="none" ${strokeAttrs}/>
|
|
2945
3172
|
`;
|
|
2946
3173
|
if (!filled2.isEmpty) s += `<path d="${filled2.data()}" fill="${stroke}" stroke="none"/>
|
|
@@ -3023,7 +3250,7 @@ function textElement(text, f, e, centered) {
|
|
|
3023
3250
|
const total = lineHeight * lines.length;
|
|
3024
3251
|
const x = centered ? midX(f) : f.x;
|
|
3025
3252
|
const top = centered ? midY(f) - total / 2 : f.y;
|
|
3026
|
-
let s = `<text x="${fmt(x)}" font-family="${cssFamily(e.style.font)}" font-size="${fmt(size)}" fill="${color(e
|
|
3253
|
+
let s = `<text x="${fmt(x)}" font-family="${cssFamily(e.style.font)}" font-size="${fmt(size)}" fill="${color(labelColor(e))}"${centered ? ` text-anchor="middle"` : ""}>`;
|
|
3027
3254
|
lines.forEach((line2, i) => {
|
|
3028
3255
|
s += `<tspan x="${fmt(x)}" y="${fmt(top + lineHeight * (i + 0.8))}">${escapeXML(line2)}</tspan>`;
|
|
3029
3256
|
});
|
|
@@ -3672,6 +3899,7 @@ function inspectFrame(page, t) {
|
|
|
3672
3899
|
d.strokeWidth = r4(e.style.strokeWidth);
|
|
3673
3900
|
d.stroke = rgba2(e.style.stroke);
|
|
3674
3901
|
d.fill = rgba2(e.style.fill);
|
|
3902
|
+
if (e.style.textColor !== null) d.textColor = rgba2(e.style.textColor);
|
|
3675
3903
|
if (e.kind === "connector") {
|
|
3676
3904
|
const [a, b] = endpoints(e, index);
|
|
3677
3905
|
d.start = pt(a);
|
|
@@ -3715,7 +3943,7 @@ function render(source, options = {}) {
|
|
|
3715
3943
|
function isFile(v) {
|
|
3716
3944
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3717
3945
|
}
|
|
3718
|
-
var VERSION = "0.6.
|
|
3946
|
+
var VERSION = "0.6.3";
|
|
3719
3947
|
export {
|
|
3720
3948
|
BOIL_DRAWINGS,
|
|
3721
3949
|
BUNDLED_FONTS,
|
|
@@ -6,18 +6,18 @@ export declare function portPoint(shape: Element, port: Port): Point;
|
|
|
6
6
|
/** Where the line from the shape's center toward `target` crosses its outline. */
|
|
7
7
|
export declare function edgePoint(shape: Element, toward: Point): Point;
|
|
8
8
|
export declare function endpoints(e: Element, index: Index): [Point, Point];
|
|
9
|
+
/** A curve is joined cubic Béziers: the start, then two controls and an end per segment. */
|
|
9
10
|
export type Route = {
|
|
10
11
|
kind: "polyline";
|
|
11
12
|
points: Point[];
|
|
12
13
|
} | {
|
|
13
|
-
kind: "
|
|
14
|
-
|
|
15
|
-
b: Point;
|
|
16
|
-
c: Point;
|
|
17
|
-
d: Point;
|
|
14
|
+
kind: "curve";
|
|
15
|
+
points: Point[];
|
|
18
16
|
};
|
|
19
|
-
/** A route's centerline as points;
|
|
17
|
+
/** A route's centerline as points; each cubic segment is sampled at 25 points. */
|
|
20
18
|
export declare function routePoints(r: Route): Point[];
|
|
19
|
+
/** Drops repeated points and the middle of three in a line, so dragged bends that fall into line disappear. */
|
|
20
|
+
export declare function simplified(pts: Point[]): Point[];
|
|
21
21
|
export declare function route(e: Element, index: Index): Route;
|
|
22
22
|
/** Arrowhead wing points for a tip at `tip` arriving from `from`. */
|
|
23
23
|
export declare function arrowhead(tip: Point, from: Point, size: number): [Point, Point];
|
package/dist/types/element.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export declare const unrotated: (e: Element, p: Point) => Point;
|
|
|
11
11
|
export declare function rotatedBounds(e: Element): Rect;
|
|
12
12
|
/** The element with automatic ink drawn as `ink`; a light ink is not used on a light, mostly opaque fill. */
|
|
13
13
|
export declare function resolvingInk(e: Element, ink: RGBA): Element;
|
|
14
|
+
/** What the element's text is drawn in: a text box's and a note's stroke, any other label's `textColor` or,
|
|
15
|
+
* without one, its stroke. */
|
|
16
|
+
export declare function labelColor(e: Element): RGBA;
|
|
14
17
|
export interface Contour {
|
|
15
18
|
closed: boolean;
|
|
16
19
|
points: Point[];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -20,4 +20,4 @@ export interface RenderOptions {
|
|
|
20
20
|
/** One page of a drawing as an SVG document, as Dottie's own SVG export draws it. */
|
|
21
21
|
export declare function render(source: string | DottieFile | unknown, options?: RenderOptions): string;
|
|
22
22
|
/** The library's version. */
|
|
23
|
-
export declare const VERSION = "0.6.
|
|
23
|
+
export declare const VERSION = "0.6.3";
|
package/dist/types/model.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface Style {
|
|
|
27
27
|
opacity: number | null;
|
|
28
28
|
/** How far into its dash pattern a dashed or dotted line starts; marching ants set it on every frame. */
|
|
29
29
|
dashOffset: number | null;
|
|
30
|
+
/** The color of a shape's or connector's label; null follows `stroke`. Text boxes and notes ignore it. */
|
|
31
|
+
textColor: RGBA | null;
|
|
30
32
|
}
|
|
31
33
|
/** `portIndex` pins the end to one of the bound element's `ports`, and wins over `port`. */
|
|
32
34
|
export interface Endpoint {
|
|
@@ -49,6 +51,10 @@ export interface ConnectorData {
|
|
|
49
51
|
routing: Routing;
|
|
50
52
|
startHead: Arrowhead;
|
|
51
53
|
endHead: Arrowhead;
|
|
54
|
+
/** Bends on the canvas the line passes through, start to end. */
|
|
55
|
+
waypoints: StrokePoint[] | null;
|
|
56
|
+
/** Hops where the line crosses a connector drawn beneath it. */
|
|
57
|
+
hops: boolean | null;
|
|
52
58
|
}
|
|
53
59
|
export interface PathSubpath {
|
|
54
60
|
closed: boolean;
|
package/dist/types/shapes.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Index } from "./connector.ts";
|
|
2
|
-
import type
|
|
1
|
+
import { type Index, type Route } from "./connector.ts";
|
|
2
|
+
import { type DashStyle, type Element, type Page } from "./model.ts";
|
|
3
3
|
import { Path, type Point } from "./path.ts";
|
|
4
4
|
/** The `.grid` cell size actually drawn. */
|
|
5
5
|
export declare const cellSize: (e: Element) => number;
|
|
@@ -13,5 +13,18 @@ export declare function fillPath(e: Element): Path;
|
|
|
13
13
|
export declare function hachurePath(e: Element): Path;
|
|
14
14
|
/** Dash lengths for a stroke width; null for a solid line. */
|
|
15
15
|
export declare function dashPattern(dash: DashStyle, width: number): number[] | null;
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A connector as the stroked line (with open arrowheads) and the closed heads filled in the stroke color. `page`
|
|
18
|
+
* holds the connectors a hopping line hops over; without it a line draws no hops.
|
|
19
|
+
*/
|
|
20
|
+
export declare function connectorParts(e: Element, index: Index, page?: Page): [Path, Path];
|
|
21
|
+
/** How big a hop is for a line `strokeWidth` wide: its radius. */
|
|
22
|
+
export declare function hopRadius(strokeWidth: number): number;
|
|
23
|
+
/** The points a route's segments are measured along: a straight segment's two ends, or a cubic sampled at 25. */
|
|
24
|
+
export declare function hopSamples(r: Route): Point[][];
|
|
25
|
+
/**
|
|
26
|
+
* Where a hopping connector's segments (straight, or each cubic of a curve) cross the connectors drawn beneath it
|
|
27
|
+
* (earlier on a visible layer): for each segment, the distances along it, ascending, leaving out crossings too close
|
|
28
|
+
* to its ends or the hop before.
|
|
29
|
+
*/
|
|
30
|
+
export declare function hopCrossings(e: Element, r: Route, page: Page, index: Index): number[][];
|