@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.
package/dist/dottie.js CHANGED
@@ -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 __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
@@ -134,19 +134,80 @@ function trim(s) {
134
134
  return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
135
135
  }
136
136
 
137
+ // src/theme.ts
138
+ var rgb = (r, g, b) => ({ r, g, b, a: 1 });
139
+ function hex(h) {
140
+ const n = parseInt(h.slice(1), 16);
141
+ return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
142
+ }
143
+ var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
144
+ var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
145
+ var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
146
+ var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
147
+ var themes = [
148
+ { id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
149
+ { id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
150
+ {
151
+ id: "sepia",
152
+ name: "Sepia",
153
+ background: rgb(0.953, 0.914, 0.824),
154
+ ink: rgb(0.23, 0.18, 0.13),
155
+ palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
156
+ },
157
+ { id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
158
+ { id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
159
+ { id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
160
+ ];
161
+ function themeNamed(id) {
162
+ return themes.find((t) => t.id === id) ?? null;
163
+ }
164
+ function pageTheme(page) {
165
+ const c = page.customTheme;
166
+ if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
167
+ return themeNamed(page.theme);
168
+ }
169
+ function paletteColor(page, name) {
170
+ const n = name.trim().toLowerCase();
171
+ return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
172
+ }
173
+ function resolvingPalette(page) {
174
+ const named = (e) => !!e.style.named;
175
+ if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
176
+ const resolve2 = (e) => {
177
+ const n = e.style.named;
178
+ if (!n) return e;
179
+ const style2 = { ...e.style };
180
+ const f = n.fill !== null ? paletteColor(page, n.fill) : null;
181
+ const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
182
+ const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
183
+ if (f) style2.fill = { ...f };
184
+ if (s) style2.stroke = { ...s };
185
+ if (t) style2.textColor = { ...t };
186
+ return { ...e, style: style2 };
187
+ };
188
+ return {
189
+ ...page,
190
+ elements: page.elements.map(resolve2),
191
+ symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve2) })) : page.symbols
192
+ };
193
+ }
194
+
137
195
  // src/model.ts
138
196
  var SUPPORTED_VERSION = 2;
139
197
  var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
140
198
  var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
141
199
  var KNOWN_FIELDS = {
142
200
  file: ["version", "pages", "images"],
143
- page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
201
+ page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
144
202
  flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
145
203
  flowOverride: ["when", "volume"],
146
204
  symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
147
205
  layer: ["id", "name", "visible", "locked"],
148
206
  rgba: ["r", "g", "b", "a"],
149
- style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
207
+ style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
208
+ colorNames: ["fill", "stroke", "textColor"],
209
+ customTheme: ["name", "background", "ink", "palette", "base"],
210
+ paletteColor: ["name", "color"],
150
211
  element: [
151
212
  "id",
152
213
  "kind",
@@ -184,8 +245,11 @@ var KNOWN_FIELDS = {
184
245
  "capacity",
185
246
  "delay",
186
247
  "queue",
187
- "gauge"
248
+ "gauge",
249
+ "containerID",
250
+ "container"
188
251
  ],
252
+ container: ["fixed", "lanes"],
189
253
  gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
190
254
  strokePoint: ["x", "y"],
191
255
  subpath: ["closed", "coords"],
@@ -194,7 +258,7 @@ var KNOWN_FIELDS = {
194
258
  extensionRef: ["id", "shape", "version", "params"],
195
259
  normalizedPoint: ["x", "y"],
196
260
  normalizedRect: ["x", "y", "width", "height"],
197
- connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
261
+ connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
198
262
  waypoint: ["x", "y"],
199
263
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
200
264
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -281,9 +345,36 @@ var Reader = class {
281
345
  gridSize: num(s.gridSize) ?? null,
282
346
  opacity: num(s.opacity) ?? null,
283
347
  dashOffset: num(s.dashOffset) ?? null,
284
- textColor: rgba(s.textColor) ?? null
348
+ textColor: rgba(s.textColor) ?? null,
349
+ named: this.colorNames(s.named)
285
350
  };
286
351
  }
352
+ container(v) {
353
+ if (!isObj(v)) return null;
354
+ this.fields(v, "container");
355
+ return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
356
+ }
357
+ colorNames(v) {
358
+ if (!isObj(v)) return null;
359
+ this.fields(v, "colorNames");
360
+ const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
361
+ return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
362
+ }
363
+ customTheme(v) {
364
+ if (!isObj(v)) return null;
365
+ this.fields(v, "customTheme");
366
+ const background = rgba(v.background), ink = rgba(v.ink);
367
+ if (!background || !ink) {
368
+ this.warn("A custom theme needs a background and an ink; drawn without it");
369
+ return null;
370
+ }
371
+ const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
372
+ this.fields(p, "paletteColor");
373
+ const name = str(p.name), color2 = rgba(p.color);
374
+ return name && color2 ? [{ name, color: color2 }] : [];
375
+ });
376
+ return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
377
+ }
287
378
  endpoint(v) {
288
379
  const e = isObj(v) ? v : {};
289
380
  const port = str(e.port);
@@ -315,6 +406,7 @@ var Reader = class {
315
406
  return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
316
407
  }) : null,
317
408
  hops: typeof v.hops === "boolean" ? v.hops : null,
409
+ avoid: typeof v.avoid === "boolean" ? v.avoid : null,
318
410
  cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
319
411
  };
