@build-qube/papyra 0.0.4 → 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 (55) hide show
  1. package/README.md +129 -0
  2. package/dist/cache.d.ts +3 -0
  3. package/dist/cache.d.ts.map +1 -1
  4. package/dist/cache.js.map +1 -1
  5. package/dist/document.d.ts +251 -3
  6. package/dist/document.d.ts.map +1 -1
  7. package/dist/document.js +444 -16
  8. package/dist/document.js.map +1 -1
  9. package/dist/encode.d.ts +49 -0
  10. package/dist/encode.d.ts.map +1 -0
  11. package/dist/encode.js +71 -0
  12. package/dist/encode.js.map +1 -0
  13. package/dist/encoded.d.ts +91 -0
  14. package/dist/encoded.d.ts.map +1 -0
  15. package/dist/encoded.js +84 -0
  16. package/dist/encoded.js.map +1 -0
  17. package/dist/errors.d.ts +45 -0
  18. package/dist/errors.d.ts.map +1 -0
  19. package/dist/errors.js +69 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/fingerprint.d.ts +18 -0
  22. package/dist/fingerprint.d.ts.map +1 -0
  23. package/dist/fingerprint.js +48 -0
  24. package/dist/fingerprint.js.map +1 -0
  25. package/dist/index.d.ts +12 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/links.d.ts +53 -0
  30. package/dist/links.d.ts.map +1 -0
  31. package/dist/links.js +26 -0
  32. package/dist/links.js.map +1 -0
  33. package/dist/outline.d.ts +72 -0
  34. package/dist/outline.d.ts.map +1 -0
  35. package/dist/outline.js +73 -0
  36. package/dist/outline.js.map +1 -0
  37. package/dist/runtime.d.ts +8 -0
  38. package/dist/runtime.d.ts.map +1 -1
  39. package/dist/runtime.js +2 -0
  40. package/dist/runtime.js.map +1 -1
  41. package/dist/scheduler.d.ts +20 -2
  42. package/dist/scheduler.d.ts.map +1 -1
  43. package/dist/scheduler.js +15 -0
  44. package/dist/scheduler.js.map +1 -1
  45. package/dist/search.d.ts +50 -0
  46. package/dist/search.d.ts.map +1 -0
  47. package/dist/search.js +151 -0
  48. package/dist/search.js.map +1 -0
  49. package/dist/text.d.ts +113 -0
  50. package/dist/text.d.ts.map +1 -0
  51. package/dist/text.js +84 -0
  52. package/dist/text.js.map +1 -0
  53. package/dist/types.d.ts +96 -0
  54. package/dist/types.d.ts.map +1 -1
  55. package/package.json +4 -3
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # papyra
2
+
3
+ Fast PDF rendering for Node **and** the browser, from one Rust core. The engine
4
+ ([hayro](https://github.com/LaurenzV/hayro)) is compiled in — there are no pdfium
5
+ binaries to ship and nothing to install alongside it.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @build-qube/papyra
11
+ ```
12
+
13
+ The native addon and the browser wasm build ship as optional dependencies; your
14
+ package manager picks the right one.
15
+
16
+ ## Render a page
17
+
18
+ ```ts
19
+ import { open, paintToCanvas } from '@build-qube/papyra';
20
+
21
+ const doc = await open(file); // Uint8Array | ArrayBuffer | Blob | File
22
+ const page = await doc.renderPage(0, { fitWidth: 1600 });
23
+
24
+ paintToCanvas(page, canvas);
25
+ ```
26
+
27
+ `renderPage` never blocks the event loop. `page` is tightly packed RGBA8, so
28
+ `paintToCanvas` is a convenience rather than the only route — `toImageData`, the
29
+ encoders, or your own buffer handling all work from the same bytes.
30
+
31
+ ## Thumbnails
32
+
33
+ `stream` yields pages as they finish, with concurrency bounded for you:
34
+
35
+ ```ts
36
+ for await (const { page, bitmap } of doc.stream({ fitWidth: 160 })) {
37
+ paint(page, bitmap);
38
+ }
39
+ ```
40
+
41
+ ## Say what is urgent
42
+
43
+ papyra does not guess which page matters — a viewer knows what is on screen and the
44
+ library does not. Attach a priority and the queue honours it. **Lower runs first**,
45
+ and the default is the most urgent tier, so callers who do not care can ignore this
46
+ entirely.
47
+
48
+ ```ts
49
+ const doc = await open(file, { concurrency: 4 }); // viewers want a narrow pool
50
+
51
+ // Thumbnails yield to whatever is on screen.
52
+ for await (const t of doc.stream({ fitWidth: 160, priority: 2 })) { /* … */ }
53
+
54
+ // Reprioritise on scroll instead of cancelling and resubmitting.
55
+ const job = doc.render(12, { fitWidth: 1600, priority: 2 });
56
+ onScroll(() => job.setPriority(isVisible(12) ? 0 : 3));
57
+ ```
58
+
59
+ Requests for the same page at the same size coalesce into one render. Pending work
60
+ reorders freely; work already running is never interrupted.
61
+
62
+ ## Export, text, and outlines
63
+
64
+ ```ts
65
+ const image = await doc.renderImage(0, { fitWidth: 2000 });
66
+ await writeFile('page-0.webp', (await image.toWebp()).bytes);
67
+
68
+ for await (const hit of doc.search('site plan')) {
69
+ highlight(hit.page, hit.rects);
70
+ }
71
+
72
+ for (const node of await doc.outline()) {
73
+ if (node.page !== null) goTo(node.page);
74
+ }
75
+ ```
76
+
77
+ `renderImage` keeps the pixels in Rust and encodes on demand, so nothing but the
78
+ finished file crosses into JS. For output that has to survive being scaled there is
79
+ `doc.renderSvg(0)`, which returns vector markup instead of pixels and so takes no
80
+ size and no quality.
81
+
82
+ ## Links, labels, and what the file says about itself
83
+
84
+ ```ts
85
+ const scale = rendered.width / doc.pageSize(index).width;
86
+ for (const link of await doc.links(index)) {
87
+ const box = scaleRect(link.rect, scale);
88
+ if (link.target.kind === 'uri') openExternally(link.target.uri);
89
+ else goTo(link.target.dest.page);
90
+ }
91
+
92
+ const labels = await doc.pageLabels();
93
+ const shown = labels[index] || String(index + 1); // "iv", not "4"
94
+
95
+ doc.metadata.title; // null when the document never said
96
+ doc.fingerprint; // stable key for per-document state
97
+ ```
98
+
99
+ Link rectangles are in the same space as extracted text — 72 DPI from the top-left,
100
+ rotation applied — so one multiply places them over any render. `pageLabels()` is empty
101
+ when the document defines no labels, which is your signal to number by index.
102
+
103
+ Encrypted files throw `PasswordRequiredError` or `IncorrectPasswordError`, both
104
+ extending `PasswordError` with a `retry` flag, so one dialog handles the cold ask and
105
+ the wrong answer.
106
+
107
+ ## Two things that will bite you
108
+
109
+ **Size with `fitWidth`, not `dpi`.** Page areas vary by two orders of magnitude. At a
110
+ fixed 36 DPI, US Letter is a 216x279 thumbnail while a 42x30in drawing is a 1512x1080,
111
+ 6.5 MB image. Sizing by output width keeps cost proportional to what you actually
112
+ display; renders above 100 MP are refused outright.
113
+
114
+ **The browser build needs cross-origin isolation.** It uses shared wasm memory, so
115
+ `SharedArrayBuffer` must be available. Serve your page with:
116
+
117
+ ```
118
+ Cross-Origin-Opener-Policy: same-origin
119
+ Cross-Origin-Embedder-Policy: require-corp
120
+ ```
121
+
122
+ ## More
123
+
124
+ The [full API reference](https://buildqube.github.io/Papyra/docs) is generated from
125
+ these sources, so it never drifts from them. The
126
+ [project README](https://github.com/BuildQube/Papyra) covers architecture, benchmarks
127
+ against pdf.js, and the measurements behind these defaults.
128
+
129
+ MIT.
package/dist/cache.d.ts CHANGED
@@ -15,8 +15,11 @@
15
15
  export interface CacheStats {
16
16
  /** Bytes currently held. */
17
17
  bytes: number;
18
+ /** Pages currently held. */
18
19
  entries: number;
20
+ /** Lookups served without rendering. */
19
21
  hits: number;
22
+ /** Lookups that had to render. */
20
23
  misses: number;
21
24
  /** Entries dropped to stay under budget. */
22
25
  evictions: number;
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,WAAW,CAAC,CAAC;;IAUxB,YAAY,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,EAG5D;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAW9B;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAc/B;IAcD,KAAK,IAAI,IAAI,CAGZ;IAED,IAAI,KAAK,IAAI,UAAU,CAQtB;CACF"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,WAAW,CAAC,CAAC;;IAUxB,YAAY,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,EAG5D;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAW9B;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAc/B;IAcD,KAAK,IAAI,IAAI,CAGZ;IAED,IAAI,KAAK,IAAI,UAAU,CAQtB;CACF"}
package/dist/cache.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -1,32 +1,226 @@
1
1
  import { PdfDocument as NativeDocument } from '@build-qube/papyra-native';
2
2
  import { type CacheStats } from './cache.js';
3
+ import { PageImage, type SvgPage } from './encode.js';
4
+ import { type PageLink } from './links.js';
5
+ import { type OutlineNode } from './outline.js';
3
6
  import { type JobHandle } from './scheduler.js';
4
- import type { OpenOptions, PageSize, PdfSource, RenderedPage, RenderOptions, StreamedPage, StreamOptions } from './types.js';
7
+ import { type SearchMatch } from './search.js';
8
+ import { type PageText } from './text.js';
9
+ import type { DocumentMetadata, OpenOptions, PageSize, PdfSource, RenderedPage, RenderOptions, SearchOptions, StreamedPage, StreamOptions, SvgOptions } from './types.js';
5
10
  /** A queued render, plus whether it was served from cache without rendering at all. */
6
11
  export interface RenderHandle extends JobHandle<RenderedPage> {
12
+ /**
13
+ * The page was already in the cache and nothing was queued.
14
+ *
15
+ * `promise` still resolves normally, so this only matters for instrumentation —
16
+ * a cache hit costs none of the ~93ms floor a real render does.
17
+ */
7
18
  readonly cached: boolean;
8
19
  }
9
- /** Open a PDF from bytes, an `ArrayBuffer`, a `Blob`, or a `File`. */
20
+ /**
21
+ * A queued export render. No `cached` flag: unlike {@link RenderHandle} the image path
22
+ * never comes from the cache, which is keyed by page and size with no format dimension.
23
+ */
24
+ export type ImageHandle = JobHandle<PageImage>;
25
+ /**
26
+ * A queued SVG conversion. Uncached for the same reason as {@link ImageHandle}, and
27
+ * unsized: an SVG has no DPI to key on.
28
+ */
29
+ export type SvgHandle = JobHandle<SvgPage>;
30
+ /**
31
+ * Open a PDF from bytes, an `ArrayBuffer`, a `Blob`, or a `File`.
32
+ *
33
+ * Throws {@link PasswordRequiredError} when the document is encrypted and no
34
+ * {@link OpenOptions.password} was given, and {@link IncorrectPasswordError} when one
35
+ * was given and rejected. Both extend {@link PasswordError}, so a viewer that shows
36
+ * one dialog for both can catch the base.
37
+ */
10
38
  export declare function open(source: PdfSource, options?: OpenOptions): Promise<Document>;
39
+ /**
40
+ * An open PDF, and the queue and caches that serve it.
41
+ *
42
+ * Every page-level call goes through one shared scheduler, so a thumbnail sweep can
43
+ * never starve the page on screen — attach a {@link RenderOptions.priority} and the
44
+ * queue reorders around it. Construct via {@link open}, not `new`.
45
+ */
11
46
  export declare class Document {
12
47
  #private;
48
+ /**
49
+ * A stable identifier for this document's bytes.
50
+ *
51
+ * Sixteen hex characters, for keying whatever you remember per document — the page
52
+ * it was left on, a zoom level, a set of highlights. Two `open` calls on the same
53
+ * file always agree.
54
+ *
55
+ * It is a hash of the file, **not** the PDF's own `/ID`: the engine exposes no
56
+ * trailer accessor, so that identifier is out of reach. The practical difference is
57
+ * that saving an incremental update changes this while `/ID` would have survived.
58
+ */
59
+ readonly fingerprint: string;
13
60
  /** @internal — construct via {@link open}. */
14
- constructor(inner: NativeDocument, options?: OpenOptions);
61
+ constructor(inner: NativeDocument, fingerprintOfBytes: string, options?: OpenOptions);
15
62
  /** Rendered pages held for reuse, and how often that has paid off. */
16
63
  get cache(): CacheStats;
17
64
  /** Drop every cached page. */
18
65
  clearCache(): void;
19
66
  /** Pages queued but not yet started, pages rendering, and the longest wait so far. */
20
67
  get queued(): {
68
+ /** Submitted but not started. These are the ones priority can still reorder. */
21
69
  pending: number;
70
+ /** Rendering now. Never preempted, so this is work you are committed to. */
22
71
  running: number;
72
+ /**
73
+ * How long the longest-waiting pending job has sat there.
74
+ *
75
+ * The number to watch in a viewer: if it climbs, the pool is too wide for the
76
+ * work or a priority is wrong.
77
+ */
23
78
  oldestWaitMs: number;
24
79
  };
80
+ /** How many pages the document has. Page indices are 0-based throughout. */
25
81
  get pageCount(): number;
82
+ /**
83
+ * The PDF specification version the file declares, e.g. `'1.7'`.
84
+ *
85
+ * Deliberately not part of {@link Document.metadata}: that is what the producer
86
+ * chose to say about the document, while this is a structural property read from
87
+ * the file's header — and from the catalog, which overrides it for 1.4 and later,
88
+ * so an incrementally saved file reports the version it was last written as.
89
+ *
90
+ * `'1.0'` is worth treating with suspicion. Genuine PDF 1.0 files are close to
91
+ * extinct, and it is also what a file with an unreadable header reports.
92
+ */
93
+ get pdfVersion(): string | null;
94
+ /**
95
+ * The document outline — bookmarks, the table of contents — as a tree.
96
+ *
97
+ * Resolves to an empty array when the document has no outline, which is the common
98
+ * case. The walk runs off the event loop and the result is memoised, so calling
99
+ * this on every render of a sidebar costs nothing after the first.
100
+ *
101
+ * Entries that point outside the document (a URL, another file) and containers that
102
+ * group children without a destination of their own both have a `dest` of `null`;
103
+ * they are kept because a viewer still lists them.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * for (const node of await doc.outline()) {
108
+ * if (node.page !== null) void doc.render(node.page, { fitWidth: 1600 });
109
+ * }
110
+ * ```
111
+ */
112
+ outline(): Promise<OutlineNode[]>;
113
+ /**
114
+ * What the document says about itself: title, author, dates, the tool that wrote it.
115
+ *
116
+ * Synchronous — the engine reads the information dictionary while loading, so this
117
+ * only decodes strings. Every field is independently `null`, and none of it is
118
+ * verified; `title` in particular is often a filename or missing entirely.
119
+ */
120
+ get metadata(): DocumentMetadata;
121
+ /**
122
+ * The label printed on each page, one entry per page.
123
+ *
124
+ * Resolves to an **empty array** when the document defines no page labels, which is
125
+ * the common case — that is the signal to number pages by index yourself. A
126
+ * document that defines labels but says nothing about a particular page gives that
127
+ * page an empty string, which is a different answer.
128
+ *
129
+ * Worth honouring wherever a page number is shown: a document with front matter
130
+ * numbers its first pages i, ii, iii, and a viewer that says "page 4 of 300" when
131
+ * the sheet reads "A101" is describing a document nobody else can see.
132
+ *
133
+ * The walk runs off the event loop and the result is memoised.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const labels = await doc.pageLabels();
138
+ * const shown = labels[index] || String(index + 1);
139
+ * ```
140
+ */
141
+ pageLabels(): Promise<readonly string[]>;
142
+ /**
143
+ * The links on one page — where they are, and where they go.
144
+ *
145
+ * Rectangles are in the same 72-DPI top-left space as {@link Document.pageText} and
146
+ * {@link Document.pageSize}, so placing them over a render is one multiply; see
147
+ * {@link scaleRect}. Only links that resolve to something actionable are reported.
148
+ *
149
+ * Unlike text extraction this never interprets a content stream — it is an
150
+ * object-graph read, roughly a thousandth the cost of rendering the page — so it
151
+ * does **not** go through the priority queue. Making a link layer wait behind
152
+ * renders would add latency a viewer can feel, for work that costs nothing.
153
+ * Results are cached, and two calls for the same page share one read.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * const scale = rendered.width / doc.pageSize(index).width;
158
+ * for (const link of await doc.links(index)) {
159
+ * const { x, y, width, height } = scaleRect(link.rect, scale);
160
+ * // ...position an anchor over the canvas
161
+ * }
162
+ * ```
163
+ */
164
+ links(index: number): Promise<readonly PageLink[]>;
26
165
  /** Page dimensions in PDF points (1/72 inch). */
27
166
  pageSize(index: number): PageSize;
28
167
  /** Render a single page. Never blocks the event loop. */
29
168
  renderPage(index: number, options?: RenderOptions): Promise<RenderedPage>;
169
+ /**
170
+ * Render a page for export, keeping the pixels in Rust.
171
+ *
172
+ * The returned {@link PageImage} encodes on demand — `toWebp()`, `toPng()`,
173
+ * `toJpeg()`, `toDataUrl()` — and never hands the raw bitmap to JS. A 42x30in
174
+ * drawing at 150 DPI is 6.5 MB raw and a fraction of that encoded, so for anything
175
+ * leaving the process that buffer is pure overhead.
176
+ *
177
+ * Use `renderPage` when you want to paint to a canvas, and `encode()` when you want
178
+ * both the pixels and a file.
179
+ *
180
+ * Shares the scheduler with every other render, so priority and concurrency behave
181
+ * exactly as they do for `render`. **Not cached** — the cache is keyed by page and
182
+ * size with no format dimension, and it measures raw bytes.
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * const img = await doc.renderImage(0, { fitWidth: 2000 });
187
+ * await writeFile('page-0.webp', (await img.toWebp()).bytes);
188
+ * ```
189
+ */
190
+ renderImage(index: number, options?: RenderOptions): Promise<PageImage>;
191
+ /**
192
+ * As {@link renderImage}, but returns the handle so the job can be reprioritised,
193
+ * cancelled, or timed — the same relationship `render` has to `renderPage`.
194
+ *
195
+ * `timing` is where the export path becomes measurable: it separates queue wait from
196
+ * render time, which end-to-end timing around the promise cannot.
197
+ */
198
+ imageHandle(index: number, options?: RenderOptions): ImageHandle;
199
+ /**
200
+ * Convert a page to SVG — vector output, not pixels.
201
+ *
202
+ * Paths stay paths and text stays glyph outlines, so the result scales to any size
203
+ * and can be edited downstream. That is the trade: an SVG of a dense drawing is
204
+ * larger than a WebP of the same page at screen size and slower for a browser to
205
+ * paint, but it is the only output that survives being scaled up.
206
+ *
207
+ * Conversion is content-stream interpretation without rasterisation — roughly the
208
+ * non-preemptible portion of a render — so it shares the scheduler with everything
209
+ * else and honours {@link SvgOptions.priority}. **Not cached**, for the same reason
210
+ * {@link renderImage} is not.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * const page = await doc.renderSvg(0, { background: 'transparent' });
215
+ * await writeFile('page-0.svg', page.markup);
216
+ * ```
217
+ */
218
+ renderSvg(index: number, options?: SvgOptions): Promise<SvgPage>;
219
+ /**
220
+ * As {@link renderSvg}, but returns the handle so the job can be reprioritised,
221
+ * cancelled, or timed — the same relationship `imageHandle` has to `renderImage`.
222
+ */
223
+ svgHandle(index: number, options?: SvgOptions): SvgHandle;
30
224
  /**
31
225
  * Render a page, returning a handle you can reprioritise or drop.
32
226
  *
@@ -73,6 +267,60 @@ export declare class Document {
73
267
  * ```
74
268
  */
75
269
  stream(options?: StreamOptions): AsyncGenerator<StreamedPage>;
270
+ /**
271
+ * Extract the text of one page.
272
+ *
273
+ * Scheduled like a render, but behind one by default: a search should not stall the
274
+ * page the user is looking at. Results are cached, so searching the same document
275
+ * repeatedly extracts each page once.
276
+ *
277
+ * Extraction is interpretation without rasterisation, which makes it roughly a
278
+ * hundredth the cost of rendering the same page — about 1ms for a dense page of a
279
+ * paper. It is cheap enough to do speculatively.
280
+ */
281
+ pageText(index: number, options?: {
282
+ priority?: number;
283
+ signal?: AbortSignal;
284
+ }): Promise<PageText>;
285
+ /**
286
+ * Extract every page's text up front, so a later search is immediate.
287
+ *
288
+ * Worth calling on a long document: on native this fans out across rayon rather
289
+ * than the JS thread pool, which an addon cannot resize past four threads —
290
+ * measured 15ms for a 14-page paper against ~1ms per page one at a time.
291
+ *
292
+ * On wasm it walks the pages through the scheduler instead. The rayon batch is a
293
+ * native-only path for the same reason `renderPages` is: rayon's wasm workers are
294
+ * Web Workers the JS event loop has to create, and driving a batch from a blocked
295
+ * thread traps (`docs/spike-results.md`, "wasm parallel rendering is UNSTABLE").
296
+ *
297
+ * Idempotent, and never necessary — {@link search} extracts what it needs as it
298
+ * goes. This only trades a pause now for instant results later.
299
+ */
300
+ indexText(options?: {
301
+ priority?: number;
302
+ }): Promise<void>;
303
+ /**
304
+ * Find `query` in the document, yielding matches as each page is searched.
305
+ *
306
+ * Streaming rather than collected, because the first hit is usually the one the user
307
+ * wants and a long document should not make them wait for the last. Breaking out of
308
+ * the loop stops the search.
309
+ *
310
+ * `order` is the lever a viewer wants: searching outward from the current page finds
311
+ * the nearest hit first, which is almost never page 1.
312
+ *
313
+ * @example
314
+ * ```ts
315
+ * for await (const hit of doc.search('site plan', { order: outward(current) })) {
316
+ * show(hit.page, hit.rects);
317
+ * break;
318
+ * }
319
+ * ```
320
+ */
321
+ search(query: string, options?: SearchOptions): AsyncGenerator<SearchMatch>;
322
+ /** Extracted page text held for reuse, and how often that has paid off. */
323
+ get textCache(): CacheStats;
76
324
  /** How many pages this document will render at once. */
77
325
  get concurrency(): number;
78
326
  }
@@ -1 +1 @@
1
- {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,YAAY,CAAC;AAO1D,OAAO,EAAoB,KAAK,SAAS,EAAa,MAAM,gBAAgB,CAAC;AAE7E,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AAcpB,uFAAuF;AACvF,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,YAAY,CAAC;IAC3D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,sEAAsE;AACtE,wBAAsB,IAAI,CACxB,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,QAAQ,CAAC,CAQnB;AAED,qBAAa,QAAQ;;IAMnB,8CAA8C;IAC9C,YAAY,KAAK,EAAE,cAAc,EAAE,OAAO,GAAE,WAAgB,EAU3D;IAED,sEAAsE;IACtE,IAAI,KAAK,IAAI,UAAU,CAEtB;IAED,8BAA8B;IAC9B,UAAU,IAAI,IAAI,CAEjB;IAED,sFAAsF;IACtF,IAAI,MAAM,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAMvE;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAEhC;IAED,yDAAyD;IACnD,UAAU,CACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;IAED;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CAgC/D;IA4BD;;;;;;;;;;;OAWG;IACG,WAAW,CACf,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,EAAE,CAAC,CAoBzB;IAED;;;;;;;;;;;;;;OAcG;IACI,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,cAAc,CAAC,YAAY,CAAC,CA2CvE;IAED,wDAAwD;IACxD,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}