@reekon-tools/boldr-utils 1.6.34 → 1.6.35
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/stampLayout.d.ts +2 -0
- package/dist/annotation/canvas/stampLayout.js +18 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +2 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +42 -5
- package/dist/canvas/AnnotationCanvas.d.ts +11 -0
- package/dist/canvas/AnnotationCanvas.js +10 -0
- package/dist/canvas/AnnotationCanvas.native.d.ts +8 -0
- package/dist/canvas/AnnotationCanvas.native.js +6 -0
- package/dist/canvas/AnnotationCanvasInner.d.ts +39 -0
- package/dist/canvas/AnnotationCanvasInner.js +219 -0
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +35 -0
- package/dist/canvas/AnnotationCanvasInner.native.js +138 -0
- package/dist/canvas/AnnotationCanvasSkia.d.ts +27 -0
- package/dist/canvas/AnnotationCanvasSkia.js +20 -0
- package/dist/canvas/Tool.d.ts +38 -0
- package/dist/canvas/Tool.js +1 -0
- package/dist/canvas/elements/BackgroundImageElement.d.ts +9 -0
- package/dist/canvas/elements/BackgroundImageElement.js +37 -0
- package/dist/canvas/elements/MeasurementStampElement.d.ts +13 -0
- package/dist/canvas/elements/MeasurementStampElement.js +30 -0
- package/dist/canvas/elements/ShapeElement.d.ts +7 -0
- package/dist/canvas/elements/ShapeElement.js +62 -0
- package/dist/canvas/elements/StrokeElement.d.ts +7 -0
- package/dist/canvas/elements/StrokeElement.js +18 -0
- package/dist/canvas/measurementPicker.d.ts +10 -0
- package/dist/canvas/measurementPicker.js +1 -0
- package/dist/canvas/measurementStampOverlay.d.ts +11 -0
- package/dist/canvas/measurementStampOverlay.js +1 -0
- package/dist/canvas/pointerAdapter.d.ts +3 -0
- package/dist/canvas/pointerAdapter.js +19 -0
- package/dist/canvas/stampLayout.d.ts +5 -0
- package/dist/canvas/stampLayout.js +14 -0
- package/dist/canvas/tools/measurementStampTool.d.ts +9 -0
- package/dist/canvas/tools/measurementStampTool.js +37 -0
- package/dist/canvas/tools/panTool.d.ts +5 -0
- package/dist/canvas/tools/panTool.js +25 -0
- package/dist/canvas/tools/penTool.d.ts +13 -0
- package/dist/canvas/tools/penTool.js +68 -0
- package/dist/canvas/tools/selectTool.d.ts +2 -0
- package/dist/canvas/tools/selectTool.js +182 -0
- package/dist/canvas/useAnnotationCanvasState.d.ts +54 -0
- package/dist/canvas/useAnnotationCanvasState.js +210 -0
- package/dist/canvas/viewport.d.ts +16 -0
- package/dist/canvas/viewport.js +54 -0
- package/dist/data/AnnotationDataContext.d.ts +8 -0
- package/dist/data/AnnotationDataContext.js +11 -0
- package/dist/data/AnnotationDataProvider.d.ts +65 -0
- package/dist/data/AnnotationDataProvider.js +4 -0
- package/dist/data/InMemoryAnnotationProvider.d.ts +30 -0
- package/dist/data/InMemoryAnnotationProvider.js +197 -0
- package/dist/data/canvasPersistence.d.ts +3 -0
- package/dist/data/canvasPersistence.js +26 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +33 -0
- package/dist/data/hooks/useAnnotationCanvasDoc.js +314 -0
- package/dist/data/hooks/useAnnotationDoc.d.ts +7 -0
- package/dist/data/hooks/useAnnotationDoc.js +33 -0
- package/dist/data/hooks/useAnnotationList.d.ts +7 -0
- package/dist/data/hooks/useAnnotationList.js +26 -0
- package/dist/data/hooks/useAnnotationMutations.d.ts +9 -0
- package/dist/data/hooks/useAnnotationMutations.js +11 -0
- package/dist/exports.d.ts +1 -1
- package/dist/exports.js +1 -1
- package/dist/hooks/useParseMeasurement.d.ts +4 -0
- package/dist/hooks/useParseMeasurement.js +14 -0
- package/dist/types/annotation.d.ts +4 -0
- package/dist/types/annotation.js +12 -0
- package/dist/utils/evaluateFormula.d.ts +20 -0
- package/dist/utils/evaluateFormula.js +31 -0
- package/package.json +2 -1
- package/dist/annotation/canvas/tools/measurementLineTool.d.ts +0 -12
- package/dist/annotation/canvas/tools/measurementLineTool.js +0 -95
|
@@ -5,5 +5,7 @@ export declare const TILE_SCALE_MIN = 0.4;
|
|
|
5
5
|
export declare const TILE_SCALE_MAX = 2;
|
|
6
6
|
export declare const resolveTileScaleFactor: (canvas: Pick<AnnotationCanvasState, "tileScaleFactor" | "tileScaleFactorMobile">, platform: TileScalePlatform) => number;
|
|
7
7
|
export declare const clampTileScale: (v: number) => number;
|
|
8
|
+
export declare const tileDocSizeForFactor: (factor: number, zoom: number) => number;
|
|
9
|
+
export declare const tileScaleFromDocSize: (docSize: number, zoom: number) => number;
|
|
8
10
|
export declare const isUnassociatedStamp: (m: Pick<PlacedMeasurementRef, "measurementId" | "measurementPath" | "columnId">) => boolean;
|
|
9
11
|
export declare const stampTileSize: (m: Pick<PlacedMeasurementRef, "scale">, tileScaleFactor?: number, viewportScale?: number) => number;
|
|
@@ -37,6 +37,24 @@ export const resolveTileScaleFactor = (canvas, platform) => (platform === 'mobil
|
|
|
37
37
|
// Clamp a tile-scale-factor candidate to the supported range. The single guard
|
|
38
38
|
// for the value before it lands in the document (slider input, restored docs).
|
|
39
39
|
export const clampTileScale = (v) => v < TILE_SCALE_MIN ? TILE_SCALE_MIN : v > TILE_SCALE_MAX ? TILE_SCALE_MAX : v;
|
|
40
|
+
// --- Cross-platform initial-size calibration ---------------------------------
|
|
41
|
+
// The platform knobs are screen-space multipliers, so the same value covers a
|
|
42
|
+
// very different share of the DRAWING on a phone than on a desktop pane. To
|
|
43
|
+
// hand mobile a sensible starting size, web persists the doc-space edge length
|
|
44
|
+
// a scale-1 tile covered as the user saw it (`tileDocSize`, stamped on tile
|
|
45
|
+
// placement and web slider commits); a mobile canvas with no own knob yet
|
|
46
|
+
// inverts that at its content-fit zoom so its tiles cover the SAME area of the
|
|
47
|
+
// image. Two inverses of one identity: screenSize = docSize × zoom.
|
|
48
|
+
// Doc-space edge length a scale-1 tile covers at `zoom` under `factor` — the
|
|
49
|
+
// web write side of the calibration. Non-positive zoom yields 0, which the
|
|
50
|
+
// setTileDocSize op ignores.
|
|
51
|
+
export const tileDocSizeForFactor = (factor, zoom) => zoom > 0 ? (STAMP_TILE_SIZE * factor) / zoom : 0;
|
|
52
|
+
// Tile-scale factor that makes a scale-1 tile cover `docSize` doc units at
|
|
53
|
+
// `zoom` — the mobile read side of the calibration. Deliberately NOT clamped
|
|
54
|
+
// to the slider range: faithfully matching the web look can land below
|
|
55
|
+
// TILE_SCALE_MIN on a phone, and rendering/hit-testing handle any positive
|
|
56
|
+
// value. The clamp applies once the user drives the slider themselves.
|
|
57
|
+
export const tileScaleFromDocSize = (docSize, zoom) => (docSize * zoom) / STAMP_TILE_SIZE;
|
|
40
58
|
// --- Tile sizing is independent of canvas size ------------------------------
|
|
41
59
|
// A measurement tile's footprint is base × per-tile `scale` × the document-wide
|
|
42
60
|
// `tileScaleFactor` (below) — and NOTHING tied to the canvas's pixel size, so
|
|
@@ -10,6 +10,8 @@ export interface AnnotationCanvasHandle {
|
|
|
10
10
|
canRedo(): boolean;
|
|
11
11
|
zoomToFit(): void;
|
|
12
12
|
resetView(): void;
|
|
13
|
+
getTileScaleFactor(): number;
|
|
14
|
+
getTileDocSize(factor: number): number;
|
|
13
15
|
placeMeasurementAtCenter(ref: MeasurementRef): void;
|
|
14
16
|
placeAnnotationAtCenter(opts?: {
|
|
15
17
|
defaultLengthDoc?: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
|
-
import { resolveTileScaleFactor } from './stampLayout.js';
|
|
3
|
+
import { resolveTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
4
4
|
import { createViewportApi, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, } from './viewport.js';
|
|
5
5
|
import { buildRemoveMeasurementOps, recomputeAnchor, rectCenter, DEFAULT_LINE_POS, } from './measurementGeometry.js';
|
|
6
6
|
// The viewport that frames the document's content on screen. When a background
|
|
@@ -76,7 +76,21 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
76
76
|
// tools (hit boxes) and overlays (drawn size) share one value. Uses the
|
|
77
77
|
// effective (preview-applied) canvas for parity with what is on screen; the
|
|
78
78
|
// slider only ever commits, so this matches ctx.document in practice.
|
|
79
|
-
|
|
79
|
+
// A mobile canvas whose own knob is still unset derives its factor from the
|
|
80
|
+
// web-stamped doc-space calibration (tileDocSize) instead of web's raw
|
|
81
|
+
// screen-space value: at the initial content-fit view its tiles then cover
|
|
82
|
+
// the same area of the image the web user saw. The first mobile slider
|
|
83
|
+
// commit writes tileScaleFactorMobile and takes over for good.
|
|
84
|
+
const tileScalePlatformResolved = tileScalePlatform ?? 'web';
|
|
85
|
+
let tileScaleFactor = resolveTileScaleFactor(effectiveCanvas, tileScalePlatformResolved);
|
|
86
|
+
if (tileScalePlatformResolved === 'mobile' &&
|
|
87
|
+
effectiveCanvas.tileScaleFactorMobile == null &&
|
|
88
|
+
effectiveCanvas.tileDocSize != null &&
|
|
89
|
+
effectiveCanvas.tileDocSize > 0 &&
|
|
90
|
+
width > 0 &&
|
|
91
|
+
height > 0) {
|
|
92
|
+
tileScaleFactor = tileScaleFromDocSize(effectiveCanvas.tileDocSize, computeContentFit(effectiveCanvas, width, height).zoom);
|
|
93
|
+
}
|
|
80
94
|
const ctx = useMemo(() => ({
|
|
81
95
|
document: canvas,
|
|
82
96
|
selection,
|
|
@@ -87,11 +101,27 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
87
101
|
setPreviewPatch(patch);
|
|
88
102
|
},
|
|
89
103
|
commit(patch) {
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
// Placing a tile on WEB stamps the doc-space calibration: the area of
|
|
105
|
+
// the drawing a scale-1 tile covers at the zoom the user is looking
|
|
106
|
+
// at, right when they judged the size against the image. Mobile
|
|
107
|
+
// derives its initial factor from it (see tileScaleFactor above).
|
|
108
|
+
// Appended into the same patch so placement + calibration are one
|
|
109
|
+
// undo step.
|
|
110
|
+
let effective = patch;
|
|
111
|
+
if (tileScalePlatformResolved !== 'mobile' &&
|
|
112
|
+
patch.ops.some((o) => o.op === 'addMeasurement')) {
|
|
113
|
+
const docSize = tileDocSizeForFactor(tileScaleFactor, viewportApi.state.zoom);
|
|
114
|
+
if (docSize > 0) {
|
|
115
|
+
effective = {
|
|
116
|
+
ops: [...patch.ops, { op: 'setTileDocSize', value: docSize }],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const inverse = invertPatch(canvas, effective);
|
|
121
|
+
undoStackRef.current.push({ forward: effective, inverse });
|
|
92
122
|
redoStackRef.current = [];
|
|
93
123
|
setPreviewPatch(null);
|
|
94
|
-
onCommit(
|
|
124
|
+
onCommit(effective);
|
|
95
125
|
},
|
|
96
126
|
setSelection(s) {
|
|
97
127
|
onSelectionChange(s);
|
|
@@ -116,6 +146,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
116
146
|
viewportApi,
|
|
117
147
|
tileViewportScale,
|
|
118
148
|
tileScaleFactor,
|
|
149
|
+
tileScalePlatformResolved,
|
|
119
150
|
onCommit,
|
|
120
151
|
onSelectionChange,
|
|
121
152
|
pickMeasurement,
|
|
@@ -264,6 +295,12 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
264
295
|
// for a high-res background is the very crop this is meant to escape.
|
|
265
296
|
setViewport(computeContentFit(canvas, width, height));
|
|
266
297
|
},
|
|
298
|
+
getTileScaleFactor() {
|
|
299
|
+
return ctxRef.current.tileScaleFactor;
|
|
300
|
+
},
|
|
301
|
+
getTileDocSize(factor) {
|
|
302
|
+
return tileDocSizeForFactor(factor, ctxRef.current.viewport.state.zoom);
|
|
303
|
+
},
|
|
267
304
|
placeMeasurementAtCenter(ref) {
|
|
268
305
|
const c = ctxRef.current;
|
|
269
306
|
const anchor = c.viewport.screenToWorld({
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.js';
|
|
3
|
+
export type { AnnotationCanvasHandle, GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
|
|
4
|
+
export interface CanvasKitOpts {
|
|
5
|
+
locateFile?: (file: string) => string;
|
|
6
|
+
}
|
|
7
|
+
export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
|
|
8
|
+
fallback?: ReactNode;
|
|
9
|
+
canvasKitOpts?: CanvasKitOpts;
|
|
10
|
+
};
|
|
11
|
+
export declare const AnnotationCanvas: ({ fallback, canvasKitOpts, ...rest }: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { WithSkiaWeb } from '@shopify/react-native-skia/lib/module/web/index.js';
|
|
3
|
+
// Web-only entry point: lazy-loads CanvasKit wasm via WithSkiaWeb. Imperative
|
|
4
|
+
// API (undo/redo/zoom) is exposed through the `imperativeRef` prop rather
|
|
5
|
+
// than React refs, since WithSkiaWeb can't forward refs through its lazy
|
|
6
|
+
// component boundary. The native build sits in `AnnotationCanvas.native.tsx`
|
|
7
|
+
// and skips WithSkiaWeb entirely (no wasm to load).
|
|
8
|
+
export const AnnotationCanvas = ({ fallback, canvasKitOpts, ...rest }) => (_jsx(WithSkiaWeb, { getComponent: () => import('./AnnotationCanvasInner.js').then((m) => ({
|
|
9
|
+
default: m.AnnotationCanvasInner,
|
|
10
|
+
})), fallback: fallback ?? null, componentProps: rest, opts: canvasKitOpts }));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.native.js';
|
|
2
|
+
export type { AnnotationCanvasHandle, } from './useAnnotationCanvasState.js';
|
|
3
|
+
export type { GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
|
|
4
|
+
export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
|
|
5
|
+
fallback?: unknown;
|
|
6
|
+
canvasKitOpts?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export declare const AnnotationCanvas: (props: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AnnotationCanvasInner } from './AnnotationCanvasInner.native.js';
|
|
3
|
+
export const AnnotationCanvas = (props) => {
|
|
4
|
+
const { fallback: _f, canvasKitOpts: _o, ...rest } = props;
|
|
5
|
+
return _jsx(AnnotationCanvasInner, { ...rest });
|
|
6
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CSSProperties, type MutableRefObject } from 'react';
|
|
2
|
+
import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
|
|
3
|
+
import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
|
|
4
|
+
import type { MeasurementRef } from './measurementPicker.js';
|
|
5
|
+
import type { Tool } from './Tool.js';
|
|
6
|
+
import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
|
|
7
|
+
import { type ViewportState } from './viewport.js';
|
|
8
|
+
import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
|
|
9
|
+
export type { AnnotationCanvasHandle };
|
|
10
|
+
export interface AnnotationCanvasInnerProps {
|
|
11
|
+
canvas: AnnotationCanvasState;
|
|
12
|
+
onCommit(patch: AnnotationDocumentPatch): void;
|
|
13
|
+
tools: Tool[];
|
|
14
|
+
activeToolId: string;
|
|
15
|
+
selection: Selection | null;
|
|
16
|
+
onSelectionChange(selection: Selection | null): void;
|
|
17
|
+
measurements?: Measurement[];
|
|
18
|
+
fallbackUnit?: Units;
|
|
19
|
+
fractionalTolerance?: FractionalTolerance;
|
|
20
|
+
decimalTolerance?: DecimalTolerance;
|
|
21
|
+
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
22
|
+
pickMeasurement?: () => Promise<MeasurementRef | null>;
|
|
23
|
+
renderMeasurementStamp?: RenderMeasurementStamp;
|
|
24
|
+
stampFontSource?: unknown;
|
|
25
|
+
stampValueFontSize?: number;
|
|
26
|
+
stampLabelFontSize?: number;
|
|
27
|
+
gestures?: GestureConfig;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
initialViewport?: ViewportState;
|
|
31
|
+
style?: CSSProperties;
|
|
32
|
+
imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
|
|
33
|
+
}
|
|
34
|
+
export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
|
|
35
|
+
export interface GestureConfig {
|
|
36
|
+
wheel?: 'zoom' | 'pan' | 'auto';
|
|
37
|
+
panTriggers?: PanTrigger[];
|
|
38
|
+
}
|
|
39
|
+
export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFont } from '@shopify/react-native-skia';
|
|
3
|
+
import { useCallback, useEffect, useRef, } from 'react';
|
|
4
|
+
import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
|
|
5
|
+
import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
|
|
6
|
+
import { STAMP_TILE_SIZE } from './stampLayout.js';
|
|
7
|
+
const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
|
|
8
|
+
export const AnnotationCanvasInner = (props) => {
|
|
9
|
+
const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, gestures, width, height, style, activeToolId, tools, } = props;
|
|
10
|
+
const wheelMode = gestures?.wheel ?? 'auto';
|
|
11
|
+
const panTriggers = gestures?.panTriggers ?? DEFAULT_PAN_TRIGGERS;
|
|
12
|
+
const allowSpacePan = panTriggers.includes('space');
|
|
13
|
+
const allowMiddlePan = panTriggers.includes('middleMouse');
|
|
14
|
+
const allowRightPan = panTriggers.includes('rightMouse');
|
|
15
|
+
const valueFont = useFont(stampFontSource, stampValueFontSize);
|
|
16
|
+
const labelFont = useFont(stampFontSource, stampLabelFontSize);
|
|
17
|
+
const state = useAnnotationCanvasState(props);
|
|
18
|
+
const containerRef = useRef(null);
|
|
19
|
+
const panGestureRef = useRef(null);
|
|
20
|
+
const spaceDownRef = useRef(false);
|
|
21
|
+
const activeTool = tools.find((t) => t.id === activeToolId) ?? null;
|
|
22
|
+
const toCanvasPointer = useCallback((event) => {
|
|
23
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
24
|
+
const screen = {
|
|
25
|
+
x: event.clientX - (rect?.left ?? 0),
|
|
26
|
+
y: event.clientY - (rect?.top ?? 0),
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
pointerId: event.pointerId,
|
|
30
|
+
screen,
|
|
31
|
+
world: state.ctx.viewport.screenToWorld(screen),
|
|
32
|
+
pressure: event.pressure || undefined,
|
|
33
|
+
shiftKey: event.shiftKey,
|
|
34
|
+
altKey: event.altKey,
|
|
35
|
+
metaKey: event.metaKey,
|
|
36
|
+
ctrlKey: event.ctrlKey,
|
|
37
|
+
};
|
|
38
|
+
}, [state.ctx.viewport]);
|
|
39
|
+
const isPanTriggerDown = useCallback((event) => {
|
|
40
|
+
if (allowMiddlePan && event.button === 1)
|
|
41
|
+
return true;
|
|
42
|
+
if (allowRightPan && event.button === 2)
|
|
43
|
+
return true;
|
|
44
|
+
if (allowSpacePan && event.button === 0 && spaceDownRef.current)
|
|
45
|
+
return true;
|
|
46
|
+
return false;
|
|
47
|
+
}, [allowMiddlePan, allowRightPan, allowSpacePan]);
|
|
48
|
+
const handlePointerDown = useCallback((event) => {
|
|
49
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
50
|
+
const screen = {
|
|
51
|
+
x: event.clientX - (rect?.left ?? 0),
|
|
52
|
+
y: event.clientY - (rect?.top ?? 0),
|
|
53
|
+
};
|
|
54
|
+
if (isPanTriggerDown(event)) {
|
|
55
|
+
panGestureRef.current = { pointerId: event.pointerId, lastScreen: screen };
|
|
56
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
61
|
+
state.dispatchPointerDown(toCanvasPointer(event));
|
|
62
|
+
}, [state, toCanvasPointer, isPanTriggerDown]);
|
|
63
|
+
const handlePointerMove = useCallback((event) => {
|
|
64
|
+
const pan = panGestureRef.current;
|
|
65
|
+
if (pan && event.pointerId === pan.pointerId) {
|
|
66
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
67
|
+
const screen = {
|
|
68
|
+
x: event.clientX - (rect?.left ?? 0),
|
|
69
|
+
y: event.clientY - (rect?.top ?? 0),
|
|
70
|
+
};
|
|
71
|
+
state.pan({
|
|
72
|
+
x: screen.x - pan.lastScreen.x,
|
|
73
|
+
y: screen.y - pan.lastScreen.y,
|
|
74
|
+
});
|
|
75
|
+
panGestureRef.current = { pointerId: pan.pointerId, lastScreen: screen };
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
state.dispatchPointerMove(toCanvasPointer(event));
|
|
79
|
+
}, [state, toCanvasPointer]);
|
|
80
|
+
const handlePointerUp = useCallback((event) => {
|
|
81
|
+
const pan = panGestureRef.current;
|
|
82
|
+
if (pan && event.pointerId === pan.pointerId) {
|
|
83
|
+
panGestureRef.current = null;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
state.dispatchPointerUp(toCanvasPointer(event));
|
|
87
|
+
}, [state, toCanvasPointer]);
|
|
88
|
+
const handlePointerCancel = useCallback(() => {
|
|
89
|
+
panGestureRef.current = null;
|
|
90
|
+
state.dispatchPointerCancel();
|
|
91
|
+
}, [state]);
|
|
92
|
+
const handleWheel = useCallback((event) => {
|
|
93
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
94
|
+
const focal = {
|
|
95
|
+
x: event.clientX - (rect?.left ?? 0),
|
|
96
|
+
y: event.clientY - (rect?.top ?? 0),
|
|
97
|
+
};
|
|
98
|
+
if (event.ctrlKey || event.metaKey) {
|
|
99
|
+
const factor = Math.exp(-event.deltaY / 200);
|
|
100
|
+
state.zoom(focal, state.ctx.viewport.state.zoom * factor);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (wheelMode === 'zoom') {
|
|
104
|
+
const factor = Math.exp(-event.deltaY / 300);
|
|
105
|
+
state.zoom(focal, state.ctx.viewport.state.zoom * factor);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (event.shiftKey) {
|
|
109
|
+
state.pan({ x: event.deltaY, y: event.deltaX });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
state.pan({ x: event.deltaX, y: event.deltaY });
|
|
113
|
+
}, [state, wheelMode]);
|
|
114
|
+
const handleContextMenu = useCallback((event) => {
|
|
115
|
+
if (allowRightPan)
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
}, [allowRightPan]);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
const el = containerRef.current;
|
|
120
|
+
if (!el)
|
|
121
|
+
return;
|
|
122
|
+
const stop = (e) => {
|
|
123
|
+
if (e.target === el || el.contains(e.target)) {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
el.addEventListener('wheel', stop, { passive: false });
|
|
128
|
+
return () => el.removeEventListener('wheel', stop);
|
|
129
|
+
}, []);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!allowSpacePan)
|
|
132
|
+
return;
|
|
133
|
+
const onKeyDown = (e) => {
|
|
134
|
+
if (e.code === 'Space' && !e.repeat) {
|
|
135
|
+
spaceDownRef.current = true;
|
|
136
|
+
if (containerRef.current)
|
|
137
|
+
containerRef.current.style.cursor = 'grab';
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const onKeyUp = (e) => {
|
|
141
|
+
if (e.code === 'Space') {
|
|
142
|
+
spaceDownRef.current = false;
|
|
143
|
+
if (containerRef.current)
|
|
144
|
+
containerRef.current.style.cursor = '';
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
window.addEventListener('keydown', onKeyDown);
|
|
148
|
+
window.addEventListener('keyup', onKeyUp);
|
|
149
|
+
return () => {
|
|
150
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
151
|
+
window.removeEventListener('keyup', onKeyUp);
|
|
152
|
+
};
|
|
153
|
+
}, [allowSpacePan]);
|
|
154
|
+
const containerStyle = {
|
|
155
|
+
position: 'relative',
|
|
156
|
+
width,
|
|
157
|
+
height,
|
|
158
|
+
overflow: 'hidden',
|
|
159
|
+
touchAction: 'none',
|
|
160
|
+
userSelect: 'none',
|
|
161
|
+
cursor: activeTool?.cursor ?? 'default',
|
|
162
|
+
...style,
|
|
163
|
+
};
|
|
164
|
+
const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
|
|
165
|
+
const { renderMeasurementStamp, selection } = props;
|
|
166
|
+
return (_jsxs("div", { ref: containerRef, style: containerStyle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, onWheel: handleWheel, onContextMenu: handleContextMenu, children: [AnnotationCanvasSkia({
|
|
167
|
+
width,
|
|
168
|
+
height,
|
|
169
|
+
effectiveCanvas: state.effectiveCanvas,
|
|
170
|
+
worldTransform: state.worldTransform,
|
|
171
|
+
measurementsById: state.measurementsById,
|
|
172
|
+
fallbackUnit,
|
|
173
|
+
fractionalTolerance,
|
|
174
|
+
decimalTolerance,
|
|
175
|
+
resolveImageUrl,
|
|
176
|
+
valueFont,
|
|
177
|
+
labelFont,
|
|
178
|
+
hideMeasurementStamps: !!renderMeasurementStamp,
|
|
179
|
+
penDrawingStroke: state.penDrawingStroke,
|
|
180
|
+
customPreview,
|
|
181
|
+
}), renderMeasurementStamp && (_jsx("div", { style: {
|
|
182
|
+
position: 'absolute',
|
|
183
|
+
inset: 0,
|
|
184
|
+
pointerEvents: 'none',
|
|
185
|
+
}, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
|
|
186
|
+
const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
|
|
187
|
+
const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
|
|
188
|
+
const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
|
|
189
|
+
const isSelected = selection?.ids.includes(placed.id) ?? false;
|
|
190
|
+
return (_jsxs("div", { style: {
|
|
191
|
+
position: 'absolute',
|
|
192
|
+
left: 0,
|
|
193
|
+
top: 0,
|
|
194
|
+
width: size,
|
|
195
|
+
height: size,
|
|
196
|
+
transform: `translate(${cx - size / 2}px, ${cy - size / 2}px)`,
|
|
197
|
+
}, children: [renderMeasurementStamp({
|
|
198
|
+
placed,
|
|
199
|
+
measurement: state.measurementsById.get(placed.measurementId) ?? null,
|
|
200
|
+
selected: isSelected,
|
|
201
|
+
size,
|
|
202
|
+
zoom: state.viewport.zoom,
|
|
203
|
+
}), isSelected && (_jsx("div", { role: "button", "aria-label": "Remove measurement", onPointerDown: (e) => {
|
|
204
|
+
e.stopPropagation();
|
|
205
|
+
state.ctx.commit({
|
|
206
|
+
ops: [{ op: 'removeMeasurement', id: placed.id }],
|
|
207
|
+
});
|
|
208
|
+
state.ctx.setSelection(null);
|
|
209
|
+
}, style: {
|
|
210
|
+
position: 'absolute',
|
|
211
|
+
top: -10,
|
|
212
|
+
right: -10,
|
|
213
|
+
width: 40,
|
|
214
|
+
height: 40,
|
|
215
|
+
cursor: 'pointer',
|
|
216
|
+
pointerEvents: 'auto',
|
|
217
|
+
} }))] }, placed.id));
|
|
218
|
+
}) }))] }));
|
|
219
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type MutableRefObject } from 'react';
|
|
2
|
+
import { type ViewStyle } from 'react-native';
|
|
3
|
+
import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
|
|
4
|
+
import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
|
|
5
|
+
import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
|
|
6
|
+
import type { MeasurementRef } from './measurementPicker.js';
|
|
7
|
+
import type { Tool } from './Tool.js';
|
|
8
|
+
import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
|
|
9
|
+
import type { ViewportState } from './viewport.js';
|
|
10
|
+
export type { AnnotationCanvasHandle };
|
|
11
|
+
export interface AnnotationCanvasInnerProps {
|
|
12
|
+
canvas: AnnotationCanvasState;
|
|
13
|
+
onCommit(patch: AnnotationDocumentPatch): void;
|
|
14
|
+
tools: Tool[];
|
|
15
|
+
activeToolId: string;
|
|
16
|
+
selection: Selection | null;
|
|
17
|
+
onSelectionChange(selection: Selection | null): void;
|
|
18
|
+
measurements?: Measurement[];
|
|
19
|
+
fallbackUnit?: Units;
|
|
20
|
+
fractionalTolerance?: FractionalTolerance;
|
|
21
|
+
decimalTolerance?: DecimalTolerance;
|
|
22
|
+
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
23
|
+
pickMeasurement?: () => Promise<MeasurementRef | null>;
|
|
24
|
+
renderMeasurementStamp?: RenderMeasurementStamp;
|
|
25
|
+
stampFontSource?: unknown;
|
|
26
|
+
stampValueFontSize?: number;
|
|
27
|
+
stampLabelFontSize?: number;
|
|
28
|
+
gestures?: unknown;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
initialViewport?: ViewportState;
|
|
32
|
+
style?: ViewStyle;
|
|
33
|
+
imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
|
|
34
|
+
}
|
|
35
|
+
export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useFont } from '@shopify/react-native-skia';
|
|
3
|
+
import { useMemo, useRef } from 'react';
|
|
4
|
+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
5
|
+
import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler';
|
|
6
|
+
import { STAMP_TILE_SIZE } from './stampLayout.js';
|
|
7
|
+
import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
|
|
8
|
+
import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
|
|
9
|
+
// Native fingerprint: one finger drives the active tool, two fingers
|
|
10
|
+
// pan/zoom the viewport. Tap counts as a brief pointer down+up so tools
|
|
11
|
+
// like measurement-stamp (which only listen to onPointerUp) work via tap.
|
|
12
|
+
export const AnnotationCanvasInner = (props) => {
|
|
13
|
+
const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, width, height, style, } = props;
|
|
14
|
+
const valueFont = useFont(stampFontSource, stampValueFontSize);
|
|
15
|
+
const labelFont = useFont(stampFontSource, stampLabelFontSize);
|
|
16
|
+
const state = useAnnotationCanvasState(props);
|
|
17
|
+
// Per-gesture refs so we always emit a matching down/move/up sequence.
|
|
18
|
+
const pointerIdRef = useRef(1);
|
|
19
|
+
const inFlightRef = useRef(null);
|
|
20
|
+
const pinchStartZoomRef = useRef(1);
|
|
21
|
+
const buildEvent = (pointerId, screen) => ({
|
|
22
|
+
pointerId,
|
|
23
|
+
screen,
|
|
24
|
+
world: state.ctx.viewport.screenToWorld(screen),
|
|
25
|
+
});
|
|
26
|
+
const gesture = useMemo(() => {
|
|
27
|
+
const toolPan = Gesture.Pan()
|
|
28
|
+
.minPointers(1)
|
|
29
|
+
.maxPointers(1)
|
|
30
|
+
.runOnJS(true)
|
|
31
|
+
.onBegin((e) => {
|
|
32
|
+
const id = pointerIdRef.current++;
|
|
33
|
+
const screen = { x: e.x, y: e.y };
|
|
34
|
+
inFlightRef.current = { id, lastScreen: screen };
|
|
35
|
+
state.dispatchPointerDown(buildEvent(id, screen));
|
|
36
|
+
})
|
|
37
|
+
.onUpdate((e) => {
|
|
38
|
+
const f = inFlightRef.current;
|
|
39
|
+
if (!f)
|
|
40
|
+
return;
|
|
41
|
+
const screen = { x: e.x, y: e.y };
|
|
42
|
+
f.lastScreen = screen;
|
|
43
|
+
state.dispatchPointerMove(buildEvent(f.id, screen));
|
|
44
|
+
})
|
|
45
|
+
.onEnd((e) => {
|
|
46
|
+
const f = inFlightRef.current;
|
|
47
|
+
if (!f)
|
|
48
|
+
return;
|
|
49
|
+
state.dispatchPointerUp(buildEvent(f.id, { x: e.x, y: e.y }));
|
|
50
|
+
inFlightRef.current = null;
|
|
51
|
+
})
|
|
52
|
+
.onFinalize(() => {
|
|
53
|
+
if (inFlightRef.current) {
|
|
54
|
+
state.dispatchPointerCancel();
|
|
55
|
+
inFlightRef.current = null;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const tap = Gesture.Tap()
|
|
59
|
+
.maxDuration(250)
|
|
60
|
+
.runOnJS(true)
|
|
61
|
+
.onEnd((e) => {
|
|
62
|
+
const id = pointerIdRef.current++;
|
|
63
|
+
const screen = { x: e.x, y: e.y };
|
|
64
|
+
// Synthesize a down+up sequence so tools that only listen to
|
|
65
|
+
// onPointerUp (e.g. measurement stamp) still fire.
|
|
66
|
+
state.dispatchPointerDown(buildEvent(id, screen));
|
|
67
|
+
state.dispatchPointerUp(buildEvent(id, screen));
|
|
68
|
+
});
|
|
69
|
+
const viewportPan = Gesture.Pan()
|
|
70
|
+
.minPointers(2)
|
|
71
|
+
.maxPointers(2)
|
|
72
|
+
.runOnJS(true)
|
|
73
|
+
.onChange((e) => {
|
|
74
|
+
state.pan({ x: e.changeX, y: e.changeY });
|
|
75
|
+
});
|
|
76
|
+
const pinch = Gesture.Pinch()
|
|
77
|
+
.runOnJS(true)
|
|
78
|
+
.onBegin(() => {
|
|
79
|
+
pinchStartZoomRef.current = state.ctx.viewport.state.zoom;
|
|
80
|
+
})
|
|
81
|
+
.onUpdate((e) => {
|
|
82
|
+
state.zoom({ x: e.focalX, y: e.focalY }, pinchStartZoomRef.current * e.scale);
|
|
83
|
+
});
|
|
84
|
+
return Gesture.Race(tap, Gesture.Simultaneous(viewportPan, pinch), toolPan);
|
|
85
|
+
}, [state]);
|
|
86
|
+
const activeTool = props.tools.find((t) => t.id === props.activeToolId) ?? null;
|
|
87
|
+
const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
|
|
88
|
+
const { renderMeasurementStamp, selection } = props;
|
|
89
|
+
return (_jsxs(GestureHandlerRootView, { style: [{ width, height }, style], children: [_jsx(GestureDetector, { gesture: gesture, children: _jsx(View, { style: { width, height }, collapsable: false, children: AnnotationCanvasSkia({
|
|
90
|
+
width,
|
|
91
|
+
height,
|
|
92
|
+
effectiveCanvas: state.effectiveCanvas,
|
|
93
|
+
worldTransform: state.worldTransform,
|
|
94
|
+
measurementsById: state.measurementsById,
|
|
95
|
+
fallbackUnit,
|
|
96
|
+
fractionalTolerance,
|
|
97
|
+
decimalTolerance,
|
|
98
|
+
resolveImageUrl,
|
|
99
|
+
valueFont,
|
|
100
|
+
labelFont,
|
|
101
|
+
hideMeasurementStamps: !!renderMeasurementStamp,
|
|
102
|
+
penDrawingStroke: state.penDrawingStroke,
|
|
103
|
+
customPreview,
|
|
104
|
+
}) }) }), renderMeasurementStamp && (_jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
|
|
105
|
+
const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
|
|
106
|
+
const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
|
|
107
|
+
const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
|
|
108
|
+
const isSelected = selection?.ids.includes(placed.id) ?? false;
|
|
109
|
+
return (_jsxs(View, { pointerEvents: "box-none", style: {
|
|
110
|
+
position: 'absolute',
|
|
111
|
+
left: 0,
|
|
112
|
+
top: 0,
|
|
113
|
+
width: size,
|
|
114
|
+
height: size,
|
|
115
|
+
transform: [
|
|
116
|
+
{ translateX: cx - size / 2 },
|
|
117
|
+
{ translateY: cy - size / 2 },
|
|
118
|
+
],
|
|
119
|
+
}, children: [_jsx(View, { pointerEvents: "none", style: StyleSheet.absoluteFill, children: renderMeasurementStamp({
|
|
120
|
+
placed,
|
|
121
|
+
measurement: state.measurementsById.get(placed.measurementId) ?? null,
|
|
122
|
+
selected: isSelected,
|
|
123
|
+
size,
|
|
124
|
+
zoom: state.viewport.zoom,
|
|
125
|
+
}) }), isSelected && (_jsx(TouchableOpacity, { accessibilityRole: "button", accessibilityLabel: "Remove measurement", hitSlop: 10, onPress: () => {
|
|
126
|
+
state.ctx.commit({
|
|
127
|
+
ops: [{ op: 'removeMeasurement', id: placed.id }],
|
|
128
|
+
});
|
|
129
|
+
state.ctx.setSelection(null);
|
|
130
|
+
}, style: {
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
top: -8,
|
|
133
|
+
right: -8,
|
|
134
|
+
width: 36,
|
|
135
|
+
height: 36,
|
|
136
|
+
} }))] }, placed.id));
|
|
137
|
+
}) }))] }));
|
|
138
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SkFont } from '@shopify/react-native-skia';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { AnnotationCanvasState, AnnotationStroke } from '../types/annotation.js';
|
|
4
|
+
import { DecimalTolerance, FractionalTolerance, Units, type Measurement } from '../types/firestore.js';
|
|
5
|
+
export interface AnnotationCanvasSkiaProps {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
effectiveCanvas: AnnotationCanvasState;
|
|
9
|
+
worldTransform: Array<{
|
|
10
|
+
scale: number;
|
|
11
|
+
} | {
|
|
12
|
+
translateX: number;
|
|
13
|
+
} | {
|
|
14
|
+
translateY: number;
|
|
15
|
+
}>;
|
|
16
|
+
measurementsById: Map<string, Measurement>;
|
|
17
|
+
fallbackUnit?: Units;
|
|
18
|
+
fractionalTolerance?: FractionalTolerance;
|
|
19
|
+
decimalTolerance?: DecimalTolerance;
|
|
20
|
+
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
21
|
+
valueFont: SkFont | null;
|
|
22
|
+
labelFont: SkFont | null;
|
|
23
|
+
hideMeasurementStamps?: boolean;
|
|
24
|
+
penDrawingStroke: AnnotationStroke | null;
|
|
25
|
+
customPreview?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, measurementsById, fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, valueFont, labelFont, hideMeasurementStamps, penDrawingStroke, customPreview, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Canvas, Group } from '@shopify/react-native-skia';
|
|
3
|
+
import { Units, } from '../types/firestore.js';
|
|
4
|
+
import { BackgroundImageElement } from './elements/BackgroundImageElement.js';
|
|
5
|
+
import { MeasurementStampElement } from './elements/MeasurementStampElement.js';
|
|
6
|
+
import { ShapeElement } from './elements/ShapeElement.js';
|
|
7
|
+
import { StrokeElement } from './elements/StrokeElement.js';
|
|
8
|
+
// Platform-agnostic Skia subtree shared by web and native Inners.
|
|
9
|
+
//
|
|
10
|
+
// Call this as a FUNCTION (`AnnotationCanvasSkia({ ... })`), not as a JSX
|
|
11
|
+
// component (`<AnnotationCanvasSkia ... />`). Used as a component, it adds
|
|
12
|
+
// a React component boundary between the parent and Skia's `<Canvas>` that
|
|
13
|
+
// breaks Skia's reconciler when the page first calls `useFont` — the JS
|
|
14
|
+
// thread hangs in `MakeFreeTypeFaceFromData`. Symptoms only appeared on
|
|
15
|
+
// Vite's dev server with a symlinked boldr-utils + React Refresh, but
|
|
16
|
+
// since the function-call pattern works identically on native we use it
|
|
17
|
+
// in both Inners for consistency. Don't add hooks here; this is a plain
|
|
18
|
+
// JSX-returning helper, not a component.
|
|
19
|
+
export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, measurementsById, fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, valueFont, labelFont, hideMeasurementStamps, penDrawingStroke, customPreview, }) => (_jsx(Canvas, { style: { width, height }, children: _jsxs(Group, { transform: worldTransform, children: [effectiveCanvas.viewport.backgroundImage && (_jsx(BackgroundImageElement, { image: effectiveCanvas.viewport.backgroundImage, docWidth: effectiveCanvas.viewport.width, docHeight: effectiveCanvas.viewport.height, fit: effectiveCanvas.viewport.backgroundFit ?? 'contain', resolveUrl: resolveImageUrl })), effectiveCanvas.strokes.map((stroke) => (_jsx(StrokeElement, { stroke: stroke }, stroke.id))), effectiveCanvas.shapes.map((shape) => (_jsx(ShapeElement, { shape: shape, font: valueFont }, shape.id))), !hideMeasurementStamps &&
|
|
20
|
+
effectiveCanvas.placedMeasurements.map((placed) => (_jsx(MeasurementStampElement, { placed: placed, measurement: measurementsById.get(placed.measurementId) ?? null, fallbackUnit: fallbackUnit ?? Units.Millimeters, fractionalTolerance: fractionalTolerance, decimalTolerance: decimalTolerance, valueFont: valueFont, labelFont: labelFont }, placed.id))), penDrawingStroke && _jsx(StrokeElement, { stroke: penDrawingStroke }), customPreview] }) }));
|