320
412
  }
@@ -383,7 +475,9 @@ var Reader = class {
383
475
  capacity: num(v.capacity) ?? null,
384
476
  delay: num(v.delay) ?? null,
385
477
  queue: v.queue === true ? true : null,
386
- gauge: this.gauge(v.gauge)
478
+ gauge: this.gauge(v.gauge),
479
+ containerID: uuid(v.containerID) ?? null,
480
+ container: this.container(v.container)
387
481
  };
388
482
  }
389
483
  gauge(v) {
@@ -604,6 +698,7 @@ var Reader = class {
604
698
  layers,
605
699
  elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
606
700
  theme: str(v.theme) ?? null,
701
+ customTheme: this.customTheme(v.customTheme),
607
702
  timeline: this.timeline(v.timeline),
608
703
  symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
609
704
  variables: this.variables(v.variables),
@@ -667,7 +762,7 @@ function parseDottie(source) {
667
762
  if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
668
763
  }
669
764
  }
670
- const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
765
+ const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
671
766
  if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
672
767
  return { version, pages, warnings: reader.warnings };
673
768
  }
@@ -687,7 +782,8 @@ function cloneElement(e) {
687
782
  ...e.style,
688
783
  stroke: { ...e.style.stroke },
689
784
  fill: { ...e.style.fill },
690
- textColor: e.style.textColor ? { ...e.style.textColor } : null
785
+ textColor: e.style.textColor ? { ...e.style.textColor } : null,
786
+ named: e.style.named ? { ...e.style.named } : e.style.named
691
787
  },
