@aseity/idocs-preview 0.0.0-stage → 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 +50 -0
- package/LICENSE +21 -0
- package/README.md +206 -2
- package/dist/core/display.d.ts +180 -0
- package/dist/core/display.js +4 -0
- package/dist/core/engine.d.ts +9 -0
- package/dist/core/engine.js +81 -0
- package/dist/core/fonts.d.ts +27 -0
- package/dist/core/fonts.js +135 -0
- package/dist/core/index.d.ts +47 -0
- package/dist/core/index.js +156 -0
- package/dist/core/protocol.d.ts +137 -0
- package/dist/core/protocol.js +1 -0
- package/dist/core/wire.d.ts +14 -0
- package/dist/core/wire.js +100 -0
- package/dist/core/worker.d.ts +1 -0
- package/dist/core/worker.js +5 -0
- package/dist/view/find-bar.d.ts +26 -0
- package/dist/view/find-bar.js +117 -0
- package/dist/view/find.d.ts +46 -0
- package/dist/view/find.js +121 -0
- package/dist/view/index.d.ts +27 -0
- package/dist/view/index.js +274 -0
- package/dist/view/selection-layer.d.ts +27 -0
- package/dist/view/selection-layer.js +269 -0
- package/dist/view/selection.d.ts +131 -0
- package/dist/view/selection.js +350 -0
- package/dist/view/shell.d.ts +77 -0
- package/dist/view/shell.js +452 -0
- package/dist/view/viewport.d.ts +100 -0
- package/dist/view/viewport.js +145 -0
- package/package.json +49 -4
- package/wasm/hyphenation-licenses/LICENSE-APACHE +201 -0
- package/wasm/hyphenation-licenses/LICENSE-MIT +21 -0
- package/wasm/hyphenation-licenses/README.md +26 -0
- package/wasm/hyphenation-licenses/en-gb.tex +8615 -0
- package/wasm/hyphenation-licenses/en-us.tex +5018 -0
- package/wasm/hyphenation-licenses/ushyphex.tex +1454 -0
- package/wasm/idocs_wasm.d.ts +45 -0
- package/wasm/idocs_wasm.js +405 -0
- package/wasm/idocs_wasm_bg.wasm +0 -0
- package/wasm/idocs_wasm_bg.wasm.d.ts +10 -0
- package/wasm/package.json +1 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { glyphX, glyphOffset } from '../core/wire.js';
|
|
2
|
+
function textLength(line) {
|
|
3
|
+
return line.text.length;
|
|
4
|
+
}
|
|
5
|
+
/** Right edge of glyph `k`: the next glyph's x, the last one's at the line end. */
|
|
6
|
+
function glyphEnd(line, k) {
|
|
7
|
+
return k + 1 < line.glyphs.length ? glyphX(line.glyphs, k + 1) : line.x + line.width;
|
|
8
|
+
}
|
|
9
|
+
/** Offset of glyph `k`'s end within the line text. */
|
|
10
|
+
function glyphEndOffset(line, k) {
|
|
11
|
+
return k + 1 < line.glyphs.length ? glyphOffset(line.glyphs, k + 1) : textLength(line);
|
|
12
|
+
}
|
|
13
|
+
/** Offset (in the line text) of the glyph whose box holds `x`, or undefined outside the glyphs. */
|
|
14
|
+
export function indexForX(line, x) {
|
|
15
|
+
for (let k = 0; k < line.glyphs.length; k++) {
|
|
16
|
+
if (x >= glyphX(line.glyphs, k) && x < glyphEnd(line, k))
|
|
17
|
+
return glyphOffset(line.glyphs, k);
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Offset (in the line text) of the glyph boundary nearest `x`: left half of a glyph before it, right half after. */
|
|
22
|
+
export function closestIndexForX(line, x) {
|
|
23
|
+
for (let k = 0; k < line.glyphs.length; k++) {
|
|
24
|
+
const start = glyphX(line.glyphs, k);
|
|
25
|
+
const end = glyphEnd(line, k);
|
|
26
|
+
if (x < (start + end) / 2)
|
|
27
|
+
return glyphOffset(line.glyphs, k);
|
|
28
|
+
}
|
|
29
|
+
return textLength(line);
|
|
30
|
+
}
|
|
31
|
+
/** x of the glyph boundary at `index` (in the line text); an index inside a glyph gives its start. */
|
|
32
|
+
export function xForIndex(line, index) {
|
|
33
|
+
for (let k = 0; k < line.glyphs.length; k++) {
|
|
34
|
+
if (index < glyphEndOffset(line, k))
|
|
35
|
+
return glyphX(line.glyphs, k);
|
|
36
|
+
}
|
|
37
|
+
return line.x + line.width;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The story a line belongs to. Mirrors the display list's offsets
|
|
41
|
+
* (`StoryOffsets::plan` in rust/idocs-core/src/display/build.rs): header and
|
|
42
|
+
* footer offsets restart on every page, so each page's is its own story.
|
|
43
|
+
*/
|
|
44
|
+
export function storyKey(line, page) {
|
|
45
|
+
return line.story === 'header' || line.story === 'footer' ? `${line.story}:${page}` : line.story;
|
|
46
|
+
}
|
|
47
|
+
/** The display list's story a story key stands for: `header:3` is a header. */
|
|
48
|
+
export function storyKind(story) {
|
|
49
|
+
return story.split(':')[0];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A point of a tbRl line's own frame on the page: the frame is turned 90°
|
|
53
|
+
* clockwise about `turn` (`TextLine.turn`, `HighlightBox.turn`).
|
|
54
|
+
*/
|
|
55
|
+
export function turnedToPage([ox, oy], x, y) {
|
|
56
|
+
return [ox - y, oy + x];
|
|
57
|
+
}
|
|
58
|
+
/** A page point in a line's frame: the inverse of `turnedToPage` for a tbRl line. */
|
|
59
|
+
function local(line, x, y) {
|
|
60
|
+
if (!line.turn)
|
|
61
|
+
return [x, y];
|
|
62
|
+
const [ox, oy] = line.turn;
|
|
63
|
+
return [y - oy, ox - x];
|
|
64
|
+
}
|
|
65
|
+
/** How far `(x, y)` is from a line's box: vertical distance first, then horizontal. */
|
|
66
|
+
function distance(placed, x, y) {
|
|
67
|
+
const dx = Math.max(placed.left - x, 0, x - placed.right);
|
|
68
|
+
// Boxes are half-open: a line's bottom edge belongs to the line below.
|
|
69
|
+
const dy = y < placed.top ? placed.top - y : y >= placed.bottom ? y - placed.bottom + 1e-6 : 0;
|
|
70
|
+
return dy * 1e6 + dx;
|
|
71
|
+
}
|
|
72
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });
|
|
73
|
+
/**
|
|
74
|
+
* The text facts of a laid-out document for selections: each story's text
|
|
75
|
+
* (line texts at their offsets, `\n` between paragraphs) and where its
|
|
76
|
+
* lines are on each page.
|
|
77
|
+
*/
|
|
78
|
+
export class TextIndex {
|
|
79
|
+
#stories = new Map();
|
|
80
|
+
#pages;
|
|
81
|
+
constructor(pages) {
|
|
82
|
+
this.#pages = pages.map((page, index) => page.lines.map((line) => {
|
|
83
|
+
const story = storyKey(line, index);
|
|
84
|
+
let box = { left: line.x, right: line.x + line.width, top: line.top, bottom: line.top + line.height };
|
|
85
|
+
if (line.turn) {
|
|
86
|
+
const [x1, y1] = turnedToPage(line.turn, box.left, box.top);
|
|
87
|
+
const [x2, y2] = turnedToPage(line.turn, box.right, box.bottom);
|
|
88
|
+
box = { left: Math.min(x1, x2), right: Math.max(x1, x2), top: Math.min(y1, y2), bottom: Math.max(y1, y2) };
|
|
89
|
+
}
|
|
90
|
+
const placed = { line, page: index, story, ...box };
|
|
91
|
+
let entry = this.#stories.get(story);
|
|
92
|
+
if (!entry)
|
|
93
|
+
this.#stories.set(story, (entry = { text: '', lines: [], reading: [], repeats: [] }));
|
|
94
|
+
entry.lines.push(placed);
|
|
95
|
+
return placed;
|
|
96
|
+
}));
|
|
97
|
+
for (const story of this.#stories.values()) {
|
|
98
|
+
// Offsets follow reading order; a repeated header row shares its offsets.
|
|
99
|
+
const ordered = [...story.lines].sort((a, b) => a.line.offset - b.line.offset);
|
|
100
|
+
let text = '';
|
|
101
|
+
for (const placed of ordered) {
|
|
102
|
+
const { line } = placed;
|
|
103
|
+
if (line.offset < text.length) {
|
|
104
|
+
story.repeats.push(placed);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
text += '\n'.repeat(line.offset - text.length) + line.text;
|
|
108
|
+
story.reading.push(placed);
|
|
109
|
+
}
|
|
110
|
+
story.text = text;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The story's text; empty for a story the document does not have. */
|
|
114
|
+
text(story) {
|
|
115
|
+
return this.#stories.get(story)?.text ?? '';
|
|
116
|
+
}
|
|
117
|
+
/** Every story of the document. */
|
|
118
|
+
stories() {
|
|
119
|
+
return [...this.#stories.keys()];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The page of the character at `offset` in `story`: the page of the line
|
|
123
|
+
* holding it (the first showing of a repeated header row), or of the
|
|
124
|
+
* nearest line before it.
|
|
125
|
+
*/
|
|
126
|
+
pageAt(story, offset) {
|
|
127
|
+
const reading = this.#stories.get(story)?.reading ?? [];
|
|
128
|
+
let lo = 0;
|
|
129
|
+
let hi = reading.length;
|
|
130
|
+
while (lo < hi) {
|
|
131
|
+
const mid = (lo + hi) >> 1;
|
|
132
|
+
if (reading[mid].line.offset <= offset)
|
|
133
|
+
lo = mid + 1;
|
|
134
|
+
else
|
|
135
|
+
hi = mid;
|
|
136
|
+
}
|
|
137
|
+
return reading[Math.max(lo - 1, 0)]?.page ?? 0;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The pages that show any of `[start, end)` in `story`, in order: those
|
|
141
|
+
* from the page of `start` to that of `end - 1`, and those where a
|
|
142
|
+
* repeated table header row shows it again.
|
|
143
|
+
*/
|
|
144
|
+
pagesShowing(story, start, end) {
|
|
145
|
+
const pages = new Set();
|
|
146
|
+
const last = this.pageAt(story, end - 1);
|
|
147
|
+
for (let page = this.pageAt(story, start); page <= last; page++)
|
|
148
|
+
pages.add(page);
|
|
149
|
+
for (const { line, page } of this.#stories.get(story)?.repeats ?? []) {
|
|
150
|
+
if (line.offset < end && line.offset + line.text.length > start)
|
|
151
|
+
pages.add(page);
|
|
152
|
+
}
|
|
153
|
+
return [...pages].sort((a, b) => a - b);
|
|
154
|
+
}
|
|
155
|
+
/** The story of the line nearest `point`, or undefined on a page without text. */
|
|
156
|
+
storyAt(point) {
|
|
157
|
+
let best;
|
|
158
|
+
let bestDistance = Infinity;
|
|
159
|
+
for (const placed of this.#pages[point.page] ?? []) {
|
|
160
|
+
const d = distance(placed, point.x, point.y);
|
|
161
|
+
if (d < bestDistance)
|
|
162
|
+
[best, bestDistance] = [placed, d];
|
|
163
|
+
}
|
|
164
|
+
return best?.story;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Where `point` falls in `story`'s text: `offset` is the nearest glyph
|
|
168
|
+
* boundary, `under` the start of the glyph under the point (the nearest
|
|
169
|
+
* boundary when there is none). Above the story's lines on that page is
|
|
170
|
+
* their start, below them their end; a page without the story's lines
|
|
171
|
+
* goes to the end of its text on the pages before.
|
|
172
|
+
*/
|
|
173
|
+
locate(story, point) {
|
|
174
|
+
const lines = this.#stories.get(story)?.lines ?? [];
|
|
175
|
+
const here = lines.filter((p) => p.page === point.page);
|
|
176
|
+
if (here.length === 0) {
|
|
177
|
+
const before = lines.filter((p) => p.page < point.page);
|
|
178
|
+
const offset = before.length ? Math.max(...before.map((p) => p.line.offset + p.line.text.length)) : 0;
|
|
179
|
+
return { offset, under: offset };
|
|
180
|
+
}
|
|
181
|
+
if (point.y < Math.min(...here.map((p) => p.top))) {
|
|
182
|
+
const offset = Math.min(...here.map((p) => p.line.offset));
|
|
183
|
+
return { offset, under: offset };
|
|
184
|
+
}
|
|
185
|
+
if (point.y >= Math.max(...here.map((p) => p.bottom))) {
|
|
186
|
+
const offset = Math.max(...here.map((p) => p.line.offset + p.line.text.length));
|
|
187
|
+
return { offset, under: offset };
|
|
188
|
+
}
|
|
189
|
+
let best = here[0];
|
|
190
|
+
let bestDistance = Infinity;
|
|
191
|
+
for (const placed of here) {
|
|
192
|
+
const d = distance(placed, point.x, point.y);
|
|
193
|
+
if (d < bestDistance)
|
|
194
|
+
[best, bestDistance] = [placed, d];
|
|
195
|
+
}
|
|
196
|
+
const [x] = local(best.line, point.x, point.y);
|
|
197
|
+
const offset = best.line.offset + closestIndexForX(best.line, x);
|
|
198
|
+
const under = indexForX(best.line, x);
|
|
199
|
+
return { offset, under: under === undefined ? offset : best.line.offset + under };
|
|
200
|
+
}
|
|
201
|
+
/** The word (by `Intl.Segmenter`) holding the character at `offset`, within its paragraph. */
|
|
202
|
+
wordAt(story, offset) {
|
|
203
|
+
const [start, end] = this.paragraphAt(story, offset);
|
|
204
|
+
const text = this.text(story).slice(start, end);
|
|
205
|
+
let at = offset - start;
|
|
206
|
+
if (at >= text.length && at > 0)
|
|
207
|
+
at = text.length - 1;
|
|
208
|
+
for (const segment of segmenter.segment(text)) {
|
|
209
|
+
const stop = segment.index + segment.segment.length;
|
|
210
|
+
if (at < stop)
|
|
211
|
+
return [start + segment.index, start + stop];
|
|
212
|
+
}
|
|
213
|
+
return [offset, offset];
|
|
214
|
+
}
|
|
215
|
+
/** The paragraph holding `offset`, without its ending newline. */
|
|
216
|
+
paragraphAt(story, offset) {
|
|
217
|
+
const text = this.text(story);
|
|
218
|
+
const start = text.lastIndexOf('\n', offset - 1) + 1;
|
|
219
|
+
const next = text.indexOf('\n', offset);
|
|
220
|
+
return [start, next < 0 ? text.length : next];
|
|
221
|
+
}
|
|
222
|
+
/** Plain text of `selection`, without the placeholders of formulas and images. */
|
|
223
|
+
copyText(selection) {
|
|
224
|
+
return this.text(selection.story).slice(selection.start, selection.end).replaceAll('\ufffc', '');
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Highlight boxes of `selection` on page `page`, one per line in page
|
|
228
|
+
* order, each a line tall and centred on its text. A line whose paragraph end is selected gets a little extra width,
|
|
229
|
+
* so a selected empty paragraph shows.
|
|
230
|
+
*/
|
|
231
|
+
highlights(selection, page) {
|
|
232
|
+
const story = this.#stories.get(selection.story);
|
|
233
|
+
if (!story || selection.start === selection.end)
|
|
234
|
+
return [];
|
|
235
|
+
const boxes = [];
|
|
236
|
+
for (const placed of this.#pages[page] ?? []) {
|
|
237
|
+
if (placed.story !== selection.story)
|
|
238
|
+
continue;
|
|
239
|
+
const { line } = placed;
|
|
240
|
+
const start = line.offset;
|
|
241
|
+
const end = line.offset + line.text.length;
|
|
242
|
+
const from = Math.max(selection.start, start);
|
|
243
|
+
const to = Math.min(selection.end, end);
|
|
244
|
+
const newline = selection.end > end && story.text[end] === '\n';
|
|
245
|
+
if (from > to || (from === to && !newline))
|
|
246
|
+
continue;
|
|
247
|
+
// A line tall, centred on the text: an exact line height puts its
|
|
248
|
+
// leading above the text, and a box on the line box would show it there.
|
|
249
|
+
const middle = line.baseline + (line.descent - line.ascent) / 2;
|
|
250
|
+
const box = {
|
|
251
|
+
left: xForIndex(line, from - start),
|
|
252
|
+
right: xForIndex(line, to - start) + (newline ? line.height * 0.3 : 0),
|
|
253
|
+
top: middle - line.height / 2,
|
|
254
|
+
bottom: middle + line.height / 2,
|
|
255
|
+
};
|
|
256
|
+
if (line.turn)
|
|
257
|
+
box.turn = line.turn;
|
|
258
|
+
boxes.push(box);
|
|
259
|
+
}
|
|
260
|
+
return boxes;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* The selection state (after Zed's editor): a committed selection and, while
|
|
265
|
+
* the pointer is down, a pending one with the mode the click count chose.
|
|
266
|
+
* Single click selects by character, double by word, triple by paragraph,
|
|
267
|
+
* quadruple the whole story. Dragging snaps to the mode's units and keeps
|
|
268
|
+
* the unit first clicked; it never leaves the story it started in.
|
|
269
|
+
*/
|
|
270
|
+
export class Selector {
|
|
271
|
+
index;
|
|
272
|
+
selection;
|
|
273
|
+
#pending;
|
|
274
|
+
constructor(index) {
|
|
275
|
+
this.index = index;
|
|
276
|
+
}
|
|
277
|
+
/** The selection being dragged, until `up`. */
|
|
278
|
+
get pending() {
|
|
279
|
+
return this.#pending?.selection;
|
|
280
|
+
}
|
|
281
|
+
/** The selection to show: the pending one while dragging. */
|
|
282
|
+
get current() {
|
|
283
|
+
return this.#pending?.selection ?? this.selection;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Pointer down at `point` with the click count `clicks` (`MouseEvent.detail`).
|
|
287
|
+
* With `extend` (shift), the committed selection's anchor stays and the
|
|
288
|
+
* selection runs to `point` by characters. Returns false off any text.
|
|
289
|
+
*/
|
|
290
|
+
down(point, clicks, extend = false) {
|
|
291
|
+
const kept = this.selection;
|
|
292
|
+
if (extend && kept) {
|
|
293
|
+
const anchor = kept.reversed ? kept.end : kept.start;
|
|
294
|
+
this.#pending = { selection: kept, mode: 'character', original: [anchor, anchor] };
|
|
295
|
+
this.move(point);
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
const story = this.index.storyAt(point);
|
|
299
|
+
if (story === undefined)
|
|
300
|
+
return false;
|
|
301
|
+
const mode = clicks >= 4 ? 'all' : clicks === 3 ? 'paragraph' : clicks === 2 ? 'word' : 'character';
|
|
302
|
+
const original = this.#unitAt(story, mode, point);
|
|
303
|
+
this.#pending = { selection: { story, start: original[0], end: original[1], reversed: false }, mode, original };
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
/** Pointer moved while down: the head follows, within the story. */
|
|
307
|
+
move(point) {
|
|
308
|
+
const pending = this.#pending;
|
|
309
|
+
if (!pending || pending.mode === 'all')
|
|
310
|
+
return;
|
|
311
|
+
const { story } = pending.selection;
|
|
312
|
+
const [start, end] = this.#unitAt(story, pending.mode, point);
|
|
313
|
+
const [first, last] = pending.original;
|
|
314
|
+
pending.selection =
|
|
315
|
+
start < first
|
|
316
|
+
? { story, start, end: last, reversed: true }
|
|
317
|
+
: { story, start: first, end: Math.max(end, last), reversed: false };
|
|
318
|
+
}
|
|
319
|
+
/** The unit of `mode` at `point` in `story`: a collapsed range by character, the whole story for `all`. */
|
|
320
|
+
#unitAt(story, mode, point) {
|
|
321
|
+
if (mode === 'all')
|
|
322
|
+
return [0, this.index.text(story).length];
|
|
323
|
+
const { offset, under } = this.index.locate(story, point);
|
|
324
|
+
if (mode === 'paragraph')
|
|
325
|
+
return this.index.paragraphAt(story, under);
|
|
326
|
+
if (mode === 'word')
|
|
327
|
+
return this.index.wordAt(story, under);
|
|
328
|
+
return [offset, offset];
|
|
329
|
+
}
|
|
330
|
+
/** Pointer up: the pending selection becomes the selection. */
|
|
331
|
+
up() {
|
|
332
|
+
if (this.#pending)
|
|
333
|
+
this.selection = this.#pending.selection;
|
|
334
|
+
this.#pending = undefined;
|
|
335
|
+
}
|
|
336
|
+
/** Select the whole of `story`: the current selection's story, else the body. */
|
|
337
|
+
selectAll(story = this.current?.story ?? 'body') {
|
|
338
|
+
this.#pending = undefined;
|
|
339
|
+
this.selection = { story, start: 0, end: this.index.text(story).length, reversed: false };
|
|
340
|
+
}
|
|
341
|
+
clear() {
|
|
342
|
+
this.#pending = undefined;
|
|
343
|
+
this.selection = undefined;
|
|
344
|
+
}
|
|
345
|
+
/** Plain text of the current selection, for the clipboard. */
|
|
346
|
+
text() {
|
|
347
|
+
const selection = this.current;
|
|
348
|
+
return selection ? this.index.copyText(selection) : '';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type OpenOptions, type PreviewDocument, type SessionOptions } from '../core/index.js';
|
|
2
|
+
import { type FindBar } from './find-bar.js';
|
|
3
|
+
import type { Selector } from './selection.js';
|
|
4
|
+
export interface ViewOptions {
|
|
5
|
+
/** 1 = 100% (one point is 4/3 CSS pixels). Clamped to 0.1–10. */
|
|
6
|
+
zoom?: number;
|
|
7
|
+
/** Called with the index (0-based) of the page filling most of the viewport, on mount and whenever it changes. */
|
|
8
|
+
onPageChange?: (index: number, doc: PreviewDocument) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Called with the new zoom when the reader zooms with Ctrl+wheel (a
|
|
11
|
+
* trackpad pinch sends the same events); not for `setZoom`.
|
|
12
|
+
*/
|
|
13
|
+
onZoomChange?: (zoom: number) => void;
|
|
14
|
+
/** Device-pixel budget of one page canvas; beyond it the page is drawn blurrier. */
|
|
15
|
+
maxCanvasPixels?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface PreviewView {
|
|
18
|
+
/** The canvases that exist right now, in page order (visible pages and one either side). */
|
|
19
|
+
readonly canvases: readonly HTMLCanvasElement[];
|
|
20
|
+
readonly zoom: number;
|
|
21
|
+
/** Index of the current page; -1 for a document without pages. */
|
|
22
|
+
readonly currentPage: number;
|
|
23
|
+
/** The text selection (#449): read `selection`, call `selectAll`, `clear`, `text`. */
|
|
24
|
+
readonly selector: Selector;
|
|
25
|
+
/** The find bar (#451), also opened by Ctrl+F while the preview has focus. */
|
|
26
|
+
readonly find: FindBar;
|
|
27
|
+
/** The zoom at which the widest page fills the viewport width (see `fitWidthZoom`). */
|
|
28
|
+
readonly fitWidthZoom: number;
|
|
29
|
+
setZoom(zoom: number): void;
|
|
30
|
+
/**
|
|
31
|
+
* Scrolls page `index` (0-based, clamped) to the top of the viewport;
|
|
32
|
+
* `behavior: 'smooth'` animates the scroll (default: jump).
|
|
33
|
+
*/
|
|
34
|
+
goToPage(index: number, options?: ScrollOptions): void;
|
|
35
|
+
/** Removes the view and frees every canvas. */
|
|
36
|
+
destroy(): void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mount `doc` into `container`, which must have a height: the view fills it
|
|
40
|
+
* and scrolls inside it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mountPreview(container: HTMLElement, doc: PreviewDocument, options?: ViewOptions): PreviewView;
|
|
43
|
+
export interface PreviewOptions extends ViewOptions {
|
|
44
|
+
/** Where the Worker and wasm are, and the font reader (see `createSession`). */
|
|
45
|
+
session?: SessionOptions;
|
|
46
|
+
/** Shown while layout runs. */
|
|
47
|
+
loadingText?: string;
|
|
48
|
+
/** Show the first page while later display pages are generated. Selection becomes available when open completes. */
|
|
49
|
+
progressive?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export type PreviewState = 'empty' | 'loading' | 'ready' | 'error';
|
|
52
|
+
export interface Preview {
|
|
53
|
+
/**
|
|
54
|
+
* Lay out `bytes` in the Worker, showing the loading state meanwhile, then
|
|
55
|
+
* show every page at once. Rejects with a `PreviewError` (the message is
|
|
56
|
+
* shown in place of the pages). When opens overlap, the last one is shown.
|
|
57
|
+
*/
|
|
58
|
+
open(bytes: Uint8Array | ArrayBuffer, options?: OpenOptions): Promise<PreviewDocument>;
|
|
59
|
+
readonly state: PreviewState;
|
|
60
|
+
/** The document on screen. */
|
|
61
|
+
readonly document: PreviewDocument | undefined;
|
|
62
|
+
/** The view of `document`, for its canvases. */
|
|
63
|
+
readonly view: PreviewView | undefined;
|
|
64
|
+
readonly zoom: number;
|
|
65
|
+
/** Index of the current page; -1 before a document is shown. */
|
|
66
|
+
readonly currentPage: number;
|
|
67
|
+
setZoom(zoom: number): void;
|
|
68
|
+
/** Scrolls page `index` (0-based) of the document on screen to the top (see `PreviewView.goToPage`). */
|
|
69
|
+
goToPage(index: number, options?: ScrollOptions): void;
|
|
70
|
+
/** Terminates the Worker and frees every canvas; later opens reject. */
|
|
71
|
+
destroy(): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The host interface: mount into `container` (which must have a height), open
|
|
75
|
+
* documents, set the zoom, hear about page changes, destroy.
|
|
76
|
+
*/
|
|
77
|
+
export declare function createPreview(container: HTMLElement, options?: PreviewOptions): Preview;
|