@laminapdf/viewer 0.2.0-alpha.4 → 0.2.0-alpha.6
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/README.md +79 -10
- package/dist/client.d.ts +60 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +241 -22
- package/dist/client.js.map +1 -1
- package/dist/edit/session.d.ts +91 -0
- package/dist/edit/session.d.ts.map +1 -0
- package/dist/edit/session.js +179 -0
- package/dist/edit/session.js.map +1 -0
- package/dist/edit-log.d.ts +32 -0
- package/dist/edit-log.d.ts.map +1 -0
- package/dist/edit-log.js +74 -0
- package/dist/edit-log.js.map +1 -0
- package/dist/find/controller.d.ts +6 -0
- package/dist/find/controller.d.ts.map +1 -1
- package/dist/find/controller.js +69 -2
- package/dist/find/controller.js.map +1 -1
- package/dist/first-draw.d.ts +10 -0
- package/dist/first-draw.d.ts.map +1 -0
- package/dist/first-draw.js +33 -0
- package/dist/first-draw.js.map +1 -0
- package/dist/history/history.d.ts +74 -0
- package/dist/history/history.d.ts.map +1 -0
- package/dist/history/history.js +135 -0
- package/dist/history/history.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/layers/copy.d.ts +26 -0
- package/dist/layers/copy.d.ts.map +1 -0
- package/dist/layers/copy.js +150 -0
- package/dist/layers/copy.js.map +1 -0
- package/dist/layers/link-layer.d.ts +101 -0
- package/dist/layers/link-layer.d.ts.map +1 -0
- package/dist/layers/link-layer.js +527 -0
- package/dist/layers/link-layer.js.map +1 -0
- package/dist/layers/link-preview.d.ts +77 -0
- package/dist/layers/link-preview.d.ts.map +1 -0
- package/dist/layers/link-preview.js +202 -0
- package/dist/layers/link-preview.js.map +1 -0
- package/dist/layers/link-url.d.ts +19 -0
- package/dist/layers/link-url.d.ts.map +1 -0
- package/dist/layers/link-url.js +47 -0
- package/dist/layers/link-url.js.map +1 -0
- package/dist/layers/spans.d.ts +0 -6
- package/dist/layers/spans.d.ts.map +1 -1
- package/dist/layers/spans.js +2 -20
- package/dist/layers/spans.js.map +1 -1
- package/dist/layers/structure-layer.d.ts.map +1 -1
- package/dist/layers/structure-layer.js +3 -0
- package/dist/layers/structure-layer.js.map +1 -1
- package/dist/layers/text-layer.d.ts +27 -6
- package/dist/layers/text-layer.d.ts.map +1 -1
- package/dist/layers/text-layer.js +310 -28
- package/dist/layers/text-layer.js.map +1 -1
- package/dist/layout/page-layout.d.ts +7 -0
- package/dist/layout/page-layout.d.ts.map +1 -1
- package/dist/layout/page-layout.js +15 -0
- package/dist/layout/page-layout.js.map +1 -1
- package/dist/protocol.d.ts +197 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +33 -6
- package/dist/protocol.js.map +1 -1
- package/dist/source/opfs.d.ts +35 -0
- package/dist/source/opfs.d.ts.map +1 -0
- package/dist/source/opfs.js +178 -0
- package/dist/source/opfs.js.map +1 -0
- package/dist/thumbnails/strip.d.ts +1 -1
- package/dist/thumbnails/strip.d.ts.map +1 -1
- package/dist/thumbnails/strip.js +48 -4
- package/dist/thumbnails/strip.js.map +1 -1
- package/dist/view/previews.d.ts +47 -0
- package/dist/view/previews.d.ts.map +1 -0
- package/dist/view/previews.js +97 -0
- package/dist/view/previews.js.map +1 -0
- package/dist/view/user-select.d.ts +3 -0
- package/dist/view/user-select.d.ts.map +1 -0
- package/dist/view/user-select.js +10 -0
- package/dist/view/user-select.js.map +1 -0
- package/dist/view/viewer.d.ts +143 -5
- package/dist/view/viewer.d.ts.map +1 -1
- package/dist/view/viewer.js +590 -50
- package/dist/view/viewer.js.map +1 -1
- package/dist/worker/document.d.ts +66 -6
- package/dist/worker/document.d.ts.map +1 -1
- package/dist/worker/document.js +139 -23
- package/dist/worker/document.js.map +1 -1
- package/dist/worker/edit.d.ts +35 -0
- package/dist/worker/edit.d.ts.map +1 -0
- package/dist/worker/edit.js +305 -0
- package/dist/worker/edit.js.map +1 -0
- package/dist/worker/find.d.ts +2 -2
- package/dist/worker/find.d.ts.map +1 -1
- package/dist/worker/find.js +8 -21
- package/dist/worker/find.js.map +1 -1
- package/dist/worker/fingerprint.d.ts +10 -0
- package/dist/worker/fingerprint.d.ts.map +1 -0
- package/dist/worker/fingerprint.js +42 -0
- package/dist/worker/fingerprint.js.map +1 -0
- package/dist/worker/methods.d.ts.map +1 -1
- package/dist/worker/methods.js +42 -4
- package/dist/worker/methods.js.map +1 -1
- package/dist/worker/navigation.d.ts +5 -1
- package/dist/worker/navigation.d.ts.map +1 -1
- package/dist/worker/navigation.js +22 -4
- package/dist/worker/navigation.js.map +1 -1
- package/dist/worker/print.d.ts +14 -0
- package/dist/worker/print.d.ts.map +1 -0
- package/dist/worker/print.js +49 -0
- package/dist/worker/print.js.map +1 -0
- package/dist/worker/recent-pages.d.ts +16 -0
- package/dist/worker/recent-pages.d.ts.map +1 -0
- package/dist/worker/recent-pages.js +68 -0
- package/dist/worker/recent-pages.js.map +1 -0
- package/dist/worker/render.d.ts.map +1 -1
- package/dist/worker/render.js +5 -2
- package/dist/worker/render.js.map +1 -1
- package/dist/worker/session.d.ts +59 -0
- package/dist/worker/session.d.ts.map +1 -0
- package/dist/worker/session.js +152 -0
- package/dist/worker/session.js.map +1 -0
- package/dist/worker/text.d.ts +11 -3
- package/dist/worker/text.d.ts.map +1 -1
- package/dist/worker/text.js +33 -25
- package/dist/worker/text.js.map +1 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -35,28 +35,38 @@ The view keeps only the pages near the viewport in the DOM and paints each in tw
|
|
|
35
35
|
- **Canvas size on touch devices.** A phone or tablet can't hold the desktop's canvases: iOS Safari reloads a tab whose canvases pass 224–384 MB in all. With a coarse pointer, every canvas holds at most 5 million pixels; on a tablet whose window covers more than 5 million device pixels, the page in view shows its backdrop rather than a detail canvas.
|
|
36
36
|
- **Memory on touch devices.** The two pools and the bitmap cache together stay under 184 MB by default. On a device that reports 2 GB of memory or less (`navigator.deviceMemory`, in Chromium) the limit is 116 MB, and on one that reports 4 GB or more, 304 MB. `createViewer`'s options override each budget, and `device` gives the hints the budgets are chosen from instead of detecting them.
|
|
37
37
|
- **Tall documents.** A document taller than 16 million CSS px at the chosen zoom gets its zoom capped, because browsers stop scrolling near that height.
|
|
38
|
+
- **Page appearance.** `setAppearance("sepia" | "dark" | "original")`, or High contrast with two colours (`{ kind: "high-contrast", ink, paper }`), draws the pages in other colours. Images and annotations keep theirs ([`@laminapdf/render`](../render/README.md) says how). The pages in view paint again, a quick pass then sharp tiles, and the scroll position stays where it was. Other mounted pages give their canvas back and paint once they come into view. Until a page's pixels arrive, its shell shows `--lamina-page-paper` (white by default), so set it to the appearance's paper. The thumbnails follow, and `getState().appearance` says which appearance is shown.
|
|
38
39
|
|
|
39
40
|
**API.**
|
|
40
41
|
|
|
41
42
|
| Member | What it does |
|
|
42
43
|
| --- | --- |
|
|
43
|
-
| `createViewer({ client, margin?, gap?, pool?, detailPool?, cacheBytes?, backdropMaxPixels?, canvasMaxPixels?, device?, settleMs?, zoom?, text? })` | Makes a viewer over a `ViewerClient`. `text` is on by default; `false` leaves the text layer out, and an object sets its `outline`, `untaggedLabel` and `onSelection`. |
|
|
44
|
+
| `createViewer({ client, margin?, gap?, pool?, detailPool?, cacheBytes?, backdropMaxPixels?, canvasMaxPixels?, device?, settleMs?, zoom?, text?, links?, history? })` | Makes a viewer over a `ViewerClient`. `text` is on by default; `false` leaves the text layer out, and an object sets its `outline`, `untaggedLabel` and `onSelection`. `links` is on by default too; an object sets its `href`, `onExternal` and `words` (below). `history.max` caps the entries kept (default 100). |
|
|
44
45
|
| `mount(container)`, `open(document)`, `destroy()` | Puts it on the page, shows a document, takes it down. |
|
|
45
46
|
| `goToPage(index, { anchor })`, `setZoom(zoom, { anchor })`, `rotate(delta)` | Navigation. `zoom` is a mode or a percentage. |
|
|
46
47
|
| `goToDestination(target)` | Goes to an outline item's or a named destination's target (below). |
|
|
48
|
+
| `jumpToPage(index)`, `jumpToDestination(target)` | Like `goToPage` and `goToDestination`, but recorded as history jumps, so Back returns to where the view was. |
|
|
49
|
+
| `history` | Back and forward through the jumps (below). |
|
|
47
50
|
| `getState()`, `subscribe(listener)` | The view state (`currentPage`, `pageCount`, `zoom`, `percent`, `rotation`, `scrollTop`, `visibleRange`), for `useSyncExternalStore`. |
|
|
48
|
-
| `on("pagechange" \| "zoomchange" \| "renderstats", …)`, `stats()` | Events and counters. `renderstats` counts renders, for measuring performance. |
|
|
51
|
+
| `on("pagechange" \| "zoomchange" \| "renderstats" \| "invalidate", …)`, `stats()` | Events and counters. `renderstats` counts renders, for measuring performance. `invalidate` lists the pages `invalidate` redrew, each with its size in points now. |
|
|
49
52
|
| `addLayer({ name, onMount, onUnmount, onZoom })` | Mounts a layer into each page. |
|
|
50
53
|
| `keepMounted(pages)` | Keeps these pages mounted wherever the view scrolls, replacing the previous set; `[]` releases them. The text layer pins the pages a selection touches. |
|
|
51
54
|
| `pointAt(x, y)`, `positionOf(point)` | Converts between viewport positions and document points. |
|
|
55
|
+
| `pageSizes()` | Every page's displayed size in points, as the view lays it out, edits included. A thumbnail strip lays its pages out from it. |
|
|
56
|
+
| `applyEdit(ops)`, `invalidate(pages, sizes?)` | Edits the document and redraws the pages the edit touched (below). |
|
|
52
57
|
| `MIN_ZOOM`, `MAX_ZOOM` | The bounds of a percentage zoom, 10 and 800, for a zoom control's range. |
|
|
53
58
|
|
|
54
|
-
**Layers.** Each page shell stacks a canvas and three slots above it: `div.lamina-layer-text`, `div.lamina-layer-annot` and `div.lamina-layer-highlight`. The text layer fills the first
|
|
59
|
+
**Layers.** Each page shell stacks a canvas and three slots above it: `div.lamina-layer-text`, `div.lamina-layer-annot` and `div.lamina-layer-highlight`. The text layer fills the first, links the second and find highlights the third, through `addLayer`. A layer with another name gets its own slot on top. Every slot above the text lets the pointer through (`pointer-events: none`), so text stays selectable under it; an element a layer wants clicked, such as a link's anchor, sets `pointer-events: auto` on itself.
|
|
55
60
|
|
|
56
61
|
**Text.** The text layer makes every page's text selectable, copyable and readable by screen readers, whichever backend draws the page.
|
|
57
62
|
- **Spans.** Each text run of Lamina PDF's extraction gets one transparent span, placed from its glyphs' quads, never from the renderer's. A rotated or vertical run gets a rotated span, and a span reads right to left when its first letter does. The spans sit in a box sized in points, which one CSS transform scales and turns to the page, so a zoom or a rotation moves no span.
|
|
58
|
-
- **Selection.**
|
|
59
|
-
- **Copy** writes the
|
|
63
|
+
- **Selection.** Only the spans are selectable. A drag runs from one page into the next and never takes in a canvas or a gap. A drag that leaves the viewer also highlights your app's text; set `user-select: none` on text you want to keep out. Copy, the pinned pages and `onSelection` keep to the part inside the viewer either way. Keep `user-select: none` off every ancestor of the viewer: Firefox then stops selecting. The pages a selection touches stay mounted while it lasts. Ctrl/⌘+A in the viewer selects the text of the mounted pages only.
|
|
64
|
+
- **Copy** writes the selected text as plain text and as HTML, in one of two modes:
|
|
65
|
+
- **By paragraph** (the default) follows the reading order Lamina PDF's extraction finds, so a selection across two columns copies the left column, then the right. A paragraph's lines join into one line, hyphenated words at a line's end join again, ligatures become their letters, and a blank line separates paragraphs.
|
|
66
|
+
- **Exact** keeps the layout's lines: a space at a gap, and a new line at each new baseline and between pages. A table's lines keep their breaks in both modes.
|
|
67
|
+
- **`copySelection(mode)`** copies in either mode from a command or a button. Call it from a click or key handler: browsers refuse clipboard writes outside one, and it then resolves `false`.
|
|
68
|
+
- **Column selection:** an Alt/Option-drag selects the text inside a rectangle, such as one column of a page. Setting `columnSelect` makes a plain drag do the same.
|
|
69
|
+
- **Selection events:** `onSelection` and the viewer's `selectionchange` event give the app the selected text, its client rectangles, its pages and the pointer that made it. `settled` is false during a drag and true once the pointer is up, when a toolbar may show; `column` marks a column selection.
|
|
60
70
|
- **Structure.** For a tagged document, a hidden tree of ARIA elements follows the structure tree. Headings carry their levels, and figures their alternate text; paragraphs, lists and tables keep their roles. Each element owns its text spans through `aria-owns`, matched by page and marked-content ID (the number a tagged PDF gives each piece of a page's content), so a screen reader reads the document in structure order. For a document without a structure tree, a screen reader hears "Untagged document" once (`untaggedLabel`).
|
|
61
71
|
|
|
62
72
|
The document Web Worker reads each page's text at `background` priority, after the tiles in view, and caches the text of the last 24 pages. Text needs the extract module, which the document Web Worker loads once the render backend is ready, so the first page never waits for it. Selection colours are the app's: style `.lamina-text-layer span::selection`, since the viewer adds no style sheet.
|
|
@@ -77,6 +87,25 @@ The document Web Worker reads each page's text at `background` priority, after t
|
|
|
77
87
|
|
|
78
88
|
A left the file leaves out keeps the current horizontal scroll, and a top it leaves out goes to the page's top. A view rotated by `rotate()` turns the point with the page, and a fit always fits the viewport's own width or height, as in pdf.js.
|
|
79
89
|
|
|
90
|
+
**Links.** The link layer puts an anchor over each link on a mounted page, from Lamina PDF's reading of the page's link annotations (`ViewerClient.pageLinks(index)`), never the renderer's. Each link does one of three things, and none runs a script or program the file names:
|
|
91
|
+
- **Inside the document.** A link to a destination, or one of the named actions Next page, Previous page, First page and Last page, goes there as a history jump. Make `links.href(page)` return the app's address for that page. It becomes the anchor's `href`, so a middle click or "Open in new tab" opens the same document there.
|
|
92
|
+
- **To the web.** Only `http:`, `https:` and `mailto:` addresses may open, and only after the app confirms. A click, a middle click or Enter calls `links.onExternal(link)`, with the address normalised (an international host in punycode) and split around its host for showing it. The anchor has no `href`, so no browser menu can open it unasked; its title shows the address. `externalLink(uri)` gives the app the same rules.
|
|
93
|
+
- **Everything else does nothing.** A link to another file, an action such as Launch or JavaScript, a refused address (`javascript:`, `file:`, `data:`) or a destination missing from the file gets no `href`, and its title says why.
|
|
94
|
+
|
|
95
|
+
Anchors follow page order, so Tab reaches a page's links in order. Each is named by its `/Contents` or by what it does ("Link to page 3"); `links.words` gives those names in another language. Only the anchors take pointer events, so text beside a link still selects. Style them as `a.lamina-link`; the viewer adds no style sheet.
|
|
96
|
+
|
|
97
|
+
**Link previews.** The viewer asks to preview a link inside the document after 300 ms of mouse or pen hover or of visible keyboard focus, or on a 500 ms long-press with a finger that doesn't move. It cancels a long-press's click, and the long-press selects no text. Web links never preview.
|
|
98
|
+
|
|
99
|
+
The viewer emits `linkpreview` with the link, its anchor, how it was asked for (`fine`, `keyboard` or `touch`) and what it shows: a destination, or the sentence of a page action or another file's link. `linkpreviewend` follows 300 ms after the pointer leaves both the link and the preview, or when focus leaves the link. The app draws the preview with these:
|
|
100
|
+
- `viewer.preview(target, { signal })` draws where the link goes, in one render by the document's Web Worker. A destination with a point gets a crop from above and left of the point across its text column, at the reader's zoom clamped to 100–150 % and at most 480 × 180 CSS px. Any other destination gets the top of its page.
|
|
101
|
+
- It returns the bitmap, its CSS size, the destination page and the text of the lines it shows. The viewer keeps the last 8 bitmaps and closes them on a zoom or rotation change and when the document closes, so draw the bitmap at once and never close it. `stats().previewBitmaps` counts them.
|
|
102
|
+
- `viewer.holdLinkPreview(true | false)` keeps the preview while the pointer is on it, `viewer.dismissLinkPreview()` hides it on Escape or Close, and `viewer.followLinkPreview()` follows the previewed link, else the focused one.
|
|
103
|
+
|
|
104
|
+
**History.** `viewer.history` records jumps: links, `jumpToPage`, `jumpToDestination`, find's moves and thumbnail clicks. Scrolling alone records nothing. Each jump saves the view it leaves, both scroll position and zoom, so Back returns there, not only to that page.
|
|
105
|
+
- `back()`, `forward()` and `goTo(id)` move the view; `getState()` and `subscribe()` say where Back and Forward go (`back`, `forward`, `current`, each an entry with a page point and a zoom).
|
|
106
|
+
- A jump that lands where the view already is adds nothing, and a new jump drops what Forward held.
|
|
107
|
+
- `onEntry(listener)` hears of each entry added, so an app can keep its own history in step: the web reader writes each jump into the browser's history through the address fragment, and `goTo(id)` answers the browser's Back.
|
|
108
|
+
|
|
80
109
|
**Thumbnails.** `new ThumbnailStrip(client, viewer, { width?, margin?, cacheBytes?, label?, name? })` makes a list of the pages as small pictures, for a sidebar. `mount(container)` puts it on the page, and `open(document)` shows the document the viewer opened. Size its `element` with CSS; it scrolls inside.
|
|
81
110
|
- **Like the viewer, it mounts only what is near its view.** It keeps the thumbnails in view and 4 either side. Each borrows a canvas from the strip's own pool, and a thumbnail scrolled away aborts its render.
|
|
82
111
|
- **Renders wait for the main view.** Thumbnails render at `background` priority, so the pages in view always come first. Bitmaps stay in a cache of their own, 32 MB by default.
|
|
@@ -84,6 +113,32 @@ A left the file leaves out keeps the current horizontal scroll, and a top it lea
|
|
|
84
113
|
- **Keyboard and pointer.** A click, or Enter or Space, goes to that page. The arrow keys, Home and End move the focus, and only one thumbnail is in the Tab order.
|
|
85
114
|
- **Names.** Each thumbnail is named by its page label and number ("Page iv (4 of 120)") and captioned with its label. For another language, pass `label` to replace both and `name` to replace the list's own name.
|
|
86
115
|
|
|
116
|
+
**Edits.** `viewer.applyEdit(ops)` edits the open document and redraws the pages the edit touched, in one call. The ops are `@laminapdf/sdk`'s, such as `{ op: "rotatePage", args: { page, rotate: 90 } }`, and apply as one transaction. The call resolves with those pages as they are after the edit: index, ID, rotation and displayed size in points, so a quarter turn swaps width and height.
|
|
117
|
+
- **How an edit reaches the screen.** PDFium draws from the bytes it opened, so the Web Worker writes the edit into memory as an incremental update and opens the render document again from the result. The old render document keeps drawing until the new one is open. The Web Worker runs one call at a time, so every render answered before the edit drew the old file, and every render after it draws the new one. A render the view asked for before an edit and that answers after it is dropped, never cached, and asked for again.
|
|
118
|
+
- **What the viewer redraws.** Only the touched pages: their bitmaps go, here and in a thumbnail strip, and their layers read their text and links again. Every other page keeps what it drew. A page in view at its old size keeps its pixels until sharp tiles cover them, so it never flashes. A page whose size changed paints its quick pass first, and the page at the top of the viewport stays there.
|
|
119
|
+
- **What changes nothing.** An edit of the document's metadata touches no page: the Web Worker opens nothing again, and the viewer redraws nothing. A failed edit, such as one naming a page that doesn't exist, rejects and leaves the document and the pages as they were. So does an edit whose new file the backend can't open: the Web Worker undoes it. An edit that adds, removes or moves pages is refused for now, since the view finds pages by index.
|
|
120
|
+
- **When an edit can't be undone.** If the undo after a failed reopen fails too, the call rejects with `worker-failed` saying to open the document again, and every later edit does the same.
|
|
121
|
+
- **Driving it yourself.** `ViewerClient.applyEdit(ops)` makes the edit without the redraw; pass its pages to `viewer.invalidate(indices, sizes)`, which redraws any pages you name.
|
|
122
|
+
- **The zoom follows.** While the zoom fits the width or the page, a page that turned is fitted again, so a page lying on its side never runs past the window.
|
|
123
|
+
|
|
124
|
+
**The edit session.** `viewer.edits` keeps the open document's history and is the store an editor reads (`getState()`, `subscribe()`):
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await viewer.edits.apply([{ op: "rotatePage", args: { page, rotate: 90 } }], "Rotate page 5");
|
|
128
|
+
await viewer.edits.undo(); // the page stands again
|
|
129
|
+
await viewer.edits.redo();
|
|
130
|
+
await viewer.edits.goTo(0); // back to the file as opened, in one call
|
|
131
|
+
const { history, busy, blocked, notice } = viewer.edits.getState();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- **One step per edit.** Each edit is a step with its label, its pages and its time. `history.steps` lists them oldest first; the first `history.position` are done and the rest are undone, ready to redo. A new edit drops the undone ones. An edit that changed nothing (setting a value the document already had) makes no step.
|
|
135
|
+
- **Undo and redo redraw too.** They draw only the pages the step touched, as an edit does, and `goTo` draws once however many steps it moves. Calls run one after another in the order they were made, so three quick presses of Undo undo three steps.
|
|
136
|
+
- **When an undo can't restore everything.** A later change can replace part of what a step changed. Then the undo restores the rest and the step is marked `partial`; `notice` holds it until the next edit, and `onReverted(listener)` hears each undo and redo with how it went. An undo that could restore nothing takes the step out of the history.
|
|
137
|
+
- **Unsaved changes.** `history.unsaved` counts the steps between the document now and the file as opened, so undoing every step leaves it clean again. The history keeps 500 steps and then drops the oldest (`history.dropped`); the steps it drops still count.
|
|
138
|
+
- **What blocks editing.** `blocked` says why the document can't be edited: none is open, or it is encrypted, or an edit failed and couldn't be undone, so it must be opened again. An encrypted file's edits can't be written yet, and `OpenedDocument.encrypted` says so when it opens.
|
|
139
|
+
- **A restart keeps the edits.** If the Web Worker crashes, the client opens the file again, replays every step, then undoes the ones that were undone, so the history and the pages are as they were (`restarted`'s `edits` is `restored`). A replay can't recreate an undo or redo that restored only part of a step, or none of it, so after one of those, or when the replay fails, the file opens as it was and `edits` is `lost`.
|
|
140
|
+
- **Without the viewer.** `ViewerClient` has the same calls (`applyEdit(ops, { label })`, `undoEdit()`, `redoEdit()`, `goToStep(n)`, `editHistory()`), each resolving with the pages to redraw and the history, and fires `edit` after each.
|
|
141
|
+
|
|
87
142
|
**Tuning.** The margin, pool size, pixel cap, cache budget and settle delay are measured by the perf harness ([Performance](#performance)); the first full run kept them.
|
|
88
143
|
|
|
89
144
|
## The document Web Worker
|
|
@@ -118,6 +173,8 @@ The client starts the Web Worker and talks to it; you call `ViewerClient`'s meth
|
|
|
118
173
|
- A queued render is dropped.
|
|
119
174
|
- A running one finishes, because synchronous WebAssembly can't be interrupted. Its late answer's bitmap is closed unpainted (`onDiscard`).
|
|
120
175
|
- (`@laminapdf/sdk/worker` itself rejects with a `cancelled` LaminaPdfError; the viewer maps it to `AbortError`.)
|
|
176
|
+
- An open is the exception. A cancel can reach the page after the Web Worker already opened the new file and closed the old one. So after a cancelled open, `open()` asks the Web Worker which document it holds (`currentDocument`). If this open made it, the cancel came too late: `open()` resolves with that document after all, and the page and the Web Worker agree. Otherwise it rejects with `AbortError`, and the old document stays open.
|
|
177
|
+
- Either way, an aborted `open()` settles only once the Web Worker has finished the open it was running. `stop()` ends a stuck one at once.
|
|
121
178
|
- **Timeouts:** the page keeps the timers, because a Web Worker inside a render can't post anything.
|
|
122
179
|
- Defaults are `soft` 10 s (`render-slow`) and `hard` 120 s.
|
|
123
180
|
- At the hard cap the client calls `stop("hard-timeout")`. For a document that needed a password, it emits `render-stuck` instead and waits for the user's `stop()`.
|
|
@@ -140,7 +197,7 @@ The client starts the Web Worker and talks to it; you call `ViewerClient`'s meth
|
|
|
140
197
|
|
|
141
198
|
## For contributors
|
|
142
199
|
|
|
143
|
-
The viewer is the web client of [ADR 0009](../../docs/decisions/0009-web-client-architecture.md); it was built in [C3a](../../docs/todos/done/C3a-viewer-worker-rpc.md) (the document worker and its typed RPC) and [C3b](../../docs/todos/done/C3b-viewer-virtualiser-and-zoom.md) (the viewer). [C3c](../../docs/todos/done/C3c-viewer-perf-harness.md) measures the tuning values with `renderstats`. The layers are for [C5a](../../docs/todos/done/C5a-text-layer-and-selection.md) (text), [C5b](../../docs/todos/done/C5b-find-in-document.md) (find) and [C6b](../../docs/todos/
|
|
200
|
+
The viewer is the web client of [ADR 0009](../../docs/decisions/0009-web-client-architecture.md); it was built in [C3a](../../docs/todos/done/C3a-viewer-worker-rpc.md) (the document worker and its typed RPC) and [C3b](../../docs/todos/done/C3b-viewer-virtualiser-and-zoom.md) (the viewer). [C3c](../../docs/todos/done/C3c-viewer-perf-harness.md) measures the tuning values with `renderstats`. The layers are for [C5a](../../docs/todos/done/C5a-text-layer-and-selection.md) (text), [C5b](../../docs/todos/done/C5b-find-in-document.md) (find) and [C6b](../../docs/todos/done/C6b-links-and-history.md) (links); C4c will read documents by range through `@laminapdf/sdk`'s `RangeSource`. Cancellation follows ADR 0009 §3.5, and warnings are [ADR 0003](../../docs/decisions/0003-reader-tolerance-and-limits.md)'s.
|
|
144
201
|
|
|
145
202
|
### Architecture
|
|
146
203
|
|
|
@@ -165,9 +222,15 @@ There is one protocol, not two: the viewer's worker is `@laminapdf/sdk/worker`'s
|
|
|
165
222
|
| Method | Params | Result | Priority |
|
|
166
223
|
| --- | --- | --- | --- |
|
|
167
224
|
| `initBackend` | `{ backend: "pdfium" \| "hayro", wasm? }` | `{ name, version }` | document. Sent by `create()`, before a file is chosen, so the WebAssembly downloads in parallel with file selection. |
|
|
168
|
-
| `openDocument` | `source` (`Blob`, `ArrayBuffer`, `Uint8Array`), `{ password?, limits?, renderLimits? }` | `{ pageCount, pages, needsPassword, backend }` | document |
|
|
225
|
+
| `openDocument` | `source` (`Blob`, `ArrayBuffer`, `Uint8Array`), `{ password?, limits?, renderLimits? }`, `ticket?` (the client's number for this open) | `{ pageCount, pages, needsPassword, backend }` | document |
|
|
226
|
+
| `currentDocument` | — | `{ document, ticket }` of the open document, or `undefined`: `open()` asks after a cancelled open | document |
|
|
169
227
|
| `closeDocument` | — | — | document |
|
|
170
228
|
| `pageInfo` | `index` | `PageInfo` (displayed size in points, rotation, ID, label) | document |
|
|
229
|
+
| `applyEdit` | `ops`, the document's `id`, `label?` | `EditResult`: the touched pages' `PageInfo`, after the edit (empty for a metadata edit), and the edit session's `history` | document. `client.applyEdit(ops)`. Saves into memory and opens the render document again when the edit touched pages; records a step unless nothing changed |
|
|
230
|
+
| `undoEdit`, `redoEdit` | the document's `id` | `EditResult` with `reverted`: the step and how it went | document. Redraws as an edit does |
|
|
231
|
+
| `goToStep` | `position`, the document's `id` | `EditResult` with every step undone or redone | document. One redraw; a failure half way takes every move back |
|
|
232
|
+
| `editHistory` | the document's `id` | `EditHistory` | document |
|
|
233
|
+
| `restoreEdits` | `steps` (ops, label, time), `undone`, the document's `id` | `EditResult` | document. A restarted worker's replay |
|
|
171
234
|
| `find` | `query`, `FindOptions`, `pages` | `FindPageResult[]`: each page's matches as quads, and whether it has no text | `background` |
|
|
172
235
|
| `render` | ADR 0004 `RenderRequest` | `{ bitmap: ImageBitmap, width, height }` (transferred) | `visible` > `prefetch` > `background` |
|
|
173
236
|
| `memory` | — | `WorkerMemory`: the bytes of the backend's and Lamina PDF's WebAssembly memories | document. `client.memory()`; the perf harness samples it. |
|
|
@@ -175,6 +238,8 @@ There is one protocol, not two: the viewer's worker is `@laminapdf/sdk/worker`'s
|
|
|
175
238
|
| `structure` | — | `StructureData`: whether the document is tagged, and its structure tree | document |
|
|
176
239
|
| `outline` | — | `OutlineNode[]`: the outline, each item's target in page space | document |
|
|
177
240
|
| `namedDestination` | `name` | `Target \| undefined` | document |
|
|
241
|
+
| `pageLinks` | `index` | `PageLink[]`: the page's links in page space, by `kind`; the last 24 pages' are cached | document |
|
|
242
|
+
| `reflow` | — | `Reflow`: the document for reader mode, its blocks in the structure tree's order with figures, formulas and tables as crops, or why not | document. `client.reflow()`; the extract module only |
|
|
178
243
|
| `networkLog` | — | `WorkerNetworkLog`: every request and blocked request since the Web Worker started | document. `client.networkLog()`: what the `network` and `csp-violation` events reported, those sent before anyone listened included; `index` merges the two. |
|
|
179
244
|
|
|
180
245
|
`client.call(method, params, { priority, signal })` is typed by the `Methods` map: an unknown method or wrong params fails `tsc` (`test/types.test.ts`).
|
|
@@ -221,15 +286,17 @@ cargo xtask viewer-perf [chromium|firefox|webkit] # the same, plus the job summ
|
|
|
221
286
|
|
|
222
287
|
It writes `target/viewer-perf/<browser>.json` and `summary.md`, and never fails on a missed target: no pull request lane asserts a duration ([ADR 0018](../../docs/decisions/0018-test-strategy.md#2-ci-lanes)).
|
|
223
288
|
|
|
224
|
-
**What it does.** It opens
|
|
289
|
+
**What it does.** It opens four seeded files, built with `@laminapdf/sdk` and cached in `perf/.fixtures/`: 10 pages of text with an image each (about 1 MB), 100 and 1,000 pages of text, and 300 pages of full-page images. A Vite build of `perf/page/` shows them, and `perf/serve.ts` serves that page gzipped, as Cloudflare serves the app. Playwright drives real input, so every run scrolls the same way:
|
|
225
290
|
|
|
226
291
|
| Scenario | Measures | Target |
|
|
227
292
|
| --- | --- | --- |
|
|
228
293
|
| Open the 10-page file, warm (after a reload) | time to the first page's first paint | ≤ 700 ms |
|
|
229
|
-
| The same, cold, on Chrome DevTools' Fast 4G (Chromium only) | time from navigation to the first page | ≤ 3 s |
|
|
294
|
+
| The same, cold, on Chrome DevTools' Fast 4G (Chromium only), the file on the device | time from navigation to the first page | ≤ 3 s |
|
|
295
|
+
| The same, cold, the file from a link (Range requests, as a `#url=` link reads it) | time from navigation to the first page | ≤ 3 s (reported) |
|
|
230
296
|
| Scroll the 1,000-page file end to end, twice, with wheel steps every 20 ms; stop at a quarter, a half and three quarters | the longest main-thread task, time to sharp after each stop, peak memory, memory after each pass, cancelled renders that painted | no task > 50 ms; sharp ≤ 300 ms; ≤ 600 MB; second pass ≤ first + 5 %; 0 |
|
|
231
297
|
| Fling across the 300 image pages, then stop | time to sharp | ≤ 300 ms |
|
|
232
298
|
| Three ctrl+wheel bursts in and out on the 10-page file | the longest frame, time to sharp after each burst | reported |
|
|
299
|
+
| Turn the page in view a quarter (`viewer.applyEdit`), five times, in the 10-, 100- and 1,000-page files; on the 100-page file, go on to 20 turns in a row | the median time from the call to the page's first new pixels (its quick pass), and to sharp; the twentieth turn's first new pixels | ≤ 150 ms at 100 pages ([E1 plan §6](../../docs/plans/editor/e1-editing-foundation.md#6-redrawing-after-an-edit)) |
|
|
233
300
|
|
|
234
301
|
"Time to sharp" runs from the last scroll or zoom event to the moment every visible page turned sharp. Memory is the page's JS heap (Chromium only), the document Web Worker's WebAssembly memories (`client.memory()`), the bitmap cache and the canvases' pixels. Firefox and WebKit have no Long Tasks API, so their longest task is the longest gap between frames, which is an upper bound.
|
|
235
302
|
|
|
@@ -237,7 +304,9 @@ It writes `target/viewer-perf/<browser>.json` and `summary.md`, and never fails
|
|
|
237
304
|
|
|
238
305
|
**The reference machine.** The official numbers are the nightly lane's, from the Mac that hosts the self-hosted runners, with nothing else running on it. Each report ends with the machine it ran on (CPU, cores, memory, OS). Numbers from other machines, CI containers included, are relative: compare them with each other, not with the targets.
|
|
239
306
|
|
|
240
|
-
**First measurements and tuning.** The first full run (Chromium 153 on a 4-core Linux container, 2026-11-05) came in under every scrolling and memory target, a first reading until the reference machine's numbers arrive. It had no long task while scrolling, pages were sharp 0–17 ms after a scroll stopped and 33 ms after a fling, memory peaked at 302 MB, the second pass ended 0.5 % above the first, and no cancelled render painted. So the defaults above stay: a margin of 2 pages or one viewport, a pool of 12 canvases and 64 million pixels, a 256 MB bitmap cache, 512 CSS px tiles and a 150 ms settle. It missed one target and found one slow spot, both for [C7c](../../docs/todos/
|
|
307
|
+
**First measurements and tuning.** The first full run (Chromium 153 on a 4-core Linux container, 2026-11-05) came in under every scrolling and memory target, a first reading until the reference machine's numbers arrive. It had no long task while scrolling, pages were sharp 0–17 ms after a scroll stopped and 33 ms after a fling, memory peaked at 302 MB, the second pass ended 0.5 % above the first, and no cancelled render painted. So the defaults above stay: a margin of 2 pages or one viewport, a pool of 12 canvases and 64 million pixels, a 256 MB bitmap cache, 512 CSS px tiles and a 150 ms settle. It missed one target and found one slow spot, both for [C7c](../../docs/todos/done/C7c-performance-gates-and-launch.md):
|
|
241
308
|
|
|
242
309
|
- **The cold first page took 4.2 s on Fast 4G (target 3 s).** The download alone takes about 3.7 s at Fast 4G's 1 MB/s: PDFium's WebAssembly (2.2 MB gzipped), Lamina PDF's base module (0.4 MB) and the 1 MB file.
|
|
310
|
+
|
|
311
|
+
**Cold first page (C7d, 2026-11-10).** By then the cold page took 5.2 s, and a trace of the worker's requests showed why. Lamina PDF's base module and PDFium downloaded one after the other; the link layer's first call needed the extract module (710 KB), and the worker answers one call at a time, so the first page's render waited for that download; and the harness fetched the whole 1 MB file over the throttled network, which a file chosen on the device never costs. Now `ViewerClient.create` compiles PDFium on the page while the worker loads its base module, the text, structure, reading-order and link calls wait for the open document's first drawn page (2 s at most), and the extract module loads after it. A file on the device paints its first page in 3.3 s, a linked 1 MB file in 4.75 s (opening reads most of this file, whose page objects are spread through it). The release gate holds the device case to the target plus its margin; the linked case is reported.
|
|
243
312
|
- **Zooming in took about 1.2 s to sharp, with a 317 ms frame.** Zooming out took about 300 ms. The plan sets no zoom target.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RenderRequest } from "@laminapdf/render";
|
|
2
|
+
import { type Op } from "@laminapdf/sdk";
|
|
2
3
|
import { type WorkerInitOptions, type WorkerWasmSource } from "@laminapdf/sdk/worker";
|
|
3
4
|
import type { FindOptions } from "./find/fold.js";
|
|
4
|
-
import { type BackendInit, type LoadProgress, type MethodName, type NetworkRecord, type OpenedDocument, type Params, type PolicyViolation, type Priority, type RenderedBitmap, type Result, type SourceInit, type ViewerOpenOptions, type WorkerNetworkLog } from "./protocol.js";
|
|
5
|
+
import { type BackendInit, type EditHistory, type EditResult, type LoadProgress, type MethodName, type NetworkRecord, type OpenedDocument, type Params, type PolicyViolation, type PrintOptions, type Priority, type RenderedBitmap, type Result, type SourceInit, type ViewerOpenOptions, type WorkerNetworkLog } from "./protocol.js";
|
|
5
6
|
/** `ViewerClient.create`'s options: the backend, Lamina PDF's WebAssembly, the timeouts, the worker and a signal to stop starting. */
|
|
6
7
|
export interface ViewerOptions {
|
|
7
8
|
/** The render engine; PDFium needs its `.wasm` URL from your bundler (`pdfium.wasm?url`). */
|
|
@@ -69,11 +70,21 @@ export interface ViewerEvents {
|
|
|
69
70
|
crashed: {
|
|
70
71
|
readonly reason: string;
|
|
71
72
|
};
|
|
72
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* A new worker serves again (and has reopened the document, unless it needed a password).
|
|
75
|
+
* `edits` says what became of the edit session's steps: `restored` (applied again, the same
|
|
76
|
+
* history), `lost` (the file is as it was opened), or absent when there were none.
|
|
77
|
+
*/
|
|
73
78
|
restarted: {
|
|
74
79
|
readonly reason: RestartReason;
|
|
75
80
|
readonly reopened: boolean;
|
|
81
|
+
readonly edits?: "restored" | "lost";
|
|
76
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* An edit, undo or redo was made (the document's semantic dirty state is `history.unsaved`):
|
|
85
|
+
* the pages it touched, as they are now, and the edit session's history.
|
|
86
|
+
*/
|
|
87
|
+
edit: EditResult;
|
|
77
88
|
/** The restarted worker can't reopen the document without its password: open it again. */
|
|
78
89
|
"password-required": Record<string, never>;
|
|
79
90
|
/** A restart failed (the backend couldn't load again, …): the client serves nothing until `stop()` works. */
|
|
@@ -115,6 +126,26 @@ export declare class ViewerClient {
|
|
|
115
126
|
get document(): OpenedDocument | undefined;
|
|
116
127
|
/** Closes the document in the worker (the worker stays, ready for another `open`). */
|
|
117
128
|
close(): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Applies `ops` to the open document as one transaction (`@laminapdf/sdk`'s ops, such as
|
|
131
|
+
* `rotatePage`), and draws the pages it touched from the edited file from then on. The edit
|
|
132
|
+
* session records it as one step named `label` ("Rotate page 5"), which undo takes back; an
|
|
133
|
+
* edit that changed nothing records none. Resolves with those pages as they now are and the
|
|
134
|
+
* history; `document` gives their new sizes too. Pass the pages to `viewer.invalidate`, or call
|
|
135
|
+
* `viewer.applyEdit`, which does both. A failed edit changes nothing. A restarted worker opens
|
|
136
|
+
* the file and applies the session's steps again (`restarted`'s `edits`).
|
|
137
|
+
*/
|
|
138
|
+
applyEdit(ops: readonly Op[], options?: ViewerCallOptions & {
|
|
139
|
+
readonly label?: string;
|
|
140
|
+
}): Promise<EditResult>;
|
|
141
|
+
/** Undoes the newest step of the edit session; the pages it touched are drawn as they were. */
|
|
142
|
+
undoEdit(options?: ViewerCallOptions): Promise<EditResult>;
|
|
143
|
+
/** Redoes the oldest undone step. */
|
|
144
|
+
redoEdit(options?: ViewerCallOptions): Promise<EditResult>;
|
|
145
|
+
/** Undoes or redoes steps until `position` of the history's steps are done, drawing once. */
|
|
146
|
+
goToStep(position: number, options?: ViewerCallOptions): Promise<EditResult>;
|
|
147
|
+
/** The edit session's history, read from the worker (after a failed call, which answers none). */
|
|
148
|
+
editHistory(options?: ViewerCallOptions): Promise<EditHistory>;
|
|
118
149
|
/**
|
|
119
150
|
* Every request the document worker has made, and had blocked, since it started: what its
|
|
120
151
|
* `network` and `csp-violation` events reported, including those sent before anyone listened.
|
|
@@ -129,6 +160,11 @@ export declare class ViewerClient {
|
|
|
129
160
|
* module, loaded by default). At `background` priority unless `options` says otherwise.
|
|
130
161
|
*/
|
|
131
162
|
pageText(index: number, options?: ViewerCallOptions): Promise<import("./protocol.js").PageTextData>;
|
|
163
|
+
/**
|
|
164
|
+
* Page `index`'s text blocks in reading order (Lamina PDF's extraction), for copy as paragraph.
|
|
165
|
+
* At `background` priority unless `options` says otherwise.
|
|
166
|
+
*/
|
|
167
|
+
readingOrder(index: number, options?: ViewerCallOptions): Promise<import("./protocol.js").ReadingBlock[]>;
|
|
132
168
|
/**
|
|
133
169
|
* The matches of `query` on each of `pages`, at `background` priority unless `options` says
|
|
134
170
|
* otherwise, so renders run first. The find controller calls it a few pages at a time.
|
|
@@ -136,6 +172,11 @@ export declare class ViewerClient {
|
|
|
136
172
|
find(query: string, find: FindOptions, pages: readonly number[], options?: ViewerCallOptions): Promise<import("./protocol.js").FindPageResult[]>;
|
|
137
173
|
/** Whether the open document says it is tagged, and its structure tree when it has one. */
|
|
138
174
|
structure(options?: ViewerCallOptions): Promise<import("./protocol.js").StructureData>;
|
|
175
|
+
/**
|
|
176
|
+
* The open document for reader mode: its blocks in the structure tree's order, figures,
|
|
177
|
+
* formulas and tables as crops with their alternate text, or why its tree can't be trusted.
|
|
178
|
+
*/
|
|
179
|
+
reflow(options?: ViewerCallOptions): Promise<import("@laminapdf/sdk").Reflow>;
|
|
139
180
|
/**
|
|
140
181
|
* The open document's outline (bookmarks): top-level items, each with its children and where
|
|
141
182
|
* it goes as a page and a page-space view; empty without one. Read when first asked for.
|
|
@@ -143,6 +184,23 @@ export declare class ViewerClient {
|
|
|
143
184
|
outline(options?: ViewerCallOptions): Promise<import("@laminapdf/wasm/text").OutlineNodeJs[]>;
|
|
144
185
|
/** Where the named destination `name` goes, or `undefined` when the document has no such name. */
|
|
145
186
|
namedDestination(name: string, options?: ViewerCallOptions): Promise<import("@laminapdf/wasm/text").TargetJs | undefined>;
|
|
187
|
+
/**
|
|
188
|
+
* What a properties view shows about the open document (`@laminapdf/sdk`'s `properties()`,
|
|
189
|
+
* read in the worker). It reads every object once: ask when the view opens.
|
|
190
|
+
*/
|
|
191
|
+
properties(options?: ViewerCallOptions): Promise<import("@laminapdf/wasm/text").PropertiesJs>;
|
|
192
|
+
/**
|
|
193
|
+
* Page `index` drawn for printing at `print.dpi` with annotations as their print flags say, as
|
|
194
|
+
* a PNG, at `background` priority unless `options` says otherwise, so the pages in view draw first.
|
|
195
|
+
*/
|
|
196
|
+
printPage(index: number, print: PrintOptions, options?: ViewerCallOptions): Promise<import("./protocol.js").PrintedPage>;
|
|
197
|
+
/**
|
|
198
|
+
* Every byte of the open remote file as it was opened, fetched where the block cache lacks
|
|
199
|
+
* them. A local file's bytes are the page's own: it never asks.
|
|
200
|
+
*/
|
|
201
|
+
fileBytes(options?: ViewerCallOptions): Promise<Uint8Array<ArrayBufferLike>>;
|
|
202
|
+
/** Page `index`'s links, in annotation order, each with what following it does (the link layer asks as pages mount). */
|
|
203
|
+
pageLinks(index: number, options?: ViewerCallOptions): Promise<readonly import("@laminapdf/wasm/text").PageLinkJs[]>;
|
|
146
204
|
/**
|
|
147
205
|
* Renders `request` as an ImageBitmap (close it when done). Identical requests in flight are
|
|
148
206
|
* drawn once. An aborted render rejects with `AbortError` and never paints.
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|