@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.
Files changed (157) hide show
  1. package/README.md +175 -132
  2. package/dist/annotation-fonts-BqoLZ4Mp.js +80 -0
  3. package/dist/annotation-fonts-BqoLZ4Mp.js.map +1 -0
  4. package/dist/annotation-fonts-Dt0IQXhz.cjs +91 -0
  5. package/dist/annotation-fonts-Dt0IQXhz.cjs.map +1 -0
  6. package/dist/annotation-style--rPIZsuP.js +879 -0
  7. package/dist/annotation-style--rPIZsuP.js.map +1 -0
  8. package/dist/annotation-style-Bzvvp8-N.cjs +879 -0
  9. package/dist/annotation-style-Bzvvp8-N.cjs.map +1 -0
  10. package/dist/ar-D9XKAmDx.js +567 -0
  11. package/dist/ar-D9XKAmDx.js.map +1 -0
  12. package/dist/ar-DnEj5vbP.cjs +567 -0
  13. package/dist/ar-DnEj5vbP.cjs.map +1 -0
  14. package/dist/chrome.cjs +378 -221
  15. package/dist/chrome.cjs.map +1 -1
  16. package/dist/chrome.d.cts +5 -34
  17. package/dist/chrome.d.ts +5 -34
  18. package/dist/chrome.js +377 -218
  19. package/dist/chrome.js.map +1 -1
  20. package/dist/commands-BmgxhJ8B.js +953 -0
  21. package/dist/commands-BmgxhJ8B.js.map +1 -0
  22. package/dist/commands-CYt4SFUX.cjs +1060 -0
  23. package/dist/commands-CYt4SFUX.cjs.map +1 -0
  24. package/dist/commands.cjs +12 -595
  25. package/dist/commands.d.cts +29 -4
  26. package/dist/commands.d.ts +29 -4
  27. package/dist/commands.js +2 -593
  28. package/dist/comments-panel-MUVgLCBI.js +514 -0
  29. package/dist/comments-panel-MUVgLCBI.js.map +1 -0
  30. package/dist/comments-panel-pEcQg4Vs.cjs +514 -0
  31. package/dist/comments-panel-pEcQg4Vs.cjs.map +1 -0
  32. package/dist/de-Bdr4aWzm.js +566 -0
  33. package/dist/de-Bdr4aWzm.js.map +1 -0
  34. package/dist/de-Dv6zvbnd.cjs +566 -0
  35. package/dist/de-Dv6zvbnd.cjs.map +1 -0
  36. package/dist/dialog-B5GBisKv.js +55 -0
  37. package/dist/dialog-B5GBisKv.js.map +1 -0
  38. package/dist/dialog-t8F13BL_.cjs +60 -0
  39. package/dist/dialog-t8F13BL_.cjs.map +1 -0
  40. package/dist/es-BP09DR9w.cjs +566 -0
  41. package/dist/es-BP09DR9w.cjs.map +1 -0
  42. package/dist/es-BSXz5tBv.js +566 -0
  43. package/dist/es-BSXz5tBv.js.map +1 -0
  44. package/dist/form-design-panel-B5MXTZ96.cjs +418 -0
  45. package/dist/form-design-panel-B5MXTZ96.cjs.map +1 -0
  46. package/dist/form-design-panel-vWWU9uB-.js +418 -0
  47. package/dist/form-design-panel-vWWU9uB-.js.map +1 -0
  48. package/dist/fr-BBQQn7tJ.cjs +566 -0
  49. package/dist/fr-BBQQn7tJ.cjs.map +1 -0
  50. package/dist/fr-Ckn3K9v1.js +566 -0
  51. package/dist/fr-Ckn3K9v1.js.map +1 -0
  52. package/dist/he-DRsG7bH7.cjs +567 -0
  53. package/dist/he-DRsG7bH7.cjs.map +1 -0
  54. package/dist/he-wdGU5PkU.js +567 -0
  55. package/dist/he-wdGU5PkU.js.map +1 -0
  56. package/dist/icons-BrK5iVL8.js +856 -0
  57. package/dist/icons-BrK5iVL8.js.map +1 -0
  58. package/dist/icons-x0Tc05bs.cjs +1010 -0
  59. package/dist/icons-x0Tc05bs.cjs.map +1 -0
  60. package/dist/index.cjs +3162 -5106
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +636 -143
  63. package/dist/index.d.ts +636 -143
  64. package/dist/index.js +3083 -4975
  65. package/dist/index.js.map +1 -1
  66. package/dist/it-BuP4o0xQ.cjs +566 -0
  67. package/dist/it-BuP4o0xQ.cjs.map +1 -0
  68. package/dist/it-Us-VmvIu.js +566 -0
  69. package/dist/it-Us-VmvIu.js.map +1 -0
  70. package/dist/ja-Cizj0-5O.js +566 -0
  71. package/dist/ja-Cizj0-5O.js.map +1 -0
  72. package/dist/ja-DovEd6ts.cjs +566 -0
  73. package/dist/ja-DovEd6ts.cjs.map +1 -0
  74. package/dist/ko-B-qJEol2.cjs +566 -0
  75. package/dist/ko-B-qJEol2.cjs.map +1 -0
  76. package/dist/ko-DlrXliE1.js +566 -0
  77. package/dist/ko-DlrXliE1.js.map +1 -0
  78. package/dist/labels-BiM9wp9K.cjs +21 -0
  79. package/dist/labels-BiM9wp9K.cjs.map +1 -0
  80. package/dist/labels-C6_i87Oj.js +16 -0
  81. package/dist/labels-C6_i87Oj.js.map +1 -0
  82. package/dist/link-editor-CSjqPnj1.cjs +125 -0
  83. package/dist/link-editor-CSjqPnj1.cjs.map +1 -0
  84. package/dist/link-editor-SxM1nnJU.js +125 -0
  85. package/dist/link-editor-SxM1nnJU.js.map +1 -0
  86. package/dist/measurement-Bs61XWbJ.js +325 -0
  87. package/dist/measurement-Bs61XWbJ.js.map +1 -0
  88. package/dist/measurement-CcCsHPOX.cjs +326 -0
  89. package/dist/measurement-CcCsHPOX.cjs.map +1 -0
  90. package/dist/nl-0aF7M_0h.js +566 -0
  91. package/dist/nl-0aF7M_0h.js.map +1 -0
  92. package/dist/nl-D3KZDDiJ.cjs +566 -0
  93. package/dist/nl-D3KZDDiJ.cjs.map +1 -0
  94. package/dist/pl-BW--AaCI.js +566 -0
  95. package/dist/pl-BW--AaCI.js.map +1 -0
  96. package/dist/pl-DSFhAUqx.cjs +566 -0
  97. package/dist/pl-DSFhAUqx.cjs.map +1 -0
  98. package/dist/pt-C5S-8MC5.cjs +566 -0
  99. package/dist/pt-C5S-8MC5.cjs.map +1 -0
  100. package/dist/pt-DIhpU5pT.js +566 -0
  101. package/dist/pt-DIhpU5pT.js.map +1 -0
  102. package/dist/redact-confirm-B6j36TPw.js +83 -0
  103. package/dist/redact-confirm-B6j36TPw.js.map +1 -0
  104. package/dist/redact-confirm-CX_SY2ZN.cjs +83 -0
  105. package/dist/redact-confirm-CX_SY2ZN.cjs.map +1 -0
  106. package/dist/redaction-panel-BmoIl-Ll.cjs +96 -0
  107. package/dist/redaction-panel-BmoIl-Ll.cjs.map +1 -0
  108. package/dist/redaction-panel-IJDshSJI.js +96 -0
  109. package/dist/redaction-panel-IJDshSJI.js.map +1 -0
  110. package/dist/search-panel-HfIhJm1M.js +249 -0
  111. package/dist/search-panel-HfIhJm1M.js.map +1 -0
  112. package/dist/search-panel-s9IoiOtv.cjs +249 -0
  113. package/dist/search-panel-s9IoiOtv.cjs.map +1 -0
  114. package/dist/sheet-BDsa_Sup.cjs +442 -0
  115. package/dist/sheet-BDsa_Sup.cjs.map +1 -0
  116. package/dist/sheet-R6XDgyEf.js +365 -0
  117. package/dist/sheet-R6XDgyEf.js.map +1 -0
  118. package/dist/sign-dialog-CgnR7akj.cjs +155 -0
  119. package/dist/sign-dialog-CgnR7akj.cjs.map +1 -0
  120. package/dist/sign-dialog-GDqCoQhA.js +155 -0
  121. package/dist/sign-dialog-GDqCoQhA.js.map +1 -0
  122. package/dist/signature-inspector-DyO313Qf.cjs +166 -0
  123. package/dist/signature-inspector-DyO313Qf.cjs.map +1 -0
  124. package/dist/signature-inspector-okFz8do6.js +166 -0
  125. package/dist/signature-inspector-okFz8do6.js.map +1 -0
  126. package/dist/signature-maker-BhCmYyHD.js +393 -0
  127. package/dist/signature-maker-BhCmYyHD.js.map +1 -0
  128. package/dist/signature-maker-BoYa8vgA.cjs +393 -0
  129. package/dist/signature-maker-BoYa8vgA.cjs.map +1 -0
  130. package/dist/signatures-panel-B56PC6d9.cjs +300 -0
  131. package/dist/signatures-panel-B56PC6d9.cjs.map +1 -0
  132. package/dist/signatures-panel-CgnFfhFm.js +300 -0
  133. package/dist/signatures-panel-CgnFfhFm.js.map +1 -0
  134. package/dist/stamp-store-BHvRs-Do.cjs +147 -0
  135. package/dist/stamp-store-BHvRs-Do.cjs.map +1 -0
  136. package/dist/stamp-store-WuF-L3Jj.js +112 -0
  137. package/dist/stamp-store-WuF-L3Jj.js.map +1 -0
  138. package/dist/stamps-panel-B9NYGayn.cjs +231 -0
  139. package/dist/stamps-panel-B9NYGayn.cjs.map +1 -0
  140. package/dist/stamps-panel-CDO0FPyK.js +231 -0
  141. package/dist/stamps-panel-CDO0FPyK.js.map +1 -0
  142. package/dist/toolbar-CJu2wKTU.cjs +1659 -0
  143. package/dist/toolbar-CJu2wKTU.cjs.map +1 -0
  144. package/dist/toolbar-z9od3tKl.js +1564 -0
  145. package/dist/toolbar-z9od3tKl.js.map +1 -0
  146. package/dist/zh-cn-3tmMeHqv.cjs +566 -0
  147. package/dist/zh-cn-3tmMeHqv.cjs.map +1 -0
  148. package/dist/zh-cn-CxkUUBct.js +566 -0
  149. package/dist/zh-cn-CxkUUBct.js.map +1 -0
  150. package/package.json +6 -4
  151. package/src/styles.css +153 -89
  152. package/dist/commands.cjs.map +0 -1
  153. package/dist/commands.js.map +0 -1
  154. package/dist/es-DYwzfxyt.cjs +0 -293
  155. package/dist/es-DYwzfxyt.cjs.map +0 -1
  156. package/dist/es-sN6uqBgD.js +0 -293
  157. 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** (`chrome`) is a **value you own**: take the default, transform
