@reekon-tools/boldr-utils 1.24.0 → 1.25.0
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/useAnnotationCanvasState.d.ts +11 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +17 -1
- package/dist/annotation/canvas/viewport.d.ts +10 -0
- package/dist/annotation/canvas/viewport.js +26 -0
- package/dist/annotation/data/hooks/usePdfCanvasDoc.js +22 -13
- package/dist/exports.d.ts +1 -1
- package/dist/exports.js +1 -1
- package/package.json +1 -1
|
@@ -23,6 +23,17 @@ export interface AnnotationCanvasHandle {
|
|
|
23
23
|
animated?: boolean;
|
|
24
24
|
durationMs?: number;
|
|
25
25
|
}): void;
|
|
26
|
+
fitDocRect(rect: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
}, opts?: {
|
|
32
|
+
animated?: boolean;
|
|
33
|
+
durationMs?: number;
|
|
34
|
+
paddingPx?: number;
|
|
35
|
+
maxZoom?: number;
|
|
36
|
+
}): void;
|
|
26
37
|
getTileScaleFactor(): number;
|
|
27
38
|
getHeaderTileScaleFactor(): number;
|
|
28
39
|
getFileTileScaleFactor(): number;
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
3
|
import { resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, resolveTileScaleFactor, stampTileDims, STAMP_TILE_SIZE, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
4
4
|
import { SNAP_RADIUS_PX } from './snapGeometry.js';
|
|
5
|
-
import { createViewportApi, fitRectToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './viewport.js';
|
|
5
|
+
import { createViewportApi, fitDocRectViewport, fitRectToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './viewport.js';
|
|
6
6
|
import { computeZoomFloor, pdfFirstPageDocRect } from './pdfDocument.js';
|
|
7
7
|
import { backgroundLayerDocRect, backgroundLayersOf, backgroundLayersUnionRect, } from './backgroundLayers.js';
|
|
8
8
|
import { buildRemoveMeasurementOps, recomputeAnchor, rectCenter, DEFAULT_LINE_POS, } from './measurementGeometry.js';
|
|
@@ -555,6 +555,22 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
555
555
|
setViewport(target);
|
|
556
556
|
}
|
|
557
557
|
},
|
|
558
|
+
fitDocRect(rect, opts) {
|
|
559
|
+
if (!(width > 0) || !(height > 0))
|
|
560
|
+
return;
|
|
561
|
+
const limits = zoomLimitsRef.current;
|
|
562
|
+
const target = fitDocRectViewport(rect, width, height, {
|
|
563
|
+
paddingPx: opts?.paddingPx,
|
|
564
|
+
minZoom: limits.min,
|
|
565
|
+
maxZoom: Math.min(opts?.maxZoom ?? limits.max, limits.max),
|
|
566
|
+
});
|
|
567
|
+
if (opts?.animated && animateViewport) {
|
|
568
|
+
animateViewport(target, opts.durationMs ?? 450);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
setViewport(target);
|
|
572
|
+
}
|
|
573
|
+
},
|
|
558
574
|
getTileScaleFactor() {
|
|
559
575
|
return ctxRef.current.tileScaleFactor;
|
|
560
576
|
},
|
|
@@ -24,6 +24,16 @@ export declare const fitRectToScreen: (rect: {
|
|
|
24
24
|
width: number;
|
|
25
25
|
height: number;
|
|
26
26
|
}, screenWidth: number, screenHeight: number, padding?: number) => ViewportState;
|
|
27
|
+
export declare const fitDocRectViewport: (rect: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}, screenWidth: number, screenHeight: number, limits?: {
|
|
33
|
+
paddingPx?: number;
|
|
34
|
+
minZoom?: number;
|
|
35
|
+
maxZoom?: number;
|
|
36
|
+
}) => ViewportState;
|
|
27
37
|
export declare const DEFAULT_ZOOM_LIMITS: {
|
|
28
38
|
readonly min: 0.05;
|
|
29
39
|
readonly max: 50;
|
|
@@ -62,6 +62,32 @@ export const fitRectToScreen = (rect, screenWidth, screenHeight, padding = 0) =>
|
|
|
62
62
|
pan: { x: rect.x - offsetX / zoom, y: rect.y - offsetY / zoom },
|
|
63
63
|
};
|
|
64
64
|
};
|
|
65
|
+
// Frame a DOC-space rect (a text-search hit, a tile) centred in the screen:
|
|
66
|
+
// zoom so the rect plus `paddingPx` of screen padding fits, clamped to
|
|
67
|
+
// [minZoom, maxZoom] — so a two-word hit does not zoom to 50× and a whole-page
|
|
68
|
+
// rect cannot go below the document's floor — then centre the rect at that
|
|
69
|
+
// zoom (screen = (world − pan) · zoom, so pan = centre − half-screen / zoom).
|
|
70
|
+
export const fitDocRectViewport = (rect, screenWidth, screenHeight, limits = {}) => {
|
|
71
|
+
const safe = {
|
|
72
|
+
x: rect.x,
|
|
73
|
+
y: rect.y,
|
|
74
|
+
width: Math.max(1, rect.width),
|
|
75
|
+
height: Math.max(1, rect.height),
|
|
76
|
+
};
|
|
77
|
+
const fit = fitRectToScreen(safe, screenWidth, screenHeight, limits.paddingPx ?? 0);
|
|
78
|
+
const upper = limits.maxZoom ?? Number.POSITIVE_INFINITY;
|
|
79
|
+
const lower = limits.minZoom ?? 0;
|
|
80
|
+
const zoom = Math.max(lower, Math.min(fit.zoom, upper));
|
|
81
|
+
const cx = safe.x + safe.width / 2;
|
|
82
|
+
const cy = safe.y + safe.height / 2;
|
|
83
|
+
return {
|
|
84
|
+
zoom,
|
|
85
|
+
pan: {
|
|
86
|
+
x: cx - screenWidth / (2 * zoom),
|
|
87
|
+
y: cy - screenHeight / (2 * zoom),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
};
|
|
65
91
|
// Zoom toward a focal screen point so the world point under the cursor stays
|
|
66
92
|
// fixed. Used for wheel-zoom on web and pinch on native.
|
|
67
93
|
//
|
|
@@ -23,14 +23,17 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
23
23
|
const provider = useAnnotationData();
|
|
24
24
|
const { data: file, loading: fileLoading, error: fileError, } = useAnnotationDoc(scope, fileId);
|
|
25
25
|
const pdfCanvas = file?.fileData.pdfCanvas ?? null;
|
|
26
|
-
|
|
26
|
+
// The current page's doc, TAGGED with the page it answers for. On a page
|
|
27
|
+
// turn the previous page's doc is still in state until the new subscription
|
|
28
|
+
// reports, and hydrating the new page from it is exactly how annotations
|
|
29
|
+
// land on the wrong page. `state` null = the page has no doc yet.
|
|
30
|
+
const [pageSnapshot, setPageSnapshot] = useState(null);
|
|
27
31
|
const [pageLoading, setPageLoading] = useState(true);
|
|
28
32
|
const [pageError, setPageError] = useState(null);
|
|
29
33
|
const [working, setWorking] = useState(null);
|
|
30
34
|
const [saveStatus, setSaveStatus] = useState('idle');
|
|
31
35
|
const workingRef = useRef(null);
|
|
32
36
|
const pdfCanvasRef = useRef(null);
|
|
33
|
-
const pageDocRef = useRef(null);
|
|
34
37
|
const statusRef = useRef('idle');
|
|
35
38
|
const pageIndexRef = useRef(pageIndex);
|
|
36
39
|
const scopeRef = useRef(scope);
|
|
@@ -48,10 +51,9 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
48
51
|
const thumbnailPromiseRef = useRef(undefined);
|
|
49
52
|
// A page whose edits must be persisted although it is no longer the current
|
|
50
53
|
// page (the user turned the page with unsaved edits).
|
|
51
|
-
const outgoingRef = useRef(
|
|
54
|
+
const outgoingRef = useRef([]);
|
|
52
55
|
workingRef.current = working;
|
|
53
56
|
pdfCanvasRef.current = pdfCanvas;
|
|
54
|
-
pageDocRef.current = pageDoc;
|
|
55
57
|
statusRef.current = saveStatus;
|
|
56
58
|
scopeRef.current = scope;
|
|
57
59
|
fileIdRef.current = fileId;
|
|
@@ -65,14 +67,17 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
65
67
|
// Subscribe to the current page's doc.
|
|
66
68
|
useEffect(() => {
|
|
67
69
|
if (!scope || !fileId) {
|
|
68
|
-
|
|
70
|
+
setPageSnapshot(null);
|
|
69
71
|
setPageLoading(false);
|
|
70
72
|
return;
|
|
71
73
|
}
|
|
74
|
+
// Drop the previous page's snapshot synchronously so nothing can read it
|
|
75
|
+
// as this page's while the subscription is still connecting.
|
|
76
|
+
setPageSnapshot(null);
|
|
72
77
|
setPageLoading(true);
|
|
73
78
|
setPageError(null);
|
|
74
79
|
const unsubscribe = provider.subscribePage(scope, fileId, pageIndex, (doc) => {
|
|
75
|
-
|
|
80
|
+
setPageSnapshot({ pageIndex, state: doc });
|
|
76
81
|
setPageLoading(false);
|
|
77
82
|
}, (err) => {
|
|
78
83
|
setPageError(err);
|
|
@@ -154,10 +159,10 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
154
159
|
// One flush pass: an outgoing page first (if a page turn left one behind),
|
|
155
160
|
// then the current page when it is dirty.
|
|
156
161
|
const runFlush = useCallback(async () => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await persist(
|
|
162
|
+
// Every page left behind with unsaved edits, in the order they were
|
|
163
|
+
// left: two quick turns must not lose the middle page.
|
|
164
|
+
while (outgoingRef.current.length > 0) {
|
|
165
|
+
await persist(outgoingRef.current.shift());
|
|
161
166
|
}
|
|
162
167
|
const canvas = workingRef.current;
|
|
163
168
|
const data = pdfCanvasRef.current;
|
|
@@ -200,11 +205,11 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
200
205
|
if (previous &&
|
|
201
206
|
data &&
|
|
202
207
|
(statusRef.current === 'dirty' || statusRef.current === 'error')) {
|
|
203
|
-
outgoingRef.current
|
|
208
|
+
outgoingRef.current.push({
|
|
204
209
|
pageIndex: pageIndexRef.current,
|
|
205
210
|
canvas: previous,
|
|
206
211
|
pdfCanvas: data,
|
|
207
|
-
};
|
|
212
|
+
});
|
|
208
213
|
void flush();
|
|
209
214
|
}
|
|
210
215
|
pageIndexRef.current = pageIndex;
|
|
@@ -225,6 +230,10 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
225
230
|
return;
|
|
226
231
|
if (pageIndex >= pdfCanvas.document.pages.length)
|
|
227
232
|
return;
|
|
233
|
+
// Only a snapshot for THIS page counts (see pageSnapshot).
|
|
234
|
+
if (!pageSnapshot || pageSnapshot.pageIndex !== pageIndex)
|
|
235
|
+
return;
|
|
236
|
+
const pageDoc = pageSnapshot.state;
|
|
228
237
|
if (workingRef.current === null) {
|
|
229
238
|
const next = pageStateToCanvasState(pdfCanvas, pageDoc, pageIndex);
|
|
230
239
|
workingRef.current = next;
|
|
@@ -241,7 +250,7 @@ export const usePdfCanvasDoc = (options) => {
|
|
|
241
250
|
workingRef.current = next;
|
|
242
251
|
setWorking(next);
|
|
243
252
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
244
|
-
}, [pdfCanvas,
|
|
253
|
+
}, [pdfCanvas, pageSnapshot, pageLoading, pageIndex]);
|
|
245
254
|
const onCommit = useCallback((patch) => {
|
|
246
255
|
editSeqRef.current += 1;
|
|
247
256
|
setWorking((prev) => {
|
package/dist/exports.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './anno
|
|
|
37
37
|
export type { CanvasBackgroundSlotArgs, RenderCanvasBackground, } from './annotation/canvas/backgroundSlot.js';
|
|
38
38
|
export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, type StampTileDims, } from './annotation/canvas/stampLayout.js';
|
|
39
39
|
export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, type GroupHeaderBadge, type GroupHeaderDescriptor, } from './annotation/canvas/groupHeaderLayout.js';
|
|
40
|
-
export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, type ViewportApi, type ViewportState, type ZoomLimits, } from './annotation/canvas/viewport.js';
|
|
40
|
+
export { createViewportApi, fitToScreen, fitRectToScreen, fitDocRectViewport, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, type ViewportApi, type ViewportState, type ZoomLimits, } from './annotation/canvas/viewport.js';
|
|
41
41
|
export { DEFAULT_PDF_PAGE_GAP, DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM, makePdfDocumentId, createPdfDocument, pdfPageDocRects, pdfDocumentExtent, pdfFirstPageDocRect, computeZoomFloor, type PdfDocRect, } from './annotation/canvas/pdfDocument.js';
|
|
42
42
|
export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, emptyPdfCanvasPageState, pdfCanvasPageDocument, pdfCanvasKnobsOf, pdfCanvasKnobsEqual, pageStateToCanvasState, canvasStateToPageState, rotatePointInPage, rotatePageState, rotatePdfCanvasPage, type PdfPageDisplaySize, type PdfRotationDelta, } from './annotation/canvas/pdfCanvasPages.js';
|
|
43
43
|
export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerIsLocked, isBackgroundLayerLocked, defaultBackgroundLayerLocked, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
|
package/dist/exports.js
CHANGED
|
@@ -43,7 +43,7 @@ export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
|
43
43
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
44
44
|
export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, } from './annotation/canvas/stampLayout.js';
|
|
45
45
|
export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, } from './annotation/canvas/groupHeaderLayout.js';
|
|
46
|
-
export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './annotation/canvas/viewport.js';
|
|
46
|
+
export { createViewportApi, fitToScreen, fitRectToScreen, fitDocRectViewport, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './annotation/canvas/viewport.js';
|
|
47
47
|
export { DEFAULT_PDF_PAGE_GAP, DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM, makePdfDocumentId, createPdfDocument, pdfPageDocRects, pdfDocumentExtent, pdfFirstPageDocRect, computeZoomFloor, } from './annotation/canvas/pdfDocument.js';
|
|
48
48
|
export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, emptyPdfCanvasPageState, pdfCanvasPageDocument, pdfCanvasKnobsOf, pdfCanvasKnobsEqual, pageStateToCanvasState, canvasStateToPageState, rotatePointInPage, rotatePageState, rotatePdfCanvasPage, } from './annotation/canvas/pdfCanvasPages.js';
|
|
49
49
|
export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerIsLocked, isBackgroundLayerLocked, defaultBackgroundLayerLocked, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
|