@phin-tech/dottie 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PARITY.md CHANGED
@@ -22,6 +22,8 @@ fixture, so the gap is visible rather than silent.
22
22
  | Pages | Supported | [edge/themes](../fixtures/edge/themes.dottie) | `page` attribute or option: name or number |
23
23
  | Connectors: straight, curved, elbow | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
24
24
  | Ports and edge-following, rotated shapes, free ends | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
25
+ | Connectors and labels on outlines (ADR 0001) | Supported | [edge/extensions](../fixtures/edge/extensions.dottie) | Edge-following meets a `.path`'s actual outline, not its box (ray against 16-step flattened curves). Endpoints pinned with `portIndex` attach to the element's `ports`. A path's label goes in its `labelRect`. |
26
+ | Extension shapes as stored (ADR 0001) | Supported | [edge/extensions](../fixtures/edge/extensions.dottie) | Drawn from the stored path with no extension installed. The `extension` field is kept as metadata and nothing runs. Parameters, commands and handles are Dottie-only. |
25
27
  | Arrowheads: arrow, triangle, dot, bar | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
26
28
  | Connector labels | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | Label boxes sized with CoreText's measurements. The player paints them on the page color, like the app; Dottie's flipbook export leaves them unpainted |
27
29
  | Keys and eases (linear, in, out, in-out, hold, spring, bounce, elastic) | Supported | [edge/timeline](../fixtures/edge/timeline.dottie), [edge/eases](../fixtures/edge/eases.dottie) | Unknown ease names play linear, with a warning |
