@rightkit/document-core 0.1.1 → 0.2.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.
Files changed (136) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +31 -7
  3. package/dist/app/chrome.js +1 -1
  4. package/dist/app/documentPrinting.d.ts +0 -6
  5. package/dist/app/documentPrinting.js +0 -18
  6. package/dist/app/shell/nativeMenu.d.ts +3 -2
  7. package/dist/app/shell/nativeMenu.js +8 -5
  8. package/dist/documents/perfEventCounters.d.ts +0 -8
  9. package/dist/documents/perfEventCounters.js +2 -1
  10. package/dist/documents/preview/previewFrame.d.ts +2 -1
  11. package/dist/documents/preview/previewFrame.js +9 -7
  12. package/dist/documents/searchUtils.js +1 -1
  13. package/dist/host.d.ts +36 -0
  14. package/dist/host.js +54 -0
  15. package/dist/index.d.ts +1 -50
  16. package/dist/index.js +1 -50
  17. package/dist/settings/themes.js +1 -1
  18. package/dist/state/bridgeEvents.d.ts +1 -1
  19. package/dist/state/bridgeEvents.js +7 -4
  20. package/dist/state/recentUrls.js +4 -3
  21. package/dist/state/tauriBridge.d.ts +3 -61
  22. package/dist/state/tauriBridge.js +8 -297
  23. package/dist/styles.css +2 -2
  24. package/package.json +25 -98
  25. package/dist/app/shortcutChords.d.ts +0 -6
  26. package/dist/app/shortcutChords.js +0 -14
  27. package/dist/app/useCloseFindOnSurfaceChange.d.ts +0 -14
  28. package/dist/app/useCloseFindOnSurfaceChange.js +0 -23
  29. package/dist/app/useEditText.d.ts +0 -6
  30. package/dist/app/useEditText.js +0 -10
  31. package/dist/appName.d.ts +0 -2
  32. package/dist/appName.js +0 -8
  33. package/dist/documents/FullscreenIcon.d.ts +0 -4
  34. package/dist/documents/FullscreenIcon.js +0 -7
  35. package/dist/documents/mdx/mdxRuntimeScript.d.ts +0 -2
  36. package/dist/documents/mdx/mdxRuntimeScript.js +0 -3
  37. package/dist/documents/mdx/mdxRuntimeScript.txt +0 -584
  38. package/dist/documents/pdf/MacPdfReadPane.d.ts +0 -23
  39. package/dist/documents/pdf/MacPdfReadPane.js +0 -708
  40. package/dist/documents/pdf/NativePdfDocument.d.ts +0 -9
  41. package/dist/documents/pdf/NativePdfDocument.js +0 -41
  42. package/dist/documents/pdf/PdfInlineTextEditor.d.ts +0 -53
  43. package/dist/documents/pdf/PdfInlineTextEditor.js +0 -225
  44. package/dist/documents/pdf/PdfOcrLayer.d.ts +0 -4
  45. package/dist/documents/pdf/PdfOcrLayer.js +0 -30
  46. package/dist/documents/pdf/PdfOutline.d.ts +0 -6
  47. package/dist/documents/pdf/PdfOutline.js +0 -12
  48. package/dist/documents/pdf/PdfPasswordPrompt.d.ts +0 -5
  49. package/dist/documents/pdf/PdfPasswordPrompt.js +0 -24
  50. package/dist/documents/pdf/PdfThumbnails.d.ts +0 -8
  51. package/dist/documents/pdf/PdfThumbnails.js +0 -201
  52. package/dist/documents/pdf/PdfToolbar.d.ts +0 -47
  53. package/dist/documents/pdf/PdfToolbar.js +0 -76
  54. package/dist/documents/pdf/PdfViewer.d.ts +0 -36
  55. package/dist/documents/pdf/PdfViewer.js +0 -1522
  56. package/dist/documents/pdf/PdfViewerBody.d.ts +0 -22
  57. package/dist/documents/pdf/PdfViewerBody.js +0 -42
  58. package/dist/documents/pdf/macPdfNativeBridge.d.ts +0 -118
  59. package/dist/documents/pdf/macPdfNativeBridge.js +0 -134
  60. package/dist/documents/pdf/nearestVisiblePdfPage.d.ts +0 -2
  61. package/dist/documents/pdf/nearestVisiblePdfPage.js +0 -22
  62. package/dist/documents/pdf/pdfBench.d.ts +0 -88
  63. package/dist/documents/pdf/pdfBench.js +0 -535
  64. package/dist/documents/pdf/pdfBenchAutopilot.d.ts +0 -48
  65. package/dist/documents/pdf/pdfBenchAutopilot.js +0 -154
  66. package/dist/documents/pdf/pdfBenchTransport.d.ts +0 -21
  67. package/dist/documents/pdf/pdfBenchTransport.js +0 -233
  68. package/dist/documents/pdf/pdfEditModel.d.ts +0 -294
  69. package/dist/documents/pdf/pdfEditModel.js +0 -105
  70. package/dist/documents/pdf/pdfEditor.d.ts +0 -100
  71. package/dist/documents/pdf/pdfEditor.js +0 -68
  72. package/dist/documents/pdf/pdfOcr.d.ts +0 -36
  73. package/dist/documents/pdf/pdfOcr.js +0 -88
  74. package/dist/documents/pdf/pdfOcrEdit.d.ts +0 -32
  75. package/dist/documents/pdf/pdfOcrEdit.js +0 -138
  76. package/dist/documents/pdf/pdfPrinting.d.ts +0 -28
  77. package/dist/documents/pdf/pdfPrinting.js +0 -110
  78. package/dist/documents/pdf/pdfSave.d.ts +0 -87
  79. package/dist/documents/pdf/pdfSave.js +0 -250
  80. package/dist/documents/pdf/pdfShared.d.ts +0 -16
  81. package/dist/documents/pdf/pdfShared.js +0 -20
  82. package/dist/documents/pdf/pdfToMarkdown.d.ts +0 -11
  83. package/dist/documents/pdf/pdfToMarkdown.js +0 -67
  84. package/dist/documents/pdf/pdfjsLibGlobal.d.ts +0 -1
  85. package/dist/documents/pdf/pdfjsLibGlobal.js +0 -12
  86. package/dist/documents/pdf/toolbarIcons.d.ts +0 -18
  87. package/dist/documents/pdf/toolbarIcons.js +0 -31
  88. package/dist/documents/pdf/usePdfDocumentLoader.d.ts +0 -40
  89. package/dist/documents/pdf/usePdfDocumentLoader.js +0 -243
  90. package/dist/documents/pdf/usePdfEditSession.d.ts +0 -88
  91. package/dist/documents/pdf/usePdfEditSession.js +0 -341
  92. package/dist/documents/pdf/usePdfEditor.d.ts +0 -59
  93. package/dist/documents/pdf/usePdfEditor.js +0 -317
  94. package/dist/documents/pdf/usePdfFit.d.ts +0 -19
  95. package/dist/documents/pdf/usePdfFit.js +0 -55
  96. package/dist/documents/pdf/usePdfOcr.d.ts +0 -38
  97. package/dist/documents/pdf/usePdfOcr.js +0 -337
  98. package/dist/documents/pdf/usePdfPreviewRecompose.d.ts +0 -28
  99. package/dist/documents/pdf/usePdfPreviewRecompose.js +0 -256
  100. package/dist/documents/pdf/usePdfSavePlan.d.ts +0 -61
  101. package/dist/documents/pdf/usePdfSavePlan.js +0 -303
  102. package/dist/documents/pdf/usePdfSearch.d.ts +0 -31
  103. package/dist/documents/pdf/usePdfSearch.js +0 -367
  104. package/dist/documents/pdf/usePdfViewControls.d.ts +0 -72
  105. package/dist/documents/pdf/usePdfViewControls.js +0 -152
  106. package/dist/documents/scopedSelectAll.d.ts +0 -5
  107. package/dist/documents/scopedSelectAll.js +0 -77
  108. package/dist/documents/txt/cmScroll.d.ts +0 -11
  109. package/dist/documents/txt/cmScroll.js +0 -52
  110. package/dist/documents/useMirroredEditText.d.ts +0 -15
  111. package/dist/documents/useMirroredEditText.js +0 -29
  112. package/dist/documents/useSplitResize.d.ts +0 -7
  113. package/dist/documents/useSplitResize.js +0 -37
  114. package/dist/settings/rk-settings.css +0 -38
  115. package/dist/settings/sections/EditorSection.d.ts +0 -1
  116. package/dist/settings/sections/EditorSection.js +0 -18
  117. package/dist/settings/sections/FileAssociationsSection.d.ts +0 -1
  118. package/dist/settings/sections/FileAssociationsSection.js +0 -5
  119. package/dist/settings/sections/MacFileAssociationsBlock.d.ts +0 -33
  120. package/dist/settings/sections/MacFileAssociationsBlock.js +0 -151
  121. package/dist/settings/sections/ThemeSection.d.ts +0 -1
  122. package/dist/settings/sections/ThemeSection.js +0 -31
  123. package/dist/settings/sections/TypographySection.d.ts +0 -1
  124. package/dist/settings/sections/TypographySection.js +0 -19
  125. package/dist/settings/sections/onOff.d.ts +0 -11
  126. package/dist/settings/sections/onOff.js +0 -8
  127. package/dist/state/browserQa.d.ts +0 -21
  128. package/dist/state/browserQa.js +0 -303
  129. package/dist/state/browserQaDocx.d.ts +0 -3
  130. package/dist/state/browserQaDocx.js +0 -35
  131. package/dist/state/browserQaLicense.d.ts +0 -8
  132. package/dist/state/browserQaLicense.js +0 -7
  133. package/dist/state/browserQaPdf.d.ts +0 -60
  134. package/dist/state/browserQaPdf.js +0 -302
  135. package/dist/state/browserQaText.d.ts +0 -6
  136. package/dist/state/browserQaText.js +0 -48
