@reekon-tools/boldr-utils 1.6.24 → 1.6.26
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.d.ts +1 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.d.ts +1 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +179 -78
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +4 -2
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +5 -2
- package/dist/annotation/canvas/elements/BackgroundImageElement.d.ts +4 -1
- package/dist/annotation/canvas/elements/BackgroundImageElement.js +21 -19
- package/dist/annotation/canvas/useAnnotationCanvasState.js +41 -10
- package/dist/annotation/canvas/viewport.d.ts +13 -1
- package/dist/annotation/canvas/viewport.js +32 -0
- 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 +2 -1
- package/dist/exports.js +2 -1
- package/dist/hooks/useParseMeasurement.d.ts +4 -0
- package/dist/hooks/useParseMeasurement.js +14 -0
- package/dist/utils/evaluateFormula.d.ts +20 -0
- package/dist/utils/evaluateFormula.js +31 -0
- package/dist/utils/indexLabels.d.ts +3 -0
- package/dist/utils/indexLabels.js +40 -0
- package/package.json +1 -1
- package/dist/annotation/canvas/tools/measurementLineTool.d.ts +0 -12
- package/dist/annotation/canvas/tools/measurementLineTool.js +0 -95
|
@@ -35,6 +35,7 @@ export interface AnnotationCanvasInnerProps {
|
|
|
35
35
|
height: number;
|
|
36
36
|
initialViewport?: ViewportState;
|
|
37
37
|
style?: CSSProperties;
|
|
38
|
+
magnifierTopOffset?: number;
|
|
38
39
|
imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
|
|
39
40
|
}
|
|
40
41
|
export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
|
|
@@ -36,6 +36,7 @@ export interface AnnotationCanvasInnerProps {
|
|
|
36
36
|
height: number;
|
|
37
37
|
initialViewport?: ViewportState;
|
|
38
38
|
style?: ViewStyle;
|
|
39
|
+
magnifierTopOffset?: number;
|
|
39
40
|
imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
|
|
40
41
|
}
|
|
41
42
|
export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Skia, useFont } from '@shopify/react-native-skia';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Group, Line, Rect, RoundedRect, Skia, rect, rrect, useFont, } from '@shopify/react-native-skia';
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
4
4
|
import { StyleSheet, TouchableOpacity, View, } from 'react-native';
|
|
5
5
|
import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler';
|
|
@@ -7,6 +7,7 @@ import Animated, { runOnJS, runOnUI, useAnimatedStyle, useDerivedValue, useShare
|
|
|
7
7
|
import { stampTileSize } from './stampLayout.js';
|
|
8
8
|
import { DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
9
9
|
import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
|
|
10
|
+
import { useBackgroundSkImage } from './elements/BackgroundImageElement.js';
|
|
10
11
|
import { buildRemoveMeasurementOps, } from './measurementGeometry.js';
|
|
11
12
|
import { buildShapeFromDrag } from './tools/shapeTool.js';
|
|
12
13
|
import { SELECTION_PAD } from './textGeometry.js';
|
|
@@ -29,6 +30,20 @@ const HANDLE_RING_PX = 2;
|
|
|
29
30
|
// selectTool's MIN_SHAPE_EXTENT (the live preview must clamp identically to the
|
|
30
31
|
// buildShapeCornerPatch commit). Keep the two in sync.
|
|
31
32
|
const MIN_SHAPE_EXTENT = 1;
|
|
33
|
+
// Magnifying-glass loupe. A separate, top-most overlay canvas re-draws the Skia
|
|
34
|
+
// scene zoomed around the finger and clipped to a fixed lens window, so it sits
|
|
35
|
+
// ABOVE the RN measurement/input overlay (which the main canvas sits below) and
|
|
36
|
+
// never touches the main canvas (no flicker). It mounts only during a precision
|
|
37
|
+
// gesture, so there's no cost at rest.
|
|
38
|
+
const LOUPE_SIZE_FRACTION = 0.3; // of the smaller canvas dimension
|
|
39
|
+
const LOUPE_MARGIN = 12; // points from the top-left corner
|
|
40
|
+
const LOUPE_MAGNIFICATION = 2; // extra zoom inside the lens, on top of viewport
|
|
41
|
+
const LOUPE_RADIUS = 16; // lens corner radius
|
|
42
|
+
const LOUPE_BORDER_WIDTH = 2;
|
|
43
|
+
const LOUPE_BORDER_COLOR = '#1F2937';
|
|
44
|
+
const LOUPE_BG_COLOR = '#FFFFFF';
|
|
45
|
+
const LOUPE_CROSSHAIR_COLOR = '#1F293799'; // ~60% dark — marks the exact point
|
|
46
|
+
const LOUPE_CROSSHAIR_ARM = 9; // half-length of each crosshair arm, points
|
|
32
47
|
// Native fingerprint: one finger drives the active tool, two fingers
|
|
33
48
|
// pan/zoom the viewport. Tap counts as a brief pointer down+up so tools
|
|
34
49
|
// like measurement-stamp (which only listen to onPointerUp) work via tap.
|
|
@@ -41,9 +56,13 @@ const MIN_SHAPE_EXTENT = 1;
|
|
|
41
56
|
// is only re-synced once each gesture ends. The tool/tap path still hops to
|
|
42
57
|
// JS (`runOnJS(true)`) since drawing/selection are React-state operations.
|
|
43
58
|
export const AnnotationCanvasInner = (props) => {
|
|
44
|
-
const { resolveImageUrl, stampFontSource, stampValueFontSize = 14, width, height, style, } = props;
|
|
59
|
+
const { resolveImageUrl, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, } = props;
|
|
45
60
|
const valueFont = useFont(stampFontSource, stampValueFontSize);
|
|
46
61
|
const state = useAnnotationCanvasState(props);
|
|
62
|
+
// Decode the background image ONCE here and share it with both the main
|
|
63
|
+
// canvas and the magnifier loupe. If each canvas loaded its own, the loupe
|
|
64
|
+
// would flash its blank base every gesture while it re-resolved + re-decoded.
|
|
65
|
+
const backgroundSkImage = useBackgroundSkImage(state.effectiveCanvas.viewport.backgroundImage, resolveImageUrl);
|
|
47
66
|
// Latest state behind a ref so the gesture object can be built once and
|
|
48
67
|
// never rebuilt mid-gesture — its JS callbacks read `stateRef.current`.
|
|
49
68
|
const stateRef = useRef(state);
|
|
@@ -68,6 +87,40 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
68
87
|
{ translateY: -panY.value },
|
|
69
88
|
];
|
|
70
89
|
});
|
|
90
|
+
// Magnifying-glass loupe. The live touch (screen points) rides shared values
|
|
91
|
+
// the gesture worklets update; `magnifying` (React state) gates whether the
|
|
92
|
+
// loupe shader wraps the canvas at all — so the extra fullscreen pass exists
|
|
93
|
+
// only while a precision gesture is in flight (drawing, shape rubber-band,
|
|
94
|
+
// endpoint/corner drags), never during idle renders or viewport pan/zoom.
|
|
95
|
+
const magTouchX = useSharedValue(0);
|
|
96
|
+
const magTouchY = useSharedValue(0);
|
|
97
|
+
const [magnifying, setMagnifying] = useState(false);
|
|
98
|
+
// Lens geometry (screen points): a fixed square in the top-left, pushed below
|
|
99
|
+
// any overlaid toolbar by magnifierTopOffset. Its CENTER maps to the live
|
|
100
|
+
// touch point, so the magnified slice is the canvas around the finger.
|
|
101
|
+
const loupeSize = Math.round(Math.min(width, height) * LOUPE_SIZE_FRACTION);
|
|
102
|
+
const loupeX = LOUPE_MARGIN;
|
|
103
|
+
const loupeY = magnifierTopOffset + LOUPE_MARGIN;
|
|
104
|
+
const loupeCx = loupeX + loupeSize / 2;
|
|
105
|
+
const loupeCy = loupeY + loupeSize / 2;
|
|
106
|
+
// World→lens transform: take the live screen point under the finger to the
|
|
107
|
+
// lens center, scaled by LOUPE_MAGNIFICATION on top of the viewport zoom.
|
|
108
|
+
// screen' = lensCenter + m * (worldTransform(world) - touch). Composed as a
|
|
109
|
+
// transform list (last entry applied to the point first), prepended onto the
|
|
110
|
+
// viewport's own [scale, -pan] ops.
|
|
111
|
+
const loupeTransform = useDerivedValue(() => {
|
|
112
|
+
'worklet';
|
|
113
|
+
return [
|
|
114
|
+
{ translateX: loupeCx },
|
|
115
|
+
{ translateY: loupeCy },
|
|
116
|
+
{ scale: LOUPE_MAGNIFICATION },
|
|
117
|
+
{ translateX: -magTouchX.value },
|
|
118
|
+
{ translateY: -magTouchY.value },
|
|
119
|
+
{ scale: zoom.value },
|
|
120
|
+
{ translateX: -panX.value },
|
|
121
|
+
{ translateY: -panY.value },
|
|
122
|
+
];
|
|
123
|
+
}, [loupeCx, loupeCy]);
|
|
71
124
|
// In-flight freehand stroke, owned by the UI thread. The drawing gesture
|
|
72
125
|
// appends world-space points to `livePoints`; `livePath` rebuilds the Skia
|
|
73
126
|
// path off it. React state is untouched until the stroke commits on
|
|
@@ -597,6 +650,8 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
597
650
|
const id = pointerIdRef.current++;
|
|
598
651
|
const screen = { x: e.x, y: e.y };
|
|
599
652
|
inFlightRef.current = { id, lastScreen: screen };
|
|
653
|
+
magTouchX.value = e.x;
|
|
654
|
+
magTouchY.value = e.y;
|
|
600
655
|
stateRef.current.dispatchPointerDown(buildEvent(id, screen));
|
|
601
656
|
})
|
|
602
657
|
.onUpdate((e) => {
|
|
@@ -605,6 +660,12 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
605
660
|
return;
|
|
606
661
|
const screen = { x: e.x, y: e.y };
|
|
607
662
|
f.lastScreen = screen;
|
|
663
|
+
// Drag-to-draw tools (measurement line / rectangle) run through this
|
|
664
|
+
// JS-thread pointer path; show the loupe once the touch actually moves
|
|
665
|
+
// so a plain tap-to-place tool never flashes it.
|
|
666
|
+
magTouchX.value = e.x;
|
|
667
|
+
magTouchY.value = e.y;
|
|
668
|
+
setMagnifying(true);
|
|
608
669
|
stateRef.current.dispatchPointerMove(buildEvent(f.id, screen));
|
|
609
670
|
})
|
|
610
671
|
.onEnd((e) => {
|
|
@@ -615,6 +676,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
615
676
|
inFlightRef.current = null;
|
|
616
677
|
})
|
|
617
678
|
.onFinalize(() => {
|
|
679
|
+
setMagnifying(false);
|
|
618
680
|
if (inFlightRef.current) {
|
|
619
681
|
stateRef.current.dispatchPointerCancel();
|
|
620
682
|
inFlightRef.current = null;
|
|
@@ -794,9 +856,14 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
794
856
|
e.x / zoom.value + panX.value,
|
|
795
857
|
e.y / zoom.value + panY.value,
|
|
796
858
|
];
|
|
859
|
+
magTouchX.value = e.x;
|
|
860
|
+
magTouchY.value = e.y;
|
|
861
|
+
runOnJS(setMagnifying)(true);
|
|
797
862
|
})
|
|
798
863
|
.onChange((e) => {
|
|
799
864
|
'worklet';
|
|
865
|
+
magTouchX.value = e.x;
|
|
866
|
+
magTouchY.value = e.y;
|
|
800
867
|
const wx = e.x / zoom.value + panX.value;
|
|
801
868
|
const wy = e.y / zoom.value + panY.value;
|
|
802
869
|
const pts = livePoints.value;
|
|
@@ -832,6 +899,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
832
899
|
// Cancel path (no onEnd): drop the in-flight preview. After a normal
|
|
833
900
|
// end the buffer is already empty, so this is a no-op.
|
|
834
901
|
livePoints.value = [];
|
|
902
|
+
runOnJS(setMagnifying)(false);
|
|
835
903
|
});
|
|
836
904
|
};
|
|
837
905
|
// Shape rubber-band (line/arrow/rect/triangle/circle tools) — one finger,
|
|
@@ -867,12 +935,17 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
867
935
|
const wx = e.x / zoom.value + panX.value;
|
|
868
936
|
const wy = e.y / zoom.value + panY.value;
|
|
869
937
|
liveShape.value = { active: true, ax: wx, ay: wy, bx: wx, by: wy };
|
|
938
|
+
magTouchX.value = e.x;
|
|
939
|
+
magTouchY.value = e.y;
|
|
940
|
+
runOnJS(setMagnifying)(true);
|
|
870
941
|
})
|
|
871
942
|
.onChange((e) => {
|
|
872
943
|
'worklet';
|
|
873
944
|
const s = liveShape.value;
|
|
874
945
|
if (!s.active)
|
|
875
946
|
return;
|
|
947
|
+
magTouchX.value = e.x;
|
|
948
|
+
magTouchY.value = e.y;
|
|
876
949
|
liveShape.value = {
|
|
877
950
|
active: true,
|
|
878
951
|
ax: s.ax,
|
|
@@ -919,6 +992,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
919
992
|
// Cancel path (no onEnd): drop the in-flight rubber-band. After a
|
|
920
993
|
// normal end the slot is already inactive, so this is a no-op.
|
|
921
994
|
liveShape.value = { active: false, ax: 0, ay: 0, bx: 0, by: 0 };
|
|
995
|
+
runOnJS(setMagnifying)(false);
|
|
922
996
|
});
|
|
923
997
|
};
|
|
924
998
|
// Element drag (select tool) — one finger, UI thread. Hit-tests on the JS
|
|
@@ -953,6 +1027,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
953
1027
|
};
|
|
954
1028
|
epTargetRef.current = { id: selId, handle };
|
|
955
1029
|
setEpDragId(selId);
|
|
1030
|
+
setMagnifying(true);
|
|
956
1031
|
return;
|
|
957
1032
|
}
|
|
958
1033
|
}
|
|
@@ -974,6 +1049,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
974
1049
|
};
|
|
975
1050
|
shapeEpTargetRef.current = { id: selId, handle: shapeHandle };
|
|
976
1051
|
setShapeEpDragId(selId);
|
|
1052
|
+
setMagnifying(true);
|
|
977
1053
|
return;
|
|
978
1054
|
}
|
|
979
1055
|
}
|
|
@@ -990,6 +1066,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
990
1066
|
};
|
|
991
1067
|
resizeTargetRef.current = { id: selId };
|
|
992
1068
|
setResizingId(selId);
|
|
1069
|
+
setMagnifying(true);
|
|
993
1070
|
return;
|
|
994
1071
|
}
|
|
995
1072
|
// Rectangle-annotation corner handle — also selected-element-only.
|
|
@@ -1003,6 +1080,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1003
1080
|
};
|
|
1004
1081
|
rectTargetRef.current = { id: selId, corner: rectCornerHit.corner };
|
|
1005
1082
|
setRectDragId(selId);
|
|
1083
|
+
setMagnifying(true);
|
|
1006
1084
|
return;
|
|
1007
1085
|
}
|
|
1008
1086
|
// Shape (rect/ellipse/polygon) bounding-box corner handle — the shape
|
|
@@ -1030,6 +1108,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1030
1108
|
corner: shapeCornerHit.corner,
|
|
1031
1109
|
};
|
|
1032
1110
|
setShapeResizeId(selId);
|
|
1111
|
+
setMagnifying(true);
|
|
1033
1112
|
return;
|
|
1034
1113
|
}
|
|
1035
1114
|
}
|
|
@@ -1064,6 +1143,8 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1064
1143
|
};
|
|
1065
1144
|
const endSelectDrag = (dx, dy) => {
|
|
1066
1145
|
const st = stateRef.current;
|
|
1146
|
+
// Any precision sub-drag that turned the loupe on is ending now.
|
|
1147
|
+
setMagnifying(false);
|
|
1067
1148
|
// Resize commit: scale the shape by the final drag (clamped in
|
|
1068
1149
|
// buildResizePatch exactly like the live preview).
|
|
1069
1150
|
const rT = resizeTargetRef.current;
|
|
@@ -1158,6 +1239,7 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1158
1239
|
setDraggingId(null);
|
|
1159
1240
|
};
|
|
1160
1241
|
const cancelSelectDrag = () => {
|
|
1242
|
+
setMagnifying(false);
|
|
1161
1243
|
// No commit — dropping the gating ids snaps everything back.
|
|
1162
1244
|
dragTargetRef.current = null;
|
|
1163
1245
|
slideTargetRef.current = null;
|
|
@@ -1182,6 +1264,10 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1182
1264
|
dragEnded.value = false;
|
|
1183
1265
|
dragX.value = 0;
|
|
1184
1266
|
dragY.value = 0;
|
|
1267
|
+
// The loupe (turned on by beginSelectDrag only for precision sub-
|
|
1268
|
+
// drags) follows the live finger, not the hit-test origin.
|
|
1269
|
+
magTouchX.value = e.x;
|
|
1270
|
+
magTouchY.value = e.y;
|
|
1185
1271
|
// Hit-test at the touch-down point — onStart fires only after the
|
|
1186
1272
|
// pan threshold, so back out the accumulated translation.
|
|
1187
1273
|
runOnJS(beginSelectDrag)({
|
|
@@ -1191,6 +1277,8 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1191
1277
|
})
|
|
1192
1278
|
.onChange((e) => {
|
|
1193
1279
|
'worklet';
|
|
1280
|
+
magTouchX.value = e.x;
|
|
1281
|
+
magTouchY.value = e.y;
|
|
1194
1282
|
// World-space delta from the drag origin. Applied to the element
|
|
1195
1283
|
// only once draggingId is set; otherwise it affects nothing.
|
|
1196
1284
|
dragX.value = e.translationX / zoom.value;
|
|
@@ -1251,80 +1339,89 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1251
1339
|
const activeTool = props.tools.find((t) => t.id === props.activeToolId) ?? null;
|
|
1252
1340
|
const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
|
|
1253
1341
|
const { renderMeasurementStamp, onMeasurementStampPress, onMeasurementStampLongPress, resolveStampMeasurement, onMeasurementStampRemove, selection, } = props;
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1342
|
+
// The Skia scene props, shared by the main canvas and the loupe overlay. The
|
|
1343
|
+
// loupe re-runs the SAME scene with a magnifying world transform + a lens
|
|
1344
|
+
// clip (see loupeWrap) in a separate top-most canvas, so it shows live vector
|
|
1345
|
+
// content (the dragged line, handles, strokes) magnified above everything.
|
|
1346
|
+
const skiaProps = {
|
|
1347
|
+
width,
|
|
1348
|
+
height,
|
|
1349
|
+
effectiveCanvas: state.effectiveCanvas,
|
|
1350
|
+
worldTransform,
|
|
1351
|
+
resolveImageUrl,
|
|
1352
|
+
backgroundSkImage,
|
|
1353
|
+
valueFont,
|
|
1354
|
+
// Freehand drawing runs on the UI thread via `livePreview`, so the
|
|
1355
|
+
// JS-state pen preview is unused on native.
|
|
1356
|
+
penDrawingStroke: null,
|
|
1357
|
+
livePreview: freehand
|
|
1358
|
+
? {
|
|
1359
|
+
path: livePath,
|
|
1360
|
+
handoffPaths: [
|
|
1361
|
+
handoffPath0,
|
|
1362
|
+
handoffPath1,
|
|
1363
|
+
handoffPath2,
|
|
1364
|
+
handoffPath3,
|
|
1365
|
+
],
|
|
1366
|
+
color: freehand.color,
|
|
1367
|
+
width: freehand.width,
|
|
1368
|
+
cap: freehand.cap ?? 'round',
|
|
1369
|
+
dash: freehand.dash ?? false,
|
|
1370
|
+
opacity: freehand.variant === 'highlighter' ? 0.3 : 1,
|
|
1371
|
+
}
|
|
1372
|
+
: null,
|
|
1373
|
+
// UI-thread shape rubber-band (live + pending handoffs) — only while a
|
|
1374
|
+
// shape tool is active (handoffs always release within a frame or two of
|
|
1375
|
+
// the commit, before the tool can change).
|
|
1376
|
+
shapePreview: shapeDraw
|
|
1377
|
+
? {
|
|
1378
|
+
path: shapeBodyPath,
|
|
1379
|
+
headPath: shapeHeadPath,
|
|
1380
|
+
color: shapeDraw.color,
|
|
1381
|
+
width: shapeDraw.width,
|
|
1382
|
+
cap: shapeDraw.cap ?? 'round',
|
|
1383
|
+
dash: shapeDraw.dash ?? false,
|
|
1384
|
+
}
|
|
1385
|
+
: null,
|
|
1386
|
+
draggingId,
|
|
1387
|
+
dragTransform,
|
|
1388
|
+
resizingId,
|
|
1389
|
+
resizeTransform,
|
|
1390
|
+
selectedId: selection?.ids[0] ?? null,
|
|
1391
|
+
endpointDragId: epDragId,
|
|
1392
|
+
shapeEndpointDragId: shapeEpDragId,
|
|
1393
|
+
liveLineP1,
|
|
1394
|
+
liveLineP2,
|
|
1395
|
+
rectDragId,
|
|
1396
|
+
liveRect: {
|
|
1397
|
+
x: liveRectX,
|
|
1398
|
+
y: liveRectY,
|
|
1399
|
+
width: liveRectW,
|
|
1400
|
+
height: liveRectH,
|
|
1401
|
+
},
|
|
1402
|
+
shapeResizeId,
|
|
1403
|
+
shapeResizePath,
|
|
1404
|
+
shapeResizeBox: {
|
|
1405
|
+
x: shapeResizeBoxX,
|
|
1406
|
+
y: shapeResizeBoxY,
|
|
1407
|
+
width: shapeResizeBoxW,
|
|
1408
|
+
height: shapeResizeBoxH,
|
|
1409
|
+
},
|
|
1410
|
+
// Endpoint/corner handles are drag affordances — only the select tool can
|
|
1411
|
+
// act on them, so suppress them when the active tool has no drag support
|
|
1412
|
+
// (e.g. view mode's pan tool, where a selected measurement still shows its
|
|
1413
|
+
// tile chrome but must not advertise draggability).
|
|
1414
|
+
handleRadius: activeTool?.dragSelection ? handleRadius : undefined,
|
|
1415
|
+
handleRingWidth: activeTool?.dragSelection ? handleRingWidth : undefined,
|
|
1416
|
+
customPreview,
|
|
1417
|
+
};
|
|
1418
|
+
// Lens window for the loupe: an opaque white base + the magnified scene
|
|
1419
|
+
// clipped to a rounded square, a border, and a crosshair marking the exact
|
|
1420
|
+
// point under the finger. Drawn in the overlay canvas's screen space (the
|
|
1421
|
+
// magnification lives in loupeTransform, applied to `content`).
|
|
1422
|
+
const loupeClip = rrect(rect(loupeX, loupeY, loupeSize, loupeSize), LOUPE_RADIUS, LOUPE_RADIUS);
|
|
1423
|
+
const loupeWrap = (content) => (_jsxs(_Fragment, { children: [_jsxs(Group, { clip: loupeClip, children: [_jsx(Rect, { x: loupeX, y: loupeY, width: loupeSize, height: loupeSize, color: LOUPE_BG_COLOR }), content] }), _jsx(RoundedRect, { x: loupeX, y: loupeY, width: loupeSize, height: loupeSize, r: LOUPE_RADIUS, color: LOUPE_BORDER_COLOR, style: "stroke", strokeWidth: LOUPE_BORDER_WIDTH }), _jsx(Line, { p1: { x: loupeCx - LOUPE_CROSSHAIR_ARM, y: loupeCy }, p2: { x: loupeCx + LOUPE_CROSSHAIR_ARM, y: loupeCy }, color: LOUPE_CROSSHAIR_COLOR, strokeWidth: 1.5 }), _jsx(Line, { p1: { x: loupeCx, y: loupeCy - LOUPE_CROSSHAIR_ARM }, p2: { x: loupeCx, y: loupeCy + LOUPE_CROSSHAIR_ARM }, color: LOUPE_CROSSHAIR_COLOR, strokeWidth: 1.5 })] }));
|
|
1424
|
+
return (_jsxs(GestureHandlerRootView, { style: [{ width, height }, style], children: [_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
|
|
1328
1425
|
? (state.measurementsById.get(placed.measurementId) ?? null)
|
|
1329
1426
|
: (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.effectiveCanvas.tileScaleFactor, tileViewportScale: state.tileViewportScale, onStampPress: onMeasurementStampPress, onStampLongPress: onMeasurementStampLongPress, onRemove: () => {
|
|
1330
1427
|
const defaultRemove = () => {
|
|
@@ -1338,7 +1435,11 @@ export const AnnotationCanvasInner = (props) => {
|
|
|
1338
1435
|
return;
|
|
1339
1436
|
}
|
|
1340
1437
|
defaultRemove();
|
|
1341
|
-
} }, placed.id))) }))
|
|
1438
|
+
} }, placed.id))) })), magnifying && (_jsx(View, { pointerEvents: "none", style: StyleSheet.absoluteFill, children: AnnotationCanvasSkia({
|
|
1439
|
+
...skiaProps,
|
|
1440
|
+
worldTransform: loupeTransform,
|
|
1441
|
+
wrapContent: loupeWrap,
|
|
1442
|
+
}) }))] }));
|
|
1342
1443
|
};
|
|
1343
1444
|
const MeasurementStampOverlayItem = ({ placed, measurement, selected, dragging, sliding, endpointDragging, rectResizing, zoomSnapshot, zoom, panX, panY, dragX, dragY, slideCtx, epCtx, rectCtx, renderMeasurementStamp, tileScaleFactor, tileViewportScale, onStampPress, onStampLongPress, onRemove, }) => {
|
|
1344
1445
|
const size = stampTileSize(placed, tileScaleFactor, tileViewportScale);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SkFont, type SkPath, type SkTypefaceFontProvider, type Transforms3d } from '@shopify/react-native-skia';
|
|
1
|
+
import { type SkFont, type SkImage, type SkPath, type SkTypefaceFontProvider, type Transforms3d } from '@shopify/react-native-skia';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import type { AnnotationCanvasState, AnnotationStroke, StrokeCap } from '../../types/annotation.js';
|
|
4
4
|
type AnimatedPoint = {
|
|
@@ -21,6 +21,7 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
21
21
|
value: Transforms3d;
|
|
22
22
|
};
|
|
23
23
|
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
24
|
+
backgroundSkImage?: SkImage | null;
|
|
24
25
|
valueFont: SkFont | null;
|
|
25
26
|
textFontMgr?: SkTypefaceFontProvider | null;
|
|
26
27
|
penDrawingStroke: AnnotationStroke | null;
|
|
@@ -86,6 +87,7 @@ export interface AnnotationCanvasSkiaProps {
|
|
|
86
87
|
value: number;
|
|
87
88
|
};
|
|
88
89
|
customPreview?: ReactNode;
|
|
90
|
+
wrapContent?: (content: ReactNode) => ReactNode;
|
|
89
91
|
}
|
|
90
|
-
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, resolveImageUrl, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, customPreview, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
92
|
+
export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, resolveImageUrl, backgroundSkImage, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, customPreview, wrapContent, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
|
|
91
93
|
export {};
|
|
@@ -74,7 +74,8 @@ const SelectionBox = ({ bounds, isDragging, transform, }) => (_jsx(DraggableElem
|
|
|
74
74
|
// since the function-call pattern works identically on native we use it
|
|
75
75
|
// in both Inners for consistency. Don't add hooks here; this is a plain
|
|
76
76
|
// JSX-returning helper, not a component.
|
|
77
|
-
export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, resolveImageUrl, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, customPreview,
|
|
77
|
+
export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, resolveImageUrl, backgroundSkImage, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, customPreview, wrapContent, }) => {
|
|
78
|
+
const world = (_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, skImage: backgroundSkImage })), effectiveCanvas.strokes.map((stroke) => (_jsx(DraggableElement, { isDragging: stroke.id === draggingId, transform: dragTransform, children: _jsx(StrokeElement, { stroke: stroke }) }, stroke.id))), effectiveCanvas.shapes.map((shape) => {
|
|
78
79
|
// Line/arrow shapes support endpoint editing. When selected they show
|
|
79
80
|
// grab handles at both ends; during an endpoint drag the line renders
|
|
80
81
|
// from the live endpoints (one following the finger) — the shape twin
|
|
@@ -214,4 +215,6 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
|
|
|
214
215
|
return (_jsxs(_Fragment, { children: [_jsx(SelectionBox, { bounds: b, isDragging: isLive, transform: liveTransform }), resizeGeom && handleRadius != null && (_jsx(DraggableElement, { isDragging: isLive, transform: liveTransform, children: _jsx(Handle, { c: resizeGeom.handle, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR }) })), cornerResizable && handleRadius != null && (_jsxs(DraggableElement, { isDragging: isDragging, transform: dragTransform, children: [_jsx(Handle, { c: { x: b.minX, y: b.minY }, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR }), _jsx(Handle, { c: { x: b.maxX, y: b.minY }, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR }), _jsx(Handle, { c: { x: b.minX, y: b.maxY }, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR }), _jsx(Handle, { c: { x: b.maxX, y: b.maxY }, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR })] }))] }));
|
|
215
216
|
}
|
|
216
217
|
return null;
|
|
217
|
-
})(), penDrawingStroke && _jsx(StrokeElement, { stroke: penDrawingStroke }), shapePreview && (_jsxs(_Fragment, { children: [_jsx(Path, { path: shapePreview.path, color: shapePreview.color, style: "stroke", strokeWidth: shapePreview.width, strokeCap: toSkiaStrokeCap(shapePreview.cap), strokeJoin: "round", children: shapePreview.dash && (_jsx(DashPathEffect, { intervals: dashIntervals(shapePreview.width) })) }), _jsx(Path, { path: shapePreview.headPath, color: shapePreview.color, style: "fill" })] })), livePreview?.handoffPaths?.map((p, i) => (_jsx(Path, { path: p, color: livePreview.color, style: "stroke", strokeWidth: livePreview.width, strokeCap: toSkiaStrokeCap(livePreview.cap), strokeJoin: "round", opacity: livePreview.opacity, children: livePreview.dash && (_jsx(DashPathEffect, { intervals: dashIntervals(livePreview.width) })) }, i))), livePreview && (_jsx(Path, { path: livePreview.path, color: livePreview.color, style: "stroke", strokeWidth: livePreview.width, strokeCap: toSkiaStrokeCap(livePreview.cap), strokeJoin: "round", opacity: livePreview.opacity, children: livePreview.dash && (_jsx(DashPathEffect, { intervals: dashIntervals(livePreview.width) })) })), customPreview] })
|
|
218
|
+
})(), penDrawingStroke && _jsx(StrokeElement, { stroke: penDrawingStroke }), shapePreview && (_jsxs(_Fragment, { children: [_jsx(Path, { path: shapePreview.path, color: shapePreview.color, style: "stroke", strokeWidth: shapePreview.width, strokeCap: toSkiaStrokeCap(shapePreview.cap), strokeJoin: "round", children: shapePreview.dash && (_jsx(DashPathEffect, { intervals: dashIntervals(shapePreview.width) })) }), _jsx(Path, { path: shapePreview.headPath, color: shapePreview.color, style: "fill" })] })), livePreview?.handoffPaths?.map((p, i) => (_jsx(Path, { path: p, color: livePreview.color, style: "stroke", strokeWidth: livePreview.width, strokeCap: toSkiaStrokeCap(livePreview.cap), strokeJoin: "round", opacity: livePreview.opacity, children: livePreview.dash && (_jsx(DashPathEffect, { intervals: dashIntervals(livePreview.width) })) }, i))), livePreview && (_jsx(Path, { path: livePreview.path, color: livePreview.color, style: "stroke", strokeWidth: livePreview.width, strokeCap: toSkiaStrokeCap(livePreview.cap), strokeJoin: "round", opacity: livePreview.opacity, children: livePreview.dash && (_jsx(DashPathEffect, { intervals: dashIntervals(livePreview.width) })) })), customPreview] }));
|
|
219
|
+
return (_jsx(Canvas, { style: { width, height }, children: wrapContent ? wrapContent(world) : world }));
|
|
220
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { type SkImage } from '@shopify/react-native-skia';
|
|
1
2
|
import type { AnnotationBackgroundImage, BackgroundFit } from '../../../types/annotation.js';
|
|
3
|
+
export declare const useBackgroundSkImage: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>) => SkImage | null;
|
|
2
4
|
export interface BackgroundImageElementProps {
|
|
3
5
|
image: AnnotationBackgroundImage;
|
|
4
6
|
docWidth: number;
|
|
5
7
|
docHeight: number;
|
|
6
8
|
fit?: BackgroundFit;
|
|
7
9
|
resolveUrl?: (path: string) => Promise<string>;
|
|
10
|
+
skImage?: SkImage | null;
|
|
8
11
|
}
|
|
9
|
-
export declare const BackgroundImageElement: import("react").MemoExoticComponent<({ image, docWidth, docHeight, fit, resolveUrl, }: BackgroundImageElementProps) => import("react/jsx-runtime").JSX.Element | null>;
|
|
12
|
+
export declare const BackgroundImageElement: import("react").MemoExoticComponent<({ image, docWidth, docHeight, fit, resolveUrl, skImage: skImageProp, }: BackgroundImageElementProps) => import("react/jsx-runtime").JSX.Element | null>;
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Image, useImage } from '@shopify/react-native-skia';
|
|
3
3
|
import { memo, useEffect, useRef, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
: Math.min(docW / imgW, docH / imgH);
|
|
14
|
-
const w = imgW * scale;
|
|
15
|
-
const h = imgH * scale;
|
|
16
|
-
return { x: (docW - w) / 2, y: (docH - h) / 2, width: w, height: h };
|
|
17
|
-
};
|
|
18
|
-
export const BackgroundImageElement = memo(({ image, docWidth, docHeight, fit = 'contain', resolveUrl, }) => {
|
|
19
|
-
const [url, setUrl] = useState(image.downloadUrl);
|
|
4
|
+
import { imageDocRect } from '../viewport.js';
|
|
5
|
+
// Resolve a background image's (possibly expired) storage URL and decode it to
|
|
6
|
+
// an SkImage. Exported so a consumer can hoist the decode ABOVE the canvas and
|
|
7
|
+
// share one SkImage across multiple canvases (e.g. the main canvas + a
|
|
8
|
+
// magnifier overlay) — otherwise each canvas mounts its own loader and flashes
|
|
9
|
+
// a blank while it re-resolves + re-decodes. Returns null until loaded, or when
|
|
10
|
+
// `image` is null/undefined. Hooks run unconditionally regardless of `image`.
|
|
11
|
+
export const useBackgroundSkImage = (image, resolveUrl) => {
|
|
12
|
+
const [url, setUrl] = useState(image?.downloadUrl ?? '');
|
|
20
13
|
// Hold resolveUrl in a ref so re-resolution is keyed only on the image
|
|
21
14
|
// identity, not the function's. Consumers commonly pass an inline closure
|
|
22
15
|
// (a new reference each render); depending on it directly would re-run a
|
|
@@ -24,6 +17,8 @@ export const BackgroundImageElement = memo(({ image, docWidth, docHeight, fit =
|
|
|
24
17
|
const resolveUrlRef = useRef(resolveUrl);
|
|
25
18
|
resolveUrlRef.current = resolveUrl;
|
|
26
19
|
useEffect(() => {
|
|
20
|
+
if (!image)
|
|
21
|
+
return;
|
|
27
22
|
let cancelled = false;
|
|
28
23
|
const resolve = resolveUrlRef.current;
|
|
29
24
|
if (resolve) {
|
|
@@ -38,10 +33,17 @@ export const BackgroundImageElement = memo(({ image, docWidth, docHeight, fit =
|
|
|
38
33
|
return () => {
|
|
39
34
|
cancelled = true;
|
|
40
35
|
};
|
|
41
|
-
}, [image
|
|
42
|
-
|
|
36
|
+
}, [image?.downloadUrl, image?.storagePath]);
|
|
37
|
+
return useImage(url || null);
|
|
38
|
+
};
|
|
39
|
+
export const BackgroundImageElement = memo(({ image, docWidth, docHeight, fit = 'contain', resolveUrl, skImage: skImageProp, }) => {
|
|
40
|
+
// Self-load only when no pre-decoded image is provided; the hook still runs
|
|
41
|
+
// unconditionally (with a null image, so it no-ops) to satisfy hook rules.
|
|
42
|
+
const provided = skImageProp !== undefined;
|
|
43
|
+
const loaded = useBackgroundSkImage(provided ? null : image, resolveUrl);
|
|
44
|
+
const skImage = provided ? skImageProp : loaded;
|
|
43
45
|
if (!skImage)
|
|
44
46
|
return null;
|
|
45
|
-
const dims =
|
|
47
|
+
const dims = imageDocRect(image.widthPx, image.heightPx, docWidth, docHeight, fit);
|
|
46
48
|
return (_jsx(Image, { image: skImage, x: dims.x, y: dims.y, width: dims.width, height: dims.height, fit: "fill" }));
|
|
47
49
|
});
|