@@ -43,6 +45,7 @@ fixture, so the gap is visible rather than silent.
43
45
  | Symbol parameters (`{{name}}` in a symbol's text, filled per copy) | Supported | [edge/params](../fixtures/edge/params.dottie) | Defaults, undeclared names left as written, values passed into nested copies, filled before text types out |
44
46
  | Copies that play when clicked | Supported | [edge/buttons](../fixtures/edge/buttons.dottie) | `<dottie-player>` only: the HTML export is drawn ahead of time and shows them at rest |
45
47
  | Clicks: play a scene, open a link, hover looks, keyboard | Supported | [edge/clicks](../fixtures/edge/clicks.dottie) | As the HTML export does; only http, https and mailto links are followed |
48
+ | Links out of the drawing (`url`) | Supported | [edge/links](../fixtures/edge/links.dottie) | A web or mail `url` opens when clicked if there is no `click`, and SVG export wraps it in `<a>`; a link to another drawing does nothing on the web |
46
49
  | Links between shapes and pages | Partial | [edge/clicks](../fixtures/edge/clicks.dottie) | A link to a shape on another page shows that page; one on the same page does nothing. The HTML export holds one page, so it has neither |
47
50
  | Editing | Missing | | |
48
51
  | Ticking task boxes in notes | Missing | | Drawn, not clickable |
package/dist/compile.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! dottie.js 0.5.4 | Apache-2.0 | https://github.com/phin-tech/dottie */
1
+ /*! dottie.js 0.6.1 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -33,6 +33,7 @@ var KNOWN_FIELDS = {
33
33
  "evenOdd",
34
34
  "rotation",
35
35
  "link",
36
+ "url",
36
37
  "anchor",
37
38
  "click",
38
39
  "hover",
@@ -41,12 +42,18 @@ var KNOWN_FIELDS = {
41
42
  "offset",
42
43
  "args",
43
44
  "image",
44
- "reveal"
45
+ "reveal",
46
+ "ports",
47
+ "labelRect",
48
+ "extension"
45
49
  ],
46
50
  strokePoint: ["x", "y"],
47
51
  subpath: ["closed", "coords"],
48
52
  inlineImage: ["data", "format"],
49
- endpoint: ["elementID", "port", "x", "y"],
53
+ endpoint: ["elementID", "port", "portIndex", "x", "y"],
54
+ extensionRef: ["id", "shape", "version", "params"],
55
+ normalizedPoint: ["x", "y"],
56
+ normalizedRect: ["x", "y", "width", "height"],
50
57
  connector: ["start", "end", "routing", "startHead", "endHead"],
51
58
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
52
59
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -129,9 +136,11 @@ var Reader = class {
129
136
  endpoint(v) {
130
137
  const e = isObj(v) ? v : {};
131
138
  const port = str(e.port);
139
+ const portIndex = num(e.portIndex);
132
140
  return {
133
141
  elementID: uuid(e.elementID) ?? null,
134
142
  port: port && ["top", "right", "bottom", "left"].includes(port) ? port : null,
143
+ portIndex: portIndex !== void 0 && Number.isInteger(portIndex) && portIndex >= 0 ? portIndex : null,
135
144
  x: num(e.x) ?? 0,
136
145
  y: num(e.y) ?? 0
137
146
  };
@@ -196,6 +205,7 @@ var Reader = class {
196
205
  evenOdd: bool(v.evenOdd) ?? null,
197
206
  rotation: num(v.rotation) ?? null,
198
207
  link: uuid(v.link) ?? null,
208
+ url: str(v.url) ?? null,
199
209
  anchor: str(v.anchor) ?? null,
200
210
  click: this.click(v.click),
201
211
  hover: this.hover(v.hover),
@@ -204,9 +214,36 @@ var Reader = class {
204
214
  offset: num(v.offset) ?? null,
205
215
  args: strings(v.args),
206
216
  image: this.image(v.image, images),
207
- reveal: num(v.reveal) ?? null
217
+ reveal: num(v.reveal) ?? null,
218
+ ports: Array.isArray(v.ports) ? v.ports.map((p) => {
219
+ const o = isObj(p) ? p : {};
220
+ return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
221
+ }) : null,
222
+ labelRect: this.labelRect(v.labelRect),
223
+ extension: this.extension(v.extension)
208
224
  };
209
225
  }
226
+ labelRect(v) {
227
+ if (!isObj(v)) return null;
228
+ this.fields(v, "normalizedRect");
229
+ return { x: num(v.x) ?? 0, y: num(v.y) ?? 0, width: num(v.width) ?? 0, height: num(v.height) ?? 0 };
230
+ }
231
+ extension(v) {
232
+ if (!isObj(v)) return null;
233
+ this.fields(v, "extensionRef");
234
+ const id = str(v.id), shape = str(v.shape), version = str(v.version);
235
+ if (id === void 0 || shape === void 0 || version === void 0) {
236
+ this.warn("Skipped an extension without an id, shape or version");
237
+ return null;
238
+ }
239
+ const params = {};
240
+ if (isObj(v.params)) {
241
+ for (const [k, p] of Object.entries(v.params)) {
242
+ if (typeof p === "number" || typeof p === "boolean" || typeof p === "string") params[k] = p;
243
+ }
244
+ }
245
+ return { id, shape, version, params };
246
+ }
210
247
  click(v) {
211
248
  if (!isObj(v)) return null;
212
249
  this.fields(v, "click");
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! dottie.js 0.5.4 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
+ /*! dottie.js 0.6.1 | Apache-2.0 | https://github.com/phin-tech/dottie */
3
3
  var __defProp = Object.defineProperty;
4
4
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
5
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -38,6 +38,7 @@ var KNOWN_FIELDS = {
38
38
  "evenOdd",
39
39
  "rotation",
40
40
  "link",
41
+ "url",
41
42
  "anchor",
42
43
  "click",
43
44
  "hover",
@@ -46,12 +47,18 @@ var KNOWN_FIELDS = {
46
47
  "offset",
47
48
  "args",
48
49
  "image",
49
- "reveal"
50
+ "reveal",
51
+ "ports",
52
+ "labelRect",
53
+ "extension"
50
54
  ],
51
55
  strokePoint: ["x", "y"],
52
56
  subpath: ["closed", "coords"],
53
57
  inlineImage: ["data", "format"],
54
- endpoint: ["elementID", "port", "x", "y"],
58
+ endpoint: ["elementID", "port", "portIndex", "x", "y"],
59
+ extensionRef: ["id", "shape", "version", "params"],
60
+ normalizedPoint: ["x", "y"],
61
+ normalizedRect: ["x", "y", "width", "height"],
55
62
  connector: ["start", "end", "routing", "startHead", "endHead"],
56
63
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
57
64
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -134,9 +141,11 @@ var Reader = class {
134
141
  endpoint(v) {
135
142
  const e = isObj(v) ? v : {};
136
143
  const port = str(e.port);
144
+ const portIndex = num(e.portIndex);
137
145
  return {
138
146
  elementID: uuid(e.elementID) ?? null,
139
147
  port: port && ["top", "right", "bottom", "left"].includes(port) ? port : null,
148
+ portIndex: portIndex !== void 0 && Number.isInteger(portIndex) && portIndex >= 0 ? portIndex : null,
140
149
  x: num(e.x) ?? 0,
141
150
  y: num(e.y) ?? 0
142
151
  };
@@ -201,6 +210,7 @@ var Reader = class {
201
210
  evenOdd: bool(v.evenOdd) ?? null,
202
211
  rotation: num(v.rotation) ?? null,
203
212
  link: uuid(v.link) ?? null,
213
+ url: str(v.url) ?? null,
204
214
  anchor: str(v.anchor) ?? null,
205
215
  click: this.click(v.click),
206
216
  hover: this.hover(v.hover),
@@ -209,9 +219,36 @@ var Reader = class {
209
219
  offset: num(v.offset) ?? null,
210
220
  args: strings(v.args),
211
221
  image: this.image(v.image, images),
212
- reveal: num(v.reveal) ?? null
222
+ reveal: num(v.reveal) ?? null,
223
+ ports: Array.isArray(v.ports) ? v.ports.map((p) => {
224
+ const o = isObj(p) ? p : {};
225
+ return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
226
+ }) : null,
227
+ labelRect: this.labelRect(v.labelRect),
228
+ extension: this.extension(v.extension)
213
229
  };
214
230
  }
231
+ labelRect(v) {
232
+ if (!isObj(v)) return null;
233
+ this.fields(v, "normalizedRect");
234
+ return { x: num(v.x) ?? 0, y: num(v.y) ?? 0, width: num(v.width) ?? 0, height: num(v.height) ?? 0 };
235
+ }
236
+ extension(v) {
237
+ if (!isObj(v)) return null;
238
+ this.fields(v, "extensionRef");
239
+ const id = str(v.id), shape = str(v.shape), version = str(v.version);
240
+ if (id === void 0 || shape === void 0 || version === void 0) {
241
+ this.warn("Skipped an extension without an id, shape or version");
242
+ return null;
243
+ }
244
+ const params = {};
245
+ if (isObj(v.params)) {
246
+ for (const [k, p] of Object.entries(v.params)) {
247
+ if (typeof p === "number" || typeof p === "boolean" || typeof p === "string") params[k] = p;
248
+ }
249
+ }
250
+ return { id, shape, version, params };
251
+ }
215
252
  click(v) {
216
253
  if (!isObj(v)) return null;
217
254
  this.fields(v, "click");
@@ -491,7 +528,7 @@ function compile(source2, runtime, options2 = {}) {
491
528
  }
492
529
 
493
530
  // src/index.ts
494
- var VERSION = "0.5.4";
531
+ var VERSION = "0.6.1";
495
532
 
496
533
  // src/cli.ts
497
534
  var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.
@@ -1,4 +1,4 @@
1
- /*! dottie.js 0.5.4 | Apache-2.0 | https://github.com/phin-tech/dottie */
1
+ /*! dottie.js 0.6.1 | Apache-2.0 | https://github.com/phin-tech/dottie */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
4
4
  var __export = (target, all) => {
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  themeNamed: () => themeNamed,
57
57
  themes: () => themes,
58
58
  transientElements: () => transientElements,
59
+ webLink: () => webLink,
59
60
  withRoughness: () => withRoughness
60
61
  });
61
62
 
@@ -89,6 +90,7 @@ var KNOWN_FIELDS = {
89
90
  "evenOdd",
90
91
  "rotation",
91
92
  "link",
93
+ "url",
92
94
  "anchor",
93
95
  "click",
94
96
  "hover",
@@ -97,12 +99,18 @@ var KNOWN_FIELDS = {
97
99
  "offset",
98
100
  "args",
99
101
  "image",
100
- "reveal"
102
+ "reveal",
103
+ "ports",
104
+ "labelRect",
105
+ "extension"
101
106
  ],
102
107
  strokePoint: ["x", "y"],
103
108
  subpath: ["closed", "coords"],
104
109
  inlineImage: ["data", "format"],
105
- endpoint: ["elementID", "port", "x", "y"],
110
+ endpoint: ["elementID", "port", "portIndex", "x", "y"],
111
+ extensionRef: ["id", "shape", "version", "params"],
112
+ normalizedPoint: ["x", "y"],
113
+ normalizedRect: ["x", "y", "width", "height"],
106
114
  connector: ["start", "end", "routing", "startHead", "endHead"],
107
115
  timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
108
116
  track: ["element", "keys", "boil", "follow", "repeats", "march"],
@@ -141,6 +149,9 @@ function allowedURL(url) {
141
149
  if (!/^(https?:\/\/|mailto:)/i.test(u) || [...u].length <= 7 || /[\u0000-\u001f\u007f-\u009f]/.test(u)) return null;
142
150
  return u;
143
151
  }
152
+ function webLink(url) {
153
+ return url ? allowedURL(url) : null;
154
+ }
144
155
  function rgba(v) {
145
156
  if (!isObj(v)) return void 0;
146
157
  const r = num(v.r), g = num(v.g), b = num(v.b);
@@ -187,9 +198,11 @@ var Reader = class {
187
198
  endpoint(v) {
188
199
  const e = isObj(v) ? v : {};
189
200
  const port = str(e.port);
201
+ const portIndex = num(e.portIndex);
190
202
  return {
191
203
  elementID: uuid(e.elementID) ?? null,
192
204
  port: port && ["top", "right", "bottom", "left"].includes(port) ? port : null,
205
+ portIndex: portIndex !== void 0 && Number.isInteger(portIndex) && portIndex >= 0 ? portIndex : null,
193
206
  x: num(e.x) ?? 0,
194
207
  y: num(e.y) ?? 0
195
208
  };
@@ -254,6 +267,7 @@ var Reader = class {
254
267
  evenOdd: bool(v.evenOdd) ?? null,
255
268
  rotation: num(v.rotation) ?? null,
256
269
  link: uuid(v.link) ?? null,
270
+ url: str(v.url) ?? null,
257
271
  anchor: str(v.anchor) ?? null,
258
272
  click: this.click(v.click),
259
273
  hover: this.hover(v.hover),
@@ -262,9 +276,36 @@ var Reader = class {
262
276
  offset: num(v.offset) ?? null,
263
277
  args: strings(v.args),
264
278
  image: this.image(v.image, images),
265
- reveal: num(v.reveal) ?? null
279
+ reveal: num(v.reveal) ?? null,
280
+ ports: Array.isArray(v.ports) ? v.ports.map((p) => {
281
+ const o = isObj(p) ? p : {};
282
+ return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
283
+ }) : null,
284
+ labelRect: this.labelRect(v.labelRect),
285
+ extension: this.extension(v.extension)
266
286
  };
267
287
  }
288
+ labelRect(v) {
289
+ if (!isObj(v)) return null;
290
+ this.fields(v, "normalizedRect");
291
+ return { x: num(v.x) ?? 0, y: num(v.y) ?? 0, width: num(v.width) ?? 0, height: num(v.height) ?? 0 };
292
+ }
293
+ extension(v) {
294
+ if (!isObj(v)) return null;
295
+ this.fields(v, "extensionRef");
296
+ const id = str(v.id), shape = str(v.shape), version = str(v.version);
297
+ if (id === void 0 || shape === void 0 || version === void 0) {
298
+ this.warn("Skipped an extension without an id, shape or version");
299
+ return null;
300
+ }
301
+ const params = {};
302
+ if (isObj(v.params)) {
303
+ for (const [k, p] of Object.entries(v.params)) {
304
+ if (typeof p === "number" || typeof p === "boolean" || typeof p === "string") params[k] = p;
305
+ }
306
+ }
307
+ return { id, shape, version, params };
308
+ }
268
309
  click(v) {
269
310
  if (!isObj(v)) return null;
270
311
  this.fields(v, "click");
@@ -770,12 +811,45 @@ function portPoint(shape, port) {
770
811
  const p = port === "top" ? { x: midX(r), y: r.y } : port === "right" ? { x: maxX(r), y: midY(r) } : port === "bottom" ? { x: midX(r), y: maxY(r) } : { x: r.x, y: midY(r) };
771
812
  return rotated(shape, p);
772
813
  }
814
+ function flattenContour(cp) {
815
+ const out = [];
816
+ for (let s = 0; s + 3 < cp.length; s += 3) {
817
+ const seg = cp.slice(s, s + 4);
818
+ for (let k = 0; k < 16; k++) out.push(bezierPoint(seg, k / 16));
819
+ }
820
+ out.push(cp[cp.length - 1]);
821
+ return out;
822
+ }
823
+ function outlineCrossing(shape, c, d) {
824
+ let nearest2 = null, farthest = null;
825
+ for (const contour of shapeContours(shape)) {
826
+ const pts = flattenContour(contour.points);
827
+ const n = pts.length;
828
+ const edges = contour.closed ? n : n - 1;
829
+ for (let i = 0; i < edges; i++) {
830
+ const a = pts[i], b = pts[(i + 1) % n];
831
+ const e = { x: b.x - a.x, y: b.y - a.y }, w = { x: a.x - c.x, y: a.y - c.y };
832
+ const det = e.x * d.y - d.x * e.y;
833
+ if (det === 0) continue;
834
+ const t = (e.x * w.y - e.y * w.x) / det, s = (d.x * w.y - d.y * w.x) / det;
835
+ if (!(t > 0 && s >= 0 && s <= 1)) continue;
836
+ if (nearest2 === null || t < nearest2) nearest2 = t;
837
+ if (t <= 1 && (farthest === null || t > farthest)) farthest = t;
838
+ }
839
+ }
840
+ const best = farthest ?? nearest2;
841
+ return best === null ? null : { x: c.x + d.x * best, y: c.y + d.y * best };
842
+ }
773
843
  function edgePoint(shape, toward) {
774
844
  const r = frame(shape);
775
845
  const c = center(shape);
776
846
  const target = unrotated(shape, toward);
777
847
  const dx = target.x - c.x, dy = target.y - c.y;
778
848
  if (!((dx !== 0 || dy !== 0) && r.width > 0 && r.height > 0)) return c;
849
+ if (shape.kind === "path") {
850
+ const hit = outlineCrossing(shape, c, { x: dx, y: dy });
851
+ if (hit) return rotated(shape, hit);
852
+ }
779
853
  const hw = r.width / 2, hh = r.height / 2;
780
854
  let t;
781
855
  if (shape.kind === "ellipse") t = 1 / Math.sqrt(dx * dx / (hw * hw) + dy * dy / (hh * hh));
@@ -787,9 +861,15 @@ function reference(ep, index) {
787
861
  const s = ep.elementID ? index.get(ep.elementID) : void 0;
788
862
  return s ? center(s) : { x: ep.x, y: ep.y };
789
863
  }
864
+ function normalizedPoint(shape, p) {
865
+ const r = frame(shape);
866
+ return rotated(shape, { x: r.x + p.x * r.width, y: r.y + p.y * r.height });
867
+ }
790
868
  function resolve(ep, other, index) {
791
869
  const s = ep.elementID ? index.get(ep.elementID) : void 0;
792
870
  if (!s) return { x: ep.x, y: ep.y };
871
+ const i = ep.portIndex, ports = s.ports ?? [];
872
+ if (typeof i === "number" && Number.isInteger(i) && i >= 0 && i < ports.length) return normalizedPoint(s, ports[i]);
793
873
  return ep.port ? portPoint(s, ep.port) : edgePoint(s, other);
794
874
  }
795
875
  function endpoints(e, index) {
@@ -1973,7 +2053,7 @@ function clickTargets(page, file) {
1973
2053
  continue;
1974
2054
  }
1975
2055
  }
1976
- const url = c?.url ? allowedURL(c.url) : null;
2056
+ const url = (c?.url ? allowedURL(c.url) : null) ?? webLink(e.url);
1977
2057
  if (url) {
1978
2058
  out.set(e.id, { url });
1979
2059
  continue;
@@ -2773,8 +2853,8 @@ function svgOpen(b, background, paint) {
2773
2853
  }
2774
2854
  return out;
2775
2855
  }
2776
- function svgParts(page, background, fixed) {
2777
- if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed);
2856
+ function svgParts(page, background, fixed, links = false) {
2857
+ if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
2778
2858
  const theme = themeNamed(page.theme);
2779
2859
  const bg = background ? theme?.background ?? background : null;
2780
2860
  const ink = theme?.ink ?? black;
@@ -2798,14 +2878,19 @@ function svgParts(page, background, fixed) {
2798
2878
  ${body}</g>
2799
2879
  `;
2800
2880
  }
2801
- parts.push({ key: e.id, markup: alpha < 1 ? `<g opacity="${fmt(alpha)}">
2881
+ if (alpha < 1) body = `<g opacity="${fmt(alpha)}">
2802
2882
  ${body}</g>
2803
- ` : body });
2883
+ `;
2884
+ const href = links ? (e.click?.url ? allowedURL(e.click.url) : null) ?? webLink(e.url) : null;
2885
+ if (href) body = `<a href="${escapeXML(href).replace(/"/g, "&quot;")}" target="_blank">
2886
+ ${body}</a>
2887
+ `;
2888
+ parts.push({ key: e.id, markup: body });
2804
2889
  }
2805
2890
  return { bounds: b, parts };
2806
2891
  }
2807
2892
  function svg(page, background = { r: 1, g: 1, b: 1, a: 1 }, fixed) {
2808
- const { bounds: b, parts } = svgParts(page, background, fixed);
2893
+ const { bounds: b, parts } = svgParts(page, background, fixed, true);
2809
2894
  return svgOpen(b, themeNamed(page.theme)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
2810
2895
  }
2811
2896
  function labelRect(e, index) {
@@ -2900,7 +2985,7 @@ function svgElement(e, index, labelBackground) {
2900
2985
  }
2901
2986
  s += `<path d="${outline(e).data()}" fill="none" ${strokeAttrs}/>
2902
2987
  `;
2903
- if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, frame(e), e, true);
2988
+ if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" ? pathLabelBox(e) : frame(e), e, true);
2904
2989
  return s;
2905
2990
  }
2906
2991
  default:
@@ -2913,6 +2998,11 @@ function placeholder(e, strokeAttrs) {
2913
2998
  return `<path d="${p.data()}" fill="none" ${strokeAttrs} stroke-opacity="0.5" stroke-dasharray="6 4"/>
2914
2999
  ` + textElement(`${e.kind}${e.text ? `: ${e.text}` : ""}`, f, { ...e, style: { ...e.style, fontSize: Math.min(e.style.fontSize, 12) } }, true);
2915
3000
  }
3001
+ function pathLabelBox(e) {
3002
+ const f = frame(e), r = e.labelRect;
3003
+ if (!r) return f;
3004
+ return { x: f.x + r.x * f.width, y: f.y + r.y * f.height, width: r.width * f.width, height: r.height * f.height };
3005
+ }
2916
3006
  function textElement(text, f, e, centered) {
2917
3007
  const size = e.style.fontSize;
2918
3008
  const lines = text.split("\n");
@@ -3611,7 +3701,7 @@ function render(source, options = {}) {
3611
3701
  function isFile(v) {
3612
3702
  return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
3613
3703
  }
3614
- var VERSION = "0.5.4";
3704
+ var VERSION = "0.6.1";
3615
3705
 
3616
3706
  // src/player-entry.ts
3617
3707
  definePlayer();
@@ -3663,5 +3753,6 @@ export {
3663
3753
  themeNamed,
3664
3754
  themes,
3665
3755
  transientElements,
3756
+ webLink,
3666
3757
  withRoughness
3667
3758
  };