@inkbar/core 0.2.0 → 0.4.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/README.md +53 -10
- package/dist/dom.d.mts +715 -26
- package/dist/dom.d.mts.map +1 -1
- package/dist/dom.mjs +5796 -1424
- package/dist/dom.mjs.map +1 -1
- package/dist/index.d.mts +123 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/dist/signatures-BHYKTwYE.d.mts +2280 -0
- package/dist/signatures-BHYKTwYE.d.mts.map +1 -0
- package/dist/signatures-DF2VmNVa.mjs +3922 -0
- package/dist/signatures-DF2VmNVa.mjs.map +1 -0
- package/package.json +4 -2
- package/schema.json +839 -102
- package/dist/document-Cywnv8Sz.d.mts +0 -1183
- package/dist/document-Cywnv8Sz.d.mts.map +0 -1
- package/dist/threads-BtvtrAhL.mjs +0 -1878
- package/dist/threads-BtvtrAhL.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
`@inkbar/core` opens PDFs, renders pages with PDF.js and keeps annotations as typed data. The Vue, React and Svelte packages build on it. You can also use it on its own: `createViewer` puts a complete viewer, with
|
|
23
|
+
`@inkbar/core` opens PDFs, renders pages with PDF.js and keeps annotations as typed data. The Vue, React and Svelte packages build on it. You can also use it on its own: `createViewer` puts a complete viewer, with comment threads, redaction review and controls to build your toolbars from, into any element in a plain TypeScript app.
|
|
24
24
|
|
|
25
25
|
Every coordinate is stored in PDF user space (points, origin bottom-left, before `/Rotate`), so annotations stay put whatever the zoom, rotation or screen.
|
|
26
26
|
|
|
@@ -47,15 +47,15 @@ doc.annotations.add({ type: 'highlight', page: 0, quads })
|
|
|
47
47
|
const json = doc.exportAnnotations('json')
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
`file` can be a `File`, `Blob`, `Uint8Array`, a URL string,
|
|
50
|
+
`file` can be a `File`, `Blob`, `Uint8Array`, a URL string, `{ url, key }` when the URL is signed and changes between loads, or `{ data, key }` for bytes you want to identify. `quads` is a list of quads in PDF user space.
|
|
51
51
|
|
|
52
|
-
Writing annotations back into the PDF needs `@inkbar/engine`, which is not released yet. Until then `save()` rejects with `EngineUnavailable`. Persist `exportAnnotations('json')` instead and re-import it on the next open.
|
|
52
|
+
Writing annotations back into the PDF needs `@inkbar/engine`, which is not released yet. Until then `save()` rejects with `EngineUnavailable`. Persist `exportAnnotations('json')` instead and re-import it on the next open. `doc.key` stays the same between opens only when you open with a key: a URL string, `{ url, key }` or `{ data, key }`. A `File`, `Blob` or raw bytes get a new random key each time.
|
|
53
53
|
|
|
54
54
|
The DOM layer (`@inkbar/core/dom`) provides the viewport, canvas renderer, text layer, overlay and tools for hosts without a framework package. Vue hosts should use [`@inkbar/vue`](https://github.com/inkbar/inkbar/tree/main/packages/vue), React hosts [`@inkbar/react`](https://github.com/inkbar/inkbar/tree/main/packages/react), Svelte hosts [`@inkbar/svelte`](https://github.com/inkbar/inkbar/tree/main/packages/svelte).
|
|
55
55
|
|
|
56
56
|
### Without a framework
|
|
57
57
|
|
|
58
|
-
`createViewer` opens a document and renders it into an element, with a
|
|
58
|
+
`createViewer` opens a document and renders it into an element, with a comment thread popover for notes and highlights and a suggested-redaction card. The toolbars are yours: build them from the controls below, which use the same markup as the framework packages, so `@inkbar/theme` styles them.
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
61
|
import {
|
|
@@ -73,7 +73,11 @@ import '@inkbar/theme/theme.css'
|
|
|
73
73
|
const viewer = createViewer(el, { source: file, assets, zoom: 'fit-width' })
|
|
74
74
|
|
|
75
75
|
const tools = createToolbar()
|
|
76
|
-
tools.append(
|
|
76
|
+
tools.append(
|
|
77
|
+
createToolButton(viewer, 'select-text'),
|
|
78
|
+
createToolButton(viewer, 'select'),
|
|
79
|
+
createToolButton(viewer, 'highlight')
|
|
80
|
+
)
|
|
77
81
|
|
|
78
82
|
const undo = document.createElement('button')
|
|
79
83
|
undo.textContent = 'Undo'
|
|
@@ -97,7 +101,7 @@ viewer.open(lockedFile, { password }) // this password applies to this load only
|
|
|
97
101
|
viewer.destroy()
|
|
98
102
|
```
|
|
99
103
|
|
|
100
|
-
It takes the [`mountViewer` options](#mounting-a-viewer) except `container` and `document`, plus `password`, `defaults`, `history`, `onReady` and `onError`. Set `
|
|
104
|
+
It takes the [`mountViewer` options](#mounting-a-viewer) except `container` and `document`, plus `source` and `assets` (both required), `password`, `defaults`, `history`, `onReady` and `onError`. Set `threads` or `suggestions` to `false` to leave that part out. `viewer.status`, `viewer.document`, `viewer.engine`, `viewer.viewport` and `viewer.tools` are readables; the last two change when the engine rebuilds. A password you pass to `open` replaces the `createViewer` one for that load. The controls are `createToolButton`, `createUndo`, `createRedo`, `createZoomIn`, `createZoomOut`, `createZoomSelect`, `createPageInput`, `createPageCount`, `createPrevPage` and `createNextPage`. Each one disables itself while no document is ready and lives as long as its viewer. Buttons show an icon from [`@inkbar/icons`](https://github.com/inkbar/inkbar/tree/main/packages/icons). Replace their children with your own icon and the accessible name stays; `createIcon` draws any `@inkbar/icons` icon for your own buttons. `createToolbar` gives one Tab stop to all its buttons, with arrow keys between them, and `createToolbarSeparator` divides groups of controls in it. An input, select or textarea you add keeps its arrow keys and its own `tabindex`: it is a Tab stop unless you give it `tabindex="-1"`.
|
|
101
105
|
|
|
102
106
|
`createViewer` reads the theme's colours from the element you pass, so that element needs to be in the page when you call it.
|
|
103
107
|
|
|
@@ -124,6 +128,8 @@ Options:
|
|
|
124
128
|
- `container` and `document`, both required.
|
|
125
129
|
- `zoom` (a number, `'fit-width'` or `'fit-page'`; default `'fit-width'`), `page`, `mode`, `rotation` and `tool` set the starting view. Change them later through `engine.viewport` and `engine.tools`.
|
|
126
130
|
- `gap`, `buffer` and `customAnnotations` are fixed for a build. Change them with `engine.rebuild`.
|
|
131
|
+
- `toolOptions` holds the tools' options, such as the shape kind. Pass one from `createToolOptions()` and keep it to carry the options over to the next document; `createViewer` keeps one for its whole life. The tool style (colours, line style, text style) belongs to the document: each new document starts from the theme's defaults.
|
|
132
|
+
- `signatures` is where saved signatures live (see [Signatures](#signatures)); `signatureFonts` lists the handwriting fonts a typed signature offers.
|
|
127
133
|
- `onActivate(id)` fires when the user activates an annotation.
|
|
128
134
|
- `onBuild(viewport, tools)` fires after every build, the first one included, before `mountViewer` returns. Subscribe to the new viewport and tool controller here.
|
|
129
135
|
- `onPageMount(page, mount, host)` and `onPageUnmount(page)` fire as pages enter and leave the mounted range.
|
|
@@ -142,17 +148,38 @@ The viewport mounts pages synchronously, so a build's page callbacks fire before
|
|
|
142
148
|
|
|
143
149
|
`add` throws if `input.id` is already in the list. Locked annotations refuse changes to their geometry, `page` and `contents`.
|
|
144
150
|
|
|
145
|
-
`doc.history.batch(fn)` records everything `fn` changes as one undo step, so a host that adds many annotations at once can undo them together. Change events still fire once per annotation. `fn` must be synchronous, and nested calls join the outer batch.
|
|
151
|
+
`doc.history.batch(fn)` records everything `fn` changes as one undo step, so a host that adds many annotations at once can undo them together. Change events still fire once per annotation. `fn` must be synchronous, and nested calls join the outer batch. In controlled mode `doc.history` records nothing; the framework packages' `useHistory().batch` works in both modes.
|
|
152
|
+
|
|
153
|
+
## PDF names in annotations
|
|
154
|
+
|
|
155
|
+
Values taken from PDF names use PDF's spelling: `OpenArrow` and `ROpenArrow` for line endings, `Comment` and `NewParagraph` for note icons, `FreeTextCallout` and `LineArrow` for intents. Other valid PDF names (no spaces, none of `( ) < > [ ] { } / %`) are kept and written back unchanged, so `icon: 'Check'` survives an import and export. `LINE_ENDINGS`, `NOTE_ICONS` and `INTENTS` list the names Inkbar knows, and `isKnown(list, name)` tests one. A line ending Inkbar does not know draws as `None`, and every note icon draws the same icon.
|
|
156
|
+
|
|
157
|
+
## Callouts and speech bubbles
|
|
158
|
+
|
|
159
|
+
A free-text callout is stored as PDF and XFDF store it: `intent: 'FreeTextCallout'`, a `rect` that holds the text box and the leader line together, a `fringe` (`{ left, top, right, bottom }`, PDF `/RD`) that insets `rect` to the text box, and a `callout` of two or three points running from the tip, through an optional bend, to where the line meets the box. `head` is the ending at the tip.
|
|
160
|
+
|
|
161
|
+
A speech bubble is a standard callout plus Inkbar's `cornerRadius`. Inkbar draws it as one rounded outline with a wedge to the tip. Other viewers ignore `cornerRadius` and draw a box with a leader line.
|
|
162
|
+
|
|
163
|
+
A callout's corner handles resize its text box: the tip and bend stay, and the point on the box moves with the edge it sits on. Its tip handle moves the tip alone. Each edit refits `rect` and `fringe` in the same update, so it is one undo step.
|
|
164
|
+
|
|
165
|
+
## Limits of PDF import
|
|
166
|
+
|
|
167
|
+
Inkbar reads the annotations embedded in a PDF through PDF.js, which passes on only part of each one. Import keeps `intent` (`/IT`) for every type. Three gaps remain, each a follow-up:
|
|
168
|
+
|
|
169
|
+
- Read FreeText `/CL`, `/RD` and `/LE` from PDFs. A callout in a PDF imports with `intent: 'FreeTextCallout'` but without `callout`, `fringe` or `head`. PDF.js still paints it from the file's own appearance.
|
|
170
|
+
- Read a note's `/Name` when the note has an appearance stream. PDF.js reports such notes as `NoIcon`, so they import with the default icon, `Note`.
|
|
171
|
+
- Keep line endings Inkbar does not know. PDF.js turns them into `None`. Annotations loaded from JSON keep them.
|
|
146
172
|
|
|
147
173
|
## Tools and keyboard
|
|
148
174
|
|
|
149
|
-
`createToolController` from `@inkbar/core/dom` routes pointer and key events on the viewport to the active tool: `
|
|
175
|
+
`createToolController` from `@inkbar/core/dom` routes pointer and key events on the viewport to the active tool: `select-text`, `select`, `highlight`, `redact-area`, `sketch`, `draw`, `shape`, `freetext`, `signature` or `note`. Highlights, text redactions, notes and signatures can be made from the keyboard. Area redactions, sketches and drawings need a pointer.
|
|
150
176
|
|
|
151
177
|
| Where focus is | Key | Effect |
|
|
152
178
|
| ---------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
153
179
|
| Viewport, `highlight` tool | `Enter` | Turns the text selection into a highlight |
|
|
154
180
|
| Viewport, `redact-area` tool | `Enter` | Turns the text selection into text redactions |
|
|
155
181
|
| Viewport, `note` tool | `Enter` | Places a note at the centre of the current page and focuses it |
|
|
182
|
+
| Viewport, `signature` tool | `Enter` | Places the chosen signature at the centre of the current page |
|
|
156
183
|
| Viewport | `+` or `=`, `-` | Zoom in, zoom out |
|
|
157
184
|
| Viewport | `Home`, `End`, `PageUp`, `PageDown` | First, last, previous, next page |
|
|
158
185
|
| Annotation | `Enter` or `Space` | Selects and activates it |
|
|
@@ -178,9 +205,25 @@ On a focused annotation, `Enter` or `Space` selects it and calls `onActivate`; w
|
|
|
178
205
|
|
|
179
206
|
Locked annotations, such as those embedded in the file, ignore move, resize and delete. Without a text selection, `Enter` in the `highlight` and `redact-area` tools does nothing.
|
|
180
207
|
|
|
181
|
-
`redact-area` draws an area mark when you drag. To redact text instead, select the text with the `
|
|
208
|
+
`redact-area` draws an area mark when you drag, even from a highlight. To redact text instead, select the text with the `select-text` tool, switch to `redact-area`, then click inside the selection or press `Enter`. The tool keeps the press from starting a new text selection, so it cannot select text itself.
|
|
209
|
+
|
|
210
|
+
`sketch` draws freehand ink, one annotation per stroke. A stroke that looks like a line, an arrow, a rectangle, an ellipse, a polygon or a star becomes that shape, in the shape style. A click, or a stroke under 4 CSS px across, stays ink. One undo turns the shape back into the stroke, and a second removes the stroke. Draw each shape in one stroke: for an arrow, draw the shaft, then the head, without lifting. The recogniser is exported from `@inkbar/core` for your own input: `recogniseStroke(points)` takes user-space points and returns the shape with a score from 0 to 1, or `undefined` when the points fit no shape. A stroke counts as a shape at `RECOGNISE_THRESHOLD` (0.8) or above.
|
|
211
|
+
|
|
212
|
+
The `shape` tool's `loupe` kind adds a loupe: a circle that shows the page under its centre at `magnification` times the size (2 by default), with a rim in the shape style's border colour and width. A drag sizes it, always as a circle, and a click places one 72 pt across. It needs a pointer: `Enter` places no loupe. It magnifies the page itself, not the annotations under it, and redraws a moment after it moves, resizes or the zoom changes. Pass the overlay a `document` (page mounts and the viewer do) or a loupe shows only its rim.
|
|
213
|
+
|
|
214
|
+
The `shape` tool's `mask` and `ovalMask` kinds add a mask: the page is dimmed everywhere outside a rectangle or an ellipse (`shape: 'rect' | 'ellipse'`), by `dim` (0 to 1, 0.5 by default) in black. A drag sizes it, with `Shift` for a square or circle, and a click places one 72 pt across. Like the loupe it needs a pointer: `Enter` places no mask. A page has one mask: adding a second replaces the first in the same undo step, and in a controlled store in the same `onChange`. The dimming is an SVG (`svg[data-ink-mask-layer]`) between the page and the annotation overlay that never takes the pointer; the mask annotation's own element is the shape, and only a thin band along its edge takes presses. Inside the shape the page works as usual: select text, highlight it, add shapes or notes. The style controls have nothing to change on a mask.
|
|
215
|
+
|
|
216
|
+
With either select tool (`select-text` or `select`), a selected area redaction, text box, shape, loupe, mask, polyline, polygon or drawing shows four corner handles (`.ink-handle[data-handle="nw|ne|sw|se"]`), and a selected line shows one on each end (`data-handle="start|end"`). Dragging a corner resizes the annotation, scaling its points with it; `Shift` keeps its aspect ratio. Dragging a line end moves that end only. The store records a single update on release. Drag the annotation itself to move it, or any member of a multi-selection to move them all in one undo step. Text markup in the selection stays on its text unless it is the one you drag.
|
|
217
|
+
|
|
218
|
+
A press on text markup (a highlight, underline, strikeout or squiggly) does nothing at first. A click selects it; a drag selects the text under it, as a drag on the bare text would. Presses on every other annotation select it straight away.
|
|
219
|
+
|
|
220
|
+
## Signatures
|
|
221
|
+
|
|
222
|
+
A signature here is a mark on the page, not a cryptographic signature of the file. Draw one, type your name in a handwriting font, or upload a scan, and it is saved as a template. The `signature` tool places the chosen template: a click puts it about 150 pt wide, centred on the press, and `Enter` puts it at the centre of the current page. It is turned against the page's `/Rotate`, so it reads upright on the page; a view rotation turns it with the page. A drawn signature becomes `ink` and the others a `stamp` (an image), both with `subject: 'Signature'`. Both keep their aspect ratio when resized.
|
|
223
|
+
|
|
224
|
+
Your app owns the templates. Pass a `SignatureStore` (`list`, `save`, `remove`, each returning a promise) as `signatures`. `createMemorySignatureStore()` is the default, and forgets everything on reload. `createLocalSignatureStore(key)` keeps them in `localStorage` and falls back to memory where storage is blocked. When storage is full, `save` rejects and the dialog says the signature was not saved. An uploaded image is scaled to at most 1000 px on its long side before it is stored. Typed signatures use the fonts in `signatureFonts`. Load each one with `@font-face` before offering it.
|
|
182
225
|
|
|
183
|
-
|
|
226
|
+
`createSignatureMenu(viewer)` lists the saved signatures, removes them, and opens `createSignatureDialog`, which draws, types or uploads a new one and selects it.
|
|
184
227
|
|
|
185
228
|
## Serving PDF.js assets
|
|
186
229
|
|