@@ -1,59 +0,0 @@
1
- import { type MutableRefObject } from "react";
2
- import type { PDFDocumentProxy } from "pdfjs-dist";
3
- import { type PdfEditorMode } from "./pdfEditor.js";
4
- type AnyUIManager = any;
5
- export interface PdfEditorState {
6
- /** Current tool. "none" = pure viewer, no AnnotationEditorLayer is interactive. */
7
- mode: PdfEditorMode;
8
- setMode: (mode: PdfEditorMode) => void;
9
- /** Construct the editor manager for an explicit edit request. Returns false when no PDF or
10
- * container is available yet. Construction is synchronous so callers can immediately render
11
- * editor layers against the returned manager. */
12
- ensureReady: () => boolean;
13
- freeTextSize: number;
14
- setFreeTextSize: (size: number) => void;
15
- /** True once any annotation-editor edit exists in the document's annotation storage. */
16
- dirty: boolean;
17
- /** Serialize current edits (editor annotations + form values) to PDF bytes via pdf.js
18
- * `saveDocument()`. Returns null if no document is loaded or serialization fails. */
19
- saveBytes: () => Promise<Uint8Array | null>;
20
- /** Checkpoint serialized annotation bytes after successful disk commit, retaining newer edits. */
21
- checkpointAnnotationSave: () => boolean;
22
- /** Stable token for detecting annotation mutations across async save/reload work. */
23
- annotationStateToken: () => string;
24
- /** Token captured immediately after pdf.js synchronously serializes current annotations. */
25
- serializedAnnotationStateToken: () => string | null;
26
- /** Add an image stamp editor on the current page from a picked file (drag-drop or file picker).
27
- * No-op if no editor layer is currently registered for the visible page (e.g. before first
28
- * render). Returns false when the add could not be performed. */
29
- addImageStamp: (file: File, layerWaitMs?: number) => Promise<boolean>;
30
- /** True once the UI manager is constructed for this document. */
31
- ready: boolean;
32
- uiManagerRef: MutableRefObject<AnyUIManager | null>;
33
- /** Forward the viewer's own scale/rotation/page state into the UI manager so newly-placed
34
- * editors and existing ones stay correctly positioned as the user zooms/rotates/scrolls. Cheap
35
- * no-op when no document/editor is loaded. */
36
- syncViewState: (state: {
37
- scale: number;
38
- rotation: number;
39
- page: number;
40
- }) => void;
41
- }
42
- /**
43
- * Stage 1 PDF editing: owns the pdf.js `AnnotationEditorUIManager` + its `EventBus` for one open
44
- * document, and exposes a small React-facing surface (mode, dirty, save, add-image) to PdfViewer.
45
- * Per-page `AnnotationEditorLayer`s are built in PdfViewer's renderPage (they need the same
46
- * per-page viewport/canvas/text-layer context renderPage already computes) and register themselves
47
- * with `uiManagerRef.current` via their own constructor (`uiManager.addLayer(this)`).
48
- *
49
- * View-first invariant: `mode` defaults to "none" on every new document, so opening a PDF is
50
- * unchanged — editing is an explicit opt-in via the toolbar.
51
- */
52
- export declare function usePdfEditor({ pdfDoc, containerRef, }: {
53
- pdfDoc: PDFDocumentProxy | null;
54
- /** The scrollable pages container — doubles as both the UI manager's focus-containment root and
55
- * its "viewer" element (only used for `--scale-factor` bookkeeping and `dir`, not for indexing
56
- * page children, so our virtualized/unloaded slots don't confuse it). */
57
- containerRef: MutableRefObject<HTMLElement | null>;
58
- }): PdfEditorState;
59
- export {};
@@ -1,317 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import * as pdfjs from "pdfjs-dist";
3
- import { logError, logEvent } from "../../state/diagnostics.js";
4
- import { modeToAnnotationEditorType } from "./pdfEditor.js";
5
- let viewerModule = null;
6
- let viewerLoad = null;
7
- function loadViewerModule() {
8
- viewerLoad ??= import("./pdfjsLibGlobal.js")
9
- .then(() => import("pdfjs-dist/web/pdf_viewer.mjs"))
10
- .then((m) => {
11
- viewerModule = m;
12
- })
13
- .catch(() => {
14
- viewerLoad = null;
15
- logError("pdf-editor:viewer-load");
16
- });
17
- return viewerLoad;
18
- }
19
- const IMAGE_STAMP_LAYER_WAIT_MS = 2000;
20
- const IMAGE_STAMP_LAYER_POLL_MS = 16;
21
- /**
22
- * Stage 1 PDF editing: owns the pdf.js `AnnotationEditorUIManager` + its `EventBus` for one open
23
- * document, and exposes a small React-facing surface (mode, dirty, save, add-image) to PdfViewer.
24
- * Per-page `AnnotationEditorLayer`s are built in PdfViewer's renderPage (they need the same
25
- * per-page viewport/canvas/text-layer context renderPage already computes) and register themselves
26
- * with `uiManagerRef.current` via their own constructor (`uiManager.addLayer(this)`).
27
- *
28
- * View-first invariant: `mode` defaults to "none" on every new document, so opening a PDF is
29
- * unchanged — editing is an explicit opt-in via the toolbar.
30
- */
31
- export function usePdfEditor({ pdfDoc, containerRef, }) {
32
- const [mode, setModeState] = useState("none");
33
- const [dirty, setDirty] = useState(false);
34
- const [freeTextSize, setFreeTextSizeState] = useState(10);
35
- const [ready, setReady] = useState(false);
36
- const uiManagerRef = useRef(null);
37
- const eventBusRef = useRef(null);
38
- const storageRef = useRef(null);
39
- const pdfDocRef = useRef(null);
40
- pdfDocRef.current = pdfDoc;
41
- // True while `saveBytes` is inside pdf.js `saveDocument()` — see `onReset` below.
42
- const serializingRef = useRef(false);
43
- const annotationEpochRef = useRef(0);
44
- const serializedEpochRef = useRef(null);
45
- const serializedAnnotationHashRef = useRef(null);
46
- const serializedStateTokenRef = useRef(null);
47
- const committedAnnotationHashRef = useRef(null);
48
- const annotationCheckpointSafeRef = useRef(true);
49
- const viewStateRef = useRef(null);
50
- const syncViewState = useCallback(({ scale, rotation, page }) => {
51
- viewStateRef.current = { scale, rotation, page };
52
- const eventBus = eventBusRef.current;
53
- if (!eventBus)
54
- return;
55
- // UIManager multiplies viewer scale by PDF_TO_CSS_UNITS. Our scale already measures CSS
56
- // pixels per PDF unit, so normalize it first to keep annotation placement at the pointer.
57
- eventBus.dispatch("scalechanging", {
58
- source: eventBus,
59
- scale: scale / pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS,
60
- });
61
- eventBus.dispatch("rotationchanging", { source: eventBus, pagesRotation: rotation });
62
- eventBus.dispatch("pagechanging", { source: eventBus, pageNumber: page });
63
- }, []);
64
- const destroyManager = useCallback(() => {
65
- const storage = storageRef.current;
66
- if (storage) {
67
- storage.onSetModified = null;
68
- storage.onResetModified = null;
69
- }
70
- storageRef.current = null;
71
- const uiManager = uiManagerRef.current;
72
- uiManagerRef.current = null;
73
- eventBusRef.current = null;
74
- if (uiManager) {
75
- // pdf.js owns a window keydown listener for editor history. Detach it before destroying
76
- // CommandManager: its destroy() intentionally nulls command storage, so a queued Cmd-Z or
77
- // Cmd-Shift-Z must not enter undo/redo after this document's manager has gone away.
78
- uiManager.removeEditListeners?.();
79
- uiManager.destroy();
80
- }
81
- setReady(false);
82
- }, []);
83
- // pdf.js's AnnotationEditorUIManager is a viewer-internal class with no stability contract on
84
- // its constructor signature (see docs/ENGINEERING-FINDINGS-2026-08.md §4 — Mozilla declined a
85
- // generic embedding API). Guard construction so a pdfjs-dist upgrade that shifts this shape
86
- // degrades to "editing controls stay disabled" rather than breaking the pure viewer.
87
- const ensureReady = useCallback(() => {
88
- if (uiManagerRef.current)
89
- return true;
90
- const doc = pdfDocRef.current;
91
- const container = containerRef.current;
92
- if (!doc || !container)
93
- return false;
94
- // The viewer module loads on the first mount; until it has, editing controls stay disabled.
95
- const viewer = viewerModule;
96
- if (!viewer) {
97
- void loadViewerModule();
98
- return false;
99
- }
100
- let eventBus = null;
101
- let uiManager = null;
102
- try {
103
- eventBus = new viewer.EventBus();
104
- const l10n = new viewer.GenericL10n(navigator.language || "en-US");
105
- uiManager = new pdfjs.AnnotationEditorUIManager(container, // container: focus/keyboard scoping root
106
- container, // viewer: only read for --scale-factor / dir bookkeeping here
107
- null, // viewerAlert
108
- null, // altTextManager
109
- null, // commentManager
110
- null, // signatureManager
111
- eventBus, doc, null, // pageColors — no forced high-contrast palette
112
- undefined, // highlightColors — pdf.js defaults
113
- false, // enableHighlightFloatingButton
114
- false, // enableUpdatedAddImage
115
- false, // enableNewAltTextWhenAddingImage
116
- null, // mlManager — no ML alt-text guesses
117
- null, // editorUndoBar — toolbar has no undo-bar UI in Stage 1
118
- false);
119
- uiManager.l10n = l10n;
120
- }
121
- catch {
122
- logError("pdf-editor:init");
123
- eventBus = null;
124
- uiManager = null;
125
- }
126
- if (!eventBus || !uiManager)
127
- return false;
128
- eventBusRef.current = eventBus;
129
- uiManagerRef.current = uiManager;
130
- // A save reload can replace the manager without ready ever rendering false.
131
- // Seed its scale immediately, before any annotation layer can accept input.
132
- if (viewStateRef.current)
133
- syncViewState(viewStateRef.current);
134
- // pdfjs-dist's generated types declare these callback slots as literal `null` (they're meant
135
- // to be assigned a function at runtime, same as pdf.js's own viewer does) — cast the boundary.
136
- // `annotationStorage` is a lazily-created getter on every real PDFDocumentProxy, but stays
137
- // undefined on the minimal PDFDocumentProxy stand-ins other PdfViewer tests construct (they
138
- // predate Stage 1 and only implement what the pure viewer touches) — guard rather than crash
139
- // the whole component (and, with it, unrelated pure-viewer behavior) from an editor-only hook.
140
- const storage = doc.annotationStorage;
141
- const onModified = () => {
142
- annotationEpochRef.current += 1;
143
- annotationCheckpointSafeRef.current = false;
144
- setDirty(true);
145
- };
146
- // pdf.js calls `annotationStorage.resetModified()` inside `saveDocument()` itself, i.e. the
147
- // moment bytes are SERIALIZED — long before the app has written anything to disk. Honouring
148
- // that reset here flipped the tab to "clean" during every save attempt, and a save that then
149
- // failed (prepare/commit rejected) left real unsaved annotations showing as saved. Dirty state
150
- // is cleared by the post-commit document reload instead (new `pdfDoc` re-runs this effect), so
151
- // resets raised while `saveBytes` is serializing are ignored.
152
- const onReset = () => {
153
- if (serializingRef.current)
154
- return;
155
- annotationCheckpointSafeRef.current = true;
156
- setDirty(false);
157
- };
158
- if (storage) {
159
- storage.onSetModified = onModified;
160
- storage.onResetModified = onReset;
161
- }
162
- storageRef.current = storage ?? null;
163
- setReady(true);
164
- return true;
165
- }, [containerRef, syncViewState]);
166
- // Build/tear down one UIManager per loaded document. The manager stays absent while a PDF is
167
- // merely being viewed, then survives mode="none" so pdf.js's unified undo history remains intact.
168
- // A document identity change always destroys it and resets mode/dirty state.
169
- // biome-ignore lint/correctness/useExhaustiveDependencies: pdfDoc identity defines the manager lifetime.
170
- // Start loading the viewer module (EventBus, GenericL10n) as soon as an editor mounts.
171
- useEffect(() => {
172
- void loadViewerModule();
173
- }, []);
174
- useEffect(() => {
175
- setModeState("none");
176
- setDirty(false);
177
- annotationEpochRef.current = 0;
178
- serializedEpochRef.current = null;
179
- serializedAnnotationHashRef.current = null;
180
- serializedStateTokenRef.current = null;
181
- committedAnnotationHashRef.current = null;
182
- annotationCheckpointSafeRef.current = true;
183
- setReady(false);
184
- return () => {
185
- destroyManager();
186
- };
187
- }, [destroyManager, pdfDoc]);
188
- const setMode = useCallback((next) => {
189
- const uiManager = next === "none" ? uiManagerRef.current : ensureReady() ? uiManagerRef.current : null;
190
- setModeState(next);
191
- if (!uiManager)
192
- return;
193
- // A rejection here previously vanished completely: pdf.js's own viewer also calls
194
- // `updateMode()` fire-and-forget, and every registered page's `AnnotationEditorLayer.
195
- // updateMode()` — the call that un-hides its div — only runs once THIS promise resolves. A
196
- // page whose annotations include a Stamp used to reject this unconditionally (see
197
- // `NULL_STRUCT_TREE_LAYER`'s doc comment in PdfViewer.tsx) with zero visible error: the mode
198
- // still LOOKED like it switched (this hook's own `mode` state flips regardless), but every
199
- // editor layer stayed permanently inert. Logging a rejection here keeps any future regression
200
- // in this class diagnosable instead of silently inert again.
201
- void uiManager
202
- .updateMode(modeToAnnotationEditorType(next))
203
- .catch(() => logError("pdf-editor:mode-switch"));
204
- }, [ensureReady]);
205
- const setFreeTextSize = useCallback((size) => {
206
- if (!Number.isFinite(size))
207
- return;
208
- const normalized = Math.max(6, Math.min(144, Math.round(size)));
209
- setFreeTextSizeState(normalized);
210
- uiManagerRef.current?.updateParams(pdfjs.AnnotationEditorParamsType.FREETEXT_SIZE, normalized);
211
- }, []);
212
- const annotationStateToken = useCallback(() => {
213
- const hash = pdfDocRef.current?.annotationStorage?.serializable?.hash ?? "";
214
- return `${annotationEpochRef.current}:${hash}`;
215
- }, []);
216
- const saveBytes = useCallback(async () => {
217
- const doc = pdfDocRef.current;
218
- if (!doc)
219
- return null;
220
- const serializedEpoch = annotationEpochRef.current;
221
- serializedEpochRef.current = serializedEpoch;
222
- serializedAnnotationHashRef.current = doc.annotationStorage?.serializable?.hash ?? null;
223
- serializedStateTokenRef.current = null;
224
- annotationCheckpointSafeRef.current = true;
225
- serializingRef.current = true;
226
- logEvent("pdf-editor:serialize:start");
227
- try {
228
- // saveDocument() invokes annotationStorage.serializable synchronously before returning its
229
- // worker promise. Capture token immediately after that call: FreeText serialization can
230
- // normalize editor state/hash without a real edit, while any later mutation must remain
231
- // visible to save-plan stale checks.
232
- const savePromise = doc.saveDocument();
233
- const serializedStateToken = annotationStateToken();
234
- const serializedEpochAfterCall = annotationEpochRef.current;
235
- if (serializedEpochAfterCall === serializedEpoch) {
236
- // Checkpoint against normalized serialization state. Keep the pre-call hash when an
237
- // epoch changed during save so newer edits remain visible to checkpointAnnotationSave.
238
- serializedAnnotationHashRef.current =
239
- doc.annotationStorage?.serializable?.hash ?? serializedAnnotationHashRef.current;
240
- }
241
- const bytes = await savePromise;
242
- const settledStateToken = annotationStateToken();
243
- if (serializedEpochAfterCall === serializedEpoch &&
244
- settledStateToken === serializedStateToken) {
245
- serializedStateTokenRef.current = serializedStateToken;
246
- }
247
- logEvent("pdf-editor:serialize:done");
248
- return bytes;
249
- }
250
- catch {
251
- logError("pdf-editor:serialize");
252
- return null;
253
- }
254
- finally {
255
- serializingRef.current = false;
256
- }
257
- }, [annotationStateToken]);
258
- const serializedAnnotationStateToken = useCallback(() => serializedStateTokenRef.current, []);
259
- const checkpointAnnotationSave = useCallback(() => {
260
- const serializedEpoch = serializedEpochRef.current;
261
- const serializedHash = serializedAnnotationHashRef.current;
262
- const storage = pdfDocRef.current?.annotationStorage;
263
- if (!storage || !annotationCheckpointSafeRef.current)
264
- return false;
265
- const currentHash = storage.serializable.hash;
266
- if (serializedEpoch === null || serializedHash === null) {
267
- return (committedAnnotationHashRef.current === null ||
268
- currentHash === committedAnnotationHashRef.current);
269
- }
270
- if (serializedEpoch !== annotationEpochRef.current || currentHash !== serializedHash) {
271
- return false;
272
- }
273
- storage.resetModified();
274
- setDirty(false);
275
- committedAnnotationHashRef.current = serializedHash;
276
- serializedEpochRef.current = null;
277
- serializedAnnotationHashRef.current = null;
278
- annotationCheckpointSafeRef.current = true;
279
- return true;
280
- }, []);
281
- const addImageStamp = useCallback(async (file, layerWaitMs = IMAGE_STAMP_LAYER_WAIT_MS) => {
282
- if (!ensureReady())
283
- return false;
284
- const uiManager = uiManagerRef.current;
285
- if (!uiManager)
286
- return false;
287
- const deadline = Date.now() + Math.min(15000, Math.max(0, layerWaitMs));
288
- while (!uiManager.currentLayer && Date.now() < deadline) {
289
- await new Promise((resolve) => setTimeout(resolve, IMAGE_STAMP_LAYER_POLL_MS));
290
- if (uiManagerRef.current !== uiManager)
291
- return false;
292
- }
293
- const layer = uiManager.currentLayer ?? null;
294
- if (!layer)
295
- return false;
296
- await uiManager.updateMode(modeToAnnotationEditorType("stamp"));
297
- await layer.pasteEditor({ mode: modeToAnnotationEditorType("stamp") }, { bitmapFile: file });
298
- setModeState("stamp");
299
- return true;
300
- }, [ensureReady]);
301
- return {
302
- mode,
303
- setMode,
304
- ensureReady,
305
- freeTextSize,
306
- setFreeTextSize,
307
- dirty,
308
- saveBytes,
309
- checkpointAnnotationSave,
310
- annotationStateToken,
311
- serializedAnnotationStateToken,
312
- addImageStamp,
313
- ready,
314
- uiManagerRef,
315
- syncViewState,
316
- };
317
- }
@@ -1,19 +0,0 @@
1
- import { type Dispatch, type MutableRefObject, type SetStateAction } from "react";
2
- import { type Size } from "./pdfShared.js";
3
- interface PdfFitDeps {
4
- scrollRef: MutableRefObject<HTMLDivElement | null>;
5
- /** Base page-1 viewport at scale 1 (null until the document loads). */
6
- base: Size | null;
7
- /** 0 | 90 | 180 | 270, applied to every page. */
8
- rotation: number;
9
- fitMode: "width" | "page" | null;
10
- setScale: Dispatch<SetStateAction<number>>;
11
- }
12
- /**
13
- * Fit-to-width/page scale math: derive `scale` from the container's client box (rotation-aware),
14
- * apply it when the base page size or fit mode changes, and re-apply on container resize —
15
- * rAF-coalesced because ResizeObserver can fire many times per frame during a window drag.
16
- * Extracted from PdfViewer verbatim — no behavior change.
17
- */
18
- export declare function usePdfFit({ scrollRef, base, rotation, fitMode, setScale }: PdfFitDeps): void;
19
- export {};
@@ -1,55 +0,0 @@
1
- import { useCallback, useEffect, } from "react";
2
- import { clampScale } from "./pdfShared.js";
3
- /**
4
- * Fit-to-width/page scale math: derive `scale` from the container's client box (rotation-aware),
5
- * apply it when the base page size or fit mode changes, and re-apply on container resize —
6
- * rAF-coalesced because ResizeObserver can fire many times per frame during a window drag.
7
- * Extracted from PdfViewer verbatim — no behavior change.
8
- */
9
- export function usePdfFit({ scrollRef, base, rotation, fitMode, setScale }) {
10
- // --- Fit-to-width: compute scale from the container width ----------------------
11
- // biome-ignore lint/correctness/useExhaustiveDependencies: refs/setters arrive as stable hook params
12
- const applyFit = useCallback((mode) => {
13
- const el = scrollRef.current;
14
- if (!el || !base)
15
- return;
16
- // On 90°/270° rotation the page's effective footprint has width/height swapped.
17
- const swap = rotation % 180 !== 0;
18
- const bw = swap ? base.h : base.w;
19
- const bh = swap ? base.w : base.h;
20
- const availW = el.clientWidth - 48; // page horizontal margin
21
- const availH = el.clientHeight - 48;
22
- const next = mode === "width" ? availW / bw : Math.min(availW / bw, availH / bh);
23
- setScale(clampScale(next));
24
- }, [base, rotation]);
25
- useEffect(() => {
26
- if (base && fitMode)
27
- applyFit(fitMode);
28
- }, [base, fitMode, applyFit]);
29
- // Recompute fit on container resize. rAF-coalesced: ResizeObserver can fire many times per frame
30
- // during an interactive window drag, and each call previously re-rasterized every visible page —
31
- // collapse to at most one applyFit per animation frame.
32
- // biome-ignore lint/correctness/useExhaustiveDependencies: refs arrive as stable hook params
33
- useEffect(() => {
34
- if (!fitMode)
35
- return;
36
- const el = scrollRef.current;
37
- if (!el)
38
- return;
39
- let raf = null;
40
- const ro = new ResizeObserver(() => {
41
- if (raf != null)
42
- return;
43
- raf = requestAnimationFrame(() => {
44
- raf = null;
45
- applyFit(fitMode);
46
- });
47
- });
48
- ro.observe(el);
49
- return () => {
50
- ro.disconnect();
51
- if (raf != null)
52
- cancelAnimationFrame(raf);
53
- };
54
- }, [fitMode, applyFit]);
55
- }
@@ -1,38 +0,0 @@
1
- import type { PDFDocumentProxy } from "pdfjs-dist";
2
- import { type OcrLine } from "./pdfOcr.js";
3
- export type PdfOcrStatus = "idle" | "running" | "done" | "error";
4
- export interface PdfOcrPageState {
5
- status: PdfOcrStatus;
6
- error?: string;
7
- }
8
- export interface PdfOcrDocumentState {
9
- status: PdfOcrStatus;
10
- completed: number;
11
- total: number;
12
- scanned: number;
13
- skipped: number;
14
- failed: number;
15
- currentPage?: number;
16
- }
17
- export declare function usePdfOcr({ documentId, pdfDoc, pdfRef, current, rotation, setPageText, invalidatePageText, rehighlight, }: {
18
- documentId: string;
19
- pdfDoc: PDFDocumentProxy | null;
20
- pdfRef: React.MutableRefObject<PDFDocumentProxy | null>;
21
- current: number;
22
- rotation: number;
23
- setPageText: (page: number, text: string) => void;
24
- invalidatePageText: (page: number) => void;
25
- rehighlight: () => void;
26
- }): {
27
- ocrAvailable: boolean;
28
- availabilityResolved: boolean;
29
- currentPageScanned: boolean;
30
- currentPageState: PdfOcrPageState;
31
- currentError: string | undefined;
32
- currentErrorIsBackendUnavailable: boolean;
33
- linesByPage: Map<number, OcrLine[]>;
34
- pageStates: Map<number, PdfOcrPageState>;
35
- recognizeCurrentPage: (inspectEligibility?: boolean) => Promise<void>;
36
- documentOcrState: PdfOcrDocumentState;
37
- recognizeDocument: () => Promise<void>;
38
- };