@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,88 +0,0 @@
1
- import * as pdfjs from "pdfjs-dist";
2
- import { readPdfPageText } from "./usePdfSearch.js";
3
- // These names are present in older pdf.js operator lists; current pdf.js folds JPEG painting into
4
- // paintImageXObject, so keep the compatibility check without adding undefined to the operator set.
5
- const paintJpegXObject = pdfjs.OPS
6
- .paintJpegXObject;
7
- const IMAGE_OPERATOR_CODES = new Set([
8
- pdfjs.OPS.paintImageXObject,
9
- ...(paintJpegXObject === undefined ? [] : [paintJpegXObject]),
10
- pdfjs.OPS.paintInlineImageXObject,
11
- pdfjs.OPS.paintImageMaskXObject,
12
- ]);
13
- /** These prefixes are a cross-language contract with src-tauri/crates/ocr/src/lib.rs; they must not drift. */
14
- export const WINDOWS_OCR_UNAVAILABLE_PREFIX = "windows-ocr-unavailable:";
15
- export const MACOS_VISION_UNAVAILABLE_PREFIX = "macos-vision-unavailable:";
16
- export const OCR_UNSUPPORTED_PLATFORM_PREFIX = "ocr-unsupported-platform:";
17
- /**
18
- * Fast eligibility check used while browsing. It reads only the streamed text layer, so opening
19
- * a document or changing pages never asks pdf.js to decode every page's operator list.
20
- */
21
- export async function isTextlessPage(page) {
22
- const text = await readPdfPageText(page);
23
- return text.trim() === "";
24
- }
25
- /**
26
- * Scanned-page recognition is offered only when there is no usable text and the page paints an
27
- * image: streamed text is authoritative because WKWebView's pdf.js worker can leave a parallel
28
- * getTextContent() request empty even while the visible streamed text layer has content, and the
29
- * image check prevents genuinely blank pages from being sent to OCR.
30
- */
31
- export async function isProbablyScannedPage(page) {
32
- if (!(await isTextlessPage(page)))
33
- return false;
34
- const operatorList = await page.getOperatorList();
35
- return operatorList.fnArray.some((operator) => IMAGE_OPERATOR_CODES.has(operator));
36
- }
37
- /**
38
- * Rasterizes one PDF page at 300 DPI by default. 300 DPI is the standard floor for reliable text
39
- * recognition on scanned documents; pdf.js's default page space is 72 DPI, so the scale converts
40
- * directly from points to the requested pixel density.
41
- */
42
- export async function renderPageToPng(page, dpi = 300, rotation) {
43
- // `rotation` is the ABSOLUTE total rotation (page rotation + the viewer's), because pdf.js
44
- // treats getViewport's rotation as absolute rather than additive. It is threaded in here rather
45
- // than by wrapping the page: a derived or proxied page object breaks page.render(), which writes
46
- // private class fields on `this` and throws "Cannot write private member #pendingCleanup to an
47
- // object whose class did not declare it".
48
- const viewport = page.getViewport(rotation === undefined ? { scale: dpi / 72 } : { scale: dpi / 72, rotation });
49
- const canvas = document.createElement("canvas");
50
- canvas.width = Math.ceil(viewport.width);
51
- canvas.height = Math.ceil(viewport.height);
52
- try {
53
- const context = canvas.getContext("2d");
54
- if (!context)
55
- throw new Error("Cannot rasterize PDF page: canvas 2D context unavailable.");
56
- // OCR needs a flat page raster, never interactive annotations. Print intent avoids pdf.js's
57
- // display-only requestAnimationFrame scheduler, which WKWebView pauses for minimized windows.
58
- // Disabling annotations also gives OCR a distinct pdf.js intent-state key from thumbnail renders,
59
- // which use print intent with annotations enabled on the same cached PDFPageProxy.
60
- await page.render({
61
- canvas,
62
- canvasContext: context,
63
- viewport,
64
- intent: "print",
65
- annotationMode: pdfjs.AnnotationMode.DISABLE,
66
- }).promise;
67
- return await new Promise((resolve, reject) => {
68
- canvas.toBlob((blob) => {
69
- if (blob)
70
- resolve(blob);
71
- else
72
- reject(new Error("Cannot rasterize PDF page: PNG encoding unavailable."));
73
- }, "image/png");
74
- });
75
- }
76
- finally {
77
- // Drop backing storage promptly: OCR can process long documents, so retaining one 300 DPI
78
- // canvas per in-flight page is enough to create a large avoidable native allocation.
79
- canvas.width = 0;
80
- canvas.height = 0;
81
- }
82
- }
83
- export { ocrPage, ocrAvailable, ocrBackendMissing } from "../../state/tauriBridge.js";
84
- export function isBackendUnavailable(message) {
85
- return (message.startsWith(WINDOWS_OCR_UNAVAILABLE_PREFIX) ||
86
- message.startsWith(MACOS_VISION_UNAVAILABLE_PREFIX) ||
87
- message.startsWith(OCR_UNSUPPORTED_PLATFORM_PREFIX));
88
- }
@@ -1,32 +0,0 @@
1
- import type { PageViewport } from "pdfjs-dist";
2
- import type { PdfColor, PdfPoint, PdfRect, PdfTextAppearance } from "./pdfEditModel.js";
3
- import type { OcrLine } from "./pdfOcr.js";
4
- export interface PdfOcrHit {
5
- index: number;
6
- line: OcrLine;
7
- }
8
- export interface PdfOcrCorrectionGeometry {
9
- lineId: string;
10
- rect: PdfRect;
11
- anchor: PdfPoint;
12
- appearance: PdfTextAppearance;
13
- }
14
- export declare function findOcrLineAt(lines: readonly OcrLine[], normalizedX: number, normalizedY: number): PdfOcrHit | null;
15
- export declare function ocrLineId(page: number, index: number, line: OcrLine): string;
16
- export declare function ocrLineCorrectionGeometry(page: number, index: number, line: OcrLine, viewport: PageViewport): PdfOcrCorrectionGeometry;
17
- /** Finds one dominant local color while tolerating dark glyph pixels over a uniform scan. */
18
- export declare function detectNearUniformBackground(data: Uint8ClampedArray): PdfColor | null;
19
- /**
20
- * Dominant background color under a CSS-pixel rect of a page slot, read from that slot's render
21
- * canvas (which is rasterized at devicePixelRatio, so CSS px are scaled by canvas/slot width).
22
- * Used to mask the original glyphs under the inline source-text editor; `null` when the area is
23
- * not near-uniform (e.g. text over an image), in which case the caller falls back to white.
24
- */
25
- export declare function sampleCanvasRectBackground(canvas: HTMLCanvasElement, rectCss: PdfRect, slotWidthCss: number): PdfColor | null;
26
- export declare function sampleOcrBackground(canvas: HTMLCanvasElement, line: OcrLine): PdfColor | null;
27
- export declare function applyOcrCorrections(source: ReadonlyMap<number, OcrLine[]>, corrections: readonly {
28
- kind: string;
29
- page?: number;
30
- lineId?: string;
31
- text?: string;
32
- }[]): Map<number, OcrLine[]>;
@@ -1,138 +0,0 @@
1
- export function findOcrLineAt(lines, normalizedX, normalizedY) {
2
- for (let index = 0; index < lines.length; index += 1) {
3
- const line = lines[index];
4
- if (normalizedX >= line.rect.x &&
5
- normalizedX <= line.rect.x + line.rect.width &&
6
- normalizedY >= line.rect.y &&
7
- normalizedY <= line.rect.y + line.rect.height) {
8
- return { index, line };
9
- }
10
- }
11
- return null;
12
- }
13
- function hashText(input) {
14
- let hash = 0x811c9dc5;
15
- for (let index = 0; index < input.length; index += 1) {
16
- hash ^= input.charCodeAt(index);
17
- hash = Math.imul(hash, 0x01000193);
18
- }
19
- return (hash >>> 0).toString(16).padStart(8, "0");
20
- }
21
- export function ocrLineId(page, index, line) {
22
- return `${page}:${index}:${hashText(JSON.stringify([line.text, line.rect]))}`;
23
- }
24
- export function ocrLineCorrectionGeometry(page, index, line, viewport) {
25
- const left = line.rect.x * viewport.width;
26
- const top = line.rect.y * viewport.height;
27
- const right = (line.rect.x + line.rect.width) * viewport.width;
28
- const bottom = (line.rect.y + line.rect.height) * viewport.height;
29
- const corners = [
30
- viewport.convertToPdfPoint(left, top),
31
- viewport.convertToPdfPoint(right, top),
32
- viewport.convertToPdfPoint(left, bottom),
33
- viewport.convertToPdfPoint(right, bottom),
34
- ];
35
- const xs = corners.map(([x]) => x);
36
- const ys = corners.map(([, y]) => y);
37
- const rect = {
38
- x: Math.min(...xs),
39
- y: Math.min(...ys),
40
- width: Math.max(...xs) - Math.min(...xs),
41
- height: Math.max(...ys) - Math.min(...ys),
42
- };
43
- const sizePt = Math.max(6, Math.min(72, rect.height * 0.72));
44
- return {
45
- lineId: ocrLineId(page, index, line),
46
- rect,
47
- anchor: { x: rect.x, y: rect.y + rect.height * 0.18 },
48
- appearance: {
49
- fontId: "source-serif-4",
50
- family: "Source Serif 4",
51
- sizePt,
52
- color: { r: 0, g: 0, b: 0, alpha: 1 },
53
- },
54
- };
55
- }
56
- /** Finds one dominant local color while tolerating dark glyph pixels over a uniform scan. */
57
- export function detectNearUniformBackground(data) {
58
- const buckets = new Map();
59
- let opaque = 0;
60
- for (let index = 0; index + 3 < data.length; index += 4) {
61
- if (data[index + 3] < 128)
62
- continue;
63
- opaque += 1;
64
- const key = `${data[index] >> 4}:${data[index + 1] >> 4}:${data[index + 2] >> 4}`;
65
- const bucket = buckets.get(key) ?? { count: 0, r: 0, g: 0, b: 0 };
66
- bucket.count += 1;
67
- bucket.r += data[index];
68
- bucket.g += data[index + 1];
69
- bucket.b += data[index + 2];
70
- buckets.set(key, bucket);
71
- }
72
- if (opaque === 0)
73
- return null;
74
- const dominant = [...buckets.values()].sort((a, b) => b.count - a.count)[0];
75
- if (!dominant || dominant.count / opaque < 0.6)
76
- return null;
77
- return {
78
- r: dominant.r / dominant.count / 255,
79
- g: dominant.g / dominant.count / 255,
80
- b: dominant.b / dominant.count / 255,
81
- alpha: 1,
82
- };
83
- }
84
- /**
85
- * Dominant background color under a CSS-pixel rect of a page slot, read from that slot's render
86
- * canvas (which is rasterized at devicePixelRatio, so CSS px are scaled by canvas/slot width).
87
- * Used to mask the original glyphs under the inline source-text editor; `null` when the area is
88
- * not near-uniform (e.g. text over an image), in which case the caller falls back to white.
89
- */
90
- export function sampleCanvasRectBackground(canvas, rectCss, slotWidthCss) {
91
- if (slotWidthCss <= 0 || canvas.width <= 0)
92
- return null;
93
- const context = canvas.getContext("2d", { willReadFrequently: true });
94
- if (!context)
95
- return null;
96
- const ratio = canvas.width / slotWidthCss;
97
- const x = Math.max(0, Math.floor(rectCss.x * ratio));
98
- const y = Math.max(0, Math.floor(rectCss.y * ratio));
99
- const width = Math.max(1, Math.min(canvas.width - x, Math.ceil(rectCss.width * ratio)));
100
- const height = Math.max(1, Math.min(canvas.height - y, Math.ceil(rectCss.height * ratio)));
101
- try {
102
- return detectNearUniformBackground(context.getImageData(x, y, width, height).data);
103
- }
104
- catch {
105
- return null;
106
- }
107
- }
108
- export function sampleOcrBackground(canvas, line) {
109
- const context = canvas.getContext("2d", { willReadFrequently: true });
110
- if (!context)
111
- return null;
112
- const x = Math.max(0, Math.floor(line.rect.x * canvas.width));
113
- const y = Math.max(0, Math.floor(line.rect.y * canvas.height));
114
- const width = Math.max(1, Math.min(canvas.width - x, Math.ceil(line.rect.width * canvas.width)));
115
- const height = Math.max(1, Math.min(canvas.height - y, Math.ceil(line.rect.height * canvas.height)));
116
- try {
117
- return detectNearUniformBackground(context.getImageData(x, y, width, height).data);
118
- }
119
- catch {
120
- return null;
121
- }
122
- }
123
- export function applyOcrCorrections(source, corrections) {
124
- const latest = new Map();
125
- for (const command of corrections) {
126
- if (command.kind === "correctOcrText" && command.lineId && command.text !== undefined) {
127
- latest.set(command.lineId, command.text);
128
- }
129
- }
130
- const result = new Map();
131
- for (const [page, lines] of source) {
132
- result.set(page, lines.map((line, index) => {
133
- const text = latest.get(ocrLineId(page, index, line));
134
- return text === undefined ? line : { ...line, text };
135
- }));
136
- }
137
- return result;
138
- }
@@ -1,28 +0,0 @@
1
- import type { PDFDocumentProxy } from "pdfjs-dist";
2
- /** Rasterize PDF bytes into the print host as flat images, then trigger native print.
3
- *
4
- * Two deliberate choices, both learned the hard way on macOS:
5
- *
6
- * 1. A SEPARATE pdf.js document is loaded from these bytes, never the on-screen viewer's live
7
- * `PDFDocumentProxy`. pdf.js caches page proxies per document, so printing off the viewer's copy
8
- * issues a second `render()` against a `PDFPageProxy` the viewer may already be rendering — a
9
- * documented failure class whose symptom is a render promise that never settles, i.e. print that
10
- * silently does nothing. An isolated document shares no page state.
11
- * 2. Each page is rasterized to a canvas and then flattened to a `data:` image. The canvas itself is
12
- * NOT appended: WKWebView's print pass re-renders the page into a print context and canvas
13
- * backing stores do not reliably survive it (the same reason the image viewer had to print a flat
14
- * `<img src="data:...">` instead of its live stage). Images do survive.
15
- *
16
- * Every await is bounded, so a hang surfaces as a rejected promise the caller can toast rather than
17
- * as silence. */
18
- export declare function printPdfBytes(bytes: ArrayBuffer | Uint8Array): Promise<boolean>;
19
- /** Print the PDF a viewer pane already has open, WITHOUT re-fetching it. `getData()` hands back the
20
- * bytes pdf.js already holds in memory, so this never re-opens the revocable viewright-stream:// URL
21
- * that the on-screen viewer is still streaming from. */
22
- export declare function printPdfDocument(doc: PDFDocumentProxy): Promise<boolean>;
23
- /** Fallback PDF print path for when no viewer pane is registered: fetch the bytes from the stream URL
24
- * and rasterize them. Prefer printPdfDocument whenever a pane is mounted — it skips the fetch. NOT a
25
- * plain `<iframe src=blobURL>`: WKWebView does not reliably render a PDF blob inline via a bare
26
- * iframe navigation, so `iframe.onload` never fires and print() never runs — the original silent
27
- * no-op on macOS that this replaced. */
28
- export declare function printPdf(assetUrl: string): Promise<boolean>;
@@ -1,110 +0,0 @@
1
- import * as pdfjs from "pdfjs-dist";
2
- import { pdfDocumentInit } from "@rightkit/pdf-ui";
3
- import { mountPrintImage, printHost } from "../../app/printPipelineShared.js";
4
- import { withTimeout } from "./pdfShared.js";
5
- // pdf.js's getDocument() spins up a Web Worker to parse the PDF off the main thread, and if that
6
- // worker never starts (silently, e.g. resolving `?url` through a fresh dynamic import chunk rather
7
- // than the already-loaded static module), `getDocument().promise` just hangs forever — no error, no
8
- // rejection, nothing to catch. That hang was indistinguishable from a printer dialog never opening.
9
- // The worker URL must therefore be resolved exactly ONCE, by the host app at startup, through
10
- // @rightkit/pdf-ui's configurePdfJs({ workerSrc }) (a Vite host: `import workerUrl from
11
- // "pdfjs-dist/build/pdf.worker.min.mjs?url"`): a published package cannot resolve a bundler URL. A hard
12
- // timeout below converts any hang into a visible failure instead of silence.
13
- /** Rasterize PDF bytes into the print host as flat images, then trigger native print.
14
- *
15
- * Two deliberate choices, both learned the hard way on macOS:
16
- *
17
- * 1. A SEPARATE pdf.js document is loaded from these bytes, never the on-screen viewer's live
18
- * `PDFDocumentProxy`. pdf.js caches page proxies per document, so printing off the viewer's copy
19
- * issues a second `render()` against a `PDFPageProxy` the viewer may already be rendering — a
20
- * documented failure class whose symptom is a render promise that never settles, i.e. print that
21
- * silently does nothing. An isolated document shares no page state.
22
- * 2. Each page is rasterized to a canvas and then flattened to a `data:` image. The canvas itself is
23
- * NOT appended: WKWebView's print pass re-renders the page into a print context and canvas
24
- * backing stores do not reliably survive it (the same reason the image viewer had to print a flat
25
- * `<img src="data:...">` instead of its live stage). Images do survive.
26
- *
27
- * Every await is bounded, so a hang surfaces as a rejected promise the caller can toast rather than
28
- * as silence. */
29
- export async function printPdfBytes(bytes) {
30
- const data = bytes instanceof Uint8Array ? bytes.slice() : new Uint8Array(bytes.slice(0));
31
- const loadingTask = pdfjs.getDocument(pdfDocumentInit(data));
32
- try {
33
- const doc = await withTimeout(loadingTask.promise, 20_000, "pdf.js getDocument timed out");
34
- let mountedPages = 0;
35
- return await printHost(async (host) => {
36
- for (let i = 1; i <= doc.numPages; i++) {
37
- const page = await withTimeout(doc.getPage(i), 20_000, `pdf.js getPage(${i}) timed out`);
38
- try {
39
- const viewport = page.getViewport({ scale: 2 });
40
- const canvas = document.createElement("canvas");
41
- canvas.width = viewport.width;
42
- canvas.height = viewport.height;
43
- try {
44
- const ctx = canvas.getContext("2d");
45
- if (!ctx)
46
- throw new Error(`PDF print page ${i} has no 2D canvas context`);
47
- const renderTask = page.render({ canvas, canvasContext: ctx, viewport });
48
- try {
49
- await withTimeout(renderTask.promise, 30_000, `pdf.js render(page ${i}) timed out`);
50
- }
51
- catch (error) {
52
- // A timeout only races the promise; cancel the underlying render before cleanup and
53
- // backing-store release so a late worker completion cannot paint a detached canvas.
54
- renderTask.cancel?.();
55
- throw error;
56
- }
57
- if (mountedPages === 0) {
58
- const pageSize = document.createElement("style");
59
- // PDF.js viewport units at scale 1 are PDF points. Matching @page to those dimensions
60
- // makes each raster image exactly one printer sheet; width-only fitting spilled an A4
61
- // PDF onto Letter's next sheet, which looked like alternating blank pages.
62
- pageSize.textContent = `@page { size: ${viewport.width / 2}pt ${viewport.height / 2}pt; margin: 0; }`;
63
- host.appendChild(pageSize);
64
- }
65
- // Prepare one page at a time. The host must retain every decoded image for native print,
66
- // but no second all-pages `pageImages` array or canvas backing stores are retained while
67
- // later pages render. Data URLs remain the portable representation required by the
68
- // Windows preview webview and by WKWebView's raster print path.
69
- const src = canvas.toDataURL("image/png");
70
- await mountPrintImage(host, src, "box-sizing:border-box;display:block;width:99.5%;height:auto;margin:0 auto;break-inside:avoid;page-break-inside:avoid;");
71
- mountedPages += 1;
72
- }
73
- finally {
74
- // Explicitly release temporary raster backing store before preparing next page.
75
- canvas.width = 0;
76
- canvas.height = 0;
77
- }
78
- }
79
- finally {
80
- page.cleanup();
81
- }
82
- }
83
- return mountedPages > 0;
84
- });
85
- }
86
- finally {
87
- try {
88
- await withTimeout(loadingTask.destroy(), 5_000, "pdf.js print cleanup timed out");
89
- }
90
- catch {
91
- // Cleanup failure must not become an unhandled rejection or replace the print error.
92
- }
93
- }
94
- }
95
- /** Print the PDF a viewer pane already has open, WITHOUT re-fetching it. `getData()` hands back the
96
- * bytes pdf.js already holds in memory, so this never re-opens the revocable viewright-stream:// URL
97
- * that the on-screen viewer is still streaming from. */
98
- export async function printPdfDocument(doc) {
99
- const bytes = await withTimeout(doc.getData(), 15_000, "pdf.js getData timed out");
100
- return printPdfBytes(bytes);
101
- }
102
- /** Fallback PDF print path for when no viewer pane is registered: fetch the bytes from the stream URL
103
- * and rasterize them. Prefer printPdfDocument whenever a pane is mounted — it skips the fetch. NOT a
104
- * plain `<iframe src=blobURL>`: WKWebView does not reliably render a PDF blob inline via a bare
105
- * iframe navigation, so `iframe.onload` never fires and print() never runs — the original silent
106
- * no-op on macOS that this replaced. */
107
- export async function printPdf(assetUrl) {
108
- const bytes = await withTimeout(fetch(assetUrl).then((r) => r.arrayBuffer()), 15_000, "pdf byte fetch timed out");
109
- return printPdfBytes(bytes);
110
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * PDF save orchestration (`docs/PDF-EDITOR-NOW.md` §9.4, §10).
3
- *
4
- * Two independent flows live here, both built against the *injected-bridge* function types frozen
5
- * in `pdfEditModel.ts` (§10.3's "Frozen cross-lane seams" — the real `tauriBridge.ts` Tauri-backed
6
- * implementations land later at Dispatch D2, so every backend call here is a parameter, never an
7
- * import):
8
- *
9
- * 1. `recomposePdfPreview` (§9.4) — calls an injected `ComposePdfPreview`-shaped function to get
10
- * memory-only candidate bytes for the current source-command history, so the on-page display of
11
- * committed-but-unsaved edits always renders from the same composer Save itself will use.
12
- *
13
- * 2. `createPdfSavePlan` (§10.1/§10.2/§10.3) — the two-phase Save protocol: prepare (Rust composes
14
- * + structurally validates a candidate and returns it plus a short-lived token), isolated
15
- * pdf.js render-verify (catch corruption client-side before committing), then commit (Rust
16
- * atomically replaces bytes). Returns a `PdfSavePlan` (§10.3's exact shape) rather than bare
17
- * bytes, so the generic document saver only updates recents/fingerprints/checkpoints after a
18
- * real commit succeeds.
19
- *
20
- * `verifyPdfCandidate` (§10.2) loads candidate bytes into a FRESH, isolated pdf.js document — never
21
- * the active on-screen `PDFDocumentProxy` — and renders affected pages to an offscreen canvas before
22
- * extracting text for the expected-text checks. This mirrors `pdfPrinting.ts`'s isolated-document
23
- * pattern (pdf.js caches page proxies per document; reusing the live viewer's document risks a
24
- * second concurrent `render()` against a `PDFPageProxy` it may already be rendering — a hang, not an
25
- * error). Every await is time-bounded so a stuck pdf.js worker surfaces as a rejected promise
26
- * (`VerificationFailed`) instead of silence.
27
- */
28
- import type { CommitPdfSave, ComposePdfPreview, ComposedPdfPreview, PdfEditCommand, PdfExpectedTextCheck, PdfSavePlan, PreparePdfSave } from "./pdfEditModel.js";
29
- /** Deps for `recomposePdfPreview` — just the injected bridge call, kept as an object so call sites
30
- * read as named fields rather than positional args (matches this module's other `*Deps` shapes). */
31
- export interface RecomposePdfPreviewDeps {
32
- composePdfPreview: ComposePdfPreview;
33
- }
34
- /** Recompose the memory-only preview candidate for the given source-command history (§9.4). No
35
- * token, no retained candidate, no write — callers re-render affected pages from the returned
36
- * bytes inside their existing page slots. Thin by design: the composer decides validity; this
37
- * function only shapes the request and forwards the injected call. */
38
- export declare function recomposePdfPreview(deps: RecomposePdfPreviewDeps, sourcePath: string, expectedSourceSha256: string, sourceCommands: readonly PdfEditCommand[]): Promise<ComposedPdfPreview>;
39
- /** Thrown by `verifyPdfCandidate` on any renderability/content failure. Callers map this to
40
- * `PdfEditError` code `"verification_failed"` (§11) rather than letting a raw pdf.js error surface,
41
- * since §11 requires logs to carry "code/page/strategy but no document text" — this type carries a
42
- * `page` when the failure is page-scoped and never embeds extracted document text in its message. */
43
- export declare class PdfCandidateVerificationError extends Error {
44
- readonly page: number | null;
45
- constructor(message: string, page?: number | null);
46
- }
47
- export interface VerifyPdfCandidateArgs {
48
- bytes: Uint8Array;
49
- pages: readonly number[];
50
- expectedText: readonly PdfExpectedTextCheck[];
51
- }
52
- /** Render every page in `pages` from a FRESH, isolated pdf.js document built from `bytes` (never the
53
- * active on-screen document — see module doc), waiting for each render to fully settle, then check
54
- * that every `expectedText` entry's text is present somewhere in that page's extracted text.
55
- * Resolves on success; rejects with `PdfCandidateVerificationError` on any load/render/text-check
56
- * failure. Always disposes the isolated document via `doc.destroy()`, success or failure, so a
57
- * failed verification never leaks a pdf.js worker/document. */
58
- export declare function verifyPdfCandidate({ bytes, pages, expectedText, }: VerifyPdfCandidateArgs): Promise<void>;
59
- export interface PdfSaveDeps {
60
- preparePdfSave: PreparePdfSave;
61
- commitPdfSave: CommitPdfSave;
62
- /** Injectable so tests can substitute a fake without mocking pdf.js. Defaults to the real
63
- * `verifyPdfCandidate` above. */
64
- verifyCandidate?: (args: VerifyPdfCandidateArgs) => Promise<void>;
65
- }
66
- export interface PdfSaveInput {
67
- sourcePath: string;
68
- expectedSourceSha256: string;
69
- annotationPdfBytes: Uint8Array;
70
- sourceCommands: readonly PdfEditCommand[];
71
- }
72
- /**
73
- * Runs §10.1 prepare against the injected `preparePdfSave`, then §10.2 isolated pdf.js
74
- * render-verify against the returned candidate. Returns a `PdfSavePlan` (§10.3's exact shape) only
75
- * once both steps succeed — `commit` is the only thing that talks to `commitPdfSave`, and it does so
76
- * lazily so a caller that decides not to commit (e.g. user cancels a Save As target picker between
77
- * prepare and commit) never invalidates the token by calling it.
78
- *
79
- * Token lifecycle (§10.3): the token this returns is single-use, expires after five minutes, and is
80
- * invalidated by any source revision in between — this module does not retry or re-prepare on a
81
- * stale-token rejection from `commit_pdf_save`; it surfaces the typed `PdfEditError` (expected code
82
- * `"write_conflict"` or `"stale_source"` per §11) to the caller, which owns the "start over" UX.
83
- * `didCommit` intentionally does nothing beyond what `commitPdfSave` already returned — §10.3 gives
84
- * the generic document saver, not this module, ownership of when recents/fingerprints/checkpoints
85
- * update, so this hook exists only as the seam that saver calls after using `result`.
86
- */
87
- export declare function prepareAndVerifyPdfSave(deps: PdfSaveDeps, input: PdfSaveInput): Promise<PdfSavePlan>;