@phin-tech/dottie 0.9.0 → 0.10.0

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.
@@ -1,4 +1,4 @@
1
- /*! dottie.js 0.9.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
1
+ /*! dottie.js 0.10.0 | 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 __export = (target, all) => {
@@ -44,6 +44,8 @@ __export(index_exports, {
44
44
  luminance: () => luminance,
45
45
  orderedElements: () => orderedElements,
46
46
  pageIndex: () => pageIndex,
47
+ pageTheme: () => pageTheme,
48
+ paletteColor: () => paletteColor,
47
49
  parseDottie: () => parseDottie,
48
50
  plan: () => plan,
49
51
  posterTime: () => posterTime,
@@ -197,19 +199,80 @@ function trim(s) {
197
199
  return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
198
200
  }
199
201
 
202
+ // src/theme.ts
203
+ var rgb = (r, g, b) => ({ r, g, b, a: 1 });
204
+ function hex(h) {
205
+ const n = parseInt(h.slice(1), 16);
206
+ return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
207
+ }
208
+ var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
209
+ var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
210
+ var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
211
+ var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
212
+ var themes = [
213
+ { id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
214
+ { id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
215
+ {
216
+ id: "sepia",
217
+ name: "Sepia",
218
+ background: rgb(0.953, 0.914, 0.824),
219
+ ink: rgb(0.23, 0.18, 0.13),
220
+ palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
221
+ },
222
+ { id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
223
+ { id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
224
+ { id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
225
+ ];
226
+ function themeNamed(id) {
227
+ return themes.find((t) => t.id === id) ?? null;
228
+ }
229
+ function pageTheme(page) {
230
+ const c = page.customTheme;
231
+ if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
232
+ return themeNamed(page.theme);
233
+ }
234
+ function paletteColor(page, name) {
235
+ const n = name.trim().toLowerCase();
236
+ return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
237
+ }
238
+ function resolvingPalette(page) {
239
+ const named = (e) => !!e.style.named;
240
+ if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
241
+ const resolve2 = (e) => {
242
+ const n = e.style.named;
243
+ if (!n) return e;
244
+ const style2 = { ...e.style };
245
+ const f = n.fill !== null ? paletteColor(page, n.fill) : null;
246
+ const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
247
+ const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
248
+ if (f) style2.fill = { ...f };
249
+ if (s) style2.stroke = { ...s };
250
+ if (t) style2.textColor = { ...t };
251
+ return { ...e, style: style2 };
252
+ };
253
+ return {
254
+ ...page,
255
+ elements: page.elements.map(resolve2),
256
+ symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve2) })) : page.symbols
257
+ };
258
+ }
259
+
200
260
  // src/model.ts
201
261
  var SUPPORTED_VERSION = 2;
202
262
  var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
203
263
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
204
264
  var KNOWN_FIELDS = {
205
265
  file: ["version", "pages", "images"],
206
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
266
+ page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
207
267
  flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
208
268
  flowOverride: ["when", "volume"],
209
269
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
210
270
  layer: ["id", "name", "visible", "locked"],
211
271
  rgba: ["r", "g", "b", "a"],
212
- style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
272
+ style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
273
+ colorNames: ["fill", "stroke", "textColor"],
274
+ customTheme: ["name", "background", "ink", "palette", "base"],
275
+ paletteColor: ["name", "color"],
213
276
  element: [
214
277
  "id",
215
278
  "kind",
@@ -247,8 +310,11 @@ var KNOWN_FIELDS = {
247
310
  "capacity",
248
311
  "delay",
249
312
  "queue",
250
- "gauge"
313
+ "gauge",
314
+ "containerID",
315
+ "container"
251
316
  ],
317
+ container: ["fixed", "lanes"],
252
318
  gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
253
319
  strokePoint: ["x", "y"],
254
320
  subpath: ["closed", "coords"],
@@ -257,7 +323,7 @@ var KNOWN_FIELDS = {
257
323
  extensionRef: ["id", "shape", "version", "params"],
258
324
  normalizedPoint: ["x", "y"],
259
325
  normalizedRect: ["x", "y", "width", "height"],
260
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
326
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
261
327
  waypoint: ["x", "y"],
262
328
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
263
329
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -344,9 +410,36 @@ var Reader = class {
344
410
  gridSize: num(s.gridSize) ?? null,
345
411
  opacity: num(s.opacity) ?? null,
346
412
  dashOffset: num(s.dashOffset) ?? null,
347
- textColor: rgba(s.textColor) ?? null
413
+ textColor: rgba(s.textColor) ?? null,
414
+ named: this.colorNames(s.named)
348
415
  };
349
416
  }
417
+ container(v) {
418
+ if (!isObj(v)) return null;
419
+ this.fields(v, "container");
420
+ return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
421
+ }
422
+ colorNames(v) {
423
+ if (!isObj(v)) return null;
424
+ this.fields(v, "colorNames");
425
+ const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
426
+ return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
427
+ }
428
+ customTheme(v) {
429
+ if (!isObj(v)) return null;
430
+ this.fields(v, "customTheme");
431
+ const background = rgba(v.background), ink = rgba(v.ink);
432
+ if (!background || !ink) {
433
+ this.warn("A custom theme needs a background and an ink; drawn without it");
434
+ return null;
435
+ }
436
+ const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
437
+ this.fields(p, "paletteColor");
438
+ const name = str(p.name), color2 = rgba(p.color);
439
+ return name && color2 ? [{ name, color: color2 }] : [];
440
+ });
441
+ return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
442
+ }
350
443
  endpoint(v) {
351
444
  const e = isObj(v) ? v : {};
352
445
  const port = str(e.port);
@@ -378,6 +471,7 @@ var Reader = class {
378
471
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
379
472
  }) : null,
380
473
  hops: typeof v.hops === "boolean" ? v.hops : null,
474
+ avoid: typeof v.avoid === "boolean" ? v.avoid : null,
381
475
  cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
382
476
  };
383
477
  }
@@ -446,7 +540,9 @@ var Reader = class {
446
540
  capacity: num(v.capacity) ?? null,
447
541
  delay: num(v.delay) ?? null,
448
542
  queue: v.queue === true ? true : null,
449
- gauge: this.gauge(v.gauge)
543
+ gauge: this.gauge(v.gauge),
544
+ containerID: uuid(v.containerID) ?? null,
545
+ container: this.container(v.container)
450
546
  };
451
547
  }
452
548
  gauge(v) {
@@ -667,6 +763,7 @@ var Reader = class {
667
763
  layers,
668
764
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
669
765
  theme: str(v.theme) ?? null,
766
+ customTheme: this.customTheme(v.customTheme),
670
767
  timeline: this.timeline(v.timeline),
671
768
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
672
769
  variables: this.variables(v.variables),
@@ -730,7 +827,7 @@ function parseDottie(source) {
730
827
  if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
731
828
  }
732
829
  }
733
- const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
830
+ const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
734
831
  if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
735
832
  return { version, pages, warnings: reader.warnings };
736
833
  }
@@ -750,7 +847,8 @@ function cloneElement(e) {
750
847
  ...e.style,
751
848
  stroke: { ...e.style.stroke },
752
849
  fill: { ...e.style.fill },
753
- textColor: e.style.textColor ? { ...e.style.textColor } : null
850
+ textColor: e.style.textColor ? { ...e.style.textColor } : null,
851
+ named: e.style.named ? { ...e.style.named } : e.style.named
754
852
  },
755
853
  connector: e.connector ? {
756
854
  ...e.connector,
@@ -1240,6 +1338,165 @@ function shapePath(e) {
1240
1338
  return path;
1241
1339
  }
1242
1340
 
1341
+ // src/elbow.ts
1342
+ var MARGIN = 16;
1343
+ var BEND = 24;
1344
+ var SMALLEST = 24;
1345
+ var MOST = 48;
1346
+ function obstacle(e) {
1347
+ if (e.kind === "connector" || e.kind === "text" || e.kind === "freehand") return null;
1348
+ if (e.kind === "path" && !e.extension) return null;
1349
+ if (e.style.opacity !== null && e.style.opacity <= 0) return null;
1350
+ const r = rotatedBounds(e);
1351
+ return r.width < SMALLEST || r.height < SMALLEST ? null : r;
1352
+ }
1353
+ function crosses(points, r) {
1354
+ const x1 = r.x + r.width, y1 = r.y + r.height;
1355
+ for (let k = 1; k < points.length; k++) {
1356
+ const p = points[k - 1], q = points[k];
1357
+ if (p.y === q.y) {
1358
+ if (r.y < p.y && p.y < y1 && Math.min(p.x, q.x) < x1 && Math.max(p.x, q.x) > r.x) return true;
1359
+ } else if (p.x === q.x) {
1360
+ if (r.x < p.x && p.x < x1 && Math.min(p.y, q.y) < y1 && Math.max(p.y, q.y) > r.y) return true;
1361
+ }
1362
+ }
1363
+ return false;
1364
+ }
1365
+ var padded = (r) => ({ x: r.x - MARGIN, y: r.y - MARGIN, width: r.width + 2 * MARGIN, height: r.height + 2 * MARGIN });
1366
+ var inside = (p, r) => r.x < p.x && p.x < r.x + r.width && r.y < p.y && p.y < r.y + r.height;
1367
+ var memo = /* @__PURE__ */ new Map();
1368
+ function avoiding(plain, a, b, startVertical, endVertical, elements, except) {
1369
+ const usable = (r) => !inside(a, padded(r)) && !inside(b, padded(r));
1370
+ const all = [];
1371
+ let crossed = false;
1372
+ for (const e of elements) {
1373
+ if (except.has(e.id)) continue;
1374
+ const r = obstacle(e);
1375
+ if (!r || !usable(r)) continue;
1376
+ all.push(r);
1377
+ if (!crossed && crosses(plain, r)) crossed = true;
1378
+ }
1379
+ if (!crossed) return plain;
1380
+ const key = JSON.stringify([a.x, a.y, b.x, b.y, startVertical, endVertical, all.map((r) => [r.x, r.y, r.width, r.height])]);
1381
+ const hit = memo.get(key);
1382
+ if (hit) return hit;
1383
+ const out = route(plain, a, b, startVertical, endVertical, all);
1384
+ if (memo.size >= 512) memo.clear();
1385
+ memo.set(key, out);
1386
+ return out;
1387
+ }
1388
+ function route(plain, a, b, startVertical, endVertical, usable) {
1389
+ let chosen = usable.map((_, i) => i).filter((i) => crosses(plain, usable[i]));
1390
+ if (chosen.length === 0) return plain;
1391
+ for (let round = 0; round < 4; round++) {
1392
+ if (chosen.length > MOST) return plain;
1393
+ const path = grid(a, b, startVertical, endVertical, chosen.map((i) => padded(usable[i])));
1394
+ if (!path) return plain;
1395
+ const more = usable.map((_, i) => i).filter((i) => !chosen.includes(i) && crosses(path, usable[i]));
1396
+ if (more.length === 0) return path;
1397
+ chosen = [...chosen, ...more].sort((p, q) => p - q);
1398
+ }
1399
+ return plain;
1400
+ }
1401
+ function grid(a, b, startVertical, endVertical, boxes) {
1402
+ let xs = [a.x, b.x, (a.x + b.x) / 2], ys = [a.y, b.y, (a.y + b.y) / 2];
1403
+ for (const r of boxes) {
1404
+ xs.push(r.x, r.x + r.width);
1405
+ ys.push(r.y, r.y + r.height);
1406
+ }
1407
+ xs = [...new Set(xs)].sort((p, q) => p - q);
1408
+ ys = [...new Set(ys)].sort((p, q) => p - q);
1409
+ const nx = xs.length, ny = ys.length;
1410
+ const ia = xs.indexOf(a.x), ja = ys.indexOf(a.y), ib = xs.indexOf(b.x), jb = ys.indexOf(b.y);
1411
+ if (ia < 0 || ja < 0 || ib < 0 || jb < 0) return null;
1412
+ const at = new Map(xs.map((x, k) => [x, k])), bt = new Map(ys.map((y, k) => [y, k]));
1413
+ const inner = new Uint8Array(nx * ny), acrossBlocked = new Uint8Array(nx * ny), downBlocked = new Uint8Array(nx * ny);
1414
+ for (const r of boxes) {
1415
+ const i0 = at.get(r.x), i1 = at.get(r.x + r.width), j0 = bt.get(r.y), j1 = bt.get(r.y + r.height);
1416
+ if (i0 === void 0 || i1 === void 0 || j0 === void 0 || j1 === void 0) continue;
1417
+ for (let i = i0; i < i1; i++) for (let j = j0; j < j1; j++) {
1418
+ if (j > j0) acrossBlocked[i * ny + j] = 1;
1419
+ if (i > i0) downBlocked[i * ny + j] = 1;
1420
+ if (i > i0 && j > j0) inner[i * ny + j] = 1;
1421
+ }
1422
+ }
1423
+ const blocked = (i, j) => inner[i * ny + j] === 1;
1424
+ const across = (i0, i1, j) => acrossBlocked[Math.min(i0, i1) * ny + j] === 1;
1425
+ const down = (j0, j1, i) => downBlocked[i * ny + Math.min(j0, j1)] === 1;
1426
+ const count = nx * ny * 2;
1427
+ const dist = new Array(count).fill(Infinity), prev = new Array(count).fill(-1);
1428
+ const done = new Array(count).fill(false);
1429
+ const heap = new Frontier();
1430
+ const first = (ia * ny + ja) * 2 + (startVertical ? 1 : 0);
1431
+ dist[first] = 0;
1432
+ heap.push(0, first);
1433
+ const want = (ib * ny + jb) * 2 + (endVertical ? 1 : 0);
1434
+ for (let top = heap.pop(); top; top = heap.pop()) {
1435
+ const [cost, s] = top;
1436
+ if (done[s]) continue;
1437
+ done[s] = true;
1438
+ if (s === want) break;
1439
+ const node = Math.floor(s / 2), axis = s % 2, i = Math.floor(node / ny), j = node % ny;
1440
+ for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
1441
+ const ni = i + di, nj = j + dj;
1442
+ if (ni < 0 || ni >= nx || nj < 0 || nj >= ny || blocked(ni, nj)) continue;
1443
+ const turn = di !== 0 ? 0 : 1;
1444
+ if (di !== 0 ? across(i, ni, j) : down(j, nj, i)) continue;
1445
+ const length = Math.abs(xs[ni] - xs[i]) + Math.abs(ys[nj] - ys[j]);
1446
+ const c = cost + length + (turn !== axis ? BEND : 0);
1447
+ const t = (ni * ny + nj) * 2 + turn;
1448
+ if (c < dist[t]) {
1449
+ dist[t] = c;
1450
+ prev[t] = s;
1451
+ heap.push(c, t);
1452
+ }
1453
+ }
1454
+ }
1455
+ if (!done[want]) return null;
1456
+ const points = [];
1457
+ for (let s = want; s >= 0; s = prev[s]) {
1458
+ const node = Math.floor(s / 2);
1459
+ points.push({ x: xs[Math.floor(node / ny)], y: ys[node % ny] });
1460
+ }
1461
+ return points.reverse();
1462
+ }
1463
+ var Frontier = class {
1464
+ constructor() {
1465
+ __publicField(this, "heap", []);
1466
+ __publicField(this, "next", 0);
1467
+ }
1468
+ less(i, j) {
1469
+ const a = this.heap[i], b = this.heap[j];
1470
+ return a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
1471
+ }
1472
+ push(cost, state) {
1473
+ this.heap.push([cost, this.next++, state]);
1474
+ for (let i = this.heap.length - 1; i > 0; ) {
1475
+ const up = i - 1 >> 1;
1476
+ if (!this.less(i, up)) break;
1477
+ [this.heap[i], this.heap[up]] = [this.heap[up], this.heap[i]];
1478
+ i = up;
1479
+ }
1480
+ }
1481
+ pop() {
1482
+ if (this.heap.length === 0) return null;
1483
+ const top = this.heap[0], last = this.heap.pop();
1484
+ if (this.heap.length > 0) {
1485
+ this.heap[0] = last;
1486
+ for (let i = 0; ; ) {
1487
+ const l = 2 * i + 1, r = l + 1;
1488
+ let m = i;
1489
+ if (l < this.heap.length && this.less(l, m)) m = l;
1490
+ if (r < this.heap.length && this.less(r, m)) m = r;
1491
+ if (m === i) break;
1492
+ [this.heap[i], this.heap[m]] = [this.heap[m], this.heap[i]];
1493
+ i = m;
1494
+ }
1495
+ }
1496
+ return [top[0], top[2]];
1497
+ }
1498
+ };
1499
+
1243
1500
  // src/connector.ts