692
788
  connector: e.connector ? {
693
789
  ...e.connector,
@@ -1177,6 +1273,165 @@ function shapePath(e) {
1177
1273
  return path;
1178
1274
  }
1179
1275
 
1276
+ // src/elbow.ts
1277
+ var MARGIN = 16;
1278
+ var BEND = 24;
1279
+ var SMALLEST = 24;
1280
+ var MOST = 48;
1281
+ function obstacle(e) {
1282
+ if (e.kind === "connector" || e.kind === "text" || e.kind === "freehand") return null;
1283
+ if (e.kind === "path" && !e.extension) return null;
1284
+ if (e.style.opacity !== null && e.style.opacity <= 0) return null;
1285
+ const r = rotatedBounds(e);
1286
+ return r.width < SMALLEST || r.height < SMALLEST ? null : r;
1287
+ }
1288
+ function crosses(points, r) {
1289
+ const x1 = r.x + r.width, y1 = r.y + r.height;
1290
+ for (let k = 1; k < points.length; k++) {
1291
+ const p = points[k - 1], q = points[k];
1292
+ if (p.y === q.y) {
1293
+ 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;
1294
+ } else if (p.x === q.x) {
1295
+ 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;
1296
+ }
1297
+ }
1298
+ return false;
1299
+ }
1300
+ var padded = (r) => ({ x: r.x - MARGIN, y: r.y - MARGIN, width: r.width + 2 * MARGIN, height: r.height + 2 * MARGIN });
1301
+ var inside = (p, r) => r.x < p.x && p.x < r.x + r.width && r.y < p.y && p.y < r.y + r.height;
1302
+ var memo = /* @__PURE__ */ new Map();
1303
+ function avoiding(plain, a, b, startVertical, endVertical, elements, except) {
1304
+ const usable = (r) => !inside(a, padded(r)) && !inside(b, padded(r));
1305
+ const all = [];
1306
+ let crossed = false;
1307
+ for (const e of elements) {
1308
+ if (except.has(e.id)) continue;
1309
+ const r = obstacle(e);
1310
+ if (!r || !usable(r)) continue;
1311
+ all.push(r);
1312
+ if (!crossed && crosses(plain, r)) crossed = true;
1313
+ }
1314
+ if (!crossed) return plain;
1315
+ 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])]);
1316
+ const hit = memo.get(key);
1317
+ if (hit) return hit;
1318
+ const out = route(plain, a, b, startVertical, endVertical, all);
1319
+ if (memo.size >= 512) memo.clear();
1320
+ memo.set(key, out);
1321
+ return out;
1322
+ }
1323
+ function route(plain, a, b, startVertical, endVertical, usable) {
1324
+ let chosen = usable.map((_, i) => i).filter((i) => crosses(plain, usable[i]));
1325
+ if (chosen.length === 0) return plain;
1326
+ for (let round = 0; round < 4; round++) {
1327
+ if (chosen.length > MOST) return plain;
1328
+ const path = grid(a, b, startVertical, endVertical, chosen.map((i) => padded(usable[i])));
1329
+ if (!path) return plain;
1330
+ const more = usable.map((_, i) => i).filter((i) => !chosen.includes(i) && crosses(path, usable[i]));
1331
+ if (more.length === 0) return path;
1332
+ chosen = [...chosen, ...more].sort((p, q) => p - q);
1333
+ }
1334
+ return plain;
1335
+ }
1336
+ function grid(a, b, startVertical, endVertical, boxes) {
1337
+ let xs = [a.x, b.x, (a.x + b.x) / 2], ys = [a.y, b.y, (a.y + b.y) / 2];
1338
+ for (const r of boxes) {
1339
+ xs.push(r.x, r.x + r.width);
1340
+ ys.push(r.y, r.y + r.height);
1341
+ }
1342
+ xs = [...new Set(xs)].sort((p, q) => p - q);
1343
+ ys = [...new Set(ys)].sort((p, q) => p - q);
1344
+ const nx = xs.length, ny = ys.length;
1345
+ const ia = xs.indexOf(a.x), ja = ys.indexOf(a.y), ib = xs.indexOf(b.x), jb = ys.indexOf(b.y);
1346
+ if (ia < 0 || ja < 0 || ib < 0 || jb < 0) return null;
1347
+ const at = new Map(xs.map((x, k) => [x, k])), bt = new Map(ys.map((y, k) => [y, k]));
1348
+ const inner = new Uint8Array(nx * ny), acrossBlocked = new Uint8Array(nx * ny), downBlocked = new Uint8Array(nx * ny);
1349
+ for (const r of boxes) {
1350
+ 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);
1351
+ if (i0 === void 0 || i1 === void 0 || j0 === void 0 || j1 === void 0) continue;
1352
+ for (let i = i0; i < i1; i++) for (let j = j0; j < j1; j++) {
1353
+ if (j > j0) acrossBlocked[i * ny + j] = 1;
1354
+ if (i > i0) downBlocked[i * ny + j] = 1;
1355
+ if (i > i0 && j > j0) inner[i * ny + j] = 1;
1356
+ }
1357
+ }
1358
+ const blocked = (i, j) => inner[i * ny + j] === 1;
1359
+ const across = (i0, i1, j) => acrossBlocked[Math.min(i0, i1) * ny + j] === 1;
1360
+ const down = (j0, j1, i) => downBlocked[i * ny + Math.min(j0, j1)] === 1;
1361
+ const count = nx * ny * 2;
1362
+ const dist = new Array(count).fill(Infinity), prev = new Array(count).fill(-1);
1363
+ const done = new Array(count).fill(false);
1364
+ const heap = new Frontier();
1365
+ const first = (ia * ny + ja) * 2 + (startVertical ? 1 : 0);
1366
+ dist[first] = 0;
1367
+ heap.push(0, first);
1368
+ const want = (ib * ny + jb) * 2 + (endVertical ? 1 : 0);
1369
+ for (let top = heap.pop(); top; top = heap.pop()) {
1370
+ const [cost, s] = top;
1371
+ if (done[s]) continue;
1372
+ done[s] = true;
1373
+ if (s === want) break;
1374
+ const node = Math.floor(s / 2), axis = s % 2, i = Math.floor(node / ny), j = node % ny;
1375
+ for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
1376
+ const ni = i + di, nj = j + dj;
1377
+ if (ni < 0 || ni >= nx || nj < 0 || nj >= ny || blocked(ni, nj)) continue;
1378
+ const turn = di !== 0 ? 0 : 1;
1379
+ if (di !== 0 ? across(i, ni, j) : down(j, nj, i)) continue;
1380
+ const length = Math.abs(xs[ni] - xs[i]) + Math.abs(ys[nj] - ys[j]);
1381
+ const c = cost + length + (turn !== axis ? BEND : 0);
1382
+ const t = (ni * ny + nj) * 2 + turn;
1383
+ if (c < dist[t]) {
1384
+ dist[t] = c;
1385
+ prev[t] = s;
1386
+ heap.push(c, t);
1387
+ }
1388
+ }
1389
+ }
1390
+ if (!done[want]) return null;
1391
+ const points = [];
1392
+ for (let s = want; s >= 0; s = prev[s]) {
1393
+ const node = Math.floor(s / 2);
1394
+ points.push({ x: xs[Math.floor(node / ny)], y: ys[node % ny] });
1395
+ }
1396
+ return points.reverse();
1397
+ }
1398
+ var Frontier = class {
1399
+ constructor() {
1400
+ __publicField(this, "heap", []);
1401
+ __publicField(this, "next", 0);
1402
+ }
1403
+ less(i, j) {
1404
+ const a = this.heap[i], b = this.heap[j];
1405
+ return a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
1406
+ }
1407
+ push(cost, state) {
1408
+ this.heap.push([cost, this.next++, state]);
1409
+ for (let i = this.heap.length - 1; i > 0; ) {
1410
+ const up = i - 1 >> 1;
1411
+ if (!this.less(i, up)) break;
1412
+ [this.heap[i], this.heap[up]] = [this.heap[up], this.heap[i]];
1413
+ i = up;
1414
+ }
1415
+ }
1416
+ pop() {
1417
+ if (this.heap.length === 0) return null;
1418
+ const top = this.heap[0], last = this.heap.pop();
1419
+ if (this.heap.length > 0) {
1420
+ this.heap[0] = last;
1421
+ for (let i = 0; ; ) {
1422
+ const l = 2 * i + 1, r = l + 1;
1423
+ let m = i;
1424
+ if (l < this.heap.length && this.less(l, m)) m = l;
1425
+ if (r < this.heap.length && this.less(r, m)) m = r;
1426
+ if (m === i) break;
1427
+ [this.heap[i], this.heap[m]] = [this.heap[m], this.heap[i]];
1428
+ i = m;
1429
+ }
1430
+ }
1431
+ return [top[0], top[2]];
1432
+ }
1433
+ };
1434
+
1180
1435
  // src/connector.ts
