@canvas-harness/core 0.1.6 → 0.1.7

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/index.d.cts CHANGED
@@ -945,10 +945,11 @@ declare const defineNode: (opts: NodeTypeDefOptions) => NodeTypeDef;
945
945
  */
946
946
  type InlineType = 'text' | 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'code' | 'link'
947
947
  /**
948
- * LaTeX math expression, content is the source between `$...$` (no
949
- * leading/trailing whitespace, no line breaks). Rendered via MathJax
950
- * SVG output and rasterized to an inline bitmap at paint time. See
951
- * `text/math/`.
948
+ * LaTeX math expression, content is the source between `$$...$$` (no
949
+ * line breaks). Rendered via MathJax SVG output and rasterized to an
950
+ * inline bitmap at paint time. See `text/math/`. Double-dollar
951
+ * delimiters avoid the false-positives of single `$` in prose that
952
+ * mentions currency (e.g. `$5 to $10`).
952
953
  */
953
954
  | 'math';
954
955
  type Token = {
@@ -1075,7 +1076,7 @@ declare const getFontEpoch: () => number;
1075
1076
  * Lazy loader for MathJax — same pattern as `render/rough/loader.ts`.
1076
1077
  *
1077
1078
  * MathJax is ~600KB and only useful for scenes with LaTeX math. We
1078
- * defer loading until the first `$...$` token requests a compile,
1079
+ * defer loading until the first `$$...$$` token requests a compile,
1079
1080
  * then convert LaTeX → SVG strings off the main rAF path.
1080
1081
  *
1081
1082
  * Loaded from jsDelivr CDN rather than bundled because the v4
package/dist/index.d.ts CHANGED
@@ -945,10 +945,11 @@ declare const defineNode: (opts: NodeTypeDefOptions) => NodeTypeDef;
945
945
  */
946
946
  type InlineType = 'text' | 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'code' | 'link'
947
947
  /**
948
- * LaTeX math expression, content is the source between `$...$` (no
949
- * leading/trailing whitespace, no line breaks). Rendered via MathJax
950
- * SVG output and rasterized to an inline bitmap at paint time. See
951
- * `text/math/`.
948
+ * LaTeX math expression, content is the source between `$$...$$` (no
949
+ * line breaks). Rendered via MathJax SVG output and rasterized to an
950
+ * inline bitmap at paint time. See `text/math/`. Double-dollar
951
+ * delimiters avoid the false-positives of single `$` in prose that
952
+ * mentions currency (e.g. `$5 to $10`).
952
953
  */
953
954
  | 'math';
954
955
  type Token = {
@@ -1075,7 +1076,7 @@ declare const getFontEpoch: () => number;
1075
1076
  * Lazy loader for MathJax — same pattern as `render/rough/loader.ts`.
1076
1077
  *
1077
1078
  * MathJax is ~600KB and only useful for scenes with LaTeX math. We
1078
- * defer loading until the first `$...$` token requests a compile,
1079
+ * defer loading until the first `$$...$$` token requests a compile,
1079
1080
  * then convert LaTeX → SVG strings off the main rAF path.
1080
1081
  *
1081
1082
  * Loaded from jsDelivr CDN rather than bundled because the v4
package/dist/index.js CHANGED
@@ -231,53 +231,6 @@ var rotatePoint = (px, py, cx, cy, cos, sin) => {
231
231
  return { x: cx + dx * cos - dy * sin, y: cy + dx * sin + dy * cos };
232
232
  };
233
233
 
234
- // src/edges/auto-route.ts
235
- var CONTROL_MAX = 200;
236
- var CONTROL_FRACTION = 0.4;
237
- var sideOf = (node, localX, localY) => {
238
- const distLeft = localX;
239
- const distRight = node.w - localX;
240
- const distTop = localY;
241
- const distBottom = node.h - localY;
242
- const minDist = Math.min(distLeft, distRight, distTop, distBottom);
243
- if (minDist === distLeft) return "w";
244
- if (minDist === distRight) return "e";
245
- if (minDist === distTop) return "n";
246
- return "s";
247
- };
248
- var sideNormalLocal = (side) => {
249
- switch (side) {
250
- case "n":
251
- return { x: 0, y: -1 };
252
- case "s":
253
- return { x: 0, y: 1 };
254
- case "e":
255
- return { x: 1, y: 0 };
256
- case "w":
257
- return { x: -1, y: 0 };
258
- }
259
- };
260
- var rotateVecByAngle = (v, angle) => {
261
- if (angle === 0) return v;
262
- const cos = Math.cos(angle);
263
- const sin = Math.sin(angle);
264
- return { x: v.x * cos - v.y * sin, y: v.x * sin + v.y * cos };
265
- };
266
- var autoRouteControls = (sourceWorld, targetWorld, sourceNormalWorld, targetNormalWorld) => {
267
- const dx = targetWorld.x - sourceWorld.x;
268
- const dy = targetWorld.y - sourceWorld.y;
269
- const dist = Math.hypot(dx, dy);
270
- const offset = Math.min(CONTROL_MAX, CONTROL_FRACTION * dist);
271
- const fallbackSource = dist > 0 ? { x: dx / dist, y: dy / dist } : { x: 1, y: 0 };
272
- const fallbackTarget = dist > 0 ? { x: -dx / dist, y: -dy / dist } : { x: -1, y: 0 };
273
- const ns = sourceNormalWorld ?? fallbackSource;
274
- const nt = targetNormalWorld ?? fallbackTarget;
275
- return {
276
- c1: { x: sourceWorld.x + ns.x * offset, y: sourceWorld.y + ns.y * offset },
277
- c2: { x: targetWorld.x + nt.x * offset, y: targetWorld.y + nt.y * offset }
278
- };
279
- };
280
-
281
234
  // src/edges/project.ts
282
235
  var projectEndToWorld = (end, getNode) => {
283
236
  if (!isAttached(end)) return end.worldPoint;
@@ -326,6 +279,112 @@ var projectToNodeBoundary = (world, node) => {
326
279
  return { x: clampedX, y: node.h };
327
280
  };
328
281
 
282
+ // src/edges/auto-route.ts
283
+ var CONTROL_MAX = 200;
284
+ var CONTROL_FRACTION = 0.4;
285
+ var BOUNDARY_EPS = 0.5;
286
+ var isLocalOffsetInsideBody = (localOffset, node) => {
287
+ const onLeft = Math.abs(localOffset.x) <= BOUNDARY_EPS;
288
+ const onRight = Math.abs(localOffset.x - node.w) <= BOUNDARY_EPS;
289
+ const onTop = Math.abs(localOffset.y) <= BOUNDARY_EPS;
290
+ const onBottom = Math.abs(localOffset.y - node.h) <= BOUNDARY_EPS;
291
+ const inside = localOffset.x > -BOUNDARY_EPS && localOffset.x < node.w + BOUNDARY_EPS && localOffset.y > -BOUNDARY_EPS && localOffset.y < node.h + BOUNDARY_EPS;
292
+ return inside && !onLeft && !onRight && !onTop && !onBottom;
293
+ };
294
+ var sideOf = (node, localX, localY) => {
295
+ const distLeft = localX;
296
+ const distRight = node.w - localX;
297
+ const distTop = localY;
298
+ const distBottom = node.h - localY;
299
+ const minDist = Math.min(distLeft, distRight, distTop, distBottom);
300
+ if (minDist === distLeft) return "w";
301
+ if (minDist === distRight) return "e";
302
+ if (minDist === distTop) return "n";
303
+ return "s";
304
+ };
305
+ var sideNormalLocal = (side) => {
306
+ switch (side) {
307
+ case "n":
308
+ return { x: 0, y: -1 };
309
+ case "s":
310
+ return { x: 0, y: 1 };
311
+ case "e":
312
+ return { x: 1, y: 0 };
313
+ case "w":
314
+ return { x: -1, y: 0 };
315
+ }
316
+ };
317
+ var rotateVecByAngle = (v, angle) => {
318
+ if (angle === 0) return v;
319
+ const cos = Math.cos(angle);
320
+ const sin = Math.sin(angle);
321
+ return { x: v.x * cos - v.y * sin, y: v.x * sin + v.y * cos };
322
+ };
323
+ var autoRouteControls = (sourceWorld, targetWorld, sourceNormalWorld, targetNormalWorld) => {
324
+ const dx = targetWorld.x - sourceWorld.x;
325
+ const dy = targetWorld.y - sourceWorld.y;
326
+ const dist = Math.hypot(dx, dy);
327
+ const offset = Math.min(CONTROL_MAX, CONTROL_FRACTION * dist);
328
+ const fallbackSource = dist > 0 ? { x: dx / dist, y: dy / dist } : { x: 1, y: 0 };
329
+ const fallbackTarget = dist > 0 ? { x: -dx / dist, y: -dy / dist } : { x: -1, y: 0 };
330
+ const ns = sourceNormalWorld ?? fallbackSource;
331
+ const nt = targetNormalWorld ?? fallbackTarget;
332
+ return {
333
+ c1: { x: sourceWorld.x + ns.x * offset, y: sourceWorld.y + ns.y * offset },
334
+ c2: { x: targetWorld.x + nt.x * offset, y: targetWorld.y + nt.y * offset }
335
+ };
336
+ };
337
+ var computeAsymmetricRoute = (sourceNode, targetNode) => {
338
+ const srcCenterWorld = {
339
+ x: sourceNode.x + sourceNode.w / 2,
340
+ y: sourceNode.y + sourceNode.h / 2
341
+ };
342
+ const srcInTgtLocal = worldToNodeLocal(srcCenterWorld, targetNode);
343
+ const tgtHalfW = targetNode.w / 2;
344
+ const tgtHalfH = targetNode.h / 2;
345
+ const dxNorm = (srcInTgtLocal.x - tgtHalfW) / Math.max(1, tgtHalfW);
346
+ const dyNorm = (srcInTgtLocal.y - tgtHalfH) / Math.max(1, tgtHalfH);
347
+ const targetSide = Math.abs(dxNorm) >= Math.abs(dyNorm) ? dxNorm > 0 ? "e" : "w" : dyNorm > 0 ? "s" : "n";
348
+ let tgtEntryLocal;
349
+ if (targetSide === "n" || targetSide === "s") {
350
+ const sideY = targetSide === "n" ? 0 : targetNode.h;
351
+ const clampX = Math.max(0, Math.min(targetNode.w, srcInTgtLocal.x));
352
+ tgtEntryLocal = { x: clampX, y: sideY };
353
+ } else {
354
+ const sideX = targetSide === "w" ? 0 : targetNode.w;
355
+ const clampY = Math.max(0, Math.min(targetNode.h, srcInTgtLocal.y));
356
+ tgtEntryLocal = { x: sideX, y: clampY };
357
+ }
358
+ const targetEntryWorld = nodeLocalToWorld(tgtEntryLocal, targetNode);
359
+ const tgtEntryInSrcLocal = worldToNodeLocal(targetEntryWorld, sourceNode);
360
+ const srcHalfW = sourceNode.w / 2;
361
+ const srcHalfH = sourceNode.h / 2;
362
+ const rayDx = tgtEntryInSrcLocal.x - srcHalfW;
363
+ const rayDy = tgtEntryInSrcLocal.y - srcHalfH;
364
+ const tx = rayDx === 0 ? Number.POSITIVE_INFINITY : (rayDx > 0 ? srcHalfW : -srcHalfW) / rayDx;
365
+ const ty = rayDy === 0 ? Number.POSITIVE_INFINITY : (rayDy > 0 ? srcHalfH : -srcHalfH) / rayDy;
366
+ const t = Math.min(tx, ty);
367
+ const srcExitLocal = {
368
+ x: srcHalfW + rayDx * t,
369
+ y: srcHalfH + rayDy * t
370
+ };
371
+ const sourceExitWorld = nodeLocalToWorld(srcExitLocal, sourceNode);
372
+ const dxWorld = targetEntryWorld.x - sourceExitWorld.x;
373
+ const dyWorld = targetEntryWorld.y - sourceExitWorld.y;
374
+ const distance2 = Math.hypot(dxWorld, dyWorld);
375
+ const offset = Math.min(CONTROL_MAX, CONTROL_FRACTION * distance2);
376
+ const c1 = distance2 > 0 ? {
377
+ x: sourceExitWorld.x + dxWorld / distance2 * offset,
378
+ y: sourceExitWorld.y + dyWorld / distance2 * offset
379
+ } : { ...sourceExitWorld };
380
+ const tgtNormalWorld = rotateVecByAngle(sideNormalLocal(targetSide), targetNode.angle);
381
+ const c2 = {
382
+ x: targetEntryWorld.x + tgtNormalWorld.x * offset,
383
+ y: targetEntryWorld.y + tgtNormalWorld.y * offset
384
+ };
385
+ return { source: sourceExitWorld, target: targetEntryWorld, c1, c2 };
386
+ };
387
+
329
388
  // src/edges/clip.ts
330
389
  var fullVisibleClipResult = (samples) => ({
331
390
  startIndex: 0,
@@ -586,8 +645,8 @@ var computeEdgeGeometry = (edge, getNode) => {
586
645
  targetNodeId
587
646
  };
588
647
  }
589
- const sourceWorld = projectEndToWorld(edge.source, getNode);
590
- const targetWorld = projectEndToWorld(edge.target, getNode);
648
+ let sourceWorld = projectEndToWorld(edge.source, getNode);
649
+ let targetWorld = projectEndToWorld(edge.target, getNode);
591
650
  if (!sourceWorld || !targetWorld) return null;
592
651
  let samples;
593
652
  if (edge.pathStyle === "bezier") {
@@ -596,6 +655,12 @@ var computeEdgeGeometry = (edge, getNode) => {
596
655
  if (edge.control && edge.control.length >= 2) {
597
656
  c1 = edge.control[0];
598
657
  c2 = edge.control[1];
658
+ } else if (sourceNode && targetNode && isAttached(edge.source) && isAttached(edge.target) && isLocalOffsetInsideBody(edge.source.localOffset, sourceNode) && isLocalOffsetInsideBody(edge.target.localOffset, targetNode)) {
659
+ const r = computeAsymmetricRoute(sourceNode, targetNode);
660
+ sourceWorld = r.source;
661
+ targetWorld = r.target;
662
+ c1 = r.c1;
663
+ c2 = r.c2;
599
664
  } else {
600
665
  const sourceNormal = sourceNode && isAttached(edge.source) ? rotateVecByAngle(
601
666
  sideNormalLocal(
@@ -1605,7 +1670,7 @@ var buildPath = (type, x, y, w, h, radius) => {
1605
1670
  };
1606
1671
 
1607
1672
  // src/text/tokens.ts
1608
- var INLINE_PATTERN = /(\$[^$\n]+?\$|\*\*[^*]+\*\*|==[^=\s](?:[^=]*?[^=\s])?==|`[^`]+`|\*[^*]+\*|__[^_]+__|~~[^~]+~~|_[^_]+_|\[[^\]]+\]\([^)]+\))/g;
1673
+ var INLINE_PATTERN = /(\$\$[^\n]+?\$\$|\*\*[^*]+\*\*|==[^=\s](?:[^=]*?[^=\s])?==|`[^`]+`|\*[^*]+\*|__[^_]+__|~~[^~]+~~|_[^_]+_|\[[^\]]+\]\([^)]+\))/g;
1609
1674
  var HR_LINE_PATTERN = /^[ \t]*---[ \t]*$/;
1610
1675
  var DOUBLE_HR_LINE_PATTERN = /^[ \t]*===[ \t]*$/;
1611
1676
  var transformSymbols = (value) => value.replace(/<=>|<->|<-|->|\[\]|\[[vx]\]/gi, (match) => {
@@ -1643,8 +1708,8 @@ var tokenizeInline = (segment) => {
1643
1708
  tokens.push({ type: "link", content: transformSymbols(match.slice(1, splitIndex)) });
1644
1709
  } else if (match.startsWith("`") && match.endsWith("`")) {
1645
1710
  tokens.push({ type: "code", content: match.slice(1, -1) });
1646
- } else if (match.startsWith("$") && match.endsWith("$")) {
1647
- tokens.push({ type: "math", content: match.slice(1, -1) });
1711
+ } else if (match.startsWith("$$") && match.endsWith("$$")) {
1712
+ tokens.push({ type: "math", content: match.slice(2, -2) });
1648
1713
  } else {
1649
1714
  tokens.push({ type: "text", content: transformSymbols(match) });
1650
1715
  }