@reekon-tools/boldr-utils 1.19.4 → 1.19.6

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.
Files changed (27) hide show
  1. package/dist/annotation/canvas/AnnotationCanvasInner.d.ts +2 -0
  2. package/dist/annotation/canvas/AnnotationCanvasInner.js +3 -1
  3. package/dist/annotation/canvas/AnnotationCanvasInner.native.d.ts +2 -0
  4. package/dist/annotation/canvas/AnnotationCanvasInner.native.js +118 -8
  5. package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +23 -1
  6. package/dist/annotation/canvas/AnnotationCanvasSkia.js +89 -13
  7. package/dist/annotation/canvas/Tool.d.ts +4 -1
  8. package/dist/annotation/canvas/backgroundLayers.d.ts +20 -1
  9. package/dist/annotation/canvas/backgroundLayers.js +94 -3
  10. package/dist/annotation/canvas/elements/BackgroundImageElement.d.ts +6 -3
  11. package/dist/annotation/canvas/elements/BackgroundImageElement.js +16 -8
  12. package/dist/annotation/canvas/elements/BackgroundSvg.d.ts +1 -0
  13. package/dist/annotation/canvas/elements/BackgroundSvg.js +4 -4
  14. package/dist/annotation/canvas/elements/backgroundUrl.d.ts +2 -1
  15. package/dist/annotation/canvas/elements/backgroundUrl.js +15 -2
  16. package/dist/annotation/canvas/fileTileSwap.d.ts +12 -0
  17. package/dist/annotation/canvas/fileTileSwap.js +87 -0
  18. package/dist/annotation/canvas/stampLayout.d.ts +1 -1
  19. package/dist/annotation/canvas/stampLayout.js +10 -8
  20. package/dist/annotation/canvas/tools/selectTool.d.ts +18 -0
  21. package/dist/annotation/canvas/tools/selectTool.js +158 -42
  22. package/dist/annotation/canvas/useAnnotationCanvasState.js +3 -1
  23. package/dist/annotation/data/hooks/useAnnotationCanvasDoc.js +5 -1
  24. package/dist/exports.d.ts +1 -0
  25. package/dist/exports.js +1 -0
  26. package/dist/types/annotation.d.ts +10 -0
  27. package/package.json +1 -1
