@weasel-js/core 0.7.0 → 0.7.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/CHANGELOG.md +94 -0
- package/dist/{chunk-7F3SDUJ4.js → chunk-2J6V527H.js} +1555 -1151
- package/dist/chunk-2J6V527H.js.map +1 -0
- package/dist/{chunk-FSZEXVCR.js → chunk-SYM6RAM4.js} +66 -5
- package/dist/chunk-SYM6RAM4.js.map +1 -0
- package/dist/{index-D31EADQG.d.ts → index-DOYRTfP0.d.ts} +156 -17
- package/dist/index.d.ts +352 -26
- package/dist/index.js +2 -2
- package/dist/renderer.d.ts +26 -2
- package/dist/renderer.js +2 -2
- package/dist/routing.d.ts +1 -1
- package/dist/routing.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-7F3SDUJ4.js.map +0 -1
- package/dist/chunk-FSZEXVCR.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { defineViewportTool, defineTool, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './chunk-
|
|
1
|
+
import { defineViewportTool, reportRouteConflicts, defineTool, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES } from './chunk-SYM6RAM4.js';
|
|
2
2
|
import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-VOVKONXA.js';
|
|
3
3
|
import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-BGGZ4CVF.js';
|
|
4
|
-
import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, meanScale, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath,
|
|
4
|
+
import { AUTO_POSE_DESCRIPTOR, RECT_POSE_DESCRIPTOR, resolveUnit, meanScale, pathPoseDescriptor, polygonFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, strokeHitTest, boundsOfPath, PATH_M, PATH_L, PATH_Z, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, transformPath, flattenCubicWithArcLen, flattenQuadraticWithArcLen, translatePath, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-CQNKCG34.js';
|
|
5
5
|
import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText } from './chunk-775XXAHR.js';
|
|
6
6
|
import { createSetSelectionOp } from './chunk-BHVYVFGV.js';
|
|
7
7
|
import { registerOpFactory, createDeleteOp, createInsertOp, rebuildOp } from './chunk-GVCNT7UH.js';
|
|
8
8
|
import { getTexture } from './chunk-Y52N27PF.js';
|
|
9
9
|
import { createContext, createElement, forwardRef, useMemo, useRef, useState, useCallback, useImperativeHandle, useEffect, useSyncExternalStore, useContext, useReducer, useLayoutEffect } from 'react';
|
|
10
|
-
import { subscribeGlyphReady, DEFAULT_BAKE_BUDGET, TEXT_VERT_SRC, TEXT_FRAG_SRC, TEXT_SDF_UNIFORMS, TEXT_SDF_ATTRIBUTES, TEXT_FRAG_R8_SRC, markAllFontsNotUploaded, resetBakeBudget,
|
|
11
|
-
export { isCanvasFont, isCanvasFont as isCanvasFont2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2 } from '@weasel-js/font';
|
|
10
|
+
import { subscribeGlyphReady, DEFAULT_BAKE_BUDGET, TEXT_VERT_SRC, TEXT_FRAG_SRC, TEXT_SDF_UNIFORMS, TEXT_SDF_ATTRIBUTES, TEXT_FRAG_R8_SRC, markAllFontsNotUploaded, resetBakeBudget, glyphGeneration, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, resolveFontVariant, resolveGlyphFallback, glyphOutline } from '@weasel-js/font';
|
|
11
|
+
export { canQueryLocalFonts, canQueryLocalFonts as canQueryLocalFonts2, enableLocalFontOutlines, enableLocalFontOutlines as enableLocalFontOutlines2, hasFontOutlines, hasFontOutlines as hasFontOutlines2, isCanvasFont, isCanvasFont as isCanvasFont2, listFontOutlines, listFontOutlines as listFontOutlines2, outlineStatus, outlineStatus as outlineStatus2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, registerFontOutlines, registerFontOutlines as registerFontOutlines2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2, unregisterFontOutlines, unregisterFontOutlines as unregisterFontOutlines2 } from '@weasel-js/font';
|
|
12
12
|
import earcut from 'earcut';
|
|
13
13
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
14
|
import { applyToPoint, rotateAboutPoint, boxToBox, pointInPolygon, segmentsCross } from '@weasel-js/geom';
|
|
@@ -18,6 +18,9 @@ import { NORMAL, IMPLICIT_TAGS } from '@weasel-js/modes';
|
|
|
18
18
|
import { createHistory } from '@weasel-js/history';
|
|
19
19
|
export * from '@weasel-js/history';
|
|
20
20
|
|
|
21
|
+
// src/version.ts
|
|
22
|
+
var VERSION = "0.7.0" ;
|
|
23
|
+
|
|
21
24
|
// src/features/grid/roundToCell.ts
|
|
22
25
|
function roundToCell(value, cellSize) {
|
|
23
26
|
return Math.round(value / cellSize) * cellSize || 0;
|
|
@@ -2067,11 +2070,11 @@ function sameFill(a, b) {
|
|
|
2067
2070
|
function groupKey(family, weight, style, synthetic, fill, source, page) {
|
|
2068
2071
|
return `${family}|${weight}|${style}|${synthetic.bold ? 1 : 0}${synthetic.italic ? 1 : 0}|${fillKey(fill)}|${source}|${page}`;
|
|
2069
2072
|
}
|
|
2070
|
-
function getOrCreateGroup(ctx, run, resolved, page) {
|
|
2073
|
+
function getOrCreateGroup(ctx, run, resolved, page, sourceOverride) {
|
|
2071
2074
|
const atlasFamily = resolved.resolved.family;
|
|
2072
2075
|
const resolvedWeight = resolved.resolved.weight;
|
|
2073
2076
|
const resolvedStyle = resolved.resolved.style;
|
|
2074
|
-
const source = resolved.source;
|
|
2077
|
+
const source = sourceOverride ?? resolved.source;
|
|
2075
2078
|
const key = groupKey(
|
|
2076
2079
|
atlasFamily,
|
|
2077
2080
|
resolvedWeight,
|
|
@@ -2091,7 +2094,8 @@ function getOrCreateGroup(ctx, run, resolved, page) {
|
|
|
2091
2094
|
source,
|
|
2092
2095
|
page,
|
|
2093
2096
|
fill: run.fill,
|
|
2094
|
-
quads: []
|
|
2097
|
+
quads: [],
|
|
2098
|
+
glyphs: []
|
|
2095
2099
|
};
|
|
2096
2100
|
ctx.groups.set(key, g);
|
|
2097
2101
|
}
|
|
@@ -2230,6 +2234,10 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2230
2234
|
while (i < entries.length) {
|
|
2231
2235
|
const e = entries[i];
|
|
2232
2236
|
if (e.isNewline) {
|
|
2237
|
+
if (cur.entries.length === 0) {
|
|
2238
|
+
cur.height = Math.max(cur.height, e.fontSize * opts.lineHeight);
|
|
2239
|
+
cur.blank = e;
|
|
2240
|
+
}
|
|
2233
2241
|
commitLine();
|
|
2234
2242
|
i++;
|
|
2235
2243
|
continue;
|
|
@@ -2263,6 +2271,13 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2263
2271
|
i = j;
|
|
2264
2272
|
}
|
|
2265
2273
|
if (cur.entries.length > 0) commitLine();
|
|
2274
|
+
function outlineFor(e) {
|
|
2275
|
+
const min = opts.outlineMinSize;
|
|
2276
|
+
if (min === void 0 || e.fontSize < min) return null;
|
|
2277
|
+
if (e.resolved.synthetic.bold) return null;
|
|
2278
|
+
const r = e.resolved.resolved;
|
|
2279
|
+
return glyphOutline(r.family, r.weight, r.style, e.cp);
|
|
2280
|
+
}
|
|
2266
2281
|
const decorations = [];
|
|
2267
2282
|
let span = null;
|
|
2268
2283
|
function flushSpan() {
|
|
@@ -2279,6 +2294,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2279
2294
|
decorations.push({ kind: "strikethrough", x0: s.x0, y0, x1: s.x1, y1: y0 + thickness, fill: s.fill });
|
|
2280
2295
|
}
|
|
2281
2296
|
}
|
|
2297
|
+
const lineBoxes = [];
|
|
2282
2298
|
let penY = origin.y;
|
|
2283
2299
|
let maxLineWidth = 0;
|
|
2284
2300
|
const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
|
|
@@ -2291,7 +2307,10 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2291
2307
|
const slack = finiteWidth - line.width;
|
|
2292
2308
|
return opts.align === "center" ? slack / 2 : slack;
|
|
2293
2309
|
})();
|
|
2294
|
-
|
|
2310
|
+
const lineX0 = origin.x + alignShift;
|
|
2311
|
+
const baselineSource = line.entries[0] ?? line.blank;
|
|
2312
|
+
const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
|
|
2313
|
+
let penX = lineX0;
|
|
2295
2314
|
for (const e of line.entries) {
|
|
2296
2315
|
penX += e.kerningBefore;
|
|
2297
2316
|
const step = e.advance + e.tracking;
|
|
@@ -2315,6 +2334,20 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2315
2334
|
} else {
|
|
2316
2335
|
flushSpan();
|
|
2317
2336
|
}
|
|
2337
|
+
const outlineD = outlineFor(e);
|
|
2338
|
+
if (outlineD !== null) {
|
|
2339
|
+
const group2 = getOrCreateGroup(ctx, e.run, e.resolved, 0, "outline");
|
|
2340
|
+
group2.glyphs.push({
|
|
2341
|
+
d: outlineD,
|
|
2342
|
+
key: `${e.resolved.resolved.family}|${e.resolved.resolved.weight}|${e.resolved.resolved.style}|${e.cp}`,
|
|
2343
|
+
x: penX,
|
|
2344
|
+
baselineY,
|
|
2345
|
+
// Em space is unit-scale, so world units per em is just the size.
|
|
2346
|
+
scale: e.fontSize
|
|
2347
|
+
});
|
|
2348
|
+
penX += step;
|
|
2349
|
+
continue;
|
|
2350
|
+
}
|
|
2318
2351
|
if (e.advance === 0 || e.glyph.width === 0 || e.glyph.page < 0) {
|
|
2319
2352
|
penX += step;
|
|
2320
2353
|
continue;
|
|
@@ -2334,15 +2367,54 @@ function layoutRuns(runs, opts, origin) {
|
|
|
2334
2367
|
penX += step;
|
|
2335
2368
|
}
|
|
2336
2369
|
flushSpan();
|
|
2370
|
+
lineBoxes.push({
|
|
2371
|
+
x0: lineX0,
|
|
2372
|
+
y0: penY,
|
|
2373
|
+
x1: lineX0 + line.width,
|
|
2374
|
+
y1: penY + line.height,
|
|
2375
|
+
baselineY: lineBaselineY
|
|
2376
|
+
});
|
|
2337
2377
|
maxLineWidth = Math.max(maxLineWidth, line.width);
|
|
2338
2378
|
penY += line.height;
|
|
2339
2379
|
}
|
|
2340
2380
|
return {
|
|
2341
2381
|
groups: [...ctx.groups.values()],
|
|
2342
2382
|
decorations,
|
|
2383
|
+
lines: lineBoxes,
|
|
2343
2384
|
bounds: { width: maxLineWidth, height: penY - origin.y }
|
|
2344
2385
|
};
|
|
2345
2386
|
}
|
|
2387
|
+
var LAYOUT_CACHE_VARIANT_LIMIT = 8;
|
|
2388
|
+
var cache2 = /* @__PURE__ */ new WeakMap();
|
|
2389
|
+
function outlineBucket(runs, min) {
|
|
2390
|
+
if (min === void 0) return -1;
|
|
2391
|
+
const sizes = /* @__PURE__ */ new Set();
|
|
2392
|
+
for (const r of runs) sizes.add(r.fontSize);
|
|
2393
|
+
let n = 0;
|
|
2394
|
+
for (const size of sizes) if (size >= min) n++;
|
|
2395
|
+
return n;
|
|
2396
|
+
}
|
|
2397
|
+
function variantKey(runs, opts, origin) {
|
|
2398
|
+
return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}|${origin.x}|${origin.y}`;
|
|
2399
|
+
}
|
|
2400
|
+
function cachedLayoutRuns(runs, opts, origin) {
|
|
2401
|
+
const fonts = glyphGeneration();
|
|
2402
|
+
let entry = cache2.get(runs);
|
|
2403
|
+
if (entry === void 0) {
|
|
2404
|
+
entry = { generation: fonts, byVariant: /* @__PURE__ */ new Map() };
|
|
2405
|
+
cache2.set(runs, entry);
|
|
2406
|
+
} else if (entry.generation !== fonts) {
|
|
2407
|
+
entry.generation = fonts;
|
|
2408
|
+
entry.byVariant.clear();
|
|
2409
|
+
}
|
|
2410
|
+
const key = variantKey(runs, opts, origin);
|
|
2411
|
+
const hit = entry.byVariant.get(key);
|
|
2412
|
+
if (hit !== void 0) return hit;
|
|
2413
|
+
const laid = layoutRuns(runs, opts, origin);
|
|
2414
|
+
if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
|
|
2415
|
+
entry.byVariant.set(key, laid);
|
|
2416
|
+
return laid;
|
|
2417
|
+
}
|
|
2346
2418
|
|
|
2347
2419
|
// src/features/text/verticalAlign.ts
|
|
2348
2420
|
function verticalAlignOffset(align, boxHeight, textHeight) {
|
|
@@ -2351,170 +2423,445 @@ function verticalAlignOffset(align, boxHeight, textHeight) {
|
|
|
2351
2423
|
return align === "center" ? slack / 2 : slack;
|
|
2352
2424
|
}
|
|
2353
2425
|
|
|
2354
|
-
// src/
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
const
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2426
|
+
// src/features/paths/pathFromD.ts
|
|
2427
|
+
var NUM_RE = /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g;
|
|
2428
|
+
function tokenize(d) {
|
|
2429
|
+
const out = [];
|
|
2430
|
+
let i = 0;
|
|
2431
|
+
while (i < d.length) {
|
|
2432
|
+
const ch = d[i];
|
|
2433
|
+
if (/[A-Za-z]/.test(ch)) {
|
|
2434
|
+
let j = i + 1;
|
|
2435
|
+
while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
|
|
2436
|
+
const argSlice = d.slice(i + 1, j);
|
|
2437
|
+
out.push({ cmd: ch, args: readNumbers(argSlice) });
|
|
2438
|
+
i = j;
|
|
2439
|
+
} else {
|
|
2440
|
+
i++;
|
|
2362
2441
|
}
|
|
2363
2442
|
}
|
|
2364
|
-
|
|
2365
|
-
const bLoc = prog.uniform("u_colorBias");
|
|
2366
|
-
if (mLoc !== void 0) gl.uniformMatrix4fv(mLoc, false, m4);
|
|
2367
|
-
if (bLoc !== void 0) gl.uniform4f(bLoc, cm[4], cm[9], cm[14], cm[19]);
|
|
2368
|
-
}
|
|
2369
|
-
function fillMeshHandle(ctx, path) {
|
|
2370
|
-
if (ctx.flattenTolerance !== void 0) {
|
|
2371
|
-
return ctx.meshCache.uploadTransient(tessellate(path, { flattenTolerance: ctx.flattenTolerance }));
|
|
2372
|
-
}
|
|
2373
|
-
return ctx.meshCache.handleFor(getMesh(path));
|
|
2374
|
-
}
|
|
2375
|
-
function dispatch(ctx, cmd) {
|
|
2376
|
-
switch (cmd.kind) {
|
|
2377
|
-
case "group":
|
|
2378
|
-
return drawGroup(ctx, cmd);
|
|
2379
|
-
case "path":
|
|
2380
|
-
return drawPath(ctx, cmd);
|
|
2381
|
-
case "text":
|
|
2382
|
-
return drawText(ctx, cmd);
|
|
2383
|
-
case "image":
|
|
2384
|
-
return drawImage(ctx, cmd);
|
|
2385
|
-
case "shader":
|
|
2386
|
-
return drawShader(ctx, cmd);
|
|
2387
|
-
}
|
|
2443
|
+
return out;
|
|
2388
2444
|
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
console.warn(`weasel drawShader: uniform "${name}" not found in program "${programId}". Check spelling, ensure it's used in the shader (unused uniforms are optimized away by the driver).`);
|
|
2445
|
+
function readNumbers(s) {
|
|
2446
|
+
const out = [];
|
|
2447
|
+
let m;
|
|
2448
|
+
NUM_RE.lastIndex = 0;
|
|
2449
|
+
while ((m = NUM_RE.exec(s)) !== null) {
|
|
2450
|
+
const n = Number(m[0]);
|
|
2451
|
+
if (Number.isFinite(n)) out.push(n);
|
|
2397
2452
|
}
|
|
2453
|
+
return out;
|
|
2398
2454
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2455
|
+
var ARG_COUNTS = {
|
|
2456
|
+
M: 2,
|
|
2457
|
+
L: 2,
|
|
2458
|
+
H: 1,
|
|
2459
|
+
V: 1,
|
|
2460
|
+
C: 6,
|
|
2461
|
+
S: 4,
|
|
2462
|
+
Q: 4,
|
|
2463
|
+
T: 2,
|
|
2464
|
+
A: 7,
|
|
2465
|
+
Z: 0
|
|
2466
|
+
};
|
|
2467
|
+
function pathFromD(d, onWarn) {
|
|
2468
|
+
const tokens = tokenize(d);
|
|
2469
|
+
const b = new PathBuilder();
|
|
2470
|
+
const st = {
|
|
2471
|
+
x: 0,
|
|
2472
|
+
y: 0,
|
|
2473
|
+
startX: 0,
|
|
2474
|
+
startY: 0,
|
|
2475
|
+
lastCubicCtrl: null,
|
|
2476
|
+
lastQuadCtrl: null
|
|
2477
|
+
};
|
|
2478
|
+
for (const { cmd, args } of tokens) {
|
|
2479
|
+
const upper = cmd.toUpperCase();
|
|
2480
|
+
const relative = cmd !== upper;
|
|
2481
|
+
const argCount = ARG_COUNTS[upper];
|
|
2482
|
+
if (argCount === void 0) {
|
|
2483
|
+
onWarn?.(`unknown path command: ${cmd}`);
|
|
2484
|
+
continue;
|
|
2407
2485
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
if (value.length === 9) {
|
|
2416
|
-
gl.uniformMatrix3fv(loc, false, value);
|
|
2417
|
-
} else if (value.length === 16) {
|
|
2418
|
-
gl.uniformMatrix4fv(loc, false, value);
|
|
2419
|
-
} else {
|
|
2420
|
-
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
2421
|
-
if (isDev3) throw new TypeError(`weasel setUniform: Float32Array must be length 9 (mat3) or 16 (mat4), got ${value.length}`);
|
|
2486
|
+
if (upper === "Z") {
|
|
2487
|
+
b.close();
|
|
2488
|
+
st.x = st.startX;
|
|
2489
|
+
st.y = st.startY;
|
|
2490
|
+
st.lastCubicCtrl = null;
|
|
2491
|
+
st.lastQuadCtrl = null;
|
|
2492
|
+
continue;
|
|
2422
2493
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
case 2:
|
|
2429
|
-
gl.uniform2fv(loc, arr);
|
|
2430
|
-
break;
|
|
2431
|
-
case 3:
|
|
2432
|
-
gl.uniform3fv(loc, arr);
|
|
2433
|
-
break;
|
|
2434
|
-
case 4:
|
|
2435
|
-
gl.uniform4fv(loc, arr);
|
|
2436
|
-
break;
|
|
2437
|
-
default: {
|
|
2438
|
-
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
2439
|
-
if (isDev3) throw new TypeError(`weasel setUniform: array length ${arr.length} not supported`);
|
|
2440
|
-
}
|
|
2494
|
+
const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
|
|
2495
|
+
for (let g = 0; g < groups; g++) {
|
|
2496
|
+
const a = args.slice(g * argCount, (g + 1) * argCount);
|
|
2497
|
+
const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
|
|
2498
|
+
runCommand(effectiveUpper, relative, a, st, b);
|
|
2441
2499
|
}
|
|
2442
|
-
return;
|
|
2443
|
-
}
|
|
2444
|
-
if (typeof value === "number") {
|
|
2445
|
-
gl.uniform1f(loc, value);
|
|
2446
|
-
return;
|
|
2447
2500
|
}
|
|
2501
|
+
return b.build();
|
|
2448
2502
|
}
|
|
2449
|
-
function
|
|
2450
|
-
const
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
const aUvLoc = program.attribute("a_uv");
|
|
2465
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, quadVbo);
|
|
2466
|
-
if (aPosLoc !== void 0) {
|
|
2467
|
-
gl.enableVertexAttribArray(aPosLoc);
|
|
2468
|
-
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 16, 0);
|
|
2469
|
-
}
|
|
2470
|
-
if (aUvLoc !== void 0) {
|
|
2471
|
-
gl.enableVertexAttribArray(aUvLoc);
|
|
2472
|
-
gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, 16, 8);
|
|
2473
|
-
}
|
|
2474
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, quadIbo);
|
|
2475
|
-
const proj = mat3.screenToClip(ctx.widthCss, ctx.heightCss);
|
|
2476
|
-
const uProj = program.uniform("u_proj");
|
|
2477
|
-
if (uProj !== void 0) gl.uniformMatrix3fv(uProj, false, proj);
|
|
2478
|
-
const uBounds = program.uniform("u_bounds");
|
|
2479
|
-
if (uBounds !== void 0) {
|
|
2480
|
-
gl.uniform4f(uBounds, cmd.bounds.x, cmd.bounds.y, cmd.bounds.w, cmd.bounds.h);
|
|
2481
|
-
}
|
|
2482
|
-
const uView = program.uniform("u_view");
|
|
2483
|
-
if (uView !== void 0) gl.uniformMatrix3fv(uView, false, ctx.state.transform);
|
|
2484
|
-
const nextTexUnit = { value: 1 };
|
|
2485
|
-
for (const [name, value] of Object.entries(cmd.uniforms)) {
|
|
2486
|
-
const loc = program.uniform(name);
|
|
2487
|
-
if (loc === void 0) {
|
|
2488
|
-
warnOnceUniform(cmd.program.id, name);
|
|
2489
|
-
continue;
|
|
2503
|
+
function runCommand(upper, relative, a, st, b) {
|
|
2504
|
+
const rx = relative ? st.x : 0;
|
|
2505
|
+
const ry = relative ? st.y : 0;
|
|
2506
|
+
switch (upper) {
|
|
2507
|
+
case "M": {
|
|
2508
|
+
const x = a[0] + rx;
|
|
2509
|
+
const y = a[1] + ry;
|
|
2510
|
+
b.moveTo(x, y);
|
|
2511
|
+
st.x = x;
|
|
2512
|
+
st.y = y;
|
|
2513
|
+
st.startX = x;
|
|
2514
|
+
st.startY = y;
|
|
2515
|
+
st.lastCubicCtrl = null;
|
|
2516
|
+
st.lastQuadCtrl = null;
|
|
2517
|
+
return;
|
|
2490
2518
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
function drawGroup(ctx, cmd) {
|
|
2501
|
-
ctx.state.push({
|
|
2502
|
-
transform: cmd.transform,
|
|
2503
|
-
alpha: cmd.alpha,
|
|
2504
|
-
colorMatrix: cmd.colorMatrix
|
|
2505
|
-
});
|
|
2506
|
-
if (cmd.clip) {
|
|
2507
|
-
const newDepth = ctx.clipDepth + 1;
|
|
2508
|
-
if (newDepth > 7) {
|
|
2509
|
-
ctx.state.pop();
|
|
2510
|
-
throw new Error(
|
|
2511
|
-
"weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
|
|
2512
|
-
);
|
|
2519
|
+
case "L": {
|
|
2520
|
+
const x = a[0] + rx;
|
|
2521
|
+
const y = a[1] + ry;
|
|
2522
|
+
b.lineTo(x, y);
|
|
2523
|
+
st.x = x;
|
|
2524
|
+
st.y = y;
|
|
2525
|
+
st.lastCubicCtrl = null;
|
|
2526
|
+
st.lastQuadCtrl = null;
|
|
2527
|
+
return;
|
|
2513
2528
|
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2529
|
+
case "H": {
|
|
2530
|
+
const x = a[0] + (relative ? st.x : 0);
|
|
2531
|
+
b.lineTo(x, st.y);
|
|
2532
|
+
st.x = x;
|
|
2533
|
+
st.lastCubicCtrl = null;
|
|
2534
|
+
st.lastQuadCtrl = null;
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
case "V": {
|
|
2538
|
+
const y = a[0] + (relative ? st.y : 0);
|
|
2539
|
+
b.lineTo(st.x, y);
|
|
2540
|
+
st.y = y;
|
|
2541
|
+
st.lastCubicCtrl = null;
|
|
2542
|
+
st.lastQuadCtrl = null;
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
case "C": {
|
|
2546
|
+
const x1 = a[0] + rx, y1 = a[1] + ry;
|
|
2547
|
+
const x2 = a[2] + rx, y2 = a[3] + ry;
|
|
2548
|
+
const x = a[4] + rx, y = a[5] + ry;
|
|
2549
|
+
b.curveTo(x1, y1, x2, y2, x, y);
|
|
2550
|
+
st.x = x;
|
|
2551
|
+
st.y = y;
|
|
2552
|
+
st.lastCubicCtrl = { x: x2, y: y2 };
|
|
2553
|
+
st.lastQuadCtrl = null;
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
case "S": {
|
|
2557
|
+
const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
|
|
2558
|
+
const x1 = 2 * st.x - ref.x;
|
|
2559
|
+
const y1 = 2 * st.y - ref.y;
|
|
2560
|
+
const x2 = a[0] + rx, y2 = a[1] + ry;
|
|
2561
|
+
const x = a[2] + rx, y = a[3] + ry;
|
|
2562
|
+
b.curveTo(x1, y1, x2, y2, x, y);
|
|
2563
|
+
st.x = x;
|
|
2564
|
+
st.y = y;
|
|
2565
|
+
st.lastCubicCtrl = { x: x2, y: y2 };
|
|
2566
|
+
st.lastQuadCtrl = null;
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
case "Q": {
|
|
2570
|
+
const x1 = a[0] + rx, y1 = a[1] + ry;
|
|
2571
|
+
const x = a[2] + rx, y = a[3] + ry;
|
|
2572
|
+
b.quadTo(x1, y1, x, y);
|
|
2573
|
+
st.x = x;
|
|
2574
|
+
st.y = y;
|
|
2575
|
+
st.lastQuadCtrl = { x: x1, y: y1 };
|
|
2576
|
+
st.lastCubicCtrl = null;
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
case "T": {
|
|
2580
|
+
const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
|
|
2581
|
+
const x1 = 2 * st.x - ref.x;
|
|
2582
|
+
const y1 = 2 * st.y - ref.y;
|
|
2583
|
+
const x = a[0] + rx, y = a[1] + ry;
|
|
2584
|
+
b.quadTo(x1, y1, x, y);
|
|
2585
|
+
st.x = x;
|
|
2586
|
+
st.y = y;
|
|
2587
|
+
st.lastQuadCtrl = { x: x1, y: y1 };
|
|
2588
|
+
st.lastCubicCtrl = null;
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
case "A": {
|
|
2592
|
+
const rxA = Math.abs(a[0]);
|
|
2593
|
+
const ryA = Math.abs(a[1]);
|
|
2594
|
+
const xRot = a[2];
|
|
2595
|
+
const largeArc = a[3] !== 0;
|
|
2596
|
+
const sweep = a[4] !== 0;
|
|
2597
|
+
const ex = a[5] + rx;
|
|
2598
|
+
const ey = a[6] + ry;
|
|
2599
|
+
arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
|
|
2600
|
+
st.x = ex;
|
|
2601
|
+
st.y = ey;
|
|
2602
|
+
st.lastCubicCtrl = null;
|
|
2603
|
+
st.lastQuadCtrl = null;
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
default:
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep, b) {
|
|
2611
|
+
if (x1 === x2 && y1 === y2) return;
|
|
2612
|
+
if (rx === 0 || ry === 0) {
|
|
2613
|
+
b.lineTo(x2, y2);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const phi = xAxisRotationDeg * Math.PI / 180;
|
|
2617
|
+
const cosPhi = Math.cos(phi);
|
|
2618
|
+
const sinPhi = Math.sin(phi);
|
|
2619
|
+
const dx = (x1 - x2) / 2;
|
|
2620
|
+
const dy = (y1 - y2) / 2;
|
|
2621
|
+
const x1p = cosPhi * dx + sinPhi * dy;
|
|
2622
|
+
const y1p = -sinPhi * dx + cosPhi * dy;
|
|
2623
|
+
let rxs = rx * rx;
|
|
2624
|
+
let rys = ry * ry;
|
|
2625
|
+
const x1ps = x1p * x1p;
|
|
2626
|
+
const y1ps = y1p * y1p;
|
|
2627
|
+
const lambda = x1ps / rxs + y1ps / rys;
|
|
2628
|
+
let rxFixed = rx;
|
|
2629
|
+
let ryFixed = ry;
|
|
2630
|
+
if (lambda > 1) {
|
|
2631
|
+
const sqrtLambda = Math.sqrt(lambda);
|
|
2632
|
+
rxFixed = sqrtLambda * rx;
|
|
2633
|
+
ryFixed = sqrtLambda * ry;
|
|
2634
|
+
rxs = rxFixed * rxFixed;
|
|
2635
|
+
rys = ryFixed * ryFixed;
|
|
2636
|
+
}
|
|
2637
|
+
const sign = largeArc === sweep ? -1 : 1;
|
|
2638
|
+
const num = rxs * rys - rxs * y1ps - rys * x1ps;
|
|
2639
|
+
const den = rxs * y1ps + rys * x1ps;
|
|
2640
|
+
const factor = sign * Math.sqrt(Math.max(0, num / den));
|
|
2641
|
+
const cxp = factor * (rxFixed * y1p) / ryFixed;
|
|
2642
|
+
const cyp = factor * -(ryFixed * x1p) / rxFixed;
|
|
2643
|
+
const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
|
|
2644
|
+
const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
|
|
2645
|
+
const angle = (ux, uy, vx, vy) => {
|
|
2646
|
+
const dot = ux * vx + uy * vy;
|
|
2647
|
+
const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
|
|
2648
|
+
let a = Math.acos(Math.max(-1, Math.min(1, dot / len)));
|
|
2649
|
+
if (ux * vy - uy * vx < 0) a = -a;
|
|
2650
|
+
return a;
|
|
2651
|
+
};
|
|
2652
|
+
const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
|
|
2653
|
+
let dTheta = angle(
|
|
2654
|
+
(x1p - cxp) / rxFixed,
|
|
2655
|
+
(y1p - cyp) / ryFixed,
|
|
2656
|
+
(-x1p - cxp) / rxFixed,
|
|
2657
|
+
(-y1p - cyp) / ryFixed
|
|
2658
|
+
);
|
|
2659
|
+
if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
|
|
2660
|
+
else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
|
|
2661
|
+
const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
|
|
2662
|
+
const segAngle = dTheta / segCount;
|
|
2663
|
+
const t = 4 / 3 * Math.tan(segAngle / 4);
|
|
2664
|
+
let prevX = x1;
|
|
2665
|
+
let prevY = y1;
|
|
2666
|
+
let prevDx = -Math.sin(theta1) * rxFixed;
|
|
2667
|
+
let prevDy = Math.cos(theta1) * ryFixed;
|
|
2668
|
+
for (let i = 1; i <= segCount; i++) {
|
|
2669
|
+
const theta = theta1 + i * segAngle;
|
|
2670
|
+
const cosT = Math.cos(theta);
|
|
2671
|
+
const sinT = Math.sin(theta);
|
|
2672
|
+
const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
|
|
2673
|
+
const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
|
|
2674
|
+
const dxNew = -sinT * rxFixed;
|
|
2675
|
+
const dyNew = cosT * ryFixed;
|
|
2676
|
+
const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
|
|
2677
|
+
const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
|
|
2678
|
+
const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
|
|
2679
|
+
const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
|
|
2680
|
+
b.curveTo(c1x, c1y, c2x, c2y, px, py);
|
|
2681
|
+
prevX = px;
|
|
2682
|
+
prevY = py;
|
|
2683
|
+
prevDx = dxNew;
|
|
2684
|
+
prevDy = dyNew;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/renderer/cache/outlineMeshCache.ts
|
|
2689
|
+
var OUTLINE_FLATTEN_TOLERANCE = 1 / 4096;
|
|
2690
|
+
var OUTLINE_MESH_CACHE_LIMIT = 2048;
|
|
2691
|
+
var cache3 = /* @__PURE__ */ new Map();
|
|
2692
|
+
function outlineMesh(key, d) {
|
|
2693
|
+
const cached = cache3.get(key);
|
|
2694
|
+
if (cached !== void 0) return cached;
|
|
2695
|
+
if (cache3.size >= OUTLINE_MESH_CACHE_LIMIT) cache3 = /* @__PURE__ */ new Map();
|
|
2696
|
+
const mesh = tessellate(pathFromD(d), { flattenTolerance: OUTLINE_FLATTEN_TOLERANCE });
|
|
2697
|
+
cache3.set(key, mesh);
|
|
2698
|
+
return mesh;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// src/renderer/draw.ts
|
|
2702
|
+
function setColorMatrixUniforms(ctx, prog) {
|
|
2703
|
+
const gl = ctx.gl;
|
|
2704
|
+
const cm = ctx.state.colorMatrix;
|
|
2705
|
+
const m4 = new Float32Array(16);
|
|
2706
|
+
for (let row = 0; row < 4; row++) {
|
|
2707
|
+
for (let col = 0; col < 4; col++) {
|
|
2708
|
+
m4[col * 4 + row] = cm[row * 5 + col];
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
const mLoc = prog.uniform("u_colorMatrix");
|
|
2712
|
+
const bLoc = prog.uniform("u_colorBias");
|
|
2713
|
+
if (mLoc !== void 0) gl.uniformMatrix4fv(mLoc, false, m4);
|
|
2714
|
+
if (bLoc !== void 0) gl.uniform4f(bLoc, cm[4], cm[9], cm[14], cm[19]);
|
|
2715
|
+
}
|
|
2716
|
+
function fillMeshHandle(ctx, path) {
|
|
2717
|
+
if (ctx.flattenTolerance !== void 0) {
|
|
2718
|
+
return ctx.meshCache.uploadTransient(tessellate(path, { flattenTolerance: ctx.flattenTolerance }));
|
|
2719
|
+
}
|
|
2720
|
+
return ctx.meshCache.handleFor(getMesh(path));
|
|
2721
|
+
}
|
|
2722
|
+
function dispatch(ctx, cmd) {
|
|
2723
|
+
switch (cmd.kind) {
|
|
2724
|
+
case "group":
|
|
2725
|
+
return drawGroup(ctx, cmd);
|
|
2726
|
+
case "path":
|
|
2727
|
+
return drawPath(ctx, cmd);
|
|
2728
|
+
case "text":
|
|
2729
|
+
return drawText(ctx, cmd);
|
|
2730
|
+
case "image":
|
|
2731
|
+
return drawImage(ctx, cmd);
|
|
2732
|
+
case "shader":
|
|
2733
|
+
return drawShader(ctx, cmd);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
var warnedUniforms = /* @__PURE__ */ new Set();
|
|
2737
|
+
function warnOnceUniform(programId, name) {
|
|
2738
|
+
const key = `${programId}:${name}`;
|
|
2739
|
+
if (warnedUniforms.has(key)) return;
|
|
2740
|
+
warnedUniforms.add(key);
|
|
2741
|
+
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
2742
|
+
if (isDev3) {
|
|
2743
|
+
console.warn(`weasel drawShader: uniform "${name}" not found in program "${programId}". Check spelling, ensure it's used in the shader (unused uniforms are optimized away by the driver).`);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
function setUniform(gl, loc, value, textureCache, nextTexUnit) {
|
|
2747
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Float32Array) && "id" in value && typeof value.id === "string") {
|
|
2748
|
+
const handle = value;
|
|
2749
|
+
const entry = getTexture(handle.id);
|
|
2750
|
+
if (!entry) {
|
|
2751
|
+
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
2752
|
+
if (isDev3) console.warn(`weasel setUniform: TextureHandle "${handle.id}" not registered`);
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
const unit2 = nextTexUnit.value++;
|
|
2756
|
+
textureCache.upload(handle.id, entry.source);
|
|
2757
|
+
textureCache.bind(handle.id, unit2);
|
|
2758
|
+
gl.uniform1i(loc, unit2);
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
if (value instanceof Float32Array) {
|
|
2762
|
+
if (value.length === 9) {
|
|
2763
|
+
gl.uniformMatrix3fv(loc, false, value);
|
|
2764
|
+
} else if (value.length === 16) {
|
|
2765
|
+
gl.uniformMatrix4fv(loc, false, value);
|
|
2766
|
+
} else {
|
|
2767
|
+
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
2768
|
+
if (isDev3) throw new TypeError(`weasel setUniform: Float32Array must be length 9 (mat3) or 16 (mat4), got ${value.length}`);
|
|
2769
|
+
}
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
if (Array.isArray(value)) {
|
|
2773
|
+
const arr = value;
|
|
2774
|
+
switch (arr.length) {
|
|
2775
|
+
case 2:
|
|
2776
|
+
gl.uniform2fv(loc, arr);
|
|
2777
|
+
break;
|
|
2778
|
+
case 3:
|
|
2779
|
+
gl.uniform3fv(loc, arr);
|
|
2780
|
+
break;
|
|
2781
|
+
case 4:
|
|
2782
|
+
gl.uniform4fv(loc, arr);
|
|
2783
|
+
break;
|
|
2784
|
+
default: {
|
|
2785
|
+
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
2786
|
+
if (isDev3) throw new TypeError(`weasel setUniform: array length ${arr.length} not supported`);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
if (typeof value === "number") {
|
|
2792
|
+
gl.uniform1f(loc, value);
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
function drawShader(ctx, cmd) {
|
|
2797
|
+
const { gl, programRegistry, quadVbo, quadIbo, textureCache } = ctx;
|
|
2798
|
+
const program = programRegistry.get(cmd.program.id);
|
|
2799
|
+
if (!program) {
|
|
2800
|
+
console.warn(
|
|
2801
|
+
`weasel drawShader: program "${cmd.program.id}" not compiled on this renderer. Call renderer.registerProgram(handle) after the module-level registerProgram().`
|
|
2802
|
+
);
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
if (!quadVbo || !quadIbo) {
|
|
2806
|
+
console.warn("weasel drawShader: quad geometry not initialized");
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
gl.useProgram(program.handle);
|
|
2810
|
+
const aPosLoc = program.attribute("a_position");
|
|
2811
|
+
const aUvLoc = program.attribute("a_uv");
|
|
2812
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadVbo);
|
|
2813
|
+
if (aPosLoc !== void 0) {
|
|
2814
|
+
gl.enableVertexAttribArray(aPosLoc);
|
|
2815
|
+
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 16, 0);
|
|
2816
|
+
}
|
|
2817
|
+
if (aUvLoc !== void 0) {
|
|
2818
|
+
gl.enableVertexAttribArray(aUvLoc);
|
|
2819
|
+
gl.vertexAttribPointer(aUvLoc, 2, gl.FLOAT, false, 16, 8);
|
|
2820
|
+
}
|
|
2821
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, quadIbo);
|
|
2822
|
+
const proj = mat3.screenToClip(ctx.widthCss, ctx.heightCss);
|
|
2823
|
+
const uProj = program.uniform("u_proj");
|
|
2824
|
+
if (uProj !== void 0) gl.uniformMatrix3fv(uProj, false, proj);
|
|
2825
|
+
const uBounds = program.uniform("u_bounds");
|
|
2826
|
+
if (uBounds !== void 0) {
|
|
2827
|
+
gl.uniform4f(uBounds, cmd.bounds.x, cmd.bounds.y, cmd.bounds.w, cmd.bounds.h);
|
|
2828
|
+
}
|
|
2829
|
+
const uView = program.uniform("u_view");
|
|
2830
|
+
if (uView !== void 0) gl.uniformMatrix3fv(uView, false, ctx.state.transform);
|
|
2831
|
+
const nextTexUnit = { value: 1 };
|
|
2832
|
+
for (const [name, value] of Object.entries(cmd.uniforms)) {
|
|
2833
|
+
const loc = program.uniform(name);
|
|
2834
|
+
if (loc === void 0) {
|
|
2835
|
+
warnOnceUniform(cmd.program.id, name);
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
setUniform(gl, loc, value, textureCache, nextTexUnit);
|
|
2839
|
+
}
|
|
2840
|
+
applyClipTest(ctx);
|
|
2841
|
+
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
|
2842
|
+
if (aPosLoc !== void 0) gl.disableVertexAttribArray(aPosLoc);
|
|
2843
|
+
if (aUvLoc !== void 0) gl.disableVertexAttribArray(aUvLoc);
|
|
2844
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
2845
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
|
2846
|
+
}
|
|
2847
|
+
function drawGroup(ctx, cmd) {
|
|
2848
|
+
ctx.state.push({
|
|
2849
|
+
transform: cmd.transform,
|
|
2850
|
+
alpha: cmd.alpha,
|
|
2851
|
+
colorMatrix: cmd.colorMatrix
|
|
2852
|
+
});
|
|
2853
|
+
if (cmd.clip) {
|
|
2854
|
+
const newDepth = ctx.clipDepth + 1;
|
|
2855
|
+
if (newDepth > 7) {
|
|
2856
|
+
ctx.state.pop();
|
|
2857
|
+
throw new Error(
|
|
2858
|
+
"weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
pushClip(ctx, cmd.clip, newDepth);
|
|
2862
|
+
ctx.clipDepth = newDepth;
|
|
2863
|
+
}
|
|
2864
|
+
for (const child of cmd.children) dispatch(ctx, child);
|
|
2518
2865
|
if (cmd.clip) {
|
|
2519
2866
|
popClip(ctx, cmd.clip, ctx.clipDepth - 1);
|
|
2520
2867
|
ctx.clipDepth -= 1;
|
|
@@ -2877,36 +3224,36 @@ function drawPathStrokeStenciled(ctx, cmd, align) {
|
|
|
2877
3224
|
gl.disable(gl.STENCIL_TEST);
|
|
2878
3225
|
gl.bindVertexArray(null);
|
|
2879
3226
|
}
|
|
3227
|
+
var OUTLINE_MIN_SCREEN_PX = 48;
|
|
3228
|
+
function modelScale(m) {
|
|
3229
|
+
const sx = Math.hypot(m[0], m[1]);
|
|
3230
|
+
const sy = Math.hypot(m[3], m[4]);
|
|
3231
|
+
return Math.sqrt(sx * sy) || 1;
|
|
3232
|
+
}
|
|
2880
3233
|
function drawText(ctx, cmd) {
|
|
2881
3234
|
const style = resolveTextStyle(cmd.style);
|
|
2882
3235
|
const lineHeight = style.lineHeight;
|
|
2883
3236
|
const align = cmd.align ?? style.align;
|
|
2884
3237
|
const maxWidth = cmd.maxWidth ?? Infinity;
|
|
2885
|
-
const
|
|
3238
|
+
const minScreen = ctx.textOutlineMinScreenSize ?? OUTLINE_MIN_SCREEN_PX;
|
|
3239
|
+
const outlineMinSize = Number.isFinite(minScreen) ? minScreen / modelScale(ctx.state.transform) : void 0;
|
|
3240
|
+
const laid = cachedLayoutRuns(
|
|
2886
3241
|
cmd.runs,
|
|
2887
|
-
{ maxWidth, lineHeight, align },
|
|
3242
|
+
{ maxWidth, lineHeight, align, outlineMinSize },
|
|
2888
3243
|
{ x: cmd.x, y: cmd.y }
|
|
2889
3244
|
);
|
|
2890
3245
|
if (laid.groups.length === 0 && laid.decorations.length === 0) return;
|
|
2891
3246
|
const dy = verticalAlignOffset(cmd.verticalAlign, cmd.height, laid.bounds.height);
|
|
2892
|
-
if (dy !== 0) {
|
|
2893
|
-
for (const group of laid.groups) {
|
|
2894
|
-
for (const q of group.quads) {
|
|
2895
|
-
q.y0 += dy;
|
|
2896
|
-
q.y1 += dy;
|
|
2897
|
-
q.baselineY += dy;
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
for (const d of laid.decorations) {
|
|
2901
|
-
d.y0 += dy;
|
|
2902
|
-
d.y1 += dy;
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
3247
|
const gl = ctx.gl;
|
|
2906
3248
|
applyClipTest(ctx);
|
|
2907
3249
|
const preparedPrograms = /* @__PURE__ */ new Set();
|
|
2908
3250
|
let currentProg = null;
|
|
2909
3251
|
for (const group of laid.groups) {
|
|
3252
|
+
if (group.source === "outline") {
|
|
3253
|
+
drawTextOutlineGroup(ctx, group, dy);
|
|
3254
|
+
currentProg = null;
|
|
3255
|
+
continue;
|
|
3256
|
+
}
|
|
2910
3257
|
const prog = group.source === "canvas" ? ctx.textSdfR8 : ctx.textSdf;
|
|
2911
3258
|
if (prog !== currentProg) {
|
|
2912
3259
|
gl.useProgram(prog.handle);
|
|
@@ -2918,11 +3265,48 @@ function drawText(ctx, cmd) {
|
|
|
2918
3265
|
setColorMatrixUniforms(ctx, prog);
|
|
2919
3266
|
gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
|
|
2920
3267
|
}
|
|
2921
|
-
drawTextGroup(ctx, group, prog);
|
|
3268
|
+
drawTextGroup(ctx, group, prog, dy);
|
|
2922
3269
|
}
|
|
2923
|
-
drawTextDecorations(ctx, laid.decorations);
|
|
3270
|
+
drawTextDecorations(ctx, laid.decorations, dy);
|
|
2924
3271
|
}
|
|
2925
|
-
|
|
3272
|
+
var SYNTHETIC_ITALIC_RADIANS = 0.2094;
|
|
3273
|
+
function drawTextOutlineGroup(ctx, group, dy) {
|
|
3274
|
+
const mesh = outlineGroupMesh(group, dy);
|
|
3275
|
+
if (!mesh) return;
|
|
3276
|
+
drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
|
|
3277
|
+
}
|
|
3278
|
+
function outlineGroupMesh(group, dy) {
|
|
3279
|
+
const parts = [];
|
|
3280
|
+
let vertexFloats = 0;
|
|
3281
|
+
let indexCount = 0;
|
|
3282
|
+
for (const glyph of group.glyphs) {
|
|
3283
|
+
const mesh = outlineMesh(glyph.key, glyph.d);
|
|
3284
|
+
if (mesh.indices.length === 0) continue;
|
|
3285
|
+
parts.push({ mesh, glyph });
|
|
3286
|
+
vertexFloats += mesh.vertices.length;
|
|
3287
|
+
indexCount += mesh.indices.length;
|
|
3288
|
+
}
|
|
3289
|
+
if (parts.length === 0) return null;
|
|
3290
|
+
const shear = group.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;
|
|
3291
|
+
const vertices = new Float32Array(vertexFloats);
|
|
3292
|
+
const indices = new Uint32Array(indexCount);
|
|
3293
|
+
let vi = 0;
|
|
3294
|
+
let ii = 0;
|
|
3295
|
+
let base = 0;
|
|
3296
|
+
for (const { mesh, glyph } of parts) {
|
|
3297
|
+
const { x, baselineY, scale: scale2 } = glyph;
|
|
3298
|
+
for (let k = 0; k < mesh.vertices.length; k += 2) {
|
|
3299
|
+
const ex = mesh.vertices[k];
|
|
3300
|
+
const ey = mesh.vertices[k + 1];
|
|
3301
|
+
vertices[vi++] = x + (ex - ey * shear) * scale2;
|
|
3302
|
+
vertices[vi++] = baselineY + dy + ey * scale2;
|
|
3303
|
+
}
|
|
3304
|
+
for (let k = 0; k < mesh.indices.length; k++) indices[ii++] = base + mesh.indices[k];
|
|
3305
|
+
base += mesh.vertices.length / 2;
|
|
3306
|
+
}
|
|
3307
|
+
return { vertices, indices };
|
|
3308
|
+
}
|
|
3309
|
+
function drawTextDecorations(ctx, decorations, dy) {
|
|
2926
3310
|
if (decorations.length === 0) return;
|
|
2927
3311
|
const batches = /* @__PURE__ */ new Map();
|
|
2928
3312
|
for (const d of decorations) {
|
|
@@ -2945,14 +3329,15 @@ function drawTextDecorations(ctx, decorations) {
|
|
|
2945
3329
|
const vertices = new Float32Array(rects.length * 4 * 2);
|
|
2946
3330
|
let vi = 0;
|
|
2947
3331
|
for (const d of rects) {
|
|
3332
|
+
const dy0 = d.y0 + dy, dy1 = d.y1 + dy;
|
|
2948
3333
|
vertices[vi++] = d.x0;
|
|
2949
|
-
vertices[vi++] =
|
|
3334
|
+
vertices[vi++] = dy0;
|
|
2950
3335
|
vertices[vi++] = d.x1;
|
|
2951
|
-
vertices[vi++] =
|
|
3336
|
+
vertices[vi++] = dy0;
|
|
2952
3337
|
vertices[vi++] = d.x0;
|
|
2953
|
-
vertices[vi++] =
|
|
3338
|
+
vertices[vi++] = dy1;
|
|
2954
3339
|
vertices[vi++] = d.x1;
|
|
2955
|
-
vertices[vi++] =
|
|
3340
|
+
vertices[vi++] = dy1;
|
|
2956
3341
|
}
|
|
2957
3342
|
const indices = new Uint32Array(rects.length * 6);
|
|
2958
3343
|
let ii = 0;
|
|
@@ -2990,7 +3375,7 @@ function drawTextDecorations(ctx, decorations) {
|
|
|
2990
3375
|
gl.deleteBuffer(ibo);
|
|
2991
3376
|
}
|
|
2992
3377
|
}
|
|
2993
|
-
function drawTextGroup(ctx, group, prog) {
|
|
3378
|
+
function drawTextGroup(ctx, group, prog, dy) {
|
|
2994
3379
|
if (group.source === "canvas") {
|
|
2995
3380
|
if (!syncDynamicPageTexture(ctx.textureCache, group.page)) return;
|
|
2996
3381
|
} else {
|
|
@@ -3001,26 +3386,27 @@ function drawTextGroup(ctx, group, prog) {
|
|
|
3001
3386
|
const vertices = new Float32Array(group.quads.length * 4 * 5);
|
|
3002
3387
|
let vi = 0;
|
|
3003
3388
|
for (const q of group.quads) {
|
|
3389
|
+
const y0 = q.y0 + dy, y1 = q.y1 + dy, by = q.baselineY + dy;
|
|
3004
3390
|
vertices[vi++] = q.x0;
|
|
3005
|
-
vertices[vi++] =
|
|
3391
|
+
vertices[vi++] = y0;
|
|
3006
3392
|
vertices[vi++] = q.u0;
|
|
3007
3393
|
vertices[vi++] = q.v0;
|
|
3008
|
-
vertices[vi++] =
|
|
3394
|
+
vertices[vi++] = by;
|
|
3009
3395
|
vertices[vi++] = q.x1;
|
|
3010
|
-
vertices[vi++] =
|
|
3396
|
+
vertices[vi++] = y0;
|
|
3011
3397
|
vertices[vi++] = q.u1;
|
|
3012
3398
|
vertices[vi++] = q.v0;
|
|
3013
|
-
vertices[vi++] =
|
|
3399
|
+
vertices[vi++] = by;
|
|
3014
3400
|
vertices[vi++] = q.x0;
|
|
3015
|
-
vertices[vi++] =
|
|
3401
|
+
vertices[vi++] = y1;
|
|
3016
3402
|
vertices[vi++] = q.u0;
|
|
3017
3403
|
vertices[vi++] = q.v1;
|
|
3018
|
-
vertices[vi++] =
|
|
3404
|
+
vertices[vi++] = by;
|
|
3019
3405
|
vertices[vi++] = q.x1;
|
|
3020
|
-
vertices[vi++] =
|
|
3406
|
+
vertices[vi++] = y1;
|
|
3021
3407
|
vertices[vi++] = q.u1;
|
|
3022
3408
|
vertices[vi++] = q.v1;
|
|
3023
|
-
vertices[vi++] =
|
|
3409
|
+
vertices[vi++] = by;
|
|
3024
3410
|
}
|
|
3025
3411
|
const indices = new Uint32Array(group.quads.length * 6);
|
|
3026
3412
|
let ii = 0;
|
|
@@ -3068,7 +3454,7 @@ function drawTextGroup(ctx, group, prog) {
|
|
|
3068
3454
|
const synthBoldAmount = group.synthetic.bold ? 0.08 : 0;
|
|
3069
3455
|
const uSynthBold = prog.uniform("u_synthBold");
|
|
3070
3456
|
if (uSynthBold !== void 0) gl.uniform1f(uSynthBold, synthBoldAmount);
|
|
3071
|
-
const synthItalicAmount = group.synthetic.italic ?
|
|
3457
|
+
const synthItalicAmount = group.synthetic.italic ? SYNTHETIC_ITALIC_RADIANS : 0;
|
|
3072
3458
|
const uSynthItalic = prog.uniform("u_synthItalic");
|
|
3073
3459
|
if (uSynthItalic !== void 0) gl.uniform1f(uSynthItalic, synthItalicAmount);
|
|
3074
3460
|
const texId = group.source === "canvas" ? dynamicPageTextureId(group.page) : textureCacheKey(group.family, group.weight, group.style);
|
|
@@ -3249,6 +3635,7 @@ var WeaselRenderer = class {
|
|
|
3249
3635
|
imageMinification;
|
|
3250
3636
|
flattenTolerance;
|
|
3251
3637
|
bakeBudget;
|
|
3638
|
+
textOutlineMinScreenSize;
|
|
3252
3639
|
contextLost = false;
|
|
3253
3640
|
boundOnLost = (e) => this.onContextLost(e);
|
|
3254
3641
|
boundOnRestored = () => this.onContextRestored();
|
|
@@ -3268,6 +3655,7 @@ var WeaselRenderer = class {
|
|
|
3268
3655
|
this.imageMinification = opts.imageMinification ?? "linear";
|
|
3269
3656
|
this.flattenTolerance = opts.flattenTolerance;
|
|
3270
3657
|
this.bakeBudget = opts.bakeBudget ?? DEFAULT_BAKE_BUDGET;
|
|
3658
|
+
this.textOutlineMinScreenSize = opts.textOutlineMinScreenSize ?? OUTLINE_MIN_SCREEN_PX;
|
|
3271
3659
|
this.canvas = opts.canvas ?? null;
|
|
3272
3660
|
if (this.canvas) {
|
|
3273
3661
|
this.canvas.width = Math.round(opts.width * opts.dpr);
|
|
@@ -3511,7 +3899,8 @@ var WeaselRenderer = class {
|
|
|
3511
3899
|
widthCss: this.widthCss,
|
|
3512
3900
|
heightCss: this.heightCss,
|
|
3513
3901
|
clipDepth: 0,
|
|
3514
|
-
flattenTolerance: this.flattenTolerance
|
|
3902
|
+
flattenTolerance: this.flattenTolerance,
|
|
3903
|
+
textOutlineMinScreenSize: this.textOutlineMinScreenSize
|
|
3515
3904
|
};
|
|
3516
3905
|
for (const cmd of commands) dispatch(ctx, cmd);
|
|
3517
3906
|
this.meshCache.freeTransient();
|
|
@@ -8058,15 +8447,25 @@ var reorderForwardAction = {
|
|
|
8058
8447
|
group: "reorder",
|
|
8059
8448
|
defaultBinding: [
|
|
8060
8449
|
{
|
|
8061
|
-
|
|
8450
|
+
// Modifier matching is strict, so Shift must be absent here — listing
|
|
8451
|
+
// the shifted `'}'` on this binding would be dead weight, since you
|
|
8452
|
+
// can't type it without the Shift this spec forbids.
|
|
8453
|
+
spec: { kind: "key", key: ["]"], mods: { mod: true } },
|
|
8062
8454
|
opts: { params: { distance: "adjacent" } }
|
|
8063
8455
|
},
|
|
8064
8456
|
{
|
|
8065
|
-
// Cmd+
|
|
8066
|
-
//
|
|
8067
|
-
//
|
|
8068
|
-
//
|
|
8069
|
-
//
|
|
8457
|
+
// Cmd+Shift+] → bring-to-front, the convention every drawing app uses.
|
|
8458
|
+
// With Shift held the US-layout key reports as '}', so both characters
|
|
8459
|
+
// are listed. Some browsers bind Cmd+Shift+[ / ] to tab switching and
|
|
8460
|
+
// may consume the event before the page sees it — hence the Cmd+Alt
|
|
8461
|
+
// binding below, which nothing reserves.
|
|
8462
|
+
spec: { kind: "key", key: ["]", "}"], mods: { mod: true, shift: true } },
|
|
8463
|
+
opts: { params: { distance: "extreme" } }
|
|
8464
|
+
},
|
|
8465
|
+
{
|
|
8466
|
+
// Cmd+Alt+] → bring-to-front. On macOS Option+] emits '‘' (U+2018);
|
|
8467
|
+
// whether Cmd suppresses that transform is browser-dependent, so match
|
|
8468
|
+
// both the bracket and the Option-produced char (US layout). matchKey is
|
|
8070
8469
|
// character-based, so non-US layouts may need a rebind.
|
|
8071
8470
|
spec: { kind: "key", key: ["]", "\u2018"], mods: { mod: true, alt: true } },
|
|
8072
8471
|
opts: { params: { distance: "extreme" } }
|
|
@@ -8093,13 +8492,17 @@ var reorderBackwardAction = {
|
|
|
8093
8492
|
group: "reorder",
|
|
8094
8493
|
defaultBinding: [
|
|
8095
8494
|
{
|
|
8096
|
-
spec: { kind: "key", key: ["["
|
|
8495
|
+
spec: { kind: "key", key: ["["], mods: { mod: true } },
|
|
8097
8496
|
opts: { params: { distance: "adjacent" } }
|
|
8098
8497
|
},
|
|
8099
8498
|
{
|
|
8100
|
-
// Cmd+
|
|
8101
|
-
|
|
8102
|
-
|
|
8499
|
+
// Cmd+Shift+[ → send-to-back. See the bring-to-front notes above.
|
|
8500
|
+
spec: { kind: "key", key: ["[", "{"], mods: { mod: true, shift: true } },
|
|
8501
|
+
opts: { params: { distance: "extreme" } }
|
|
8502
|
+
},
|
|
8503
|
+
{
|
|
8504
|
+
// Cmd+Alt+[ → send-to-back, the binding no browser reserves. On macOS
|
|
8505
|
+
// Option+[ emits '“' (U+201C), included alongside the bracket.
|
|
8103
8506
|
spec: { kind: "key", key: ["[", "\u201C"], mods: { mod: true, alt: true } },
|
|
8104
8507
|
opts: { params: { distance: "extreme" } }
|
|
8105
8508
|
}
|
|
@@ -10437,6 +10840,9 @@ function useHoverTracking(args) {
|
|
|
10437
10840
|
}
|
|
10438
10841
|
|
|
10439
10842
|
// src/interactions/dispatcher/dispatcher.ts
|
|
10843
|
+
function pointerGestureId(pointerId) {
|
|
10844
|
+
return `pointer-${pointerId ?? "mouse"}`;
|
|
10845
|
+
}
|
|
10440
10846
|
var DEV2 = (() => {
|
|
10441
10847
|
try {
|
|
10442
10848
|
return Boolean(import.meta.env?.DEV);
|
|
@@ -10577,7 +10983,7 @@ function createDispatcher(opts) {
|
|
|
10577
10983
|
return `key-held-${event.key}`;
|
|
10578
10984
|
}
|
|
10579
10985
|
if (event.kind === "pointerdown" || event.kind === "pointermove" || event.kind === "pointerup" || event.kind === "pointercancel") {
|
|
10580
|
-
return
|
|
10986
|
+
return pointerGestureId(event.pointerId);
|
|
10581
10987
|
}
|
|
10582
10988
|
if (event.kind === "multitouch") {
|
|
10583
10989
|
return `multitouch-${event.fingers}`;
|
|
@@ -11314,6 +11720,7 @@ function useGestureDispatcher(opts) {
|
|
|
11314
11720
|
const ev = {
|
|
11315
11721
|
kind: "pointerdown",
|
|
11316
11722
|
target: e.target,
|
|
11723
|
+
pointerId: e.pointerId,
|
|
11317
11724
|
x: w.x,
|
|
11318
11725
|
y: w.y,
|
|
11319
11726
|
clientX: e.clientX,
|
|
@@ -11343,6 +11750,7 @@ function useGestureDispatcher(opts) {
|
|
|
11343
11750
|
shiftKey: e.shiftKey
|
|
11344
11751
|
});
|
|
11345
11752
|
if (activePointers.size >= 2) {
|
|
11753
|
+
for (const id of activePointers) bufferedDown.delete(id);
|
|
11346
11754
|
const { centroid, spread } = computeMultiTouchGeometry(pointerPositions);
|
|
11347
11755
|
if (!multiTouchStart) {
|
|
11348
11756
|
multiTouchStart = { fingers: activePointers.size, centroid: { ...centroid } };
|
|
@@ -11393,6 +11801,7 @@ function useGestureDispatcher(opts) {
|
|
|
11393
11801
|
const w = toWorld(e.clientX, e.clientY);
|
|
11394
11802
|
const ev = {
|
|
11395
11803
|
kind: "pointermove",
|
|
11804
|
+
pointerId: e.pointerId,
|
|
11396
11805
|
x: w.x,
|
|
11397
11806
|
y: w.y,
|
|
11398
11807
|
clientX: e.clientX,
|
|
@@ -11516,10 +11925,11 @@ function useGestureDispatcher(opts) {
|
|
|
11516
11925
|
}
|
|
11517
11926
|
const downForClick = lastPointerDown.get(e.pointerId);
|
|
11518
11927
|
const movedDuringDrag = downForClick ? downForClick.clientX !== e.clientX || downForClick.clientY !== e.clientY : true;
|
|
11519
|
-
const hadDragInFlight = dispatcher.inFlight().has(
|
|
11928
|
+
const hadDragInFlight = dispatcher.inFlight().has(pointerGestureId(e.pointerId)) && movedDuringDrag;
|
|
11520
11929
|
const wUp = toWorld(e.clientX, e.clientY);
|
|
11521
11930
|
const ev = {
|
|
11522
11931
|
kind: "pointerup",
|
|
11932
|
+
pointerId: e.pointerId,
|
|
11523
11933
|
x: wUp.x,
|
|
11524
11934
|
y: wUp.y,
|
|
11525
11935
|
clientX: e.clientX,
|
|
@@ -11594,6 +12004,7 @@ function useGestureDispatcher(opts) {
|
|
|
11594
12004
|
multiTouchStart = null;
|
|
11595
12005
|
const ev = {
|
|
11596
12006
|
kind: "pointercancel",
|
|
12007
|
+
pointerId: e.pointerId,
|
|
11597
12008
|
altKey: e.altKey,
|
|
11598
12009
|
ctrlKey: e.ctrlKey,
|
|
11599
12010
|
metaKey: e.metaKey,
|
|
@@ -12062,6 +12473,17 @@ function useTools(opts) {
|
|
|
12062
12473
|
if (hotkeyRef.current) dlog("tools", "hotkey disengaged:", hotkeyRef.current);
|
|
12063
12474
|
ctx.popHotkey();
|
|
12064
12475
|
}, [ctx]);
|
|
12476
|
+
const lastConflictSigRef = useRef(null);
|
|
12477
|
+
const toolSetSig = Object.keys(opts.registry).join(",") + "|" + (opts.ambient ?? []).map((t) => t.id).join(",");
|
|
12478
|
+
useEffect(() => {
|
|
12479
|
+
if (process.env.NODE_ENV === "production") return;
|
|
12480
|
+
if (lastConflictSigRef.current === toolSetSig) return;
|
|
12481
|
+
lastConflictSigRef.current = toolSetSig;
|
|
12482
|
+
reportRouteConflicts({
|
|
12483
|
+
registry: registryRef.current,
|
|
12484
|
+
ambient: ambientRef.current
|
|
12485
|
+
});
|
|
12486
|
+
}, [toolSetSig]);
|
|
12065
12487
|
const has = useCallback(
|
|
12066
12488
|
(id) => id in registryRef.current || ambientRef.current.some((t) => t.id === id),
|
|
12067
12489
|
[]
|
|
@@ -12670,17 +13092,123 @@ function pathContainsPolygon(path, polygon) {
|
|
|
12670
13092
|
(p) => p.x >= path.x && p.x <= path.x + path.width && p.y >= path.y && p.y <= path.y + path.height
|
|
12671
13093
|
);
|
|
12672
13094
|
}
|
|
12673
|
-
return polygonContainsPolygon(extractVertices(path), polygon);
|
|
13095
|
+
return polygonContainsPolygon(extractVertices(path), polygon);
|
|
13096
|
+
}
|
|
13097
|
+
function pathIntersectsPolygon(path, polygon) {
|
|
13098
|
+
if (path.kind === "rect") {
|
|
13099
|
+
return polygonsIntersect(rectToVerts(path), polygon);
|
|
13100
|
+
}
|
|
13101
|
+
return polygonsIntersect(extractVertices(path), polygon);
|
|
13102
|
+
}
|
|
13103
|
+
|
|
13104
|
+
// src/features/text/runs.ts
|
|
13105
|
+
function toRuns(input) {
|
|
13106
|
+
if (typeof input === "string") {
|
|
13107
|
+
return input.length === 0 ? [] : [{ text: input }];
|
|
13108
|
+
}
|
|
13109
|
+
for (let i = 0; i < input.length; i++) {
|
|
13110
|
+
const r = input[i];
|
|
13111
|
+
if (typeof r?.text !== "string") {
|
|
13112
|
+
throw new Error(`toRuns: run at index ${i} is missing string \`text\``);
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
return input;
|
|
13116
|
+
}
|
|
13117
|
+
function runsToPlainText(runs) {
|
|
13118
|
+
let out = "";
|
|
13119
|
+
for (const r of runs) out += r.text;
|
|
13120
|
+
return out;
|
|
13121
|
+
}
|
|
13122
|
+
function escapeMarkdown(s) {
|
|
13123
|
+
return s.replace(/\\/g, "\\\\").replace(/\*/g, "\\*");
|
|
13124
|
+
}
|
|
13125
|
+
function runsToMarkdown(runs) {
|
|
13126
|
+
let out = "";
|
|
13127
|
+
for (const r of runs) {
|
|
13128
|
+
const escaped = escapeMarkdown(r.text);
|
|
13129
|
+
if (r.bold && r.italic) out += `***${escaped}***`;
|
|
13130
|
+
else if (r.bold) out += `**${escaped}**`;
|
|
13131
|
+
else if (r.italic) out += `*${escaped}*`;
|
|
13132
|
+
else out += escaped;
|
|
13133
|
+
}
|
|
13134
|
+
return out;
|
|
13135
|
+
}
|
|
13136
|
+
function markdownToRuns(input) {
|
|
13137
|
+
const runs = [];
|
|
13138
|
+
let bold = false;
|
|
13139
|
+
let italic = false;
|
|
13140
|
+
let buf = "";
|
|
13141
|
+
let i = 0;
|
|
13142
|
+
function flush() {
|
|
13143
|
+
if (buf.length === 0) return;
|
|
13144
|
+
const run = { text: buf };
|
|
13145
|
+
if (bold) run.bold = true;
|
|
13146
|
+
if (italic) run.italic = true;
|
|
13147
|
+
runs.push(run);
|
|
13148
|
+
buf = "";
|
|
13149
|
+
}
|
|
13150
|
+
while (i < input.length) {
|
|
13151
|
+
const ch = input[i];
|
|
13152
|
+
if (ch === "\\" && i + 1 < input.length && "*\\".includes(input[i + 1])) {
|
|
13153
|
+
buf += input[i + 1];
|
|
13154
|
+
i += 2;
|
|
13155
|
+
continue;
|
|
13156
|
+
}
|
|
13157
|
+
if (ch === "*") {
|
|
13158
|
+
let count = 0;
|
|
13159
|
+
while (i + count < input.length && input[i + count] === "*") count++;
|
|
13160
|
+
flush();
|
|
13161
|
+
if (count >= 3) {
|
|
13162
|
+
bold = !bold;
|
|
13163
|
+
italic = !italic;
|
|
13164
|
+
i += 3;
|
|
13165
|
+
} else if (count === 2) {
|
|
13166
|
+
bold = !bold;
|
|
13167
|
+
i += 2;
|
|
13168
|
+
} else {
|
|
13169
|
+
italic = !italic;
|
|
13170
|
+
i += 1;
|
|
13171
|
+
}
|
|
13172
|
+
continue;
|
|
13173
|
+
}
|
|
13174
|
+
buf += ch;
|
|
13175
|
+
i++;
|
|
13176
|
+
}
|
|
13177
|
+
flush();
|
|
13178
|
+
return runs;
|
|
12674
13179
|
}
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
13180
|
+
|
|
13181
|
+
// src/features/text/lineBoxes.ts
|
|
13182
|
+
function textLineBoxes(pose, opts = {}) {
|
|
13183
|
+
const padding = opts.padding ?? 0;
|
|
13184
|
+
const style = resolveTextStyle(pose.style);
|
|
13185
|
+
const source = pose.runs && pose.runs.length > 0 ? pose.runs : pose.text;
|
|
13186
|
+
const runs = resolveRuns(toRuns(source), style);
|
|
13187
|
+
const laid = layoutRuns(
|
|
13188
|
+
runs,
|
|
13189
|
+
{
|
|
13190
|
+
maxWidth: opts.maxWidth ?? pose.width,
|
|
13191
|
+
lineHeight: style.lineHeight,
|
|
13192
|
+
align: style.align
|
|
13193
|
+
},
|
|
13194
|
+
{ x: pose.x, y: pose.y }
|
|
13195
|
+
);
|
|
13196
|
+
const dy = verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);
|
|
13197
|
+
const out = [];
|
|
13198
|
+
for (const line of laid.lines) {
|
|
13199
|
+
if (!opts.includeEmpty && line.x1 <= line.x0) continue;
|
|
13200
|
+
out.push({
|
|
13201
|
+
x: line.x0 - padding,
|
|
13202
|
+
y: line.y0 + dy - padding,
|
|
13203
|
+
width: line.x1 - line.x0 + padding * 2,
|
|
13204
|
+
height: line.y1 - line.y0 + padding * 2
|
|
13205
|
+
});
|
|
12678
13206
|
}
|
|
12679
|
-
return
|
|
13207
|
+
return out;
|
|
12680
13208
|
}
|
|
12681
13209
|
|
|
12682
13210
|
// src/features/images/imageCache.ts
|
|
12683
|
-
var
|
|
13211
|
+
var cache4 = /* @__PURE__ */ new Map();
|
|
12684
13212
|
var subscribers = /* @__PURE__ */ new Set();
|
|
12685
13213
|
var defaultLoader = async (src) => {
|
|
12686
13214
|
if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
|
|
@@ -12698,28 +13226,28 @@ function notify() {
|
|
|
12698
13226
|
}
|
|
12699
13227
|
function startLoad(src) {
|
|
12700
13228
|
const entry = { status: "loading" };
|
|
12701
|
-
|
|
13229
|
+
cache4.set(src, entry);
|
|
12702
13230
|
loader(src).then(
|
|
12703
13231
|
(bitmap) => {
|
|
12704
|
-
if (
|
|
12705
|
-
|
|
13232
|
+
if (cache4.get(src) !== entry) return;
|
|
13233
|
+
cache4.set(src, { status: "ready", bitmap });
|
|
12706
13234
|
notify();
|
|
12707
13235
|
},
|
|
12708
13236
|
() => {
|
|
12709
|
-
if (
|
|
12710
|
-
|
|
13237
|
+
if (cache4.get(src) !== entry) return;
|
|
13238
|
+
cache4.set(src, { status: "error" });
|
|
12711
13239
|
notify();
|
|
12712
13240
|
}
|
|
12713
13241
|
);
|
|
12714
13242
|
}
|
|
12715
13243
|
function getImageBitmap(src) {
|
|
12716
|
-
const entry =
|
|
13244
|
+
const entry = cache4.get(src);
|
|
12717
13245
|
if (entry) return entry.bitmap;
|
|
12718
13246
|
startLoad(src);
|
|
12719
13247
|
return void 0;
|
|
12720
13248
|
}
|
|
12721
13249
|
function imageStatus(src) {
|
|
12722
|
-
return
|
|
13250
|
+
return cache4.get(src)?.status ?? "idle";
|
|
12723
13251
|
}
|
|
12724
13252
|
function subscribeImageReady(cb) {
|
|
12725
13253
|
subscribers.add(cb);
|
|
@@ -12728,7 +13256,39 @@ function subscribeImageReady(cb) {
|
|
|
12728
13256
|
};
|
|
12729
13257
|
}
|
|
12730
13258
|
|
|
13259
|
+
// src/core/scene/nodeMemo.ts
|
|
13260
|
+
var MEMO = /* @__PURE__ */ new WeakMap();
|
|
13261
|
+
var generation = 0;
|
|
13262
|
+
function nodeMemo(node, slot, pose, derive) {
|
|
13263
|
+
let record = MEMO.get(node);
|
|
13264
|
+
if (record === void 0) {
|
|
13265
|
+
record = { generation, slots: /* @__PURE__ */ new Map() };
|
|
13266
|
+
MEMO.set(node, record);
|
|
13267
|
+
} else if (record.generation !== generation) {
|
|
13268
|
+
record.generation = generation;
|
|
13269
|
+
record.slots.clear();
|
|
13270
|
+
}
|
|
13271
|
+
const data = node.data;
|
|
13272
|
+
const hit = record.slots.get(slot);
|
|
13273
|
+
if (hit !== void 0 && hit.pose === pose && hit.data === data) {
|
|
13274
|
+
return hit.value;
|
|
13275
|
+
}
|
|
13276
|
+
const value = derive();
|
|
13277
|
+
if (hit !== void 0) {
|
|
13278
|
+
hit.pose = pose;
|
|
13279
|
+
hit.data = data;
|
|
13280
|
+
hit.value = value;
|
|
13281
|
+
} else {
|
|
13282
|
+
record.slots.set(slot, { pose, data, value });
|
|
13283
|
+
}
|
|
13284
|
+
return value;
|
|
13285
|
+
}
|
|
13286
|
+
function bumpNodeMemoGeneration() {
|
|
13287
|
+
generation++;
|
|
13288
|
+
}
|
|
13289
|
+
|
|
12731
13290
|
// src/canvas/NodeShape.ts
|
|
13291
|
+
var DEFAULT_INK = { filled: true, strokeWidth: 0 };
|
|
12732
13292
|
var PAINTERS = {
|
|
12733
13293
|
high: [],
|
|
12734
13294
|
normal: []
|
|
@@ -12736,12 +13296,25 @@ var PAINTERS = {
|
|
|
12736
13296
|
function registerNodeShape(painter, opts = {}) {
|
|
12737
13297
|
const list = opts.priority === "high" ? PAINTERS.high : PAINTERS.normal;
|
|
12738
13298
|
list.push(painter);
|
|
13299
|
+
bumpNodeMemoGeneration();
|
|
12739
13300
|
return () => {
|
|
12740
13301
|
const i = list.indexOf(painter);
|
|
12741
13302
|
if (i >= 0) list.splice(i, 1);
|
|
13303
|
+
bumpNodeMemoGeneration();
|
|
12742
13304
|
};
|
|
12743
13305
|
}
|
|
13306
|
+
var PAINTER_SLOT = "shape:painter";
|
|
13307
|
+
var SILHOUETTE_SLOT = "shape:silhouette";
|
|
13308
|
+
var PAINT_SLOT = "shape:paint";
|
|
12744
13309
|
function findNodeShape(node) {
|
|
13310
|
+
return nodeMemo(
|
|
13311
|
+
node,
|
|
13312
|
+
PAINTER_SLOT,
|
|
13313
|
+
void 0,
|
|
13314
|
+
() => matchNodeShape(node)
|
|
13315
|
+
);
|
|
13316
|
+
}
|
|
13317
|
+
function matchNodeShape(node) {
|
|
12745
13318
|
for (const p of PAINTERS.high) {
|
|
12746
13319
|
if (p.matches(node)) {
|
|
12747
13320
|
return p;
|
|
@@ -12755,10 +13328,22 @@ function findNodeShape(node) {
|
|
|
12755
13328
|
return void 0;
|
|
12756
13329
|
}
|
|
12757
13330
|
function findShapeSilhouette(node, pose) {
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
13331
|
+
return nodeMemo(node, SILHOUETTE_SLOT, pose, () => {
|
|
13332
|
+
const sil = findNodeShape(node)?.silhouette?.(node, pose) ?? null;
|
|
13333
|
+
const r = sil ? poseRotationOf(pose) : null;
|
|
13334
|
+
return sil && r ? rotatePathAround(sil, r.cx, r.cy, r.rotation) : sil;
|
|
13335
|
+
});
|
|
13336
|
+
}
|
|
13337
|
+
function findShapeInk(node, pose) {
|
|
13338
|
+
return findNodeShape(node)?.ink?.(node, pose) ?? null;
|
|
13339
|
+
}
|
|
13340
|
+
function shapeCoversPoint(node, pose, x, y, opts = {}) {
|
|
13341
|
+
const sil = findShapeSilhouette(node, pose);
|
|
13342
|
+
if (sil === null) return true;
|
|
13343
|
+
const ink = findShapeInk(node, pose) ?? DEFAULT_INK;
|
|
13344
|
+
if (ink.filled && pathContainsPoint(sil, x, y)) return true;
|
|
13345
|
+
const reach = ink.strokeWidth / 2 + (opts.tolerance ?? 0);
|
|
13346
|
+
return reach > 0 && strokeHitTest(sil, x, y, reach);
|
|
12762
13347
|
}
|
|
12763
13348
|
function getNodeShapes() {
|
|
12764
13349
|
return [...PAINTERS.high, ...PAINTERS.normal];
|
|
@@ -12769,21 +13354,86 @@ var TEXT_PAINTER = {
|
|
|
12769
13354
|
const d = node.data;
|
|
12770
13355
|
return d?.text != null;
|
|
12771
13356
|
},
|
|
12772
|
-
|
|
13357
|
+
// Memoized, and not for its own sake — `resolveTextStyle` + `resolveRuns`
|
|
13358
|
+
// are cheap (0.14 ms/frame at 1000 text nodes). It is the enabler: the
|
|
13359
|
+
// renderer's layout cache keys on the `ResolvedRun[]` array identity, and
|
|
13360
|
+
// `layoutRuns` costs 25.9 ms/frame for 200 wrapped paragraphs. Handing back
|
|
13361
|
+
// a fresh runs array per frame would leave that cache missing on every
|
|
13362
|
+
// draw, exactly as a fresh `Path` per frame defeated the mesh cache. Both
|
|
13363
|
+
// resolvers are pure style merging — no font reads — so `(data, pose)` is
|
|
13364
|
+
// the whole key. See PAINT_SLOT.
|
|
13365
|
+
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
12773
13366
|
const d = node.data;
|
|
12774
13367
|
const p = pose;
|
|
12775
|
-
const
|
|
12776
|
-
const y = p.y + fontSize;
|
|
13368
|
+
const y = p.y;
|
|
12777
13369
|
return d.runs && d.runs.length > 0 ? [textCommandFromRuns(p.x, y, d.runs, d.style, void 0, p.height)] : [textCommand(p.x, y, d.text, d.style, void 0, p.height)];
|
|
13370
|
+
}),
|
|
13371
|
+
// The pose is a *wrap box*, not a bounding box — "Away" in a 300-unit box
|
|
13372
|
+
// leaves most of it empty, and a pose-rect silhouette claims all of it. The
|
|
13373
|
+
// union of the line boxes is what the node actually covers, so picking,
|
|
13374
|
+
// lasso and clipping stop grabbing blank space. `Infinity` because `paint`
|
|
13375
|
+
// above deliberately does not forward `maxWidth`: this text does not wrap,
|
|
13376
|
+
// and boxes measured against `p.width` would wrap where the paint did not.
|
|
13377
|
+
//
|
|
13378
|
+
// `null` rather than an empty path when there are no non-blank lines: an
|
|
13379
|
+
// empty text node would otherwise become unpickable, and a caller reading
|
|
13380
|
+
// "no silhouette" falls back to the pose rect, which is the behavior an
|
|
13381
|
+
// empty box wants.
|
|
13382
|
+
silhouette: (node, pose) => {
|
|
13383
|
+
const d = node.data;
|
|
13384
|
+
const p = pose;
|
|
13385
|
+
const boxes = textLineBoxes(
|
|
13386
|
+
{
|
|
13387
|
+
x: p.x,
|
|
13388
|
+
y: p.y,
|
|
13389
|
+
width: p.width,
|
|
13390
|
+
height: p.height,
|
|
13391
|
+
text: d.text,
|
|
13392
|
+
runs: d.runs,
|
|
13393
|
+
style: d.style
|
|
13394
|
+
},
|
|
13395
|
+
{ maxWidth: Infinity }
|
|
13396
|
+
);
|
|
13397
|
+
if (boxes.length === 0) return null;
|
|
13398
|
+
return rectsToPath(boxes);
|
|
12778
13399
|
}
|
|
12779
13400
|
};
|
|
13401
|
+
function rectsToPath(rects) {
|
|
13402
|
+
const commands = new Uint8Array(rects.length * 6);
|
|
13403
|
+
const coords = new Float32Array(rects.length * 10);
|
|
13404
|
+
let ci = 0;
|
|
13405
|
+
let cmd = 0;
|
|
13406
|
+
for (const r of rects) {
|
|
13407
|
+
const { x, y, width: w, height: h } = r;
|
|
13408
|
+
commands[cmd++] = PATH_M;
|
|
13409
|
+
coords[ci++] = x;
|
|
13410
|
+
coords[ci++] = y;
|
|
13411
|
+
commands[cmd++] = PATH_L;
|
|
13412
|
+
coords[ci++] = x + w;
|
|
13413
|
+
coords[ci++] = y;
|
|
13414
|
+
commands[cmd++] = PATH_L;
|
|
13415
|
+
coords[ci++] = x + w;
|
|
13416
|
+
coords[ci++] = y + h;
|
|
13417
|
+
commands[cmd++] = PATH_L;
|
|
13418
|
+
coords[ci++] = x;
|
|
13419
|
+
coords[ci++] = y + h;
|
|
13420
|
+
commands[cmd++] = PATH_L;
|
|
13421
|
+
coords[ci++] = x;
|
|
13422
|
+
coords[ci++] = y;
|
|
13423
|
+
commands[cmd++] = PATH_Z;
|
|
13424
|
+
}
|
|
13425
|
+
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
13426
|
+
}
|
|
12780
13427
|
var PATH_PAINTER = {
|
|
12781
13428
|
id: "kit:path",
|
|
12782
13429
|
matches: (node) => {
|
|
12783
13430
|
const d = node.data;
|
|
12784
13431
|
return d?.path != null;
|
|
12785
13432
|
},
|
|
12786
|
-
|
|
13433
|
+
// Memoized: a pure function of `(data, pose)`, and `pathInPoseFrame`
|
|
13434
|
+
// allocates on every call but the identity (`kind: 'rect'`) branch. See
|
|
13435
|
+
// PAINT_SLOT.
|
|
13436
|
+
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
12787
13437
|
const d = node.data;
|
|
12788
13438
|
const projected = pathInPoseFrame(d.path, pose);
|
|
12789
13439
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
@@ -12796,10 +13446,23 @@ var PATH_PAINTER = {
|
|
|
12796
13446
|
...hasStroke ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
12797
13447
|
};
|
|
12798
13448
|
return [cmd];
|
|
12799
|
-
},
|
|
13449
|
+
}),
|
|
12800
13450
|
silhouette: (node, pose) => {
|
|
12801
13451
|
const d = node.data;
|
|
12802
13452
|
return pathInPoseFrame(d.path, pose);
|
|
13453
|
+
},
|
|
13454
|
+
// Mirrors the fill/stroke decisions `paint` makes above — same reads, same
|
|
13455
|
+
// 'none' handling, same "no fill declared and a stroke present means
|
|
13456
|
+
// stroke-only" fallback. Kept in step with `paint` by
|
|
13457
|
+
// `NodeShape.ink.test.ts`, which asserts the two agree.
|
|
13458
|
+
ink: (node) => {
|
|
13459
|
+
const d = node.data;
|
|
13460
|
+
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
13461
|
+
const fillColor = d.fill ?? d.color;
|
|
13462
|
+
return {
|
|
13463
|
+
filled: fillColor !== "none" && (fillColor !== void 0 || !hasStroke),
|
|
13464
|
+
strokeWidth: hasStroke ? d.strokeWidth ?? 1 : 0
|
|
13465
|
+
};
|
|
12803
13466
|
}
|
|
12804
13467
|
};
|
|
12805
13468
|
var SHAPE_PAINTER = {
|
|
@@ -12808,7 +13471,10 @@ var SHAPE_PAINTER = {
|
|
|
12808
13471
|
const s = node.data?.shape;
|
|
12809
13472
|
return s != null && SHAPE_KINDS.has(s);
|
|
12810
13473
|
},
|
|
12811
|
-
|
|
13474
|
+
// Memoized: a pure function of `(data, pose)`, and `pathForShape` builds a
|
|
13475
|
+
// fresh `Uint8Array` + `Float32Array` for every ellipse, polygon and star.
|
|
13476
|
+
// See PAINT_SLOT.
|
|
13477
|
+
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
12812
13478
|
const d = node.data;
|
|
12813
13479
|
const path = pathForShape(d, pose);
|
|
12814
13480
|
return [{
|
|
@@ -12817,10 +13483,17 @@ var SHAPE_PAINTER = {
|
|
|
12817
13483
|
fill: { color: d.fill ?? d.color ?? "#888" },
|
|
12818
13484
|
...d.stroke && (d.strokeWidth ?? 0) > 0 ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
12819
13485
|
}];
|
|
12820
|
-
},
|
|
13486
|
+
}),
|
|
12821
13487
|
silhouette: (node, pose) => {
|
|
12822
13488
|
const d = node.data;
|
|
12823
13489
|
return pathForShape(d, pose);
|
|
13490
|
+
},
|
|
13491
|
+
// `paint` above always emits a fill (defaulting to '#888'), so this shape
|
|
13492
|
+
// family is always filled — unlike `kit:path`, it has no stroke-only form.
|
|
13493
|
+
ink: (node) => {
|
|
13494
|
+
const d = node.data;
|
|
13495
|
+
const hasStroke = !!d.stroke && (d.strokeWidth ?? 0) > 0;
|
|
13496
|
+
return { filled: true, strokeWidth: hasStroke ? d.strokeWidth ?? 1 : 0 };
|
|
12824
13497
|
}
|
|
12825
13498
|
};
|
|
12826
13499
|
var SHAPE_KINDS = /* @__PURE__ */ new Set(["rect", "ellipse", "polygon", "star"]);
|
|
@@ -12951,15 +13624,29 @@ var MULTI_RESIZE_TARGET_ID = "__weasel:multi-selection";
|
|
|
12951
13624
|
// src/tools/builtin/select/useSelectTool.ts
|
|
12952
13625
|
function useSelectTool(adapter, options) {
|
|
12953
13626
|
const poseBoundsFn = options.poseBounds ?? ((p) => p);
|
|
13627
|
+
const preciseLeaves = options.leafPicking === "silhouette";
|
|
13628
|
+
const tolerance = options.pickTolerance ?? 0;
|
|
12954
13629
|
const pickEveryFn = options.pickEvery ?? ((worldX, worldY) => {
|
|
12955
13630
|
const hier = adapter;
|
|
13631
|
+
const covers = (node, pose, b) => {
|
|
13632
|
+
const t = preciseLeaves ? tolerance : 0;
|
|
13633
|
+
if (!(worldX >= b.x - t && worldX <= b.x + b.width + t && worldY >= b.y - t && worldY <= b.y + b.height + t)) {
|
|
13634
|
+
return false;
|
|
13635
|
+
}
|
|
13636
|
+
if (!preciseLeaves) return true;
|
|
13637
|
+
return shapeCoversPoint(
|
|
13638
|
+
node,
|
|
13639
|
+
pose,
|
|
13640
|
+
worldX,
|
|
13641
|
+
worldY,
|
|
13642
|
+
{ tolerance }
|
|
13643
|
+
);
|
|
13644
|
+
};
|
|
12956
13645
|
if (typeof hier.getChildren !== "function" || typeof hier.getNode !== "function") {
|
|
12957
13646
|
const out2 = [];
|
|
12958
13647
|
for (const obj of adapter.getNodes()) {
|
|
12959
|
-
const
|
|
12960
|
-
if (
|
|
12961
|
-
out2.push(obj.id);
|
|
12962
|
-
}
|
|
13648
|
+
const pose = adapter.getPose(obj.id);
|
|
13649
|
+
if (covers(obj, pose, poseBoundsFn(pose))) out2.push(obj.id);
|
|
12963
13650
|
}
|
|
12964
13651
|
return out2;
|
|
12965
13652
|
}
|
|
@@ -12997,10 +13684,7 @@ function useSelectTool(adapter, options) {
|
|
|
12997
13684
|
for (const clip of ancestorClips) {
|
|
12998
13685
|
if (!pathContainsPoint(clip, worldX, worldY)) continue nextChild;
|
|
12999
13686
|
}
|
|
13000
|
-
|
|
13001
|
-
if (worldX >= b.x && worldX <= b.x + b.width && worldY >= b.y && worldY <= b.y + b.height) {
|
|
13002
|
-
out.push(childId);
|
|
13003
|
-
}
|
|
13687
|
+
if (covers(node, pose, poseBoundsFn(pose))) out.push(childId);
|
|
13004
13688
|
}
|
|
13005
13689
|
}
|
|
13006
13690
|
}
|
|
@@ -13218,6 +13902,101 @@ function useSelectTool(adapter, options) {
|
|
|
13218
13902
|
);
|
|
13219
13903
|
}
|
|
13220
13904
|
|
|
13905
|
+
// src/affordances/hitAffordanceRegions.ts
|
|
13906
|
+
function hitAffordanceRegions(affordances, wx, wy, state, view, isVisible) {
|
|
13907
|
+
for (let i = affordances.length - 1; i >= 0; i--) {
|
|
13908
|
+
const a = affordances[i];
|
|
13909
|
+
if (isVisible && !isVisible(a.id)) continue;
|
|
13910
|
+
const regions = a.regions(state);
|
|
13911
|
+
let best = null;
|
|
13912
|
+
for (let j = 0; j < regions.length; j++) {
|
|
13913
|
+
const region = regions[j];
|
|
13914
|
+
const xf = transformOf(state, region.targetId);
|
|
13915
|
+
if (!hitRegion(region, wx, wy, xf, view)) continue;
|
|
13916
|
+
const d2 = regionDistance2(region, wx, wy, xf);
|
|
13917
|
+
if (best === null || d2 <= best.d2) best = { region, d2 };
|
|
13918
|
+
}
|
|
13919
|
+
if (best) {
|
|
13920
|
+
return {
|
|
13921
|
+
affordanceId: a.id,
|
|
13922
|
+
regionId: best.region.id,
|
|
13923
|
+
region: best.region,
|
|
13924
|
+
binding: best.region.bind()
|
|
13925
|
+
};
|
|
13926
|
+
}
|
|
13927
|
+
}
|
|
13928
|
+
return null;
|
|
13929
|
+
}
|
|
13930
|
+
function regionDistance2(region, wx, wy, xf) {
|
|
13931
|
+
const s = region.shape;
|
|
13932
|
+
const local = s.kind === "point" ? { x: s.x, y: s.y } : s.kind === "annulus" ? { x: s.cx, y: s.cy } : { x: s.x + s.width / 2, y: s.y + s.height / 2 };
|
|
13933
|
+
const w = localToWorld(xf, local.x, local.y);
|
|
13934
|
+
const dx = w.x - wx;
|
|
13935
|
+
const dy = w.y - wy;
|
|
13936
|
+
return dx * dx + dy * dy;
|
|
13937
|
+
}
|
|
13938
|
+
var IDENTITY_XF = { cx: 0, cy: 0, cos: 1, sin: 0, identity: true };
|
|
13939
|
+
function transformOf(state, targetId) {
|
|
13940
|
+
if (targetId === null) return IDENTITY_XF;
|
|
13941
|
+
const b = state.boundsOf(targetId);
|
|
13942
|
+
if (!b) return IDENTITY_XF;
|
|
13943
|
+
const r = poseRotationOf(b);
|
|
13944
|
+
if (!r) return IDENTITY_XF;
|
|
13945
|
+
return {
|
|
13946
|
+
cx: r.cx,
|
|
13947
|
+
cy: r.cy,
|
|
13948
|
+
cos: Math.cos(r.rotation),
|
|
13949
|
+
sin: Math.sin(r.rotation),
|
|
13950
|
+
identity: false
|
|
13951
|
+
};
|
|
13952
|
+
}
|
|
13953
|
+
function localToWorld(xf, lx, ly) {
|
|
13954
|
+
if (xf.identity) return { x: lx, y: ly };
|
|
13955
|
+
const dx = lx - xf.cx;
|
|
13956
|
+
const dy = ly - xf.cy;
|
|
13957
|
+
return {
|
|
13958
|
+
x: xf.cx + xf.cos * dx - xf.sin * dy,
|
|
13959
|
+
y: xf.cy + xf.sin * dx + xf.cos * dy
|
|
13960
|
+
};
|
|
13961
|
+
}
|
|
13962
|
+
function worldToLocal(xf, wx, wy) {
|
|
13963
|
+
if (xf.identity) return { x: wx, y: wy };
|
|
13964
|
+
const dx = wx - xf.cx;
|
|
13965
|
+
const dy = wy - xf.cy;
|
|
13966
|
+
return {
|
|
13967
|
+
x: xf.cx + xf.cos * dx + xf.sin * dy,
|
|
13968
|
+
y: xf.cy - xf.sin * dx + xf.cos * dy
|
|
13969
|
+
};
|
|
13970
|
+
}
|
|
13971
|
+
function annulusSemiAxes(shape, view) {
|
|
13972
|
+
const bandPx = shape.minBandPx ?? 0;
|
|
13973
|
+
if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };
|
|
13974
|
+
const band = bandPx / meanScale(view.scale);
|
|
13975
|
+
return {
|
|
13976
|
+
rx: Math.max(shape.rx, shape.innerWidth / 2 + band),
|
|
13977
|
+
ry: Math.max(shape.ry, shape.innerHeight / 2 + band)
|
|
13978
|
+
};
|
|
13979
|
+
}
|
|
13980
|
+
function hitRegion(region, wx, wy, xf, view) {
|
|
13981
|
+
const local = worldToLocal(xf, wx, wy);
|
|
13982
|
+
if (region.shape.kind === "point") {
|
|
13983
|
+
const radiusWorld = region.shape.hitRadiusPx / meanScale(view.scale);
|
|
13984
|
+
return Math.abs(local.x - region.shape.x) <= radiusWorld && Math.abs(local.y - region.shape.y) <= radiusWorld;
|
|
13985
|
+
}
|
|
13986
|
+
if (region.shape.kind === "annulus") {
|
|
13987
|
+
const s = region.shape;
|
|
13988
|
+
const insideInner = local.x >= s.innerX && local.x <= s.innerX + s.innerWidth && local.y >= s.innerY && local.y <= s.innerY + s.innerHeight;
|
|
13989
|
+
if (insideInner) return false;
|
|
13990
|
+
const { rx, ry } = annulusSemiAxes(s, view);
|
|
13991
|
+
if (rx <= 0 || ry <= 0) return false;
|
|
13992
|
+
const ex = (local.x - s.cx) / rx;
|
|
13993
|
+
const ey = (local.y - s.cy) / ry;
|
|
13994
|
+
return ex * ex + ey * ey <= 1;
|
|
13995
|
+
}
|
|
13996
|
+
const r = region.shape;
|
|
13997
|
+
return local.x >= r.x && local.x <= r.x + r.width && local.y >= r.y && local.y <= r.y + r.height;
|
|
13998
|
+
}
|
|
13999
|
+
|
|
13221
14000
|
// src/affordances/composeAffordanceLayer.ts
|
|
13222
14001
|
function composeAffordanceLayer(id, label, affordances) {
|
|
13223
14002
|
return {
|
|
@@ -13243,21 +14022,11 @@ function composeAffordanceLayer(id, label, affordances) {
|
|
|
13243
14022
|
}
|
|
13244
14023
|
return out;
|
|
13245
14024
|
},
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
for (let j = regs.length - 1; j >= 0; j--) {
|
|
13252
|
-
const region = regs[j];
|
|
13253
|
-
const xf = transformOf(state, region.targetId);
|
|
13254
|
-
if (hitRegion(region, wx, wy, xf, view)) {
|
|
13255
|
-
return region.bind();
|
|
13256
|
-
}
|
|
13257
|
-
}
|
|
13258
|
-
}
|
|
13259
|
-
return null;
|
|
13260
|
-
}
|
|
14025
|
+
// The walk itself lives in `hitAffordanceRegions` so this layer and
|
|
14026
|
+
// `buildAffordanceAt` share one implementation rather than two that can
|
|
14027
|
+
// drift. This wrapper exists because `RenderLayer.hitTest` wants only the
|
|
14028
|
+
// binding back.
|
|
14029
|
+
hitTest: (wx, wy, state, view, _dims, isVisible) => hitAffordanceRegions(affordances, wx, wy, state, view, isVisible)?.binding ?? null
|
|
13261
14030
|
};
|
|
13262
14031
|
}
|
|
13263
14032
|
function asChromeState(data) {
|
|
@@ -13291,11 +14060,12 @@ function recordRegionHitbox(debug, affordanceId, region, xf, view) {
|
|
|
13291
14060
|
}
|
|
13292
14061
|
if (region.shape.kind === "annulus") {
|
|
13293
14062
|
const s = region.shape;
|
|
14063
|
+
const { rx, ry } = annulusSemiAxes(s, view);
|
|
13294
14064
|
const pts = [
|
|
13295
|
-
localToWorld(xf, s.cx +
|
|
13296
|
-
localToWorld(xf, s.cx -
|
|
13297
|
-
localToWorld(xf, s.cx, s.cy +
|
|
13298
|
-
localToWorld(xf, s.cx, s.cy -
|
|
14065
|
+
localToWorld(xf, s.cx + rx, s.cy),
|
|
14066
|
+
localToWorld(xf, s.cx - rx, s.cy),
|
|
14067
|
+
localToWorld(xf, s.cx, s.cy + ry),
|
|
14068
|
+
localToWorld(xf, s.cx, s.cy - ry)
|
|
13299
14069
|
];
|
|
13300
14070
|
const minX = Math.min(...pts.map((p) => p.x));
|
|
13301
14071
|
const minY = Math.min(...pts.map((p) => p.y));
|
|
@@ -13322,39 +14092,6 @@ function recordRegionHitbox(debug, affordanceId, region, xf, view) {
|
|
|
13322
14092
|
...xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }
|
|
13323
14093
|
});
|
|
13324
14094
|
}
|
|
13325
|
-
var IDENTITY_XF = { cx: 0, cy: 0, cos: 1, sin: 0, identity: true };
|
|
13326
|
-
function transformOf(state, targetId) {
|
|
13327
|
-
if (targetId === null) return IDENTITY_XF;
|
|
13328
|
-
const b = state.boundsOf(targetId);
|
|
13329
|
-
if (!b) return IDENTITY_XF;
|
|
13330
|
-
const r = poseRotationOf(b);
|
|
13331
|
-
if (!r) return IDENTITY_XF;
|
|
13332
|
-
return {
|
|
13333
|
-
cx: r.cx,
|
|
13334
|
-
cy: r.cy,
|
|
13335
|
-
cos: Math.cos(r.rotation),
|
|
13336
|
-
sin: Math.sin(r.rotation),
|
|
13337
|
-
identity: false
|
|
13338
|
-
};
|
|
13339
|
-
}
|
|
13340
|
-
function localToWorld(xf, lx, ly) {
|
|
13341
|
-
if (xf.identity) return { x: lx, y: ly };
|
|
13342
|
-
const dx = lx - xf.cx;
|
|
13343
|
-
const dy = ly - xf.cy;
|
|
13344
|
-
return {
|
|
13345
|
-
x: xf.cx + xf.cos * dx - xf.sin * dy,
|
|
13346
|
-
y: xf.cy + xf.sin * dx + xf.cos * dy
|
|
13347
|
-
};
|
|
13348
|
-
}
|
|
13349
|
-
function worldToLocal(xf, wx, wy) {
|
|
13350
|
-
if (xf.identity) return { x: wx, y: wy };
|
|
13351
|
-
const dx = wx - xf.cx;
|
|
13352
|
-
const dy = wy - xf.cy;
|
|
13353
|
-
return {
|
|
13354
|
-
x: xf.cx + xf.cos * dx + xf.sin * dy,
|
|
13355
|
-
y: xf.cy - xf.sin * dx + xf.cos * dy
|
|
13356
|
-
};
|
|
13357
|
-
}
|
|
13358
14095
|
function paintRegion(region, xf, view, state, viewT, out) {
|
|
13359
14096
|
const paint = region.paint;
|
|
13360
14097
|
if (paint.kind === "square") {
|
|
@@ -13377,13 +14114,13 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
13377
14114
|
if (region.shape.kind !== "annulus") return;
|
|
13378
14115
|
const s = region.shape;
|
|
13379
14116
|
const insetLocal = (paint.insetPx ?? 0) / meanScale(view.scale);
|
|
13380
|
-
const cmd = annulusCommand(s, insetLocal, xf, viewT, paint.fill, paint.stroke);
|
|
14117
|
+
const cmd = annulusCommand(s, annulusSemiAxes(s, view), insetLocal, xf, viewT, paint.fill, paint.stroke);
|
|
13381
14118
|
if (cmd) out.push(cmd);
|
|
13382
14119
|
return;
|
|
13383
14120
|
}
|
|
13384
14121
|
if (paint.kind === "custom") {
|
|
13385
14122
|
const ctx = {
|
|
13386
|
-
world: worldOf(region, xf),
|
|
14123
|
+
world: worldOf(region, xf, view),
|
|
13387
14124
|
local: region.shape,
|
|
13388
14125
|
view,
|
|
13389
14126
|
state
|
|
@@ -13392,9 +14129,9 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
13392
14129
|
return;
|
|
13393
14130
|
}
|
|
13394
14131
|
}
|
|
13395
|
-
function annulusCommand(shape, insetLocal, xf, viewT, fill, stroke) {
|
|
13396
|
-
const rx =
|
|
13397
|
-
const ry =
|
|
14132
|
+
function annulusCommand(shape, semiAxes, insetLocal, xf, viewT, fill, stroke) {
|
|
14133
|
+
const rx = semiAxes.rx - insetLocal;
|
|
14134
|
+
const ry = semiAxes.ry - insetLocal;
|
|
13398
14135
|
if (rx <= 0 || ry <= 0) return null;
|
|
13399
14136
|
const cx = shape.cx;
|
|
13400
14137
|
const cy = shape.cy;
|
|
@@ -13460,18 +14197,19 @@ function annulusCommand(shape, insetLocal, xf, viewT, fill, stroke) {
|
|
|
13460
14197
|
...stroke ? { stroke } : {}
|
|
13461
14198
|
};
|
|
13462
14199
|
}
|
|
13463
|
-
function worldOf(region, xf) {
|
|
14200
|
+
function worldOf(region, xf, view) {
|
|
13464
14201
|
if (region.shape.kind === "point") {
|
|
13465
14202
|
const w = localToWorld(xf, region.shape.x, region.shape.y);
|
|
13466
14203
|
return { x: w.x, y: w.y };
|
|
13467
14204
|
}
|
|
13468
14205
|
if (region.shape.kind === "annulus") {
|
|
13469
14206
|
const s = region.shape;
|
|
14207
|
+
const { rx, ry } = annulusSemiAxes(s, view);
|
|
13470
14208
|
const pts = [
|
|
13471
|
-
localToWorld(xf, s.cx +
|
|
13472
|
-
localToWorld(xf, s.cx -
|
|
13473
|
-
localToWorld(xf, s.cx, s.cy +
|
|
13474
|
-
localToWorld(xf, s.cx, s.cy -
|
|
14209
|
+
localToWorld(xf, s.cx + rx, s.cy),
|
|
14210
|
+
localToWorld(xf, s.cx - rx, s.cy),
|
|
14211
|
+
localToWorld(xf, s.cx, s.cy + ry),
|
|
14212
|
+
localToWorld(xf, s.cx, s.cy - ry)
|
|
13475
14213
|
];
|
|
13476
14214
|
const minX2 = Math.min(...pts.map((p) => p.x));
|
|
13477
14215
|
const minY2 = Math.min(...pts.map((p) => p.y));
|
|
@@ -13490,24 +14228,6 @@ function worldOf(region, xf) {
|
|
|
13490
14228
|
const maxY = Math.max(a.y, b.y, c.y, d.y);
|
|
13491
14229
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
13492
14230
|
}
|
|
13493
|
-
function hitRegion(region, wx, wy, xf, view) {
|
|
13494
|
-
const local = worldToLocal(xf, wx, wy);
|
|
13495
|
-
if (region.shape.kind === "point") {
|
|
13496
|
-
const radiusWorld = region.shape.hitRadiusPx / meanScale(view.scale);
|
|
13497
|
-
return Math.abs(local.x - region.shape.x) <= radiusWorld && Math.abs(local.y - region.shape.y) <= radiusWorld;
|
|
13498
|
-
}
|
|
13499
|
-
if (region.shape.kind === "annulus") {
|
|
13500
|
-
const s = region.shape;
|
|
13501
|
-
const insideInner = local.x >= s.innerX && local.x <= s.innerX + s.innerWidth && local.y >= s.innerY && local.y <= s.innerY + s.innerHeight;
|
|
13502
|
-
if (insideInner) return false;
|
|
13503
|
-
if (s.rx <= 0 || s.ry <= 0) return false;
|
|
13504
|
-
const ex = (local.x - s.cx) / s.rx;
|
|
13505
|
-
const ey = (local.y - s.cy) / s.ry;
|
|
13506
|
-
return ex * ex + ey * ey <= 1;
|
|
13507
|
-
}
|
|
13508
|
-
const r = region.shape;
|
|
13509
|
-
return local.x >= r.x && local.x <= r.x + r.width && local.y >= r.y && local.y <= r.y + r.height;
|
|
13510
|
-
}
|
|
13511
14231
|
|
|
13512
14232
|
// src/affordances/rotationHandle.ts
|
|
13513
14233
|
var DEFAULT_PAINT = {
|
|
@@ -13538,13 +14258,8 @@ function createRotationAffordance(opts = {}) {
|
|
|
13538
14258
|
const target = pickTarget(state);
|
|
13539
14259
|
if (!target) return [];
|
|
13540
14260
|
const b = target.bounds;
|
|
13541
|
-
const minBand = bandPx;
|
|
13542
14261
|
const halfW = b.width / 2;
|
|
13543
14262
|
const halfH = b.height / 2;
|
|
13544
|
-
const naturalRx = halfW * Math.SQRT2;
|
|
13545
|
-
const naturalRy = halfH * Math.SQRT2;
|
|
13546
|
-
const rx = Math.max(naturalRx, halfW + minBand);
|
|
13547
|
-
const ry = Math.max(naturalRy, halfH + minBand);
|
|
13548
14263
|
const region = {
|
|
13549
14264
|
id: "selection.rotation-handle",
|
|
13550
14265
|
targetId: target.id,
|
|
@@ -13552,17 +14267,24 @@ function createRotationAffordance(opts = {}) {
|
|
|
13552
14267
|
kind: "annulus",
|
|
13553
14268
|
cx: b.x + halfW,
|
|
13554
14269
|
cy: b.y + halfH,
|
|
13555
|
-
rx,
|
|
13556
|
-
ry,
|
|
14270
|
+
rx: halfW * Math.SQRT2,
|
|
14271
|
+
ry: halfH * Math.SQRT2,
|
|
13557
14272
|
innerX: b.x,
|
|
13558
14273
|
innerY: b.y,
|
|
13559
14274
|
innerWidth: b.width,
|
|
13560
|
-
innerHeight: b.height
|
|
14275
|
+
innerHeight: b.height,
|
|
14276
|
+
minBandPx: bandPx
|
|
13561
14277
|
},
|
|
13562
14278
|
...annulusPaint ? { paint: annulusPaint } : {},
|
|
14279
|
+
hitKind: "rotate-handle",
|
|
13563
14280
|
cursor,
|
|
13564
14281
|
bind: () => ({
|
|
13565
|
-
initialScratch: {
|
|
14282
|
+
initialScratch: {
|
|
14283
|
+
targetId: target.id,
|
|
14284
|
+
// Pivot. `rotateAction` recomputes its own union pivot for
|
|
14285
|
+
// multi-selection, but the single-target case reads this.
|
|
14286
|
+
fixedPoint: { x: b.x + halfW, y: b.y + halfH }
|
|
14287
|
+
}
|
|
13566
14288
|
})
|
|
13567
14289
|
};
|
|
13568
14290
|
return [region];
|
|
@@ -13630,8 +14352,7 @@ function useRotateTool(adapter, options) {
|
|
|
13630
14352
|
const ghosts = ghostOverlay.draw(data, view, dims);
|
|
13631
14353
|
const aff = affordanceOverlay.draw(toChromeState(data), view, dims);
|
|
13632
14354
|
return [...ghosts, ...aff];
|
|
13633
|
-
}
|
|
13634
|
-
hitTest: (wx, wy, data, view, dims) => affordanceOverlay.hitTest(wx, wy, toChromeState(data), view, dims)
|
|
14355
|
+
}
|
|
13635
14356
|
}),
|
|
13636
14357
|
[ghostOverlay, affordanceOverlay]
|
|
13637
14358
|
);
|
|
@@ -14476,12 +15197,12 @@ function defaultDrawOne(node, pose, ctx) {
|
|
|
14476
15197
|
const data = node.data;
|
|
14477
15198
|
if (data?.label && data.text == null) {
|
|
14478
15199
|
const p = pose;
|
|
14479
|
-
primary
|
|
15200
|
+
return [...primary, textCommand(
|
|
14480
15201
|
p.x + 6,
|
|
14481
15202
|
p.y + 14,
|
|
14482
15203
|
data.label,
|
|
14483
15204
|
{ fontFamily: "sans-serif", fontSize: 11, fill: { fill: "solid", color: "rgba(0,0,0,0.7)" } }
|
|
14484
|
-
)
|
|
15205
|
+
)];
|
|
14485
15206
|
}
|
|
14486
15207
|
return primary;
|
|
14487
15208
|
}
|
|
@@ -17546,28 +18267,32 @@ function aabbOfPose(pose) {
|
|
|
17546
18267
|
);
|
|
17547
18268
|
}
|
|
17548
18269
|
var DEFAULT_PATH_STROKE_SLOP = 4;
|
|
17549
|
-
function poseContains(pose, wx, wy) {
|
|
18270
|
+
function poseContains(pose, wx, wy, tolerance) {
|
|
17550
18271
|
if (isPathLike(pose)) {
|
|
17551
18272
|
if (pointInPath(pose, wx, wy)) return true;
|
|
17552
|
-
return strokeHitTest(pose, wx, wy, DEFAULT_PATH_STROKE_SLOP);
|
|
18273
|
+
return strokeHitTest(pose, wx, wy, tolerance ?? DEFAULT_PATH_STROKE_SLOP);
|
|
17553
18274
|
}
|
|
17554
18275
|
const b = aabbOfPose(pose);
|
|
17555
|
-
|
|
18276
|
+
const t = tolerance ?? 0;
|
|
18277
|
+
return wx >= b.x - t && wx <= b.x + b.width + t && wy >= b.y - t && wy <= b.y + b.height + t;
|
|
17556
18278
|
}
|
|
17557
|
-
function poseContainsRotated(pose, wx, wy) {
|
|
18279
|
+
function poseContainsRotated(pose, wx, wy, tolerance) {
|
|
17558
18280
|
const r = poseRotationOf(pose);
|
|
17559
18281
|
if (r) {
|
|
17560
18282
|
const [lx, ly] = applyToPoint(rotateAboutPoint(r.cx, r.cy, -r.rotation), wx, wy);
|
|
17561
|
-
return poseContains(pose, lx, ly);
|
|
18283
|
+
return poseContains(pose, lx, ly, tolerance);
|
|
17562
18284
|
}
|
|
17563
|
-
return poseContains(pose, wx, wy);
|
|
18285
|
+
return poseContains(pose, wx, wy, tolerance);
|
|
17564
18286
|
}
|
|
17565
18287
|
|
|
17566
18288
|
// src/canvas/SceneCanvas/useSceneSelectTool.ts
|
|
18289
|
+
var DEFAULT_PICK_TOLERANCE_PX = 4;
|
|
17567
18290
|
function useSceneSelectTool(args) {
|
|
17568
|
-
const { scene, selection, geometry, selectTool: opts, insertTool, layouts } = args;
|
|
18291
|
+
const { scene, selection, geometry, selectTool: opts, insertTool, layouts, getView } = args;
|
|
17569
18292
|
const pickEveryProp = geometry?.pickEvery;
|
|
17570
18293
|
const boundsOfProp = geometry?.boundsOf;
|
|
18294
|
+
const shapePicking = (geometry?.picking ?? "shape") === "shape";
|
|
18295
|
+
const pickTolerancePx = geometry?.pickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;
|
|
17571
18296
|
const moveOptions = opts?.move;
|
|
17572
18297
|
const rotateOptions = opts?.rotate;
|
|
17573
18298
|
const snap2 = opts?.snap;
|
|
@@ -17658,14 +18383,17 @@ function useSceneSelectTool(args) {
|
|
|
17658
18383
|
if (r == null) return [];
|
|
17659
18384
|
return Array.isArray(r) ? r : [r];
|
|
17660
18385
|
}
|
|
18386
|
+
const tolerance = pickTolerancePx / meanScale(getView?.()?.scale ?? { x: 1, y: 1 });
|
|
17661
18387
|
const out = [];
|
|
17662
18388
|
for (const id of scene.renderOrder()) {
|
|
17663
18389
|
const n = scene.get(id);
|
|
17664
|
-
if (n
|
|
18390
|
+
if (!n || !poseContainsRotated(n.pose, wx, wy, tolerance)) continue;
|
|
18391
|
+
if (shapePicking && !shapeCoversPoint(n, n.pose, wx, wy, { tolerance })) continue;
|
|
18392
|
+
out.push(n.id);
|
|
17665
18393
|
}
|
|
17666
18394
|
return out;
|
|
17667
18395
|
};
|
|
17668
|
-
}, [scene, pickEveryProp]);
|
|
18396
|
+
}, [scene, pickEveryProp, shapePicking, pickTolerancePx, getView]);
|
|
17669
18397
|
const wiredBoundsOf = useMemo(() => {
|
|
17670
18398
|
return (id) => {
|
|
17671
18399
|
if (boundsOfProp) return boundsOfProp(id);
|
|
@@ -18417,77 +19145,76 @@ function enumerateAnchors(path) {
|
|
|
18417
19145
|
};
|
|
18418
19146
|
anchors.push(a);
|
|
18419
19147
|
lastAnchor = a;
|
|
18420
|
-
ci += 4;
|
|
18421
|
-
break;
|
|
18422
|
-
}
|
|
18423
|
-
case PATH_Z: {
|
|
18424
|
-
lastAnchor = null;
|
|
18425
|
-
break;
|
|
18426
|
-
}
|
|
18427
|
-
}
|
|
18428
|
-
}
|
|
18429
|
-
return anchors;
|
|
18430
|
-
}
|
|
18431
|
-
function withCoord(path, coordIndex, x, y) {
|
|
18432
|
-
const next = new Float32Array(path.coords);
|
|
18433
|
-
next[coordIndex] = x;
|
|
18434
|
-
next[coordIndex + 1] = y;
|
|
18435
|
-
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
18436
|
-
}
|
|
18437
|
-
|
|
18438
|
-
// src/interactions/actions/edit-anchors/handles.ts
|
|
18439
|
-
function hitAnchor(path, worldX, worldY, threshold) {
|
|
18440
|
-
const anchors = enumerateAnchors(path);
|
|
18441
|
-
let bestControl = null;
|
|
18442
|
-
let bestAnchor = null;
|
|
18443
|
-
const t2 = threshold * threshold;
|
|
18444
|
-
for (const a of anchors) {
|
|
18445
|
-
if (a.controlIn) {
|
|
18446
|
-
const dx2 = a.controlIn.x - worldX;
|
|
18447
|
-
const dy2 = a.controlIn.y - worldY;
|
|
18448
|
-
const d22 = dx2 * dx2 + dy2 * dy2;
|
|
18449
|
-
if (d22 <= t2 && (!bestControl || d22 < bestControl.d2)) {
|
|
18450
|
-
bestControl = {
|
|
18451
|
-
d2: d22,
|
|
18452
|
-
hit: { anchorIndex: a.anchorIndex, kind: "controlIn", coordIndex: a.controlIn.coordIndex }
|
|
18453
|
-
};
|
|
18454
|
-
}
|
|
18455
|
-
}
|
|
18456
|
-
if (a.controlOut) {
|
|
18457
|
-
const dx2 = a.controlOut.x - worldX;
|
|
18458
|
-
const dy2 = a.controlOut.y - worldY;
|
|
18459
|
-
const d22 = dx2 * dx2 + dy2 * dy2;
|
|
18460
|
-
if (d22 <= t2 && (!bestControl || d22 < bestControl.d2)) {
|
|
18461
|
-
bestControl = {
|
|
18462
|
-
d2: d22,
|
|
18463
|
-
hit: { anchorIndex: a.anchorIndex, kind: "controlOut", coordIndex: a.controlOut.coordIndex }
|
|
18464
|
-
};
|
|
19148
|
+
ci += 4;
|
|
19149
|
+
break;
|
|
19150
|
+
}
|
|
19151
|
+
case PATH_Z: {
|
|
19152
|
+
lastAnchor = null;
|
|
19153
|
+
break;
|
|
18465
19154
|
}
|
|
18466
|
-
}
|
|
18467
|
-
const dx = a.x - worldX;
|
|
18468
|
-
const dy = a.y - worldY;
|
|
18469
|
-
const d2 = dx * dx + dy * dy;
|
|
18470
|
-
if (d2 <= t2 && (!bestAnchor || d2 < bestAnchor.d2)) {
|
|
18471
|
-
bestAnchor = {
|
|
18472
|
-
d2,
|
|
18473
|
-
hit: { anchorIndex: a.anchorIndex, kind: "anchor", coordIndex: a.coordIndex }
|
|
18474
|
-
};
|
|
18475
19155
|
}
|
|
18476
19156
|
}
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
19157
|
+
return anchors;
|
|
19158
|
+
}
|
|
19159
|
+
function withCoord(path, coordIndex, x, y) {
|
|
19160
|
+
const next = new Float32Array(path.coords);
|
|
19161
|
+
next[coordIndex] = x;
|
|
19162
|
+
next[coordIndex + 1] = y;
|
|
19163
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
18480
19164
|
}
|
|
18481
19165
|
|
|
18482
|
-
// src/
|
|
18483
|
-
var
|
|
18484
|
-
var
|
|
18485
|
-
function
|
|
18486
|
-
const
|
|
18487
|
-
|
|
18488
|
-
|
|
19166
|
+
// src/affordances/cornerResize.ts
|
|
19167
|
+
var DEFAULT_FILL3 = "#d4c4a8";
|
|
19168
|
+
var DEFAULT_STROKE = "#1a130d";
|
|
19169
|
+
function createCornerResizeAffordance(opts = {}) {
|
|
19170
|
+
const {
|
|
19171
|
+
handleHitRadius = 8,
|
|
19172
|
+
handleSize = 8,
|
|
19173
|
+
fill = DEFAULT_FILL3,
|
|
19174
|
+
stroke = DEFAULT_STROKE
|
|
19175
|
+
} = opts;
|
|
19176
|
+
const paint = {
|
|
19177
|
+
kind: "square",
|
|
19178
|
+
sizePx: handleSize,
|
|
19179
|
+
fill: { color: fill },
|
|
19180
|
+
stroke: { paint: { color: stroke }, width: 1 }
|
|
19181
|
+
};
|
|
19182
|
+
return {
|
|
19183
|
+
id: "selection.resize-handles",
|
|
19184
|
+
regions(state) {
|
|
19185
|
+
const target = pickRenderTarget(state);
|
|
19186
|
+
if (!target) return [];
|
|
19187
|
+
const b = target.bounds;
|
|
19188
|
+
const xf = transformOf(state, target.id);
|
|
19189
|
+
return CORNER_ANCHORS.map((c) => {
|
|
19190
|
+
const corner = cornerPoint(b, c);
|
|
19191
|
+
const fixedLocal = fixedCornerOf(b, c.anchor);
|
|
19192
|
+
const fixedPoint = localToWorld(xf, fixedLocal.x, fixedLocal.y);
|
|
19193
|
+
return {
|
|
19194
|
+
id: `corner-${c.tag}`,
|
|
19195
|
+
targetId: target.id,
|
|
19196
|
+
shape: { kind: "point", x: corner.x, y: corner.y, hitRadiusPx: handleHitRadius },
|
|
19197
|
+
paint,
|
|
19198
|
+
hitKind: c.kind,
|
|
19199
|
+
// Diagonal by fixed-corner parity: a matched-axis anchor (min-min /
|
|
19200
|
+
// max-max fixed) means the dragged corner sits on the ↘ diagonal;
|
|
19201
|
+
// mixed axes sit on the ↗ diagonal. Not rotation-aware — a rotated
|
|
19202
|
+
// target keeps the unrotated hint, the same policy as every
|
|
19203
|
+
// mainstream editor short of Figma.
|
|
19204
|
+
cursor: c.anchor.x === c.anchor.y ? "nwse-resize" : "nesw-resize",
|
|
19205
|
+
bind: () => ({
|
|
19206
|
+
initialScratch: {
|
|
19207
|
+
anchor: c.anchor,
|
|
19208
|
+
targetId: target.id,
|
|
19209
|
+
fixedPoint
|
|
19210
|
+
}
|
|
19211
|
+
})
|
|
19212
|
+
};
|
|
19213
|
+
});
|
|
19214
|
+
}
|
|
19215
|
+
};
|
|
18489
19216
|
}
|
|
18490
|
-
function
|
|
19217
|
+
function pickRenderTarget(state) {
|
|
18491
19218
|
if (state.multiActive && state.unionBounds) {
|
|
18492
19219
|
return { id: MULTI_RESIZE_TARGET_ID, bounds: state.unionBounds };
|
|
18493
19220
|
}
|
|
@@ -18498,110 +19225,130 @@ function pickResizeTarget(state) {
|
|
|
18498
19225
|
}
|
|
18499
19226
|
return null;
|
|
18500
19227
|
}
|
|
18501
|
-
|
|
18502
|
-
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
|
|
18507
|
-
const
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
|
|
18511
|
-
|
|
18512
|
-
|
|
18513
|
-
|
|
18514
|
-
const state = getChromeState();
|
|
18515
|
-
const isVisible = getIsVisible?.() ?? (() => true);
|
|
18516
|
-
const hr = typeof hitRadius === "function" ? hitRadius() : hitRadius;
|
|
18517
|
-
const rd = typeof rotateDistance === "function" ? rotateDistance() : rotateDistance;
|
|
18518
|
-
const r2 = hr * hr;
|
|
18519
|
-
if (isVisible("selection.resize-handles")) {
|
|
18520
|
-
const resizeTarget = pickResizeTarget(state);
|
|
18521
|
-
if (resizeTarget) {
|
|
18522
|
-
for (const c of cornersFor(resizeTarget.bounds)) {
|
|
18523
|
-
if (dist22(wx, wy, c.worldX, c.worldY) <= r2) {
|
|
18524
|
-
return {
|
|
18525
|
-
kind: c.kind,
|
|
18526
|
-
fixedPoint: { x: c.fixedX, y: c.fixedY },
|
|
18527
|
-
targetIds: [resizeTarget.id],
|
|
18528
|
-
anchor: c.anchor,
|
|
18529
|
-
// Diagonal by fixed-corner parity: a matched-axis anchor
|
|
18530
|
-
// (min-min / max-max fixed) means the dragged corner sits on
|
|
18531
|
-
// the ↘ diagonal; mixed axes sit on the ↗ diagonal. Cursor is
|
|
18532
|
-
// not rotation-aware — a rotated target keeps the unrotated
|
|
18533
|
-
// hint (same policy as every mainstream editor short of Figma).
|
|
18534
|
-
cursor: c.anchor.x === c.anchor.y ? "nwse-resize" : "nesw-resize"
|
|
18535
|
-
};
|
|
18536
|
-
}
|
|
18537
|
-
}
|
|
18538
|
-
}
|
|
19228
|
+
|
|
19229
|
+
// src/affordances/pathAnchors.ts
|
|
19230
|
+
var PATH_ANCHOR_CHROME_ID = "path-edit.anchors";
|
|
19231
|
+
function createPathAnchorAffordances(getAnchorState, opts = {}) {
|
|
19232
|
+
const { hitRadius = 8, cursor = "pointer" } = opts;
|
|
19233
|
+
const editablePaths = (state) => {
|
|
19234
|
+
const anchorState = getAnchorState();
|
|
19235
|
+
if (!anchorState) return [];
|
|
19236
|
+
const out = [];
|
|
19237
|
+
for (const id of state.selection) {
|
|
19238
|
+
const pose = anchorState.getPose(id);
|
|
19239
|
+
if (!pose || pose.kind !== "polygon") continue;
|
|
19240
|
+
out.push({ id, path: pose });
|
|
18539
19241
|
}
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
|
|
18561
|
-
|
|
18562
|
-
|
|
18563
|
-
|
|
18564
|
-
|
|
18565
|
-
}
|
|
18566
|
-
|
|
19242
|
+
return out;
|
|
19243
|
+
};
|
|
19244
|
+
const point = (id, targetId, x, y, scratch, hitKind) => ({
|
|
19245
|
+
id,
|
|
19246
|
+
// Anchor coordinates are already in the path's own space, which for an
|
|
19247
|
+
// editable path is world space — the pose rotation that `targetId` would
|
|
19248
|
+
// introduce is baked into the coords. Anchoring to `null` keeps the
|
|
19249
|
+
// framework from applying it twice.
|
|
19250
|
+
targetId: null,
|
|
19251
|
+
shape: { kind: "point", x, y, hitRadiusPx: hitRadius },
|
|
19252
|
+
hitKind,
|
|
19253
|
+
cursor,
|
|
19254
|
+
bind: () => ({ initialScratch: { ...scratch, targetId } })
|
|
19255
|
+
});
|
|
19256
|
+
const anchors = {
|
|
19257
|
+
id: PATH_ANCHOR_CHROME_ID,
|
|
19258
|
+
regions(state) {
|
|
19259
|
+
const out = [];
|
|
19260
|
+
for (const { id, path } of editablePaths(state)) {
|
|
19261
|
+
for (const a of enumerateAnchors(path)) {
|
|
19262
|
+
out.push(point(
|
|
19263
|
+
`${id}:anchor:${a.anchorIndex}`,
|
|
19264
|
+
id,
|
|
19265
|
+
a.x,
|
|
19266
|
+
a.y,
|
|
19267
|
+
{ targetId: id, anchorIndex: a.anchorIndex, part: "anchor" },
|
|
19268
|
+
`anchor:${a.anchorIndex}`
|
|
19269
|
+
));
|
|
18567
19270
|
}
|
|
18568
19271
|
}
|
|
19272
|
+
return out;
|
|
18569
19273
|
}
|
|
18570
|
-
|
|
19274
|
+
};
|
|
19275
|
+
const controls = {
|
|
19276
|
+
id: PATH_ANCHOR_CHROME_ID,
|
|
19277
|
+
regions(state) {
|
|
18571
19278
|
const anchorState = getAnchorState();
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
|
|
18595
|
-
}
|
|
18596
|
-
|
|
18597
|
-
if (best !== null) {
|
|
18598
|
-
return { kind: `anchor:${best.anchorIndex}`, targetIds: [id] };
|
|
18599
|
-
}
|
|
19279
|
+
const editingId = anchorState?.editingId ?? null;
|
|
19280
|
+
if (editingId === null) return [];
|
|
19281
|
+
const out = [];
|
|
19282
|
+
for (const { id, path } of editablePaths(state)) {
|
|
19283
|
+
if (id !== editingId) continue;
|
|
19284
|
+
for (const a of enumerateAnchors(path)) {
|
|
19285
|
+
if (a.controlIn) {
|
|
19286
|
+
out.push(point(
|
|
19287
|
+
`${id}:controlIn:${a.anchorIndex}`,
|
|
19288
|
+
id,
|
|
19289
|
+
a.controlIn.x,
|
|
19290
|
+
a.controlIn.y,
|
|
19291
|
+
{ targetId: id, anchorIndex: a.anchorIndex, part: "controlIn" },
|
|
19292
|
+
`controlIn:${a.anchorIndex}`
|
|
19293
|
+
));
|
|
19294
|
+
}
|
|
19295
|
+
if (a.controlOut) {
|
|
19296
|
+
out.push(point(
|
|
19297
|
+
`${id}:controlOut:${a.anchorIndex}`,
|
|
19298
|
+
id,
|
|
19299
|
+
a.controlOut.x,
|
|
19300
|
+
a.controlOut.y,
|
|
19301
|
+
{ targetId: id, anchorIndex: a.anchorIndex, part: "controlOut" },
|
|
19302
|
+
`controlOut:${a.anchorIndex}`
|
|
19303
|
+
));
|
|
18600
19304
|
}
|
|
18601
19305
|
}
|
|
18602
19306
|
}
|
|
19307
|
+
return out;
|
|
18603
19308
|
}
|
|
18604
|
-
|
|
19309
|
+
};
|
|
19310
|
+
return [anchors, controls];
|
|
19311
|
+
}
|
|
19312
|
+
|
|
19313
|
+
// src/canvas/affordanceAt.ts
|
|
19314
|
+
var HANDLE_HIT_RADIUS = 8;
|
|
19315
|
+
var ANCHOR_HIT_RADIUS = 8;
|
|
19316
|
+
function buildAffordanceAt(opts) {
|
|
19317
|
+
const {
|
|
19318
|
+
getChromeState,
|
|
19319
|
+
getView,
|
|
19320
|
+
handleHitRadius = HANDLE_HIT_RADIUS,
|
|
19321
|
+
anchorHitRadius = ANCHOR_HIT_RADIUS,
|
|
19322
|
+
rotateBandPx = DEFAULT_ROTATION_HANDLE_DISTANCE,
|
|
19323
|
+
getAnchorState,
|
|
19324
|
+
getIsVisible
|
|
19325
|
+
} = opts;
|
|
19326
|
+
const affordances = [
|
|
19327
|
+
createRotationAffordance({ bandPx: rotateBandPx, paint: null }),
|
|
19328
|
+
createCornerResizeAffordance({ handleHitRadius }),
|
|
19329
|
+
...getAnchorState ? createPathAnchorAffordances(getAnchorState, { hitRadius: anchorHitRadius }) : []
|
|
19330
|
+
];
|
|
19331
|
+
return function affordanceAt({ x: wx, y: wy }) {
|
|
19332
|
+
const hit = hitAffordanceRegions(
|
|
19333
|
+
affordances,
|
|
19334
|
+
wx,
|
|
19335
|
+
wy,
|
|
19336
|
+
getChromeState(),
|
|
19337
|
+
getView(),
|
|
19338
|
+
getIsVisible?.()
|
|
19339
|
+
);
|
|
19340
|
+
return hit ? toAffordanceHit(hit) : null;
|
|
19341
|
+
};
|
|
19342
|
+
}
|
|
19343
|
+
function toAffordanceHit(hit) {
|
|
19344
|
+
const scratch = hit.binding.initialScratch ?? {};
|
|
19345
|
+
return {
|
|
19346
|
+
kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,
|
|
19347
|
+
...scratch.targetId !== void 0 ? { targetIds: [scratch.targetId] } : {},
|
|
19348
|
+
...scratch.anchor !== void 0 ? { anchor: scratch.anchor } : {},
|
|
19349
|
+
...scratch.fixedPoint !== void 0 ? { fixedPoint: scratch.fixedPoint } : {},
|
|
19350
|
+
...hit.region.cursor !== void 0 ? { cursor: hit.region.cursor } : {},
|
|
19351
|
+
...hit.binding.initialScratch !== void 0 ? { payload: hit.binding.initialScratch } : {}
|
|
18605
19352
|
};
|
|
18606
19353
|
}
|
|
18607
19354
|
function buildClassifyTarget(getSelection, pickBest, kindOfNode) {
|
|
@@ -19881,7 +20628,10 @@ function mergeLayersWithDefaults(user) {
|
|
|
19881
20628
|
}
|
|
19882
20629
|
var BUNDLE_TOOLS = {
|
|
19883
20630
|
minimal: ["select", "hand"],
|
|
19884
|
-
|
|
20631
|
+
// No `pencil`: freehand is a specialist instrument, not part of the
|
|
20632
|
+
// everyday shape-drawing set. It stays in `exhaustive`, which means
|
|
20633
|
+
// everything.
|
|
20634
|
+
standard: ["select", "rotate", "hand", "rect", "ellipse", "line"],
|
|
19885
20635
|
exhaustive: [
|
|
19886
20636
|
"select",
|
|
19887
20637
|
"rotate",
|
|
@@ -20094,6 +20844,8 @@ function SceneCanvasInner(props, ref) {
|
|
|
20094
20844
|
scene,
|
|
20095
20845
|
selection,
|
|
20096
20846
|
geometry,
|
|
20847
|
+
// The pick tolerance is declared in screen pixels; this is what converts it.
|
|
20848
|
+
getView: () => currentViewRef.current,
|
|
20097
20849
|
selectTool: selectToolWithDefaults,
|
|
20098
20850
|
...insertTool ? { insertTool } : {},
|
|
20099
20851
|
...layouts ? { layouts } : {}
|
|
@@ -20701,8 +21453,8 @@ function GestureDispatcherMounter({
|
|
|
20701
21453
|
}, []);
|
|
20702
21454
|
const affordanceAt = useMemo(() => {
|
|
20703
21455
|
if (!selectionRef || !boundsOf2 || !viewRef) return void 0;
|
|
20704
|
-
return buildAffordanceAt(
|
|
20705
|
-
() => {
|
|
21456
|
+
return buildAffordanceAt({
|
|
21457
|
+
getChromeState: () => {
|
|
20706
21458
|
const sel = selectionRef.current;
|
|
20707
21459
|
const selection = sel?.current ?? [];
|
|
20708
21460
|
const multiActive2 = selection.length >= 2;
|
|
@@ -20728,21 +21480,18 @@ function GestureDispatcherMounter({
|
|
|
20728
21480
|
}
|
|
20729
21481
|
};
|
|
20730
21482
|
},
|
|
20731
|
-
//
|
|
20732
|
-
//
|
|
20733
|
-
//
|
|
20734
|
-
|
|
20735
|
-
// to body classification → move binding fires instead of resize.
|
|
20736
|
-
() => HANDLE_HIT_RADIUS / meanScale(viewRef.current?.scale ?? { x: 1, y: 1 }),
|
|
20737
|
-
() => DEFAULT_ROTATION_HANDLE_DISTANCE / meanScale(viewRef.current?.scale ?? { x: 1, y: 1 }),
|
|
21483
|
+
// Radii are declared in screen pixels and converted against this view.
|
|
21484
|
+
// The caller used to do that division itself (`8 / meanScale(scale)`),
|
|
21485
|
+
// in two places, with a comment explaining what breaks if you forget.
|
|
21486
|
+
getView: () => viewRef.current ?? { x: 0, y: 0, scale: { x: 1, y: 1 } },
|
|
20738
21487
|
getAnchorState,
|
|
20739
21488
|
// Chrome-caps resolver: keep the affordance hit-test in sync with what
|
|
20740
21489
|
// the renderer is actually painting. Without this, a click on a (no
|
|
20741
21490
|
// longer visible) resize handle position still classifies as a resize
|
|
20742
21491
|
// handle — e.g. an anchor drag in path-edit mode resizes the path's
|
|
20743
21492
|
// bounding box instead of moving the anchor.
|
|
20744
|
-
getIsVisibleForCanvas ? () => getIsVisibleForCanvas() :
|
|
20745
|
-
);
|
|
21493
|
+
...getIsVisibleForCanvas ? { getIsVisible: () => getIsVisibleForCanvas() } : {}
|
|
21494
|
+
});
|
|
20746
21495
|
}, [selectionRef, boundsOf2, viewRef, getAnchorState, getIsVisibleForCanvas]);
|
|
20747
21496
|
const classifyTarget = useMemo(() => {
|
|
20748
21497
|
if (!selectionRef || !pickEvery || !viewRef) return void 0;
|
|
@@ -21502,166 +22251,39 @@ function createChildrenLayer(opts) {
|
|
|
21502
22251
|
};
|
|
21503
22252
|
}
|
|
21504
22253
|
|
|
21505
|
-
// src/affordances/cornerResize.ts
|
|
21506
|
-
var DEFAULT_FILL3 = "#d4c4a8";
|
|
21507
|
-
var DEFAULT_STROKE = "#1a130d";
|
|
21508
|
-
function createCornerResizeAffordance(opts = {}) {
|
|
21509
|
-
const {
|
|
21510
|
-
handleHitRadius = 8,
|
|
21511
|
-
handleSize = 8,
|
|
21512
|
-
fill = DEFAULT_FILL3,
|
|
21513
|
-
stroke = DEFAULT_STROKE
|
|
21514
|
-
} = opts;
|
|
21515
|
-
const paint = {
|
|
21516
|
-
kind: "square",
|
|
21517
|
-
sizePx: handleSize,
|
|
21518
|
-
fill: { color: fill },
|
|
21519
|
-
stroke: { paint: { color: stroke }, width: 1 }
|
|
21520
|
-
};
|
|
21521
|
-
return {
|
|
21522
|
-
id: "selection.resize-handles",
|
|
21523
|
-
regions(state) {
|
|
21524
|
-
const target = pickRenderTarget(state);
|
|
21525
|
-
if (!target) return [];
|
|
21526
|
-
const b = target.bounds;
|
|
21527
|
-
const corners = CORNER_ANCHORS.map((c) => {
|
|
21528
|
-
const p = cornerPoint(b, c);
|
|
21529
|
-
return { lx: p.x, ly: p.y, anchor: c.anchor, tag: c.tag };
|
|
21530
|
-
});
|
|
21531
|
-
return corners.map(({ lx, ly, anchor, tag }) => ({
|
|
21532
|
-
id: `corner-${tag}`,
|
|
21533
|
-
targetId: target.id,
|
|
21534
|
-
shape: { kind: "point", x: lx, y: ly, hitRadiusPx: handleHitRadius },
|
|
21535
|
-
paint,
|
|
21536
|
-
bind: () => ({
|
|
21537
|
-
initialScratch: { anchor, targetId: target.id }
|
|
21538
|
-
})
|
|
21539
|
-
}));
|
|
21540
|
-
}
|
|
21541
|
-
};
|
|
21542
|
-
}
|
|
21543
|
-
function pickRenderTarget(state) {
|
|
21544
|
-
if (state.multiActive && state.unionBounds) {
|
|
21545
|
-
return { id: MULTI_RESIZE_TARGET_ID, bounds: state.unionBounds };
|
|
21546
|
-
}
|
|
21547
|
-
if (state.selection.length === 1) {
|
|
21548
|
-
const id = state.selection[0];
|
|
21549
|
-
const b = state.boundsOf(id);
|
|
21550
|
-
return b ? { id, bounds: b } : null;
|
|
21551
|
-
}
|
|
21552
|
-
return null;
|
|
21553
|
-
}
|
|
21554
|
-
|
|
21555
22254
|
// src/features/text/renderLabel.ts
|
|
21556
22255
|
function renderLabel(ctx, text, x, y, options = {}) {
|
|
21557
22256
|
const {
|
|
21558
22257
|
align = "center",
|
|
21559
22258
|
fontSize = 13,
|
|
21560
22259
|
renderText,
|
|
21561
|
-
width,
|
|
21562
|
-
height,
|
|
21563
|
-
padX = 4,
|
|
21564
|
-
padY = 1,
|
|
21565
|
-
cornerRadius = 3
|
|
21566
|
-
} = options;
|
|
21567
|
-
ctx.save();
|
|
21568
|
-
ctx.font = `${fontSize}px sans-serif`;
|
|
21569
|
-
ctx.textBaseline = "top";
|
|
21570
|
-
const contentW = width ?? ctx.measureText(text).width;
|
|
21571
|
-
const contentH = height ?? fontSize;
|
|
21572
|
-
const w = contentW + padX * 2;
|
|
21573
|
-
const h = contentH + padY * 2;
|
|
21574
|
-
const rx = align === "center" ? x - w / 2 : x - padX;
|
|
21575
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
21576
|
-
ctx.beginPath();
|
|
21577
|
-
ctx.roundRect(rx, y - padY, w, h, cornerRadius);
|
|
21578
|
-
ctx.fill();
|
|
21579
|
-
ctx.textAlign = align === "center" ? "center" : "left";
|
|
21580
|
-
const tx = align === "center" ? x : x;
|
|
21581
|
-
(renderText ?? defaultLabelTextRenderer)(ctx, text, tx, y);
|
|
21582
|
-
ctx.restore();
|
|
21583
|
-
}
|
|
21584
|
-
var defaultLabelTextRenderer = (ctx, text, x, y) => {
|
|
21585
|
-
ctx.fillStyle = "#FFFFFF";
|
|
21586
|
-
ctx.fillText(text, x, y);
|
|
21587
|
-
};
|
|
21588
|
-
|
|
21589
|
-
// src/features/text/runs.ts
|
|
21590
|
-
function toRuns(input) {
|
|
21591
|
-
if (typeof input === "string") {
|
|
21592
|
-
return input.length === 0 ? [] : [{ text: input }];
|
|
21593
|
-
}
|
|
21594
|
-
for (let i = 0; i < input.length; i++) {
|
|
21595
|
-
const r = input[i];
|
|
21596
|
-
if (typeof r?.text !== "string") {
|
|
21597
|
-
throw new Error(`toRuns: run at index ${i} is missing string \`text\``);
|
|
21598
|
-
}
|
|
21599
|
-
}
|
|
21600
|
-
return input;
|
|
21601
|
-
}
|
|
21602
|
-
function runsToPlainText(runs) {
|
|
21603
|
-
let out = "";
|
|
21604
|
-
for (const r of runs) out += r.text;
|
|
21605
|
-
return out;
|
|
21606
|
-
}
|
|
21607
|
-
function escapeMarkdown(s) {
|
|
21608
|
-
return s.replace(/\\/g, "\\\\").replace(/\*/g, "\\*");
|
|
21609
|
-
}
|
|
21610
|
-
function runsToMarkdown(runs) {
|
|
21611
|
-
let out = "";
|
|
21612
|
-
for (const r of runs) {
|
|
21613
|
-
const escaped = escapeMarkdown(r.text);
|
|
21614
|
-
if (r.bold && r.italic) out += `***${escaped}***`;
|
|
21615
|
-
else if (r.bold) out += `**${escaped}**`;
|
|
21616
|
-
else if (r.italic) out += `*${escaped}*`;
|
|
21617
|
-
else out += escaped;
|
|
21618
|
-
}
|
|
21619
|
-
return out;
|
|
21620
|
-
}
|
|
21621
|
-
function markdownToRuns(input) {
|
|
21622
|
-
const runs = [];
|
|
21623
|
-
let bold = false;
|
|
21624
|
-
let italic = false;
|
|
21625
|
-
let buf = "";
|
|
21626
|
-
let i = 0;
|
|
21627
|
-
function flush() {
|
|
21628
|
-
if (buf.length === 0) return;
|
|
21629
|
-
const run = { text: buf };
|
|
21630
|
-
if (bold) run.bold = true;
|
|
21631
|
-
if (italic) run.italic = true;
|
|
21632
|
-
runs.push(run);
|
|
21633
|
-
buf = "";
|
|
21634
|
-
}
|
|
21635
|
-
while (i < input.length) {
|
|
21636
|
-
const ch = input[i];
|
|
21637
|
-
if (ch === "\\" && i + 1 < input.length && "*\\".includes(input[i + 1])) {
|
|
21638
|
-
buf += input[i + 1];
|
|
21639
|
-
i += 2;
|
|
21640
|
-
continue;
|
|
21641
|
-
}
|
|
21642
|
-
if (ch === "*") {
|
|
21643
|
-
let count = 0;
|
|
21644
|
-
while (i + count < input.length && input[i + count] === "*") count++;
|
|
21645
|
-
flush();
|
|
21646
|
-
if (count >= 3) {
|
|
21647
|
-
bold = !bold;
|
|
21648
|
-
italic = !italic;
|
|
21649
|
-
i += 3;
|
|
21650
|
-
} else if (count === 2) {
|
|
21651
|
-
bold = !bold;
|
|
21652
|
-
i += 2;
|
|
21653
|
-
} else {
|
|
21654
|
-
italic = !italic;
|
|
21655
|
-
i += 1;
|
|
21656
|
-
}
|
|
21657
|
-
continue;
|
|
21658
|
-
}
|
|
21659
|
-
buf += ch;
|
|
21660
|
-
i++;
|
|
21661
|
-
}
|
|
21662
|
-
flush();
|
|
21663
|
-
return runs;
|
|
22260
|
+
width,
|
|
22261
|
+
height,
|
|
22262
|
+
padX = 4,
|
|
22263
|
+
padY = 1,
|
|
22264
|
+
cornerRadius = 3
|
|
22265
|
+
} = options;
|
|
22266
|
+
ctx.save();
|
|
22267
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
22268
|
+
ctx.textBaseline = "top";
|
|
22269
|
+
const contentW = width ?? ctx.measureText(text).width;
|
|
22270
|
+
const contentH = height ?? fontSize;
|
|
22271
|
+
const w = contentW + padX * 2;
|
|
22272
|
+
const h = contentH + padY * 2;
|
|
22273
|
+
const rx = align === "center" ? x - w / 2 : x - padX;
|
|
22274
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
22275
|
+
ctx.beginPath();
|
|
22276
|
+
ctx.roundRect(rx, y - padY, w, h, cornerRadius);
|
|
22277
|
+
ctx.fill();
|
|
22278
|
+
ctx.textAlign = align === "center" ? "center" : "left";
|
|
22279
|
+
const tx = align === "center" ? x : x;
|
|
22280
|
+
(renderText ?? defaultLabelTextRenderer)(ctx, text, tx, y);
|
|
22281
|
+
ctx.restore();
|
|
21664
22282
|
}
|
|
22283
|
+
var defaultLabelTextRenderer = (ctx, text, x, y) => {
|
|
22284
|
+
ctx.fillStyle = "#FFFFFF";
|
|
22285
|
+
ctx.fillText(text, x, y);
|
|
22286
|
+
};
|
|
21665
22287
|
|
|
21666
22288
|
// src/features/text/markdownText.ts
|
|
21667
22289
|
function layoutMarkdown(runs, maxWidth, fontSize, measure3, lineHeightFactor = 1.3) {
|
|
@@ -22801,502 +23423,240 @@ function nestedHitTester(adapter, opts) {
|
|
|
22801
23423
|
if (wx >= b.x && wx <= b.x + b.width && wy >= b.y && wy <= b.y + b.height) {
|
|
22802
23424
|
return o.id;
|
|
22803
23425
|
}
|
|
22804
|
-
}
|
|
22805
|
-
return null;
|
|
22806
|
-
};
|
|
22807
|
-
const pickOutermost = (wx, wy) => {
|
|
22808
|
-
const leaf = hitLeaf(wx, wy);
|
|
22809
|
-
return leaf === null ? null : ancestorChain(leaf)[0];
|
|
22810
|
-
};
|
|
22811
|
-
const pickBest = (wx, wy, alt, selection) => {
|
|
22812
|
-
const leaf = hitLeaf(wx, wy);
|
|
22813
|
-
if (leaf === null) return null;
|
|
22814
|
-
const chain = ancestorChain(leaf);
|
|
22815
|
-
if (!alt) return chain[0];
|
|
22816
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
22817
|
-
if (selection.includes(chain[i])) {
|
|
22818
|
-
return chain[Math.min(i + 1, chain.length - 1)];
|
|
22819
|
-
}
|
|
22820
|
-
}
|
|
22821
|
-
return chain[chain.length - 1];
|
|
22822
|
-
};
|
|
22823
|
-
return { pickOutermost, pickBest };
|
|
22824
|
-
}
|
|
22825
|
-
|
|
22826
|
-
// src/features/paths/pathDistance.ts
|
|
22827
|
-
var BEZIER_SAMPLES = 16;
|
|
22828
|
-
function pointSegDist2(px, py, ax, ay, bx, by) {
|
|
22829
|
-
const dx = bx - ax, dy = by - ay;
|
|
22830
|
-
const len2 = dx * dx + dy * dy;
|
|
22831
|
-
if (len2 === 0) {
|
|
22832
|
-
const ex2 = px - ax, ey2 = py - ay;
|
|
22833
|
-
return ex2 * ex2 + ey2 * ey2;
|
|
22834
|
-
}
|
|
22835
|
-
let t = ((px - ax) * dx + (py - ay) * dy) / len2;
|
|
22836
|
-
if (t < 0) t = 0;
|
|
22837
|
-
else if (t > 1) t = 1;
|
|
22838
|
-
const cx = ax + dx * t, cy = ay + dy * t;
|
|
22839
|
-
const ex = px - cx, ey = py - cy;
|
|
22840
|
-
return ex * ex + ey * ey;
|
|
22841
|
-
}
|
|
22842
|
-
function pointCubicDist2(px, py, x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
22843
|
-
let prevX = x0, prevY = y0;
|
|
22844
|
-
let best = Infinity;
|
|
22845
|
-
for (let i = 1; i <= BEZIER_SAMPLES; i++) {
|
|
22846
|
-
const t = i / BEZIER_SAMPLES;
|
|
22847
|
-
const u = 1 - t;
|
|
22848
|
-
const sx = u * u * u * x0 + 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t * x3;
|
|
22849
|
-
const sy = u * u * u * y0 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y3;
|
|
22850
|
-
const d2 = pointSegDist2(px, py, prevX, prevY, sx, sy);
|
|
22851
|
-
if (d2 < best) best = d2;
|
|
22852
|
-
prevX = sx;
|
|
22853
|
-
prevY = sy;
|
|
22854
|
-
}
|
|
22855
|
-
return best;
|
|
22856
|
-
}
|
|
22857
|
-
function pointToRectPerimeterDist(px, py, rx, ry, rw, rh) {
|
|
22858
|
-
const dx = Math.max(rx - px, px - (rx + rw), 0);
|
|
22859
|
-
const dy = Math.max(ry - py, py - (ry + rh), 0);
|
|
22860
|
-
if (dx > 0 || dy > 0) return Math.hypot(dx, dy);
|
|
22861
|
-
const left = px - rx, right = rx + rw - px;
|
|
22862
|
-
const top = py - ry, bottom = ry + rh - py;
|
|
22863
|
-
return Math.min(left, right, top, bottom);
|
|
22864
|
-
}
|
|
22865
|
-
function pathDistanceToPoint(path, px, py) {
|
|
22866
|
-
if (path.kind === "rect") {
|
|
22867
|
-
return pointToRectPerimeterDist(px, py, path.x, path.y, path.width, path.height);
|
|
22868
|
-
}
|
|
22869
|
-
return polygonPathDistance(path, px, py);
|
|
22870
|
-
}
|
|
22871
|
-
function polygonPathDistance(path, px, py) {
|
|
22872
|
-
const { commands, coords } = path;
|
|
22873
|
-
let ci = 0;
|
|
22874
|
-
let startX = 0, startY = 0;
|
|
22875
|
-
let curX = 0, curY = 0;
|
|
22876
|
-
let best = Infinity;
|
|
22877
|
-
for (let i = 0; i < commands.length; i++) {
|
|
22878
|
-
const cmd = commands[i];
|
|
22879
|
-
if (cmd === PATH_M) {
|
|
22880
|
-
startX = coords[ci];
|
|
22881
|
-
startY = coords[ci + 1];
|
|
22882
|
-
curX = startX;
|
|
22883
|
-
curY = startY;
|
|
22884
|
-
ci += 2;
|
|
22885
|
-
} else if (cmd === PATH_L) {
|
|
22886
|
-
const x = coords[ci], y = coords[ci + 1];
|
|
22887
|
-
const d2 = pointSegDist2(px, py, curX, curY, x, y);
|
|
22888
|
-
if (d2 < best) best = d2;
|
|
22889
|
-
curX = x;
|
|
22890
|
-
curY = y;
|
|
22891
|
-
ci += 2;
|
|
22892
|
-
} else if (cmd === PATH_C) {
|
|
22893
|
-
const c1x = coords[ci], c1y = coords[ci + 1];
|
|
22894
|
-
const c2x = coords[ci + 2], c2y = coords[ci + 3];
|
|
22895
|
-
const x = coords[ci + 4], y = coords[ci + 5];
|
|
22896
|
-
const d2 = pointCubicDist2(px, py, curX, curY, c1x, c1y, c2x, c2y, x, y);
|
|
22897
|
-
if (d2 < best) best = d2;
|
|
22898
|
-
curX = x;
|
|
22899
|
-
curY = y;
|
|
22900
|
-
ci += 6;
|
|
22901
|
-
} else if (cmd === PATH_Q) {
|
|
22902
|
-
const qx = coords[ci], qy = coords[ci + 1];
|
|
22903
|
-
const x = coords[ci + 2], y = coords[ci + 3];
|
|
22904
|
-
const c1x = curX + 2 / 3 * (qx - curX);
|
|
22905
|
-
const c1y = curY + 2 / 3 * (qy - curY);
|
|
22906
|
-
const c2x = x + 2 / 3 * (qx - x);
|
|
22907
|
-
const c2y = y + 2 / 3 * (qy - y);
|
|
22908
|
-
const d2 = pointCubicDist2(px, py, curX, curY, c1x, c1y, c2x, c2y, x, y);
|
|
22909
|
-
if (d2 < best) best = d2;
|
|
22910
|
-
curX = x;
|
|
22911
|
-
curY = y;
|
|
22912
|
-
ci += 4;
|
|
22913
|
-
} else if (cmd === PATH_Z) {
|
|
22914
|
-
const d2 = pointSegDist2(px, py, curX, curY, startX, startY);
|
|
22915
|
-
if (d2 < best) best = d2;
|
|
22916
|
-
curX = startX;
|
|
22917
|
-
curY = startY;
|
|
22918
|
-
}
|
|
22919
|
-
}
|
|
22920
|
-
return best === Infinity ? Infinity : Math.sqrt(best);
|
|
22921
|
-
}
|
|
22922
|
-
|
|
22923
|
-
// src/features/paths/pathLayer.ts
|
|
22924
|
-
var PLACEHOLDER_FILL = { color: "#ffffff" };
|
|
22925
|
-
var PLACEHOLDER_STROKE = { paint: { color: "#ffffff" }, width: 1 };
|
|
22926
|
-
function resolveOverride(base, override, tMs) {
|
|
22927
|
-
if (!override) return base;
|
|
22928
|
-
if (typeof override === "function") {
|
|
22929
|
-
if (!base) return base;
|
|
22930
|
-
const result = override(base, tMs);
|
|
22931
|
-
if (result.length !== base.length) return base;
|
|
22932
|
-
return result;
|
|
22933
|
-
}
|
|
22934
|
-
return override;
|
|
22935
|
-
}
|
|
22936
|
-
function createPathLayer(opts) {
|
|
22937
|
-
const {
|
|
22938
|
-
id = "paths",
|
|
22939
|
-
label = "Paths",
|
|
22940
|
-
getNodes,
|
|
22941
|
-
getPath,
|
|
22942
|
-
getFill,
|
|
22943
|
-
getStroke,
|
|
22944
|
-
isHidden,
|
|
22945
|
-
getVertexColors,
|
|
22946
|
-
getStrokeVertexColors,
|
|
22947
|
-
getStrokeVertexWidths,
|
|
22948
|
-
colorOverrides,
|
|
22949
|
-
now = () => typeof performance !== "undefined" ? performance.now() : Date.now()
|
|
22950
|
-
} = opts;
|
|
22951
|
-
const warned = /* @__PURE__ */ new Set();
|
|
22952
|
-
const isDev3 = typeof import.meta !== "undefined" && import.meta.env && import.meta.env.DEV;
|
|
22953
|
-
return {
|
|
22954
|
-
id,
|
|
22955
|
-
label,
|
|
22956
|
-
draw: () => {
|
|
22957
|
-
const children = [];
|
|
22958
|
-
const nodes = getNodes();
|
|
22959
|
-
for (let idx = 0; idx < nodes.length; idx++) {
|
|
22960
|
-
const node = nodes[idx];
|
|
22961
|
-
if (isHidden?.(node)) continue;
|
|
22962
|
-
const path = getPath(node);
|
|
22963
|
-
const fillFromHook = getFill?.(node);
|
|
22964
|
-
const strokeFromHook = getStroke?.(node);
|
|
22965
|
-
const baseVColors = getVertexColors?.(node);
|
|
22966
|
-
const baseStrokeVColors = getStrokeVertexColors?.(node);
|
|
22967
|
-
const strokeVWidths = getStrokeVertexWidths?.(node);
|
|
22968
|
-
const nodeId = node.id ?? String(idx);
|
|
22969
|
-
const tMs = colorOverrides ? now() : 0;
|
|
22970
|
-
const fillOverride = colorOverrides?.get(nodeId, "fill");
|
|
22971
|
-
const strokeOverride = colorOverrides?.get(nodeId, "stroke");
|
|
22972
|
-
const vColors = resolveOverride(baseVColors, fillOverride, tMs);
|
|
22973
|
-
const strokeVColors = resolveOverride(baseStrokeVColors, strokeOverride, tMs);
|
|
22974
|
-
const nodeKey = nodeId;
|
|
22975
|
-
const anchorCount2 = countPathAnchors(path);
|
|
22976
|
-
const expectedLen = 4 * anchorCount2;
|
|
22977
|
-
let useVColors = null;
|
|
22978
|
-
if (vColors != null) {
|
|
22979
|
-
if (vColors.length === expectedLen) {
|
|
22980
|
-
useVColors = vColors;
|
|
22981
|
-
} else if (isDev3) {
|
|
22982
|
-
const key = `${id}:${nodeKey}:fill`;
|
|
22983
|
-
if (!warned.has(key)) {
|
|
22984
|
-
warned.add(key);
|
|
22985
|
-
console.warn(
|
|
22986
|
-
`[createPathLayer ${id}] node ${nodeKey}: fill vertexColors length ${vColors.length}, expected ${expectedLen}; dropping`
|
|
22987
|
-
);
|
|
22988
|
-
}
|
|
22989
|
-
}
|
|
22990
|
-
}
|
|
22991
|
-
let useStrokeVColors = null;
|
|
22992
|
-
if (strokeVColors != null) {
|
|
22993
|
-
if (strokeVColors.length === expectedLen) {
|
|
22994
|
-
useStrokeVColors = strokeVColors;
|
|
22995
|
-
} else if (isDev3) {
|
|
22996
|
-
const key = `${id}:${nodeKey}:stroke`;
|
|
22997
|
-
if (!warned.has(key)) {
|
|
22998
|
-
warned.add(key);
|
|
22999
|
-
console.warn(
|
|
23000
|
-
`[createPathLayer ${id}] node ${nodeKey}: stroke vertexColors length ${strokeVColors.length}, expected ${expectedLen}; dropping`
|
|
23001
|
-
);
|
|
23002
|
-
}
|
|
23003
|
-
}
|
|
23004
|
-
}
|
|
23005
|
-
const fill = fillFromHook != null ? fillFromHook : useVColors != null ? PLACEHOLDER_FILL : void 0;
|
|
23006
|
-
let useStrokeVWidths = null;
|
|
23007
|
-
if (strokeVWidths != null) {
|
|
23008
|
-
if (strokeVWidths.length === anchorCount2) {
|
|
23009
|
-
useStrokeVWidths = strokeVWidths;
|
|
23010
|
-
} else if (isDev3) {
|
|
23011
|
-
const key = `${id}:${nodeKey}:strokeWidths`;
|
|
23012
|
-
if (!warned.has(key)) {
|
|
23013
|
-
warned.add(key);
|
|
23014
|
-
console.warn(
|
|
23015
|
-
`[createPathLayer ${id}] node ${nodeKey}: stroke vertexWidths length ${strokeVWidths.length}, expected ${anchorCount2}; dropping`
|
|
23016
|
-
);
|
|
23017
|
-
}
|
|
23018
|
-
}
|
|
23019
|
-
}
|
|
23020
|
-
const baseStroke = strokeFromHook != null ? strokeFromHook : useStrokeVColors != null || useStrokeVWidths != null ? PLACEHOLDER_STROKE : void 0;
|
|
23021
|
-
const stroke = baseStroke != null ? {
|
|
23022
|
-
...baseStroke,
|
|
23023
|
-
...useStrokeVColors != null ? { vertexColors: useStrokeVColors } : {},
|
|
23024
|
-
...useStrokeVWidths != null ? { vertexWidths: useStrokeVWidths } : {}
|
|
23025
|
-
} : baseStroke;
|
|
23026
|
-
if (fill == null && stroke == null) continue;
|
|
23027
|
-
children.push({
|
|
23028
|
-
kind: "path",
|
|
23029
|
-
path,
|
|
23030
|
-
...fill != null ? { fill } : {},
|
|
23031
|
-
...stroke != null ? { stroke } : {},
|
|
23032
|
-
...useVColors != null ? { vertexColors: useVColors } : {}
|
|
23033
|
-
});
|
|
23426
|
+
}
|
|
23427
|
+
return null;
|
|
23428
|
+
};
|
|
23429
|
+
const pickOutermost = (wx, wy) => {
|
|
23430
|
+
const leaf = hitLeaf(wx, wy);
|
|
23431
|
+
return leaf === null ? null : ancestorChain(leaf)[0];
|
|
23432
|
+
};
|
|
23433
|
+
const pickBest = (wx, wy, alt, selection) => {
|
|
23434
|
+
const leaf = hitLeaf(wx, wy);
|
|
23435
|
+
if (leaf === null) return null;
|
|
23436
|
+
const chain = ancestorChain(leaf);
|
|
23437
|
+
if (!alt) return chain[0];
|
|
23438
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
23439
|
+
if (selection.includes(chain[i])) {
|
|
23440
|
+
return chain[Math.min(i + 1, chain.length - 1)];
|
|
23034
23441
|
}
|
|
23035
|
-
return children;
|
|
23036
23442
|
}
|
|
23443
|
+
return chain[chain.length - 1];
|
|
23037
23444
|
};
|
|
23445
|
+
return { pickOutermost, pickBest };
|
|
23038
23446
|
}
|
|
23039
23447
|
|
|
23040
|
-
// src/features/paths/
|
|
23041
|
-
var
|
|
23042
|
-
function
|
|
23043
|
-
const
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
const
|
|
23047
|
-
|
|
23048
|
-
let j = i + 1;
|
|
23049
|
-
while (j < d.length && !/[A-Za-z]/.test(d[j])) j++;
|
|
23050
|
-
const argSlice = d.slice(i + 1, j);
|
|
23051
|
-
out.push({ cmd: ch, args: readNumbers(argSlice) });
|
|
23052
|
-
i = j;
|
|
23053
|
-
} else {
|
|
23054
|
-
i++;
|
|
23055
|
-
}
|
|
23448
|
+
// src/features/paths/pathDistance.ts
|
|
23449
|
+
var BEZIER_SAMPLES = 16;
|
|
23450
|
+
function pointSegDist2(px, py, ax, ay, bx, by) {
|
|
23451
|
+
const dx = bx - ax, dy = by - ay;
|
|
23452
|
+
const len2 = dx * dx + dy * dy;
|
|
23453
|
+
if (len2 === 0) {
|
|
23454
|
+
const ex2 = px - ax, ey2 = py - ay;
|
|
23455
|
+
return ex2 * ex2 + ey2 * ey2;
|
|
23056
23456
|
}
|
|
23057
|
-
|
|
23457
|
+
let t = ((px - ax) * dx + (py - ay) * dy) / len2;
|
|
23458
|
+
if (t < 0) t = 0;
|
|
23459
|
+
else if (t > 1) t = 1;
|
|
23460
|
+
const cx = ax + dx * t, cy = ay + dy * t;
|
|
23461
|
+
const ex = px - cx, ey = py - cy;
|
|
23462
|
+
return ex * ex + ey * ey;
|
|
23058
23463
|
}
|
|
23059
|
-
function
|
|
23060
|
-
|
|
23061
|
-
let
|
|
23062
|
-
|
|
23063
|
-
|
|
23064
|
-
const
|
|
23065
|
-
|
|
23464
|
+
function pointCubicDist2(px, py, x0, y0, x1, y1, x2, y2, x3, y3) {
|
|
23465
|
+
let prevX = x0, prevY = y0;
|
|
23466
|
+
let best = Infinity;
|
|
23467
|
+
for (let i = 1; i <= BEZIER_SAMPLES; i++) {
|
|
23468
|
+
const t = i / BEZIER_SAMPLES;
|
|
23469
|
+
const u = 1 - t;
|
|
23470
|
+
const sx = u * u * u * x0 + 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t * x3;
|
|
23471
|
+
const sy = u * u * u * y0 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y3;
|
|
23472
|
+
const d2 = pointSegDist2(px, py, prevX, prevY, sx, sy);
|
|
23473
|
+
if (d2 < best) best = d2;
|
|
23474
|
+
prevX = sx;
|
|
23475
|
+
prevY = sy;
|
|
23066
23476
|
}
|
|
23067
|
-
return
|
|
23477
|
+
return best;
|
|
23068
23478
|
}
|
|
23069
|
-
|
|
23070
|
-
|
|
23071
|
-
|
|
23072
|
-
|
|
23073
|
-
|
|
23074
|
-
|
|
23075
|
-
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
};
|
|
23081
|
-
function pathFromD(d, onWarn) {
|
|
23082
|
-
const tokens = tokenize(d);
|
|
23083
|
-
const b = new PathBuilder();
|
|
23084
|
-
const st = {
|
|
23085
|
-
x: 0,
|
|
23086
|
-
y: 0,
|
|
23087
|
-
startX: 0,
|
|
23088
|
-
startY: 0,
|
|
23089
|
-
lastCubicCtrl: null,
|
|
23090
|
-
lastQuadCtrl: null
|
|
23091
|
-
};
|
|
23092
|
-
for (const { cmd, args } of tokens) {
|
|
23093
|
-
const upper = cmd.toUpperCase();
|
|
23094
|
-
const relative = cmd !== upper;
|
|
23095
|
-
const argCount = ARG_COUNTS[upper];
|
|
23096
|
-
if (argCount === void 0) {
|
|
23097
|
-
onWarn?.(`unknown path command: ${cmd}`);
|
|
23098
|
-
continue;
|
|
23099
|
-
}
|
|
23100
|
-
if (upper === "Z") {
|
|
23101
|
-
b.close();
|
|
23102
|
-
st.x = st.startX;
|
|
23103
|
-
st.y = st.startY;
|
|
23104
|
-
st.lastCubicCtrl = null;
|
|
23105
|
-
st.lastQuadCtrl = null;
|
|
23106
|
-
continue;
|
|
23107
|
-
}
|
|
23108
|
-
const groups = argCount === 0 ? 1 : Math.floor(args.length / argCount);
|
|
23109
|
-
for (let g = 0; g < groups; g++) {
|
|
23110
|
-
const a = args.slice(g * argCount, (g + 1) * argCount);
|
|
23111
|
-
const effectiveUpper = upper === "M" && g > 0 ? "L" : upper;
|
|
23112
|
-
runCommand(effectiveUpper, relative, a, st, b);
|
|
23113
|
-
}
|
|
23479
|
+
function pointToRectPerimeterDist(px, py, rx, ry, rw, rh) {
|
|
23480
|
+
const dx = Math.max(rx - px, px - (rx + rw), 0);
|
|
23481
|
+
const dy = Math.max(ry - py, py - (ry + rh), 0);
|
|
23482
|
+
if (dx > 0 || dy > 0) return Math.hypot(dx, dy);
|
|
23483
|
+
const left = px - rx, right = rx + rw - px;
|
|
23484
|
+
const top = py - ry, bottom = ry + rh - py;
|
|
23485
|
+
return Math.min(left, right, top, bottom);
|
|
23486
|
+
}
|
|
23487
|
+
function pathDistanceToPoint(path, px, py) {
|
|
23488
|
+
if (path.kind === "rect") {
|
|
23489
|
+
return pointToRectPerimeterDist(px, py, path.x, path.y, path.width, path.height);
|
|
23114
23490
|
}
|
|
23115
|
-
return
|
|
23491
|
+
return polygonPathDistance(path, px, py);
|
|
23116
23492
|
}
|
|
23117
|
-
function
|
|
23118
|
-
const
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
23126
|
-
|
|
23127
|
-
|
|
23128
|
-
|
|
23129
|
-
|
|
23130
|
-
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
|
|
23137
|
-
|
|
23138
|
-
|
|
23139
|
-
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
23144
|
-
|
|
23145
|
-
|
|
23146
|
-
|
|
23147
|
-
|
|
23148
|
-
|
|
23149
|
-
|
|
23150
|
-
|
|
23151
|
-
|
|
23152
|
-
const
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
const
|
|
23161
|
-
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
st.x = x;
|
|
23165
|
-
st.y = y;
|
|
23166
|
-
st.lastCubicCtrl = { x: x2, y: y2 };
|
|
23167
|
-
st.lastQuadCtrl = null;
|
|
23168
|
-
return;
|
|
23169
|
-
}
|
|
23170
|
-
case "S": {
|
|
23171
|
-
const ref = st.lastCubicCtrl ?? { x: st.x, y: st.y };
|
|
23172
|
-
const x1 = 2 * st.x - ref.x;
|
|
23173
|
-
const y1 = 2 * st.y - ref.y;
|
|
23174
|
-
const x2 = a[0] + rx, y2 = a[1] + ry;
|
|
23175
|
-
const x = a[2] + rx, y = a[3] + ry;
|
|
23176
|
-
b.curveTo(x1, y1, x2, y2, x, y);
|
|
23177
|
-
st.x = x;
|
|
23178
|
-
st.y = y;
|
|
23179
|
-
st.lastCubicCtrl = { x: x2, y: y2 };
|
|
23180
|
-
st.lastQuadCtrl = null;
|
|
23181
|
-
return;
|
|
23182
|
-
}
|
|
23183
|
-
case "Q": {
|
|
23184
|
-
const x1 = a[0] + rx, y1 = a[1] + ry;
|
|
23185
|
-
const x = a[2] + rx, y = a[3] + ry;
|
|
23186
|
-
b.quadTo(x1, y1, x, y);
|
|
23187
|
-
st.x = x;
|
|
23188
|
-
st.y = y;
|
|
23189
|
-
st.lastQuadCtrl = { x: x1, y: y1 };
|
|
23190
|
-
st.lastCubicCtrl = null;
|
|
23191
|
-
return;
|
|
23192
|
-
}
|
|
23193
|
-
case "T": {
|
|
23194
|
-
const ref = st.lastQuadCtrl ?? { x: st.x, y: st.y };
|
|
23195
|
-
const x1 = 2 * st.x - ref.x;
|
|
23196
|
-
const y1 = 2 * st.y - ref.y;
|
|
23197
|
-
const x = a[0] + rx, y = a[1] + ry;
|
|
23198
|
-
b.quadTo(x1, y1, x, y);
|
|
23199
|
-
st.x = x;
|
|
23200
|
-
st.y = y;
|
|
23201
|
-
st.lastQuadCtrl = { x: x1, y: y1 };
|
|
23202
|
-
st.lastCubicCtrl = null;
|
|
23203
|
-
return;
|
|
23204
|
-
}
|
|
23205
|
-
case "A": {
|
|
23206
|
-
const rxA = Math.abs(a[0]);
|
|
23207
|
-
const ryA = Math.abs(a[1]);
|
|
23208
|
-
const xRot = a[2];
|
|
23209
|
-
const largeArc = a[3] !== 0;
|
|
23210
|
-
const sweep = a[4] !== 0;
|
|
23211
|
-
const ex = a[5] + rx;
|
|
23212
|
-
const ey = a[6] + ry;
|
|
23213
|
-
arcToCubics(st.x, st.y, ex, ey, rxA, ryA, xRot, largeArc, sweep, b);
|
|
23214
|
-
st.x = ex;
|
|
23215
|
-
st.y = ey;
|
|
23216
|
-
st.lastCubicCtrl = null;
|
|
23217
|
-
st.lastQuadCtrl = null;
|
|
23218
|
-
return;
|
|
23493
|
+
function polygonPathDistance(path, px, py) {
|
|
23494
|
+
const { commands, coords } = path;
|
|
23495
|
+
let ci = 0;
|
|
23496
|
+
let startX = 0, startY = 0;
|
|
23497
|
+
let curX = 0, curY = 0;
|
|
23498
|
+
let best = Infinity;
|
|
23499
|
+
for (let i = 0; i < commands.length; i++) {
|
|
23500
|
+
const cmd = commands[i];
|
|
23501
|
+
if (cmd === PATH_M) {
|
|
23502
|
+
startX = coords[ci];
|
|
23503
|
+
startY = coords[ci + 1];
|
|
23504
|
+
curX = startX;
|
|
23505
|
+
curY = startY;
|
|
23506
|
+
ci += 2;
|
|
23507
|
+
} else if (cmd === PATH_L) {
|
|
23508
|
+
const x = coords[ci], y = coords[ci + 1];
|
|
23509
|
+
const d2 = pointSegDist2(px, py, curX, curY, x, y);
|
|
23510
|
+
if (d2 < best) best = d2;
|
|
23511
|
+
curX = x;
|
|
23512
|
+
curY = y;
|
|
23513
|
+
ci += 2;
|
|
23514
|
+
} else if (cmd === PATH_C) {
|
|
23515
|
+
const c1x = coords[ci], c1y = coords[ci + 1];
|
|
23516
|
+
const c2x = coords[ci + 2], c2y = coords[ci + 3];
|
|
23517
|
+
const x = coords[ci + 4], y = coords[ci + 5];
|
|
23518
|
+
const d2 = pointCubicDist2(px, py, curX, curY, c1x, c1y, c2x, c2y, x, y);
|
|
23519
|
+
if (d2 < best) best = d2;
|
|
23520
|
+
curX = x;
|
|
23521
|
+
curY = y;
|
|
23522
|
+
ci += 6;
|
|
23523
|
+
} else if (cmd === PATH_Q) {
|
|
23524
|
+
const qx = coords[ci], qy = coords[ci + 1];
|
|
23525
|
+
const x = coords[ci + 2], y = coords[ci + 3];
|
|
23526
|
+
const c1x = curX + 2 / 3 * (qx - curX);
|
|
23527
|
+
const c1y = curY + 2 / 3 * (qy - curY);
|
|
23528
|
+
const c2x = x + 2 / 3 * (qx - x);
|
|
23529
|
+
const c2y = y + 2 / 3 * (qy - y);
|
|
23530
|
+
const d2 = pointCubicDist2(px, py, curX, curY, c1x, c1y, c2x, c2y, x, y);
|
|
23531
|
+
if (d2 < best) best = d2;
|
|
23532
|
+
curX = x;
|
|
23533
|
+
curY = y;
|
|
23534
|
+
ci += 4;
|
|
23535
|
+
} else if (cmd === PATH_Z) {
|
|
23536
|
+
const d2 = pointSegDist2(px, py, curX, curY, startX, startY);
|
|
23537
|
+
if (d2 < best) best = d2;
|
|
23538
|
+
curX = startX;
|
|
23539
|
+
curY = startY;
|
|
23219
23540
|
}
|
|
23220
|
-
default:
|
|
23221
|
-
return;
|
|
23222
23541
|
}
|
|
23542
|
+
return best === Infinity ? Infinity : Math.sqrt(best);
|
|
23223
23543
|
}
|
|
23224
|
-
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
const x1p = cosPhi * dx + sinPhi * dy;
|
|
23236
|
-
const y1p = -sinPhi * dx + cosPhi * dy;
|
|
23237
|
-
let rxs = rx * rx;
|
|
23238
|
-
let rys = ry * ry;
|
|
23239
|
-
const x1ps = x1p * x1p;
|
|
23240
|
-
const y1ps = y1p * y1p;
|
|
23241
|
-
const lambda = x1ps / rxs + y1ps / rys;
|
|
23242
|
-
let rxFixed = rx;
|
|
23243
|
-
let ryFixed = ry;
|
|
23244
|
-
if (lambda > 1) {
|
|
23245
|
-
const sqrtLambda = Math.sqrt(lambda);
|
|
23246
|
-
rxFixed = sqrtLambda * rx;
|
|
23247
|
-
ryFixed = sqrtLambda * ry;
|
|
23248
|
-
rxs = rxFixed * rxFixed;
|
|
23249
|
-
rys = ryFixed * ryFixed;
|
|
23544
|
+
|
|
23545
|
+
// src/features/paths/pathLayer.ts
|
|
23546
|
+
var PLACEHOLDER_FILL = { color: "#ffffff" };
|
|
23547
|
+
var PLACEHOLDER_STROKE = { paint: { color: "#ffffff" }, width: 1 };
|
|
23548
|
+
function resolveOverride(base, override, tMs) {
|
|
23549
|
+
if (!override) return base;
|
|
23550
|
+
if (typeof override === "function") {
|
|
23551
|
+
if (!base) return base;
|
|
23552
|
+
const result = override(base, tMs);
|
|
23553
|
+
if (result.length !== base.length) return base;
|
|
23554
|
+
return result;
|
|
23250
23555
|
}
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
const
|
|
23255
|
-
|
|
23256
|
-
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
23264
|
-
|
|
23556
|
+
return override;
|
|
23557
|
+
}
|
|
23558
|
+
function createPathLayer(opts) {
|
|
23559
|
+
const {
|
|
23560
|
+
id = "paths",
|
|
23561
|
+
label = "Paths",
|
|
23562
|
+
getNodes,
|
|
23563
|
+
getPath,
|
|
23564
|
+
getFill,
|
|
23565
|
+
getStroke,
|
|
23566
|
+
isHidden,
|
|
23567
|
+
getVertexColors,
|
|
23568
|
+
getStrokeVertexColors,
|
|
23569
|
+
getStrokeVertexWidths,
|
|
23570
|
+
colorOverrides,
|
|
23571
|
+
now = () => typeof performance !== "undefined" ? performance.now() : Date.now()
|
|
23572
|
+
} = opts;
|
|
23573
|
+
const warned = /* @__PURE__ */ new Set();
|
|
23574
|
+
const isDev3 = typeof import.meta !== "undefined" && import.meta.env && import.meta.env.DEV;
|
|
23575
|
+
return {
|
|
23576
|
+
id,
|
|
23577
|
+
label,
|
|
23578
|
+
draw: () => {
|
|
23579
|
+
const children = [];
|
|
23580
|
+
const nodes = getNodes();
|
|
23581
|
+
for (let idx = 0; idx < nodes.length; idx++) {
|
|
23582
|
+
const node = nodes[idx];
|
|
23583
|
+
if (isHidden?.(node)) continue;
|
|
23584
|
+
const path = getPath(node);
|
|
23585
|
+
const fillFromHook = getFill?.(node);
|
|
23586
|
+
const strokeFromHook = getStroke?.(node);
|
|
23587
|
+
const baseVColors = getVertexColors?.(node);
|
|
23588
|
+
const baseStrokeVColors = getStrokeVertexColors?.(node);
|
|
23589
|
+
const strokeVWidths = getStrokeVertexWidths?.(node);
|
|
23590
|
+
const nodeId = node.id ?? String(idx);
|
|
23591
|
+
const tMs = colorOverrides ? now() : 0;
|
|
23592
|
+
const fillOverride = colorOverrides?.get(nodeId, "fill");
|
|
23593
|
+
const strokeOverride = colorOverrides?.get(nodeId, "stroke");
|
|
23594
|
+
const vColors = resolveOverride(baseVColors, fillOverride, tMs);
|
|
23595
|
+
const strokeVColors = resolveOverride(baseStrokeVColors, strokeOverride, tMs);
|
|
23596
|
+
const nodeKey = nodeId;
|
|
23597
|
+
const anchorCount2 = countPathAnchors(path);
|
|
23598
|
+
const expectedLen = 4 * anchorCount2;
|
|
23599
|
+
let useVColors = null;
|
|
23600
|
+
if (vColors != null) {
|
|
23601
|
+
if (vColors.length === expectedLen) {
|
|
23602
|
+
useVColors = vColors;
|
|
23603
|
+
} else if (isDev3) {
|
|
23604
|
+
const key = `${id}:${nodeKey}:fill`;
|
|
23605
|
+
if (!warned.has(key)) {
|
|
23606
|
+
warned.add(key);
|
|
23607
|
+
console.warn(
|
|
23608
|
+
`[createPathLayer ${id}] node ${nodeKey}: fill vertexColors length ${vColors.length}, expected ${expectedLen}; dropping`
|
|
23609
|
+
);
|
|
23610
|
+
}
|
|
23611
|
+
}
|
|
23612
|
+
}
|
|
23613
|
+
let useStrokeVColors = null;
|
|
23614
|
+
if (strokeVColors != null) {
|
|
23615
|
+
if (strokeVColors.length === expectedLen) {
|
|
23616
|
+
useStrokeVColors = strokeVColors;
|
|
23617
|
+
} else if (isDev3) {
|
|
23618
|
+
const key = `${id}:${nodeKey}:stroke`;
|
|
23619
|
+
if (!warned.has(key)) {
|
|
23620
|
+
warned.add(key);
|
|
23621
|
+
console.warn(
|
|
23622
|
+
`[createPathLayer ${id}] node ${nodeKey}: stroke vertexColors length ${strokeVColors.length}, expected ${expectedLen}; dropping`
|
|
23623
|
+
);
|
|
23624
|
+
}
|
|
23625
|
+
}
|
|
23626
|
+
}
|
|
23627
|
+
const fill = fillFromHook != null ? fillFromHook : useVColors != null ? PLACEHOLDER_FILL : void 0;
|
|
23628
|
+
let useStrokeVWidths = null;
|
|
23629
|
+
if (strokeVWidths != null) {
|
|
23630
|
+
if (strokeVWidths.length === anchorCount2) {
|
|
23631
|
+
useStrokeVWidths = strokeVWidths;
|
|
23632
|
+
} else if (isDev3) {
|
|
23633
|
+
const key = `${id}:${nodeKey}:strokeWidths`;
|
|
23634
|
+
if (!warned.has(key)) {
|
|
23635
|
+
warned.add(key);
|
|
23636
|
+
console.warn(
|
|
23637
|
+
`[createPathLayer ${id}] node ${nodeKey}: stroke vertexWidths length ${strokeVWidths.length}, expected ${anchorCount2}; dropping`
|
|
23638
|
+
);
|
|
23639
|
+
}
|
|
23640
|
+
}
|
|
23641
|
+
}
|
|
23642
|
+
const baseStroke = strokeFromHook != null ? strokeFromHook : useStrokeVColors != null || useStrokeVWidths != null ? PLACEHOLDER_STROKE : void 0;
|
|
23643
|
+
const stroke = baseStroke != null ? {
|
|
23644
|
+
...baseStroke,
|
|
23645
|
+
...useStrokeVColors != null ? { vertexColors: useStrokeVColors } : {},
|
|
23646
|
+
...useStrokeVWidths != null ? { vertexWidths: useStrokeVWidths } : {}
|
|
23647
|
+
} : baseStroke;
|
|
23648
|
+
if (fill == null && stroke == null) continue;
|
|
23649
|
+
children.push({
|
|
23650
|
+
kind: "path",
|
|
23651
|
+
path,
|
|
23652
|
+
...fill != null ? { fill } : {},
|
|
23653
|
+
...stroke != null ? { stroke } : {},
|
|
23654
|
+
...useVColors != null ? { vertexColors: useVColors } : {}
|
|
23655
|
+
});
|
|
23656
|
+
}
|
|
23657
|
+
return children;
|
|
23658
|
+
}
|
|
23265
23659
|
};
|
|
23266
|
-
const theta1 = angle(1, 0, (x1p - cxp) / rxFixed, (y1p - cyp) / ryFixed);
|
|
23267
|
-
let dTheta = angle(
|
|
23268
|
-
(x1p - cxp) / rxFixed,
|
|
23269
|
-
(y1p - cyp) / ryFixed,
|
|
23270
|
-
(-x1p - cxp) / rxFixed,
|
|
23271
|
-
(-y1p - cyp) / ryFixed
|
|
23272
|
-
);
|
|
23273
|
-
if (!sweep && dTheta > 0) dTheta -= 2 * Math.PI;
|
|
23274
|
-
else if (sweep && dTheta < 0) dTheta += 2 * Math.PI;
|
|
23275
|
-
const segCount = Math.max(1, Math.ceil(Math.abs(dTheta) / (Math.PI / 2)));
|
|
23276
|
-
const segAngle = dTheta / segCount;
|
|
23277
|
-
const t = 4 / 3 * Math.tan(segAngle / 4);
|
|
23278
|
-
let prevX = x1;
|
|
23279
|
-
let prevY = y1;
|
|
23280
|
-
let prevDx = -Math.sin(theta1) * rxFixed;
|
|
23281
|
-
let prevDy = Math.cos(theta1) * ryFixed;
|
|
23282
|
-
for (let i = 1; i <= segCount; i++) {
|
|
23283
|
-
const theta = theta1 + i * segAngle;
|
|
23284
|
-
const cosT = Math.cos(theta);
|
|
23285
|
-
const sinT = Math.sin(theta);
|
|
23286
|
-
const px = cosPhi * (cosT * rxFixed) - sinPhi * (sinT * ryFixed) + cx;
|
|
23287
|
-
const py = sinPhi * (cosT * rxFixed) + cosPhi * (sinT * ryFixed) + cy;
|
|
23288
|
-
const dxNew = -sinT * rxFixed;
|
|
23289
|
-
const dyNew = cosT * ryFixed;
|
|
23290
|
-
const c1x = prevX + cosPhi * (t * prevDx) - sinPhi * (t * prevDy);
|
|
23291
|
-
const c1y = prevY + sinPhi * (t * prevDx) + cosPhi * (t * prevDy);
|
|
23292
|
-
const c2x = px - cosPhi * (t * dxNew) + sinPhi * (t * dyNew);
|
|
23293
|
-
const c2y = py - sinPhi * (t * dxNew) - cosPhi * (t * dyNew);
|
|
23294
|
-
b.curveTo(c1x, c1y, c2x, c2y, px, py);
|
|
23295
|
-
prevX = px;
|
|
23296
|
-
prevY = py;
|
|
23297
|
-
prevDx = dxNew;
|
|
23298
|
-
prevDy = dyNew;
|
|
23299
|
-
}
|
|
23300
23660
|
}
|
|
23301
23661
|
|
|
23302
23662
|
// src/features/paths/compose.ts
|
|
@@ -24763,6 +25123,50 @@ function useDragRadial(options = {}) {
|
|
|
24763
25123
|
}
|
|
24764
25124
|
}), [start, move, gesture.end, gesture.cancel]);
|
|
24765
25125
|
}
|
|
25126
|
+
|
|
25127
|
+
// src/interactions/actions/edit-anchors/handles.ts
|
|
25128
|
+
function hitAnchor(path, worldX, worldY, threshold) {
|
|
25129
|
+
const anchors = enumerateAnchors(path);
|
|
25130
|
+
let bestControl = null;
|
|
25131
|
+
let bestAnchor = null;
|
|
25132
|
+
const t2 = threshold * threshold;
|
|
25133
|
+
for (const a of anchors) {
|
|
25134
|
+
if (a.controlIn) {
|
|
25135
|
+
const dx2 = a.controlIn.x - worldX;
|
|
25136
|
+
const dy2 = a.controlIn.y - worldY;
|
|
25137
|
+
const d22 = dx2 * dx2 + dy2 * dy2;
|
|
25138
|
+
if (d22 <= t2 && (!bestControl || d22 < bestControl.d2)) {
|
|
25139
|
+
bestControl = {
|
|
25140
|
+
d2: d22,
|
|
25141
|
+
hit: { anchorIndex: a.anchorIndex, kind: "controlIn", coordIndex: a.controlIn.coordIndex }
|
|
25142
|
+
};
|
|
25143
|
+
}
|
|
25144
|
+
}
|
|
25145
|
+
if (a.controlOut) {
|
|
25146
|
+
const dx2 = a.controlOut.x - worldX;
|
|
25147
|
+
const dy2 = a.controlOut.y - worldY;
|
|
25148
|
+
const d22 = dx2 * dx2 + dy2 * dy2;
|
|
25149
|
+
if (d22 <= t2 && (!bestControl || d22 < bestControl.d2)) {
|
|
25150
|
+
bestControl = {
|
|
25151
|
+
d2: d22,
|
|
25152
|
+
hit: { anchorIndex: a.anchorIndex, kind: "controlOut", coordIndex: a.controlOut.coordIndex }
|
|
25153
|
+
};
|
|
25154
|
+
}
|
|
25155
|
+
}
|
|
25156
|
+
const dx = a.x - worldX;
|
|
25157
|
+
const dy = a.y - worldY;
|
|
25158
|
+
const d2 = dx * dx + dy * dy;
|
|
25159
|
+
if (d2 <= t2 && (!bestAnchor || d2 < bestAnchor.d2)) {
|
|
25160
|
+
bestAnchor = {
|
|
25161
|
+
d2,
|
|
25162
|
+
hit: { anchorIndex: a.anchorIndex, kind: "anchor", coordIndex: a.coordIndex }
|
|
25163
|
+
};
|
|
25164
|
+
}
|
|
25165
|
+
}
|
|
25166
|
+
if (bestControl) return bestControl.hit;
|
|
25167
|
+
if (bestAnchor) return bestAnchor.hit;
|
|
25168
|
+
return null;
|
|
25169
|
+
}
|
|
24766
25170
|
function useDistribute(adapter, options = {}) {
|
|
24767
25171
|
const adapterRef = useRef(adapter);
|
|
24768
25172
|
adapterRef.current = adapter;
|
|
@@ -26197,7 +26601,7 @@ function hslToRgbUnit(h, s, l) {
|
|
|
26197
26601
|
}
|
|
26198
26602
|
|
|
26199
26603
|
// src/layout/snaps.ts
|
|
26200
|
-
function
|
|
26604
|
+
function dist22(a, b) {
|
|
26201
26605
|
const dx = a.x - b.x;
|
|
26202
26606
|
const dy = a.y - b.y;
|
|
26203
26607
|
return dx * dx + dy * dy;
|
|
@@ -26205,9 +26609,9 @@ function dist23(a, b) {
|
|
|
26205
26609
|
function nearestOf(targets, pointer) {
|
|
26206
26610
|
if (targets.length === 0) return null;
|
|
26207
26611
|
let best = targets[0];
|
|
26208
|
-
let bestD =
|
|
26612
|
+
let bestD = dist22(best.origin, pointer);
|
|
26209
26613
|
for (let i = 1; i < targets.length; i++) {
|
|
26210
|
-
const d =
|
|
26614
|
+
const d = dist22(targets[i].origin, pointer);
|
|
26211
26615
|
if (d < bestD) {
|
|
26212
26616
|
bestD = d;
|
|
26213
26617
|
best = targets[i];
|
|
@@ -26227,7 +26631,7 @@ function nearestWithin(opts) {
|
|
|
26227
26631
|
pickTarget(targets, pointer) {
|
|
26228
26632
|
const got = nearestOf(targets, pointer);
|
|
26229
26633
|
if (got === null) return null;
|
|
26230
|
-
return
|
|
26634
|
+
return dist22(got.origin, pointer) <= tol2 ? got : null;
|
|
26231
26635
|
}
|
|
26232
26636
|
};
|
|
26233
26637
|
}
|
|
@@ -26466,6 +26870,6 @@ function snapPoint(opts) {
|
|
|
26466
26870
|
};
|
|
26467
26871
|
}
|
|
26468
26872
|
|
|
26469
|
-
export { ALWAYS, ActionDisabledReason, ActionsProvider, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_STYLE, DEFAULT_VELOCITY_DECAY, DepRegistryProvider, DepRegistryProviderIfRoot, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_POSE_COMPOSITION, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, RectIcon, SPRING_PRESETS, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TextIcon, UnionIcon, UnknownIcon, WeaselProvider, WeaselRenderer, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, alignedStrokeRect, always, and, animateLifecycle, animateOnSetPose, applyBooleanOp, applyHitExistingGate, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, bezierCubic, bezierQuadratic, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, cloneAction, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView2 as computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewportLayer, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic2 as easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, evaluateEnabled, findNodeShape, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, hexToRgba, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerNodeShape, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, sliceAction, snapPoint, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, tileGrid, toHex8, toRuns, translatePoseViaDescriptor, translateRectPose, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useAction, useActionsRegistry, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDepRegistry, useDepSource, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, useOptionalDepRegistry, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, verticalAlignOffset, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
|
|
26470
|
-
//# sourceMappingURL=chunk-
|
|
26471
|
-
//# sourceMappingURL=chunk-
|
|
26873
|
+
export { ALWAYS, ActionDisabledReason, ActionsProvider, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, BUNDLE_TOOLS, CURVE_REPS, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_THEME, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_STROKE_COLOR, DEFAULT_TEXT_STYLE, DEFAULT_VELOCITY_DECAY, DepRegistryProvider, DepRegistryProviderIfRoot, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_POSE_COMPOSITION, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, RectIcon, SPRING_PRESETS, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TextIcon, UnionIcon, UnknownIcon, VERSION, WeaselProvider, WeaselRenderer, aabbCenter, actionIs, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, alignedStrokeRect, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyHitExistingGate, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, bezierCubic, bezierQuadratic, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, cloneAction, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView2 as computeFitView, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMarkdownRenderer, createMoveToIndexOp, createNodeProperties, createNodeRouting, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createViewportLayer, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic2 as easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, evaluateEnabled, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, fontString, forEachCoalesced, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getNodeShapes, getStylusData, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, isEditableTarget2 as isEditableTarget, lassoSelectAction, layoutMarkdown, lerpColorArray, lerpOklab, lerpOklch, linear, makeViewportZoomAction, markdownToRuns, mat3, matchAlignment, matchesKeyBinding, measureText, measureTextBounds, measuredWidth, mergeAlphaFromPrev, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, or, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerNodeShape, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveRuns, resolveTextStyle, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, runsToMarkdown, runsToPlainText, sceneFromJSON, sceneNodeClientRect, sceneToAdapter, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, shapeCoversPoint, sliceAction, snapPoint, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textLineBoxes, tileGrid, toHex8, toRuns, translatePoseViaDescriptor, translateRectPose, tweenPose, tweenVertexColors, unionBounds, unionBoundsPath, useAction, useActionsRegistry, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDepRegistry, useDepSource, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useOptionalActiveToolContext, useOptionalDepRegistry, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewTween, useZoom, verticalAlignOffset, viewToMat3, viewToTransform, viewportDragPanAction, viewportZoomAction, when, withAlpha01, withCoord, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
|
|
26874
|
+
//# sourceMappingURL=chunk-2J6V527H.js.map
|
|
26875
|
+
//# sourceMappingURL=chunk-2J6V527H.js.map
|