@reekon-tools/boldr-utils 1.15.4 → 1.15.5
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/annotation/canvas/AnnotationCanvasInner.native.js +134 -11
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +4 -1
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +3 -3
- package/dist/annotation/canvas/elements/PlaneElement.d.ts +3 -0
- package/dist/annotation/canvas/elements/PlaneElement.js +5 -5
- package/dist/annotation/canvas/measurementGeometry.js +3 -0
- package/dist/annotation/canvas/planeGeometry.js +21 -10
- package/package.json +1 -1
|
@@ -7,9 +7,10 @@ import Animated, { runOnJS, runOnUI, useAnimatedStyle, useDerivedValue, useShare
|
|
|
7
7
|
import { stampTileDims } from './stampLayout.js';
|
|
8
8
|
import { DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
9
9
|
import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
|
|
10
|
-
import { backgroundLayersOf } from './backgroundLayers.js';
|
|
10
|
+
import { backgroundLayersOf, backgroundLayerDocRect, } from './backgroundLayers.js';
|
|
11
11
|
import { BackgroundSkImageLoader } from './elements/BackgroundImageElement.js';
|
|
12
|
-
import { buildRemoveMeasurementOps, } from './measurementGeometry.js';
|
|
12
|
+
import { buildRemoveMeasurementOps, linePosOf, } from './measurementGeometry.js';
|
|
13
|
+
import { planeEdgeLine, planeGridSegments, planeGridSegmentsForScale, resolvePlaneGridSpacingUm, } from './planeGeometry.js';
|
|
13
14
|
import { buildShapeFromDrag } from './tools/shapeTool.js';
|
|
14
15
|
import { SELECTION_PAD } from './textGeometry.js';
|
|
15
16
|
import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
|
|
@@ -481,10 +482,13 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
481
482
|
// The plane tool's JS preview path re-rendered the whole canvas (and
|
|
482
483
|
// recomputed the photo-spanning grid) per pointer-move, dropping JS frames
|
|
483
484
|
// on device. Instead the drag runs like the select tool's: the gesture
|
|
484
|
-
// begin hit-tests once on JS, the translation lives in shared values
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
485
|
+
// begin hit-tests once on JS, the translation lives in shared values, and
|
|
486
|
+
// the drop commits exactly once. Everything the drag visually moves is
|
|
487
|
+
// rebuilt per frame on the UI thread: the outline (worklet twin of
|
|
488
|
+
// PlaneElement's path), the grid (planeGeometry's generators are worklets),
|
|
489
|
+
// and the dimension-tile chips (a plane branch in the RN overlay's animated
|
|
490
|
+
// style, driven by planeTileCtx). Only the tiles' computed VALUES wait for
|
|
491
|
+
// the commit — they're user-entered dims, which a corner drag can't change.
|
|
488
492
|
// planeDragMode: 0 idle · 1 handle drag (UI thread) · 2 everything else
|
|
489
493
|
// (marking / blocked-creation), which streams JS pointer events like
|
|
490
494
|
// toolPan would have.
|
|
@@ -541,6 +545,67 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
541
545
|
path.lineTo(b.x, b.y);
|
|
542
546
|
return path;
|
|
543
547
|
});
|
|
548
|
+
// Live grid during the drag. Spacing and bounds are start-of-drag constants
|
|
549
|
+
// computed on JS (the dims a corner drag can't change decide the spacing;
|
|
550
|
+
// the photo rect decides the bounds — both mirror PlaneElement's inputs),
|
|
551
|
+
// captured into the derived value; the segments themselves recompute per
|
|
552
|
+
// frame on the UI thread from the live corner — planeGeometry's generators
|
|
553
|
+
// carry 'worklet' directives for exactly this call site. Scale mode's doc
|
|
554
|
+
// spacing tracks the live reference line, which is correct: stretching the
|
|
555
|
+
// line IS changing the calibration's ratio.
|
|
556
|
+
const planeDragGridBounds = useMemo(() => {
|
|
557
|
+
const first = backgroundLayers[0];
|
|
558
|
+
return first
|
|
559
|
+
? backgroundLayerDocRect(first)
|
|
560
|
+
: {
|
|
561
|
+
x: 0,
|
|
562
|
+
y: 0,
|
|
563
|
+
width: state.effectiveCanvas.viewport.width,
|
|
564
|
+
height: state.effectiveCanvas.viewport.height,
|
|
565
|
+
};
|
|
566
|
+
}, [backgroundLayers, state.effectiveCanvas.viewport]);
|
|
567
|
+
const planeDragGridSpacing = planeDragPlane && planeDragPlane.gridVisible !== false
|
|
568
|
+
? resolvePlaneGridSpacingUm(planeDragPlane, 'metric', {
|
|
569
|
+
width: planeDragGridBounds.width,
|
|
570
|
+
height: planeDragGridBounds.height,
|
|
571
|
+
})
|
|
572
|
+
: null;
|
|
573
|
+
const planeDragGridPath = useDerivedValue(() => {
|
|
574
|
+
'worklet';
|
|
575
|
+
const path = Skia.Path.Make();
|
|
576
|
+
if (!planeDragPlane ||
|
|
577
|
+
planeDragGridSpacing == null ||
|
|
578
|
+
planeDragMode.value !== 1) {
|
|
579
|
+
return path;
|
|
580
|
+
}
|
|
581
|
+
const lx = planeDragStartX.value + planeDragTX.value / zoom.value;
|
|
582
|
+
const ly = planeDragStartY.value + planeDragTY.value / zoom.value;
|
|
583
|
+
let segments;
|
|
584
|
+
if (planeDragPlane.mode === 'perspective') {
|
|
585
|
+
const c = planeDragPlane.corners;
|
|
586
|
+
const live = [0, 1, 2, 3].map((i) => i === planeDragIndexVal ? { x: lx, y: ly } : c[i]);
|
|
587
|
+
// A mid-drag non-convex quad yields a null homography → empty grid for
|
|
588
|
+
// that frame; the outline keeps tracking and the drop snaps back.
|
|
589
|
+
segments = planeGridSegments({ ...planeDragPlane, corners: live }, planeDragGridSpacing, planeDragGridBounds);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
const a = planeDragIndexVal === 0 ? { x: lx, y: ly } : planeDragPlane.refLine.a;
|
|
593
|
+
const b = planeDragIndexVal === 1 ? { x: lx, y: ly } : planeDragPlane.refLine.b;
|
|
594
|
+
segments = planeGridSegmentsForScale({ ...planeDragPlane, refLine: { a, b } }, planeDragGridSpacing, planeDragGridBounds);
|
|
595
|
+
}
|
|
596
|
+
for (const s of segments) {
|
|
597
|
+
path.moveTo(s.a.x, s.a.y);
|
|
598
|
+
path.lineTo(s.b.x, s.b.y);
|
|
599
|
+
}
|
|
600
|
+
return path;
|
|
601
|
+
});
|
|
602
|
+
// Per-tile follow data for the dragged plane's dimension chips, written
|
|
603
|
+
// once at drag begin (see beginPlaneDrag): the committed edge endpoints,
|
|
604
|
+
// whether each end follows the dragged handle, and the tile's linePos.
|
|
605
|
+
// The overlay's animated style lerps along the live edge at that same
|
|
606
|
+
// linePos — the exact anchor syncDimensionTileOps recomputes on commit,
|
|
607
|
+
// so the chip lands where it already is.
|
|
608
|
+
const planeTileCtx = useSharedValue({});
|
|
544
609
|
// Rectangle-annotation corner drag. `rectDragId` (React state) marks which
|
|
545
610
|
// annotation's rect renders from the live geometry; `rectCtx` carries the
|
|
546
611
|
// fixed (opposite) corner and the grabbed corner's start position so the
|
|
@@ -1568,6 +1633,32 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1568
1633
|
if (start) {
|
|
1569
1634
|
planeDragStartX.value = start.x;
|
|
1570
1635
|
planeDragStartY.value = start.y;
|
|
1636
|
+
// Follow data for the plane's dimension chips: each tile's edge
|
|
1637
|
+
// (planeEdgeLine — the committed geometry its line mirrors), which
|
|
1638
|
+
// endpoint(s) track the grabbed handle (coordinate equality with
|
|
1639
|
+
// the handle's start — both values come from the same committed
|
|
1640
|
+
// plane, so exact comparison is sound), and its linePos.
|
|
1641
|
+
const plane = st.ctx.document.planes?.find((p) => p.id === planeId);
|
|
1642
|
+
const follow = {};
|
|
1643
|
+
if (plane) {
|
|
1644
|
+
for (const m of st.ctx.document.placedMeasurements) {
|
|
1645
|
+
if (m.planeId !== planeId || !m.planeEdge)
|
|
1646
|
+
continue;
|
|
1647
|
+
const edge = planeEdgeLine(plane, m.planeEdge);
|
|
1648
|
+
if (!edge)
|
|
1649
|
+
continue;
|
|
1650
|
+
follow[m.id] = {
|
|
1651
|
+
ax: edge.a.x,
|
|
1652
|
+
ay: edge.a.y,
|
|
1653
|
+
bx: edge.b.x,
|
|
1654
|
+
by: edge.b.y,
|
|
1655
|
+
ma: edge.a.x === start.x && edge.a.y === start.y ? 1 : 0,
|
|
1656
|
+
mb: edge.b.x === start.x && edge.b.y === start.y ? 1 : 0,
|
|
1657
|
+
t: linePosOf(m),
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
planeTileCtx.value = follow;
|
|
1571
1662
|
planeDragMode.value = 1;
|
|
1572
1663
|
const drag = { planeId, index };
|
|
1573
1664
|
planeDragRef.current = drag;
|
|
@@ -1588,7 +1679,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1588
1679
|
if (f)
|
|
1589
1680
|
stateRef.current.dispatchPointerMove(buildEvent(f.id, screen));
|
|
1590
1681
|
};
|
|
1591
|
-
const endPlaneDrag = (screen) => {
|
|
1682
|
+
const endPlaneDrag = (screen, translation) => {
|
|
1592
1683
|
const st = stateRef.current;
|
|
1593
1684
|
if (planeDragMode.value === 2) {
|
|
1594
1685
|
const f = planeJsPointerRef.current;
|
|
@@ -1596,7 +1687,17 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1596
1687
|
st.dispatchPointerUp(buildEvent(f.id, screen));
|
|
1597
1688
|
}
|
|
1598
1689
|
else if (planeDragMode.value === 1 && planeDragRef.current) {
|
|
1599
|
-
|
|
1690
|
+
// Commit the position the live outline showed: handle start + the
|
|
1691
|
+
// finger's translation. NOT screenToWorld(finger) — the hit-test
|
|
1692
|
+
// accepts a grab anywhere inside the handle's radius, so the
|
|
1693
|
+
// finger's absolute position is off the handle by that grab offset,
|
|
1694
|
+
// and committing it made the corner visibly shift on release.
|
|
1695
|
+
// Zoom can't change mid-drag (this pan won the gesture race, so the
|
|
1696
|
+
// pinch never activated), so reading it at drop matches every frame.
|
|
1697
|
+
const world = {
|
|
1698
|
+
x: planeDragStartX.value + translation.x / zoom.value,
|
|
1699
|
+
y: planeDragStartY.value + translation.y / zoom.value,
|
|
1700
|
+
};
|
|
1600
1701
|
const patch = cfg.buildHandleDropPatch(st.ctx.document, planeDragRef.current.index, world);
|
|
1601
1702
|
// Null = invalid drop (non-convex) — clearing the drag state below
|
|
1602
1703
|
// snaps the outline back to the committed plane.
|
|
@@ -1650,7 +1751,10 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1650
1751
|
.onEnd((e) => {
|
|
1651
1752
|
'worklet';
|
|
1652
1753
|
planeDragEnded.value = true;
|
|
1653
|
-
|
|
1754
|
+
// The final translation travels as an argument rather than being
|
|
1755
|
+
// read back from the shared values on JS: the last onChange isn't
|
|
1756
|
+
// guaranteed to have carried it, and onEnd doesn't write them.
|
|
1757
|
+
runOnJS(endPlaneDrag)({ x: e.x, y: e.y }, { x: e.translationX, y: e.translationY });
|
|
1654
1758
|
})
|
|
1655
1759
|
.onFinalize(() => {
|
|
1656
1760
|
'worklet';
|
|
@@ -1701,6 +1805,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1701
1805
|
planeDragTY,
|
|
1702
1806
|
planeDragStartX,
|
|
1703
1807
|
planeDragStartY,
|
|
1808
|
+
planeTileCtx,
|
|
1704
1809
|
longPressEnabled,
|
|
1705
1810
|
dragX,
|
|
1706
1811
|
dragY,
|
|
@@ -1799,6 +1904,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1799
1904
|
planeDragId: planeDrag?.planeId ?? null,
|
|
1800
1905
|
planeDragIndex: planeDrag?.index ?? null,
|
|
1801
1906
|
planeDragPath,
|
|
1907
|
+
planeDragGridPath,
|
|
1802
1908
|
planeDragHandle,
|
|
1803
1909
|
customPreview,
|
|
1804
1910
|
};
|
|
@@ -1815,7 +1921,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1815
1921
|
// non-fullscreen canvas (e.g. a diagram strip) escape into surrounding UI.
|
|
1816
1922
|
_jsxs(GestureHandlerRootView, { style: [{ width, height, overflow: 'hidden' }, style], children: [backgroundLayers.map((layer) => (_jsx(BackgroundSkImageLoader, { layer: layer, resolveUrl: resolveImageUrl, onImage: onBackgroundSkImage }, layer.id))), _jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: { width, height }, collapsable: false, children: AnnotationCanvasSkia(skiaProps) }) }), renderMeasurementStamp && (_jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: state.effectiveCanvas.placedMeasurements.map((placed) => (_jsx(MeasurementStampOverlayItem, { placed: placed, measurement: placed.measurementId
|
|
1817
1923
|
? (state.measurementsById.get(placed.measurementId) ?? null)
|
|
1818
|
-
: (resolveStampMeasurement?.(placed) ?? null), selected: selection?.ids.includes(placed.id) ?? false, dragging: draggingId === placed.id, sliding: slidingId === placed.id, endpointDragging: epDragId === placed.id, rectResizing: rectDragId === placed.id, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, rectCtx: rectCtx, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, fileTileScaleFactor: state.fileTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
1924
|
+
: (resolveStampMeasurement?.(placed) ?? null), selected: selection?.ids.includes(placed.id) ?? false, dragging: draggingId === placed.id, sliding: slidingId === placed.id, endpointDragging: epDragId === placed.id, rectResizing: rectDragId === placed.id, planeEdgeDragging: planeDrag != null && placed.planeId === planeDrag.planeId, zoomSnapshot: state.viewport.zoom, zoom: zoom, panX: panX, panY: panY, dragX: dragX, dragY: dragY, slideCtx: slideCtx, epCtx: epCtx, rectCtx: rectCtx, planeTileCtx: planeTileCtx, planeDragTX: planeDragTX, planeDragTY: planeDragTY, renderMeasurementStamp: renderMeasurementStamp, tileScaleFactor: state.tileScaleFactor, headerTileScaleFactor: state.headerTileScaleFactor, fileTileScaleFactor: state.fileTileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: onMeasurementStampRemove
|
|
1819
1925
|
? () => {
|
|
1820
1926
|
const defaultRemove = () => {
|
|
1821
1927
|
const { ops, keepSelection } = buildRemoveMeasurementOps(placed);
|
|
@@ -1831,7 +1937,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1831
1937
|
wrapContent: loupeWrap,
|
|
1832
1938
|
}) }))] }));
|
|
1833
1939
|
};
|
|
1834
|
-
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, renderMeasurementStamp, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
1940
|
+
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, planeEdgeDragging, planeTileCtx, planeDragTX, planeDragTY, renderMeasurementStamp, tileScaleFactor, headerTileScaleFactor, fileTileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
1835
1941
|
// Square tile or wide group-header pill — one footprint source of truth
|
|
1836
1942
|
// shared with the hit-test (stampTileDims).
|
|
1837
1943
|
const { width, height } = stampTileDims(placed, tileScaleFactor, tileViewportScale, headerTileScaleFactor ?? tileScaleFactor, fileTileScaleFactor ?? tileScaleFactor);
|
|
@@ -1845,6 +1951,7 @@ const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging,
|
|
|
1845
1951
|
const removeSize = Math.min(36, Math.min(width, height) * 0.4);
|
|
1846
1952
|
const anchorX = placed.anchor.x;
|
|
1847
1953
|
const anchorY = placed.anchor.y;
|
|
1954
|
+
const placedId = placed.id;
|
|
1848
1955
|
// doc → screen each frame, on the UI thread. Position is a translate
|
|
1849
1956
|
// transform (cheap, no layout) so the tile stays glued to its anchor. While
|
|
1850
1957
|
// dragging, the world-space drag offset is folded in; while sliding, the
|
|
@@ -1923,6 +2030,22 @@ const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging,
|
|
|
1923
2030
|
worldX = (c.fx + c.mx + dragX.value) / 2;
|
|
1924
2031
|
worldY = (c.fy + c.my + dragY.value) / 2;
|
|
1925
2032
|
}
|
|
2033
|
+
else if (planeEdgeDragging) {
|
|
2034
|
+
// Tile stays at its linePos along the LIVE plane edge. The plane drag's
|
|
2035
|
+
// translation is screen px (unlike dragX/dragY's world units), so it
|
|
2036
|
+
// converts through the live zoom, same as the outline twin.
|
|
2037
|
+
const c = planeTileCtx.value[placedId];
|
|
2038
|
+
if (c) {
|
|
2039
|
+
const dx = planeDragTX.value / zoom.value;
|
|
2040
|
+
const dy = planeDragTY.value / zoom.value;
|
|
2041
|
+
const ax = c.ma === 1 ? c.ax + dx : c.ax;
|
|
2042
|
+
const ay = c.ma === 1 ? c.ay + dy : c.ay;
|
|
2043
|
+
const bx = c.mb === 1 ? c.bx + dx : c.bx;
|
|
2044
|
+
const by = c.mb === 1 ? c.by + dy : c.by;
|
|
2045
|
+
worldX = ax + (bx - ax) * c.t;
|
|
2046
|
+
worldY = ay + (by - ay) * c.t;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
1926
2049
|
else if (dragging) {
|
|
1927
2050
|
worldX = anchorX + dragX.value;
|
|
1928
2051
|
worldY = anchorY + dragY.value;
|
|
@@ -94,10 +94,13 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
94
94
|
planeDragPath?: SkPath | {
|
|
95
95
|
value: SkPath;
|
|
96
96
|
};
|
|
97
|
+
planeDragGridPath?: SkPath | {
|
|
98
|
+
value: SkPath;
|
|
99
|
+
};
|
|
97
100
|
planeDragHandle?: AnimatedPoint;
|
|
98
101
|
customPreview?: ReactNode;
|
|
99
102
|
wrapContent?: (content: ReactNode) => ReactNode;
|
|
100
103
|
canvasRef?: RefObject<CanvasRef | null>;
|
|
101
104
|
}
|
|
102
|
-
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragHandle, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
105
|
+
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
103
106
|
export {};
|
|
@@ -6,7 +6,7 @@ import { SELECTION_PAD, textResizeGeometry, textShapeBounds, } from './textGeome
|
|
|
6
6
|
import { visualShapeBounds } from './shapeGeometry.js';
|
|
7
7
|
import { backgroundLayerDocRect, backgroundLayersOf, } from './backgroundLayers.js';
|
|
8
8
|
import { BackgroundLayerElement } from './elements/BackgroundImageElement.js';
|
|
9
|
-
import { PlaneElement, PLANE_CHROME_COLOR, PLANE_OUTLINE_WIDTH, } from './elements/PlaneElement.js';
|
|
9
|
+
import { PlaneElement, PLANE_CHROME_COLOR, PLANE_GRID_COLOR, PLANE_GRID_OPACITY, PLANE_GRID_WIDTH, PLANE_OUTLINE_WIDTH, } from './elements/PlaneElement.js';
|
|
10
10
|
import { ShapeElement } from './elements/ShapeElement.js';
|
|
11
11
|
import { StrokeElement } from './elements/StrokeElement.js';
|
|
12
12
|
// Default visual constants for the measurement-annotation line (the tile itself
|
|
@@ -80,7 +80,7 @@ const SelectionBox = ({ bounds, isDragging, transform, }) => (_jsx(DraggableElem
|
|
|
80
80
|
// since the function-call pattern works identically on native we use it
|
|
81
81
|
// in both Inners for consistency. Don't add hooks here; this is a plain
|
|
82
82
|
// JSX-returning helper, not a component.
|
|
83
|
-
export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragHandle, customPreview, wrapContent, canvasRef, }) => {
|
|
83
|
+
export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, customPreview, wrapContent, canvasRef, }) => {
|
|
84
84
|
// Doc rect a SCALE-mode plane grid covers: the first background layer (the
|
|
85
85
|
// photo being calibrated), else the document rect. Pinned to layer 0 like
|
|
86
86
|
// the tile-sizing reference — adding a second image must not re-span the
|
|
@@ -99,7 +99,7 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
|
|
|
99
99
|
const statics = plane.mode === 'perspective'
|
|
100
100
|
? plane.corners
|
|
101
101
|
: [plane.refLine.a, plane.refLine.b];
|
|
102
|
-
return (_jsxs(Group, { children: [_jsx(Path, { path: planeDragPath, color: PLANE_CHROME_COLOR, style: "stroke", strokeWidth: PLANE_OUTLINE_WIDTH, strokeJoin: "round" }), handleRadius != null &&
|
|
102
|
+
return (_jsxs(Group, { children: [planeDragGridPath && (_jsx(Path, { path: planeDragGridPath, color: PLANE_GRID_COLOR, style: "stroke", strokeWidth: PLANE_GRID_WIDTH, opacity: PLANE_GRID_OPACITY })), _jsx(Path, { path: planeDragPath, color: PLANE_CHROME_COLOR, style: "stroke", strokeWidth: PLANE_OUTLINE_WIDTH, strokeJoin: "round" }), handleRadius != null &&
|
|
103
103
|
statics.map((p, i) => i === planeDragIndex ? null : (_jsxs(Group, { children: [_jsx(Circle, { c: p, r: handleRadius, color: PLANE_CHROME_COLOR }), _jsx(Circle, { c: p, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }, i))), handleRadius != null && planeDragHandle && (_jsxs(_Fragment, { children: [_jsx(Circle, { c: planeDragHandle, r: handleRadius, color: PLANE_CHROME_COLOR }), _jsx(Circle, { c: planeDragHandle, r: handleRadius, color: "#FFFFFF", style: "stroke", strokeWidth: handleRingWidth })] }))] }, plane.id));
|
|
104
104
|
}
|
|
105
105
|
return (_jsx(PlaneElement, { plane: plane, scaleBounds: planeScaleBounds, showHandles: showPlaneHandles === true, handleRadius: showPlaneHandles ? handleRadius : undefined, handleRingWidth: showPlaneHandles ? handleRingWidth : undefined }, plane.id));
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { PlaneCalibration } from '../../../types/annotation.js';
|
|
2
2
|
export declare const PLANE_CHROME_COLOR: "#BD30A8";
|
|
3
3
|
export declare const PLANE_OUTLINE_WIDTH = 14;
|
|
4
|
+
export declare const PLANE_GRID_COLOR = "#FFD1F8";
|
|
5
|
+
export declare const PLANE_GRID_OPACITY = 0.9;
|
|
6
|
+
export declare const PLANE_GRID_WIDTH = 2.5;
|
|
4
7
|
type AnimatedNumber = number | {
|
|
5
8
|
value: number;
|
|
6
9
|
};
|
|
@@ -8,15 +8,15 @@ import { FormulaColors } from '../../../theme/colors.js';
|
|
|
8
8
|
// information: the formula purple outlines the calibrated quad / reference
|
|
9
9
|
// line (and inks its handles and dimension-tile lines — see planeTool), while
|
|
10
10
|
// the grid it casts gets its own pale pink so the two never blur.
|
|
11
|
-
// Exported for the live UI-thread handle drag, which redraws the outline
|
|
12
|
-
// AnnotationCanvasSkia while this element is hidden.
|
|
11
|
+
// Exported for the live UI-thread handle drag, which redraws the outline and
|
|
12
|
+
// grid in AnnotationCanvasSkia while this element is hidden.
|
|
13
13
|
export const PLANE_CHROME_COLOR = FormulaColors.purple;
|
|
14
14
|
export const PLANE_OUTLINE_WIDTH = 14;
|
|
15
15
|
const PLANE_COLOR = PLANE_CHROME_COLOR;
|
|
16
|
-
const PLANE_GRID_COLOR = '#FFD1F8';
|
|
17
|
-
const PLANE_GRID_OPACITY = 0.9;
|
|
16
|
+
export const PLANE_GRID_COLOR = '#FFD1F8';
|
|
17
|
+
export const PLANE_GRID_OPACITY = 0.9;
|
|
18
18
|
// Doc-space stroke widths (scale with zoom, like the drawn shapes).
|
|
19
|
-
const PLANE_GRID_WIDTH = 2.5;
|
|
19
|
+
export const PLANE_GRID_WIDTH = 2.5;
|
|
20
20
|
// Cross-tick half-length (doc units) marking the ends of a scale-mode
|
|
21
21
|
// reference line, so it reads as "a measured span", not just a line.
|
|
22
22
|
const REF_TICK_LEN = 18;
|
|
@@ -44,7 +44,10 @@ export const projectToLinePos = (line, world) => {
|
|
|
44
44
|
export const snapLinePos = (t, threshold = 0.06) => Math.abs(t - 0.5) <= threshold ? 0.5 : t;
|
|
45
45
|
// Euclidean length of a line in doc space (used to convert a screen-px snap
|
|
46
46
|
// radius into a t-space threshold).
|
|
47
|
+
// 'worklet': called from planeGeometry's grid generators, which run per-frame
|
|
48
|
+
// on the UI thread during a plane handle drag. Inert everywhere else.
|
|
47
49
|
export const lineLength = (line) => {
|
|
50
|
+
'worklet';
|
|
48
51
|
const dx = line.b.x - line.a.x;
|
|
49
52
|
const dy = line.b.y - line.a.y;
|
|
50
53
|
return Math.sqrt(dx * dx + dy * dy);
|
|
@@ -7,15 +7,17 @@
|
|
|
7
7
|
// SIZING (stampLayout.ts). Everything here is plane calibration — mapping
|
|
8
8
|
// DOC-space geometry to physical lengths in micrometers.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// The homography/grid chain carries 'worklet' directives (inert everywhere
|
|
11
|
+
// but a Reanimated build): AnnotationCanvasInner.native.tsx rebuilds the grid
|
|
12
|
+
// per frame on the UI thread during a plane handle drag, calling these
|
|
13
|
+
// directly rather than maintaining an inline twin of ~200 lines of math.
|
|
14
|
+
// Values (planeDistanceUm etc.) still recompute on commit, JS-side.
|
|
14
15
|
import { lineLength } from './measurementGeometry.js';
|
|
15
16
|
// Degeneracy guards are scaled to the quad's own extent — doc coordinates are
|
|
16
17
|
// arbitrary (a photo canvas is typically ~1000 units across), so a fixed
|
|
17
18
|
// epsilon would misfire on very small or very large documents.
|
|
18
19
|
const relativeEps = (corners) => {
|
|
20
|
+
'worklet';
|
|
19
21
|
let span = 1;
|
|
20
22
|
for (const p of corners) {
|
|
21
23
|
span = Math.max(span, Math.abs(p.x), Math.abs(p.y));
|
|
@@ -27,6 +29,7 @@ const relativeEps = (corners) => {
|
|
|
27
29
|
// (collinear/coincident corners). Corners must already be in TL,TR,BR,BL order
|
|
28
30
|
// (see normalizeQuadOrder).
|
|
29
31
|
export const homographyFromQuad = (corners) => {
|
|
32
|
+
'worklet';
|
|
30
33
|
const [p0, p1, p2, p3] = corners;
|
|
31
34
|
const eps = relativeEps(corners);
|
|
32
35
|
const sx = p0.x - p1.x + p2.x - p3.x;
|
|
@@ -71,6 +74,7 @@ export const homographyFromQuad = (corners) => {
|
|
|
71
74
|
// Returns null when that entry (a·e − b·d) is ~zero — the inverse would put
|
|
72
75
|
// the whole doc plane on the vanishing line.
|
|
73
76
|
export const invertHomography = (m) => {
|
|
77
|
+
'worklet';
|
|
74
78
|
const norm = m.a * m.e - m.b * m.d;
|
|
75
79
|
if (Math.abs(norm) < 1e-12)
|
|
76
80
|
return null;
|
|
@@ -88,6 +92,7 @@ export const invertHomography = (m) => {
|
|
|
88
92
|
// Apply a homography to a point. Null when the point sits (numerically) on the
|
|
89
93
|
// vanishing line — the plane there is at infinity.
|
|
90
94
|
export const applyHomography = (m, p) => {
|
|
95
|
+
'worklet';
|
|
91
96
|
const w = m.g * p.x + m.h * p.y + 1;
|
|
92
97
|
if (Math.abs(w) < 1e-12)
|
|
93
98
|
return null;
|
|
@@ -148,12 +153,15 @@ export const normalizeQuadOrder = (pts) => {
|
|
|
148
153
|
// The dimensions a calibration has been given so far. Null until the user has
|
|
149
154
|
// entered them all (through the plane's dimension tiles) — an incomplete
|
|
150
155
|
// calibration renders its geometry but computes nothing.
|
|
151
|
-
const perspectiveDims = (cal) =>
|
|
152
|
-
|
|
153
|
-
cal.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
const perspectiveDims = (cal) => {
|
|
157
|
+
'worklet';
|
|
158
|
+
return cal.widthUm != null &&
|
|
159
|
+
cal.widthUm > 0 &&
|
|
160
|
+
cal.heightUm != null &&
|
|
161
|
+
cal.heightUm > 0
|
|
162
|
+
? { widthUm: cal.widthUm, heightUm: cal.heightUm }
|
|
163
|
+
: null;
|
|
164
|
+
};
|
|
157
165
|
// Doc-space point → plane coordinates in micrometers (x across the rectangle's
|
|
158
166
|
// width, y down its height; the TL corner is the origin). Null when the
|
|
159
167
|
// calibration's quad is degenerate, its dimensions aren't entered yet, or the
|
|
@@ -333,6 +341,7 @@ export const resolvePlaneGridSpacingUm = (cal, system, scaleBounds) => {
|
|
|
333
341
|
const GRID_EXTENT_CAP = 12;
|
|
334
342
|
// Liang–Barsky segment/rect clip, DOC space. Null when fully outside.
|
|
335
343
|
const clipSegmentToRect = (a, b, rect) => {
|
|
344
|
+
'worklet';
|
|
336
345
|
const dx = b.x - a.x;
|
|
337
346
|
const dy = b.y - a.y;
|
|
338
347
|
let t0 = 0;
|
|
@@ -385,6 +394,7 @@ const clipSegmentToRect = (a, b, rect) => {
|
|
|
385
394
|
// far side of the horizon are dropped (w stays positive across a segment
|
|
386
395
|
// only when it's positive at both ends — w is affine on the plane).
|
|
387
396
|
export const planeGridSegments = (cal, spacingUm, bounds) => {
|
|
397
|
+
'worklet';
|
|
388
398
|
const dims = perspectiveDims(cal);
|
|
389
399
|
const h = homographyFromQuad(cal.corners);
|
|
390
400
|
if (!dims ||
|
|
@@ -476,6 +486,7 @@ export const planeGridSegments = (cal, spacingUm, bounds) => {
|
|
|
476
486
|
// rect, or the document rect when there's no background), DOC space. Lines
|
|
477
487
|
// anchor at the bounds origin.
|
|
478
488
|
export const planeGridSegmentsForScale = (cal, spacingUm, bounds) => {
|
|
489
|
+
'worklet';
|
|
479
490
|
const refDoc = lineLength(cal.refLine);
|
|
480
491
|
const lengthUm = cal.lengthUm ?? 0;
|
|
481
492
|
if (!(refDoc > 0) ||
|