1181
1436
  function portPoint(shape, port) {
1182
1437
  const r = frame(shape);
@@ -1320,7 +1575,7 @@ function routeThrough(p, c) {
1320
1575
  }
1321
1576
  }
1322
1577
  }
1323
- function route(e, index) {
1578
+ function route2(e, index) {
1324
1579
  const c = e.connector;
1325
1580
  if (!c) return { kind: "polyline", points: [] };
1326
1581
  const [a, b] = endpoints(e, index);
@@ -1336,16 +1591,19 @@ function route(e, index) {
1336
1591
  case "elbow": {
1337
1592
  const startV = vertical(c.start.port) ?? !horizontal;
1338
1593
  const endV = vertical(c.end.port) ?? !horizontal;
1594
+ let plain;
1339
1595
  if (startV && endV) {
1340
1596
  const my = a.y + dy / 2;
1341
- return { kind: "polyline", points: [a, { x: a.x, y: my }, { x: b.x, y: my }, b] };
1342
- }
1343
- if (!startV && !endV) {
1597
+ plain = [a, { x: a.x, y: my }, { x: b.x, y: my }, b];
1598
+ } else if (!startV && !endV) {
1344
1599
  const mx = a.x + dx / 2;
1345
- return { kind: "polyline", points: [a, { x: mx, y: a.y }, { x: mx, y: b.y }, b] };
1346
- }
1347
- if (startV) return { kind: "polyline", points: [a, { x: a.x, y: b.y }, b] };
1348
- return { kind: "polyline", points: [a, { x: b.x, y: a.y }, b] };
1600
+ plain = [a, { x: mx, y: a.y }, { x: mx, y: b.y }, b];
1601
+ } else if (startV) plain = [a, { x: a.x, y: b.y }, b];
1602
+ else plain = [a, { x: b.x, y: a.y }, b];
1603
+ if (c.avoid !== true) return { kind: "polyline", points: plain };
1604
+ const ends = new Set([c.start.elementID, c.end.elementID].filter((x) => x !== null));
1605
+ const routed = avoiding(plain, a, b, startV, endV, index.values(), ends);
1606
+ return { kind: "polyline", points: routed === plain ? plain : simplified(routed) };
1349
1607
  }
1350
1608
  }
1351
1609
  }
