@embedpdf/viewer-chrome 3.0.0-next.13 → 3.0.0-next.15
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 +175 -132
- package/dist/annotation-fonts-BqoLZ4Mp.js +80 -0
- package/dist/annotation-fonts-BqoLZ4Mp.js.map +1 -0
- package/dist/annotation-fonts-Dt0IQXhz.cjs +91 -0
- package/dist/annotation-fonts-Dt0IQXhz.cjs.map +1 -0
- package/dist/annotation-style--rPIZsuP.js +879 -0
- package/dist/annotation-style--rPIZsuP.js.map +1 -0
- package/dist/annotation-style-Bzvvp8-N.cjs +879 -0
- package/dist/annotation-style-Bzvvp8-N.cjs.map +1 -0
- package/dist/ar-D9XKAmDx.js +567 -0
- package/dist/ar-D9XKAmDx.js.map +1 -0
- package/dist/ar-DnEj5vbP.cjs +567 -0
- package/dist/ar-DnEj5vbP.cjs.map +1 -0
- package/dist/chrome.cjs +378 -221
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.d.cts +5 -34
- package/dist/chrome.d.ts +5 -34
- package/dist/chrome.js +377 -218
- package/dist/chrome.js.map +1 -1
- package/dist/commands-BmgxhJ8B.js +953 -0
- package/dist/commands-BmgxhJ8B.js.map +1 -0
- package/dist/commands-CYt4SFUX.cjs +1060 -0
- package/dist/commands-CYt4SFUX.cjs.map +1 -0
- package/dist/commands.cjs +12 -595
- package/dist/commands.d.cts +29 -4
- package/dist/commands.d.ts +29 -4
- package/dist/commands.js +2 -593
- package/dist/comments-panel-MUVgLCBI.js +514 -0
- package/dist/comments-panel-MUVgLCBI.js.map +1 -0
- package/dist/comments-panel-pEcQg4Vs.cjs +514 -0
- package/dist/comments-panel-pEcQg4Vs.cjs.map +1 -0
- package/dist/de-Bdr4aWzm.js +566 -0
- package/dist/de-Bdr4aWzm.js.map +1 -0
- package/dist/de-Dv6zvbnd.cjs +566 -0
- package/dist/de-Dv6zvbnd.cjs.map +1 -0
- package/dist/dialog-B5GBisKv.js +55 -0
- package/dist/dialog-B5GBisKv.js.map +1 -0
- package/dist/dialog-t8F13BL_.cjs +60 -0
- package/dist/dialog-t8F13BL_.cjs.map +1 -0
- package/dist/es-BP09DR9w.cjs +566 -0
- package/dist/es-BP09DR9w.cjs.map +1 -0
- package/dist/es-BSXz5tBv.js +566 -0
- package/dist/es-BSXz5tBv.js.map +1 -0
- package/dist/form-design-panel-B5MXTZ96.cjs +418 -0
- package/dist/form-design-panel-B5MXTZ96.cjs.map +1 -0
- package/dist/form-design-panel-vWWU9uB-.js +418 -0
- package/dist/form-design-panel-vWWU9uB-.js.map +1 -0
- package/dist/fr-BBQQn7tJ.cjs +566 -0
- package/dist/fr-BBQQn7tJ.cjs.map +1 -0
- package/dist/fr-Ckn3K9v1.js +566 -0
- package/dist/fr-Ckn3K9v1.js.map +1 -0
- package/dist/he-DRsG7bH7.cjs +567 -0
- package/dist/he-DRsG7bH7.cjs.map +1 -0
- package/dist/he-wdGU5PkU.js +567 -0
- package/dist/he-wdGU5PkU.js.map +1 -0
- package/dist/icons-BrK5iVL8.js +856 -0
- package/dist/icons-BrK5iVL8.js.map +1 -0
- package/dist/icons-x0Tc05bs.cjs +1010 -0
- package/dist/icons-x0Tc05bs.cjs.map +1 -0
- package/dist/index.cjs +3162 -5106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +636 -143
- package/dist/index.d.ts +636 -143
- package/dist/index.js +3083 -4975
- package/dist/index.js.map +1 -1
- package/dist/it-BuP4o0xQ.cjs +566 -0
- package/dist/it-BuP4o0xQ.cjs.map +1 -0
- package/dist/it-Us-VmvIu.js +566 -0
- package/dist/it-Us-VmvIu.js.map +1 -0
- package/dist/ja-Cizj0-5O.js +566 -0
- package/dist/ja-Cizj0-5O.js.map +1 -0
- package/dist/ja-DovEd6ts.cjs +566 -0
- package/dist/ja-DovEd6ts.cjs.map +1 -0
- package/dist/ko-B-qJEol2.cjs +566 -0
- package/dist/ko-B-qJEol2.cjs.map +1 -0
- package/dist/ko-DlrXliE1.js +566 -0
- package/dist/ko-DlrXliE1.js.map +1 -0
- package/dist/labels-BiM9wp9K.cjs +21 -0
- package/dist/labels-BiM9wp9K.cjs.map +1 -0
- package/dist/labels-C6_i87Oj.js +16 -0
- package/dist/labels-C6_i87Oj.js.map +1 -0
- package/dist/link-editor-CSjqPnj1.cjs +125 -0
- package/dist/link-editor-CSjqPnj1.cjs.map +1 -0
- package/dist/link-editor-SxM1nnJU.js +125 -0
- package/dist/link-editor-SxM1nnJU.js.map +1 -0
- package/dist/measurement-Bs61XWbJ.js +325 -0
- package/dist/measurement-Bs61XWbJ.js.map +1 -0
- package/dist/measurement-CcCsHPOX.cjs +326 -0
- package/dist/measurement-CcCsHPOX.cjs.map +1 -0
- package/dist/nl-0aF7M_0h.js +566 -0
- package/dist/nl-0aF7M_0h.js.map +1 -0
- package/dist/nl-D3KZDDiJ.cjs +566 -0
- package/dist/nl-D3KZDDiJ.cjs.map +1 -0
- package/dist/pl-BW--AaCI.js +566 -0
- package/dist/pl-BW--AaCI.js.map +1 -0
- package/dist/pl-DSFhAUqx.cjs +566 -0
- package/dist/pl-DSFhAUqx.cjs.map +1 -0
- package/dist/pt-C5S-8MC5.cjs +566 -0
- package/dist/pt-C5S-8MC5.cjs.map +1 -0
- package/dist/pt-DIhpU5pT.js +566 -0
- package/dist/pt-DIhpU5pT.js.map +1 -0
- package/dist/redact-confirm-B6j36TPw.js +83 -0
- package/dist/redact-confirm-B6j36TPw.js.map +1 -0
- package/dist/redact-confirm-CX_SY2ZN.cjs +83 -0
- package/dist/redact-confirm-CX_SY2ZN.cjs.map +1 -0
- package/dist/redaction-panel-BmoIl-Ll.cjs +96 -0
- package/dist/redaction-panel-BmoIl-Ll.cjs.map +1 -0
- package/dist/redaction-panel-IJDshSJI.js +96 -0
- package/dist/redaction-panel-IJDshSJI.js.map +1 -0
- package/dist/search-panel-HfIhJm1M.js +249 -0
- package/dist/search-panel-HfIhJm1M.js.map +1 -0
- package/dist/search-panel-s9IoiOtv.cjs +249 -0
- package/dist/search-panel-s9IoiOtv.cjs.map +1 -0
- package/dist/sheet-BDsa_Sup.cjs +442 -0
- package/dist/sheet-BDsa_Sup.cjs.map +1 -0
- package/dist/sheet-R6XDgyEf.js +365 -0
- package/dist/sheet-R6XDgyEf.js.map +1 -0
- package/dist/sign-dialog-CgnR7akj.cjs +155 -0
- package/dist/sign-dialog-CgnR7akj.cjs.map +1 -0
- package/dist/sign-dialog-GDqCoQhA.js +155 -0
- package/dist/sign-dialog-GDqCoQhA.js.map +1 -0
- package/dist/signature-inspector-DyO313Qf.cjs +166 -0
- package/dist/signature-inspector-DyO313Qf.cjs.map +1 -0
- package/dist/signature-inspector-okFz8do6.js +166 -0
- package/dist/signature-inspector-okFz8do6.js.map +1 -0
- package/dist/signature-maker-BhCmYyHD.js +393 -0
- package/dist/signature-maker-BhCmYyHD.js.map +1 -0
- package/dist/signature-maker-BoYa8vgA.cjs +393 -0
- package/dist/signature-maker-BoYa8vgA.cjs.map +1 -0
- package/dist/signatures-panel-B56PC6d9.cjs +300 -0
- package/dist/signatures-panel-B56PC6d9.cjs.map +1 -0
- package/dist/signatures-panel-CgnFfhFm.js +300 -0
- package/dist/signatures-panel-CgnFfhFm.js.map +1 -0
- package/dist/stamp-store-BHvRs-Do.cjs +147 -0
- package/dist/stamp-store-BHvRs-Do.cjs.map +1 -0
- package/dist/stamp-store-WuF-L3Jj.js +112 -0
- package/dist/stamp-store-WuF-L3Jj.js.map +1 -0
- package/dist/stamps-panel-B9NYGayn.cjs +231 -0
- package/dist/stamps-panel-B9NYGayn.cjs.map +1 -0
- package/dist/stamps-panel-CDO0FPyK.js +231 -0
- package/dist/stamps-panel-CDO0FPyK.js.map +1 -0
- package/dist/toolbar-CJu2wKTU.cjs +1659 -0
- package/dist/toolbar-CJu2wKTU.cjs.map +1 -0
- package/dist/toolbar-z9od3tKl.js +1564 -0
- package/dist/toolbar-z9od3tKl.js.map +1 -0
- package/dist/zh-cn-3tmMeHqv.cjs +566 -0
- package/dist/zh-cn-3tmMeHqv.cjs.map +1 -0
- package/dist/zh-cn-CxkUUBct.js +566 -0
- package/dist/zh-cn-CxkUUBct.js.map +1 -0
- package/package.json +6 -4
- package/src/styles.css +153 -89
- package/dist/commands.cjs.map +0 -1
- package/dist/commands.js.map +0 -1
- package/dist/es-DYwzfxyt.cjs +0 -293
- package/dist/es-DYwzfxyt.cjs.map +0 -1
- package/dist/es-sN6uqBgD.js +0 -293
- package/dist/es-sN6uqBgD.js.map +0 -1
package/README.md
CHANGED
|
@@ -32,18 +32,24 @@ Two kinds of surface, two deliberate semantics:
|
|
|
32
32
|
- **Registries** (`commands`, `icons`, `strings`) are **additive**: your
|
|
33
33
|
entries merge over the defaults by id; a colliding id overrides. Safe,
|
|
34
34
|
order-independent, upgrade-friendly.
|
|
35
|
-
- **Structure** (`
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
- **Structure** (`layout`) is a **value you own**: take the default, edit it,
|
|
36
|
+
or write your own. It is never merged — the edits are the layout's own
|
|
37
|
+
methods, and the layout is plain data small enough to read.
|
|
38
38
|
|
|
39
39
|
### Remove features
|
|
40
40
|
|
|
41
41
|
```tsx
|
|
42
|
-
<FullViewer engine={engine}
|
|
42
|
+
<FullViewer engine={engine} disable={['forms', 'redaction', 'tool:highlight']} />
|
|
43
|
+
<FullViewer engine={engine} readOnly={{ except: ['forms.filling'] }} />
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
`disable` takes features (`annotations`, or a part such as
|
|
47
|
+
`annotations.highlight`) and single commands (a name with a colon) in one list;
|
|
48
|
+
`readOnly` turns off every feature that changes the document, minus `except`.
|
|
49
|
+
What's off vanishes everywhere at once — toolbar, menus, overflow, shortcuts —
|
|
50
|
+
because every surface is a projection of the same command registry. Both follow
|
|
51
|
+
their props while the viewer runs, and both are only the interface: the
|
|
52
|
+
plugins keep working for your code, and `scope` is what the engine enforces.
|
|
47
53
|
|
|
48
54
|
### Add a button
|
|
49
55
|
|
|
@@ -75,89 +81,87 @@ The 90% case, end to end — one command, one icon, one string, one placement:
|
|
|
75
81
|
},
|
|
76
82
|
},
|
|
77
83
|
]}
|
|
78
|
-
|
|
79
|
-
h.addItem(base, {
|
|
80
|
-
bar: 'main',
|
|
81
|
-
section: 'end',
|
|
82
|
-
group: 'panels',
|
|
83
|
-
item: 'acme:send',
|
|
84
|
-
})
|
|
85
|
-
}
|
|
84
|
+
layout={(layout) => layout.add('acme:send', { to: 'main', group: 'panels' })}
|
|
86
85
|
/>
|
|
87
86
|
```
|
|
88
87
|
|
|
89
88
|
That one registration is a button that degrades responsively (`importance` is
|
|
90
89
|
the only responsive knob — there are no breakpoints to edit), drops into the
|
|
91
90
|
derived overflow menu on narrow screens, has a working keyboard shortcut, and
|
|
92
|
-
is localized.
|
|
93
|
-
|
|
91
|
+
is localized. A layout that names an id the viewer doesn't have logs a warning
|
|
92
|
+
with the id and the closest one that exists; an unknown icon logs a dev-mode
|
|
93
|
+
warning naming it.
|
|
94
94
|
|
|
95
|
-
`
|
|
96
|
-
|
|
95
|
+
`layout` takes a layout, or a **function** that gets the default and the
|
|
96
|
+
viewer's env (`size`, `width`, `touch`, `dir`, measured on the viewer itself)
|
|
97
|
+
and returns yours. It runs again whenever the env changes. Every edit returns a
|
|
98
|
+
new layout, so edits chain and need no imports:
|
|
97
99
|
|
|
98
100
|
```tsx
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
layout={(layout, env) =>
|
|
102
|
+
layout
|
|
103
|
+
.replace('panel:comments', { command: 'acme:chat', importance: 5 })
|
|
104
|
+
.remove('tool:squiggly', { from: 'annotate' })
|
|
105
|
+
.place('main', env.size === 'compact' ? 'bottom' : 'top')
|
|
104
106
|
}
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
- `
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- `
|
|
111
|
-
|
|
112
|
-
- `
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
- `place(bar, place)` — an edge (`'top'`, `'bottom'`, `'start'`, `'end'`, or
|
|
110
|
+
`{ edge, before | after }`), `{ float }`, `{ with: bar }` (along another bar,
|
|
111
|
+
as the mode bars ride `main`) or `{ at: 'selection' | 'annotation' }`.
|
|
112
|
+
- `addBar(id, bar)` / `removeBar(id)` — a bar with a `mode` adds that mode's
|
|
113
|
+
tab and its `mode:<id>` command; removing it removes the tab.
|
|
114
|
+
- `add(item, { to, section, group, before, after })` — `to` is a bar or a menu;
|
|
115
|
+
without a position the item starts a new group at the end of `end`.
|
|
116
|
+
- `remove(ids, { from })` — always one place; to leave something out
|
|
117
|
+
everywhere, use `disable`. A group with nothing left disappears.
|
|
118
|
+
- `replace(id, item)` — swaps an item in place, in every bar and menu.
|
|
119
|
+
- `addMenu(id, sections)`, `addPanel(id, panel)`, `panel(id, changes)`,
|
|
120
|
+
`removePanel(id)`, `tabs('always' | 'multiple' | 'never')`, and `toJSON()`
|
|
121
|
+
for the data.
|
|
115
122
|
|
|
116
123
|
### Own the structure
|
|
117
124
|
|
|
118
125
|
For real restructuring, don't patch — write the value. It is the complete
|
|
119
|
-
structural definition of the viewer, and it is small:
|
|
126
|
+
structural definition of the viewer, as plain data, and it is small: groups
|
|
127
|
+
are arrays, items are command ids or small objects.
|
|
120
128
|
|
|
121
129
|
```tsx
|
|
122
|
-
import {
|
|
123
|
-
FullViewer,
|
|
124
|
-
defineChrome,
|
|
125
|
-
group,
|
|
126
|
-
custom,
|
|
127
|
-
defaultChrome,
|
|
128
|
-
} from '@embedpdf/viewer-chrome';
|
|
130
|
+
import { FullViewer, defaultLayout } from '@embedpdf/viewer-chrome';
|
|
129
131
|
|
|
130
132
|
// A minimal "reading room": zoom, search, download. Nothing else.
|
|
131
|
-
const
|
|
133
|
+
const layout = {
|
|
132
134
|
bars: {
|
|
133
135
|
main: {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
custom
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
]),
|
|
136
|
+
place: 'top',
|
|
137
|
+
start: [
|
|
138
|
+
[
|
|
139
|
+
{
|
|
140
|
+
custom: 'zoom',
|
|
141
|
+
command: 'menu:zoom',
|
|
142
|
+
variants: ['inline', 'button'],
|
|
143
|
+
},
|
|
143
144
|
],
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
],
|
|
146
|
+
end: [['panel:search', 'document:download']],
|
|
146
147
|
},
|
|
147
148
|
},
|
|
148
|
-
menus: { zoom:
|
|
149
|
-
}
|
|
149
|
+
menus: { zoom: defaultLayout.toJSON().menus!.zoom },
|
|
150
|
+
};
|
|
150
151
|
|
|
151
|
-
<FullViewer engine={engine}
|
|
152
|
+
<FullViewer engine={engine} layout={layout} />;
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
A bar with a `mode` adds a whole mode: the mode tabs list every bar with a
|
|
156
|
+
mode, so a custom review workflow is one bar (`{ place: { with: 'main' },
|
|
157
|
+
mode: 'review', title: 'Review', center: [...] }`) — its tab and its
|
|
158
|
+
`mode:review` command come with it. A mode whose bars show nothing has no tab,
|
|
159
|
+
and a bar with nothing visible draws nothing.
|
|
157
160
|
|
|
158
|
-
Your stability, your choice: pass no `
|
|
159
|
-
features appear on upgrade); own the value and
|
|
160
|
-
EmbedPDF features arrive as new command ids you
|
|
161
|
+
Your stability, your choice: pass no `layout`, or a function of the default,
|
|
162
|
+
and you track our default (new features appear on upgrade); own the value and
|
|
163
|
+
your toolbar never moves — new EmbedPDF features arrive as new command ids you
|
|
164
|
+
opt into by adding a line.
|
|
161
165
|
|
|
162
166
|
### Locale
|
|
163
167
|
|
|
@@ -172,55 +176,57 @@ falls back to English for everything you didn't provide.
|
|
|
172
176
|
|
|
173
177
|
### Slots — your component inside the measured toolbar
|
|
174
178
|
|
|
175
|
-
Reserve a socket in the
|
|
176
|
-
|
|
177
|
-
`
|
|
179
|
+
Reserve a socket in the layout (`{ custom, command }`), then — through
|
|
180
|
+
`<embedpdf-viewer>` or any wrapper — supply a child with the slot
|
|
181
|
+
`item:<id>` (in React, a `<ToolbarItem id>`):
|
|
178
182
|
|
|
179
183
|
```tsx
|
|
180
184
|
// @embedpdf/viewer-react
|
|
181
185
|
<PDFViewer
|
|
182
186
|
src="/report.pdf"
|
|
183
187
|
commands={[{ id: 'acme:status', labelKey: 'acme.status', run: () => {} }]}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
item: h.custom('doc-status', { terminal: 'acme:status' }),
|
|
190
|
-
})
|
|
188
|
+
layout={(layout) =>
|
|
189
|
+
layout.add(
|
|
190
|
+
{ custom: 'doc-status', command: 'acme:status' },
|
|
191
|
+
{ to: 'main', group: 'view' },
|
|
192
|
+
)
|
|
191
193
|
}
|
|
192
194
|
>
|
|
193
|
-
<
|
|
195
|
+
<ToolbarItem id="doc-status">
|
|
196
|
+
<DocStatus />
|
|
197
|
+
</ToolbarItem>
|
|
194
198
|
</PDFViewer>
|
|
195
199
|
```
|
|
196
200
|
|
|
197
201
|
The child stays in YOUR world — light DOM, your framework's tree, your page
|
|
198
202
|
CSS — while the browser projects it into the toolbar. The socket is measured
|
|
199
|
-
live, so the solver budgets its true width; when it no longer fits,
|
|
200
|
-
`
|
|
201
|
-
socket displays
|
|
202
|
-
|
|
203
|
-
this package's own React consumers see the fallback).
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
tree wins projection).
|
|
203
|
+
live, so the solver budgets its true width; when it no longer fits, its
|
|
204
|
+
`command` represents it in the derived overflow menu (without one, it never
|
|
205
|
+
leaves the bar). An unfilled socket displays that command as native slot
|
|
206
|
+
fallback, so the same layout works with and without the child (and outside
|
|
207
|
+
shadow DOM entirely — this package's own React consumers see the fallback).
|
|
208
|
+
|
|
209
|
+
Item ids must be unique across the layout (one `<slot name>` per shadow
|
|
210
|
+
tree wins projection). The slotted element is told where it sits —
|
|
211
|
+
`data-orientation`, `data-edge`, `data-variant`, `data-size` — and the
|
|
212
|
+
viewer's own components (`<epdf-button>`, `<epdf-menu>`) inside it are drawn
|
|
213
|
+
like its own.
|
|
207
214
|
|
|
208
215
|
### The frame — regions: arrangement, visibility, replacement
|
|
209
216
|
|
|
210
|
-
Regions are a FIXED vocabulary (`header`, `tabs`, the
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
Regions are a FIXED vocabulary (`header`, `footer`, `tabs`, `empty`, the bars — each carries
|
|
218
|
+
semantics the shell owns: measurement physics, the document gate, aria). Three
|
|
219
|
+
independent knobs on each:
|
|
213
220
|
|
|
214
221
|
```tsx
|
|
215
|
-
// 1. ARRANGE + HIDE — part of the
|
|
216
|
-
|
|
217
|
-
...base,
|
|
218
|
-
frame: { toolbar: 'bottom', tabs: 'multiple', header: false },
|
|
219
|
-
})}
|
|
222
|
+
// 1. ARRANGE + HIDE — part of the layout, because it is structure:
|
|
223
|
+
layout={(layout) => layout.place('main', 'bottom').tabs('multiple')}
|
|
220
224
|
|
|
221
225
|
// 2. REPLACE — regions are sockets; the built-in is the slot fallback:
|
|
222
226
|
<PDFViewer documents={docs} onReady={setViewer}>
|
|
223
|
-
<
|
|
227
|
+
<Region name="tabs">
|
|
228
|
+
<AcmeTabBar viewer={viewer} />
|
|
229
|
+
</Region>
|
|
224
230
|
</PDFViewer>
|
|
225
231
|
```
|
|
226
232
|
|
|
@@ -230,48 +236,56 @@ not to the PDF. The socket is there so yours can sit inside the frame:
|
|
|
230
236
|
|
|
231
237
|
```tsx
|
|
232
238
|
<PDFViewer documents={docs}>
|
|
233
|
-
<
|
|
239
|
+
<Region name="header">
|
|
240
|
+
<AcmeHeader />
|
|
241
|
+
</Region>
|
|
234
242
|
</PDFViewer>
|
|
235
243
|
```
|
|
236
244
|
|
|
237
|
-
Left unfilled it renders nothing
|
|
238
|
-
socket entirely. Theme and locale stay under your control as props —
|
|
245
|
+
Left unfilled it renders nothing. Theme and locale stay under your control as props —
|
|
239
246
|
`theme={{ preference, tokens }}` and `locale` (`'auto'` negotiates from the
|
|
240
247
|
browser) — so a switch in your own header is a state change on your side.
|
|
241
248
|
|
|
242
249
|
A replacement region is fully functional through the handle — the kernel's
|
|
243
250
|
document registry IS the tab model (`viewer.documents.list()/activeId()/
|
|
244
|
-
setActive()/close()` + `viewer.watch`). A region
|
|
245
|
-
its socket too: visibility outranks slotted content. The mode
|
|
246
|
-
main toolbar's content side (below a top bar, above a bottom bar).
|
|
251
|
+
setActive()/close()` + `viewer.watch`). A region the layout hides (`tabs('never')`)
|
|
252
|
+
hides its socket too: visibility outranks slotted content. The mode bars ride
|
|
253
|
+
the main toolbar's content side (below a top bar, above a bottom bar).
|
|
247
254
|
|
|
248
255
|
### Drive — control the viewer from code
|
|
249
256
|
|
|
250
|
-
The
|
|
251
|
-
thin skin over the kernel: `get(Token)` returns each plugin's
|
|
252
|
-
capability lens** verbatim — the same documented API the chrome's own
|
|
253
|
-
use —
|
|
257
|
+
The `viewer` (`el.viewer` on the custom element, `onReady` on the wrappers) is
|
|
258
|
+
a thin skin over the kernel: `get(name)` (or `get(Token)`) returns each plugin's
|
|
259
|
+
**public capability lens** verbatim — the same documented API the chrome's own
|
|
260
|
+
buttons use — `documents` and `commands` are two of them, plus ONE reactivity
|
|
261
|
+
primitive:
|
|
254
262
|
|
|
255
263
|
```ts
|
|
256
264
|
el.addEventListener('epdf:ready', () => {
|
|
257
265
|
const viewer = el.viewer;
|
|
258
266
|
|
|
259
|
-
viewer.execute('zoom:in'); // commands: UI verbs
|
|
260
|
-
const annotation = viewer.get(
|
|
267
|
+
await viewer.commands.execute('zoom:in'); // commands: UI verbs
|
|
268
|
+
const annotation = viewer.get('annotation'); // capabilities: the API
|
|
261
269
|
await annotation.create(pon, draft);
|
|
262
270
|
|
|
263
271
|
viewer.watch(
|
|
264
272
|
// the one primitive
|
|
265
|
-
() => annotation.
|
|
273
|
+
() => annotation.getSelectionFields(),
|
|
266
274
|
(props) => myPanel.render(props),
|
|
267
275
|
);
|
|
268
276
|
viewer.documents.list(); // the tab model
|
|
269
277
|
});
|
|
270
278
|
```
|
|
271
279
|
|
|
280
|
+
A `viewer` lives as long as the viewer it reaches: once that one unmounts, or
|
|
281
|
+
mounts again with a new config, the listeners added through it stop and its
|
|
282
|
+
calls refuse with `instance-closed` (`createViewer` returns it with its
|
|
283
|
+
`dispose`).
|
|
284
|
+
|
|
272
285
|
Rule of thumb: **buttons speak commands; code speaks capabilities.** The
|
|
273
|
-
|
|
274
|
-
|
|
286
|
+
plugin names (`VIEWER_PLUGINS` in `handle.ts`) and the token re-export list in
|
|
287
|
+
`index.ts` are the public-API act — host lenses
|
|
288
|
+
(`/contract/host` entries) are structurally absent from delivery bundles. Coarse
|
|
275
289
|
DOM events (`epdf:ready`, `epdf:documentchange`) are sugar over `watch`.
|
|
276
290
|
|
|
277
291
|
### Theme — match your brand
|
|
@@ -279,16 +293,25 @@ DOM events (`epdf:ready`, `epdf:documentchange`) are sugar over `watch`.
|
|
|
279
293
|
```tsx
|
|
280
294
|
theme={{
|
|
281
295
|
preference: 'system',
|
|
282
|
-
|
|
283
|
-
|
|
296
|
+
accent: '#7c3aed', // both modes
|
|
297
|
+
tokens: { surface: '#fdfcff', radius: '4px' }, // both modes
|
|
298
|
+
dark: { accent: '#a78bfa' }, // dark only
|
|
284
299
|
}}
|
|
285
300
|
```
|
|
286
301
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
302
|
+
The tokens are `THEME_TOKENS` (`ui/theme.tsx`): `accent`, `background`,
|
|
303
|
+
`surface`, `surface-raised`, `text`, `text-muted`, `border`, `hover`,
|
|
304
|
+
`canvas`, `radius` and `font`, each the CSS variable `--epdf-<name>`.
|
|
305
|
+
`styles.css` declares them on the viewer's root with a default for each mode,
|
|
306
|
+
and every other shade the chrome draws with is mixed from them in its
|
|
307
|
+
`@theme inline` block, so the Tailwind classes (`bg-surface`, `text-fg`,
|
|
308
|
+
`border-border-subtle`, `rounded-md`) follow them. `accent` is the same
|
|
309
|
+
variable the pages read (the headless docs' "Colors & theming"), so it themes
|
|
310
|
+
the pages and the chrome together. A variable the page sets on the element or
|
|
311
|
+
around it wins over the config and the defaults: the sheet reads the page's
|
|
312
|
+
value on the host first. The custom element adopts the config's tokens into
|
|
313
|
+
its shadow root (`themeTokenCss`); direct consumers of this package set the
|
|
314
|
+
variables in their own CSS.
|
|
292
315
|
|
|
293
316
|
### Restyle — reshape built-ins with page CSS
|
|
294
317
|
|
|
@@ -303,9 +326,10 @@ embedpdf-viewer::part(tab-active) {
|
|
|
303
326
|
}
|
|
304
327
|
```
|
|
305
328
|
|
|
306
|
-
The
|
|
307
|
-
|
|
308
|
-
`menu`, `menu-item(-active)
|
|
329
|
+
The part vocabulary is public API, grown by demand: `toolbar` (every bar,
|
|
330
|
+
the floating ones' pill and the bars over a selection), `toolbar-button(-active)`,
|
|
331
|
+
`mode-tab(-active)`, `tab(-active)`, `menu`, `menu-item(-active)`, `panel`
|
|
332
|
+
and `panel-header`.
|
|
309
333
|
|
|
310
334
|
### Signatures — sign with a mark, through a key you bring
|
|
311
335
|
|
|
@@ -316,30 +340,49 @@ field to sign it — or click anywhere else to drop it as a stamp, as Preview
|
|
|
316
340
|
does. A "sign here" click on an unsigned field targets it; the next mark
|
|
317
341
|
picked goes in. The Form tab authors signature fields.
|
|
318
342
|
|
|
319
|
-
What placing a mark on a field does is the
|
|
343
|
+
What placing a mark on a field does is the signature plugin's settings, in
|
|
344
|
+
`plugins.signature`, next to the two the viewer adds (`kinds`, `fonts`); the
|
|
345
|
+
standard and quick stamps are the viewer's settings in `plugins.stamp`:
|
|
320
346
|
|
|
321
347
|
```tsx
|
|
322
|
-
import {
|
|
348
|
+
import {
|
|
349
|
+
FullViewer,
|
|
350
|
+
personalSigner,
|
|
351
|
+
indexedDbKeyStore,
|
|
352
|
+
} from '@embedpdf/viewer-chrome';
|
|
323
353
|
|
|
324
354
|
<FullViewer
|
|
325
355
|
engine={engine}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
356
|
+
plugins={{
|
|
357
|
+
signature: {
|
|
358
|
+
// The key: your own (`webCryptoSigner`), a signing service
|
|
359
|
+
// (`remoteSigner`), or one self-signed identity per person kept in the
|
|
360
|
+
// browser (`personalSigner`). Without a key the mark is only drawn
|
|
361
|
+
// into the field: nothing is sealed.
|
|
362
|
+
key: () =>
|
|
363
|
+
personalSigner({
|
|
364
|
+
subject: 'Ada Lovelace',
|
|
365
|
+
store: indexedDbKeyStore('acme-keys'),
|
|
366
|
+
}),
|
|
367
|
+
// Anchors a reader trusts; none → "valid, signer not trusted" at best.
|
|
368
|
+
trust: { anchors: async () => [rootCertificateDer] },
|
|
369
|
+
// 'sign' (default with a key) | 'visual' (default without) | 'ask' (a dialog first)
|
|
370
|
+
mode: 'ask',
|
|
371
|
+
allowCertify: true, // offer a certification (first signature) in the dialog
|
|
372
|
+
kinds: ['signature'], // no initials
|
|
373
|
+
fonts: [
|
|
374
|
+
{
|
|
375
|
+
key: 'great-vibes',
|
|
376
|
+
url: '/fonts/GreatVibes.ttf',
|
|
377
|
+
label: 'Great Vibes',
|
|
378
|
+
},
|
|
379
|
+
], // typed marks
|
|
380
|
+
},
|
|
381
|
+
stamp: {
|
|
382
|
+
standard: true, // the standard stamps, from your own build (a URL, or false for none)
|
|
383
|
+
sidebar: ['stamps'], // library kinds the stamps sidebar lists
|
|
384
|
+
quick: ['standard:Approved', 'standard:Rejected'], // quick marks in the Insert bar
|
|
385
|
+
},
|
|
343
386
|
}}
|
|
344
387
|
/>;
|
|
345
388
|
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { l as useConfiguredFonts } from "./icons-BrK5iVL8.js";
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
3
|
+
import { isLocalEngine, useKernel } from "@embedpdf/react/runtime";
|
|
4
|
+
import { mountWebFont } from "@embedpdf/web";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/ui/annotation-fonts.tsx
|
|
7
|
+
/**
|
|
8
|
+
* The engine's configured fonts (`engine.fonts`), loaded once per viewer: read
|
|
9
|
+
* the bytes (fetched from `url`, or `data`) → register them on the viewer's
|
|
10
|
+
* engine under the key (what a free-text `fontFamily` carries and what the
|
|
11
|
+
* engine embeds) → mount the same bytes as a `@font-face` for that key (the
|
|
12
|
+
* family the plugin's `cssFontFamily` emits for a registered font, so the live
|
|
13
|
+
* editor renders the face the appearance stream will bake). The style panel
|
|
14
|
+
* lists a font only once both have happened — an offered key always resolves
|
|
15
|
+
* on write.
|
|
16
|
+
*/
|
|
17
|
+
const LoadedFontsContext = createContext([]);
|
|
18
|
+
/** A font's bytes: its `data`, or its file fetched from `url`. */
|
|
19
|
+
async function bytesOf(font) {
|
|
20
|
+
if (font.data) return new Uint8Array(font.data);
|
|
21
|
+
if (!font.url) throw new Error("a font needs a url or its data");
|
|
22
|
+
const response = await fetch(font.url);
|
|
23
|
+
if (!response.ok) throw new Error(`${font.url}: HTTP ${response.status}`);
|
|
24
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
25
|
+
}
|
|
26
|
+
function AnnotationFontsProvider({ children }) {
|
|
27
|
+
const kernel = useKernel();
|
|
28
|
+
const fonts = useConfiguredFonts();
|
|
29
|
+
const [loaded, setLoaded] = useState([]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!fonts.length || !isLocalEngine(kernel.engine)) return;
|
|
32
|
+
const engineFonts = kernel.engine.fonts;
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
const unmounts = [];
|
|
35
|
+
(async () => {
|
|
36
|
+
for (const font of fonts) try {
|
|
37
|
+
const bytes = await bytesOf(font);
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
await engineFonts.register({
|
|
40
|
+
key: font.key,
|
|
41
|
+
familyName: font.familyName,
|
|
42
|
+
weight: font.weight,
|
|
43
|
+
italic: font.italic,
|
|
44
|
+
data: bytes
|
|
45
|
+
});
|
|
46
|
+
const unmount = await mountWebFont(font.key, bytes, {
|
|
47
|
+
weight: font.weight,
|
|
48
|
+
style: font.italic ? "italic" : "normal"
|
|
49
|
+
});
|
|
50
|
+
if (cancelled) {
|
|
51
|
+
unmount();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
unmounts.push(unmount);
|
|
55
|
+
setLoaded((previous) => previous.some((existing) => existing.key === font.key) ? previous : [...previous, {
|
|
56
|
+
key: font.key,
|
|
57
|
+
label: font.familyName ?? font.key
|
|
58
|
+
}]);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn(`[embedpdf] engine font "${font.key}" was not loaded:`, error);
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
return () => {
|
|
64
|
+
cancelled = true;
|
|
65
|
+
for (const unmount of unmounts) unmount();
|
|
66
|
+
};
|
|
67
|
+
}, [kernel, fonts]);
|
|
68
|
+
return /* @__PURE__ */ jsx(LoadedFontsContext.Provider, {
|
|
69
|
+
value: loaded,
|
|
70
|
+
children
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** The configured fonts that are registered and mounted, in config order. */
|
|
74
|
+
function useAnnotationFonts() {
|
|
75
|
+
return useContext(LoadedFontsContext);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { useAnnotationFonts as n, AnnotationFontsProvider as t };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=annotation-fonts-BqoLZ4Mp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-fonts-BqoLZ4Mp.js","names":[],"sources":["../src/ui/annotation-fonts.tsx"],"sourcesContent":["/**\n * The engine's configured fonts (`engine.fonts`), loaded once per viewer: read\n * the bytes (fetched from `url`, or `data`) → register them on the viewer's\n * engine under the key (what a free-text `fontFamily` carries and what the\n * engine embeds) → mount the same bytes as a `@font-face` for that key (the\n * family the plugin's `cssFontFamily` emits for a registered font, so the live\n * editor renders the face the appearance stream will bake). The style panel\n * lists a font only once both have happened — an offered key always resolves\n * on write.\n */\nimport { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { isLocalEngine, useKernel } from '@embedpdf/react/runtime';\nimport { mountWebFont } from '@embedpdf/web';\nimport { useConfiguredFonts, type ViewerFont } from '../config-context';\n\nexport interface LoadedAnnotationFont {\n readonly key: string;\n readonly label: string;\n}\n\nconst LoadedFontsContext = createContext<readonly LoadedAnnotationFont[]>([]);\n\n/** A font's bytes: its `data`, or its file fetched from `url`. */\nasync function bytesOf(font: ViewerFont): Promise<Uint8Array> {\n if (font.data) return new Uint8Array(font.data);\n if (!font.url) throw new Error('a font needs a url or its data');\n const response = await fetch(font.url);\n if (!response.ok) throw new Error(`${font.url}: HTTP ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n}\n\nexport function AnnotationFontsProvider({ children }: { children: ReactNode }) {\n const kernel = useKernel();\n const fonts = useConfiguredFonts();\n const [loaded, setLoaded] = useState<readonly LoadedAnnotationFont[]>([]);\n\n useEffect(() => {\n // The cloud engine registers no fonts (a server policy), so a key could\n // never resolve on write: offer nothing rather than a font that fails.\n if (!fonts.length || !isLocalEngine(kernel.engine)) return;\n const engineFonts = kernel.engine.fonts;\n let cancelled = false;\n const unmounts: Array<() => void> = [];\n void (async () => {\n for (const font of fonts) {\n try {\n const bytes = await bytesOf(font);\n if (cancelled) return;\n await engineFonts.register({\n key: font.key,\n familyName: font.familyName,\n weight: font.weight,\n italic: font.italic,\n data: bytes,\n });\n const unmount = await mountWebFont(font.key, bytes, {\n weight: font.weight,\n style: font.italic ? 'italic' : 'normal',\n });\n if (cancelled) {\n unmount();\n return;\n }\n unmounts.push(unmount);\n setLoaded((previous) =>\n previous.some((existing) => existing.key === font.key)\n ? previous\n : [...previous, { key: font.key, label: font.familyName ?? font.key }],\n );\n } catch (error) {\n console.warn(`[embedpdf] engine font \"${font.key}\" was not loaded:`, error);\n }\n }\n })();\n return () => {\n cancelled = true;\n for (const unmount of unmounts) unmount();\n };\n }, [kernel, fonts]);\n\n return <LoadedFontsContext.Provider value={loaded}>{children}</LoadedFontsContext.Provider>;\n}\n\n/** The configured fonts that are registered and mounted, in config order. */\nexport function useAnnotationFonts(): readonly LoadedAnnotationFont[] {\n return useContext(LoadedFontsContext);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAM,qBAAqB,cAA+C,CAAC,CAAC;;AAG5E,eAAe,QAAQ,MAAuC;CAC5D,IAAI,KAAK,MAAM,OAAO,IAAI,WAAW,KAAK,IAAI;CAC9C,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,MAAM,gCAAgC;CAC/D,MAAM,WAAW,MAAM,MAAM,KAAK,GAAG;CACrC,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,GAAG,KAAK,IAAI,SAAS,SAAS,QAAQ;CACxE,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACpD;AAEA,SAAgB,wBAAwB,EAAE,YAAqC;CAC7E,MAAM,SAAS,UAAU;CACzB,MAAM,QAAQ,mBAAmB;CACjC,MAAM,CAAC,QAAQ,aAAa,SAA0C,CAAC,CAAC;CAExE,gBAAgB;EAGd,IAAI,CAAC,MAAM,UAAU,CAAC,cAAc,OAAO,MAAM,GAAG;EACpD,MAAM,cAAc,OAAO,OAAO;EAClC,IAAI,YAAY;EAChB,MAAM,WAA8B,CAAC;EACrC,CAAM,YAAY;GAChB,KAAK,MAAM,QAAQ,OACjB,IAAI;IACF,MAAM,QAAQ,MAAM,QAAQ,IAAI;IAChC,IAAI,WAAW;IACf,MAAM,YAAY,SAAS;KACzB,KAAK,KAAK;KACV,YAAY,KAAK;KACjB,QAAQ,KAAK;KACb,QAAQ,KAAK;KACb,MAAM;IACR,CAAC;IACD,MAAM,UAAU,MAAM,aAAa,KAAK,KAAK,OAAO;KAClD,QAAQ,KAAK;KACb,OAAO,KAAK,SAAS,WAAW;IAClC,CAAC;IACD,IAAI,WAAW;KACb,QAAQ;KACR;IACF;IACA,SAAS,KAAK,OAAO;IACrB,WAAW,aACT,SAAS,MAAM,aAAa,SAAS,QAAQ,KAAK,GAAG,IACjD,WACA,CAAC,GAAG,UAAU;KAAE,KAAK,KAAK;KAAK,OAAO,KAAK,cAAc,KAAK;IAAI,CAAC,CACzE;GACF,SAAS,OAAO;IACd,QAAQ,KAAK,2BAA2B,KAAK,IAAI,oBAAoB,KAAK;GAC5E;EAEJ,EAAA,CAAG;EACH,aAAa;GACX,YAAY;GACZ,KAAK,MAAM,WAAW,UAAU,QAAQ;EAC1C;CACF,GAAG,CAAC,QAAQ,KAAK,CAAC;CAElB,OAAO,oBAAC,mBAAmB,UAApB;EAA6B,OAAO;EAAS;CAAsC,CAAA;AAC5F;;AAGA,SAAgB,qBAAsD;CACpE,OAAO,WAAW,kBAAkB;AACtC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const require_icons = require("./icons-x0Tc05bs.cjs");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let _embedpdf_react_runtime = require("@embedpdf/react/runtime");
|
|
4
|
+
let _embedpdf_web = require("@embedpdf/web");
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
//#region src/ui/annotation-fonts.tsx
|
|
7
|
+
/**
|
|
8
|
+
* The engine's configured fonts (`engine.fonts`), loaded once per viewer: read
|
|
9
|
+
* the bytes (fetched from `url`, or `data`) → register them on the viewer's
|
|
10
|
+
* engine under the key (what a free-text `fontFamily` carries and what the
|
|
11
|
+
* engine embeds) → mount the same bytes as a `@font-face` for that key (the
|
|
12
|
+
* family the plugin's `cssFontFamily` emits for a registered font, so the live
|
|
13
|
+
* editor renders the face the appearance stream will bake). The style panel
|
|
14
|
+
* lists a font only once both have happened — an offered key always resolves
|
|
15
|
+
* on write.
|
|
16
|
+
*/
|
|
17
|
+
const LoadedFontsContext = (0, react.createContext)([]);
|
|
18
|
+
/** A font's bytes: its `data`, or its file fetched from `url`. */
|
|
19
|
+
async function bytesOf(font) {
|
|
20
|
+
if (font.data) return new Uint8Array(font.data);
|
|
21
|
+
if (!font.url) throw new Error("a font needs a url or its data");
|
|
22
|
+
const response = await fetch(font.url);
|
|
23
|
+
if (!response.ok) throw new Error(`${font.url}: HTTP ${response.status}`);
|
|
24
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
25
|
+
}
|
|
26
|
+
function AnnotationFontsProvider({ children }) {
|
|
27
|
+
const kernel = (0, _embedpdf_react_runtime.useKernel)();
|
|
28
|
+
const fonts = require_icons.useConfiguredFonts();
|
|
29
|
+
const [loaded, setLoaded] = (0, react.useState)([]);
|
|
30
|
+
(0, react.useEffect)(() => {
|
|
31
|
+
if (!fonts.length || !(0, _embedpdf_react_runtime.isLocalEngine)(kernel.engine)) return;
|
|
32
|
+
const engineFonts = kernel.engine.fonts;
|
|
33
|
+
let cancelled = false;
|
|
34
|
+
const unmounts = [];
|
|
35
|
+
(async () => {
|
|
36
|
+
for (const font of fonts) try {
|
|
37
|
+
const bytes = await bytesOf(font);
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
await engineFonts.register({
|
|
40
|
+
key: font.key,
|
|
41
|
+
familyName: font.familyName,
|
|
42
|
+
weight: font.weight,
|
|
43
|
+
italic: font.italic,
|
|
44
|
+
data: bytes
|
|
45
|
+
});
|
|
46
|
+
const unmount = await (0, _embedpdf_web.mountWebFont)(font.key, bytes, {
|
|
47
|
+
weight: font.weight,
|
|
48
|
+
style: font.italic ? "italic" : "normal"
|
|
49
|
+
});
|
|
50
|
+
if (cancelled) {
|
|
51
|
+
unmount();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
unmounts.push(unmount);
|
|
55
|
+
setLoaded((previous) => previous.some((existing) => existing.key === font.key) ? previous : [...previous, {
|
|
56
|
+
key: font.key,
|
|
57
|
+
label: font.familyName ?? font.key
|
|
58
|
+
}]);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn(`[embedpdf] engine font "${font.key}" was not loaded:`, error);
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
return () => {
|
|
64
|
+
cancelled = true;
|
|
65
|
+
for (const unmount of unmounts) unmount();
|
|
66
|
+
};
|
|
67
|
+
}, [kernel, fonts]);
|
|
68
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoadedFontsContext.Provider, {
|
|
69
|
+
value: loaded,
|
|
70
|
+
children
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** The configured fonts that are registered and mounted, in config order. */
|
|
74
|
+
function useAnnotationFonts() {
|
|
75
|
+
return (0, react.useContext)(LoadedFontsContext);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
Object.defineProperty(exports, "AnnotationFontsProvider", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
get: function() {
|
|
81
|
+
return AnnotationFontsProvider;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(exports, "useAnnotationFonts", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
get: function() {
|
|
87
|
+
return useAnnotationFonts;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=annotation-fonts-Dt0IQXhz.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-fonts-Dt0IQXhz.cjs","names":["useConfiguredFonts"],"sources":["../src/ui/annotation-fonts.tsx"],"sourcesContent":["/**\n * The engine's configured fonts (`engine.fonts`), loaded once per viewer: read\n * the bytes (fetched from `url`, or `data`) → register them on the viewer's\n * engine under the key (what a free-text `fontFamily` carries and what the\n * engine embeds) → mount the same bytes as a `@font-face` for that key (the\n * family the plugin's `cssFontFamily` emits for a registered font, so the live\n * editor renders the face the appearance stream will bake). The style panel\n * lists a font only once both have happened — an offered key always resolves\n * on write.\n */\nimport { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { isLocalEngine, useKernel } from '@embedpdf/react/runtime';\nimport { mountWebFont } from '@embedpdf/web';\nimport { useConfiguredFonts, type ViewerFont } from '../config-context';\n\nexport interface LoadedAnnotationFont {\n readonly key: string;\n readonly label: string;\n}\n\nconst LoadedFontsContext = createContext<readonly LoadedAnnotationFont[]>([]);\n\n/** A font's bytes: its `data`, or its file fetched from `url`. */\nasync function bytesOf(font: ViewerFont): Promise<Uint8Array> {\n if (font.data) return new Uint8Array(font.data);\n if (!font.url) throw new Error('a font needs a url or its data');\n const response = await fetch(font.url);\n if (!response.ok) throw new Error(`${font.url}: HTTP ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n}\n\nexport function AnnotationFontsProvider({ children }: { children: ReactNode }) {\n const kernel = useKernel();\n const fonts = useConfiguredFonts();\n const [loaded, setLoaded] = useState<readonly LoadedAnnotationFont[]>([]);\n\n useEffect(() => {\n // The cloud engine registers no fonts (a server policy), so a key could\n // never resolve on write: offer nothing rather than a font that fails.\n if (!fonts.length || !isLocalEngine(kernel.engine)) return;\n const engineFonts = kernel.engine.fonts;\n let cancelled = false;\n const unmounts: Array<() => void> = [];\n void (async () => {\n for (const font of fonts) {\n try {\n const bytes = await bytesOf(font);\n if (cancelled) return;\n await engineFonts.register({\n key: font.key,\n familyName: font.familyName,\n weight: font.weight,\n italic: font.italic,\n data: bytes,\n });\n const unmount = await mountWebFont(font.key, bytes, {\n weight: font.weight,\n style: font.italic ? 'italic' : 'normal',\n });\n if (cancelled) {\n unmount();\n return;\n }\n unmounts.push(unmount);\n setLoaded((previous) =>\n previous.some((existing) => existing.key === font.key)\n ? previous\n : [...previous, { key: font.key, label: font.familyName ?? font.key }],\n );\n } catch (error) {\n console.warn(`[embedpdf] engine font \"${font.key}\" was not loaded:`, error);\n }\n }\n })();\n return () => {\n cancelled = true;\n for (const unmount of unmounts) unmount();\n };\n }, [kernel, fonts]);\n\n return <LoadedFontsContext.Provider value={loaded}>{children}</LoadedFontsContext.Provider>;\n}\n\n/** The configured fonts that are registered and mounted, in config order. */\nexport function useAnnotationFonts(): readonly LoadedAnnotationFont[] {\n return useContext(LoadedFontsContext);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAoBA,MAAM,sBAAA,GAAA,MAAA,cAAA,CAAoE,CAAC,CAAC;;AAG5E,eAAe,QAAQ,MAAuC;CAC5D,IAAI,KAAK,MAAM,OAAO,IAAI,WAAW,KAAK,IAAI;CAC9C,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,MAAM,gCAAgC;CAC/D,MAAM,WAAW,MAAM,MAAM,KAAK,GAAG;CACrC,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,GAAG,KAAK,IAAI,SAAS,SAAS,QAAQ;CACxE,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACpD;AAEA,SAAgB,wBAAwB,EAAE,YAAqC;CAC7E,MAAM,UAAA,GAAA,wBAAA,UAAA,CAAmB;CACzB,MAAM,QAAQA,cAAAA,mBAAmB;CACjC,MAAM,CAAC,QAAQ,cAAA,GAAA,MAAA,SAAA,CAAuD,CAAC,CAAC;CAExE,CAAA,GAAA,MAAA,UAAA,OAAgB;EAGd,IAAI,CAAC,MAAM,UAAU,EAAA,GAAA,wBAAA,cAAA,CAAe,OAAO,MAAM,GAAG;EACpD,MAAM,cAAc,OAAO,OAAO;EAClC,IAAI,YAAY;EAChB,MAAM,WAA8B,CAAC;EACrC,CAAM,YAAY;GAChB,KAAK,MAAM,QAAQ,OACjB,IAAI;IACF,MAAM,QAAQ,MAAM,QAAQ,IAAI;IAChC,IAAI,WAAW;IACf,MAAM,YAAY,SAAS;KACzB,KAAK,KAAK;KACV,YAAY,KAAK;KACjB,QAAQ,KAAK;KACb,QAAQ,KAAK;KACb,MAAM;IACR,CAAC;IACD,MAAM,UAAU,OAAA,GAAA,cAAA,aAAA,CAAmB,KAAK,KAAK,OAAO;KAClD,QAAQ,KAAK;KACb,OAAO,KAAK,SAAS,WAAW;IAClC,CAAC;IACD,IAAI,WAAW;KACb,QAAQ;KACR;IACF;IACA,SAAS,KAAK,OAAO;IACrB,WAAW,aACT,SAAS,MAAM,aAAa,SAAS,QAAQ,KAAK,GAAG,IACjD,WACA,CAAC,GAAG,UAAU;KAAE,KAAK,KAAK;KAAK,OAAO,KAAK,cAAc,KAAK;IAAI,CAAC,CACzE;GACF,SAAS,OAAO;IACd,QAAQ,KAAK,2BAA2B,KAAK,IAAI,oBAAoB,KAAK;GAC5E;EAEJ,EAAA,CAAG;EACH,aAAa;GACX,YAAY;GACZ,KAAK,MAAM,WAAW,UAAU,QAAQ;EAC1C;CACF,GAAG,CAAC,QAAQ,KAAK,CAAC;CAElB,OAAO,iBAAA,GAAA,kBAAA,IAAA,CAAC,mBAAmB,UAApB;EAA6B,OAAO;EAAS;CAAsC,CAAA;AAC5F;;AAGA,SAAgB,qBAAsD;CACpE,QAAA,GAAA,MAAA,WAAA,CAAkB,kBAAkB;AACtC"}
|