@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.d.cts
CHANGED
|
@@ -574,6 +574,10 @@ interface RenderStatsSnapshot {
|
|
|
574
574
|
p95FrameMs: number;
|
|
575
575
|
lastFrameMs: number;
|
|
576
576
|
lastGridMs: number;
|
|
577
|
+
layersMs: number;
|
|
578
|
+
backgroundMs: number;
|
|
579
|
+
compositeMs: number;
|
|
580
|
+
overlayMs: number;
|
|
577
581
|
frameCount: number;
|
|
578
582
|
}
|
|
579
583
|
|
|
@@ -1247,6 +1251,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
1247
1251
|
undo(_store: ElementStore): void;
|
|
1248
1252
|
}
|
|
1249
1253
|
|
|
1250
|
-
declare const VERSION = "0.
|
|
1254
|
+
declare const VERSION = "0.22.0";
|
|
1251
1255
|
|
|
1252
1256
|
export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, isNoteContentEmpty, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
|
package/dist/index.d.ts
CHANGED
|
@@ -574,6 +574,10 @@ interface RenderStatsSnapshot {
|
|
|
574
574
|
p95FrameMs: number;
|
|
575
575
|
lastFrameMs: number;
|
|
576
576
|
lastGridMs: number;
|
|
577
|
+
layersMs: number;
|
|
578
|
+
backgroundMs: number;
|
|
579
|
+
compositeMs: number;
|
|
580
|
+
overlayMs: number;
|
|
577
581
|
frameCount: number;
|
|
578
582
|
}
|
|
579
583
|
|
|
@@ -1247,6 +1251,6 @@ declare class UpdateLayerCommand implements Command {
|
|
|
1247
1251
|
undo(_store: ElementStore): void;
|
|
1248
1252
|
}
|
|
1249
1253
|
|
|
1250
|
-
declare const VERSION = "0.
|
|
1254
|
+
declare const VERSION = "0.22.0";
|
|
1251
1255
|
|
|
1252
1256
|
export { type ActiveFormats, AddElementCommand, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, Background, type BackgroundOptions, type BackgroundPattern, BatchCommand, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, CreateLayerCommand, DEFAULT_FONT_SIZE_PRESETS, DEFAULT_NOTE_FONT_SIZE, DoubleTapDetector, type DoubleTapDetectorOptions, ElementRenderer, ElementStore, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, EventBus, type ExportImageOptions, type FilterAction, type FilteredEvent, type FilteredUpEvent, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryRecorder, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, InputFilter, InputHandler, type InputHandlerOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, NoteEditor, type NoteEditorOptions, type NoteElement, NoteTool, type NoteToolOptions, NoteToolbar, PencilTool, type PencilToolOptions, type Point, type PointerState, Quadtree, RemoveElementCommand, RemoveLayerCommand, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type StyledRun, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, UpdateElementCommand, UpdateLayerCommand, VERSION, Viewport, type ViewportOptions, boundsIntersect, clearStaleBindings, createArrow, createGrid, createHtmlElement, createId, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, exportState, findBindTarget, findBoundArrows, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getEdgeIntersection, getElementBounds, getElementCenter, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isBindable, isNearBezier, isNoteContentEmpty, parseState, sanitizeNoteHtml, setFontSize, smartSnap, snapPoint, snapToHexCenter, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, unbindArrow, updateBoundArrow };
|
package/dist/index.js
CHANGED
|
@@ -1823,10 +1823,134 @@ function getTemplateBounds(el) {
|
|
|
1823
1823
|
}
|
|
1824
1824
|
}
|
|
1825
1825
|
}
|
|
1826
|
+
function transferStrokeBounds(prev, next) {
|
|
1827
|
+
if (prev.type !== "stroke" || next.type !== "stroke") return;
|
|
1828
|
+
if (prev.points !== next.points) return;
|
|
1829
|
+
if (prev.position.x !== next.position.x || prev.position.y !== next.position.y) return;
|
|
1830
|
+
const bounds = strokeBoundsCache.get(prev);
|
|
1831
|
+
if (bounds) strokeBoundsCache.set(next, bounds);
|
|
1832
|
+
}
|
|
1826
1833
|
function boundsIntersect(a, b) {
|
|
1827
1834
|
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;
|
|
1828
1835
|
}
|
|
1829
1836
|
|
|
1837
|
+
// src/elements/stroke-smoothing.ts
|
|
1838
|
+
var MIN_PRESSURE_SCALE = 0.2;
|
|
1839
|
+
function pressureToWidth(pressure, baseWidth) {
|
|
1840
|
+
return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
|
|
1841
|
+
}
|
|
1842
|
+
function simplifyPoints(points, tolerance) {
|
|
1843
|
+
if (points.length <= 2) return points.slice();
|
|
1844
|
+
return rdp(points, 0, points.length - 1, tolerance);
|
|
1845
|
+
}
|
|
1846
|
+
function rdp(points, start, end, tolerance) {
|
|
1847
|
+
const first = points[start];
|
|
1848
|
+
const last = points[end];
|
|
1849
|
+
if (!first || !last) return [];
|
|
1850
|
+
if (end - start <= 1) return [first, last];
|
|
1851
|
+
let maxDist = 0;
|
|
1852
|
+
let maxIndex = start;
|
|
1853
|
+
for (let i = start + 1; i < end; i++) {
|
|
1854
|
+
const pt = points[i];
|
|
1855
|
+
if (!pt) continue;
|
|
1856
|
+
const dist = perpendicularDistance(pt, first, last);
|
|
1857
|
+
if (dist > maxDist) {
|
|
1858
|
+
maxDist = dist;
|
|
1859
|
+
maxIndex = i;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
if (maxDist <= tolerance) return [first, last];
|
|
1863
|
+
const left = rdp(points, start, maxIndex, tolerance);
|
|
1864
|
+
const right = rdp(points, maxIndex, end, tolerance);
|
|
1865
|
+
return left.concat(right.slice(1));
|
|
1866
|
+
}
|
|
1867
|
+
function perpendicularDistance(pt, lineStart, lineEnd) {
|
|
1868
|
+
const dx = lineEnd.x - lineStart.x;
|
|
1869
|
+
const dy = lineEnd.y - lineStart.y;
|
|
1870
|
+
const lenSq = dx * dx + dy * dy;
|
|
1871
|
+
if (lenSq === 0) {
|
|
1872
|
+
const ex = pt.x - lineStart.x;
|
|
1873
|
+
const ey = pt.y - lineStart.y;
|
|
1874
|
+
return Math.sqrt(ex * ex + ey * ey);
|
|
1875
|
+
}
|
|
1876
|
+
const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
|
|
1877
|
+
return num / Math.sqrt(lenSq);
|
|
1878
|
+
}
|
|
1879
|
+
function smoothToSegments(points) {
|
|
1880
|
+
if (points.length < 2) return [];
|
|
1881
|
+
if (points.length === 2) {
|
|
1882
|
+
const p0 = points[0];
|
|
1883
|
+
const p1 = points[1];
|
|
1884
|
+
if (!p0 || !p1) return [];
|
|
1885
|
+
const mx = (p0.x + p1.x) / 2;
|
|
1886
|
+
const my = (p0.y + p1.y) / 2;
|
|
1887
|
+
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
1888
|
+
}
|
|
1889
|
+
const segments = [];
|
|
1890
|
+
const n = points.length;
|
|
1891
|
+
for (let i = 0; i < n - 1; i++) {
|
|
1892
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
1893
|
+
const p1 = points[i];
|
|
1894
|
+
const p2 = points[i + 1];
|
|
1895
|
+
const p3 = points[Math.min(n - 1, i + 2)];
|
|
1896
|
+
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
1897
|
+
const cp1 = {
|
|
1898
|
+
x: p1.x + (p2.x - p0.x) / 6,
|
|
1899
|
+
y: p1.y + (p2.y - p0.y) / 6
|
|
1900
|
+
};
|
|
1901
|
+
const cp2 = {
|
|
1902
|
+
x: p2.x - (p3.x - p1.x) / 6,
|
|
1903
|
+
y: p2.y - (p3.y - p1.y) / 6
|
|
1904
|
+
};
|
|
1905
|
+
segments.push({ start: p1, cp1, cp2, end: p2 });
|
|
1906
|
+
}
|
|
1907
|
+
return segments;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/elements/stroke-cache.ts
|
|
1911
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
1912
|
+
var WIDTH_QUANTUM = 0.25;
|
|
1913
|
+
function buildWidthBuckets(segments, widths) {
|
|
1914
|
+
if (typeof Path2D === "undefined") return null;
|
|
1915
|
+
const byWidth = /* @__PURE__ */ new Map();
|
|
1916
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1917
|
+
const seg = segments[i];
|
|
1918
|
+
const w = widths[i];
|
|
1919
|
+
if (!seg || w === void 0) continue;
|
|
1920
|
+
const q = Math.max(WIDTH_QUANTUM, Math.round(w / WIDTH_QUANTUM) * WIDTH_QUANTUM);
|
|
1921
|
+
let path = byWidth.get(q);
|
|
1922
|
+
if (!path) {
|
|
1923
|
+
path = new Path2D();
|
|
1924
|
+
byWidth.set(q, path);
|
|
1925
|
+
}
|
|
1926
|
+
path.moveTo(seg.start.x, seg.start.y);
|
|
1927
|
+
path.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
|
|
1928
|
+
}
|
|
1929
|
+
return [...byWidth.entries()].map(([width, path]) => ({ width, path }));
|
|
1930
|
+
}
|
|
1931
|
+
function computeStrokeSegments(stroke) {
|
|
1932
|
+
const segments = smoothToSegments(stroke.points);
|
|
1933
|
+
const widths = [];
|
|
1934
|
+
for (const seg of segments) {
|
|
1935
|
+
const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
|
|
1936
|
+
widths.push(w);
|
|
1937
|
+
}
|
|
1938
|
+
const data = { segments, widths, buckets: buildWidthBuckets(segments, widths) };
|
|
1939
|
+
cache.set(stroke, data);
|
|
1940
|
+
return data;
|
|
1941
|
+
}
|
|
1942
|
+
function getStrokeRenderData(stroke) {
|
|
1943
|
+
const cached = cache.get(stroke);
|
|
1944
|
+
if (cached) return cached;
|
|
1945
|
+
return computeStrokeSegments(stroke);
|
|
1946
|
+
}
|
|
1947
|
+
function transferStrokeRenderData(prev, next) {
|
|
1948
|
+
if (prev.type !== "stroke" || next.type !== "stroke") return;
|
|
1949
|
+
if (prev.points !== next.points) return;
|
|
1950
|
+
const data = cache.get(prev);
|
|
1951
|
+
if (data) cache.set(next, data);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1830
1954
|
// src/elements/element-store.ts
|
|
1831
1955
|
var ElementStore = class {
|
|
1832
1956
|
elements = /* @__PURE__ */ new Map();
|
|
@@ -1877,6 +2001,10 @@ var ElementStore = class {
|
|
|
1877
2001
|
this.sortedCache = null;
|
|
1878
2002
|
this._versions.set(id, (this._versions.get(id) ?? 0) + 1);
|
|
1879
2003
|
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
2004
|
+
if (updated.type === "stroke" && existing.type === "stroke") {
|
|
2005
|
+
transferStrokeRenderData(existing, updated);
|
|
2006
|
+
transferStrokeBounds(existing, updated);
|
|
2007
|
+
}
|
|
1880
2008
|
if (updated.type === "arrow") {
|
|
1881
2009
|
const arrow = updated;
|
|
1882
2010
|
arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
@@ -1920,6 +2048,12 @@ var ElementStore = class {
|
|
|
1920
2048
|
this._versions.set(el.id, 0);
|
|
1921
2049
|
const bounds = getElementBounds(el);
|
|
1922
2050
|
if (bounds) this.spatialIndex.insert(el.id, bounds);
|
|
2051
|
+
if (el.type === "stroke") {
|
|
2052
|
+
computeStrokeSegments(el);
|
|
2053
|
+
}
|
|
2054
|
+
if (el.type === "arrow" && el.bend !== 0 && !el.cachedControlPoint) {
|
|
2055
|
+
el.cachedControlPoint = getArrowControlPoint(el.from, el.to, el.bend);
|
|
2056
|
+
}
|
|
1923
2057
|
}
|
|
1924
2058
|
this.bus.emit("clear", null);
|
|
1925
2059
|
for (const el of elements) {
|
|
@@ -2005,6 +2139,20 @@ var ElementStore = class {
|
|
|
2005
2139
|
}
|
|
2006
2140
|
};
|
|
2007
2141
|
|
|
2142
|
+
// src/elements/arrow-render-cache.ts
|
|
2143
|
+
var cache2 = /* @__PURE__ */ new WeakMap();
|
|
2144
|
+
function getArrowRenderGeometry(arrow) {
|
|
2145
|
+
const hit = cache2.get(arrow);
|
|
2146
|
+
if (hit) return hit;
|
|
2147
|
+
const geometry = {
|
|
2148
|
+
controlPoint: arrow.bend !== 0 ? arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend) : null,
|
|
2149
|
+
tangentStart: getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 0),
|
|
2150
|
+
tangentEnd: getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1)
|
|
2151
|
+
};
|
|
2152
|
+
cache2.set(arrow, geometry);
|
|
2153
|
+
return geometry;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2008
2156
|
// src/elements/arrow-binding.ts
|
|
2009
2157
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
2010
2158
|
function isBindable(element) {
|
|
@@ -2125,98 +2273,6 @@ function unbindArrow(arrow, store) {
|
|
|
2125
2273
|
return updates;
|
|
2126
2274
|
}
|
|
2127
2275
|
|
|
2128
|
-
// src/elements/stroke-smoothing.ts
|
|
2129
|
-
var MIN_PRESSURE_SCALE = 0.2;
|
|
2130
|
-
function pressureToWidth(pressure, baseWidth) {
|
|
2131
|
-
return baseWidth * (MIN_PRESSURE_SCALE + (1 - MIN_PRESSURE_SCALE) * pressure);
|
|
2132
|
-
}
|
|
2133
|
-
function simplifyPoints(points, tolerance) {
|
|
2134
|
-
if (points.length <= 2) return points.slice();
|
|
2135
|
-
return rdp(points, 0, points.length - 1, tolerance);
|
|
2136
|
-
}
|
|
2137
|
-
function rdp(points, start, end, tolerance) {
|
|
2138
|
-
const first = points[start];
|
|
2139
|
-
const last = points[end];
|
|
2140
|
-
if (!first || !last) return [];
|
|
2141
|
-
if (end - start <= 1) return [first, last];
|
|
2142
|
-
let maxDist = 0;
|
|
2143
|
-
let maxIndex = start;
|
|
2144
|
-
for (let i = start + 1; i < end; i++) {
|
|
2145
|
-
const pt = points[i];
|
|
2146
|
-
if (!pt) continue;
|
|
2147
|
-
const dist = perpendicularDistance(pt, first, last);
|
|
2148
|
-
if (dist > maxDist) {
|
|
2149
|
-
maxDist = dist;
|
|
2150
|
-
maxIndex = i;
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
if (maxDist <= tolerance) return [first, last];
|
|
2154
|
-
const left = rdp(points, start, maxIndex, tolerance);
|
|
2155
|
-
const right = rdp(points, maxIndex, end, tolerance);
|
|
2156
|
-
return left.concat(right.slice(1));
|
|
2157
|
-
}
|
|
2158
|
-
function perpendicularDistance(pt, lineStart, lineEnd) {
|
|
2159
|
-
const dx = lineEnd.x - lineStart.x;
|
|
2160
|
-
const dy = lineEnd.y - lineStart.y;
|
|
2161
|
-
const lenSq = dx * dx + dy * dy;
|
|
2162
|
-
if (lenSq === 0) {
|
|
2163
|
-
const ex = pt.x - lineStart.x;
|
|
2164
|
-
const ey = pt.y - lineStart.y;
|
|
2165
|
-
return Math.sqrt(ex * ex + ey * ey);
|
|
2166
|
-
}
|
|
2167
|
-
const num = Math.abs(dy * pt.x - dx * pt.y + lineEnd.x * lineStart.y - lineEnd.y * lineStart.x);
|
|
2168
|
-
return num / Math.sqrt(lenSq);
|
|
2169
|
-
}
|
|
2170
|
-
function smoothToSegments(points) {
|
|
2171
|
-
if (points.length < 2) return [];
|
|
2172
|
-
if (points.length === 2) {
|
|
2173
|
-
const p0 = points[0];
|
|
2174
|
-
const p1 = points[1];
|
|
2175
|
-
if (!p0 || !p1) return [];
|
|
2176
|
-
const mx = (p0.x + p1.x) / 2;
|
|
2177
|
-
const my = (p0.y + p1.y) / 2;
|
|
2178
|
-
return [{ start: p0, cp1: { x: mx, y: my }, cp2: { x: mx, y: my }, end: p1 }];
|
|
2179
|
-
}
|
|
2180
|
-
const segments = [];
|
|
2181
|
-
const n = points.length;
|
|
2182
|
-
for (let i = 0; i < n - 1; i++) {
|
|
2183
|
-
const p0 = points[Math.max(0, i - 1)];
|
|
2184
|
-
const p1 = points[i];
|
|
2185
|
-
const p2 = points[i + 1];
|
|
2186
|
-
const p3 = points[Math.min(n - 1, i + 2)];
|
|
2187
|
-
if (!p0 || !p1 || !p2 || !p3) continue;
|
|
2188
|
-
const cp1 = {
|
|
2189
|
-
x: p1.x + (p2.x - p0.x) / 6,
|
|
2190
|
-
y: p1.y + (p2.y - p0.y) / 6
|
|
2191
|
-
};
|
|
2192
|
-
const cp2 = {
|
|
2193
|
-
x: p2.x - (p3.x - p1.x) / 6,
|
|
2194
|
-
y: p2.y - (p3.y - p1.y) / 6
|
|
2195
|
-
};
|
|
2196
|
-
segments.push({ start: p1, cp1, cp2, end: p2 });
|
|
2197
|
-
}
|
|
2198
|
-
return segments;
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
// src/elements/stroke-cache.ts
|
|
2202
|
-
var cache = /* @__PURE__ */ new WeakMap();
|
|
2203
|
-
function computeStrokeSegments(stroke) {
|
|
2204
|
-
const segments = smoothToSegments(stroke.points);
|
|
2205
|
-
const widths = [];
|
|
2206
|
-
for (const seg of segments) {
|
|
2207
|
-
const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
|
|
2208
|
-
widths.push(w);
|
|
2209
|
-
}
|
|
2210
|
-
const data = { segments, widths };
|
|
2211
|
-
cache.set(stroke, data);
|
|
2212
|
-
return data;
|
|
2213
|
-
}
|
|
2214
|
-
function getStrokeRenderData(stroke) {
|
|
2215
|
-
const cached = cache.get(stroke);
|
|
2216
|
-
if (cached) return cached;
|
|
2217
|
-
return computeStrokeSegments(stroke);
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
2276
|
// src/elements/grid-renderer.ts
|
|
2221
2277
|
function getSquareGridLines(bounds, cellSize) {
|
|
2222
2278
|
if (cellSize <= 0) return { verticals: [], horizontals: [] };
|
|
@@ -2662,21 +2718,29 @@ var ElementRenderer = class {
|
|
|
2662
2718
|
ctx.lineCap = "round";
|
|
2663
2719
|
ctx.lineJoin = "round";
|
|
2664
2720
|
ctx.globalAlpha = stroke.opacity;
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2721
|
+
const data = getStrokeRenderData(stroke);
|
|
2722
|
+
if (data.buckets) {
|
|
2723
|
+
for (const bucket of data.buckets) {
|
|
2724
|
+
ctx.lineWidth = bucket.width;
|
|
2725
|
+
ctx.stroke(bucket.path);
|
|
2726
|
+
}
|
|
2727
|
+
} else {
|
|
2728
|
+
for (let i = 0; i < data.segments.length; i++) {
|
|
2729
|
+
const seg = data.segments[i];
|
|
2730
|
+
const w = data.widths[i];
|
|
2731
|
+
if (!seg || w === void 0) continue;
|
|
2732
|
+
ctx.lineWidth = w;
|
|
2733
|
+
ctx.beginPath();
|
|
2734
|
+
ctx.moveTo(seg.start.x, seg.start.y);
|
|
2735
|
+
ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.end.x, seg.end.y);
|
|
2736
|
+
ctx.stroke();
|
|
2737
|
+
}
|
|
2675
2738
|
}
|
|
2676
2739
|
ctx.restore();
|
|
2677
2740
|
}
|
|
2678
2741
|
renderArrow(ctx, arrow) {
|
|
2679
|
-
const
|
|
2742
|
+
const geometry = getArrowRenderGeometry(arrow);
|
|
2743
|
+
const { visualFrom, visualTo } = this.getVisualEndpoints(arrow, geometry);
|
|
2680
2744
|
ctx.save();
|
|
2681
2745
|
ctx.strokeStyle = arrow.color;
|
|
2682
2746
|
ctx.lineWidth = arrow.width;
|
|
@@ -2687,17 +2751,18 @@ var ElementRenderer = class {
|
|
|
2687
2751
|
ctx.beginPath();
|
|
2688
2752
|
ctx.moveTo(visualFrom.x, visualFrom.y);
|
|
2689
2753
|
if (arrow.bend !== 0) {
|
|
2690
|
-
const cp =
|
|
2691
|
-
|
|
2754
|
+
const cp = geometry.controlPoint;
|
|
2755
|
+
if (cp) {
|
|
2756
|
+
ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
|
|
2757
|
+
}
|
|
2692
2758
|
} else {
|
|
2693
2759
|
ctx.lineTo(visualTo.x, visualTo.y);
|
|
2694
2760
|
}
|
|
2695
2761
|
ctx.stroke();
|
|
2696
|
-
this.renderArrowhead(ctx, arrow, visualTo);
|
|
2762
|
+
this.renderArrowhead(ctx, arrow, visualTo, geometry.tangentEnd);
|
|
2697
2763
|
ctx.restore();
|
|
2698
2764
|
}
|
|
2699
|
-
renderArrowhead(ctx, arrow, tip) {
|
|
2700
|
-
const angle = getArrowTangentAngle(arrow.from, arrow.to, arrow.bend, 1);
|
|
2765
|
+
renderArrowhead(ctx, arrow, tip, angle) {
|
|
2701
2766
|
ctx.beginPath();
|
|
2702
2767
|
ctx.moveTo(tip.x, tip.y);
|
|
2703
2768
|
ctx.lineTo(
|
|
@@ -2712,7 +2777,7 @@ var ElementRenderer = class {
|
|
|
2712
2777
|
ctx.fillStyle = arrow.color;
|
|
2713
2778
|
ctx.fill();
|
|
2714
2779
|
}
|
|
2715
|
-
getVisualEndpoints(arrow) {
|
|
2780
|
+
getVisualEndpoints(arrow, geometry) {
|
|
2716
2781
|
let visualFrom = arrow.from;
|
|
2717
2782
|
let visualTo = arrow.to;
|
|
2718
2783
|
if (!this.store) return { visualFrom, visualTo };
|
|
@@ -2721,7 +2786,7 @@ var ElementRenderer = class {
|
|
|
2721
2786
|
if (el) {
|
|
2722
2787
|
const bounds = getElementBounds(el);
|
|
2723
2788
|
if (bounds) {
|
|
2724
|
-
const tangentAngle =
|
|
2789
|
+
const tangentAngle = geometry.tangentStart;
|
|
2725
2790
|
const rayTarget = {
|
|
2726
2791
|
x: arrow.from.x + Math.cos(tangentAngle) * 1e3,
|
|
2727
2792
|
y: arrow.from.y + Math.sin(tangentAngle) * 1e3
|
|
@@ -2735,7 +2800,7 @@ var ElementRenderer = class {
|
|
|
2735
2800
|
if (el) {
|
|
2736
2801
|
const bounds = getElementBounds(el);
|
|
2737
2802
|
if (bounds) {
|
|
2738
|
-
const tangentAngle =
|
|
2803
|
+
const tangentAngle = geometry.tangentEnd;
|
|
2739
2804
|
const rayTarget = {
|
|
2740
2805
|
x: arrow.to.x - Math.cos(tangentAngle) * 1e3,
|
|
2741
2806
|
y: arrow.to.y - Math.sin(tangentAngle) * 1e3
|
|
@@ -4099,19 +4164,19 @@ function loadImages(elements) {
|
|
|
4099
4164
|
const imageElements = elements.filter(
|
|
4100
4165
|
(el) => el.type === "image" && "src" in el
|
|
4101
4166
|
);
|
|
4102
|
-
const
|
|
4103
|
-
if (imageElements.length === 0) return Promise.resolve(
|
|
4167
|
+
const cache3 = /* @__PURE__ */ new Map();
|
|
4168
|
+
if (imageElements.length === 0) return Promise.resolve(cache3);
|
|
4104
4169
|
return new Promise((resolve) => {
|
|
4105
4170
|
let remaining = imageElements.length;
|
|
4106
4171
|
const done = () => {
|
|
4107
4172
|
remaining--;
|
|
4108
|
-
if (remaining <= 0) resolve(
|
|
4173
|
+
if (remaining <= 0) resolve(cache3);
|
|
4109
4174
|
};
|
|
4110
4175
|
for (const el of imageElements) {
|
|
4111
4176
|
const img = new Image();
|
|
4112
4177
|
img.crossOrigin = "anonymous";
|
|
4113
4178
|
img.onload = () => {
|
|
4114
|
-
|
|
4179
|
+
cache3.set(el.id, img);
|
|
4115
4180
|
done();
|
|
4116
4181
|
};
|
|
4117
4182
|
img.onerror = done;
|
|
@@ -4592,18 +4657,39 @@ var RenderStats = class {
|
|
|
4592
4657
|
frameTimes = [];
|
|
4593
4658
|
frameCount = 0;
|
|
4594
4659
|
_lastGridMs = 0;
|
|
4595
|
-
|
|
4660
|
+
_lastLayersMs = 0;
|
|
4661
|
+
_lastBackgroundMs = 0;
|
|
4662
|
+
_lastCompositeMs = 0;
|
|
4663
|
+
_lastOverlayMs = 0;
|
|
4664
|
+
recordFrame(durationMs, breakdown) {
|
|
4596
4665
|
this.frameCount++;
|
|
4597
4666
|
this.frameTimes.push(durationMs);
|
|
4598
4667
|
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
4599
4668
|
this.frameTimes.shift();
|
|
4600
4669
|
}
|
|
4601
|
-
if (
|
|
4670
|
+
if (breakdown !== void 0) {
|
|
4671
|
+
if (breakdown.gridMs !== void 0) this._lastGridMs = breakdown.gridMs;
|
|
4672
|
+
if (breakdown.layersMs !== void 0) this._lastLayersMs = breakdown.layersMs;
|
|
4673
|
+
if (breakdown.backgroundMs !== void 0) this._lastBackgroundMs = breakdown.backgroundMs;
|
|
4674
|
+
if (breakdown.compositeMs !== void 0) this._lastCompositeMs = breakdown.compositeMs;
|
|
4675
|
+
if (breakdown.overlayMs !== void 0) this._lastOverlayMs = breakdown.overlayMs;
|
|
4676
|
+
}
|
|
4602
4677
|
}
|
|
4603
4678
|
getSnapshot() {
|
|
4604
4679
|
const times = this.frameTimes;
|
|
4605
4680
|
if (times.length === 0) {
|
|
4606
|
-
return {
|
|
4681
|
+
return {
|
|
4682
|
+
fps: 0,
|
|
4683
|
+
avgFrameMs: 0,
|
|
4684
|
+
p95FrameMs: 0,
|
|
4685
|
+
lastFrameMs: 0,
|
|
4686
|
+
lastGridMs: 0,
|
|
4687
|
+
layersMs: 0,
|
|
4688
|
+
backgroundMs: 0,
|
|
4689
|
+
compositeMs: 0,
|
|
4690
|
+
overlayMs: 0,
|
|
4691
|
+
frameCount: 0
|
|
4692
|
+
};
|
|
4607
4693
|
}
|
|
4608
4694
|
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
4609
4695
|
const sorted = [...times].sort((a, b) => a - b);
|
|
@@ -4615,6 +4701,10 @@ var RenderStats = class {
|
|
|
4615
4701
|
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
4616
4702
|
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
4617
4703
|
lastGridMs: Math.round(this._lastGridMs * 100) / 100,
|
|
4704
|
+
layersMs: Math.round(this._lastLayersMs * 100) / 100,
|
|
4705
|
+
backgroundMs: Math.round(this._lastBackgroundMs * 100) / 100,
|
|
4706
|
+
compositeMs: Math.round(this._lastCompositeMs * 100) / 100,
|
|
4707
|
+
overlayMs: Math.round(this._lastOverlayMs * 100) / 100,
|
|
4618
4708
|
frameCount: this.frameCount
|
|
4619
4709
|
};
|
|
4620
4710
|
}
|
|
@@ -4642,7 +4732,7 @@ var RenderLoop = class {
|
|
|
4642
4732
|
lastCamX;
|
|
4643
4733
|
lastCamY;
|
|
4644
4734
|
stats = new RenderStats();
|
|
4645
|
-
|
|
4735
|
+
layerGroups = /* @__PURE__ */ new Map();
|
|
4646
4736
|
gridCacheCanvas = null;
|
|
4647
4737
|
gridCacheCtx = null;
|
|
4648
4738
|
gridCacheZoom = -1;
|
|
@@ -4740,6 +4830,9 @@ var RenderLoop = class {
|
|
|
4740
4830
|
const t0 = performance.now();
|
|
4741
4831
|
const ctx = this.canvasEl.getContext("2d");
|
|
4742
4832
|
if (!ctx) return;
|
|
4833
|
+
let layersMs = 0;
|
|
4834
|
+
let compositeMs = 0;
|
|
4835
|
+
let gridMs = 0;
|
|
4743
4836
|
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
4744
4837
|
const cssWidth = this.canvasEl.clientWidth;
|
|
4745
4838
|
const cssHeight = this.canvasEl.clientHeight;
|
|
@@ -4756,6 +4849,7 @@ var RenderLoop = class {
|
|
|
4756
4849
|
ctx.scale(dpr, dpr);
|
|
4757
4850
|
this.renderer.setCanvasSize(cssWidth, cssHeight);
|
|
4758
4851
|
const hasGridElement = this.store.getElementsByType("grid").length > 0;
|
|
4852
|
+
const bgT0 = performance.now();
|
|
4759
4853
|
if (hasGridElement) {
|
|
4760
4854
|
ctx.save();
|
|
4761
4855
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
@@ -4764,6 +4858,7 @@ var RenderLoop = class {
|
|
|
4764
4858
|
} else {
|
|
4765
4859
|
this.background.render(ctx, this.camera);
|
|
4766
4860
|
}
|
|
4861
|
+
const backgroundMs = performance.now() - bgT0;
|
|
4767
4862
|
ctx.save();
|
|
4768
4863
|
ctx.translate(this.camera.position.x, this.camera.position.y);
|
|
4769
4864
|
ctx.scale(this.camera.zoom, this.camera.zoom);
|
|
@@ -4776,7 +4871,7 @@ var RenderLoop = class {
|
|
|
4776
4871
|
h: visibleRect.h + margin * 2
|
|
4777
4872
|
};
|
|
4778
4873
|
const allElements = this.store.getAll();
|
|
4779
|
-
|
|
4874
|
+
this.layerGroups.clear();
|
|
4780
4875
|
const gridElements = [];
|
|
4781
4876
|
let domZIndex = 0;
|
|
4782
4877
|
for (const element of allElements) {
|
|
@@ -4799,25 +4894,30 @@ var RenderLoop = class {
|
|
|
4799
4894
|
gridElements.push(element);
|
|
4800
4895
|
continue;
|
|
4801
4896
|
}
|
|
4802
|
-
let group =
|
|
4897
|
+
let group = this.layerGroups.get(element.layerId);
|
|
4803
4898
|
if (!group) {
|
|
4804
4899
|
group = [];
|
|
4805
|
-
|
|
4900
|
+
this.layerGroups.set(element.layerId, group);
|
|
4806
4901
|
}
|
|
4807
4902
|
group.push(element);
|
|
4808
4903
|
}
|
|
4809
|
-
for (const [layerId, elements] of
|
|
4904
|
+
for (const [layerId, elements] of this.layerGroups) {
|
|
4810
4905
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
4811
4906
|
if (!this.layerCache.isDirty(layerId)) {
|
|
4907
|
+
const compT0 = performance.now();
|
|
4812
4908
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
4909
|
+
compositeMs += performance.now() - compT0;
|
|
4813
4910
|
continue;
|
|
4814
4911
|
}
|
|
4815
4912
|
if (isActiveDrawingLayer) {
|
|
4913
|
+
const compT0 = performance.now();
|
|
4816
4914
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
4915
|
+
compositeMs += performance.now() - compT0;
|
|
4817
4916
|
continue;
|
|
4818
4917
|
}
|
|
4819
4918
|
const offCtx = this.layerCache.getContext(layerId);
|
|
4820
4919
|
if (offCtx) {
|
|
4920
|
+
const layerT0 = performance.now();
|
|
4821
4921
|
const offCanvas = this.layerCache.getCanvas(layerId);
|
|
4822
4922
|
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
|
|
4823
4923
|
offCtx.save();
|
|
@@ -4831,7 +4931,10 @@ var RenderLoop = class {
|
|
|
4831
4931
|
}
|
|
4832
4932
|
offCtx.restore();
|
|
4833
4933
|
this.layerCache.markClean(layerId);
|
|
4934
|
+
layersMs += performance.now() - layerT0;
|
|
4935
|
+
const compT0 = performance.now();
|
|
4834
4936
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
4937
|
+
compositeMs += performance.now() - compT0;
|
|
4835
4938
|
}
|
|
4836
4939
|
}
|
|
4837
4940
|
if (gridElements.length > 0) {
|
|
@@ -4872,15 +4975,23 @@ var RenderLoop = class {
|
|
|
4872
4975
|
this.gridCacheHeight = cssHeight;
|
|
4873
4976
|
this.lastGridRef = gridRef;
|
|
4874
4977
|
}
|
|
4875
|
-
|
|
4978
|
+
gridMs = performance.now() - gridT0;
|
|
4876
4979
|
}
|
|
4980
|
+
const overlayT0 = performance.now();
|
|
4877
4981
|
const activeTool = this.toolManager.activeTool;
|
|
4878
4982
|
if (activeTool?.renderOverlay) {
|
|
4879
4983
|
activeTool.renderOverlay(ctx);
|
|
4880
4984
|
}
|
|
4985
|
+
const overlayMs = performance.now() - overlayT0;
|
|
4881
4986
|
ctx.restore();
|
|
4882
4987
|
ctx.restore();
|
|
4883
|
-
this.stats.recordFrame(performance.now() - t0,
|
|
4988
|
+
this.stats.recordFrame(performance.now() - t0, {
|
|
4989
|
+
gridMs,
|
|
4990
|
+
layersMs,
|
|
4991
|
+
backgroundMs,
|
|
4992
|
+
compositeMs,
|
|
4993
|
+
overlayMs
|
|
4994
|
+
});
|
|
4884
4995
|
}
|
|
4885
4996
|
};
|
|
4886
4997
|
|
|
@@ -5292,7 +5403,7 @@ var Viewport = class {
|
|
|
5292
5403
|
const id = setInterval(() => {
|
|
5293
5404
|
const s = this.getRenderStats();
|
|
5294
5405
|
console.log(
|
|
5295
|
-
`[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms`
|
|
5406
|
+
`[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`
|
|
5296
5407
|
);
|
|
5297
5408
|
}, intervalMs);
|
|
5298
5409
|
return () => clearInterval(id);
|
|
@@ -7290,7 +7401,7 @@ var TemplateTool = class {
|
|
|
7290
7401
|
};
|
|
7291
7402
|
|
|
7292
7403
|
// src/index.ts
|
|
7293
|
-
var VERSION = "0.
|
|
7404
|
+
var VERSION = "0.22.0";
|
|
7294
7405
|
export {
|
|
7295
7406
|
AddElementCommand,
|
|
7296
7407
|
ArrowTool,
|