@@ -1356,7 +1614,7 @@ function arrowhead(tip, from, size) {
1356
1614
  return [wing(spread), wing(-spread)];
1357
1615
  }
1358
1616
  function connectorBounds(e, index) {
1359
- const pts = routePoints(route(e, index));
1617
+ const pts = routePoints(route2(e, index));
1360
1618
  if (pts.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
1361
1619
  let x0 = pts[0].x, y0 = pts[0].y, x1 = x0, y1 = y0;
1362
1620
  for (const p of pts) {
@@ -1368,7 +1626,7 @@ function connectorBounds(e, index) {
1368
1626
  return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
1369
1627
  }
1370
1628
  function midpoint(e, index) {
1371
- const pts = routePoints(route(e, index));
1629
+ const pts = routePoints(route2(e, index));
1372
1630
  if (pts.length <= 1) return pts[0] ?? { x: 0, y: 0 };
1373
1631
  const lengths = [];
1374
1632
  let total = 0;
@@ -1517,7 +1775,7 @@ function graphOf(frame2) {
1517
1775
  graphs.set(els, { members: [...els], graph });
1518
1776
  return graph;
1519
1777
  }
1520
- var Frontier = class {
1778
+ var Frontier2 = class {
1521
1779
  constructor() {
1522
1780
  __publicField(this, "heap", []);
1523
1781
  }
@@ -1571,7 +1829,7 @@ function grow({ shapes: shapes2, next }, from, offline, until) {
1571
1829
  const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
1572
1830
  let counter = 1;
1573
1831
  const tree = new FlowTree(from);
1574
- const open = new Frontier();
1832
+ const open = new Frontier2();
1575
1833
  open.push(from, 0, 0);
1576
1834
  while (open.size > 0) {
1577
1835
  const { id: n, order: o } = open.pop();
@@ -1677,6 +1935,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1677
1935
  return pts;
1678
1936
  };
1679
1937
  const metrics2 = emptyMetrics();
1938
+ const routes = /* @__PURE__ */ new Map();
1680
1939
  for (const e of frame2.elements) {
1681
1940
  if (e.kind === "connector" && !e.connector) continue;
1682
1941
  metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
@@ -1688,7 +1947,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1688
1947
  if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
1689
1948
  }
1690
1949
  const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
1691
- const route2 = (tree, goal) => {
1950
+ const route3 = (tree, goal) => {
1692
1951
  const hops = [], shapes2 = [];
1693
1952
  let at = goal;
1694
1953
  for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
@@ -1700,7 +1959,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1700
1959
  return { hops: hops.reverse(), shapes: shapes2.reverse() };
1701
1960
  };
1702
1961
  const way = (hops, shapes2) => {
1703
- const points = [], inside = [], stretches = [];
1962
+ const points = [], inside2 = [], stretches = [];
1704
1963
  let length = 0;
1705
1964
  hops.forEach(([id, back], k) => {
1706
1965
  const c = byID.get(id);
@@ -1711,7 +1970,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1711
1970
  if (last) {
1712
1971
  const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
1713
1972
  const shape = byID.get(shapes2[k]);
1714
- inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
1973
+ inside2.push([length, length + gap, shape ? delayOf(shape) : 0]);
1715
1974
  length += gap;
1716
1975
  }
1717
1976
  const start2 = length;
@@ -1719,7 +1978,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1719
1978
  stretches.push([id, start2, length]);
1720
1979
  points.push(...line2);
1721
1980
  });
1722
- return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
1981
+ return length > 0 ? { line: new Polyline(points), length, inside: inside2, stretches } : null;
1723
1982
  };
1724
1983
  const along = (w, elapsedAt, speed) => {
1725
1984
  let elapsed = elapsedAt, d = 0;
@@ -1760,6 +2019,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1760
2019
  if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
1761
2020
  const source = byID.get(flow.from);
1762
2021
  const base2 = source ? baseTree(flow.from) : null;
2022
+ routes.set(flow.id, []);
1763
2023
  if (!source || !base2) continue;
1764
2024
  const volume = amountIn(flow, values);
1765
2025
  const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
@@ -1774,7 +2034,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1774
2034
  const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
1775
2035
  const goal2 = tree?.goals(candidates, false)[0];
1776
2036
  if (!tree || goal2 === void 0) break;
1777
- const r = route2(tree, goal2);
2037
+ const r = route3(tree, goal2);
1778
2038
  const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
1779
2039
  const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
1780
2040
  const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
@@ -1787,7 +2047,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1787
2047
  let at = flow.from;
1788
2048
  const goal = base2.goals(candidates, false)[0];
1789
2049
  if (goal !== void 0) {
1790
- const r = route2(base2, goal), done = full();
2050
+ const r = route3(base2, goal), done = full();
1791
2051
  const sequence = [[r.shapes[0], r.shapes[0]]];
1792
2052
  r.hops.forEach(([id], k) => {
1793
2053
  sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
@@ -1816,6 +2076,13 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1816
2076
  else waiting2.push([at, left]);
1817
2077
  }
1818
2078
  waitingBy.set(flow.id, waiting2);
2079
+ routes.set(flow.id, placements.map((p) => ({
2080
+ goal: p.goal,
2081
+ hops: p.hops,
2082
+ amount: p.amount,
2083
+ waits: p.waits === true,
2084
+ cost: p.hops.reduce((s, [id]) => s + routeCost(byID.get(id)?.connector?.cost ?? null), 0)
2085
+ })));
1819
2086
  for (const p of placements) {
1820
2087
  for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
1821
2088
  m.throughput += p.amount;
@@ -1898,7 +2165,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1898
2165
  const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
1899
2166
  const goal = tree?.goals(flow.to, false)[0];
1900
2167
  if (!tree || goal === void 0) break;
1901
- const r = route2(tree, goal);
2168
+ const r = route3(tree, goal);
1902
2169
  const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
1903
2170
  const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
1904
2171
  const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
@@ -1953,7 +2220,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
1953
2220
  if (c === null || !Number.isFinite(c) || !m) continue;
1954
2221
  m.load = c > 0 ? m.throughput / c : 1;
1955
2222
  }
1956
- return { packets, metrics: metrics2 };
2223
+ return { packets, metrics: metrics2, routes };
1957
2224
  }
1958
2225
  var BLANK = {
1959
2226
  seed: 1,
@@ -2047,16 +2314,16 @@ function finishing(elements, metrics2, values, frame2, flows, since = Infinity,
2047
2314
  }
2048
2315
  var gaugeKey = (id) => "gauge|" + id;
2049
2316
  var GAUGE_EASE = 1;
2050
- var hex = (h) => {
2317
+ var hex2 = (h) => {
2051
2318
  const v = parseInt(h.slice(1), 16);
2052
2319
  return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
2053
2320
  };
2054
- var GAUGE_GREEN = hex("#16a34a");
2055
- var GAUGE_AMBER = hex("#d97706");
2056
- var GAUGE_RED = hex("#dc2626");
2057
- var GAUGE_BLUE = hex("#2f6fdf");
2058
- var GAUGE_TRACK = hex("#e5e7eb");
2059
- var GAUGE_GRAY = hex("#9ca3af");
2321
+ var GAUGE_GREEN = hex2("#16a34a");
2322
+ var GAUGE_AMBER = hex2("#d97706");
2323
+ var GAUGE_RED = hex2("#dc2626");
2324
+ var GAUGE_BLUE = hex2("#2f6fdf");
2325
+ var GAUGE_TRACK = hex2("#e5e7eb");
2326
+ var GAUGE_GRAY = hex2("#9ca3af");
2060
2327
  function formatted(metric, v) {
2061
2328
  if (v === null || !Number.isFinite(v)) return "\u2013";
2062
2329
  switch (metric) {
@@ -2342,17 +2609,17 @@ function clusterStrokes(c, angle2, size) {
2342
2609
  });
2343
2610
  }
2344
2611
  function contains(contours, p) {
2345
- let inside = false;
2612
+ let inside2 = false;
2346
2613
  for (const poly of contours) {
2347
2614
  if (poly.length < 3) continue;
2348
2615
  let k = poly.length - 1;
2349
2616
  for (let i = 0; i < poly.length; i++) {
2350
2617
  const a = poly[i], b = poly[k];
2351
- 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;
2618
+ 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;
2352
2619
  k = i;
2353
2620
  }
2354
2621
  }
2355
- return inside;
2622
+ return inside2;
2356
2623
  }
2357
2624
  function distance(p, contours) {
2358
2625
  let best = Infinity;
@@ -2629,7 +2896,7 @@ function connectorParts(e, index, page) {
2629
2896
  const c = e.connector;
2630
2897
  if (!c) return [new Path(), new Path()];
2631
2898
  const [a, b] = endpoints(e, index);
2632
- const r = route(e, index);
2899
+ const r = route2(e, index);
2633
2900
  const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
2634
2901
  const key = [
2635
2902
  e.seed,
@@ -2717,7 +2984,7 @@ function linesBeneath(e, page, index) {
2717
2984
  }
2718
2985
  const count = f.first.get(e.id) ?? f.under.length;
2719
2986
  while (f.lines.length < count) {
2720
- const points = routePoints(route(f.under[f.lines.length], index));
2987
+ const points = routePoints(route2(f.under[f.lines.length], index));
2721
2988
  f.lines.push({ points, box: boxOf(points) });
2722
2989
  }
2723
2990
  return { lines: f.lines, count };
@@ -3021,7 +3288,7 @@ function followSpan(track, t) {
3021
3288
  return f;
3022
3289
  }
3023
3290
  function tracing(e, page, index = indexOf(page)) {
3024
- if (e.kind === "connector") return new Polyline(routePoints(route(e, index)));
3291
+ if (e.kind === "connector") return new Polyline(routePoints(route2(e, index)));
3025
3292
  const clean = cloneElement(e);
3026
3293
  clean.style.roughness = 0;
3027
3294
  const pts = outline(clean).contours()[0] ?? [];
@@ -3301,10 +3568,10 @@ var Expansions = class {
3301
3568
  this.posing = posing;
3302
3569
  }
3303
3570
  };
3304
- function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo) {
3571
+ function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo2) {
3305
3572
  if (!hasInstances(page)) return page;
3306
3573
  const budget = { left: MAX_ELEMENTS };
3307
- const m = memo && memo.page === page && memo.t === t && memo.posing === posing ? memo : void 0;
3574
+ const m = memo2 && memo2.page === page && memo2.t === t && memo2.posing === posing ? memo2 : void 0;
3308
3575
  return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
3309
3576
  }
3310
3577
  function symbolTable(page) {
@@ -3312,7 +3579,7 @@ function symbolTable(page) {
3312
3579
  for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
3313
3580
  return table;
3314
3581
  }
3315
- function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo) {
3582
+ function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo2) {
3316
3583
  const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
3317
3584
  if (instances.size === 0) {
3318
3585
  budget.left -= elements.length;
@@ -3338,7 +3605,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
3338
3605
  continue;
3339
3606
  }
3340
3607
  const override0 = stack.length === 0 ? times.get(e.id) : void 0;
3341
- const was = memo?.made.get(e);
3608
+ const was = memo2?.made.get(e);
3342
3609
  if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
3343
3610
  budget.left -= was.before - was.after;
3344
3611
  for (const x of was.out) out.push(x);
@@ -3360,7 +3627,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
3360
3627
  layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
3361
3628
  };
3362
3629
  for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
3363
- if (memo && stack.length === 0 && !was) memo.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
3630
+ if (memo2 && stack.length === 0 && !was) memo2.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
3364
3631
  }
3365
3632
  return out;
3366
3633
  }
@@ -3413,8 +3680,8 @@ function mix2(x) {
3413
3680
  return z ^ z >> 31n;
3414
3681
  }
3415
3682
  function halves(id) {
3416
- const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
3417
- return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
3683
+ const hex3 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
3684
+ return [BigInt(`0x${hex3.slice(0, 16)}`), BigInt(`0x${hex3.slice(16)}`)];
3418
3685
  }
3419
3686
  function derivedID(instance, inner) {
3420
3687
  const [a, b] = halves(instance), [c, d] = halves(inner);
@@ -3742,12 +4009,23 @@ function inline(src, style2 = {}) {
3742
4009
  i += m[0].length;
3743
4010
  continue;
3744
4011
  }
3745
- if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
4012
+ if (m = /^(\*\*\*|___)(?=\S)([\s\S]*?\S)\1(?!\*|_)/.exec(rest)) {
3746
4013
  emit();
3747
- out.push(...inline(m[2], { ...style2, bold: true }));
4014
+ out.push(...inline(m[2], { ...style2, bold: true, italic: true }));
3748
4015
  i += m[0].length;
3749
4016
  continue;
3750
4017
  }
4018
+ if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
4019
+ let body = m[2], length = m[0].length;
4020
+ if (m[1] === "**" && rest[length] === "*" && (body.replace(/\*\*/g, "").match(/\*/g) ?? []).length % 2 === 1) {
4021
+ body += "*";
4022
+ length++;
4023
+ }
4024
+ emit();
4025
+ out.push(...inline(body, { ...style2, bold: true }));
4026
+ i += length;
4027
+ continue;
4028
+ }
3751
4029
  if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
3752
4030
  emit();
3753
4031
  out.push(...inline(m[2], { ...style2, italic: true }));
@@ -4131,20 +4409,6 @@ function sameStyle(a, b) {
4131
4409
  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;
4132
4410
  }
4133
4411
 
4134
- // src/theme.ts
4135
- var rgb = (r, g, b) => ({ r, g, b, a: 1 });
4136
- var themes = [
4137
- { id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
4138
- { id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
4139
- { id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
4140
- { id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
4141
- { id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
4142
- { id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
4143
- ];
4144
- function themeNamed(id) {
4145
- return themes.find((t) => t.id === id) ?? null;
4146
- }
4147
-
4148
4412
  // src/svg.ts
4149
4413
  function bounds(page, padding = 24) {
4150
4414
  if (hasInstances(page)) return bounds(expandingSymbols(page, 0), padding);
@@ -4210,7 +4474,7 @@ function svgOpen(b, background, paint) {
4210
4474
  }
4211
4475
  function svgParts(page, background, fixed, links = false, only) {
4212
4476
  if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
4213
- const theme = themeNamed(page.theme);
4477
+ const theme = pageTheme(page);
4214
4478
  const bg = background ? theme?.background ?? background : null;
4215
4479
  const ink = theme?.ink ?? black;
4216
4480
  const d = { ...page, elements: page.elements.map((e) => resolvingInk(e, ink)) };
@@ -4246,7 +4510,7 @@ ${body}</a>
4246
4510
  }
4247
4511
  function svg(page, background = { r: 1, g: 1, b: 1, a: 1 }, fixed) {
4248
4512
  const { bounds: b, parts } = svgParts(page, background, fixed, true);
4249
- return svgOpen(b, themeNamed(page.theme)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
4513
+ return svgOpen(b, pageTheme(page)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
4250
4514
  }
4251
4515
  function labelRect(e, index) {
4252
4516
  const m = midpoint(e, index);
@@ -4340,7 +4604,7 @@ function svgElement(e, index, labelBackground, page) {
4340
4604
  }
4341
4605
  s += `<path d="${outline(e).data()}" fill="none" ${strokeAttrs}/>
4342
4606
  `;
4343
- if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" ? pathLabelBox(e) : frame(e), e, true);
4607
+ if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" || e.kind === "rectangle" ? pathLabelBox(e) : frame(e), e, true);
4344
4608
  return s;
4345
4609
  }
4346
4610
  default:
@@ -4568,7 +4832,7 @@ var Playback = class {
4568
4832
  this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
4569
4833
  this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
4570
4834
  this.state = { ...page.variables ?? {} };
4571
- const theme = themeNamed(page.theme);
4835
+ const theme = pageTheme(page);
4572
4836
  this.background = theme?.background ?? WHITE;
4573
4837
  this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
4574
4838
  const ns = "http://www.w3.org/2000/svg";
@@ -5222,7 +5486,7 @@ function inspectFrame(page, t) {
5222
5486
  const [a, b] = endpoints(e, index);
5223
5487
  d.start = pt(a);
5224
5488
  d.end = pt(b);
5225
- d.route = routePoints(route(e, index)).map(pt);
5489
+ d.route = routePoints(route2(e, index)).map(pt);
5226
5490
  }
5227
5491
  return d;
5228
5492
  });
@@ -5261,7 +5525,7 @@ function render(source, options = {}) {
5261
5525
  function isFile(v) {
5262
5526
  return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
5263
5527
  }
5264
- var VERSION = "0.9.0";
5528
+ var VERSION = "0.10.0";
5265
5529
  export {
5266
5530
  BOIL_DRAWINGS,
5267
5531
  BUNDLED_FONTS,
@@ -5297,6 +5561,8 @@ export {
5297
5561
  luminance,
5298
5562
  orderedElements,
5299
5563
  pageIndex,
5564
+ pageTheme,
5565
+ paletteColor,
5300
5566
  parseDottie,
5301
5567
  plan,
5302
5568
  posterTime,