@@ -20,6 +20,8 @@ export interface AnnotationCanvasInnerProps {
20
20
  fractionalTolerance?: FractionalTolerance;
21
21
  decimalTolerance?: DecimalTolerance;
22
22
  resolveImageUrl?: (storagePath: string) => Promise<string>;
23
+ deferBackgroundToResolver?: boolean;
24
+ hideLinkBadges?: boolean;
23
25
  pickMeasurement?: () => Promise<MeasurementRef | null>;
24
26
  requestTextInput?: RequestTextInput;
25
27
  renderBackground?: RenderCanvasBackground;
@@ -27,7 +27,7 @@ const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
27
27
  // (SSR / the vitest environment), where 1 is the right answer.
28
28
  const devicePixelRatioOf = () => typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1;
29
29
  export const AnnotationCanvasInner = (props) => {
30
- const { resolveImageUrl, stampFontSource, stampValueFontSize = 14, gestures, width, height, style, activeToolId, tools, snapshotRef, renderBackground, } = props;
30
+ const { resolveImageUrl, deferBackgroundToResolver, hideLinkBadges, stampFontSource, stampValueFontSize = 14, gestures, width, height, style, activeToolId, tools, snapshotRef, renderBackground, } = props;
31
31
  // Snapshot plumbing for thumbnail capture: hold the <Canvas> ref and publish
32
32
  // a capture fn on the consumer's snapshotRef. encodeToBytes() yields PNG.
33
33
  const canvasRef = useCanvasRef();
@@ -315,6 +315,8 @@ export const AnnotationCanvasInner = (props) => {
315
315
  worldTransform: state.worldTransform,
316
316
  viewport: state.viewport,
317
317
  resolveImageUrl,
318
+ deferBackgroundToResolver,
319
+ hideLinkBadges,
318
320
  backgroundContent,
319
321
  canvasRef,
320
322
  valueFont,
@@ -21,6 +21,8 @@ export interface AnnotationCanvasInnerProps {
21
21
  fractionalTolerance?: FractionalTolerance;
22
22
  decimalTolerance?: DecimalTolerance;
23
23
  resolveImageUrl?: (storagePath: string) => Promise<string>;
24
+ deferBackgroundToResolver?: boolean;
25
+ hideLinkBadges?: boolean;
24
26
  pickMeasurement?: () => Promise<MeasurementRef | null>;
25
27
  requestTextInput?: RequestTextInput;
26
28
  renderBackground?: RenderCanvasBackground;
@@ -7,7 +7,7 @@ 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, backgroundLayerDocRect, } from './backgroundLayers.js';
10
+ import { MIN_BACKGROUND_DOC_EXTENT, backgroundLayersOf, backgroundLayerDocRect, backgroundLayerFullDocRect, } from './backgroundLayers.js';
11
11
  import { BackgroundSkImageLoader } from './elements/BackgroundImageElement.js';
12
12
  import { buildRemoveMeasurementOps, linePosOf, } from './measurementGeometry.js';
13
13
  import { planeEdgeLine, planeGridSegments, planeGridSegmentsForScale, resolvePlaneGridSpacingUm, } from './planeGeometry.js';
@@ -64,7 +64,7 @@ const LOUPE_CROSS_WIDTH_PX = 1.5;
64
64
  // is only re-synced once each gesture ends. The tool/tap path still hops to
65
65
  // JS (`runOnJS(true)`) since drawing/selection are React-state operations.
66
66
  export const AnnotationCanvasInner = (props) => {
67
- const { resolveImageUrl, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, renderBackground, } = props;
67
+ const { resolveImageUrl, deferBackgroundToResolver, hideLinkBadges, stampFontSource, stampValueFontSize = 14, width, height, style, magnifierTopOffset = 0, renderBackground, } = props;
68
68
  const valueFont = useFont(stampFontSource, stampValueFontSize);
69
69
  // The animated-viewport implementation needs the Reanimated shared values,
70
70
  // which are created below AFTER the state hook — so the hook gets a stable
@@ -661,6 +661,9 @@ export const AnnotationCanvasInner = (props) => {
661
661
  // goes through buildResizePatch, which clamps identically.
662
662
  const [resizingId, setResizingId] = useState(null);
663
663
  const resizeCtx = useSharedValue({ px: 0, py: 0, hx: 0, hy: 0, minS: 1, maxS: 1 });
664
+ // The grab-start geometry rides along to the commit: a background offers
665
+ // four corner handles, each scaling about its opposite corner, so the patch
666
+ // must reuse whichever geometry the grab's hit-test chose.
664
667
  const resizeTargetRef = useRef(null);
665
668
  const resizeTransform = useDerivedValue(() => {
666
669
  'worklet';
@@ -684,6 +687,60 @@ export const AnnotationCanvasInner = (props) => {
684
687
  { translateY: -c.py },
685
688
  ];
686
689
  });
690
+ // Background crop-bar drag. `bgCropId` (React state) gates which layer
691
+ // clips to the live rect; `bgCropCtx` carries the grabbed edge (0 top ·
692
+ // 1 bottom · 2 left · 3 right), the visible doc rect at grab-start and the
693
+ // full (uncropped) image's doc rect it may expand back out to — so the
694
+ // preview is a pure UI-thread rect rebuild driven by dragX/dragY. The
695
+ // commit on release goes through buildBackgroundCropPatch, which clamps
696
+ // identically.
697
+ const [bgCropId, setBgCropId] = useState(null);
698
+ const bgCropTargetRef = useRef(null);
699
+ const bgCropCtx = useSharedValue({ edge: 0, x: 0, y: 0, w: 0, h: 0, bx: 0, by: 0, bw: 0, bh: 0 });
700
+ const liveBgCropRect = useDerivedValue(() => {
701
+ 'worklet';
702
+ // WORKLET TWIN of selectTool's cropDragVisibleRect — keep in sync.
703
+ const c = bgCropCtx.value;
704
+ let x = c.x;
705
+ let y = c.y;
706
+ let w = c.w;
707
+ let h = c.h;
708
+ if (c.edge === 0) {
709
+ const newY = Math.min(Math.max(c.y + dragY.value, c.by), c.y + c.h - MIN_BACKGROUND_DOC_EXTENT);
710
+ h = c.y + c.h - newY;
711
+ y = newY;
712
+ }
713
+ else if (c.edge === 1) {
714
+ h = Math.min(Math.max(c.h + dragY.value, MIN_BACKGROUND_DOC_EXTENT), c.by + c.bh - c.y);
715
+ }
716
+ else if (c.edge === 2) {
717
+ const newX = Math.min(Math.max(c.x + dragX.value, c.bx), c.x + c.w - MIN_BACKGROUND_DOC_EXTENT);
718
+ w = c.x + c.w - newX;
719
+ x = newX;
720
+ }
721
+ else {
722
+ w = Math.min(Math.max(c.w + dragX.value, MIN_BACKGROUND_DOC_EXTENT), c.bx + c.bw - c.x);
723
+ }
724
+ return { x, y, width: w, height: h };
725
+ });
726
+ // Per-component slices for the live outline (Skia props take one animated
727
+ // value each; the clip takes the whole rect).
728
+ const liveBgCropX = useDerivedValue(() => {
729
+ 'worklet';
730
+ return liveBgCropRect.value.x;
731
+ });
732
+ const liveBgCropY = useDerivedValue(() => {
733
+ 'worklet';
734
+ return liveBgCropRect.value.y;
735
+ });
736
+ const liveBgCropW = useDerivedValue(() => {
737
+ 'worklet';
738
+ return liveBgCropRect.value.width;
739
+ });
740
+ const liveBgCropH = useDerivedValue(() => {
741
+ 'worklet';
742
+ return liveBgCropRect.value.height;
743
+ });
687
744
  // Geometric-shape corner resize (rect/ellipse/polygon). `shapeResizeId`
688
745
  // (React state) gates which shape renders live; `shapeResizeCtx` carries the
689
746
  // fixed (opposite) corner and the grabbed corner's start position (from
@@ -1477,11 +1534,42 @@ export const AnnotationCanvasInner = (props) => {
1477
1534
  minS: resizeGeom.minScale,
1478
1535
  maxS: resizeGeom.maxScale,
1479
1536
  };
1480
- resizeTargetRef.current = { id: selId };
1537
+ resizeTargetRef.current = { id: selId, geom: resizeGeom };
1481
1538
  setResizingId(selId);
1482
1539
  setMagnifying(true);
1483
1540
  return;
1484
1541
  }
1542
+ // Crop bar on a selected, unlocked background layer — the edge
1543
+ // windows the image (non-destructively) instead of scaling it.
1544
+ const cropEdge = cfg.hitTestBackgroundCropBar?.(st.ctx.document, selId, world, zoomNow);
1545
+ if (cropEdge) {
1546
+ const cropLayer = backgroundLayersOf(st.ctx.document.viewport).find((l) => l.id === selId);
1547
+ if (cropLayer) {
1548
+ const visible = backgroundLayerDocRect(cropLayer);
1549
+ const full = backgroundLayerFullDocRect(cropLayer);
1550
+ bgCropCtx.value = {
1551
+ edge: cropEdge === 'top'
1552
+ ? 0
1553
+ : cropEdge === 'bottom'
1554
+ ? 1
1555
+ : cropEdge === 'left'
1556
+ ? 2
1557
+ : 3,
1558
+ x: visible.x,
1559
+ y: visible.y,
1560
+ w: visible.width,
1561
+ h: visible.height,
1562
+ bx: full.x,
1563
+ by: full.y,
1564
+ bw: full.width,
1565
+ bh: full.height,
1566
+ };
1567
+ bgCropTargetRef.current = { id: selId, edge: cropEdge };
1568
+ setBgCropId(selId);
1569
+ setMagnifying(true);
1570
+ return;
1571
+ }
1572
+ }
1485
1573
  // Rectangle-annotation corner handle — also selected-element-only.
1486
1574
  const rectCornerHit = cfg.hitTestRectCorner?.(st.ctx.document, selId, world, zoomNow);
1487
1575
  if (rectCornerHit) {
@@ -1580,10 +1668,7 @@ export const AnnotationCanvasInner = (props) => {
1580
1668
  const rT = resizeTargetRef.current;
1581
1669
  if (rT) {
1582
1670
  if (dx !== 0 || dy !== 0) {
1583
- const patch = cfg.buildResizePatch?.(st.ctx.document, rT.id, {
1584
- x: dx,
1585
- y: dy,
1586
- });
1671
+ const patch = cfg.buildResizePatch?.(st.ctx.document, rT.id, { x: dx, y: dy }, rT.geom);
1587
1672
  if (patch)
1588
1673
  st.ctx.commit(patch);
1589
1674
  }
@@ -1591,6 +1676,19 @@ export const AnnotationCanvasInner = (props) => {
1591
1676
  setResizingId(null);
1592
1677
  return;
1593
1678
  }
1679
+ // Crop commit: window the layer to the live rect's final edges
1680
+ // (clamped in buildBackgroundCropPatch exactly like the live preview).
1681
+ const cropT = bgCropTargetRef.current;
1682
+ if (cropT) {
1683
+ if (dx !== 0 || dy !== 0) {
1684
+ const patch = cfg.buildBackgroundCropPatch?.(st.ctx.document, cropT.id, cropT.edge, { x: dx, y: dy });
1685
+ if (patch)
1686
+ st.ctx.commit(patch);
1687
+ }
1688
+ bgCropTargetRef.current = null;
1689
+ setBgCropId(null);
1690
+ return;
1691
+ }
1594
1692
  // Rect-corner commit: drag the grabbed corner by the world delta
1595
1693
  // (opposite corner fixed); anchor re-centers in the same patch.
1596
1694
  const rectT = rectTargetRef.current;
@@ -1678,6 +1776,7 @@ export const AnnotationCanvasInner = (props) => {
1678
1776
  resizeTargetRef.current = null;
1679
1777
  rectTargetRef.current = null;
1680
1778
  shapeResizeTargetRef.current = null;
1779
+ bgCropTargetRef.current = null;
1681
1780
  setDraggingId(null);
1682
1781
  setSlidingId(null);
1683
1782
  setEpDragId(null);
@@ -1685,6 +1784,7 @@ export const AnnotationCanvasInner = (props) => {
1685
1784
  setResizingId(null);
1686
1785
  setRectDragId(null);
1687
1786
  setShapeResizeId(null);
1787
+ setBgCropId(null);
1688
1788
  };
1689
1789
  return Gesture.Pan()
1690
1790
  .minPointers(1)
@@ -1949,6 +2049,8 @@ export const AnnotationCanvasInner = (props) => {
1949
2049
  effectiveCanvas: state.effectiveCanvas,
1950
2050
  worldTransform,
1951
2051
  resolveImageUrl,
2052
+ deferBackgroundToResolver,
2053
+ hideLinkBadges,
1952
2054
  backgroundSkImages,
1953
2055
  // Shared with the loupe overlay below (it re-runs this same scene through a
1954
2056
  // magnifying transform), so the lens shows the substrate magnified instead
@@ -1997,6 +2099,14 @@ export const AnnotationCanvasInner = (props) => {
1997
2099
  shapeEndpointDragId: shapeEpDragId,
1998
2100
  liveLineP1,
1999
2101
  liveLineP2,
2102
+ bgCropId,
2103
+ liveBgCrop: {
2104
+ x: liveBgCropX,
2105
+ y: liveBgCropY,
2106
+ width: liveBgCropW,
2107
+ height: liveBgCropH,
2108
+ },
2109
+ liveBgCropClip: liveBgCropRect,
2000
2110
  rectDragId,
2001
2111
  liveRect: {
2002
2112
  x: liveRectX,
@@ -2045,7 +2155,7 @@ export const AnnotationCanvasInner = (props) => {
2045
2155
  // drawing to the canvas box by construction, but the stamp overlay is
2046
2156
  // plain RN views — without clipping, tiles panned past the edge of a
2047
2157
  // non-fullscreen canvas (e.g. a diagram strip) escape into surrounding UI.
2048
- _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
2158
+ _jsxs(GestureHandlerRootView, { style: [{ width, height, overflow: 'hidden' }, style], children: [backgroundLayers.map((layer) => (_jsx(BackgroundSkImageLoader, { layer: layer, resolveUrl: resolveImageUrl, deferToResolver: deferBackgroundToResolver, 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
2049
2159
  ? (state.measurementsById.get(placed.measurementId) ?? null)
2050
2160
  : (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
2051
2161
  ? () => {
@@ -23,6 +23,8 @@ export interface AnnotationCanvasSkiaProps {
23
23
  };
24
24
  viewport?: ViewportState;
25
25
  resolveImageUrl?: (storagePath: string) => Promise<string>;
26
+ deferBackgroundToResolver?: boolean;
27
+ hideLinkBadges?: boolean;
26
28
  backgroundSkImages?: Record<string, SkImage | null>;
27
29
  backgroundContent?: ReactNode;
28
30
  valueFont: SkFont | null;
@@ -73,6 +75,26 @@ export interface AnnotationCanvasSkiaProps {
73
75
  width: AnimatedNumber;
74
76
  height: AnimatedNumber;
75
77
  };
78
+ bgCropId?: string | null;
79
+ liveBgCrop?: {
80
+ x: AnimatedNumber;
81
+ y: AnimatedNumber;
82
+ width: AnimatedNumber;
83
+ height: AnimatedNumber;
84
+ };
85
+ liveBgCropClip?: {
86
+ x: number;
87
+ y: number;
88
+ width: number;
89
+ height: number;
90
+ } | {
91
+ value: {
92
+ x: number;
93
+ y: number;
94
+ width: number;
95
+ height: number;
96
+ };
97
+ };
76
98
  shapeResizeId?: string | null;
77
99
  shapeResizePath?: SkPath | {
78
100
  value: SkPath;
@@ -105,5 +127,5 @@ export interface AnnotationCanvasSkiaProps {
105
127
  wrapContent?: (content: ReactNode) => ReactNode;
106
128
  canvasRef?: RefObject<CanvasRef | null>;
107
129
  }
108
- export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, backgroundContent, 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, planeOutlineScale, measurementLineScale, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
130
+ export declare const AnnotationCanvasSkia: ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, deferBackgroundToResolver, hideLinkBadges, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, bgCropId, liveBgCrop, liveBgCropClip, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale, measurementLineScale, customPreview, wrapContent, canvasRef, }: AnnotationCanvasSkiaProps) => import("react/jsx-runtime").JSX.Element;
109
131
  export {};
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Canvas, Circle, DashPathEffect, Group, Line, Path, Rect, Skia, } from '@shopify/react-native-skia';
2
+ import { Canvas, Circle, DashPathEffect, Group, Line, Path, Rect, RoundedRect, Skia, } from '@shopify/react-native-skia';
3
3
  import { normalizeRect, placementOf } from './measurementGeometry.js';
4
4
  import { arrowheadTriangle, dashIntervals, toSkiaStrokeCap, } from './strokeGeometry.js';
5
5
  import { SELECTION_PAD, textResizeGeometry, textShapeBounds, } from './textGeometry.js';
6
6
  import { visualShapeBounds } from './shapeGeometry.js';
7
- import { backgroundLayerDocRect, backgroundLayersOf, isBackgroundLayerLocked, } from './backgroundLayers.js';
7
+ import { backgroundCropBars, backgroundLayerDocRect, backgroundLayersOf, isBackgroundLayerLocked, } from './backgroundLayers.js';
8
+ import { YellowColors } from '../../theme/colors.js';
8
9
  import { BackgroundLayerElement } from './elements/BackgroundImageElement.js';
9
10
  import { PlaneElement, PLANE_CHROME_COLOR, PLANE_GRID_COLOR, PLANE_GRID_OPACITY, PLANE_GRID_WIDTH, PLANE_OUTLINE_WIDTH, } from './elements/PlaneElement.js';
10
11
  import { ShapeElement } from './elements/ShapeElement.js';
@@ -29,6 +30,30 @@ const MEASUREMENT_HANDLE_COLOR = '#0066FF';
29
30
  // hit-test the text resize handle, which sits on the padded box corner.
30
31
  const SELECTION_COLOR = '#0066FF';
31
32
  const SELECTION_STROKE = 1.5;
33
+ // Outline of a selected LOCKED background layer: the lock accent (the same
34
+ // yellow the selected group-header border uses), distinct from the blue
35
+ // manipulable-selection chrome — locked means "selected, but pinned".
36
+ const BACKGROUND_LOCKED_COLOR = YellowColors.mainYellow;
37
+ // Link badge on a LINKED background layer (layer.fileId — an expanded file
38
+ // tile, see fileTileSwap.ts): a chain glyph in a white disc at the layer's
39
+ // top-right, saying "this is a group file's image, not a plain background".
40
+ // Always on (selected or not) so the link is legible at a glance — which
41
+ // makes it the one piece of chrome selection-clearing does NOT strip before a
42
+ // capture: hosts must pass `hideLinkBadges` while snapshotting for
43
+ // thumbnails/share/export, or the badge bakes into the output image.
44
+ // Doc-space sized off the visible rect, like the selection stroke and bars.
45
+ const LINKED_BADGE_BG = '#FFFFFF';
46
+ const LINKED_BADGE_INK = '#181A1C';
47
+ const LINKED_BADGE_RADIUS_RATIO = 0.05;
48
+ // Lucide `link` (24-unit viewBox, stroke-width 2) — drawn stroked inside a
49
+ // scale-to-badge Group, so the stroke width scales with the glyph.
50
+ const LINKED_GLYPH_SVG = 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' +
51
+ 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71';
52
+ // Lazy + cached: on web this module is only evaluated after CanvasKit loads
53
+ // (WithSkiaWeb gates the import), but deferring to first draw costs nothing
54
+ // and can't regress if that ever changes.
55
+ let linkedGlyphPath = null;
56
+ const getLinkedGlyphPath = () => (linkedGlyphPath ?? (linkedGlyphPath = Skia.Path.MakeFromSVGString(LINKED_GLYPH_SVG)));
32
57
  // A draggable selection handle (line endpoints, rect corners, text resize):
33
58
  // a white disc with a colored ring, so it reads as a distinct, grabbable knob
34
59
  // over a line of ANY color — including the default measurement-line blue, which
@@ -80,7 +105,7 @@ const SelectionBox = ({ bounds, isDragging, transform, }) => (_jsx(DraggableElem
80
105
  // since the function-call pattern works identically on native we use it
81
106
  // in both Inners for consistency. Don't add hooks here; this is a plain
82
107
  // JSX-returning helper, not a component.
83
- export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, backgroundSkImages, backgroundContent, 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, planeOutlineScale = 1, measurementLineScale = 1, customPreview, wrapContent, canvasRef, }) => {
108
+ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTransform, viewport, resolveImageUrl, deferBackgroundToResolver, hideLinkBadges, backgroundSkImages, backgroundContent, valueFont, textFontMgr, penDrawingStroke, livePreview, shapePreview, draggingId, dragTransform, resizingId, resizeTransform, selectedId, endpointDragId, shapeEndpointDragId, liveLineP1, liveLineP2, rectDragId, liveRect, bgCropId, liveBgCrop, liveBgCropClip, shapeResizeId, shapeResizePath, shapeResizeBox, handleRadius, handleRingWidth, showPlaneHandles, planeDragId, planeDragIndex, planeDragPath, planeDragGridPath, planeDragHandle, planeOutlineScale = 1, measurementLineScale = 1, customPreview, wrapContent, canvasRef, }) => {
84
109
  // Doc rect a SCALE-mode plane grid covers: the first background layer (the
85
110
  // photo being calibrated), else the document rect. Pinned to layer 0 like
86
111
  // the tile-sizing reference — adding a second image must not re-span the
@@ -94,7 +119,39 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
94
119
  width: effectiveCanvas.viewport.width,
95
120
  height: effectiveCanvas.viewport.height,
96
121
  };
97
- const world = (_jsxs(Group, { transform: worldTransform, children: [backgroundContent, backgroundLayersOf(effectiveCanvas.viewport).map((layer) => (_jsx(DraggableElement, { isDragging: layer.id === draggingId || layer.id === resizingId, transform: layer.id === resizingId ? resizeTransform : dragTransform, children: _jsx(BackgroundLayerElement, { layer: layer, resolveUrl: resolveImageUrl, skImage: backgroundSkImages?.[layer.id], viewport: viewport, screenWidth: width, screenHeight: height }) }, layer.id))), (effectiveCanvas.planes ?? []).map((plane) => {
122
+ const world = (_jsxs(Group, { transform: worldTransform, children: [backgroundContent, backgroundLayersOf(effectiveCanvas.viewport).map((layer) => {
123
+ // Native crop-bar drag: clip to the LIVE visible rect (doc space)
124
+ // instead of the committed crop window, which is suppressed so a bar
125
+ // dragged outward reveals what the window hid.
126
+ const isCropDrag = layer.id === bgCropId && liveBgCropClip != null;
127
+ const element = (_jsx(BackgroundLayerElement, { layer: layer, resolveUrl: resolveImageUrl, deferToResolver: deferBackgroundToResolver, skImage: backgroundSkImages?.[layer.id], viewport: viewport, screenWidth: width, screenHeight: height, suppressCrop: isCropDrag }));
128
+ // Link badge on a LINKED layer (an expanded file tile) — always on,
129
+ // selected or not, locked or not: the link is a fact about the layer,
130
+ // not an affordance. A sibling of the (possibly live-clipped) image
131
+ // inside the same DraggableElement, so it rides drags and resizes;
132
+ // hidden during a live crop drag (it would sit on stale committed
133
+ // edges) and under `hideLinkBadges` (capture/export renders).
134
+ const badge = layer.fileId && !isCropDrag && !hideLinkBadges
135
+ ? (() => {
136
+ const glyph = getLinkedGlyphPath();
137
+ if (!glyph)
138
+ return null;
139
+ const r = backgroundLayerDocRect(layer);
140
+ const badgeR = Math.min(r.width, r.height) * LINKED_BADGE_RADIUS_RATIO;
141
+ const bc = {
142
+ x: r.x + r.width - 2 * badgeR,
143
+ y: r.y + 2 * badgeR,
144
+ };
145
+ const glyphSize = badgeR * 1.2;
146
+ return (_jsxs(_Fragment, { children: [_jsx(Circle, { c: bc, r: badgeR, color: LINKED_BADGE_BG }), _jsx(Group, { transform: [
147
+ { translateX: bc.x - glyphSize / 2 },
148
+ { translateY: bc.y - glyphSize / 2 },
149
+ { scale: glyphSize / 24 },
150
+ ], children: _jsx(Path, { path: glyph, color: LINKED_BADGE_INK, style: "stroke", strokeWidth: 2, strokeCap: "round", strokeJoin: "round" }) })] }));
151
+ })()
152
+ : null;
153
+ return (_jsxs(DraggableElement, { isDragging: layer.id === draggingId || layer.id === resizingId, transform: layer.id === resizingId ? resizeTransform : dragTransform, children: [isCropDrag ? (_jsx(Group, { clip: liveBgCropClip, children: element })) : (element), badge] }, layer.id));
154
+ }), (effectiveCanvas.planes ?? []).map((plane) => {
98
155
  if (plane.id === planeDragId && planeDragPath) {
99
156
  const statics = plane.mode === 'perspective'
100
157
  ? plane.corners
@@ -247,21 +304,40 @@ export const AnnotationCanvasSkia = ({ width, height, effectiveCanvas, worldTran
247
304
  shape.kind === 'polygon';
248
305
  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 })] }))] }));
249
306
  }
250
- // A selected background layer: outline its doc rect and offer ONE
251
- // bottom-right handle — backgrounds scale uniformly about their
252
- // top-left corner (the transform origin), so a single opposite-corner
253
- // grab is the whole gizmo. Chrome tracks the live drag/resize
254
- // transform the same way the layer itself does above. A LOCKED layer
255
- // (backgroundLayerIsLocked) keeps the outline — it is selected, and
256
- // the host's chrome offers the lock toggle — but gets no handle: it
257
- // cannot scale, so a grabbable corner would lie.
307
+ // A selected background layer: outline its VISIBLE (cropped) doc rect
308
+ // and, when unlocked, offer FOUR corner handles (each scales about
309
+ // its opposite corner) plus four edge crop bars (raster layers only —
310
+ // dragging a bar windows the image non-destructively). Chrome tracks
311
+ // the live drag/resize transform the same way the layer itself does
312
+ // above. A LOCKED layer (backgroundLayerIsLocked) keeps the outline —
313
+ // it is selected, and the host's chrome offers the lock toggle — but
314
+ // recolored to the lock yellow and with no handles: it cannot scale
315
+ // or crop, so a grabbable gizmo would lie. During a native crop-bar
316
+ // drag the chrome is just the LIVE outline — handles and bars would
317
+ // sit on stale committed edges.
258
318
  const layer = backgroundLayersOf(effectiveCanvas.viewport).find((l) => l.id === selectedId);
259
319
  if (layer) {
320
+ if (layer.id === bgCropId && liveBgCrop) {
321
+ return (_jsx(Rect, { x: liveBgCrop.x, y: liveBgCrop.y, width: liveBgCrop.width, height: liveBgCrop.height, color: SELECTION_COLOR, style: "stroke", strokeWidth: SELECTION_STROKE }));
322
+ }
260
323
  const locked = isBackgroundLayerLocked(effectiveCanvas.viewport, layer.id);
261
324
  const r = backgroundLayerDocRect(layer);
262
325
  const isResizing = layer.id === resizingId;
263
326
  const liveTransform = isResizing ? resizeTransform : dragTransform;
264
- return (_jsxs(DraggableElement, { isDragging: isDragging || isResizing, transform: liveTransform, children: [_jsx(Rect, { x: r.x, y: r.y, width: r.width, height: r.height, color: SELECTION_COLOR, style: "stroke", strokeWidth: SELECTION_STROKE }), handleRadius != null && !locked && (_jsx(Handle, { c: { x: r.x + r.width, y: r.y + r.height }, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR }))] }));
327
+ const corners = [
328
+ { x: r.x, y: r.y },
329
+ { x: r.x + r.width, y: r.y },
330
+ { x: r.x, y: r.y + r.height },
331
+ { x: r.x + r.width, y: r.y + r.height },
332
+ ];
333
+ return (_jsxs(DraggableElement, { isDragging: isDragging || isResizing, transform: liveTransform, children: [_jsx(Rect, { x: r.x, y: r.y, width: r.width, height: r.height, color: locked ? BACKGROUND_LOCKED_COLOR : SELECTION_COLOR, style: "stroke", strokeWidth: SELECTION_STROKE }), !locked &&
334
+ layer.format !== 'svg' &&
335
+ backgroundCropBars(layer).map((bar) => {
336
+ const inset = Math.min(bar.width, bar.height) * 0.18;
337
+ return (_jsxs(Group, { children: [_jsx(RoundedRect, { x: bar.x, y: bar.y, width: bar.width, height: bar.height, r: Math.min(bar.width, bar.height) / 2, color: "#FFFFFF" }), _jsx(RoundedRect, { x: bar.x + inset, y: bar.y + inset, width: bar.width - 2 * inset, height: bar.height - 2 * inset, r: (Math.min(bar.width, bar.height) - 2 * inset) / 2, color: SELECTION_COLOR })] }, bar.edge));
338
+ }), handleRadius != null &&
339
+ !locked &&
340
+ corners.map((c, i) => (_jsx(Handle, { c: c, r: handleRadius, ringWidth: handleRingWidth, color: SELECTION_COLOR }, i)))] }));
265
341
  }
266
342
  return null;
267
343
  })(), 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] }));
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode, ComponentType } from 'react';
2
2
  import type { AnnotationCanvasState, AnnotationDocumentPatch, AnnotationElement, AnnotationElementId, Selection, StrokeCap, Vec2 } from '../../types/annotation.js';
3
+ import type { BackgroundCropEdge } from './backgroundLayers.js';
3
4
  import type { MeasurementRef } from './measurementPicker.js';
4
5
  import type { RectCorner } from './measurementGeometry.js';
5
6
  import type { ShapeToolKind } from './shapeGeometry.js';
@@ -90,7 +91,9 @@ export interface DragSelectionConfig {
90
91
  locked: boolean;
91
92
  } | null;
92
93
  hitTestResizeHandle?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number): ResizeGeometry | null;
93
- buildResizePatch?(doc: AnnotationCanvasState, id: AnnotationElementId, delta: Vec2): AnnotationDocumentPatch | null;
94
+ buildResizePatch?(doc: AnnotationCanvasState, id: AnnotationElementId, delta: Vec2, geom?: ResizeGeometry): AnnotationDocumentPatch | null;
95
+ hitTestBackgroundCropBar?(doc: AnnotationCanvasState, id: AnnotationElementId, world: Vec2, zoom: number): BackgroundCropEdge | null;
96
+ buildBackgroundCropPatch?(doc: AnnotationCanvasState, id: AnnotationElementId, edge: BackgroundCropEdge, delta: Vec2): AnnotationDocumentPatch | null;
94
97
  }
95
98
  export interface PlaneEditConfig {
96
99
  hitHandle(doc: AnnotationCanvasState, world: Vec2, zoom: number): number | null;
@@ -1,16 +1,35 @@
1
- import type { AnnotationBackgroundLayer, AnnotationViewport, BackgroundLayerTransform } from '../../types/annotation.js';
1
+ import type { AnnotationBackgroundLayer, AnnotationViewport, BackgroundLayerCrop, BackgroundLayerTransform } from '../../types/annotation.js';
2
2
  export declare const LEGACY_BACKGROUND_LAYER_ID = "background-legacy";
3
3
  export declare const BACKGROUND_PLACEMENT_GAP_RATIO = 0.02;
4
4
  export declare const backgroundLayersOf: (viewport: AnnotationViewport) => AnnotationBackgroundLayer[];
5
5
  export declare const backgroundLayerIsLocked: (layer: AnnotationBackgroundLayer, index: number) => boolean;
6
6
  export declare const isBackgroundLayerLocked: (viewport: AnnotationViewport, id: string) => boolean;
7
7
  export declare const defaultBackgroundLayerLocked: (existing: AnnotationBackgroundLayer[]) => boolean;
8
+ export declare const backgroundLayerCropRect: (layer: AnnotationBackgroundLayer) => BackgroundLayerCrop;
8
9
  export declare const backgroundLayerDocRect: (layer: AnnotationBackgroundLayer) => {
9
10
  x: number;
10
11
  y: number;
11
12
  width: number;
12
13
  height: number;
13
14
  };
15
+ export declare const backgroundLayerFullDocRect: (layer: AnnotationBackgroundLayer) => {
16
+ x: number;
17
+ y: number;
18
+ width: number;
19
+ height: number;
20
+ };
21
+ export declare const MIN_BACKGROUND_DOC_EXTENT = 16;
22
+ export declare const MAX_BACKGROUND_DOC_EXTENT = 65536;
23
+ export type BackgroundCropEdge = 'top' | 'bottom' | 'left' | 'right';
24
+ export declare const BACKGROUND_CROP_EDGES: readonly BackgroundCropEdge[];
25
+ export interface BackgroundCropBar {
26
+ edge: BackgroundCropEdge;
27
+ x: number;
28
+ y: number;
29
+ width: number;
30
+ height: number;
31
+ }
32
+ export declare const backgroundCropBars: (layer: AnnotationBackgroundLayer) => BackgroundCropBar[];
14
33
  export declare const backgroundLayersUnionRect: (layers: AnnotationBackgroundLayer[]) => {
15
34
  x: number;
16
35
  y: number;
@@ -69,14 +69,105 @@ export const isBackgroundLayerLocked = (viewport, id) => {
69
69
  // against it. Written explicitly onto the new layer so the state survives
70
70
  // later removals — a second image stays free even once it is the only one.
71
71
  export const defaultBackgroundLayerLocked = (existing) => existing.length === 0;
72
- // The doc-space rectangle a layer occupies: the intrinsic pixel grid under
73
- // the layer's translate+scale.
74
- export const backgroundLayerDocRect = (layer) => ({
72
+ // The layer's crop window in intrinsic pixels, clamped defensively into the
73
+ // image bounds (a hand-edited or future-client doc must not push the visible
74
+ // rect outside the bytes). Absent crop = the full image.
75
+ export const backgroundLayerCropRect = (layer) => {
76
+ const c = layer.crop;
77
+ if (!c)
78
+ return { x: 0, y: 0, width: layer.widthPx, height: layer.heightPx };
79
+ const x = Math.min(Math.max(c.x, 0), layer.widthPx);
80
+ const y = Math.min(Math.max(c.y, 0), layer.heightPx);
81
+ return {
82
+ x,
83
+ y,
84
+ width: Math.min(Math.max(c.width, 0), layer.widthPx - x),
85
+ height: Math.min(Math.max(c.height, 0), layer.heightPx - y),
86
+ };
87
+ };
88
+ // The doc-space rectangle a layer occupies: its VISIBLE (crop-windowed)
89
+ // pixel grid under the layer's translate+scale. Everything that treats "the
90
+ // layer" as a rect — selection chrome, hit-tests, fit/union math, new-layer
91
+ // placement — reads this, so a crop moves all of them together; only the
92
+ // renderer additionally knows the full image extends past it (clipped, see
93
+ // BackgroundLayerElement).
94
+ export const backgroundLayerDocRect = (layer) => {
95
+ const c = backgroundLayerCropRect(layer);
96
+ return {
97
+ x: layer.transform.x + c.x * layer.transform.scaleX,
98
+ y: layer.transform.y + c.y * layer.transform.scaleY,
99
+ width: c.width * layer.transform.scaleX,
100
+ height: c.height * layer.transform.scaleY,
101
+ };
102
+ };
103
+ // The full (uncropped) image's doc rect — the bounds a crop edge can expand
104
+ // back out to.
105
+ export const backgroundLayerFullDocRect = (layer) => ({
75
106
  x: layer.transform.x,
76
107
  y: layer.transform.y,
77
108
  width: layer.widthPx * layer.transform.scaleX,
78
109
  height: layer.heightPx * layer.transform.scaleY,
79
110
  });
111
+ // Doc-space clamp on a background layer's larger doc dimension (corner
112
+ // resize) and on its visible extent (crop). The floor keeps a drag from
113
+ // collapsing a layer below grabbable size; the ceiling keeps the doc rect
114
+ // (which feeds fit math and the web SVG raster planner) from exploding to
115
+ // numerically silly sizes. Here rather than in selectTool because the native
116
+ // crop preview worklet needs the floor too.
117
+ export const MIN_BACKGROUND_DOC_EXTENT = 16;
118
+ export const MAX_BACKGROUND_DOC_EXTENT = 65536;
119
+ export const BACKGROUND_CROP_EDGES = [
120
+ 'top',
121
+ 'bottom',
122
+ 'left',
123
+ 'right',
124
+ ];
125
+ // Bar proportions relative to the visible rect. Doc-space sizing, like the
126
+ // selection box stroke — the bars belong to the rect they crop, so they zoom
127
+ // with it; grab tolerance stays screen-constant in the hit-test instead.
128
+ const CROP_BAR_LENGTH_RATIO = 0.15;
129
+ const CROP_BAR_THICKNESS_RATIO = 0.22;
130
+ // The four crop bars for a layer's visible rect: pill-shaped grabs centered on
131
+ // each edge midpoint, long side along the edge.
132
+ export const backgroundCropBars = (layer) => {
133
+ const r = backgroundLayerDocRect(layer);
134
+ const midX = r.x + r.width / 2;
135
+ const midY = r.y + r.height / 2;
136
+ const hLen = r.width * CROP_BAR_LENGTH_RATIO;
137
+ const hThick = hLen * CROP_BAR_THICKNESS_RATIO;
138
+ const vLen = r.height * CROP_BAR_LENGTH_RATIO;
139
+ const vThick = vLen * CROP_BAR_THICKNESS_RATIO;
140
+ return [
141
+ {
142
+ edge: 'top',
143
+ x: midX - hLen / 2,
144
+ y: r.y - hThick / 2,
145
+ width: hLen,
146
+ height: hThick,
147
+ },
148
+ {
149
+ edge: 'bottom',
150
+ x: midX - hLen / 2,
151
+ y: r.y + r.height - hThick / 2,
152
+ width: hLen,
153
+ height: hThick,
154
+ },
155
+ {
156
+ edge: 'left',
157
+ x: r.x - vThick / 2,
158
+ y: midY - vLen / 2,
159
+ width: vThick,
160
+ height: vLen,
161
+ },
162
+ {
163
+ edge: 'right',
164
+ x: r.x + r.width - vThick / 2,
165
+ y: midY - vLen / 2,
166
+ width: vThick,
167
+ height: vLen,
168
+ },
169
+ ];
170
+ };
80
171
  // Bounding box of every layer's doc rect. Null when there are no layers —
81
172
  // callers fall back to the document rect.
82
173
  export const backgroundLayersUnionRect = (layers) => {
@@ -1,19 +1,22 @@
1
1
  import { type SkImage } from '@shopify/react-native-skia';
2
2
  import type { AnnotationBackgroundImage, AnnotationBackgroundLayer } from '../../../types/annotation.js';
3
3
  import type { ViewportState } from '../viewport.js';
4
- export declare const useBackgroundSkImage: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>) => SkImage | null;
4
+ export declare const useBackgroundSkImage: (image: AnnotationBackgroundImage | null | undefined, resolveUrl?: (path: string) => Promise<string>, deferToResolver?: boolean) => SkImage | null;
5
5
  export interface BackgroundSkImageLoaderProps {
6
6
  layer: AnnotationBackgroundLayer;
7
7
  resolveUrl?: (path: string) => Promise<string>;
8
+ deferToResolver?: boolean;
8
9
  onImage: (id: string, image: SkImage | null) => void;
9
10
  }
10
- export declare const BackgroundSkImageLoader: ({ layer, resolveUrl, onImage, }: BackgroundSkImageLoaderProps) => null;
11
+ export declare const BackgroundSkImageLoader: ({ layer, resolveUrl, deferToResolver, onImage, }: BackgroundSkImageLoaderProps) => null;
11
12
  export interface BackgroundLayerElementProps {
12
13
  layer: AnnotationBackgroundLayer;
13
14
  resolveUrl?: (path: string) => Promise<string>;
15
+ deferToResolver?: boolean;
14
16
  skImage?: SkImage | null;
15
17
  viewport?: ViewportState;
16
18
  screenWidth?: number;
17
19
  screenHeight?: number;
20
+ suppressCrop?: boolean;
18
21
  }
19
- export declare const BackgroundLayerElement: import("react").MemoExoticComponent<({ layer, resolveUrl, skImage: skImageProp, viewport, screenWidth, screenHeight, }: BackgroundLayerElementProps) => import("react/jsx-runtime").JSX.Element | null>;
22
+ export declare const BackgroundLayerElement: import("react").MemoExoticComponent<({ layer, resolveUrl, deferToResolver, skImage: skImageProp, viewport, screenWidth, screenHeight, suppressCrop, }: BackgroundLayerElementProps) => import("react/jsx-runtime").JSX.Element | null>;