1244
1501
  function portPoint(shape, port) {
1245
1502
  const r = frame(shape);
@@ -1383,7 +1640,7 @@ function routeThrough(p, c) {
1383
1640
  }
1384
1641
  }
1385
1642
  }
1386
- function route(e, index) {
1643
+ function route2(e, index) {
1387
1644
  const c = e.connector;
1388
1645
  if (!c) return { kind: "polyline", points: [] };
1389
1646
  const [a, b] = endpoints(e, index);
@@ -1399,16 +1656,19 @@ function route(e, index) {
1399
1656
  case "elbow": {
1400
1657
  const startV = vertical(c.start.port) ?? !horizontal;
1401
1658
  const endV = vertical(c.end.port) ?? !horizontal;
1659
+ let plain;
1402
1660
  if (startV && endV) {
1403
1661
  const my = a.y + dy / 2;
1404
- return { kind: "polyline", points: [a, { x: a.x, y: my }, { x: b.x, y: my }, b] };
1405
- }
1406
- if (!startV && !endV) {
1662
+ plain = [a, { x: a.x, y: my }, { x: b.x, y: my }, b];
1663
+ } else if (!startV && !endV) {
1407
1664
  const mx = a.x + dx / 2;
1408
- return { kind: "polyline", points: [a, { x: mx, y: a.y }, { x: mx, y: b.y }, b] };
1409
- }
1410
- if (startV) return { kind: "polyline", points: [a, { x: a.x, y: b.y }, b] };
1411
- return { kind: "polyline", points: [a, { x: b.x, y: a.y }, b] };
1665
+ plain = [a, { x: mx, y: a.y }, { x: mx, y: b.y }, b];
1666
+ } else if (startV) plain = [a, { x: a.x, y: b.y }, b];
1667
+ else plain = [a, { x: b.x, y: a.y }, b];
1668
+ if (c.avoid !== true) return { kind: "polyline", points: plain };
1669
+ const ends = new Set([c.start.elementID, c.end.elementID].filter((x) => x !== null));
1670
+ const routed = avoiding(plain, a, b, startV, endV, index.values(), ends);
1671
+ return { kind: "polyline", points: routed === plain ? plain : simplified(routed) };
1412
1672
  }
1413
1673
  }
1414
1674
  }
