@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.cjs +117 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +117 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
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
|
|
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
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
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
|
|
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
|
-
|
|
590
|
-
|
|
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 = /(
|
|
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("
|
|
1647
|
-
tokens.push({ type: "math", content: match.slice(
|
|
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
|
}
|