@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.
- package/CHANGELOG.md +29 -0
- package/README.md +31 -7
- package/dist/app/chrome.js +1 -1
- package/dist/app/documentPrinting.d.ts +0 -6
- package/dist/app/documentPrinting.js +0 -18
- package/dist/app/shell/nativeMenu.d.ts +3 -2
- package/dist/app/shell/nativeMenu.js +8 -5
- package/dist/documents/perfEventCounters.d.ts +0 -8
- package/dist/documents/perfEventCounters.js +2 -1
- package/dist/documents/preview/previewFrame.d.ts +2 -1
- package/dist/documents/preview/previewFrame.js +9 -7
- package/dist/documents/searchUtils.js +1 -1
- package/dist/host.d.ts +36 -0
- package/dist/host.js +54 -0
- package/dist/index.d.ts +1 -50
- package/dist/index.js +1 -50
- package/dist/settings/themes.js +1 -1
- package/dist/state/bridgeEvents.d.ts +1 -1
- package/dist/state/bridgeEvents.js +7 -4
- package/dist/state/recentUrls.js +4 -3
- package/dist/state/tauriBridge.d.ts +3 -61
- package/dist/state/tauriBridge.js +8 -297
- package/dist/styles.css +2 -2
- package/package.json +25 -98
- package/dist/app/shortcutChords.d.ts +0 -6
- package/dist/app/shortcutChords.js +0 -14
- package/dist/app/useCloseFindOnSurfaceChange.d.ts +0 -14
- package/dist/app/useCloseFindOnSurfaceChange.js +0 -23
- package/dist/app/useEditText.d.ts +0 -6
- package/dist/app/useEditText.js +0 -10
- package/dist/appName.d.ts +0 -2
- package/dist/appName.js +0 -8
- package/dist/documents/FullscreenIcon.d.ts +0 -4
- package/dist/documents/FullscreenIcon.js +0 -7
- package/dist/documents/mdx/mdxRuntimeScript.d.ts +0 -2
- package/dist/documents/mdx/mdxRuntimeScript.js +0 -3
- package/dist/documents/mdx/mdxRuntimeScript.txt +0 -584
- package/dist/documents/pdf/MacPdfReadPane.d.ts +0 -23
- package/dist/documents/pdf/MacPdfReadPane.js +0 -708
- package/dist/documents/pdf/NativePdfDocument.d.ts +0 -9
- package/dist/documents/pdf/NativePdfDocument.js +0 -41
- package/dist/documents/pdf/PdfInlineTextEditor.d.ts +0 -53
- package/dist/documents/pdf/PdfInlineTextEditor.js +0 -225
- package/dist/documents/pdf/PdfOcrLayer.d.ts +0 -4
- package/dist/documents/pdf/PdfOcrLayer.js +0 -30
- package/dist/documents/pdf/PdfOutline.d.ts +0 -6
- package/dist/documents/pdf/PdfOutline.js +0 -12
- package/dist/documents/pdf/PdfPasswordPrompt.d.ts +0 -5
- package/dist/documents/pdf/PdfPasswordPrompt.js +0 -24
- package/dist/documents/pdf/PdfThumbnails.d.ts +0 -8
- package/dist/documents/pdf/PdfThumbnails.js +0 -201
- package/dist/documents/pdf/PdfToolbar.d.ts +0 -47
- package/dist/documents/pdf/PdfToolbar.js +0 -76
- package/dist/documents/pdf/PdfViewer.d.ts +0 -36
- package/dist/documents/pdf/PdfViewer.js +0 -1522
- package/dist/documents/pdf/PdfViewerBody.d.ts +0 -22
- package/dist/documents/pdf/PdfViewerBody.js +0 -42
- package/dist/documents/pdf/macPdfNativeBridge.d.ts +0 -118
- package/dist/documents/pdf/macPdfNativeBridge.js +0 -134
- package/dist/documents/pdf/nearestVisiblePdfPage.d.ts +0 -2
- package/dist/documents/pdf/nearestVisiblePdfPage.js +0 -22
- package/dist/documents/pdf/pdfBench.d.ts +0 -88
- package/dist/documents/pdf/pdfBench.js +0 -535
- package/dist/documents/pdf/pdfBenchAutopilot.d.ts +0 -48
- package/dist/documents/pdf/pdfBenchAutopilot.js +0 -154
- package/dist/documents/pdf/pdfBenchTransport.d.ts +0 -21
- package/dist/documents/pdf/pdfBenchTransport.js +0 -233
- package/dist/documents/pdf/pdfEditModel.d.ts +0 -294
- package/dist/documents/pdf/pdfEditModel.js +0 -105
- package/dist/documents/pdf/pdfEditor.d.ts +0 -100
- package/dist/documents/pdf/pdfEditor.js +0 -68
- package/dist/documents/pdf/pdfOcr.d.ts +0 -36
- package/dist/documents/pdf/pdfOcr.js +0 -88
- package/dist/documents/pdf/pdfOcrEdit.d.ts +0 -32
- package/dist/documents/pdf/pdfOcrEdit.js +0 -138
- package/dist/documents/pdf/pdfPrinting.d.ts +0 -28
- package/dist/documents/pdf/pdfPrinting.js +0 -110
- package/dist/documents/pdf/pdfSave.d.ts +0 -87
- package/dist/documents/pdf/pdfSave.js +0 -250
- package/dist/documents/pdf/pdfShared.d.ts +0 -16
- package/dist/documents/pdf/pdfShared.js +0 -20
- package/dist/documents/pdf/pdfToMarkdown.d.ts +0 -11
- package/dist/documents/pdf/pdfToMarkdown.js +0 -67
- package/dist/documents/pdf/pdfjsLibGlobal.d.ts +0 -1
- package/dist/documents/pdf/pdfjsLibGlobal.js +0 -12
- package/dist/documents/pdf/toolbarIcons.d.ts +0 -18
- package/dist/documents/pdf/toolbarIcons.js +0 -31
- package/dist/documents/pdf/usePdfDocumentLoader.d.ts +0 -40
- package/dist/documents/pdf/usePdfDocumentLoader.js +0 -243
- package/dist/documents/pdf/usePdfEditSession.d.ts +0 -88
- package/dist/documents/pdf/usePdfEditSession.js +0 -341
- package/dist/documents/pdf/usePdfEditor.d.ts +0 -59
- package/dist/documents/pdf/usePdfEditor.js +0 -317
- package/dist/documents/pdf/usePdfFit.d.ts +0 -19
- package/dist/documents/pdf/usePdfFit.js +0 -55
- package/dist/documents/pdf/usePdfOcr.d.ts +0 -38
- package/dist/documents/pdf/usePdfOcr.js +0 -337
- package/dist/documents/pdf/usePdfPreviewRecompose.d.ts +0 -28
- package/dist/documents/pdf/usePdfPreviewRecompose.js +0 -256
- package/dist/documents/pdf/usePdfSavePlan.d.ts +0 -61
- package/dist/documents/pdf/usePdfSavePlan.js +0 -303
- package/dist/documents/pdf/usePdfSearch.d.ts +0 -31
- package/dist/documents/pdf/usePdfSearch.js +0 -367
- package/dist/documents/pdf/usePdfViewControls.d.ts +0 -72
- package/dist/documents/pdf/usePdfViewControls.js +0 -152
- package/dist/documents/scopedSelectAll.d.ts +0 -5
- package/dist/documents/scopedSelectAll.js +0 -77
- package/dist/documents/txt/cmScroll.d.ts +0 -11
- package/dist/documents/txt/cmScroll.js +0 -52
- package/dist/documents/useMirroredEditText.d.ts +0 -15
- package/dist/documents/useMirroredEditText.js +0 -29
- package/dist/documents/useSplitResize.d.ts +0 -7
- package/dist/documents/useSplitResize.js +0 -37
- package/dist/settings/rk-settings.css +0 -38
- package/dist/settings/sections/EditorSection.d.ts +0 -1
- package/dist/settings/sections/EditorSection.js +0 -18
- package/dist/settings/sections/FileAssociationsSection.d.ts +0 -1
- package/dist/settings/sections/FileAssociationsSection.js +0 -5
- package/dist/settings/sections/MacFileAssociationsBlock.d.ts +0 -33
- package/dist/settings/sections/MacFileAssociationsBlock.js +0 -151
- package/dist/settings/sections/ThemeSection.d.ts +0 -1
- package/dist/settings/sections/ThemeSection.js +0 -31
- package/dist/settings/sections/TypographySection.d.ts +0 -1
- package/dist/settings/sections/TypographySection.js +0 -19
- package/dist/settings/sections/onOff.d.ts +0 -11
- package/dist/settings/sections/onOff.js +0 -8
- package/dist/state/browserQa.d.ts +0 -21
- package/dist/state/browserQa.js +0 -303
- package/dist/state/browserQaDocx.d.ts +0 -3
- package/dist/state/browserQaDocx.js +0 -35
- package/dist/state/browserQaLicense.d.ts +0 -8
- package/dist/state/browserQaLicense.js +0 -7
- package/dist/state/browserQaPdf.d.ts +0 -60
- package/dist/state/browserQaPdf.js +0 -302
- package/dist/state/browserQaText.d.ts +0 -6
- 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>;
|