@@ -1419,7 +1679,7 @@ function arrowhead(tip, from, size) {
1419
1679
  return [wing(spread), wing(-spread)];
1420
1680
  }
1421
1681
  function connectorBounds(e, index) {
1422
- const pts = routePoints(route(e, index));
1682
+ const pts = routePoints(route2(e, index));
1423
1683
  if (pts.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
1424
1684
  let x0 = pts[0].x, y0 = pts[0].y, x1 = x0, y1 = y0;
1425
1685
  for (const p of pts) {
@@ -1431,7 +1691,7 @@ function connectorBounds(e, index) {
1431
1691
  return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
1432
1692
  }
1433
1693
  function midpoint(e, index) {
1434
- const pts = routePoints(route(e, index));
1694
+ const pts = routePoints(route2(e, index));
1435
1695
  if (pts.length <= 1) return pts[0] ?? { x: 0, y: 0 };
1436
1696
  const lengths = [];
1437
1697
  let total = 0;
@@ -1580,7 +1840,7 @@ function graphOf(frame2) {
1580
1840
  graphs.set(els, { members: [...els], graph });
1581
1841
  return graph;
1582
1842
  }
1583
- var Frontier = class {
1843
+ var Frontier2 = class {
1584
1844
  constructor() {
1585
1845
  __publicField(this, "heap", []);
1586
1846
  }
@@ -1634,7 +1894,7 @@ function grow({ shapes: shapes2, next }, from, offline, until) {
1634
1894
  const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
1635
1895
  let counter = 1;
1636
1896
  const tree = new FlowTree(from);
1637
- const open = new Frontier();
1897
+ const open = new Frontier2();
1638
1898
  open.push(from, 0, 0);
1639
1899
  while (open.size > 0) {
1640
1900
  const { id: n, order: o } = open.pop();
@@ -1740,6 +2000,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1740
2000
  return pts;
1741
2001
  };
1742
2002
  const metrics2 = emptyMetrics();
2003
+ const routes = /* @__PURE__ */ new Map();
1743
2004
  for (const e of frame2.elements) {
1744
2005
  if (e.kind === "connector" && !e.connector) continue;
1745
2006
  metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
@@ -1751,7 +2012,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1751
2012
  if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
1752
2013
  }
1753
2014
  const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
1754
- const route2 = (tree, goal) => {
2015
+ const route3 = (tree, goal) => {
1755
2016
  const hops = [], shapes2 = [];
1756
2017
  let at = goal;
1757
2018
  for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
@@ -1763,7 +2024,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1763
2024
  return { hops: hops.reverse(), shapes: shapes2.reverse() };
1764
2025
  };
1765
2026
  const way = (hops, shapes2) => {
1766
- const points = [], inside = [], stretches = [];
2027
+ const points = [], inside2 = [], stretches = [];
1767
2028
  let length = 0;
1768
2029
  hops.forEach(([id, back], k) => {
1769
2030
  const c = byID.get(id);
@@ -1774,7 +2035,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1774
2035
  if (last) {
1775
2036
  const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
1776
2037
  const shape = byID.get(shapes2[k]);
1777
- inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
2038
+ inside2.push([length, length + gap, shape ? delayOf(shape) : 0]);
1778
2039
  length += gap;
1779
2040
  }
1780
2041
  const start2 = length;
@@ -1782,7 +2043,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1782
2043
  stretches.push([id, start2, length]);
1783
2044
  points.push(...line2);
1784
2045
  });
1785
- return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
2046
+ return length > 0 ? { line: new Polyline(points), length, inside: inside2, stretches } : null;
1786
2047
  };
1787
2048
  const along = (w, elapsedAt, speed) => {
1788
2049
  let elapsed = elapsedAt, d = 0;
@@ -1823,6 +2084,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1823
2084
  if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
1824
2085
  const source = byID.get(flow.from);
1825
2086
  const base2 = source ? baseTree(flow.from) : null;
2087
+ routes.set(flow.id, []);
1826
2088
  if (!source || !base2) continue;
1827
2089
  const volume = amountIn(flow, values);
1828
2090
  const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
@@ -1837,7 +2099,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1837
2099
  const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
1838
2100
  const goal2 = tree?.goals(candidates, false)[0];
1839
2101
  if (!tree || goal2 === void 0) break;
1840
- const r = route2(tree, goal2);
2102
+ const r = route3(tree, goal2);
1841
2103
  const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
1842
2104
  const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
1843
2105
  const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
@@ -1850,7 +2112,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1850
2112
  let at = flow.from;
1851
2113
  const goal = base2.goals(candidates, false)[0];
1852
2114
  if (goal !== void 0) {
1853
- const r = route2(base2, goal), done = full();
2115
+ const r = route3(base2, goal), done = full();
1854
2116
  const sequence = [[r.shapes[0], r.shapes[0]]];
1855
2117
  r.hops.forEach(([id], k) => {
1856
2118
  sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
@@ -1879,6 +2141,13 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1879
2141
  else waiting2.push([at, left]);
1880
2142
  }
1881
2143
  waitingBy.set(flow.id, waiting2);
2144
+ routes.set(flow.id, placements.map((p) => ({
2145
+ goal: p.goal,
2146
+ hops: p.hops,
2147
+ amount: p.amount,
2148
+ waits: p.waits === true,
2149
+ cost: p.hops.reduce((s, [id]) => s + routeCost(byID.get(id)?.connector?.cost ?? null), 0)
2150
+ })));
1882
2151
  for (const p of placements) {
1883
2152
  for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
1884
2153
  m.throughput += p.amount;
@@ -1961,7 +2230,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1961
2230
  const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
1962
2231
  const goal = tree?.goals(flow.to, false)[0];
1963
2232
  if (!tree || goal === void 0) break;
1964
- const r = route2(tree, goal);
2233
+ const r = route3(tree, goal);
1965
2234
  const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
1966
2235
  const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
1967
2236
  const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
@@ -2016,7 +2285,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
2016
2285
  if (c === null || !Number.isFinite(c) || !m) continue;
2017
2286
  m.load = c > 0 ? m.throughput / c : 1;
2018
2287
  }
2019
- return { packets, metrics: metrics2 };
2288
+ return { packets, metrics: metrics2, routes };
2020
2289
  }
2021
2290
  var BLANK = {
2022
2291
  seed: 1,
@@ -2110,16 +2379,16 @@ function finishing(elements, metrics2, values, frame2, flows, since = Infinity,
2110
2379
  }
2111
2380
  var gaugeKey = (id) => "gauge|" + id;
2112
2381
  var GAUGE_EASE = 1;
2113
- var hex = (h) => {
2382
+ var hex2 = (h) => {
2114
2383
  const v = parseInt(h.slice(1), 16);
2115
2384
  return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
2116
2385
  };
2117
- var GAUGE_GREEN = hex("#16a34a");
2118
- var GAUGE_AMBER = hex("#d97706");
2119
- var GAUGE_RED = hex("#dc2626");
2120
- var GAUGE_BLUE = hex("#2f6fdf");
2121
- var GAUGE_TRACK = hex("#e5e7eb");
2122
- var GAUGE_GRAY = hex("#9ca3af");
2386
+ var GAUGE_GREEN = hex2("#16a34a");
2387
+ var GAUGE_AMBER = hex2("#d97706");
2388
+ var GAUGE_RED = hex2("#dc2626");
2389
+ var GAUGE_BLUE = hex2("#2f6fdf");
2390
+ var GAUGE_TRACK = hex2("#e5e7eb");
2391
+ var GAUGE_GRAY = hex2("#9ca3af");
2123
2392
  function formatted(metric, v) {
2124
2393
  if (v === null || !Number.isFinite(v)) return "\u2013";
2125
2394
  switch (metric) {
@@ -2405,17 +2674,17 @@ function clusterStrokes(c, angle2, size) {
2405
2674
  });
2406
2675
  }
2407
2676
  function contains(contours, p) {
2408
- let inside = false;
2677
+ let inside2 = false;
2409
2678
  for (const poly of contours) {
2410
2679
  if (poly.length < 3) continue;
2411
2680
  let k = poly.length - 1;
2412
2681
  for (let i = 0; i < poly.length; i++) {
2413
2682
  const a = poly[i], b = poly[k];
2414
- if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside = !inside;
2683
+ if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside2 = !inside2;
2415
2684
  k = i;
2416
2685
  }
2417
2686
  }
2418
- return inside;
2687
+ return inside2;
2419
2688
  }
2420
2689
  function distance(p, contours) {
2421
2690
  let best = Infinity;
@@ -2692,7 +2961,7 @@ function connectorParts(e, index, page) {
2692
2961
  const c = e.connector;
2693
2962
  if (!c) return [new Path(), new Path()];
2694
2963
  const [a, b] = endpoints(e, index);
2695
- const r = route(e, index);
2964
+ const r = route2(e, index);
2696
2965
  const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
2697
2966
  const key = [
2698
2967
  e.seed,
@@ -2780,7 +3049,7 @@ function linesBeneath(e, page, index) {
2780
3049
  }
2781
3050
  const count = f.first.get(e.id) ?? f.under.length;
2782
3051
  while (f.lines.length < count) {
2783
- const points = routePoints(route(f.under[f.lines.length], index));
3052
+ const points = routePoints(route2(f.under[f.lines.length], index));
2784
3053
  f.lines.push({ points, box: boxOf(points) });
2785
3054
  }
2786
3055
  return { lines: f.lines, count };
@@ -3084,7 +3353,7 @@ function followSpan(track, t) {
3084
3353
  return f;
3085
3354
  }
3086
3355
  function tracing(e, page, index = indexOf(page)) {
3087
- if (e.kind === "connector") return new Polyline(routePoints(route(e, index)));
3356
+ if (e.kind === "connector") return new Polyline(routePoints(route2(e, index)));
3088
3357
  const clean = cloneElement(e);
3089
3358
  clean.style.roughness = 0;
3090
3359
  const pts = outline(clean).contours()[0] ?? [];
@@ -3364,10 +3633,10 @@ var Expansions = class {
3364
3633
  this.posing = posing;
3365
3634
  }
3366
3635
  };
3367
- function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo) {
3636
+ function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo2) {
3368
3637
  if (!hasInstances(page)) return page;
3369
3638
  const budget = { left: MAX_ELEMENTS };
3370
- const m = memo && memo.page === page && memo.t === t && memo.posing === posing ? memo : void 0;
3639
+ const m = memo2 && memo2.page === page && memo2.t === t && memo2.posing === posing ? memo2 : void 0;
3371
3640
  return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
3372
3641
  }
3373
3642
  function symbolTable(page) {
@@ -3375,7 +3644,7 @@ function symbolTable(page) {
3375
3644
  for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
3376
3645
  return table;
3377
3646
  }
3378
- function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo) {
3647
+ function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo2) {
3379
3648
  const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
3380
3649
  if (instances.size === 0) {
3381
3650
  budget.left -= elements.length;
@@ -3401,7 +3670,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
3401
3670
  continue;
3402
3671
  }
3403
3672
  const override0 = stack.length === 0 ? times.get(e.id) : void 0;
3404
- const was = memo?.made.get(e);
3673
+ const was = memo2?.made.get(e);
3405
3674
  if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
3406
3675
  budget.left -= was.before - was.after;
3407
3676
  for (const x of was.out) out.push(x);
@@ -3423,7 +3692,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
3423
3692
  layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
3424
3693
  };
3425
3694
  for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
3426
- if (memo && stack.length === 0 && !was) memo.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
3695
+ if (memo2 && stack.length === 0 && !was) memo2.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
3427
3696
  }
3428
3697
  return out;
3429
3698
  }
@@ -3476,8 +3745,8 @@ function mix2(x) {
3476
3745
  return z ^ z >> 31n;
3477
3746
  }
3478
3747
  function halves(id) {
3479
- const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
3480
- return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
3748
+ const hex3 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
3749
+ return [BigInt(`0x${hex3.slice(0, 16)}`), BigInt(`0x${hex3.slice(16)}`)];
3481
3750
  }
3482
3751
  function derivedID(instance, inner) {
3483
3752
  const [a, b] = halves(instance), [c, d] = halves(inner);
@@ -3805,12 +4074,23 @@ function inline(src, style2 = {}) {
3805
4074
  i += m[0].length;
3806
4075
  continue;
3807
4076
  }
3808
- if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
4077
+ if (m = /^(\*\*\*|___)(?=\S)([\s\S]*?\S)\1(?!\*|_)/.exec(rest)) {
3809
4078
  emit();
3810
- out.push(...inline(m[2], { ...style2, bold: true }));
4079
+ out.push(...inline(m[2], { ...style2, bold: true, italic: true }));
3811
4080
  i += m[0].length;
3812
4081
  continue;
3813
4082
  }
4083
+ if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
4084
+ let body = m[2], length = m[0].length;
4085
+ if (m[1] === "**" && rest[length] === "*" && (body.replace(/\*\*/g, "").match(/\*/g) ?? []).length % 2 === 1) {
4086
+ body += "*";
4087
+ length++;
4088
+ }
4089
+ emit();
4090
+ out.push(...inline(body, { ...style2, bold: true }));
4091
+ i += length;
4092
+ continue;
4093
+ }
3814
4094
  if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
3815
4095
  emit();
3816
4096
  out.push(...inline(m[2], { ...style2, italic: true }));
@@ -4194,20 +4474,6 @@ function sameStyle(a, b) {
4194
4474
  return a === b || a.size === b.size && a.bold === b.bold && a.italic === b.italic && a.mono === b.mono && a.strike === b.strike && a.underline === b.underline && a.alpha === b.alpha;
4195
4475
  }
4196
4476
 
4197
- // src/theme.ts
4198
- var rgb = (r, g, b) => ({ r, g, b, a: 1 });
4199
- var themes = [
4200
- { id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
4201
- { id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
4202
- { id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
4203
- { id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
4204
- { id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
4205
- { id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
4206
- ];
4207
- function themeNamed(id) {
4208
- return themes.find((t) => t.id === id) ?? null;
4209
- }
4210
-
4211
4477
  // src/svg.ts
4212
4478
  function bounds(page, padding = 24) {
4213
4479
  if (hasInstances(page)) return bounds(expandingSymbols(page, 0), padding);
@@ -4273,7 +4539,7 @@ function svgOpen(b, background, paint) {
4273
4539
  }
4274
4540
  function svgParts(page, background, fixed, links = false, only) {
4275
4541
  if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
4276
- const theme = themeNamed(page.theme);
4542
+ const theme = pageTheme(page);
4277
4543
  const bg = background ? theme?.background ?? background : null;
4278
4544
  const ink = theme?.ink ?? black;
4279
4545
  const d = { ...page, elements: page.elements.map((e) => resolvingInk(e, ink)) };
@@ -4309,7 +4575,7 @@ ${body}</a>
4309
4575
  }
4310
4576
  function svg(page, background = { r: 1, g: 1, b: 1, a: 1 }, fixed) {
4311
4577
  const { bounds: b, parts } = svgParts(page, background, fixed, true);
4312
- return svgOpen(b, themeNamed(page.theme)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
4578
+ return svgOpen(b, pageTheme(page)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
4313
4579
  }
4314
4580
  function labelRect(e, index) {
4315
4581
  const m = midpoint(e, index);
@@ -4403,7 +4669,7 @@ function svgElement(e, index, labelBackground, page) {
4403
4669
  }
4404
4670
  s += `<path d="${outline(e).data()}" fill="none" ${strokeAttrs}/>
4405
4671
  `;
4406
- if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" ? pathLabelBox(e) : frame(e), e, true);
4672
+ if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" || e.kind === "rectangle" ? pathLabelBox(e) : frame(e), e, true);
4407
4673
  return s;
4408
4674
  }
4409
4675
  default:
@@ -4646,7 +4912,7 @@ var Playback = class {
4646
4912
  this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
4647
4913
  this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
4648
4914
  this.state = { ...page.variables ?? {} };
4649
- const theme = themeNamed(page.theme);
4915
+ const theme = pageTheme(page);
4650
4916
  this.background = theme?.background ?? WHITE;
4651
4917
  this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
4652
4918
  const ns = "http://www.w3.org/2000/svg";
@@ -5300,7 +5566,7 @@ function inspectFrame(page, t) {
5300
5566
  const [a, b] = endpoints(e, index);
5301
5567
  d.start = pt(a);
5302
5568
  d.end = pt(b);
5303
- d.route = routePoints(route(e, index)).map(pt);
5569
+ d.route = routePoints(route2(e, index)).map(pt);
5304
5570
  }
5305
5571
  return d;
5306
5572
  });
@@ -5339,7 +5605,7 @@ function render(source, options = {}) {
5339
5605
  function isFile(v) {
5340
5606
  return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
5341
5607
  }
5342
- var VERSION = "0.9.0";
5608
+ var VERSION = "0.10.0";
5343
5609
 
5344
5610
  // src/player-entry.ts
5345
5611
  servePlans();
@@ -5380,6 +5646,8 @@ export {
5380
5646
  luminance,
5381
5647
  orderedElements,
5382
5648
  pageIndex,
5649
+ pageTheme,
5650
+ paletteColor,
5383
5651
  parseDottie,
5384
5652
  plan,
5385
5653
  posterTime,