36
- it, or write your own. It is never merged — there is no patch grammar to
37
- learn, because the schema is small enough to read.
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} disabledCategories={['form', 'redaction']} />
42
+ <FullViewer engine={engine} disable={['forms', 'redaction', 'tool:highlight']} />
43
+ <FullViewer engine={engine} readOnly={{ except: ['forms.filling'] }} />
43
44
  ```
44
45
 
45
- A disabled category vanishes everywhere at once — toolbar, menus, overflow,
46
- shortcuts — because every surface is a projection of the same command registry.
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
- chrome={(base, h) =>
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. Referencing an unknown command or icon logs a dev-mode warning
93
- naming the exact id.
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
- `chrome` accepts the schema **value** or a **transform** of the default. The
96
- helpers come in as the second argument so simple edits need no imports:
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
- chrome={(base, h) =>
100
- h.removeItems(
101
- h.replaceItem(base, 'panel:comment', { command: 'acme:chat', importance: 5 }),
102
- ['annotation:add-squiggly'],
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
- - `h.addItem(schema, { bar, section, group, item, at? })` — `bar` is a bar id
108
- (`'main'`, a mode bar like `'annotate'`, or a strip); a new `group` id
109
- creates that group at the end of the section.
110
- - `h.removeItems(schema, ids)` — purges the commands from every bar, mode bar,
111
- strip, and menu; emptied groups disappear.
112
- - `h.replaceItem(schema, id, item)` — swaps a command in place, everywhere.
113
- - Plus the authoring sugar (`h.item`, `h.group`, `h.custom`, `h.defineChrome`)
114
- for building bigger pieces inline.
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 chrome = defineChrome({
133
+ const layout = {
132
134
  bars: {
133
135
  main: {
134
- id: 'main',
135
- sections: {
136
- start: [
137
- group('zoom', [
138
- custom('zoom-controls', {
139
- variants: ['inline', 'button'],
140
- terminal: 'zoom:menu',
141
- }),
142
- ]),
136
+ place: 'top',
137
+ start: [
138
+ [
139
+ {
140
+ custom: 'zoom',
141
+ command: 'menu:zoom',
142
+ variants: ['inline', 'button'],
143
+ },
143
144
  ],
144
- end: [group('actions', ['panel:search', 'document:download'])],
145
- },
145
+ ],
146
+ end: [['panel:search', 'document:download']],
146
147
  },
147
148
  },
148
- menus: { zoom: defaultChrome.menus.zoom },
149
- });
149
+ menus: { zoom: defaultLayout.toJSON().menus!.zoom },
150
+ };
150
151
 
151
- <FullViewer engine={engine} chrome={chrome} />;
152
+ <FullViewer engine={engine} layout={layout} />;
152
153
  ```
