@fieldnotes/core 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +481 -481
- package/dist/index.cjs +238 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +238 -127
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1933,10 +1933,134 @@ function getTemplateBounds(el) {
|
|
|
1933
1933
|
}
|
|
1934
1934
|
}
|
|
1935
1935
|
}
|
|
1936
|
+
function transferStrokeBounds(prev, next) {
|
|
1937
|
+
if (prev.type !== "stroke" || next.type !== "stroke") return;
|
|
1938
|
+
if (prev.points !== next.points) return;
|
|
1939
|
+
if (prev.position.x !== next.position.x || prev.position.y !== next.position.y) return;
|
|
1940
|
+
const bounds = strokeBoundsCache.get(prev);
|
|
1941
|
+
if (bounds) strokeBoundsCache.set(next, bounds);
|
|
1942
|
+
}
|
|
1936
1943
|
function boundsIntersect(a, b) {
|
|
1937
1944
|
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
1938
1945
|
}
|
|
1939
1946
|
|
|
1947
|
+
// src/elements/stroke-smoothing.ts
|
|
1948
|
+
var MIN_PRESSURE_SCALE = 0.2;
|
|
1949
|
+
function pressureToWidth(pressure, baseWidth) {
|
|
1950
|
+
return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
|
|
1951
|
+
}
|
|
1952
|
+
function simplifyPoints(points, tolerance) {
|
|
1953
|
+
if (points.length <= 2) return points.slice();
|
|
1954
|
+
return rdp(points, 0, points.length - 1, tolerance);
|
|
1955
|
+
}
|
|
1956
|
+
function rdp(points, start, end, tolerance) {
|
|
1957
|
+
const first = points[start];
|
|
1958
|
+
const last = points[end];
|
|
1959
|
+
if (!first || !last) return [];
|
|
1960
|
+
if (end - start <= 1) return [first, last];
|
|
1961
|
+
let maxDist = 0;
|
|
1962
|
+
let maxIndex = start;
|
|
1963
|
+
for (let i = start + 1; i < end; i++) {
|
|
1964
|
+
const pt = points[i];
|
|
1965
|
+
if (!pt) continue;
|
|
1966
|
+
const dist = perpendicularDistance(pt, first, last);
|
|
1967
|
+
if (dist > maxDist) {
|
|
1968
|
+
maxDist = dist;
|
|
1969
|
+
maxIndex = i;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
if (maxDist <= tolerance) return [first, last];
|
|
1973
|
+
const left = rdp(points, start, maxIndex, tolerance);
|
|
1974
|
+
const right = rdp(points, maxIndex, end, tolerance);
|
|
1975
|
+
return left.concat(right.slice(1));
|
|
1976
|
+
}
|
|
1977
|
+
function perpendicularDistance(pt, lineStart, lineEnd) {
|
|
1978
|
+
const dx = lineEnd.x - lineStart.x;
|
|
1979
|
+
const dy = lineEnd.y - lineStart.y;
|
|
1980
|
+
const lenSq = dx * dx + dy * dy;
|
|
1981
|
+
if (lenSq === 0) {
|
|
1982
|
+
const ex = pt.x - lineStart.x;
|
|
1983
|
+
const ey = pt.y - lineStart.y;
|
|
1984
|
+
return Math.sqrt(ex * ex + ey * ey);
|
|
1985
|
+
}
|
|
1986
|
+
const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
|
|
1987
|
+
return num / Math.sqrt(lenSq);
|
|
1988
|
+
}
|
|
1989
|
+
function smoothToSegments(points) {
|
|
1990
|
+
if (points.length < 2) return [];
|
|
1991
|
+
if (points.length === 2) {
|
|
1992
|
+
const p0 = points[0];
|
|
1993
|
+
const p1 = points[1];
|
|
1994
|
+
if (!p0 || !p1) return [];
|
|
1995
|
+
const mx = (p0.x + p1.x) / 2;
|
|
1996
|
+
const my = (p0.y + p1.y) / 2;
|
|
1997
|
+
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
1998
|
+
}
|
|
1999
|
+
const segments = [];
|
|
2000
|
+
const n = points.length;
|
|
2001
|
+
for (let i = 0; i < n - 1; i++) {
|
|
2002
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
2003
|
+
const p1 = points[i];
|
|
2004
|
+
const p2 = points[i + 1];
|
|
2005
|
+
const p3 = points[Math.min(n - 1, i + 2)];
|
|
2006
|
+
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
2007
|
+
const cp1 = {
|
|
2008
|
+
x: p1.x + (p2.x - p0.x) / 6,
|
|
2009
|
+
y: p1.y + (p2.y - p0.y) / 6
|
|
2010
|
+
};
|
|
2011
|
+
const cp2 = {
|
|
2012
|
+
x: p2.x - (p3.x - p1.x) / 6,
|
|
2013
|
+
y: p2.y - (p3.y - p1.y) / 6
|
|
2014
|
+
};
|
|
2015
|
+
segments.push({ start: p1, cp1, cp2, end: p2 });
|
|
2016
|
+
}
|
|
2017
|
+
return segments;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// src/elements/stroke-cache.ts
|
|
2021
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
2022
|
+
var WIDTH_QUANTUM = 0.25;
|
|
2023
|
+
function buildWidthBuckets(segments, widths) {
|
|
2024
|
+
if (typeof Path2D === "undefined") return null;
|
|
2025
|
+
const byWidth = /* @__PURE__ */ new Map();
|
|
2026
|
+
for (let i = 0; i < segments.length; i++) {
|
|
2027
|
+
const seg = segments[i];
|
|
2028
|
+
const w = widths[i];
|
|
2029
|
+
if (!seg || w === void 0) continue;
|
|
2030
|
+
const q = Math.max(WIDTH_QUANTUM, Math.round(w / WIDTH_QUANTUM) * WIDTH_QUANTUM);
|
|
2031
|
+
let path = byWidth.get(q);
|
|
2032
|
+
if (!path) {
|
|
2033
|
+
path = new Path2D();
|
|
2034
|
+
byWidth.set(q, path);
|
|
2035
|
+
}
|
|
2036
|
+
path.moveTo(seg.start.x, seg.start.y);
|
|
2037
|
+
path.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
|
|
2038
|
+
}
|
|
2039
|
+
return [...byWidth.entries()].map(([width, path]) => ({ width, path }));
|
|
2040
|
+
}
|
|
2041
|
+
function computeStrokeSegments(stroke) {
|
|
2042
|
+
const segments = smoothToSegments(stroke.points);
|
|
2043
|
+
const widths = [];
|
|
2044
|
+
for (const seg of segments) {
|
|
2045
|
+
const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
|
|
2046
|
+
widths.push(w);
|
|
2047
|
+
}
|
|
2048
|
+
const data = { segments, widths, buckets: buildWidthBuckets(segments, widths) };
|
|
2049
|
+
cache.set(stroke, data);
|
|
2050
|
+
return data;
|
|
2051
|
+
}
|
|
2052
|
+
function getStrokeRenderData(stroke) {
|
|
2053
|
+
const cached = cache.get(stroke);
|
|
2054
|
+
if (cached) return cached;
|
|
2055
|
+
return computeStrokeSegments(stroke);
|
|
2056
|
+
}
|
|
2057
|
+
function transferStrokeRenderData(prev, next) {
|
|
2058
|
+
if (prev.type !== "stroke" || next.type !== "stroke") return;
|
|
2059
|
+
if (prev.points !== next.points) return;
|
|
2060
|
+
const data = cache.get(prev);
|
|
2061
|
+
if (data) cache.set(next, data);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1940
2064
|
// src/elements/element-store.ts
|
|
1941
2065
|
var ElementStore = class {
|
|
1942
2066
|
elements = /* @__PURE__ */ new Map();
|
|
@@ -1987,6 +2111,10 @@ var ElementStore = class {
|
|
|
1987
2111
|
this.sortedCache = null;
|
|
1988
2112
|
this._versions.set(id, (this._versions.get(id) ?? 0) + 1);
|
|
1989
2113
|
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
2114
|
+
if (updated.type === "stroke" && existing.type === "stroke") {
|
|
2115
|
+
transferStrokeRenderData(existing, updated);
|
|
2116
|
+
transferStrokeBounds(existing, updated);
|
|
2117
|
+
}
|
|
1990
2118
|
if (updated.type === "arrow") {
|
|
1991
2119
|
const arrow = updated;
|
|
1992
2120
|
arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
@@ -2030,6 +2158,12 @@ var ElementStore = class {
|
|
|
2030
2158
|
this._versions.set(el.id, 0);
|
|
2031
2159
|
const bounds = getElementBounds(el);
|
|
2032
2160
|
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
2161
|
+
if (el.type === "stroke") {
|
|
2162
|
+
computeStrokeSegments(el);
|
|
2163
|
+
}
|
|
2164
|
+
if (el.type === "arrow" && el.bend !== 0 && !el.cachedControlPoint) {
|
|
2165
|
+
el.cachedControlPoint = getArrowControlPoint(el.from, el.to, el.bend);
|
|
2166
|
+
}
|
|
2033
2167
|
}
|
|
2034
2168
|
this.bus.emit("clear", null);
|
|
2035
2169
|
for (const el of elements) {
|
|
@@ -2115,6 +2249,20 @@ var ElementStore = class {
|
|
|
2115
2249
|
}
|
|
2116
2250
|
};
|
|
2117
2251
|
|
|
2252
|
+
// src/elements/arrow-render-cache.ts
|
|
2253
|
+
var cache2 = /* @__PURE__ */ new WeakMap();
|
|
2254
|
+
function getArrowRenderGeometry(arrow) {
|
|
2255
|
+
const hit = cache2.get(arrow);
|
|
2256
|
+
if (hit) return hit;
|
|
2257
|
+
const geometry = {
|
|
2258
|
+
controlPoint: arrow.bend !== 0 ? arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend) : null,
|
|
2259
|
+
tangentStart: getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0),
|
|
2260
|
+
tangentEnd: getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1)
|
|
2261
|
+
};
|
|
2262
|
+
cache2.set(arrow, geometry);
|
|
2263
|
+
return geometry;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2118
2266
|
// src/elements/arrow-binding.ts
|
|
2119
2267
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
2120
2268
|
function isBindable(element) {
|
|
@@ -2235,98 +2383,6 @@ function unbindArrow(arrow, store) {
|
|
|
2235
2383
|
return updates;
|
|
2236
2384
|
}
|
|
2237
2385
|
|
|
2238
|
-
// src/elements/stroke-smoothing.ts
|
|
2239
|
-
var MIN_PRESSURE_SCALE = 0.2;
|
|
2240
|
-
function pressureToWidth(pressure, baseWidth) {
|
|
2241
|
-
return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
|
|
2242
|
-
}
|
|
2243
|
-
function simplifyPoints(points, tolerance) {
|
|
2244
|
-
if (points.length <= 2) return points.slice();
|
|
2245
|
-
return rdp(points, 0, points.length - 1, tolerance);
|
|
2246
|
-
}
|
|
2247
|
-
function rdp(points, start, end, tolerance) {
|
|
2248
|
-
const first = points[start];
|
|
2249
|
-
const last = points[end];
|
|
2250
|
-
if (!first || !last) return [];
|
|
2251
|
-
if (end - start <= 1) return [first, last];
|
|
2252
|
-
let maxDist = 0;
|
|
2253
|
-
let maxIndex = start;
|
|
2254
|
-
for (let i = start + 1; i < end; i++) {
|
|
2255
|
-
const pt = points[i];
|
|
2256
|
-
if (!pt) continue;
|
|
2257
|
-
const dist = perpendicularDistance(pt, first, last);
|
|
2258
|
-
if (dist > maxDist) {
|
|
2259
|
-
maxDist = dist;
|
|
2260
|
-
maxIndex = i;
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
if (maxDist <= tolerance) return [first, last];
|
|
2264
|
-
const left = rdp(points, start, maxIndex, tolerance);
|
|
2265
|
-
const right = rdp(points, maxIndex, end, tolerance);
|
|
2266
|
-
return left.concat(right.slice(1));
|
|
2267
|
-
}
|
|
2268
|
-
function perpendicularDistance(pt, lineStart, lineEnd) {
|
|
2269
|
-
const dx = lineEnd.x - lineStart.x;
|
|
2270
|
-
const dy = lineEnd.y - lineStart.y;
|
|
2271
|
-
const lenSq = dx * dx + dy * dy;
|
|
2272
|
-
if (lenSq === 0) {
|
|
2273
|
-
const ex = pt.x - lineStart.x;
|
|
2274
|
-
const ey = pt.y - lineStart.y;
|
|
2275
|
-
return Math.sqrt(ex * ex + ey * ey);
|
|
2276
|
-
}
|
|
2277
|
-
const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
|
|
2278
|
-
return num / Math.sqrt(lenSq);
|
|
2279
|
-
}
|
|
2280
|
-
function smoothToSegments(points) {
|
|
2281
|
-
if (points.length < 2) return [];
|
|
2282
|
-
if (points.length === 2) {
|
|
2283
|
-
const p0 = points[0];
|
|
2284
|
-
const p1 = points[1];
|
|
2285
|
-
if (!p0 || !p1) return [];
|
|
2286
|
-
const mx = (p0.x + p1.x) / 2;
|
|
2287
|
-
const my = (p0.y + p1.y) / 2;
|
|
2288
|
-
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
2289
|
-
}
|
|
2290
|
-
const segments = [];
|
|
2291
|
-
const n = points.length;
|
|
2292
|
-
for (let i = 0; i < n - 1; i++) {
|
|
2293
|
-
const p0 = points[Math.max(0, i - 1)];
|
|
2294
|
-
const p1 = points[i];
|
|
2295
|
-
const p2 = points[i + 1];
|
|
2296
|
-
const p3 = points[Math.min(n - 1, i + 2)];
|
|
2297
|
-
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
2298
|
-
const cp1 = {
|
|
2299
|
-
x: p1.x + (p2.x - p0.x) / 6,
|
|
2300
|
-
y: p1.y + (p2.y - p0.y) / 6
|
|
2301
|
-
};
|
|
2302
|
-
const cp2 = {
|
|
2303
|
-
x: p2.x - (p3.x - p1.x) / 6,
|
|
2304
|
-
y: p2.y - (p3.y - p1.y) / 6
|
|
2305
|
-
};
|
|
2306
|
-
segments.push({ start: p1, cp1, cp2, end: p2 });
|
|
2307
|
-
}
|
|
2308
|
-
return segments;
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
// src/elements/stroke-cache.ts
|
|
2312
|
-
var cache = /* @__PURE__ */ new WeakMap();
|
|
2313
|
-
function computeStrokeSegments(stroke) {
|
|
2314
|
-
const segments = smoothToSegments(stroke.points);
|
|
2315
|
-
const widths = [];
|
|
2316
|
-
for (const seg of segments) {
|
|
2317
|
-
const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
|
|
2318
|
-
widths.push(w);
|
|
2319
|
-
}
|
|
2320
|
-
const data = { segments, widths };
|
|
2321
|
-
cache.set(stroke, data);
|
|
2322
|
-
return data;
|
|
2323
|
-
}
|
|
2324
|
-
function getStrokeRenderData(stroke) {
|
|
2325
|
-
const cached = cache.get(stroke);
|
|
2326
|
-
if (cached) return cached;
|
|
2327
|
-
return computeStrokeSegments(stroke);
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
2386
|
// src/elements/grid-renderer.ts
|
|
2331
2387
|
function getSquareGridLines(bounds, cellSize) {
|
|
2332
2388
|
if (cellSize <= 0) return { verticals: [], horizontals: [] };
|
|
@@ -2772,21 +2828,29 @@ var ElementRenderer = class {
|
|
|
2772
2828
|
ctx.lineCap = "round";
|
|
2773
2829
|
ctx.lineJoin = "round";
|
|
2774
2830
|
ctx.globalAlpha = stroke.opacity;
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2831
|
+
const data = getStrokeRenderData(stroke);
|
|
2832
|
+
if (data.buckets) {
|
|
2833
|
+
for (const bucket of data.buckets) {
|
|
2834
|
+
ctx.lineWidth = bucket.width;
|
|
2835
|
+
ctx.stroke(bucket.path);
|
|
2836
|
+
}
|
|
2837
|
+
} else {
|
|
2838
|
+
for (let i = 0; i < data.segments.length; i++) {
|
|
2839
|
+
const seg = data.segments[i];
|
|
2840
|
+
const w = data.widths[i];
|
|
2841
|
+
if (!seg || w === void 0) continue;
|
|
2842
|
+
ctx.lineWidth = w;
|
|
2843
|
+
ctx.beginPath();
|
|
2844
|
+
ctx.moveTo(seg.start.x, seg.start.y);
|
|
2845
|
+
ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
|
|
2846
|
+
ctx.stroke();
|
|
2847
|
+
}
|
|
2785
2848
|
}
|
|
2786
2849
|
ctx.restore();
|
|
2787
2850
|
}
|
|
2788
2851
|
renderArrow(ctx, arrow) {
|
|
2789
|
-
const
|
|
2852
|
+
const geometry = getArrowRenderGeometry(arrow);
|
|
2853
|
+
const { visualFrom, visualTo } = this.getVisualEndpoints(arrow, geometry);
|
|
2790
2854
|
ctx.save();
|
|
2791
2855
|
ctx.strokeStyle = arrow.color;
|
|
2792
2856
|
ctx.lineWidth = arrow.width;
|
|
@@ -2797,17 +2861,18 @@ var ElementRenderer = class {
|
|
|
2797
2861
|
ctx.beginPath();
|
|
2798
2862
|
ctx.moveTo(visualFrom.x, visualFrom.y);
|
|
2799
2863
|
if (arrow.bend !== 0) {
|
|
2800
|
-
const cp =
|
|
2801
|
-
|
|
2864
|
+
const cp = geometry.controlPoint;
|
|
2865
|
+
if (cp) {
|
|
2866
|
+
ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
|
|
2867
|
+
}
|
|
2802
2868
|
} else {
|
|
2803
2869
|
ctx.lineTo(visualTo.x, visualTo.y);
|
|
2804
2870
|
}
|
|
2805
2871
|
ctx.stroke();
|
|
2806
|
-
this.renderArrowhead(ctx, arrow, visualTo);
|
|
2872
|
+
this.renderArrowhead(ctx, arrow, visualTo, geometry.tangentEnd);
|
|
2807
2873
|
ctx.restore();
|
|
2808
2874
|
}
|
|
2809
|
-
renderArrowhead(ctx, arrow, tip) {
|
|
2810
|
-
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
2875
|
+
renderArrowhead(ctx, arrow, tip, angle) {
|
|
2811
2876
|
ctx.beginPath();
|
|
2812
2877
|
ctx.moveTo(tip.x, tip.y);
|
|
2813
2878
|
ctx.lineTo(
|
|
@@ -2822,7 +2887,7 @@ var ElementRenderer = class {
|
|
|
2822
2887
|
ctx.fillStyle = arrow.color;
|
|
2823
2888
|
ctx.fill();
|
|
2824
2889
|
}
|
|
2825
|
-
getVisualEndpoints(arrow) {
|
|
2890
|
+
getVisualEndpoints(arrow, geometry) {
|
|
2826
2891
|
let visualFrom = arrow.from;
|
|
2827
2892
|
let visualTo = arrow.to;
|
|
2828
2893
|
if (!this.store) return { visualFrom, visualTo };
|
|
@@ -2831,7 +2896,7 @@ var ElementRenderer = class {
|
|
|
2831
2896
|
if (el) {
|
|
2832
2897
|
const bounds = getElementBounds(el);
|
|
2833
2898
|
if (bounds) {
|
|
2834
|
-
const tangentAngle =
|
|
2899
|
+
const tangentAngle = geometry.tangentStart;
|
|
2835
2900
|
const rayTarget = {
|
|
2836
2901
|
x: arrow.from.x + Math.cos(tangentAngle) * 1e3,
|
|
2837
2902
|
y: arrow.from.y + Math.sin(tangentAngle) * 1e3
|
|
@@ -2845,7 +2910,7 @@ var ElementRenderer = class {
|
|
|
2845
2910
|
if (el) {
|
|
2846
2911
|
const bounds = getElementBounds(el);
|
|
2847
2912
|
if (bounds) {
|
|
2848
|
-
const tangentAngle =
|
|
2913
|
+
const tangentAngle = geometry.tangentEnd;
|
|
2849
2914
|
const rayTarget = {
|
|
2850
2915
|
x: arrow.to.x - Math.cos(tangentAngle) * 1e3,
|
|
2851
2916
|
y: arrow.to.y - Math.sin(tangentAngle) * 1e3
|
|
@@ -4209,19 +4274,19 @@ function loadImages(elements) {
|
|
|
4209
4274
|
const imageElements = elements.filter(
|
|
4210
4275
|
(el) => el.type === "image" && "src" in el
|
|
4211
4276
|
);
|
|
4212
|
-
const
|
|
4213
|
-
if (imageElements.length === 0) return Promise.resolve(
|
|
4277
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
4278
|
+
if (imageElements.length === 0) return Promise.resolve(cache3);
|
|
4214
4279
|
return new Promise((resolve) => {
|
|
4215
4280
|
let remaining = imageElements.length;
|
|
4216
4281
|
const done = () => {
|
|
4217
4282
|
remaining--;
|
|
4218
|
-
if (remaining <= 0) resolve(
|
|
4283
|
+
if (remaining <= 0) resolve(cache3);
|
|
4219
4284
|
};
|
|
4220
4285
|
for (const el of imageElements) {
|
|
4221
4286
|
const img = new Image();
|
|
4222
4287
|
img.crossOrigin = "anonymous";
|
|
4223
4288
|
img.onload = () => {
|
|
4224
|
-
|
|
4289
|
+
cache3.set(el.id, img);
|
|
4225
4290
|
done();
|
|
4226
4291
|
};
|
|
4227
4292
|
img.onerror = done;
|
|
@@ -4702,18 +4767,39 @@ var RenderStats = class {
|
|
|
4702
4767
|
frameTimes = [];
|
|
4703
4768
|
frameCount = 0;
|
|
4704
4769
|
_lastGridMs = 0;
|
|
4705
|
-
|
|
4770
|
+
_lastLayersMs = 0;
|
|
4771
|
+
_lastBackgroundMs = 0;
|
|
4772
|
+
_lastCompositeMs = 0;
|
|
4773
|
+
_lastOverlayMs = 0;
|
|
4774
|
+
recordFrame(durationMs, breakdown) {
|
|
4706
4775
|
this.frameCount++;
|
|
4707
4776
|
this.frameTimes.push(durationMs);
|
|
4708
4777
|
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
4709
4778
|
this.frameTimes.shift();
|
|
4710
4779
|
}
|
|
4711
|
-
if (
|
|
4780
|
+
if (breakdown !== void 0) {
|
|
4781
|
+
if (breakdown.gridMs !== void 0) this._lastGridMs = breakdown.gridMs;
|
|
4782
|
+
if (breakdown.layersMs !== void 0) this._lastLayersMs = breakdown.layersMs;
|
|
4783
|
+
if (breakdown.backgroundMs !== void 0) this._lastBackgroundMs = breakdown.backgroundMs;
|
|
4784
|
+
if (breakdown.compositeMs !== void 0) this._lastCompositeMs = breakdown.compositeMs;
|
|
4785
|
+
if (breakdown.overlayMs !== void 0) this._lastOverlayMs = breakdown.overlayMs;
|
|
4786
|
+
}
|
|
4712
4787
|
}
|
|
4713
4788
|
getSnapshot() {
|
|
4714
4789
|
const times = this.frameTimes;
|
|
4715
4790
|
if (times.length === 0) {
|
|
4716
|
-
return {
|
|
4791
|
+
return {
|
|
4792
|
+
fps: 0,
|
|
4793
|
+
avgFrameMs: 0,
|
|
4794
|
+
p95FrameMs: 0,
|
|
4795
|
+
lastFrameMs: 0,
|
|
4796
|
+
lastGridMs: 0,
|
|
4797
|
+
layersMs: 0,
|
|
4798
|
+
backgroundMs: 0,
|
|
4799
|
+
compositeMs: 0,
|
|
4800
|
+
overlayMs: 0,
|
|
4801
|
+
frameCount: 0
|
|
4802
|
+
};
|
|
4717
4803
|
}
|
|
4718
4804
|
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
4719
4805
|
const sorted = [...times].sort((a, b) => a - b);
|
|
@@ -4725,6 +4811,10 @@ var RenderStats = class {
|
|
|
4725
4811
|
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
4726
4812
|
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
4727
4813
|
lastGridMs: Math.round(this._lastGridMs * 100) / 100,
|
|
4814
|
+
layersMs: Math.round(this._lastLayersMs * 100) / 100,
|
|
4815
|
+
backgroundMs: Math.round(this._lastBackgroundMs * 100) / 100,
|
|
4816
|
+
compositeMs: Math.round(this._lastCompositeMs * 100) / 100,
|
|
4817
|
+
overlayMs: Math.round(this._lastOverlayMs * 100) / 100,
|
|
4728
4818
|
frameCount: this.frameCount
|
|
4729
4819
|
};
|
|
4730
4820
|
}
|
|
@@ -4752,7 +4842,7 @@ var RenderLoop = class {
|
|
|
4752
4842
|
lastCamX;
|
|
4753
4843
|
lastCamY;
|
|
4754
4844
|
stats = new RenderStats();
|
|
4755
|
-
|
|
4845
|
+
layerGroups = /* @__PURE__ */ new Map();
|
|
4756
4846
|
gridCacheCanvas = null;
|
|
4757
4847
|
gridCacheCtx = null;
|
|
4758
4848
|
gridCacheZoom = -1;
|
|
@@ -4850,6 +4940,9 @@ var RenderLoop = class {
|
|
|
4850
4940
|
const t0 = performance.now();
|
|
4851
4941
|
const ctx = this.canvasEl.getContext("2d");
|
|
4852
4942
|
if (!ctx) return;
|
|
4943
|
+
let layersMs = 0;
|
|
4944
|
+
let compositeMs = 0;
|
|
4945
|
+
let gridMs = 0;
|
|
4853
4946
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4854
4947
|
const cssWidth = this.canvasEl.clientWidth;
|
|
4855
4948
|
const cssHeight = this.canvasEl.clientHeight;
|
|
@@ -4866,6 +4959,7 @@ var RenderLoop = class {
|
|
|
4866
4959
|
ctx.scale(dpr, dpr);
|
|
4867
4960
|
this.renderer.setCanvasSize(cssWidth, cssHeight);
|
|
4868
4961
|
const hasGridElement = this.store.getElementsByType("grid").length > 0;
|
|
4962
|
+
const bgT0 = performance.now();
|
|
4869
4963
|
if (hasGridElement) {
|
|
4870
4964
|
ctx.save();
|
|
4871
4965
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
@@ -4874,6 +4968,7 @@ var RenderLoop = class {
|
|
|
4874
4968
|
} else {
|
|
4875
4969
|
this.background.render(ctx, this.camera);
|
|
4876
4970
|
}
|
|
4971
|
+
const backgroundMs = performance.now() - bgT0;
|
|
4877
4972
|
ctx.save();
|
|
4878
4973
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
4879
4974
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
@@ -4886,7 +4981,7 @@ var RenderLoop = class {
|
|
|
4886
4981
|
h: visibleRect.h + margin * 2
|
|
4887
4982
|
};
|
|
4888
4983
|
const allElements = this.store.getAll();
|
|
4889
|
-
|
|
4984
|
+
this.layerGroups.clear();
|
|
4890
4985
|
const gridElements = [];
|
|
4891
4986
|
let domZIndex = 0;
|
|
4892
4987
|
for (const element of allElements) {
|
|
@@ -4909,25 +5004,30 @@ var RenderLoop = class {
|
|
|
4909
5004
|
gridElements.push(element);
|
|
4910
5005
|
continue;
|
|
4911
5006
|
}
|
|
4912
|
-
let group =
|
|
5007
|
+
let group = this.layerGroups.get(element.layerId);
|
|
4913
5008
|
if (!group) {
|
|
4914
5009
|
group = [];
|
|
4915
|
-
|
|
5010
|
+
this.layerGroups.set(element.layerId, group);
|
|
4916
5011
|
}
|
|
4917
5012
|
group.push(element);
|
|
4918
5013
|
}
|
|
4919
|
-
for (const [layerId, elements] of
|
|
5014
|
+
for (const [layerId, elements] of this.layerGroups) {
|
|
4920
5015
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
4921
5016
|
if (!this.layerCache.isDirty(layerId)) {
|
|
5017
|
+
const compT0 = performance.now();
|
|
4922
5018
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
5019
|
+
compositeMs += performance.now() - compT0;
|
|
4923
5020
|
continue;
|
|
4924
5021
|
}
|
|
4925
5022
|
if (isActiveDrawingLayer) {
|
|
5023
|
+
const compT0 = performance.now();
|
|
4926
5024
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
5025
|
+
compositeMs += performance.now() - compT0;
|
|
4927
5026
|
continue;
|
|
4928
5027
|
}
|
|
4929
5028
|
const offCtx = this.layerCache.getContext(layerId);
|
|
4930
5029
|
if (offCtx) {
|
|
5030
|
+
const layerT0 = performance.now();
|
|
4931
5031
|
const offCanvas = this.layerCache.getCanvas(layerId);
|
|
4932
5032
|
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
|
|
4933
5033
|
offCtx.save();
|
|
@@ -4941,7 +5041,10 @@ var RenderLoop = class {
|
|
|
4941
5041
|
}
|
|
4942
5042
|
offCtx.restore();
|
|
4943
5043
|
this.layerCache.markClean(layerId);
|
|
5044
|
+
layersMs += performance.now() - layerT0;
|
|
5045
|
+
const compT0 = performance.now();
|
|
4944
5046
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
5047
|
+
compositeMs += performance.now() - compT0;
|
|
4945
5048
|
}
|
|
4946
5049
|
}
|
|
4947
5050
|
if (gridElements.length > 0) {
|
|
@@ -4982,15 +5085,23 @@ var RenderLoop = class {
|
|
|
4982
5085
|
this.gridCacheHeight = cssHeight;
|
|
4983
5086
|
this.lastGridRef = gridRef;
|
|
4984
5087
|
}
|
|
4985
|
-
|
|
5088
|
+
gridMs = performance.now() - gridT0;
|
|
4986
5089
|
}
|
|
5090
|
+
const overlayT0 = performance.now();
|
|
4987
5091
|
const activeTool = this.toolManager.activeTool;
|
|
4988
5092
|
if (activeTool?.renderOverlay) {
|
|
4989
5093
|
activeTool.renderOverlay(ctx);
|
|
4990
5094
|
}
|
|
5095
|
+
const overlayMs = performance.now() - overlayT0;
|
|
4991
5096
|
ctx.restore();
|
|
4992
5097
|
ctx.restore();
|
|
4993
|
-
this.stats.recordFrame(performance.now() - t0,
|
|
5098
|
+
this.stats.recordFrame(performance.now() - t0, {
|
|
5099
|
+
gridMs,
|
|
5100
|
+
layersMs,
|
|
5101
|
+
backgroundMs,
|
|
5102
|
+
compositeMs,
|
|
5103
|
+
overlayMs
|
|
5104
|
+
});
|
|
4994
5105
|
}
|
|
4995
5106
|
};
|
|
4996
5107
|
|
|
@@ -5402,7 +5513,7 @@ var Viewport = class {
|
|
|
5402
5513
|
const id = setInterval(() => {
|
|
5403
5514
|
const s = this.getRenderStats();
|
|
5404
5515
|
console.log(
|
|
5405
|
-
`[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms`
|
|
5516
|
+
`[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms layers=${s.layersMs}ms comp=${s.compositeMs}ms bg=${s.backgroundMs}ms overlay=${s.overlayMs}ms`
|
|
5406
5517
|
);
|
|
5407
5518
|
}, intervalMs);
|
|
5408
5519
|
return () => clearInterval(id);
|
|
@@ -7400,7 +7511,7 @@ var TemplateTool = class {
|
|
|
7400
7511
|
};
|
|
7401
7512
|
|
|
7402
7513
|
// src/index.ts
|
|
7403
|
-
var VERSION = "0.
|
|
7514
|
+
var VERSION = "0.22.0";
|
|
7404
7515
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7405
7516
|
0 && (module.exports = {
|
|
7406
7517
|
AddElementCommand,
|