@2018kguo/recto 0.0.0-stage → 0.1.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/LICENSE +26 -0
- package/README.md +25 -2
- package/dist/assets.d.ts +14 -0
- package/dist/assets.js +169 -0
- package/dist/document.d.ts +261 -0
- package/dist/document.js +300 -0
- package/dist/engine.d.ts +54 -0
- package/dist/engine.js +205 -0
- package/dist/errors.d.ts +34 -0
- package/dist/errors.js +11 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -0
- package/dist/page-measures.d.ts +34 -0
- package/dist/page-measures.js +166 -0
- package/dist/ranged.d.ts +88 -0
- package/dist/ranged.js +470 -0
- package/dist/reading-order.d.ts +14 -0
- package/dist/reading-order.js +173 -0
- package/dist/sha256.d.ts +1 -0
- package/dist/sha256.js +78 -0
- package/dist/text-items.d.ts +49 -0
- package/dist/text-items.js +272 -0
- package/package.json +47 -4
- package/wasm/THIRD_PARTY_NOTICES +2518 -0
- package/wasm/cjk-fallback.ttf +0 -0
- package/wasm/cmaps.brotli +0 -0
- package/wasm/fonts.bin +0 -0
- package/wasm/pins.d.mts +1 -0
- package/wasm/pins.mjs +7 -0
- package/wasm/recto.gen.d.mts +2 -0
- package/wasm/recto.gen.mjs +1097 -0
- package/wasm/recto.types.d.mts +250 -0
- package/wasm/recto.wasm +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Guo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
vendor/ holds patched copies of the hayro crates, MIT or Apache-2.0 licensed by
|
|
24
|
+
their authors; see vendor/README.md and each crate's licence files. The shipped
|
|
25
|
+
fonts and CMaps have their own licences, gathered in js/wasm/THIRD_PARTY_NOTICES
|
|
26
|
+
by the build.
|
package/README.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @2018kguo/recto
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A PDF engine for JavaScript: text with positions, page objects and rendering, in Rust compiled
|
|
4
|
+
to WebAssembly. No runtime dependencies and no install scripts; the built wasm ships in this
|
|
5
|
+
package, so you don't need Rust.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { openPdf } from "@2018kguo/recto";
|
|
10
|
+
|
|
11
|
+
const bytes = await readFile("report.pdf");
|
|
12
|
+
await using doc = await openPdf(bytes);
|
|
13
|
+
doc.pageCount;
|
|
14
|
+
doc.text(0).text;
|
|
15
|
+
const { width, height, rgba } = doc.render(0, { width: 1024 });
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Calls run synchronously on the calling thread, so put heavy work on a `worker_threads` worker. In
|
|
19
|
+
a browser, use a dedicated Web Worker: `import` it there and call `openPdf`. Nothing else is
|
|
20
|
+
needed. It also runs on a page's main thread and in a service worker, where it fetches its assets
|
|
21
|
+
up front, but a main thread freezes for the length of each call.
|
|
22
|
+
|
|
23
|
+
[Documentation](https://github.com/2018kguo/recto#readme) ·
|
|
24
|
+
[Reference](https://github.com/2018kguo/recto/blob/main/docs/reference.md)
|
|
25
|
+
|
|
26
|
+
MIT. The bundled fonts and CMaps have their own licences: see `wasm/THIRD_PARTY_NOTICES`.
|
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PINS } from "../wasm/pins.mjs";
|
|
2
|
+
export type AssetName = keyof typeof PINS;
|
|
3
|
+
/** Whether an asset can be read at the moment the engine asks for it. */
|
|
4
|
+
export declare function canReadSync(): boolean;
|
|
5
|
+
/** `assetBase` as a directory URL, or null for the package's own files. */
|
|
6
|
+
export declare function assetBaseUrl(base: string | URL | undefined): URL | null;
|
|
7
|
+
/** The wasm's bytes, for compiling. Asynchronous where it can be. */
|
|
8
|
+
export declare function loadWasm(base: URL | null): Promise<Uint8Array<ArrayBuffer>>;
|
|
9
|
+
/** Fetches assets ahead of time, for a thread that can't read them when they're needed. */
|
|
10
|
+
export declare function preloadAssets(base: URL | null, names: AssetName[]): Promise<void>;
|
|
11
|
+
/** An asset, read synchronously. Throws a PdfError rather than returning nothing. */
|
|
12
|
+
export declare function loadAssetSync(name: AssetName, base: URL | null): Uint8Array;
|
|
13
|
+
/** Entry `index` of fonts.bin: u32 count, u32 offsets[count + 1], bytes (little-endian). */
|
|
14
|
+
export declare function fontFromBundle(bundle: Uint8Array, index: number): Uint8Array;
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// The files in ../wasm: where they are, how to read them, and the check that they are the
|
|
2
|
+
// ones this build was made with.
|
|
3
|
+
//
|
|
4
|
+
// The engine asks for a font or CMap from inside a synchronous call, the first time a
|
|
5
|
+
// document needs one, so reading must be synchronous too: fs in Node, a synchronous
|
|
6
|
+
// XMLHttpRequest in a Web Worker. A page's main thread may not fetch bytes synchronously, and a
|
|
7
|
+
// service worker has no XMLHttpRequest, so there they are fetched up front instead. The URLs
|
|
8
|
+
// are written out as `new URL("literal", import.meta.url)` because that is the form
|
|
9
|
+
// bundlers recognise and rewrite, emitting the file.
|
|
10
|
+
//
|
|
11
|
+
// Each file is pinned by SHA-256: the engine reads its tables as trusted, and a dev server's
|
|
12
|
+
// HTML fallback page must not read as a font. pins.mjs is written by the build.
|
|
13
|
+
import { PINS } from "../wasm/pins.mjs";
|
|
14
|
+
import { PdfError } from "./errors.js";
|
|
15
|
+
import { sha256Hex } from "./sha256.js";
|
|
16
|
+
function packagedUrl(name) {
|
|
17
|
+
switch (name) {
|
|
18
|
+
case "recto.wasm":
|
|
19
|
+
return new URL("../wasm/recto.wasm", import.meta.url);
|
|
20
|
+
case "fonts.bin":
|
|
21
|
+
return new URL("../wasm/fonts.bin", import.meta.url);
|
|
22
|
+
case "cjk-fallback.ttf":
|
|
23
|
+
return new URL("../wasm/cjk-fallback.ttf", import.meta.url);
|
|
24
|
+
case "cmaps.brotli":
|
|
25
|
+
return new URL("../wasm/cmaps.brotli", import.meta.url);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** A stalled server must not block the thread for ever; the largest asset is 5 MB. */
|
|
29
|
+
const ASSET_TIMEOUT_MS = 60_000;
|
|
30
|
+
/** Node's fs when there is one. `getBuiltinModule` keeps bundlers from following it. */
|
|
31
|
+
function nodeFs() {
|
|
32
|
+
return globalThis.process?.getBuiltinModule?.("node:fs");
|
|
33
|
+
}
|
|
34
|
+
/** Whether an asset can be read at the moment the engine asks for it. */
|
|
35
|
+
export function canReadSync() {
|
|
36
|
+
if (nodeFs()) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return typeof document === "undefined" && typeof XMLHttpRequest !== "undefined";
|
|
40
|
+
}
|
|
41
|
+
/** `assetBase` as a directory URL, or null for the package's own files. */
|
|
42
|
+
export function assetBaseUrl(base) {
|
|
43
|
+
if (base === undefined) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
let url;
|
|
47
|
+
if (base instanceof URL) {
|
|
48
|
+
url = new URL(base);
|
|
49
|
+
}
|
|
50
|
+
else if (/^[a-z][a-z0-9+.-]+:/iu.test(base)) {
|
|
51
|
+
url = new URL(base);
|
|
52
|
+
}
|
|
53
|
+
else if (nodeFs()) {
|
|
54
|
+
url = globalThis.process.getBuiltinModule("node:url").pathToFileURL(base);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
url = new URL(base, location.href);
|
|
58
|
+
}
|
|
59
|
+
if (!url.pathname.endsWith("/")) {
|
|
60
|
+
url.pathname += "/";
|
|
61
|
+
}
|
|
62
|
+
if (nodeFs() && url.protocol !== "file:") {
|
|
63
|
+
throw new TypeError("In Node, assetBase must be a directory path or a file: URL: the engine reads fonts synchronously.");
|
|
64
|
+
}
|
|
65
|
+
return url;
|
|
66
|
+
}
|
|
67
|
+
function assetUrl(name, base) {
|
|
68
|
+
return base ? new URL(name, base) : packagedUrl(name);
|
|
69
|
+
}
|
|
70
|
+
function loadFailure(name, url, reason, cause) {
|
|
71
|
+
return new PdfError("missing-asset", `recto could not load ${name}${url ? ` from ${url.href}` : ""}: ${reason}. It ships in the package's wasm/ ` +
|
|
72
|
+
"directory. If a bundler or dev server moved the package without its files, copy them " +
|
|
73
|
+
"somewhere it serves and pass that directory as `assetBase` (with Vite's dev server, add " +
|
|
74
|
+
'optimizeDeps: { exclude: ["@2018kguo/recto"] } instead).', cause);
|
|
75
|
+
}
|
|
76
|
+
function checked(name, url, bytes) {
|
|
77
|
+
if (sha256Hex(bytes) !== PINS[name]) {
|
|
78
|
+
throw loadFailure(name, url, `the ${bytes.byteLength} bytes there are not this build's file (a server's fallback page, or a different version of the package)`);
|
|
79
|
+
}
|
|
80
|
+
return bytes;
|
|
81
|
+
}
|
|
82
|
+
function readFromDisk(url) {
|
|
83
|
+
return nodeFs().readFileSync(url);
|
|
84
|
+
}
|
|
85
|
+
function readSync(url) {
|
|
86
|
+
const xhr = new XMLHttpRequest();
|
|
87
|
+
xhr.open("GET", url.href, false);
|
|
88
|
+
xhr.responseType = "arraybuffer";
|
|
89
|
+
xhr.timeout = ASSET_TIMEOUT_MS;
|
|
90
|
+
xhr.send();
|
|
91
|
+
if (xhr.status !== 200) {
|
|
92
|
+
throw new Error(`HTTP ${xhr.status}`);
|
|
93
|
+
}
|
|
94
|
+
return new Uint8Array(xhr.response);
|
|
95
|
+
}
|
|
96
|
+
/** The wasm's bytes, for compiling. Asynchronous where it can be. */
|
|
97
|
+
export async function loadWasm(base) {
|
|
98
|
+
let url = null;
|
|
99
|
+
let bytes;
|
|
100
|
+
try {
|
|
101
|
+
// Inside the try: under a blob: worker `import.meta.url` is no base for a URL.
|
|
102
|
+
url = assetUrl("recto.wasm", base);
|
|
103
|
+
if (nodeFs()) {
|
|
104
|
+
bytes = readFromDisk(url);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const response = await fetch(url);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`HTTP ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw loadFailure("recto.wasm", url, error instanceof Error ? error.message : String(error), error);
|
|
116
|
+
}
|
|
117
|
+
return checked("recto.wasm", url, bytes);
|
|
118
|
+
}
|
|
119
|
+
/** Verified assets by URL: hash and read each once per thread. */
|
|
120
|
+
const loaded = new Map();
|
|
121
|
+
/** Fetches assets ahead of time, for a thread that can't read them when they're needed. */
|
|
122
|
+
export async function preloadAssets(base, names) {
|
|
123
|
+
await Promise.all(names.map(async (name) => {
|
|
124
|
+
let url = null;
|
|
125
|
+
let bytes;
|
|
126
|
+
try {
|
|
127
|
+
url = assetUrl(name, base);
|
|
128
|
+
if (loaded.has(url.href)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const response = await fetch(url);
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`HTTP ${response.status}`);
|
|
134
|
+
}
|
|
135
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw loadFailure(name, url, error instanceof Error ? error.message : String(error), error);
|
|
139
|
+
}
|
|
140
|
+
loaded.set(url.href, checked(name, url, bytes));
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
/** An asset, read synchronously. Throws a PdfError rather than returning nothing. */
|
|
144
|
+
export function loadAssetSync(name, base) {
|
|
145
|
+
let url = null;
|
|
146
|
+
let bytes;
|
|
147
|
+
try {
|
|
148
|
+
url = assetUrl(name, base);
|
|
149
|
+
const cached = loaded.get(url.href);
|
|
150
|
+
if (cached) {
|
|
151
|
+
return cached;
|
|
152
|
+
}
|
|
153
|
+
if (!canReadSync()) {
|
|
154
|
+
throw new Error("this thread can only use assets fetched up front, and it was left out of preloadAssets");
|
|
155
|
+
}
|
|
156
|
+
bytes = nodeFs() ? readFromDisk(url) : readSync(url);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw loadFailure(name, url, error instanceof Error ? error.message : String(error), error);
|
|
160
|
+
}
|
|
161
|
+
loaded.set(url.href, checked(name, url, bytes));
|
|
162
|
+
return bytes;
|
|
163
|
+
}
|
|
164
|
+
/** Entry `index` of fonts.bin: u32 count, u32 offsets[count + 1], bytes (little-endian). */
|
|
165
|
+
export function fontFromBundle(bundle, index) {
|
|
166
|
+
const view = new DataView(bundle.buffer, bundle.byteOffset, bundle.byteLength);
|
|
167
|
+
const base = 4 + (view.getUint32(0, true) + 1) * 4;
|
|
168
|
+
return bundle.subarray(base + view.getUint32(4 + index * 4, true), base + view.getUint32(8 + index * 4, true));
|
|
169
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { createGlue } from "../wasm/recto.gen.mjs";
|
|
2
|
+
import type * as Glue from "../wasm/recto.types.mjs";
|
|
3
|
+
import { PdfError } from "./errors.ts";
|
|
4
|
+
export type PdfBox = {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
};
|
|
10
|
+
export type PdfPageInfo = {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
rotation: 0 | 90 | 180 | 270;
|
|
14
|
+
};
|
|
15
|
+
export type PdfFont = {
|
|
16
|
+
name: string;
|
|
17
|
+
/** PDF font descriptor flags (fixed pitch, serif, symbolic, italic, ...). */
|
|
18
|
+
flags: number;
|
|
19
|
+
/** 100 to 900 as the file states it, or as its name says; 0 where it says nothing. */
|
|
20
|
+
weight: number;
|
|
21
|
+
embedded: boolean;
|
|
22
|
+
/** Top of this font's char boxes, in ems above the baseline: the descriptor's, or 0.8. */
|
|
23
|
+
ascent: number;
|
|
24
|
+
/** Bottom of this font's char boxes, in ems (negative): the descriptor's, or -0.2. */
|
|
25
|
+
descent: number;
|
|
26
|
+
};
|
|
27
|
+
/** `PdfPageText.flags` bit: a space or line break the engine inferred; not in the content stream. */
|
|
28
|
+
export declare const CHAR_GENERATED = 1;
|
|
29
|
+
/** `PdfPageText.flags` bit: the glyph has no Unicode mapping, so its text is unreadable. */
|
|
30
|
+
export declare const CHAR_UNMAPPED = 2;
|
|
31
|
+
/**
|
|
32
|
+
* A page's characters as columns (structure of arrays): char `i` is entry
|
|
33
|
+
* `i` of every column, and entries `4i..4i+3` of `boxes`. Typed arrays, so
|
|
34
|
+
* a page crosses a thread boundary by transfer, not by copying objects.
|
|
35
|
+
*/
|
|
36
|
+
export type PdfPageText = {
|
|
37
|
+
/** The whole page's text, generated spaces included. */
|
|
38
|
+
text: string;
|
|
39
|
+
count: number;
|
|
40
|
+
/** Unicode code points (U+FFFD where a glyph's code is no scalar value). */
|
|
41
|
+
codepoints: Uint32Array<ArrayBuffer>;
|
|
42
|
+
/** x, y, width, height per char: the advance by the font's ascent and descent, not glyph ink. */
|
|
43
|
+
boxes: Float32Array<ArrayBuffer>;
|
|
44
|
+
/** The size each char is drawn at, in points: `Tf` scaled by the text and page matrices. */
|
|
45
|
+
fontSizes: Float32Array<ArrayBuffer>;
|
|
46
|
+
/** The `Tf` operand each char was shown with, unscaled. */
|
|
47
|
+
tfSizes: Float32Array<ArrayBuffer>;
|
|
48
|
+
/** Radians, counter-clockwise. */
|
|
49
|
+
angles: Float32Array<ArrayBuffer>;
|
|
50
|
+
/** Fill colour as 0xRRGGBBAA. */
|
|
51
|
+
fills: Uint32Array<ArrayBuffer>;
|
|
52
|
+
/** Index into `fonts`; -1 for characters the engine generated. */
|
|
53
|
+
fontIds: Int32Array<ArrayBuffer>;
|
|
54
|
+
/** CHAR_GENERATED | CHAR_UNMAPPED. */
|
|
55
|
+
flags: Uint8Array<ArrayBuffer>;
|
|
56
|
+
/** One per font dictionary used on the page. */
|
|
57
|
+
fonts: PdfFont[];
|
|
58
|
+
/** The page showed more glyphs than one page may collect (2^19); the rest aren't here. */
|
|
59
|
+
truncated: boolean;
|
|
60
|
+
/** Runs of non-space chars in content order, as char ranges and boxes. */
|
|
61
|
+
words: PdfWords;
|
|
62
|
+
/**
|
|
63
|
+
* Index into `runs` per char: the text object (one show-text operator)
|
|
64
|
+
* the char was drawn by; -1 for chars the engine generated.
|
|
65
|
+
*/
|
|
66
|
+
runIds: Int32Array<ArrayBuffer>;
|
|
67
|
+
runs: PdfTextRuns;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* One entry per text object, in the order its chars first appear: where
|
|
71
|
+
* its text starts and how large it is drawn. Every char of a run sits on
|
|
72
|
+
* the run's baseline (a rise or a new line starts a new text object).
|
|
73
|
+
*/
|
|
74
|
+
export type PdfTextRuns = {
|
|
75
|
+
count: number;
|
|
76
|
+
/** x, y per run: the text origin on the baseline, in the viewer frame. */
|
|
77
|
+
origins: Float32Array<ArrayBuffer>;
|
|
78
|
+
/** The font size as drawn, in points (Tf scaled by the text and current matrices). */
|
|
79
|
+
ems: Float32Array<ArrayBuffer>;
|
|
80
|
+
};
|
|
81
|
+
export type PdfWords = {
|
|
82
|
+
count: number;
|
|
83
|
+
/** First and last char index of each word, inclusive. */
|
|
84
|
+
firstChar: Uint32Array<ArrayBuffer>;
|
|
85
|
+
lastChar: Uint32Array<ArrayBuffer>;
|
|
86
|
+
/** x, y, width, height per word: the union of its char boxes. */
|
|
87
|
+
boxes: Float32Array<ArrayBuffer>;
|
|
88
|
+
};
|
|
89
|
+
/** Entry `i` of a `boxes` column. */
|
|
90
|
+
export declare function boxAt(boxes: Float32Array, i: number): PdfBox;
|
|
91
|
+
/** The text of chars `first..last` (inclusive). */
|
|
92
|
+
export declare function charsText(page: Pick<PdfPageText, "codepoints">, first: number, last: number): string;
|
|
93
|
+
/** Segment kinds, in the low bits of `PdfPathSegments.kinds`. */
|
|
94
|
+
export declare const SEGMENT_LINE = 0;
|
|
95
|
+
export declare const SEGMENT_BEZIER = 1;
|
|
96
|
+
export declare const SEGMENT_MOVE = 2;
|
|
97
|
+
/** `PdfPathSegments.kinds` bit: the segment closes its subpath. */
|
|
98
|
+
export declare const SEGMENT_CLOSE = 4;
|
|
99
|
+
/**
|
|
100
|
+
* `PdfPathSegments.kinds` bit on a move: its subpath is a rectangle drawn by
|
|
101
|
+
* `re` (pdf.js counts only those as rectangles).
|
|
102
|
+
*/
|
|
103
|
+
export declare const SEGMENT_RECT = 8;
|
|
104
|
+
/** `paths.flags` bits. */
|
|
105
|
+
export declare const PATH_FILL = 1;
|
|
106
|
+
export declare const PATH_STROKE = 2;
|
|
107
|
+
/** All paths' segments end to end; path `i` owns `start[i]..start[i+1]`. */
|
|
108
|
+
export type PdfPathSegments = {
|
|
109
|
+
count: number;
|
|
110
|
+
/** x, y per segment end point, in the viewer frame. */
|
|
111
|
+
points: Float32Array<ArrayBuffer>;
|
|
112
|
+
/** SEGMENT_LINE | SEGMENT_BEZIER | SEGMENT_MOVE, plus SEGMENT_CLOSE and SEGMENT_RECT. */
|
|
113
|
+
kinds: Uint8Array<ArrayBuffer>;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* A page's images and paths as columns. Form XObjects are flattened, their
|
|
117
|
+
* transforms applied; every box and point is in the viewer frame.
|
|
118
|
+
*/
|
|
119
|
+
export type PdfPageObjects = {
|
|
120
|
+
textObjects: number;
|
|
121
|
+
/** The page held more than one page may collect (2^21 path segments, 2^19 images); the rest aren't here. */
|
|
122
|
+
truncated: boolean;
|
|
123
|
+
images: {
|
|
124
|
+
count: number;
|
|
125
|
+
/** x, y, width, height per image. */
|
|
126
|
+
boxes: Float32Array<ArrayBuffer>;
|
|
127
|
+
/** Pixel width, height per image (0, 0 when the engine cannot say). */
|
|
128
|
+
pixels: Uint32Array<ArrayBuffer>;
|
|
129
|
+
};
|
|
130
|
+
paths: {
|
|
131
|
+
count: number;
|
|
132
|
+
/** x, y, width, height per path. */
|
|
133
|
+
boxes: Float32Array<ArrayBuffer>;
|
|
134
|
+
/** PATH_FILL | PATH_STROKE. */
|
|
135
|
+
flags: Uint8Array<ArrayBuffer>;
|
|
136
|
+
/** count + 1 offsets into `segments`. */
|
|
137
|
+
start: Uint32Array<ArrayBuffer>;
|
|
138
|
+
};
|
|
139
|
+
segments: PdfPathSegments;
|
|
140
|
+
};
|
|
141
|
+
export type PdfAnnotation = {
|
|
142
|
+
subtype: number;
|
|
143
|
+
box: PdfBox;
|
|
144
|
+
/** /Contents (a FreeText's text; a note for most other kinds); "" when absent. */
|
|
145
|
+
contents: string;
|
|
146
|
+
/**
|
|
147
|
+
* A widget's form field: its type (/FT: "Tx", "Ch", "Btn", "Sig") and
|
|
148
|
+
* value (/V, both inherited through /Parent); null for other annotations.
|
|
149
|
+
* A multi-select choice field's /V array reads as "".
|
|
150
|
+
*/
|
|
151
|
+
field: {
|
|
152
|
+
type: string;
|
|
153
|
+
value: string;
|
|
154
|
+
} | null;
|
|
155
|
+
/**
|
|
156
|
+
* Objects in the annotation's normal appearance, and how many of them are
|
|
157
|
+
* text: a stamp that paints words has text objects. 0 without an
|
|
158
|
+
* appearance.
|
|
159
|
+
*/
|
|
160
|
+
appearanceObjects: number;
|
|
161
|
+
appearanceTextObjects: number;
|
|
162
|
+
/**
|
|
163
|
+
* The text the appearance's text objects paint, one object a line (a
|
|
164
|
+
* stamp's words, a list box's options), each line trimmed.
|
|
165
|
+
*/
|
|
166
|
+
appearanceText: string;
|
|
167
|
+
};
|
|
168
|
+
export type PdfRenderOptions = {
|
|
169
|
+
/** Target width in pixels; the scale also keeps height within `maxHeight`. */
|
|
170
|
+
width: number;
|
|
171
|
+
/** Default 4096. May be Infinity, to fit the width alone. A render is capped at 16,384 px a side and 64 Mpx. */
|
|
172
|
+
maxHeight?: number;
|
|
173
|
+
};
|
|
174
|
+
export type PdfBitmap = {
|
|
175
|
+
width: number;
|
|
176
|
+
height: number;
|
|
177
|
+
/** Opaque RGBA on a white background, four bytes a pixel. */
|
|
178
|
+
rgba: Uint8Array<ArrayBuffer>;
|
|
179
|
+
/**
|
|
180
|
+
* The page passed a limit on what one render may do (more than 32 groups that need the page
|
|
181
|
+
* redrawn under them), so past that those groups are drawn as if isolated.
|
|
182
|
+
*/
|
|
183
|
+
truncated: boolean;
|
|
184
|
+
};
|
|
185
|
+
export declare const ANNOT_SUBTYPE: {
|
|
186
|
+
readonly text: 1;
|
|
187
|
+
readonly link: 2;
|
|
188
|
+
readonly freeText: 3;
|
|
189
|
+
readonly stamp: 13;
|
|
190
|
+
readonly widget: 20;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* An open PDF. Every call is synchronous CPU work on this thread. Closing is idempotent, and
|
|
194
|
+
* every other method throws a PdfError with code "closed" afterwards.
|
|
195
|
+
*/
|
|
196
|
+
/** A bookmark. `page` is -1 if it goes nowhere in this file; `url` is set if it links out. */
|
|
197
|
+
export type PdfOutlineItem = {
|
|
198
|
+
title: string;
|
|
199
|
+
page: number;
|
|
200
|
+
url: string | null;
|
|
201
|
+
children: PdfOutlineItem[];
|
|
202
|
+
};
|
|
203
|
+
export interface PdfDocument {
|
|
204
|
+
readonly pageCount: number;
|
|
205
|
+
/** The document's bookmarks; empty if it has none. */
|
|
206
|
+
outline(): PdfOutlineItem[];
|
|
207
|
+
pageInfo(index: number): PdfPageInfo;
|
|
208
|
+
text(index: number): PdfPageText;
|
|
209
|
+
/** `text`, `objects` and `annotations` from one interpretation of the page. */
|
|
210
|
+
scan(index: number): {
|
|
211
|
+
text: PdfPageText;
|
|
212
|
+
objects: PdfPageObjects;
|
|
213
|
+
annotations: PdfAnnotation[];
|
|
214
|
+
};
|
|
215
|
+
/** `text` and `annotations` from one interpretation of the page. */
|
|
216
|
+
textAndAnnotations(index: number): {
|
|
217
|
+
text: PdfPageText;
|
|
218
|
+
annotations: PdfAnnotation[];
|
|
219
|
+
};
|
|
220
|
+
objects(index: number): PdfPageObjects;
|
|
221
|
+
annotations(index: number): PdfAnnotation[];
|
|
222
|
+
render(index: number, options: PdfRenderOptions): PdfBitmap;
|
|
223
|
+
close(): void;
|
|
224
|
+
[Symbol.dispose](): void;
|
|
225
|
+
}
|
|
226
|
+
export type Instance = {
|
|
227
|
+
glue: ReturnType<typeof createGlue>;
|
|
228
|
+
memory: WebAssembly.Memory;
|
|
229
|
+
generation: number;
|
|
230
|
+
openDocuments: number;
|
|
231
|
+
/** Set once the instance trapped or an asset failed; nothing may call it again. */
|
|
232
|
+
dead: boolean;
|
|
233
|
+
/** An asset's load failure, rethrown by the call that needed the asset. */
|
|
234
|
+
failure: PdfError | undefined;
|
|
235
|
+
/** Called when a document on this instance closes. */
|
|
236
|
+
onClose?: () => void;
|
|
237
|
+
};
|
|
238
|
+
/** What a call into the instance threw, as what the caller should see. */
|
|
239
|
+
export declare function engineFailure(instance: Instance, error: unknown): unknown;
|
|
240
|
+
export declare class RectoDocument implements PdfDocument {
|
|
241
|
+
#private;
|
|
242
|
+
readonly pageCount: number;
|
|
243
|
+
constructor(instance: Instance, doc: Glue.Document, fetchFailure?: () => PdfError | null);
|
|
244
|
+
pageInfo(index: number): PdfPageInfo;
|
|
245
|
+
text(index: number): PdfPageText;
|
|
246
|
+
objects(index: number): PdfPageObjects;
|
|
247
|
+
outline(): PdfOutlineItem[];
|
|
248
|
+
annotations(index: number): PdfAnnotation[];
|
|
249
|
+
scan(index: number): {
|
|
250
|
+
text: PdfPageText;
|
|
251
|
+
objects: PdfPageObjects;
|
|
252
|
+
annotations: PdfAnnotation[];
|
|
253
|
+
};
|
|
254
|
+
textAndAnnotations(index: number): {
|
|
255
|
+
text: PdfPageText;
|
|
256
|
+
annotations: PdfAnnotation[];
|
|
257
|
+
};
|
|
258
|
+
render(index: number, options: PdfRenderOptions): PdfBitmap;
|
|
259
|
+
close(): void;
|
|
260
|
+
[Symbol.dispose](): void;
|
|
261
|
+
}
|