@fieldnotes/core 0.8.7 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +404 -90
- package/dist/index.d.cts +25 -9
- package/dist/index.d.ts +25 -9
- package/dist/index.js +404 -90
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -542,6 +542,13 @@ var Background = class {
|
|
|
542
542
|
color;
|
|
543
543
|
dotRadius;
|
|
544
544
|
lineWidth;
|
|
545
|
+
cachedCanvas = null;
|
|
546
|
+
cachedCtx = null;
|
|
547
|
+
lastZoom = -1;
|
|
548
|
+
lastOffsetX = -Infinity;
|
|
549
|
+
lastOffsetY = -Infinity;
|
|
550
|
+
lastWidth = 0;
|
|
551
|
+
lastHeight = 0;
|
|
545
552
|
constructor(options = {}) {
|
|
546
553
|
this.pattern = options.pattern ?? DEFAULTS.pattern;
|
|
547
554
|
this.spacing = options.spacing ?? DEFAULTS.spacing;
|
|
@@ -557,13 +564,69 @@ var Background = class {
|
|
|
557
564
|
ctx.save();
|
|
558
565
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
559
566
|
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
567
|
+
if (this.pattern === "none") {
|
|
568
|
+
ctx.restore();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const spacing = this.adaptSpacing(this.spacing, camera.zoom);
|
|
572
|
+
const keyZoom = camera.zoom;
|
|
573
|
+
const keyX = Math.floor(camera.position.x % spacing);
|
|
574
|
+
const keyY = Math.floor(camera.position.y % spacing);
|
|
575
|
+
if (this.cachedCanvas !== null && keyZoom === this.lastZoom && keyX === this.lastOffsetX && keyY === this.lastOffsetY && cssWidth === this.lastWidth && cssHeight === this.lastHeight) {
|
|
576
|
+
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
577
|
+
ctx.restore();
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
this.ensureCachedCanvas(cssWidth, cssHeight, dpr);
|
|
581
|
+
if (this.cachedCtx === null) {
|
|
582
|
+
if (this.pattern === "dots") {
|
|
583
|
+
this.renderDots(ctx, camera, cssWidth, cssHeight);
|
|
584
|
+
} else if (this.pattern === "grid") {
|
|
585
|
+
this.renderGrid(ctx, camera, cssWidth, cssHeight);
|
|
586
|
+
}
|
|
587
|
+
ctx.restore();
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const offCtx = this.cachedCtx;
|
|
591
|
+
offCtx.clearRect(0, 0, cssWidth, cssHeight);
|
|
560
592
|
if (this.pattern === "dots") {
|
|
561
|
-
this.renderDots(
|
|
593
|
+
this.renderDots(offCtx, camera, cssWidth, cssHeight);
|
|
562
594
|
} else if (this.pattern === "grid") {
|
|
563
|
-
this.renderGrid(
|
|
564
|
-
}
|
|
595
|
+
this.renderGrid(offCtx, camera, cssWidth, cssHeight);
|
|
596
|
+
}
|
|
597
|
+
this.lastZoom = keyZoom;
|
|
598
|
+
this.lastOffsetX = keyX;
|
|
599
|
+
this.lastOffsetY = keyY;
|
|
600
|
+
this.lastWidth = cssWidth;
|
|
601
|
+
this.lastHeight = cssHeight;
|
|
602
|
+
ctx.drawImage(this.cachedCanvas, 0, 0);
|
|
565
603
|
ctx.restore();
|
|
566
604
|
}
|
|
605
|
+
ensureCachedCanvas(cssWidth, cssHeight, dpr) {
|
|
606
|
+
if (this.cachedCanvas !== null && this.lastWidth === cssWidth && this.lastHeight === cssHeight) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const physWidth = Math.round(cssWidth * dpr);
|
|
610
|
+
const physHeight = Math.round(cssHeight * dpr);
|
|
611
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
612
|
+
this.cachedCanvas = new OffscreenCanvas(physWidth, physHeight);
|
|
613
|
+
} else if (typeof document !== "undefined") {
|
|
614
|
+
const el = document.createElement("canvas");
|
|
615
|
+
el.width = physWidth;
|
|
616
|
+
el.height = physHeight;
|
|
617
|
+
this.cachedCanvas = el;
|
|
618
|
+
} else {
|
|
619
|
+
this.cachedCanvas = null;
|
|
620
|
+
this.cachedCtx = null;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const offCtx = this.cachedCanvas.getContext("2d");
|
|
624
|
+
if (offCtx !== null) {
|
|
625
|
+
offCtx.scale(dpr, dpr);
|
|
626
|
+
}
|
|
627
|
+
this.cachedCtx = offCtx;
|
|
628
|
+
this.lastZoom = -1;
|
|
629
|
+
}
|
|
567
630
|
adaptSpacing(baseSpacing, zoom) {
|
|
568
631
|
let spacing = baseSpacing * zoom;
|
|
569
632
|
while (spacing < MIN_PATTERN_SPACING) {
|
|
@@ -1046,6 +1109,10 @@ var ElementStore = class {
|
|
|
1046
1109
|
const existing = this.elements.get(id);
|
|
1047
1110
|
if (!existing) return;
|
|
1048
1111
|
const updated = { ...existing, ...partial, id: existing.id, type: existing.type };
|
|
1112
|
+
if (updated.type === "arrow") {
|
|
1113
|
+
const arrow = updated;
|
|
1114
|
+
arrow.cachedControlPoint = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
1115
|
+
}
|
|
1049
1116
|
this.elements.set(id, updated);
|
|
1050
1117
|
const newBounds = getElementBounds(updated);
|
|
1051
1118
|
if (newBounds) {
|
|
@@ -1301,6 +1368,25 @@ function smoothToSegments(points) {
|
|
|
1301
1368
|
return segments;
|
|
1302
1369
|
}
|
|
1303
1370
|
|
|
1371
|
+
// src/elements/stroke-cache.ts
|
|
1372
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
1373
|
+
function computeStrokeSegments(stroke) {
|
|
1374
|
+
const segments = smoothToSegments(stroke.points);
|
|
1375
|
+
const widths = [];
|
|
1376
|
+
for (const seg of segments) {
|
|
1377
|
+
const w = (pressureToWidth(seg.start.pressure, stroke.width) + pressureToWidth(seg.end.pressure, stroke.width)) / 2;
|
|
1378
|
+
widths.push(w);
|
|
1379
|
+
}
|
|
1380
|
+
const data = { segments, widths };
|
|
1381
|
+
cache.set(stroke, data);
|
|
1382
|
+
return data;
|
|
1383
|
+
}
|
|
1384
|
+
function getStrokeRenderData(stroke) {
|
|
1385
|
+
const cached = cache.get(stroke);
|
|
1386
|
+
if (cached) return cached;
|
|
1387
|
+
return computeStrokeSegments(stroke);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1304
1390
|
// src/elements/grid-renderer.ts
|
|
1305
1391
|
function getSquareGridLines(bounds, cellSize) {
|
|
1306
1392
|
if (cellSize <= 0) return { verticals: [], horizontals: [] };
|
|
@@ -1318,58 +1404,6 @@ function getSquareGridLines(bounds, cellSize) {
|
|
|
1318
1404
|
}
|
|
1319
1405
|
return { verticals, horizontals };
|
|
1320
1406
|
}
|
|
1321
|
-
function getHexVertices(cx, cy, circumradius, orientation) {
|
|
1322
|
-
const vertices = [];
|
|
1323
|
-
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1324
|
-
for (let i = 0; i < 6; i++) {
|
|
1325
|
-
const angle = Math.PI / 3 * i + angleOffset;
|
|
1326
|
-
vertices.push({
|
|
1327
|
-
x: cx + circumradius * Math.cos(angle),
|
|
1328
|
-
y: cy + circumradius * Math.sin(angle)
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
return vertices;
|
|
1332
|
-
}
|
|
1333
|
-
function getHexCenters(bounds, circumradius, orientation) {
|
|
1334
|
-
if (circumradius <= 0) return [];
|
|
1335
|
-
const centers = [];
|
|
1336
|
-
if (orientation === "pointy") {
|
|
1337
|
-
const hexW = Math.sqrt(3) * circumradius;
|
|
1338
|
-
const hexH = 2 * circumradius;
|
|
1339
|
-
const rowH = hexH * 0.75;
|
|
1340
|
-
const startRow = Math.floor((bounds.minY - circumradius) / rowH);
|
|
1341
|
-
const endRow = Math.ceil((bounds.maxY + circumradius) / rowH);
|
|
1342
|
-
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1343
|
-
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1344
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
1345
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1346
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
1347
|
-
centers.push({
|
|
1348
|
-
x: col * hexW + offsetX,
|
|
1349
|
-
y: row * rowH
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
} else {
|
|
1354
|
-
const hexW = 2 * circumradius;
|
|
1355
|
-
const hexH = Math.sqrt(3) * circumradius;
|
|
1356
|
-
const colW = hexW * 0.75;
|
|
1357
|
-
const startCol = Math.floor((bounds.minX - circumradius) / colW);
|
|
1358
|
-
const endCol = Math.ceil((bounds.maxX + circumradius) / colW);
|
|
1359
|
-
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1360
|
-
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1361
|
-
for (let col = startCol; col <= endCol; col++) {
|
|
1362
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1363
|
-
for (let row = startRow; row <= endRow; row++) {
|
|
1364
|
-
centers.push({
|
|
1365
|
-
x: col * colW,
|
|
1366
|
-
y: row * hexH + offsetY
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
return centers;
|
|
1372
|
-
}
|
|
1373
1407
|
function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opacity) {
|
|
1374
1408
|
if (cellSize <= 0) return;
|
|
1375
1409
|
const { verticals, horizontals } = getSquareGridLines(bounds, cellSize);
|
|
@@ -1391,27 +1425,172 @@ function renderSquareGrid(ctx, bounds, cellSize, strokeColor, strokeWidth, opaci
|
|
|
1391
1425
|
}
|
|
1392
1426
|
function renderHexGrid(ctx, bounds, cellSize, orientation, strokeColor, strokeWidth, opacity) {
|
|
1393
1427
|
if (cellSize <= 0) return;
|
|
1394
|
-
const
|
|
1428
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1429
|
+
const ox0 = cellSize * Math.cos(angleOffset);
|
|
1430
|
+
const oy0 = cellSize * Math.sin(angleOffset);
|
|
1431
|
+
const ox1 = cellSize * Math.cos(Math.PI / 3 + angleOffset);
|
|
1432
|
+
const oy1 = cellSize * Math.sin(Math.PI / 3 + angleOffset);
|
|
1433
|
+
const ox2 = cellSize * Math.cos(2 * Math.PI / 3 + angleOffset);
|
|
1434
|
+
const oy2 = cellSize * Math.sin(2 * Math.PI / 3 + angleOffset);
|
|
1435
|
+
const ox3 = cellSize * Math.cos(Math.PI + angleOffset);
|
|
1436
|
+
const oy3 = cellSize * Math.sin(Math.PI + angleOffset);
|
|
1437
|
+
const ox4 = cellSize * Math.cos(4 * Math.PI / 3 + angleOffset);
|
|
1438
|
+
const oy4 = cellSize * Math.sin(4 * Math.PI / 3 + angleOffset);
|
|
1439
|
+
const ox5 = cellSize * Math.cos(5 * Math.PI / 3 + angleOffset);
|
|
1440
|
+
const oy5 = cellSize * Math.sin(5 * Math.PI / 3 + angleOffset);
|
|
1395
1441
|
ctx.save();
|
|
1396
1442
|
ctx.strokeStyle = strokeColor;
|
|
1397
1443
|
ctx.lineWidth = strokeWidth;
|
|
1398
1444
|
ctx.globalAlpha = opacity;
|
|
1399
1445
|
ctx.beginPath();
|
|
1400
|
-
|
|
1401
|
-
const
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1446
|
+
if (orientation === "pointy") {
|
|
1447
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
1448
|
+
const rowH = 1.5 * cellSize;
|
|
1449
|
+
const startRow = Math.floor((bounds.minY - cellSize) / rowH);
|
|
1450
|
+
const endRow = Math.ceil((bounds.maxY + cellSize) / rowH);
|
|
1451
|
+
const startCol = Math.floor((bounds.minX - hexW) / hexW);
|
|
1452
|
+
const endCol = Math.ceil((bounds.maxX + hexW) / hexW);
|
|
1453
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1454
|
+
const offX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1455
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1456
|
+
const cx = col * hexW + offX;
|
|
1457
|
+
const cy = row * rowH;
|
|
1458
|
+
ctx.moveTo(cx + ox0, cy + oy0);
|
|
1459
|
+
ctx.lineTo(cx + ox1, cy + oy1);
|
|
1460
|
+
ctx.lineTo(cx + ox2, cy + oy2);
|
|
1461
|
+
ctx.lineTo(cx + ox3, cy + oy3);
|
|
1462
|
+
ctx.lineTo(cx + ox4, cy + oy4);
|
|
1463
|
+
ctx.lineTo(cx + ox5, cy + oy5);
|
|
1464
|
+
ctx.closePath();
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
1469
|
+
const colW = 1.5 * cellSize;
|
|
1470
|
+
const startCol = Math.floor((bounds.minX - cellSize) / colW);
|
|
1471
|
+
const endCol = Math.ceil((bounds.maxX + cellSize) / colW);
|
|
1472
|
+
const startRow = Math.floor((bounds.minY - hexH) / hexH);
|
|
1473
|
+
const endRow = Math.ceil((bounds.maxY + hexH) / hexH);
|
|
1474
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1475
|
+
const offY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1476
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
1477
|
+
const cx = col * colW;
|
|
1478
|
+
const cy = row * hexH + offY;
|
|
1479
|
+
ctx.moveTo(cx + ox0, cy + oy0);
|
|
1480
|
+
ctx.lineTo(cx + ox1, cy + oy1);
|
|
1481
|
+
ctx.lineTo(cx + ox2, cy + oy2);
|
|
1482
|
+
ctx.lineTo(cx + ox3, cy + oy3);
|
|
1483
|
+
ctx.lineTo(cx + ox4, cy + oy4);
|
|
1484
|
+
ctx.lineTo(cx + ox5, cy + oy5);
|
|
1485
|
+
ctx.closePath();
|
|
1486
|
+
}
|
|
1409
1487
|
}
|
|
1410
|
-
ctx.closePath();
|
|
1411
1488
|
}
|
|
1412
1489
|
ctx.stroke();
|
|
1413
1490
|
ctx.restore();
|
|
1414
1491
|
}
|
|
1492
|
+
function createHexGridTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale) {
|
|
1493
|
+
let tileW;
|
|
1494
|
+
let tileH;
|
|
1495
|
+
if (orientation === "pointy") {
|
|
1496
|
+
tileW = Math.sqrt(3) * cellSize;
|
|
1497
|
+
tileH = 3 * cellSize;
|
|
1498
|
+
} else {
|
|
1499
|
+
tileW = 3 * cellSize;
|
|
1500
|
+
tileH = Math.sqrt(3) * cellSize;
|
|
1501
|
+
}
|
|
1502
|
+
const pxW = Math.ceil(tileW * scale);
|
|
1503
|
+
const pxH = Math.ceil(tileH * scale);
|
|
1504
|
+
if (pxW <= 0 || pxH <= 0) return null;
|
|
1505
|
+
let canvas;
|
|
1506
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
1507
|
+
canvas = new OffscreenCanvas(pxW, pxH);
|
|
1508
|
+
} else if (typeof document !== "undefined") {
|
|
1509
|
+
const el = document.createElement("canvas");
|
|
1510
|
+
el.width = pxW;
|
|
1511
|
+
el.height = pxH;
|
|
1512
|
+
canvas = el;
|
|
1513
|
+
} else {
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
const tc = canvas.getContext("2d");
|
|
1517
|
+
if (!tc) return null;
|
|
1518
|
+
tc.scale(scale, scale);
|
|
1519
|
+
tc.beginPath();
|
|
1520
|
+
tc.rect(0, 0, tileW, tileH);
|
|
1521
|
+
tc.clip();
|
|
1522
|
+
const angleOffset = orientation === "pointy" ? -Math.PI / 2 : 0;
|
|
1523
|
+
const ox0 = cellSize * Math.cos(angleOffset);
|
|
1524
|
+
const oy0 = cellSize * Math.sin(angleOffset);
|
|
1525
|
+
const ox1 = cellSize * Math.cos(Math.PI / 3 + angleOffset);
|
|
1526
|
+
const oy1 = cellSize * Math.sin(Math.PI / 3 + angleOffset);
|
|
1527
|
+
const ox2 = cellSize * Math.cos(2 * Math.PI / 3 + angleOffset);
|
|
1528
|
+
const oy2 = cellSize * Math.sin(2 * Math.PI / 3 + angleOffset);
|
|
1529
|
+
const ox3 = cellSize * Math.cos(Math.PI + angleOffset);
|
|
1530
|
+
const oy3 = cellSize * Math.sin(Math.PI + angleOffset);
|
|
1531
|
+
const ox4 = cellSize * Math.cos(4 * Math.PI / 3 + angleOffset);
|
|
1532
|
+
const oy4 = cellSize * Math.sin(4 * Math.PI / 3 + angleOffset);
|
|
1533
|
+
const ox5 = cellSize * Math.cos(5 * Math.PI / 3 + angleOffset);
|
|
1534
|
+
const oy5 = cellSize * Math.sin(5 * Math.PI / 3 + angleOffset);
|
|
1535
|
+
tc.strokeStyle = strokeColor;
|
|
1536
|
+
tc.lineWidth = strokeWidth;
|
|
1537
|
+
tc.globalAlpha = opacity;
|
|
1538
|
+
tc.beginPath();
|
|
1539
|
+
if (orientation === "pointy") {
|
|
1540
|
+
const hexW = tileW;
|
|
1541
|
+
const rowH = 1.5 * cellSize;
|
|
1542
|
+
for (let row = -1; row <= 3; row++) {
|
|
1543
|
+
const offX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
1544
|
+
for (let col = -1; col <= 1; col++) {
|
|
1545
|
+
const cx = col * hexW + offX;
|
|
1546
|
+
const cy = row * rowH;
|
|
1547
|
+
tc.moveTo(cx + ox0, cy + oy0);
|
|
1548
|
+
tc.lineTo(cx + ox1, cy + oy1);
|
|
1549
|
+
tc.lineTo(cx + ox2, cy + oy2);
|
|
1550
|
+
tc.lineTo(cx + ox3, cy + oy3);
|
|
1551
|
+
tc.lineTo(cx + ox4, cy + oy4);
|
|
1552
|
+
tc.lineTo(cx + ox5, cy + oy5);
|
|
1553
|
+
tc.closePath();
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
} else {
|
|
1557
|
+
const hexH = tileH;
|
|
1558
|
+
const colW = 1.5 * cellSize;
|
|
1559
|
+
for (let col = -1; col <= 3; col++) {
|
|
1560
|
+
const offY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
1561
|
+
for (let row = -1; row <= 1; row++) {
|
|
1562
|
+
const cx = col * colW;
|
|
1563
|
+
const cy = row * hexH + offY;
|
|
1564
|
+
tc.moveTo(cx + ox0, cy + oy0);
|
|
1565
|
+
tc.lineTo(cx + ox1, cy + oy1);
|
|
1566
|
+
tc.lineTo(cx + ox2, cy + oy2);
|
|
1567
|
+
tc.lineTo(cx + ox3, cy + oy3);
|
|
1568
|
+
tc.lineTo(cx + ox4, cy + oy4);
|
|
1569
|
+
tc.lineTo(cx + ox5, cy + oy5);
|
|
1570
|
+
tc.closePath();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
tc.stroke();
|
|
1575
|
+
return { canvas, tileW, tileH };
|
|
1576
|
+
}
|
|
1577
|
+
function renderHexGridTiled(ctx, bounds, cellSize, tile, scale) {
|
|
1578
|
+
const pattern = ctx.createPattern(tile.canvas, "repeat");
|
|
1579
|
+
if (!pattern) return;
|
|
1580
|
+
const mat = new DOMMatrix();
|
|
1581
|
+
mat.scaleSelf(1 / scale, 1 / scale);
|
|
1582
|
+
pattern.setTransform(mat);
|
|
1583
|
+
ctx.save();
|
|
1584
|
+
ctx.fillStyle = pattern;
|
|
1585
|
+
const pad = cellSize * 2;
|
|
1586
|
+
ctx.fillRect(
|
|
1587
|
+
bounds.minX - pad,
|
|
1588
|
+
bounds.minY - pad,
|
|
1589
|
+
bounds.maxX - bounds.minX + pad * 2,
|
|
1590
|
+
bounds.maxY - bounds.minY + pad * 2
|
|
1591
|
+
);
|
|
1592
|
+
ctx.restore();
|
|
1593
|
+
}
|
|
1415
1594
|
|
|
1416
1595
|
// src/elements/element-renderer.ts
|
|
1417
1596
|
var DOM_ELEMENT_TYPES = /* @__PURE__ */ new Set(["note", "html", "text"]);
|
|
@@ -1423,6 +1602,8 @@ var ElementRenderer = class {
|
|
|
1423
1602
|
onImageLoad = null;
|
|
1424
1603
|
camera = null;
|
|
1425
1604
|
canvasSize = null;
|
|
1605
|
+
hexTileCache = null;
|
|
1606
|
+
hexTileCacheKey = "";
|
|
1426
1607
|
setStore(store) {
|
|
1427
1608
|
this.store = store;
|
|
1428
1609
|
}
|
|
@@ -1465,9 +1646,11 @@ var ElementRenderer = class {
|
|
|
1465
1646
|
ctx.lineCap = "round";
|
|
1466
1647
|
ctx.lineJoin = "round";
|
|
1467
1648
|
ctx.globalAlpha = stroke.opacity;
|
|
1468
|
-
const segments =
|
|
1469
|
-
for (
|
|
1470
|
-
const
|
|
1649
|
+
const { segments, widths } = getStrokeRenderData(stroke);
|
|
1650
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1651
|
+
const seg = segments[i];
|
|
1652
|
+
const w = widths[i];
|
|
1653
|
+
if (!seg || w === void 0) continue;
|
|
1471
1654
|
ctx.lineWidth = w;
|
|
1472
1655
|
ctx.beginPath();
|
|
1473
1656
|
ctx.moveTo(seg.start.x, seg.start.y);
|
|
@@ -1488,7 +1671,7 @@ var ElementRenderer = class {
|
|
|
1488
1671
|
ctx.beginPath();
|
|
1489
1672
|
ctx.moveTo(visualFrom.x, visualFrom.y);
|
|
1490
1673
|
if (arrow.bend !== 0) {
|
|
1491
|
-
const cp = getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
1674
|
+
const cp = arrow.cachedControlPoint ?? getArrowControlPoint(arrow.from, arrow.to, arrow.bend);
|
|
1492
1675
|
ctx.quadraticCurveTo(cp.x, cp.y, visualTo.x, visualTo.y);
|
|
1493
1676
|
} else {
|
|
1494
1677
|
ctx.lineTo(visualTo.x, visualTo.y);
|
|
@@ -1606,15 +1789,29 @@ var ElementRenderer = class {
|
|
|
1606
1789
|
maxY: bottomRight.y
|
|
1607
1790
|
};
|
|
1608
1791
|
if (grid.gridType === "hex") {
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1792
|
+
const dpr = typeof devicePixelRatio !== "undefined" ? devicePixelRatio : 1;
|
|
1793
|
+
const scale = cam.zoom * dpr;
|
|
1794
|
+
const tile = this.getHexTile(
|
|
1612
1795
|
grid.cellSize,
|
|
1613
1796
|
grid.hexOrientation,
|
|
1614
1797
|
grid.strokeColor,
|
|
1615
1798
|
grid.strokeWidth,
|
|
1616
|
-
grid.opacity
|
|
1799
|
+
grid.opacity,
|
|
1800
|
+
scale
|
|
1617
1801
|
);
|
|
1802
|
+
if (tile) {
|
|
1803
|
+
renderHexGridTiled(ctx, bounds, grid.cellSize, tile, scale);
|
|
1804
|
+
} else {
|
|
1805
|
+
renderHexGrid(
|
|
1806
|
+
ctx,
|
|
1807
|
+
bounds,
|
|
1808
|
+
grid.cellSize,
|
|
1809
|
+
grid.hexOrientation,
|
|
1810
|
+
grid.strokeColor,
|
|
1811
|
+
grid.strokeWidth,
|
|
1812
|
+
grid.opacity
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1618
1815
|
} else {
|
|
1619
1816
|
renderSquareGrid(
|
|
1620
1817
|
ctx,
|
|
@@ -1629,15 +1826,45 @@ var ElementRenderer = class {
|
|
|
1629
1826
|
renderImage(ctx, image) {
|
|
1630
1827
|
const img = this.getImage(image.src);
|
|
1631
1828
|
if (!img) return;
|
|
1632
|
-
ctx.drawImage(
|
|
1829
|
+
ctx.drawImage(
|
|
1830
|
+
img,
|
|
1831
|
+
image.position.x,
|
|
1832
|
+
image.position.y,
|
|
1833
|
+
image.size.w,
|
|
1834
|
+
image.size.h
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
getHexTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale) {
|
|
1838
|
+
const key = `${cellSize}:${orientation}:${strokeColor}:${strokeWidth}:${opacity}:${scale}`;
|
|
1839
|
+
if (this.hexTileCacheKey === key && this.hexTileCache) {
|
|
1840
|
+
return this.hexTileCache;
|
|
1841
|
+
}
|
|
1842
|
+
const tile = createHexGridTile(cellSize, orientation, strokeColor, strokeWidth, opacity, scale);
|
|
1843
|
+
if (tile) {
|
|
1844
|
+
this.hexTileCache = tile;
|
|
1845
|
+
this.hexTileCacheKey = key;
|
|
1846
|
+
}
|
|
1847
|
+
return tile;
|
|
1633
1848
|
}
|
|
1634
1849
|
getImage(src) {
|
|
1635
1850
|
const cached = this.imageCache.get(src);
|
|
1636
|
-
if (cached)
|
|
1851
|
+
if (cached) {
|
|
1852
|
+
if (cached instanceof HTMLImageElement) return cached.complete ? cached : null;
|
|
1853
|
+
return cached;
|
|
1854
|
+
}
|
|
1637
1855
|
const img = new Image();
|
|
1638
1856
|
img.src = src;
|
|
1639
1857
|
this.imageCache.set(src, img);
|
|
1640
|
-
img.onload = () =>
|
|
1858
|
+
img.onload = () => {
|
|
1859
|
+
this.onImageLoad?.();
|
|
1860
|
+
if (typeof createImageBitmap !== "undefined") {
|
|
1861
|
+
createImageBitmap(img).then((bitmap) => {
|
|
1862
|
+
this.imageCache.set(src, bitmap);
|
|
1863
|
+
this.onImageLoad?.();
|
|
1864
|
+
}).catch(() => {
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1641
1868
|
return null;
|
|
1642
1869
|
}
|
|
1643
1870
|
};
|
|
@@ -2013,6 +2240,7 @@ function createNote(input) {
|
|
|
2013
2240
|
};
|
|
2014
2241
|
}
|
|
2015
2242
|
function createArrow(input) {
|
|
2243
|
+
const bend = input.bend ?? 0;
|
|
2016
2244
|
const result = {
|
|
2017
2245
|
id: createId("arrow"),
|
|
2018
2246
|
type: "arrow",
|
|
@@ -2022,9 +2250,10 @@ function createArrow(input) {
|
|
|
2022
2250
|
layerId: input.layerId ?? "",
|
|
2023
2251
|
from: input.from,
|
|
2024
2252
|
to: input.to,
|
|
2025
|
-
bend
|
|
2253
|
+
bend,
|
|
2026
2254
|
color: input.color ?? "#000000",
|
|
2027
|
-
width: input.width ?? 2
|
|
2255
|
+
width: input.width ?? 2,
|
|
2256
|
+
cachedControlPoint: getArrowControlPoint(input.from, input.to, bend)
|
|
2028
2257
|
};
|
|
2029
2258
|
if (input.fromBinding) result.fromBinding = input.fromBinding;
|
|
2030
2259
|
if (input.toBinding) result.toBinding = input.toBinding;
|
|
@@ -2275,19 +2504,19 @@ function loadImages(elements) {
|
|
|
2275
2504
|
const imageElements = elements.filter(
|
|
2276
2505
|
(el) => el.type === "image" && "src" in el
|
|
2277
2506
|
);
|
|
2278
|
-
const
|
|
2279
|
-
if (imageElements.length === 0) return Promise.resolve(
|
|
2507
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
2508
|
+
if (imageElements.length === 0) return Promise.resolve(cache2);
|
|
2280
2509
|
return new Promise((resolve) => {
|
|
2281
2510
|
let remaining = imageElements.length;
|
|
2282
2511
|
const done = () => {
|
|
2283
2512
|
remaining--;
|
|
2284
|
-
if (remaining <= 0) resolve(
|
|
2513
|
+
if (remaining <= 0) resolve(cache2);
|
|
2285
2514
|
};
|
|
2286
2515
|
for (const el of imageElements) {
|
|
2287
2516
|
const img = new Image();
|
|
2288
2517
|
img.crossOrigin = "anonymous";
|
|
2289
2518
|
img.onload = () => {
|
|
2290
|
-
|
|
2519
|
+
cache2.set(el.id, img);
|
|
2291
2520
|
done();
|
|
2292
2521
|
};
|
|
2293
2522
|
img.onerror = done;
|
|
@@ -2717,17 +2946,19 @@ var SAMPLE_SIZE = 60;
|
|
|
2717
2946
|
var RenderStats = class {
|
|
2718
2947
|
frameTimes = [];
|
|
2719
2948
|
frameCount = 0;
|
|
2720
|
-
|
|
2949
|
+
_lastGridMs = 0;
|
|
2950
|
+
recordFrame(durationMs, gridMs) {
|
|
2721
2951
|
this.frameCount++;
|
|
2722
2952
|
this.frameTimes.push(durationMs);
|
|
2723
2953
|
if (this.frameTimes.length > SAMPLE_SIZE) {
|
|
2724
2954
|
this.frameTimes.shift();
|
|
2725
2955
|
}
|
|
2956
|
+
if (gridMs !== void 0) this._lastGridMs = gridMs;
|
|
2726
2957
|
}
|
|
2727
2958
|
getSnapshot() {
|
|
2728
2959
|
const times = this.frameTimes;
|
|
2729
2960
|
if (times.length === 0) {
|
|
2730
|
-
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, frameCount: 0 };
|
|
2961
|
+
return { fps: 0, avgFrameMs: 0, p95FrameMs: 0, lastFrameMs: 0, lastGridMs: 0, frameCount: 0 };
|
|
2731
2962
|
}
|
|
2732
2963
|
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
2733
2964
|
const sorted = [...times].sort((a, b) => a - b);
|
|
@@ -2738,6 +2969,7 @@ var RenderStats = class {
|
|
|
2738
2969
|
avgFrameMs: Math.round(avg * 100) / 100,
|
|
2739
2970
|
p95FrameMs: Math.round((sorted[p95Index] ?? 0) * 100) / 100,
|
|
2740
2971
|
lastFrameMs: Math.round(lastFrame * 100) / 100,
|
|
2972
|
+
lastGridMs: Math.round(this._lastGridMs * 100) / 100,
|
|
2741
2973
|
frameCount: this.frameCount
|
|
2742
2974
|
};
|
|
2743
2975
|
}
|
|
@@ -2765,6 +2997,15 @@ var RenderLoop = class {
|
|
|
2765
2997
|
lastCamX;
|
|
2766
2998
|
lastCamY;
|
|
2767
2999
|
stats = new RenderStats();
|
|
3000
|
+
lastGridMs = 0;
|
|
3001
|
+
gridCacheCanvas = null;
|
|
3002
|
+
gridCacheCtx = null;
|
|
3003
|
+
gridCacheZoom = -1;
|
|
3004
|
+
gridCacheCamX = -Infinity;
|
|
3005
|
+
gridCacheCamY = -Infinity;
|
|
3006
|
+
gridCacheWidth = 0;
|
|
3007
|
+
gridCacheHeight = 0;
|
|
3008
|
+
lastGridRef = null;
|
|
2768
3009
|
constructor(deps) {
|
|
2769
3010
|
this.canvasEl = deps.canvasEl;
|
|
2770
3011
|
this.camera = deps.camera;
|
|
@@ -2827,6 +3068,29 @@ var RenderLoop = class {
|
|
|
2827
3068
|
ctx.drawImage(cached, 0, 0);
|
|
2828
3069
|
ctx.restore();
|
|
2829
3070
|
}
|
|
3071
|
+
ensureGridCache(cssWidth, cssHeight, dpr) {
|
|
3072
|
+
if (this.gridCacheCanvas !== null && this.gridCacheWidth === cssWidth && this.gridCacheHeight === cssHeight) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
const physWidth = Math.round(cssWidth * dpr);
|
|
3076
|
+
const physHeight = Math.round(cssHeight * dpr);
|
|
3077
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
3078
|
+
this.gridCacheCanvas = new OffscreenCanvas(
|
|
3079
|
+
physWidth,
|
|
3080
|
+
physHeight
|
|
3081
|
+
);
|
|
3082
|
+
} else if (typeof document !== "undefined") {
|
|
3083
|
+
const el = document.createElement("canvas");
|
|
3084
|
+
el.width = physWidth;
|
|
3085
|
+
el.height = physHeight;
|
|
3086
|
+
this.gridCacheCanvas = el;
|
|
3087
|
+
} else {
|
|
3088
|
+
this.gridCacheCanvas = null;
|
|
3089
|
+
this.gridCacheCtx = null;
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
this.gridCacheCtx = this.gridCacheCanvas.getContext("2d");
|
|
3093
|
+
}
|
|
2830
3094
|
render() {
|
|
2831
3095
|
const t0 = performance.now();
|
|
2832
3096
|
const ctx = this.canvasEl.getContext("2d");
|
|
@@ -2889,9 +3153,6 @@ var RenderLoop = class {
|
|
|
2889
3153
|
}
|
|
2890
3154
|
group.push(element);
|
|
2891
3155
|
}
|
|
2892
|
-
for (const grid of gridElements) {
|
|
2893
|
-
this.renderer.renderCanvasElement(ctx, grid);
|
|
2894
|
-
}
|
|
2895
3156
|
for (const [layerId, elements] of layerElements) {
|
|
2896
3157
|
const isActiveDrawingLayer = layerId === this.activeDrawingLayerId;
|
|
2897
3158
|
if (!this.layerCache.isDirty(layerId)) {
|
|
@@ -2920,13 +3181,53 @@ var RenderLoop = class {
|
|
|
2920
3181
|
this.compositeLayerCache(ctx, layerId, dpr);
|
|
2921
3182
|
}
|
|
2922
3183
|
}
|
|
3184
|
+
if (gridElements.length > 0) {
|
|
3185
|
+
const gridT0 = performance.now();
|
|
3186
|
+
const gridRef = gridElements[0];
|
|
3187
|
+
const gridCacheHit = this.gridCacheCanvas !== null && currentZoom === this.gridCacheZoom && currentCamX === this.gridCacheCamX && currentCamY === this.gridCacheCamY && cssWidth === this.gridCacheWidth && cssHeight === this.gridCacheHeight && gridRef === this.lastGridRef;
|
|
3188
|
+
if (gridCacheHit) {
|
|
3189
|
+
ctx.save();
|
|
3190
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3191
|
+
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
3192
|
+
ctx.restore();
|
|
3193
|
+
} else {
|
|
3194
|
+
this.ensureGridCache(cssWidth, cssHeight, dpr);
|
|
3195
|
+
if (this.gridCacheCtx && this.gridCacheCanvas) {
|
|
3196
|
+
const gc = this.gridCacheCtx;
|
|
3197
|
+
gc.clearRect(0, 0, this.gridCacheCanvas.width, this.gridCacheCanvas.height);
|
|
3198
|
+
gc.save();
|
|
3199
|
+
gc.scale(dpr, dpr);
|
|
3200
|
+
gc.translate(currentCamX, currentCamY);
|
|
3201
|
+
gc.scale(currentZoom, currentZoom);
|
|
3202
|
+
for (const grid of gridElements) {
|
|
3203
|
+
this.renderer.renderCanvasElement(gc, grid);
|
|
3204
|
+
}
|
|
3205
|
+
gc.restore();
|
|
3206
|
+
ctx.save();
|
|
3207
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3208
|
+
ctx.drawImage(this.gridCacheCanvas, 0, 0);
|
|
3209
|
+
ctx.restore();
|
|
3210
|
+
} else {
|
|
3211
|
+
for (const grid of gridElements) {
|
|
3212
|
+
this.renderer.renderCanvasElement(ctx, grid);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
this.gridCacheZoom = currentZoom;
|
|
3216
|
+
this.gridCacheCamX = currentCamX;
|
|
3217
|
+
this.gridCacheCamY = currentCamY;
|
|
3218
|
+
this.gridCacheWidth = cssWidth;
|
|
3219
|
+
this.gridCacheHeight = cssHeight;
|
|
3220
|
+
this.lastGridRef = gridRef;
|
|
3221
|
+
}
|
|
3222
|
+
this.lastGridMs = performance.now() - gridT0;
|
|
3223
|
+
}
|
|
2923
3224
|
const activeTool = this.toolManager.activeTool;
|
|
2924
3225
|
if (activeTool?.renderOverlay) {
|
|
2925
3226
|
activeTool.renderOverlay(ctx);
|
|
2926
3227
|
}
|
|
2927
3228
|
ctx.restore();
|
|
2928
3229
|
ctx.restore();
|
|
2929
|
-
this.stats.recordFrame(performance.now() - t0);
|
|
3230
|
+
this.stats.recordFrame(performance.now() - t0, this.lastGridMs);
|
|
2930
3231
|
}
|
|
2931
3232
|
};
|
|
2932
3233
|
|
|
@@ -3226,6 +3527,18 @@ var Viewport = class {
|
|
|
3226
3527
|
this.historyRecorder.commit();
|
|
3227
3528
|
this.requestRender();
|
|
3228
3529
|
}
|
|
3530
|
+
getRenderStats() {
|
|
3531
|
+
return this.renderLoop.getStats();
|
|
3532
|
+
}
|
|
3533
|
+
logPerformance(intervalMs = 2e3) {
|
|
3534
|
+
const id = setInterval(() => {
|
|
3535
|
+
const s = this.getRenderStats();
|
|
3536
|
+
console.log(
|
|
3537
|
+
`[FieldNotes] fps=${s.fps} frame=${s.avgFrameMs}ms p95=${s.p95FrameMs}ms grid=${s.lastGridMs}ms`
|
|
3538
|
+
);
|
|
3539
|
+
}, intervalMs);
|
|
3540
|
+
return () => clearInterval(id);
|
|
3541
|
+
}
|
|
3229
3542
|
destroy() {
|
|
3230
3543
|
this.renderLoop.stop();
|
|
3231
3544
|
this.interactMode.destroy();
|
|
@@ -3535,6 +3848,7 @@ var PencilTool = class {
|
|
|
3535
3848
|
layerId: ctx.activeLayerId ?? ""
|
|
3536
3849
|
});
|
|
3537
3850
|
ctx.store.add(stroke);
|
|
3851
|
+
computeStrokeSegments(stroke);
|
|
3538
3852
|
this.points = [];
|
|
3539
3853
|
ctx.requestRender();
|
|
3540
3854
|
}
|
|
@@ -4609,7 +4923,7 @@ var UpdateLayerCommand = class {
|
|
|
4609
4923
|
};
|
|
4610
4924
|
|
|
4611
4925
|
// src/index.ts
|
|
4612
|
-
var VERSION = "0.8.
|
|
4926
|
+
var VERSION = "0.8.8";
|
|
4613
4927
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4614
4928
|
0 && (module.exports = {
|
|
4615
4929
|
AddElementCommand,
|