153
154
 
154
- Adding an entry under `chrome.modeBars` adds a whole mode: the mode tab strip
155
- is derived from `modeBars` keys, so a custom review workflow is one command
156
- (the mode surface) plus one bar schema — not a special case.
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 `chrome` and you track our default (new
159
- features appear on upgrade); own the value and your toolbar never moves — new
160
- EmbedPDF features arrive as new command ids you opt into by adding a line.
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 chrome (`custom()` with a `terminal` command), then —
176
- through `<embedpdf-viewer>` or any wrapper — supply a child with the matching
177
- `slot` attribute:
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
- chrome={(base, h) =>
185
- h.addItem(base, {
186
- bar: 'main',
187
- section: 'start',
188
- group: 'workspace',
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
- <DocStatus slot="doc-status" />
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, the
200
- `terminal` command represents it in the derived overflow menu. An unfilled
201
- socket displays its terminal command as native slot fallback, so the same
202
- chrome works with and without the child (and outside shadow DOM entirely —
203
- this package's own React consumers see the fallback).
204
-
205
- Slot names must be unique across the chrome (one `<slot name>` per shadow
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 toolbar band — each
211
- carries semantics the shell owns: measurement physics, the document gate,
212
- aria). Three independent knobs on each:
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 chrome value, because it is structure:
216
- chrome={(base) => ({
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
- <AcmeTabBar slot="tabs" viewer={viewer} />
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
- <AcmeHeader slot="header" />
239
+ <Region name="header">
240
+ <AcmeHeader />
241
+ </Region>
234
242
  </PDFViewer>
235
243
  ```
236
244
 
237
- Left unfilled it renders nothing, and `frame: { header: false }` drops the
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 hidden by the frame hides
245
- its socket too: visibility outranks slotted content. The mode band rides the
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 handle (`el.viewer` on the custom element, `onReady` on the wrappers) is a
251
- thin skin over the kernel: `get(Token)` returns each plugin's **public
252
- capability lens** verbatim — the same documented API the chrome's own buttons
253
- use — plus ONE reactivity primitive and the command trio:
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(AnnotationToken); // capabilities: the API
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.getSelectionProps(),
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
- token re-export list in `index.ts` is the public-API act — internal lenses
274
- (`/internal` entries) are structurally absent from delivery bundles. Coarse
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
- tokens: { accent: '#7c3aed', 'accent-hover': '#6d28d9' }, // both modes
283
- dark: { accent: '#a78bfa' }, // dark overrides
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
- Token names are the `--ep-*` variables in `styles.css` without the prefix —
288
- the prefix exists because custom properties inherit through shadow boundaries,
289
- so unprefixed names could collide with the host page. The custom element
290
- adopts the overrides into its shadow root; direct consumers of this package
291
- just set the `--ep-*` variables in their own CSS.
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 v1 part vocabulary — public API, grown by demand, never speculatively:
307
- `toolbar`, `toolbar-button(-active)`, `mode-tab(-active)`, `tab(-active)`,
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 `signatures` config:
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 { FullViewer, personalSigner, indexedDbKeyStore } from '@embedpdf/viewer-chrome';
348
+ import {
349
+ FullViewer,
350
+ personalSigner,
351
+ indexedDbKeyStore,
352
+ } from '@embedpdf/viewer-chrome';
323
353
 
324
354
  <FullViewer
325
355
  engine={engine}
326
- signatures={{
327
- // The key holder: your own key (`webCryptoSigner`), a signing service
328
- // (`remoteSigner`), or one self-signed identity per person kept in the
329
- // browser (`personalSigner`). Without a signer the mark is only DRAWN
330
- // into the field — nothing is sealed.
331
- signer: () => personalSigner({ subject: 'Ada Lovelace', store: indexedDbKeyStore('acme-keys') }),
332
- // Anchors a reader trusts; none → "valid, signer not trusted" at best.
333
- trust: { anchors: async () => [rootCertificateDer] },
334
- // 'sign' (default with a signer) | 'visual' (default without) | 'ask' (a dialog first)
335
- mode: 'ask',
336
- allowCertify: true, // offer a certification (first signature) in the dialog
337
- kinds: ['signature'], // no initials
338
- fonts: [{ key: 'great-vibes', url: '/fonts/GreatVibes.ttf', label: 'Great Vibes' }], // typed marks
339
- }}
340
- stamps={{
341
- sidebar: ['stamps'], // library kinds the stamps sidebar lists
342
- toolbar: ['embedpdf-standard:Check', 'embedpdf-standard:Cross'], // quick marks in the Insert bar
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"}