@inkbar/core 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,70 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Redactr Platforms Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Third-party notices
26
+
27
+ The toolbar icons in src/dom/viewer/icons.ts (Hand, MousePointer2, Highlighter, Square,
28
+ MessageSquare, ZoomIn and ZoomOut) are copied from Lucide (https://lucide.dev), under the ISC
29
+ licence:
30
+
31
+ ISC License
32
+
33
+ Copyright (c) 2026 Lucide Icons and Contributors
34
+
35
+ Permission to use, copy, modify, and/or distribute this software for any
36
+ purpose with or without fee is hereby granted, provided that the above
37
+ copyright notice and this permission notice appear in all copies.
38
+
39
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
40
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
41
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
42
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
43
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
44
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
45
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
46
+
47
+ Of these, Square, ZoomIn and ZoomOut are derived from the Feather project, under the MIT
48
+ licence:
49
+
50
+ The MIT License (MIT) (for the icons listed above)
51
+
52
+ Copyright (c) 2013-present Cole Bemis
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining a copy
55
+ of this software and associated documentation files (the "Software"), to deal
56
+ in the Software without restriction, including without limitation the rights
57
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
58
+ copies of the Software, and to permit persons to whom the Software is
59
+ furnished to do so, subject to the following conditions:
60
+
61
+ The above copyright notice and this permission notice shall be included in all
62
+ copies or substantial portions of the Software.
63
+
64
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
65
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
66
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
67
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
68
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
69
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
70
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,219 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/inkbar/inkbar/main/.github/assets/inkbar-mark-on-dark.svg">
4
+ <img src="https://raw.githubusercontent.com/inkbar/inkbar/main/.github/assets/inkbar-mark-on-light.svg" alt="Inkbar" width="72" height="72">
5
+ </picture>
6
+ </p>
2
7
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
8
+ <h1 align="center">@inkbar/core</h1>
9
+
10
+ <p align="center">
11
+ The engine under Inkbar: documents, rendering, annotations and tools, with no framework dependency.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://inkbar.dev">Website</a> ·
16
+ <a href="https://inkbar.dev/docs/">Docs</a> ·
17
+ <a href="https://sandbox.inkbar.dev">Sandbox</a> ·
18
+ <a href="https://github.com/inkbar/inkbar">GitHub</a>
19
+ </p>
20
+
21
+ ---
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 toolbar, note threads and redaction review, into any element in a plain TypeScript app.
24
+
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
+
27
+ - `@inkbar/core` holds the document model and annotation store. It is safe to import during server rendering.
28
+ - `@inkbar/core/dom` renders pages, text layers and annotation layers into the DOM.
29
+ - `@inkbar/core/vite` is a Vite plugin that serves the PDF.js assets.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ pnpm add @inkbar/core
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```ts
40
+ import { openDocument } from '@inkbar/core'
41
+ import { assets } from 'virtual:inkbar-assets' // from the Vite plugin, see below
42
+
43
+ const doc = await openDocument(file, { assets })
44
+ await doc.ready
45
+
46
+ doc.annotations.add({ type: 'highlight', page: 0, quads })
47
+ const json = doc.exportAnnotations('json')
48
+ ```
49
+
50
+ `file` can be a `File`, `Blob`, `Uint8Array`, a URL string, or `{ url, key }` when the URL is signed and changes between loads. `quads` is a list of quads in PDF user space.
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.
53
+
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
+
56
+ ### Without a framework
57
+
58
+ `createViewer` opens a document and renders a complete viewer into an element: a toolbar with tools, zoom and page controls, plus a note thread and a suggested-redaction card. It uses the same markup as the framework packages, so `@inkbar/theme` styles it.
59
+
60
+ ```ts
61
+ import { createViewer } from '@inkbar/core/dom'
62
+ import '@inkbar/theme/theme.css'
63
+
64
+ const viewer = createViewer(el, { source: file, assets, zoom: 'fit-width' })
65
+
66
+ const undo = document.createElement('button')
67
+ undo.textContent = 'Undo'
68
+ undo.onclick = () => viewer.document.get()?.history.undo()
69
+ viewer.toolbar?.append(undo) // buttons join the toolbar's arrow-key order
70
+
71
+ viewer.open(otherFile) // destroys the current document; the latest call wins
72
+ viewer.open(lockedFile, { password }) // this password applies to this load only
73
+ viewer.destroy()
74
+ ```
75
+
76
+ It takes the [`mountViewer` options](#mounting-a-viewer) except `container` and `document`, plus `password`, `defaults`, `history`, `onReady` and `onError`. Set `toolbar`, `notes` or `suggestions` to `false` to leave that part out. `viewer.status`, `viewer.document` and `viewer.engine` are readables. A password you pass to `open` replaces the `createViewer` one for that load. 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"`.
77
+
78
+ `createViewer` reads the theme's colours from the element you pass, so that element needs to be in the page when you call it.
79
+
80
+ ### Mounting a viewer
81
+
82
+ `mountViewer` wires the viewport, page layers, tools and zoom and page keys onto a scroll container. You render the chrome around it.
83
+
84
+ ```ts
85
+ import { mountViewer } from '@inkbar/core/dom'
86
+
87
+ const engine = mountViewer({
88
+ container, // a focusable, scrollable element
89
+ document: doc,
90
+ zoom: 'fit-width',
91
+ onPageError: (page, error) => console.warn(page, error),
92
+ })
93
+ engine.tools.set('highlight')
94
+ engine.rebuild({ gap: 32 }) // construction-only options; zoom, page, tool and scroll position carry over
95
+ engine.destroy()
96
+ ```
97
+
98
+ Options:
99
+
100
+ - `container` and `document`, both required.
101
+ - `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`.
102
+ - `gap`, `buffer` and `customAnnotations` are fixed for a build. Change them with `engine.rebuild`.
103
+ - `onActivate(id)` fires when the user activates an annotation.
104
+ - `onBuild(viewport, tools)` fires after every build, the first one included, before `mountViewer` returns. Subscribe to the new viewport and tool controller here.
105
+ - `onPageMount(page, mount, host)` and `onPageUnmount(page)` fire as pages enter and leave the mounted range.
106
+ - `onPageError(page, error)` reports a page whose canvas or text layer failed, and `onPageError(page, null)` once every failed layer renders again. Unmounting a failed page emits no `null`, so clear any per-page error in `onPageUnmount`.
107
+
108
+ The viewport mounts pages synchronously, so a build's page callbacks fire before its `onBuild`. During a rebuild, `engine.viewport` and `engine.tools` still point at the destroyed objects while those callbacks run. Read the arguments instead, or wait for `onBuild`.
109
+
110
+ ## Annotation changes
111
+
112
+ `doc.annotations.on('change', fn)`, and `onChange(next, change)` in controlled mode, describe each mutation:
113
+
114
+ - `{ kind: 'add', annotation }`
115
+ - `{ kind: 'update', id, before, after }`
116
+ - `{ kind: 'remove', annotation, removed }`. Removing a note also removes its replies, so `removed` lists everything that went: `annotation` first, then the replies. A host that syncs by change should delete every id in `removed`.
117
+ - `{ kind: 'replace', reason }` for an import, a history step or a controlled sync. Read the whole list.
118
+
119
+ `add` throws if `input.id` is already in the list. Locked annotations refuse changes to their geometry, `page` and `contents`.
120
+
121
+ `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.
122
+
123
+ ## Tools and keyboard
124
+
125
+ `createToolController` from `@inkbar/core/dom` routes pointer and key events on the viewport to the active tool: `view`, `select`, `highlight`, `redact-area` or `note`. Every tool works from the keyboard as well as the pointer.
126
+
127
+ | Where focus is | Key | Effect |
128
+ | ---------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------- |
129
+ | Viewport, `highlight` tool | `Enter` | Turns the text selection into a highlight |
130
+ | Viewport, `redact-area` tool | `Enter` | Turns the text selection into text redactions |
131
+ | Viewport, `note` tool | `Enter` | Places a note at the centre of the current page and focuses it |
132
+ | Viewport | `+` or `=`, `-` | Zoom in, zoom out |
133
+ | Viewport | `Home`, `End`, `PageUp`, `PageDown` | First, last, previous, next page |
134
+ | Annotation | `Enter` or `Space` | Selects and activates it |
135
+ | Annotation | `ArrowUp`, `ArrowDown` | Previous or next annotation on the page, in reading order |
136
+ | Annotation | `Shift` + arrow | Moves it 1 pt (`Shift` + `Alt` + arrow: 10 pt) |
137
+ | Annotation | `Ctrl` or `Cmd` + `Shift` + arrow | Resizes an area redaction, text box or shape by 1 pt. Right and Down grow it, Left and Up shrink it |
138
+ | Annotation | `Delete` or `Backspace` | Removes it |
139
+ | Annotation | `Escape` | Clears the selection |
140
+
141
+ The tool controller and the overlay handle the tool and annotation keys. The zoom and page keys (`+`, `=`, `-`, `Home`, `End`, `PageUp`, `PageDown`) come from `attachViewportKeys`, which the tool controller does not install. Attach it to the same container:
142
+
143
+ ```ts
144
+ import { attachViewportKeys } from '@inkbar/core/dom'
145
+
146
+ const detach = attachViewportKeys(container, {
147
+ viewport,
148
+ pageCount: () => doc.pages.length,
149
+ })
150
+ // later: detach()
151
+ ```
152
+
153
+ On a focused annotation, `Enter` or `Space` selects it and calls `onActivate`; what activation opens is up to the host.
154
+
155
+ 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.
156
+
157
+ `redact-area` draws an area mark when you drag. To redact text instead, select the text with the `view` 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.
158
+
159
+ With the `select` tool, a selected area redaction, text box or shape shows four corner handles (`.ink-handle[data-handle="nw|ne|sw|se"]`). Dragging one resizes it; the store records a single update on release. Drag the annotation itself to move it.
160
+
161
+ ## Serving PDF.js assets
162
+
163
+ PDF.js needs a worker script plus directories of CMaps, standard fonts, wasm decoders and ICC profiles. Inkbar loads them from URLs you pass as the `assets` option. The option has no default, because the right URLs depend on how you deploy your app.
164
+
165
+ ### With Vite
166
+
167
+ Add the plugin:
168
+
169
+ ```ts
170
+ // vite.config.ts
171
+ import { inkbar } from '@inkbar/core/vite'
172
+ import { defineConfig } from 'vite'
173
+
174
+ export default defineConfig({
175
+ plugins: [inkbar()],
176
+ })
177
+ ```
178
+
179
+ Then import the URLs from the virtual module:
180
+
181
+ ```ts
182
+ import { assets } from 'virtual:inkbar-assets'
183
+ ```
184
+
185
+ In development the plugin serves the files from `node_modules`. In a build it copies them into `<outDir>/inkbar/`. The URLs respect your `base`. With a relative `base` such as `./`, the plugin resolves the URLs at runtime from the location of your bundle, so the build works from any path. Pass `inkbar({ assetsDir: 'pdf' })` to use a different directory name.
186
+
187
+ For TypeScript, reference the module declaration once, for example in `env.d.ts`:
188
+
189
+ ```ts
190
+ /// <reference types="@inkbar/core/vite/client" />
191
+ ```
192
+
193
+ ### Without Vite
194
+
195
+ Copy these from the installed `pdfjs-dist` package to a path your server exposes:
196
+
197
+ - `build/pdf.worker.min.mjs`
198
+ - `cmaps/`
199
+ - `standard_fonts/`
200
+ - `wasm/`
201
+ - `iccs/` (optional)
202
+
203
+ Pass their URLs. Directory URLs end in `/`:
204
+
205
+ ```ts
206
+ const assets = {
207
+ worker: '/pdfjs/pdf.worker.min.mjs',
208
+ cMaps: '/pdfjs/cmaps/',
209
+ standardFonts: '/pdfjs/standard_fonts/',
210
+ wasm: '/pdfjs/wasm/',
211
+ icc: '/pdfjs/iccs/',
212
+ }
213
+ ```
214
+
215
+ Use the `pdfjs-dist` version that `@inkbar/core` depends on, so the worker matches the library.
216
+
217
+ ## Licence
218
+
219
+ [MIT](https://github.com/inkbar/inkbar/blob/main/LICENSE). From the team behind [Redactr](https://redactr.io).