@betteroffice/docx 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +522 -3
  2. package/dist/{chunk-JHJLG4I6.mjs → chunk-5B4VMUXM.mjs} +1 -1
  3. package/dist/chunk-5JEQC76K.mjs +1 -0
  4. package/dist/chunk-7GJG6LLU.mjs +1 -0
  5. package/dist/chunk-FE6FWGBJ.mjs +1 -0
  6. package/dist/chunk-H5YYCN42.mjs +1 -0
  7. package/dist/chunk-NT2OYIJ4.mjs +57 -0
  8. package/dist/chunk-QE4ZDRKV.mjs +1 -0
  9. package/dist/chunk-VQXNED2P.mjs +2 -0
  10. package/dist/chunk-Z4HZIPBS.mjs +1 -0
  11. package/dist/collaboration/index.d.mts +8 -6
  12. package/dist/compareDocx-XXTZV4CG.mjs +1 -0
  13. package/dist/computeLayout-LWLAJWYA.mjs +1 -0
  14. package/dist/{content-CvEtPl0c.d.mts → content-BzoC6bSz.d.mts} +48 -8
  15. package/dist/core.d.mts +172 -9
  16. package/dist/core.mjs +1 -1
  17. package/dist/{displayListQueries-DF8F6fMl.d.mts → displayListQueries-BYFrBkw5.d.mts} +2 -2
  18. package/dist/docx/index.d.mts +5 -4
  19. package/dist/docx/index.mjs +1 -1
  20. package/dist/editor/index.d.mts +10 -8
  21. package/dist/editor/index.mjs +1 -1
  22. package/dist/{fontLoader-b8eicJKV.d.mts → fontLoader-CkXcPB1C.d.mts} +1 -1
  23. package/dist/{formatting-7zKCTu5a.d.mts → formatting-gqLFeJ9X.d.mts} +8 -0
  24. package/dist/{frameDelta-pA4jrQ1G.d.mts → frameDelta-CVt5FAXT.d.mts} +14 -3
  25. package/dist/generated/edit/docx_edit_bg.wasm +0 -0
  26. package/dist/generated/layout/docx_layout_bg.wasm +0 -0
  27. package/dist/generated/opc/ooxml_opc_bg.wasm +0 -0
  28. package/dist/generated/parse/docx_parse_bg.wasm +0 -0
  29. package/dist/{index-CHl2zLJ7.d.mts → index-BbZwdkPz.d.mts} +457 -29
  30. package/dist/layout/index.d.mts +6 -6
  31. package/dist/layout/index.mjs +1 -1
  32. package/dist/layout/pagination/index.d.mts +4 -4
  33. package/dist/layout/render/index.d.mts +12 -10
  34. package/dist/layout/render/index.mjs +1 -1
  35. package/dist/pagedExport-D3E4jZ-B.d.mts +907 -0
  36. package/dist/plugin-api/RenderedDomContext.d.mts +10 -9
  37. package/dist/plugin-api/RenderedDomContext.mjs +1 -1
  38. package/dist/plugin-api/index.d.mts +21 -4
  39. package/dist/plugin-api/resolveItemPositions.d.mts +3 -3
  40. package/dist/rezip-Bxiq4Z_R.d.mts +24 -0
  41. package/dist/{rustDisplayList-BJqaN9tt.d.mts → rustDisplayList-CwOoUn3t.d.mts} +2 -2
  42. package/dist/{rustMeasureSource-C0EZB055.d.mts → rustMeasureSource-CyBMcy1x.d.mts} +12 -4
  43. package/dist/{selectiveSave-CRh-_Y_C.d.mts → selectiveSave-r-NMK6aI.d.mts} +1 -22
  44. package/dist/styles/index.d.mts +2 -2
  45. package/dist/{styles-7e-mq3c-.d.mts → styles-AryxLAhx.d.mts} +1 -1
  46. package/dist/types/content.d.mts +2 -2
  47. package/dist/types/document.d.mts +6 -6
  48. package/dist/{types-C4A8mWjU.d.mts → types-B4jD0rrc.d.mts} +19 -2
  49. package/dist/utils/comments.d.mts +2 -2
  50. package/dist/utils/index.d.mts +7 -4
  51. package/dist/utils/index.mjs +1 -1
  52. package/dist/utils/stylePreview.d.mts +2 -2
  53. package/dist/wasm-G62X4WRW.mjs +1 -0
  54. package/dist/wasm-US6R7SWN.mjs +1 -0
  55. package/dist/wasm-edit.d.mts +246 -13
  56. package/dist/wasm-edit.mjs +1 -1
  57. package/dist/wasm-layout.d.mts +8 -1
  58. package/dist/wasm-layout.mjs +1 -1
  59. package/dist/yrs/index.d.mts +7 -5
  60. package/dist/yrs/index.mjs +1 -1
  61. package/dist/yrs/residentEngineWorker.mjs +1 -1
  62. package/package.json +2 -2
  63. package/dist/chunk-6LQJAKOC.mjs +0 -1
  64. package/dist/chunk-AFMVSI4Z.mjs +0 -57
  65. package/dist/chunk-GD4VNJ64.mjs +0 -1
  66. package/dist/chunk-WRWRCBLK.mjs +0 -1
  67. package/dist/chunk-WTFRNX5K.mjs +0 -1
  68. package/dist/wasm-7SOSHNGP.mjs +0 -1
  69. package/dist/wasm-ZQAVP7P2.mjs +0 -1
package/README.md CHANGED
@@ -53,9 +53,9 @@ provider.connect();
53
53
 
54
54
  `replica` can be a direct `YrsSession`, the worker-aware adapter returned by
55
55
  `createWorkerCollaborationReplica`, or the value published by the React
56
- editor's `collaboration.onReplica` callback. The provider speaks Yjs sync-v1;
57
- room routing, authentication, awareness, and reconnection policy remain
58
- transport concerns. Pass a persisted Yrs update as `collaboration.initialUpdate`
56
+ editor's `collaboration.onReplica` callback. The provider handles Yjs sync-v1 and
57
+ awareness; room routing, authentication, and reconnection remain transport
58
+ concerns. Pass a persisted Yrs update as `collaboration.initialUpdate`
59
59
  when a React editor joins an existing room so it hydrates the shared history
60
60
  instead of independently importing the same DOCX.
61
61
 
@@ -68,3 +68,522 @@ Package builds, demo startup, and CI run this step automatically.
68
68
 
69
69
  [JavaScript guide](https://docs.betteroffice.dev/docs/javascript) ·
70
70
  [Changelog](https://github.com/openooxml/betteroffice/blob/main/packages/docx/CHANGELOG.md) · Apache-2.0.
71
+
72
+ ### Undo capture modes and boundaries
73
+
74
+ `session.setUndoCaptureMode(mode)` selects how tracked local transactions are
75
+ combined into undo steps. `session.undoCaptureMode()` returns the current mode.
76
+
77
+ | Mode | Grouping |
78
+ | --- | --- |
79
+ | `auto` (default) | Edits within 500 ms coalesce; switching stories closes the group. |
80
+ | `manual` | Edits coalesce across pauses and stories until an explicit boundary. |
81
+
82
+ `session.addUndoBoundary()` closes the current group in any mode. Changing modes
83
+ also closes the group; setting the same mode again does not. Both operations
84
+ preserve undo/redo history and are safe before capture starts. Repeated boundaries
85
+ create no empty undo steps. Remote transactions remain outside local undo history.
86
+
87
+ ```ts
88
+ session.setUndoCaptureMode('manual');
89
+ session.insertText(firstLocation, 'Prefixo ');
90
+ session.insertText(secondLocation, 'Suffix');
91
+ session.addUndoBoundary();
92
+ session.deleteRange(markerRange);
93
+ session.addUndoBoundary();
94
+ session.setUndoCaptureMode('auto');
95
+ ```
96
+
97
+ The two insertions undo together; marker deletion is a separate step. Boundaries
98
+ are also available in automatic mode when a host action needs to be isolated from
99
+ surrounding typing. Undo/redo close capture as usual. Manual mode groups history;
100
+ edit batches provide atomic execution.
101
+ The host must close manual groups so later unrelated edits do not join them.
102
+
103
+ ### Paragraph identities
104
+
105
+ A session addresses paragraphs by session keys (`YrsLoc.paraId`); Word stores its
106
+ own paragraph ID (`w14:paraId`) in the file. A session anchor resolves on every
107
+ replica of one collaborative session, and each seeding open (`openDocx`, `seedFromDocx`, `documentToYrs`) starts a new one unless given a fixed
108
+ `generation`, as a deterministic shared seed needs. Source IDs are kept as authored, and every
109
+ paragraph authored in the session gets a fresh, valid ID that avoids every ID the
110
+ package already uses.
111
+
112
+ `saveYrsDocx(session)` saves a session opened from DOCX bytes and returns each saved
113
+ paragraph's persisted anchor, qualified by its package part:
114
+
115
+ ```ts
116
+ import { createYrsSession, saveYrsDocx } from '@betteroffice/docx/yrs';
117
+
118
+ const { secondParaId } = session.splitParagraph({ story: 'body', paraId, offset: 12 });
119
+ const saved = await saveYrsDocx(session);
120
+ const anchor = saved.paragraphs.find((p) => p.session.paraId === secondParaId)!.persisted;
121
+
122
+ const reopened = await createYrsSession();
123
+ reopened.openDocx(saved.bytes, true);
124
+ reopened.resolveParagraphAnchor(anchor); // { status: 'found', anchor: { kind: 'session', … } }
125
+ ```
126
+
127
+ `saved.conflicts` lists saved paragraphs whose ID the live session reassigned while the
128
+ save ran, such as by a duplicate repair after a remote update; their anchors find them in
129
+ the saved bytes only. The React editor's Save writes and records these IDs. `saveYrsDocx`
130
+ also returns anchors and preserves unchanged source parts. `session.persistParagraphIds()`
131
+ assigns IDs to source paragraphs without one across every story part, comments,
132
+ note separators and retained XML included, and repairs duplicates: source IDs and
133
+ saved IDs keep theirs over copies. It refuses, changing nothing, rather than guess at
134
+ an ambiguous comment reference. The change is replicated, stays out of undo history,
135
+ and later saves keep it; a save whose stories are otherwise unchanged patches the IDs
136
+ into the source bytes. `session.paragraphIdentities()` lists each paragraph's session,
137
+ persisted and exact-source anchors. A persisted anchor is scoped to the document the
138
+ host chose, and repeated source IDs resolve as `ambiguous`.
139
+
140
+ ### Selection state
141
+
142
+ `session.selectionContext(range)` aggregates the range for toolbars and
143
+ assistive technology. Toggle marks, including `superscript` and `subscript`,
144
+ are `true`, `false` or `'mixed'`; value marks such as `fontFamily`, `color` and
145
+ `highlight` are `null` when absent or not uniform. `highlight` reports Word's
146
+ highlight name (`'yellow'`) or an unmapped hex value. `toggleMark` accepts
147
+ `{ type: 'superscript' }` and `{ type: 'subscript' }`; adding one clears the other.
148
+
149
+ ### Version-checked edit batches
150
+
151
+ Read what you will target together with the session version, then apply a batch
152
+ against that version. Every step resolves against the state that was read; the
153
+ batch commits as one transaction or returns a typed refusal with nothing changed.
154
+
155
+ ```ts
156
+ const read = session.readParagraphs({ story: 'body', view: 'accepted' });
157
+ if (!read.ok) throw new Error(read.failure.message);
158
+ const clause = read.paragraphs.find((paragraph) => paragraph.text.startsWith('Term'))!;
159
+
160
+ const result = session.applyEdits({
161
+ expectVersion: read.version,
162
+ steps: [
163
+ {
164
+ op: 'replaceText',
165
+ target: { kind: 'search', text: '30 days', view: 'accepted',
166
+ within: { kind: 'paragraph', story: 'body', paraId: clause.paraId } },
167
+ text: '45 days',
168
+ expect: { text: '30 days' },
169
+ },
170
+ {
171
+ op: 'insertParagraphs',
172
+ target: { story: 'body', paraId: clause.paraId },
173
+ at: 'end',
174
+ paragraphs: [{ text: 'Renewal is automatic.' }],
175
+ },
176
+ ],
177
+ });
178
+ if (!result.ok) console.warn(result.failure.code, result.failure.message);
179
+ ```
180
+
181
+ `version()` changes with every committed change, local or remote (including
182
+ undo and redo), and when the session reopens its document. Tokens are scoped to
183
+ one session; after a lost response, read again rather than retrying with a new
184
+ version.
185
+
186
+ Offsets are UTF-16 positions into a paragraph's projected text. Each inline atom
187
+ (hard break, image, content control, note reference, field, other embed) is one
188
+ U+FFFC listed in `atoms`, tabs stay `\t`, and paragraph marks are excluded. The
189
+ `accepted` view includes pending insertions and hides pending deletions;
190
+ `original` does the reverse. `findText` is exact, case-sensitive and
191
+ paragraph-local, and a `search` target must match exactly once in its scope.
192
+
193
+ | Step | Effect |
194
+ | --- | --- |
195
+ | `insertText` | Inserts at the start or end of a target, formatted like typing there. |
196
+ | `replaceText` | Replaces a target's text inside one paragraph, keeping the paragraph. |
197
+ | `deleteText` | Deletes a target's text inside one paragraph. |
198
+ | `insertParagraphs` | Inserts complete paragraphs before or after an anchor, in the given or the anchor's style. |
199
+ | `deleteParagraphs` | Deletes a contiguous span of complete paragraphs without merging properties into a neighbour. |
200
+ | `setParagraphStyle` | Applies a document paragraph style with its paragraph and run formatting. |
201
+
202
+ Refusals carry a `code` (`stale-version`, `missing-target`, `ambiguous-target`,
203
+ `content-mismatch`, `overlapping-steps`, `locked-target`,
204
+ `tracked-revision-conflict`, `unsupported`, `invalid-step`, `limit-exceeded`),
205
+ the failing `stepIndex`, and the target. Editor hosts add `read-only` for an
206
+ editor that does not accept edits. Malformed requests throw.
207
+ `validateEdits(request)` runs the same checks and previews each step without
208
+ changing anything or reserving ids.
209
+
210
+ An applied batch is exactly one undo step in both capture modes; pass
211
+ `history: 'none'` to keep it out of undo history while preserving existing undo
212
+ and redo entries. `source: 'agent'` records provenance only. Add
213
+ `suggest: { author, date }` to a text step to record it as a tracked change.
214
+
215
+ Paragraph ids are session keys; inserted paragraphs get Word paragraph IDs as
216
+ typed ones do, so `saveYrsDocx` returns persisted anchors for them.
217
+
218
+ ### Structured export
219
+
220
+ Export read-only structured JSON or Markdown with block and inline anchors and
221
+ diagnostics.
222
+
223
+ ```ts
224
+ import { exportDocxMarkdown, exportDocxStructured } from '@betteroffice/docx';
225
+
226
+ const content = await exportDocxStructured(bytes, {
227
+ revisionView: 'accepted',
228
+ stories: ['body', 'headers', 'footers'],
229
+ });
230
+ for (const block of content.stories[0].blocks) {
231
+ if (block.kind === 'heading') console.log(block.heading.outlineLevel, block.anchor);
232
+ }
233
+ const { markdown, anchors } = await exportDocxMarkdown(bytes, { revisionView: 'markup' });
234
+ ```
235
+
236
+ On a live session, `session.exportStructured(options)` and
237
+ `session.exportMarkdown(options)` return `{ ok: true, version, content }` from one
238
+ read of the committed state: anchors resolve against that version, and nothing is
239
+ committed, flushed or published. Bytes exports and `renderDocxMarkdown(content)`
240
+ return snapshot content and throw `DocxExportError` for unusable options. A
241
+ refusal's `failure` is `{ code, target, message }`, with `target: null` when it
242
+ concerns no anchor; `unsupported` means the session holds no document content.
243
+ `session.headings(story)` lists a story's headings classified the same way; the
244
+ legacy `collectHeadings` tree walker from `@betteroffice/docx/utils` is deprecated
245
+ in its favour.
246
+
247
+ Content is `schemaVersion: 1`: ordered stories (`body` by default; `headers`,
248
+ `footers`, `footnotes`, `endnotes` and `comments` only when selected), each a list
249
+ of paragraphs, headings (outline level and whether it came from direct formatting,
250
+ the style chain, document defaults or a `HeadingN` style id), list items (numbering
251
+ format and the rendered marker, counted as Word counts them: numbering instances of
252
+ one abstract definition continue each other unless one overrides a start and so
253
+ begins its own list, levels begin at `w:start` and restart as `w:lvlRestart` says),
254
+ tables on the source grid with spans, skipped grid columns and vertical-merge
255
+ continuations, content controls, section breaks, and placeholders naming the
256
+ source element of other content. Inlines cover text, tabs, line,
257
+ page and column breaks where the source has them, note and comment references,
258
+ fields with their cached result in result order, hyperlinks and nested fields
259
+ included, all anchored to the field (a numeric field's result
260
+ blocks stay inside the field), images (alt text and the relationship of the part
261
+ that owns them) and inline controls. A header or footer part
262
+ referenced through several relationships is one story with every section that
263
+ uses it while the copies hold the same content, and one story per copy with a
264
+ diagnostic once they differ. `revisionView` is required: `accepted` and
265
+ `original` project pending revisions like `readParagraphs`, and `markup` keeps
266
+ both with `revisions` attribution on each inline, moves as `moveFrom` and
267
+ `moveTo`. Other revision history exports as an anchored `unsupported`
268
+ placeholder with an `unsupported-revision` diagnostic.
269
+
270
+ Anchors use the batch offsets and session keys: a paragraph anchor's `paraId` is the
271
+ batch target key, and a range is paragraph-local UTF-16 in the view it
272
+ names, one U+FFFC per atom, the same shape the edit batches take; ranges from a
273
+ session export address the version it returned. Content without a session
274
+ location (comment bodies, omitted raw XML) carries a `sourcePart` anchor: the
275
+ part, its SHA-256, and element-child ordinals into its XML. Content with no
276
+ location of its own carries `{ kind: 'unlocated', story, reason }`: the reason is
277
+ `duplicate-paragraph-id` (a paragraph whose Word paragraph ID repeats an earlier one
278
+ opens with a session key of its own, and edits and merges repair a key two paragraphs
279
+ share, so only shared state written outside the engine has it), `story-too-large`,
280
+ `missing-story` or
281
+ `provenance-unavailable`. Comment metadata reads
282
+ the session's comment store once a field has been written there, and the source
283
+ until then. Ids are deterministic export-tree paths, and a bytes export is
284
+ identical across runs. Exports admit one top-level block at a time, charge text before copying it, and stop at
285
+ `maxBlocks` (10,000 by default, every nested block counted) or `maxBytes`
286
+ (8,388,608 by default, on the compact JSON) at a whole top-level block, set
287
+ `truncated`, and end with a `truncated` diagnostic; the prefix already admitted is
288
+ kept, and a larger `maxBytes` never keeps fewer blocks. Of a story longer than
289
+ four units per remaining byte beyond one million, only the leading paragraphs
290
+ within that bound are read: those that fit are kept, then the export stops and says
291
+ so. A comment body, field payload, shape or table cell past the bound is not read
292
+ at all. Markdown keeps story separators, headings, lists, tables (pipe tables, or
293
+ entity-escaped HTML, nested where needed, for merged or multi-block cells), attributed insertions and deletions, and a
294
+ `<!-- docx-export:N -->` marker per block, nested ones included, mapped to its
295
+ anchor in `anchors`. Only http, https, mailto and internal-anchor targets are linked,
296
+ judged after entity and percent decoding, and `&` in a target is written `&amp;`; document text, titles and alt text never keep a
297
+ line break or unescaped HTML.
298
+
299
+ ### Page fragments
300
+
301
+ A paged export attaches a page map to the structured content: the physical page,
302
+ section and displayed page label showing each block and inline, and the body,
303
+ header, footer or note occurrence it sits in.
304
+
305
+ ```ts
306
+ import { exportDocxStructuredWithPages, renderDocxMarkdownWithPages } from '@betteroffice/docx';
307
+
308
+ const paged = await exportDocxStructuredWithPages(
309
+ bytes,
310
+ { revisionView: 'markup', stories: ['body', 'headers', 'footnotes'], includeGeometry: true },
311
+ { fonts: [{ key: 'sans', data: fontBase64 }], defaultChain: ['sans'] }
312
+ );
313
+ paged.layout.pages; // pageIndex, displayedLabel, sectionIndex, parityFiller, size
314
+ paged.layout.fragments; // pageIndex, occurrenceId, nodeId, slice, continuation flags, geometry
315
+ const { markdown } = await renderDocxMarkdownWithPages(paged, { pageMarkers: true });
316
+ ```
317
+
318
+ `exportDocxStructuredWithPages` opens the bytes in a private session, registers
319
+ exactly the fonts it is given, as base64 data, in a measurement font store of its
320
+ own (a requirement `family|bold|italic` takes the chain named by its key, else by
321
+ its lowercase family, else `defaultChain`; nothing is looked up on the system and
322
+ no editor's fonts change), lays out every section, header, footer and note, and
323
+ exports from that one state. Its options are plain JSON: `measurementDefaults`
324
+ (`fontFamily`, `fontSize`), `renderEnvironment` (`showHiddenText`,
325
+ `defaultTabStopTwips`) and `compatibility`, all fingerprinted in the map's
326
+ provenance. A requirement left with no font, including the `measurementDefaults`
327
+ family text naming none falls back to, or any text measured with stand-in metrics
328
+ is refused as `layout-unavailable`, as is a live layout whose font store was
329
+ cleared or added to since.
330
+ The snapshot map is deterministic for the same bytes, fonts and options and
331
+ carries no session token. `session.exportStructuredWithPages(options)` reads the
332
+ region layout a session retains and lays nothing out, flushes nothing and loads no
333
+ font; the layout must have lowered the current version from the session's own
334
+ stories, with section, settings and note metadata that describe it and a
335
+ registered font for every requirement. Its map carries `documentVersion` and a
336
+ `layoutVersion` to pass back as `expectLayoutVersion`; its references describe the
337
+ authoritative layout of that version, which a later edit may supersede before it is
338
+ painted.
339
+
340
+ `pageIndex` counts physical pages from zero, blank parity pages included, while
341
+ `displayedNumber`/`displayedLabel` follow the section's PAGE numbering (restarts,
342
+ continuation and Roman or letter formats). A header or footer part stays one exported story with an occurrence
343
+ on every page showing it; a note has an occurrence where its note area is, which
344
+ may differ from its reference's page. Text slices are ranges in the export's own
345
+ offsets, split where the node, paragraph, view or page changes; atoms (fields,
346
+ controls, images, note marks, breaks) are sliced whole, and one split across
347
+ pages carries `partial` and an `anchor-only` diagnostic. Table
348
+ fragments list their row window with `continuedFromPrevious`, `continuedOnNext`
349
+ and `repeatedHeader`, and each cell paragraph has fragments of its own for the lines
350
+ its cell shows (a line the cell cuts through is listed with a `clipped-content`
351
+ diagnostic). Pages are
352
+ laid out with revision markup, so while a laid-out story holds pending revisions a
353
+ paged export reads the `markup` view, and `accepted` or `original` return
354
+ `unsupported-revision-layout`. The other refusal codes are `stale-document`,
355
+ `stale-layout` (stale section, settings or note metadata, fonts, options or
356
+ `expectLayoutVersion`), `layout-unavailable`, `layout-not-converged` and
357
+ `unsupported`.
358
+ Geometry is off by default; rectangles are unzoomed CSS pixels (96 per inch) from
359
+ the physical page's top-left corner. `maxFragments` (100,000 by default) and
360
+ `maxLayoutBytes` bound the map separately and mark it `truncated`; mapping stops a
361
+ page past the limit, so a truncated map leaves out the diagnostics of later pages
362
+ and of paragraphs no page shows. With
363
+ `pageMarkers`, Markdown follows each block marker with
364
+ `<!-- docx-pages: 0=i 1=ii -->` (labels percent-encoded) and writes no page break
365
+ into the text; a map from other content is refused. The map is `schemaVersion: 1`,
366
+ versioned apart from the content: additive fields keep the version, and a change
367
+ existing readers would misread bumps it.
368
+
369
+ ### Content controls
370
+
371
+ List a document's content controls and fill its plain- and rich-text controls in
372
+ one version-checked batch.
373
+
374
+ ```ts
375
+ const read = session.listContentControls();
376
+ if (!read.ok) throw new Error(read.failure.message);
377
+ const name = read.content.controls.find((control) => control.tag === 'customer.name')!;
378
+
379
+ const result = session.applyEdits({
380
+ expectVersion: read.version,
381
+ steps: [
382
+ { op: 'setContentControlText', target: { kind: 'id', controlId: name.controlId }, text: 'Ada Lovelace' },
383
+ { op: 'setContentControlText', target: { kind: 'tag', tag: 'customer.address' }, text: '12 Example Street\nLondon' },
384
+ ],
385
+ });
386
+ ```
387
+
388
+ Controls come in document order (body, headers, footers, footnotes, endnotes,
389
+ comments; a control before the controls inside it), each with the structured
390
+ export's control metadata (`controlId`, `ooxmlId`, `controlType`, `tag`, `alias`,
391
+ `lock`, `showingPlaceholder`, `dataBound`) plus `placement`, `anchor`,
392
+ `parentControlId`, `value` (its text with tabs as `\t` and line and paragraph
393
+ breaks as LF, or `unavailable` with a reason), `multiLine` and `effectiveLock`,
394
+ which folds in the locks of the controls containing it. `stories` narrows the read
395
+ to some categories; `maxControls` (10,000) and `maxBytes` (8,388,608) refuse with
396
+ `limit-exceeded` rather than returning part of the list, and `complete: false`
397
+ includes coverage diagnostics. Tags, aliases and
398
+ `ooxmlId`s match exactly and case-sensitively, and `findContentControls` returns
399
+ every match. `listDocxContentControls(bytes)` and
400
+ `findDocxContentControls(bytes, query)` read bytes without a session and throw
401
+ `DocxContentControlsError` for refused options. Concurrent fills from
402
+ collaborators resolve last-writer-wins for an inline control and merge like
403
+ concurrent typing for a block control.
404
+
405
+ A step's `target` is `{ kind: 'id', controlId }`, `{ kind: 'tag', tag }` or
406
+ `{ kind: 'ooxmlId', ooxmlId }`. `controlId` is a locator scoped to the
407
+ version or snapshot it was read at; list again after a version change. Tags are the template author's names for
408
+ controls. `ooxmlId` is the control's authored `w:id`, the identity that survives
409
+ save and reopen. A tag or `ooxmlId` write searches every story regardless of read
410
+ filters and needs exactly one control in the document to carry it.
411
+
412
+ `setContentControlText` replaces the content of a plain- or rich-text control
413
+ with plain text and clears its placeholder flag everywhere it is recorded,
414
+ leaving tags, aliases, ids, bindings and every other property as captured. CRLF
415
+ becomes LF; LF is a line break, except in a rich-text block control, where it
416
+ starts a paragraph: surviving paragraphs keep their ids and properties and new
417
+ ones take the first paragraph's style defaults. A plain-text control accepts LF
418
+ only when its `w:text` sets `w:multiLine` and keeps its lines in one paragraph.
419
+ The text takes the formatting of the control's first text run, or the control's
420
+ own run properties while it shows its placeholder. Equal text is a no-op unless
421
+ the placeholder still shows or a plain-text control holds several paragraphs,
422
+ which the fill joins into one. Refusals
423
+ carry the batch code and a `reason`: `missing-control`, `missing-tag` or
424
+ `missing-ooxml-id`, `ambiguous-control-id`, `ambiguous-tag` or
425
+ `ambiguous-ooxml-id`, `content-locked`, `bound-control`,
426
+ `unsupported-control-type`, `unsupported-children` (content other than text,
427
+ tabs and line breaks, such as bookmarks, fields or tables), `nested-controls`,
428
+ `unknown-lock`, `unsupported-suggestion`, `provenance-unavailable` (the session
429
+ was not opened from DOCX bytes), `unsupported-story` (a control kept only in
430
+ source XML, such as a comment body), `multiline-not-allowed` and `invalid-text`.
431
+ Checkbox, dropdown and date controls keep `setContentControlValue`; a
432
+ string passed to it fills a text control through the same step, and
433
+ `clearContentControlValue` never erases a text control's text.
434
+
435
+ ### Compare documents into tracked changes
436
+
437
+ Compare an original and a revised DOCX and get back the original with the
438
+ body text differences as tracked insertions and deletions, attributed to the
439
+ author and date you pass:
440
+
441
+ ```ts
442
+ import { compareDocx } from '@betteroffice/docx';
443
+
444
+ const result = await compareDocx(original, revised, {
445
+ author: 'Contract review',
446
+ date: '2024-05-06T09:30:00+02:00',
447
+ granularity: 'word',
448
+ unsupported: 'fail',
449
+ });
450
+ if (result.ok) {
451
+ for (const change of result.changes) console.log(change.id, change.kind, change.revised.text);
452
+ } else {
453
+ console.warn(result.diagnostics.map((diagnostic) => diagnostic.code));
454
+ }
455
+ ```
456
+
457
+ `compareDocx` compares the text of body paragraphs whose structure is unchanged. Both
458
+ packages are inspected in full before anything is authored, and any blocking
459
+ difference refuses the comparison with `{ ok: false, diagnostics }`; no partial
460
+ redline is ever returned. `unsupported: 'report'` keeps inspecting and returns
461
+ every diagnostic, bounded by `limits.maxDiagnostics`. Blocking codes are
462
+ `invalid-options`, `invalid-docx`, `existing-revisions` (anywhere, headers and
463
+ property revisions included), `paragraph-insertion`, `paragraph-deletion`,
464
+ `paragraph-move`, `ambiguous-alignment`, `table-change`, `structure-change`,
465
+ `field-change`, `object-change`, `content-control-change`, `formatting-change`,
466
+ `unsupported-content` and `unsupported-formatting` (a changed paragraph holding
467
+ content, or run properties, that edits cannot carry, such as fields,
468
+ hyperlinks, bookmarks, objects, page breaks, patterned shading or `w:lang`),
469
+ `out-of-scope-change` (headers, footers, notes, comments), `opaque-part-change`,
470
+ `provenance-unavailable`, `limit-exceeded`, `diagnostics-truncated`,
471
+ `batch-refused`, `serialization-failed` and `roundtrip-mismatch`.
472
+ `metadata-difference` (informational: modified dates, revision-session ids and
473
+ document statistics, kept from the original) and `ambiguous-identity` (a
474
+ warning: repeated paragraph ids, so changes are addressed by position) accompany
475
+ a result. Every part whose content type or content is XML is scanned for
476
+ revisions, and wrappers such as custom XML elements, body-level markers and
477
+ every part must match exactly; an XML-typed part that cannot be read refuses the
478
+ comparison.
479
+
480
+ Paragraphs are aligned conservatively: text unique to one paragraph on each side
481
+ anchors the alignment, crossing anchors are moves, one paragraph against one
482
+ between anchors is a replacement, and several need shared words and exactly one
483
+ best correspondence. Clearing a paragraph's text is a deletion; the paragraph
484
+ mark stays. Differences are reported by Unicode word, keeping punctuation and
485
+ whitespace tokens, or with `granularity: 'char'` by grapheme cluster. Each
486
+ change is one tracked replacement: deleted text keeps its formatting and
487
+ inserted text takes the revised formatting. Changes are ordered by original
488
+ position, `id` is `change-0`, `change-1` and so on, and their spans address the
489
+ inputs by part, SHA-256 and element-child path with paragraph-local UTF-16
490
+ offsets. `date` must be RFC 3339 with an explicit offset and is recorded in UTC;
491
+ the clock is never read. `author` must not be blank.
492
+
493
+ Only the changed paragraphs' content is rewritten; their start tags and
494
+ properties, every other paragraph and every other part keep the original's
495
+ bytes, and identical inputs return the original bytes. Rewritten runs carry the
496
+ session's formatting vocabulary, so run properties a style supplies are written
497
+ as direct formatting, and complex-script fonts, sizes, bold and italic are
498
+ written only as the source states them. The result is reopened and checked
499
+ against both inputs before it is returned, with each run's effective formatting
500
+ read from the XML, style toggles such as bold combined across every level of a
501
+ style's `basedOn` chain; a difference refuses with `roundtrip-mismatch`.
502
+ Defaults, which are also the ceilings: 32 MiB per input, 128 MiB inflated,
503
+ 10,000 paragraphs per input and 1,048,576 UTF-16 units of text in both inputs,
504
+ counted across every story while the parts are read, 250,000 alignment cells,
505
+ 4,000,000 diff cells, 128 changes, 256 diagnostics, 64 MiB of staged state,
506
+ 8 MiB of result JSON without the output package (at least 1 KiB) and a 64 MiB
507
+ output, the no-op included.
508
+ Review in BetterOffice is tested.
509
+
510
+ ### Editor plugin contract (deprecated)
511
+
512
+ `@betteroffice/docx/plugin-api` keeps its geometry and sidebar building blocks:
513
+ `RenderedDomContext`, `PositionCoordinates`, `SidebarItem`,
514
+ `createRenderedDomContext`, `createCanvasHostProjector` and
515
+ `resolveItemPositions`. The snapshot-based `EditorPluginCore`,
516
+ `PluginPanelProps`, `PanelConfig` and `SidebarItemContext` are deprecated: they
517
+ pass serialized `Document` snapshots and have no host that manages their
518
+ lifecycle. Host plugins for the React editor use `defineDocxPlugin` and the
519
+ `plugins` prop of
520
+ [`@betteroffice/docx-react`](https://www.npmjs.com/package/@betteroffice/docx-react),
521
+ which provide versioned reads, granted commands and edit batches, lifecycle
522
+ events and cleanup.
523
+
524
+ ### Reanchor an existing comment
525
+
526
+ `session.setCommentRanges(commentId, ranges)` replaces only the sticky anchors of
527
+ an existing comment. The id, author, date, body, reply relationship and resolution
528
+ state remain intact. Ranges use the same paragraph locations as `addComment`;
529
+ one range may span paragraphs, and separate ranges may address different stories.
530
+
531
+ Every range must be non-empty, ordered, and within existing paragraphs. An empty
532
+ list, unknown comment/story/paragraph, or invalid offset throws before any content
533
+ changes. The host supplies the surviving text's new range.
534
+
535
+ If replacement removes all commented text, the host must explicitly choose a new
536
+ non-empty range or handle the comment's removal. Rejected reanchoring leaves the
537
+ comment unchanged and does not roll back a text replacement already performed.
538
+
539
+ `saveYrsDocx()` writes the current ranges under the comment's own ID, with its
540
+ author, date, body, replies and resolution.
541
+
542
+ Reanchoring joins the current local undo capture, so an immediate replacement and
543
+ reanchor can undo together. Comment edits participate in the session's history;
544
+ undoing comment changes conservatively invalidates all stories for saved anchors.
545
+
546
+ The undo manager retains comment item boundaries with their undo/redo entries so
547
+ anchors inside replaced text survive repeated history traversal with Yrs 0.27.
548
+ Discarding history releases its bookkeeping; undo/redo traverses a local snapshot
549
+ when those boundaries need restoration.
550
+
551
+ ### External drop positions
552
+
553
+ `RenderedDomContext.getPositionAtPoint(clientX, clientY)` queries the same canvas
554
+ hit testing used for the caret. Pass viewport coordinates, such as a drop event's
555
+ `clientX` and `clientY`. Page scroll and CSS zoom are resolved from the live page
556
+ geometry. The query never changes selection or focus.
557
+
558
+ The result contains `position`, `pageIndex`, and `region`. Header/footer hits also
559
+ carry `rId`; footnote/endnote hits carry `noteId`. Positions are local to that
560
+ region's display stream in the layout the context renders, and shift with every
561
+ edit.
562
+
563
+ In React, `editorRef.getPositionAtPoint(clientX, clientY)` runs the same query and
564
+ adds `version` and `target`: the collapsed accepted-view range at the point, keyed
565
+ by session paragraph keys, in the body, header, footer, note or table cell story
566
+ the point is in. Use it as a text step's target with `expectVersion: version`:
567
+
568
+ ```ts
569
+ const hit = editorRef.current!.getPositionAtPoint(event.clientX, event.clientY);
570
+ if (hit) {
571
+ await editorRef.current!.applyEdits({
572
+ expectVersion: hit.version,
573
+ steps: [{ op: 'insertText', target: hit.target, at: 'start', text: '{{customer.name}}' }],
574
+ });
575
+ }
576
+ ```
577
+
578
+ It returns `null` while typed or composed input is pending and until the painted
579
+ pages show the current version (retry after `flushPendingInput()` or on the next
580
+ frame), and a batch applied after further typing refuses with `stale-version`, so a
581
+ drop never lands at a shifted position. Plugins get the same query as
582
+ `context.geometry.getPositionAtPoint`, with the layout's `layoutId`. The lower-level
583
+ `editorRef.getEditorRef()?.displayPositionToYrsLoc(hit)` maps a hit to its live
584
+ `YrsLoc` (offsets that count text a pending deletion hides) without the version
585
+ check; passing a number to it retains body mapping.
586
+
587
+ Text runs and their editable content boxes are accepted. Page margins, images,
588
+ page gaps, points outside pages, and queries without ready canvas geometry return
589
+ `null`.
@@ -1,4 +1,4 @@
1
- import {d as d$1}from'./chunk-6LQJAKOC.mjs';import {a,b as b$1}from'./chunk-HLDJMU3R.mjs';import {a as a$1}from'./chunk-ACGKK7IB.mjs';async function pe(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Uint8Array){let e=new ArrayBuffer(n.byteLength);return new Uint8Array(e).set(n),e}if(n instanceof Blob)return n.arrayBuffer();throw new TypeError(`Unsupported DocxInput type: ${typeof n}`)}function P(n){let e=n.replace(/[^0-9a-fA-F]/g,"");if(e.length!==32)return null;let t=new Uint8Array(16);for(let r=0;r<16;r++){let i=Number.parseInt(e.slice(r*2,r*2+2),16);if(Number.isNaN(i))return null;t[r]=i;}let o=new Uint8Array(16);for(let r=0;r<16;r++)o[r]=t[15-r];return o}function B(n){return n?P(n)!==null:false}function v(n,e){let t=P(e);if(!t)throw new Error(`Invalid embedded-font key: "${e}"`);let o=new Uint8Array(n.slice(0)),r=Math.min(32,o.length);for(let i=0;i<r;i++)o[i]^=t[i%16];return o.buffer}var W={calibri:{googleFont:"Carlito",category:"sans-serif",fallbackStack:["Calibri","Carlito","Arial","Helvetica","sans-serif"],singleLineRatio:1.2207},cambria:{googleFont:"Caladea",category:"serif",fallbackStack:["Cambria","Caladea","Georgia","serif"],singleLineRatio:1.2676},arial:{googleFont:"Arimo",category:"sans-serif",fallbackStack:["Arial","Arimo","Helvetica","sans-serif"],singleLineRatio:1.1172},"times new roman":{googleFont:"Tinos",category:"serif",fallbackStack:["Times New Roman","Tinos","Times","serif"],singleLineRatio:1.1074},"courier new":{googleFont:"Cousine",category:"monospace",fallbackStack:["Courier New","Cousine","Courier","monospace"],singleLineRatio:1.1328},georgia:{googleFont:"Tinos",category:"serif",fallbackStack:["Georgia","Tinos","Times New Roman","serif"],singleLineRatio:1.1362},verdana:{googleFont:"Open Sans",category:"sans-serif",fallbackStack:["Verdana","Open Sans","Arial","sans-serif"],singleLineRatio:1.2153},tahoma:{googleFont:"Open Sans",category:"sans-serif",fallbackStack:["Tahoma","Open Sans","Arial","sans-serif"],singleLineRatio:1.2075},"trebuchet ms":{googleFont:"Fira Sans",category:"sans-serif",fallbackStack:["Trebuchet MS","Fira Sans","Arial","sans-serif"],singleLineRatio:1.1431},"comic sans ms":{googleFont:"Comic Neue",category:"cursive",fallbackStack:["Comic Sans MS","Comic Neue","cursive"],singleLineRatio:1.3936},impact:{googleFont:"Anton",category:"sans-serif",fallbackStack:["Impact","Anton","Arial Black","sans-serif"],singleLineRatio:1.2197},"palatino linotype":{googleFont:"EB Garamond",category:"serif",fallbackStack:["Palatino Linotype","EB Garamond","Palatino","Georgia","serif"],singleLineRatio:1.0259},"book antiqua":{googleFont:"EB Garamond",category:"serif",fallbackStack:["Book Antiqua","EB Garamond","Palatino","Georgia","serif"],singleLineRatio:1.0259},garamond:{googleFont:"EB Garamond",category:"serif",fallbackStack:["Garamond","EB Garamond","Georgia","serif"],singleLineRatio:1.068},"century gothic":{googleFont:"Questrial",category:"sans-serif",fallbackStack:["Century Gothic","Questrial","Arial","sans-serif"],singleLineRatio:1.1611},"lucida sans":{googleFont:"Open Sans",category:"sans-serif",fallbackStack:["Lucida Sans","Open Sans","Arial","sans-serif"],singleLineRatio:1.1655},"lucida console":{googleFont:"Cousine",category:"monospace",fallbackStack:["Lucida Console","Cousine","Courier New","monospace"],singleLineRatio:1.1387},consolas:{googleFont:"Inconsolata",category:"monospace",fallbackStack:["Consolas","Inconsolata","Cousine","Courier New","monospace"],singleLineRatio:1.1626},"ms mincho":{googleFont:"Noto Serif JP",category:"serif",fallbackStack:["MS Mincho","Noto Serif JP","serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \u660E\u671D":{googleFont:"Noto Serif JP",category:"serif",fallbackStack:["MS Mincho","\uFF2D\uFF33 \u660E\u671D","Noto Serif JP","serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \uFF50\u660E\u671D":{googleFont:"Noto Serif JP",category:"serif",fallbackStack:["MS PMincho","\uFF2D\uFF33 \uFF30\u660E\u671D","Noto Serif JP","serif"],singleLineRatio:1.15},"ms gothic":{googleFont:"Noto Sans JP",category:"sans-serif",fallbackStack:["MS Gothic","Noto Sans JP","sans-serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \u30B4\u30B7\u30C3\u30AF":{googleFont:"Noto Sans JP",category:"sans-serif",fallbackStack:["MS Gothic","\uFF2D\uFF33 \u30B4\u30B7\u30C3\u30AF","Noto Sans JP","sans-serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \uFF50\u30B4\u30B7\u30C3\u30AF":{googleFont:"Noto Sans JP",category:"sans-serif",fallbackStack:["MS PGothic","\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF","Noto Sans JP","sans-serif"],singleLineRatio:1.15},simhei:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["SimHei","Noto Sans SC","sans-serif"],singleLineRatio:1.15},simsun:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["SimSun","Noto Serif SC","serif"],singleLineRatio:1.15},"malgun gothic":{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Malgun Gothic","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\u5B8B\u4F53:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["SimSun","\u5B8B\u4F53","Noto Serif SC","serif"],singleLineRatio:1.15},\u9ED1\u4F53:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["SimHei","\u9ED1\u4F53","Noto Sans SC","sans-serif"],singleLineRatio:1.15},\u5FAE\u8F6F\u96C5\u9ED1:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["Microsoft YaHei","\u5FAE\u8F6F\u96C5\u9ED1","Noto Sans SC","sans-serif"],singleLineRatio:1.15},\u7B49\u7EBF:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["DengXian","\u7B49\u7EBF","Noto Sans SC","sans-serif"],singleLineRatio:1.15},\u4EFF\u5B8B:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["FangSong","\u4EFF\u5B8B","Noto Serif SC","serif"],singleLineRatio:1.15},\u6977\u4F53:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["KaiTi","\u6977\u4F53","Noto Serif SC","serif"],singleLineRatio:1.15},\u65B0\u7D30\u660E\u9AD4:{googleFont:"Noto Serif TC",category:"serif",fallbackStack:["PMingLiU","\u65B0\u7D30\u660E\u9AD4","Noto Serif TC","serif"],singleLineRatio:1.15},\u7D30\u660E\u9AD4:{googleFont:"Noto Serif TC",category:"serif",fallbackStack:["MingLiU","\u7D30\u660E\u9AD4","Noto Serif TC","serif"],singleLineRatio:1.15},\u5FAE\u8EDF\u6B63\u9ED1\u9AD4:{googleFont:"Noto Sans TC",category:"sans-serif",fallbackStack:["Microsoft JhengHei","\u5FAE\u8EDF\u6B63\u9ED1\u9AD4","Noto Sans TC","sans-serif"],singleLineRatio:1.15},\u6A19\u6977\u9AD4:{googleFont:"Noto Serif TC",category:"serif",fallbackStack:["DFKai-SB","\u6A19\u6977\u9AD4","Noto Serif TC","serif"],singleLineRatio:1.15},"\uB9D1\uC740 \uACE0\uB515":{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Malgun Gothic","\uB9D1\uC740 \uACE0\uB515","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\uAD74\uB9BC:{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Gulim","\uAD74\uB9BC","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\uB3CB\uC6C0:{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Dotum","\uB3CB\uC6C0","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\uBC14\uD0D5:{googleFont:"Noto Serif KR",category:"serif",fallbackStack:["Batang","\uBC14\uD0D5","Noto Serif KR","serif"],singleLineRatio:1.15},\uAD81\uC11C:{googleFont:"Noto Serif KR",category:"serif",fallbackStack:["Gungsuh","\uAD81\uC11C","Noto Serif KR","serif"],singleLineRatio:1.15}},q={"sans-serif":"Arial, Helvetica, sans-serif",serif:"Times New Roman, Times, serif",monospace:"Courier New, Courier, monospace",cursive:"cursive",fantasy:"fantasy","system-ui":"system-ui, sans-serif"};function J(n){let e=n.toLowerCase();return e.includes("mono")||e.includes("courier")||e.includes("consolas")||e.includes("console")||e.includes("code")||e.includes("terminal")?"monospace":e.includes("times")||e.includes("georgia")||e.includes("garamond")||e.includes("palatino")||e.includes("baskerville")||e.includes("bodoni")||e.includes("cambria")||e.includes("mincho")||e.includes("\u660E\u671D")||e.includes("\u660E\u9AD4")||e.includes("\u5B8B")||e.includes("ming")||e.includes("song")||e.includes("serif")?"serif":e.includes("script")||e.includes("cursive")||e.includes("comic")||e.includes("brush")||e.includes("hand")?"cursive":"sans-serif"}var Y={"microsoft yahei":"\u5FAE\u8F6F\u96C5\u9ED1",dengxian:"\u7B49\u7EBF",fangsong:"\u4EFF\u5B8B",kaiti:"\u6977\u4F53",pmingliu:"\u65B0\u7D30\u660E\u9AD4",mingliu:"\u7D30\u660E\u9AD4","microsoft jhenghei":"\u5FAE\u8EDF\u6B63\u9ED1\u9AD4","dfkai-sb":"\u6A19\u6977\u9AD4",gulim:"\uAD74\uB9BC",dotum:"\uB3CB\uC6C0",batang:"\uBC14\uD0D5",gungsuh:"\uAD81\uC11C",meiryo:"ms gothic",\u30E1\u30A4\u30EA\u30AA:"ms gothic","yu gothic":"ms gothic",\u6E38\u30B4\u30B7\u30C3\u30AF:"ms gothic","yu mincho":"ms mincho",\u6E38\u660E\u671D:"ms mincho"};function O(n){let e=n.trim().toLowerCase(),t=W[Y[e]??e];if(t)return {googleFont:t.googleFont,cssFallback:t.fallbackStack.map(D).join(", "),originalFont:n,hasGoogleEquivalent:true,singleLineRatio:t.singleLineRatio};let o=J(n),r=q[o];return {googleFont:null,cssFallback:`${D(n)}, ${r}`,originalFont:n,hasGoogleEquivalent:false,singleLineRatio:1.15}}var V={"\n":"\\a ","\r":"\\d ","\f":"\\c "};function X(n){return n.replace(/["\\]/g,"\\$&").replace(/[\n\r\f]/g,e=>V[e])}function D(n){return ["serif","sans-serif","monospace","cursive","fantasy","system-ui"].includes(n.toLowerCase())?n:/[\s,'"()]/.test(n)?`"${X(n)}"`:n}var g=new Set,m=new Map,C=new Set,f=new Map,w=new Set,b=new Set,d=false,L=true,h=new Set,E=new Set;function p(n,e){let t=n instanceof Error?n.message:String(n),o=new Error(`[font] ${e}: ${t}`,{cause:n});if(b.size>0)for(let r of b)try{r(o);}catch(i){console.error("Font error subscriber threw:",i);}else console.warn(o.message);}function M(n,e="normal",t="normal"){return `${n.trim()}|${e}|${t}`}function R(n){return h.has(n)?false:E.has(n)?true:re(n)?(E.add(n),queueMicrotask(()=>T([n])),true):false}function Q(n){let e=`${n}|`,t=[];for(let[o,r]of f)o.startsWith(e)&&t.push(r);return t}function $(n,e){C.add(n),g.add(e),T([e]);}function Z(n,e=[400,700],t=["normal","italic"]){let o=encodeURIComponent(n),r=[];for(let a of t){let s=a==="italic"?1:0;for(let c of e)r.push(`${s},${c}`);}r.sort();let i=r.join(";");return `https://fonts.googleapis.com/css2?family=${o}:ital,wght@${i}&display=swap`}async function A(n,e){if(typeof document>"u")return false;let t=n.trim();if(g.has(t))return true;let o=m.get(t);if(o)return o;if(!e&&R(t))return true;let r=Q(t);if(!L&&r.length===0)return false;let i=(async()=>{d=true;try{if(r.length>0&&(await Promise.all(r),g.has(t)||!e&&R(t)))return !0;if(!L)return !1;let a=Z(t,e?.weights,e?.styles),s=document.createElement("link");return s.rel="stylesheet",s.href=a,await new Promise(l=>{let u=setTimeout(()=>l(!1),5e3);s.onload=()=>{clearTimeout(u),l(!0);},s.onerror=()=>{clearTimeout(u),l(!1);},document.head.appendChild(s);})?(await I(t,3e3),g.add(t),T([t]),!0):!1}catch(a){return p(a,`failed to load "${t}"`),false}finally{m.delete(t),m.size===0&&f.size===0&&(d=false);}})();return m.set(t,i),i}async function ee(n,e){let t=n.filter(o=>!g.has(o.trim()));t.length!==0&&await Promise.all(t.map(o=>A(o,e)));}function ne(n){let e=n.trim();return g.has(e)||E.has(e)}function Re(n){L=n;}function Ae(){return L}function Ce(){return d}function Te(){return Array.from(new Set([...g,...E]))}function Ie(n){return w.add(n),()=>{w.delete(n);}}function T(n){for(let e of w)try{e(n);}catch(t){p(t,"load callback threw");}}function _e(n){return b.add(n),()=>{b.delete(n);}}async function I(n,e){if("fonts"in document)try{let t=`400 16px "${n}"`;return await Promise.race([document.fonts.load(t),new Promise(o=>setTimeout(o,e))]),document.fonts.check(t)}catch{}return await new Promise(t=>setTimeout(t,100)),true}var F,U=new Map,te="abcdefghijklmnopqrstuvwxyz0123456789";function oe(){return F===void 0&&(F=document.createElement("canvas").getContext("2d"),F&&(F.textBaseline="top")),F}function re(n,e="sans-serif"){if(typeof document>"u")return false;let t=oe();if(!t)return false;let o=i=>(t.font=i,t.measureText(te).width),r=i=>{let a=U.get(i);return a===void 0&&(a=o(`72px ${i}`),U.set(i,a)),o(`72px "${n}", ${i}`)!==a};return r(e)||r(e==="sans-serif"?"serif":"sans-serif")}var ie={"<":"\\3c ",">":"\\3e ","\n":"\\a ","\r":"\\d ","\f":"\\c "},K=n=>n.replace(/["\\]/g,"\\$&").replace(/[<>\n\r\f]/g,e=>ie[e]);async function H(n,e,t){if(typeof document>"u")return false;let o=n.trim(),r=t?.style??"normal",i=M(o,t?.weight,r);if(h.add(o),C.has(i))return true;let a=f.get(i);if(a)return a;let s=(async()=>{d=true;try{let c=new Blob([e],{type:"font/ttf"}),l=URL.createObjectURL(c),u=document.createElement("style");return u.textContent=`
1
+ import {d as d$1}from'./chunk-QE4ZDRKV.mjs';import {a,b as b$1}from'./chunk-HLDJMU3R.mjs';import {a as a$1}from'./chunk-ACGKK7IB.mjs';async function pe(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Uint8Array){let e=new ArrayBuffer(n.byteLength);return new Uint8Array(e).set(n),e}if(n instanceof Blob)return n.arrayBuffer();throw new TypeError(`Unsupported DocxInput type: ${typeof n}`)}function P(n){let e=n.replace(/[^0-9a-fA-F]/g,"");if(e.length!==32)return null;let t=new Uint8Array(16);for(let r=0;r<16;r++){let i=Number.parseInt(e.slice(r*2,r*2+2),16);if(Number.isNaN(i))return null;t[r]=i;}let o=new Uint8Array(16);for(let r=0;r<16;r++)o[r]=t[15-r];return o}function B(n){return n?P(n)!==null:false}function v(n,e){let t=P(e);if(!t)throw new Error(`Invalid embedded-font key: "${e}"`);let o=new Uint8Array(n.slice(0)),r=Math.min(32,o.length);for(let i=0;i<r;i++)o[i]^=t[i%16];return o.buffer}var W={calibri:{googleFont:"Carlito",category:"sans-serif",fallbackStack:["Calibri","Carlito","Arial","Helvetica","sans-serif"],singleLineRatio:1.2207},cambria:{googleFont:"Caladea",category:"serif",fallbackStack:["Cambria","Caladea","Georgia","serif"],singleLineRatio:1.2676},arial:{googleFont:"Arimo",category:"sans-serif",fallbackStack:["Arial","Arimo","Helvetica","sans-serif"],singleLineRatio:1.1172},"times new roman":{googleFont:"Tinos",category:"serif",fallbackStack:["Times New Roman","Tinos","Times","serif"],singleLineRatio:1.1074},"courier new":{googleFont:"Cousine",category:"monospace",fallbackStack:["Courier New","Cousine","Courier","monospace"],singleLineRatio:1.1328},georgia:{googleFont:"Tinos",category:"serif",fallbackStack:["Georgia","Tinos","Times New Roman","serif"],singleLineRatio:1.1362},verdana:{googleFont:"Open Sans",category:"sans-serif",fallbackStack:["Verdana","Open Sans","Arial","sans-serif"],singleLineRatio:1.2153},tahoma:{googleFont:"Open Sans",category:"sans-serif",fallbackStack:["Tahoma","Open Sans","Arial","sans-serif"],singleLineRatio:1.2075},"trebuchet ms":{googleFont:"Fira Sans",category:"sans-serif",fallbackStack:["Trebuchet MS","Fira Sans","Arial","sans-serif"],singleLineRatio:1.1431},"comic sans ms":{googleFont:"Comic Neue",category:"cursive",fallbackStack:["Comic Sans MS","Comic Neue","cursive"],singleLineRatio:1.3936},impact:{googleFont:"Anton",category:"sans-serif",fallbackStack:["Impact","Anton","Arial Black","sans-serif"],singleLineRatio:1.2197},"palatino linotype":{googleFont:"EB Garamond",category:"serif",fallbackStack:["Palatino Linotype","EB Garamond","Palatino","Georgia","serif"],singleLineRatio:1.0259},"book antiqua":{googleFont:"EB Garamond",category:"serif",fallbackStack:["Book Antiqua","EB Garamond","Palatino","Georgia","serif"],singleLineRatio:1.0259},garamond:{googleFont:"EB Garamond",category:"serif",fallbackStack:["Garamond","EB Garamond","Georgia","serif"],singleLineRatio:1.068},"century gothic":{googleFont:"Questrial",category:"sans-serif",fallbackStack:["Century Gothic","Questrial","Arial","sans-serif"],singleLineRatio:1.1611},"lucida sans":{googleFont:"Open Sans",category:"sans-serif",fallbackStack:["Lucida Sans","Open Sans","Arial","sans-serif"],singleLineRatio:1.1655},"lucida console":{googleFont:"Cousine",category:"monospace",fallbackStack:["Lucida Console","Cousine","Courier New","monospace"],singleLineRatio:1.1387},consolas:{googleFont:"Inconsolata",category:"monospace",fallbackStack:["Consolas","Inconsolata","Cousine","Courier New","monospace"],singleLineRatio:1.1626},"ms mincho":{googleFont:"Noto Serif JP",category:"serif",fallbackStack:["MS Mincho","Noto Serif JP","serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \u660E\u671D":{googleFont:"Noto Serif JP",category:"serif",fallbackStack:["MS Mincho","\uFF2D\uFF33 \u660E\u671D","Noto Serif JP","serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \uFF50\u660E\u671D":{googleFont:"Noto Serif JP",category:"serif",fallbackStack:["MS PMincho","\uFF2D\uFF33 \uFF30\u660E\u671D","Noto Serif JP","serif"],singleLineRatio:1.15},"ms gothic":{googleFont:"Noto Sans JP",category:"sans-serif",fallbackStack:["MS Gothic","Noto Sans JP","sans-serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \u30B4\u30B7\u30C3\u30AF":{googleFont:"Noto Sans JP",category:"sans-serif",fallbackStack:["MS Gothic","\uFF2D\uFF33 \u30B4\u30B7\u30C3\u30AF","Noto Sans JP","sans-serif"],singleLineRatio:1.15},"\uFF4D\uFF53 \uFF50\u30B4\u30B7\u30C3\u30AF":{googleFont:"Noto Sans JP",category:"sans-serif",fallbackStack:["MS PGothic","\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF","Noto Sans JP","sans-serif"],singleLineRatio:1.15},simhei:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["SimHei","Noto Sans SC","sans-serif"],singleLineRatio:1.15},simsun:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["SimSun","Noto Serif SC","serif"],singleLineRatio:1.15},"malgun gothic":{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Malgun Gothic","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\u5B8B\u4F53:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["SimSun","\u5B8B\u4F53","Noto Serif SC","serif"],singleLineRatio:1.15},\u9ED1\u4F53:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["SimHei","\u9ED1\u4F53","Noto Sans SC","sans-serif"],singleLineRatio:1.15},\u5FAE\u8F6F\u96C5\u9ED1:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["Microsoft YaHei","\u5FAE\u8F6F\u96C5\u9ED1","Noto Sans SC","sans-serif"],singleLineRatio:1.15},\u7B49\u7EBF:{googleFont:"Noto Sans SC",category:"sans-serif",fallbackStack:["DengXian","\u7B49\u7EBF","Noto Sans SC","sans-serif"],singleLineRatio:1.15},\u4EFF\u5B8B:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["FangSong","\u4EFF\u5B8B","Noto Serif SC","serif"],singleLineRatio:1.15},\u6977\u4F53:{googleFont:"Noto Serif SC",category:"serif",fallbackStack:["KaiTi","\u6977\u4F53","Noto Serif SC","serif"],singleLineRatio:1.15},\u65B0\u7D30\u660E\u9AD4:{googleFont:"Noto Serif TC",category:"serif",fallbackStack:["PMingLiU","\u65B0\u7D30\u660E\u9AD4","Noto Serif TC","serif"],singleLineRatio:1.15},\u7D30\u660E\u9AD4:{googleFont:"Noto Serif TC",category:"serif",fallbackStack:["MingLiU","\u7D30\u660E\u9AD4","Noto Serif TC","serif"],singleLineRatio:1.15},\u5FAE\u8EDF\u6B63\u9ED1\u9AD4:{googleFont:"Noto Sans TC",category:"sans-serif",fallbackStack:["Microsoft JhengHei","\u5FAE\u8EDF\u6B63\u9ED1\u9AD4","Noto Sans TC","sans-serif"],singleLineRatio:1.15},\u6A19\u6977\u9AD4:{googleFont:"Noto Serif TC",category:"serif",fallbackStack:["DFKai-SB","\u6A19\u6977\u9AD4","Noto Serif TC","serif"],singleLineRatio:1.15},"\uB9D1\uC740 \uACE0\uB515":{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Malgun Gothic","\uB9D1\uC740 \uACE0\uB515","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\uAD74\uB9BC:{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Gulim","\uAD74\uB9BC","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\uB3CB\uC6C0:{googleFont:"Noto Sans KR",category:"sans-serif",fallbackStack:["Dotum","\uB3CB\uC6C0","Noto Sans KR","sans-serif"],singleLineRatio:1.15},\uBC14\uD0D5:{googleFont:"Noto Serif KR",category:"serif",fallbackStack:["Batang","\uBC14\uD0D5","Noto Serif KR","serif"],singleLineRatio:1.15},\uAD81\uC11C:{googleFont:"Noto Serif KR",category:"serif",fallbackStack:["Gungsuh","\uAD81\uC11C","Noto Serif KR","serif"],singleLineRatio:1.15}},q={"sans-serif":"Arial, Helvetica, sans-serif",serif:"Times New Roman, Times, serif",monospace:"Courier New, Courier, monospace",cursive:"cursive",fantasy:"fantasy","system-ui":"system-ui, sans-serif"};function J(n){let e=n.toLowerCase();return e.includes("mono")||e.includes("courier")||e.includes("consolas")||e.includes("console")||e.includes("code")||e.includes("terminal")?"monospace":e.includes("times")||e.includes("georgia")||e.includes("garamond")||e.includes("palatino")||e.includes("baskerville")||e.includes("bodoni")||e.includes("cambria")||e.includes("mincho")||e.includes("\u660E\u671D")||e.includes("\u660E\u9AD4")||e.includes("\u5B8B")||e.includes("ming")||e.includes("song")||e.includes("serif")?"serif":e.includes("script")||e.includes("cursive")||e.includes("comic")||e.includes("brush")||e.includes("hand")?"cursive":"sans-serif"}var Y={"microsoft yahei":"\u5FAE\u8F6F\u96C5\u9ED1",dengxian:"\u7B49\u7EBF",fangsong:"\u4EFF\u5B8B",kaiti:"\u6977\u4F53",pmingliu:"\u65B0\u7D30\u660E\u9AD4",mingliu:"\u7D30\u660E\u9AD4","microsoft jhenghei":"\u5FAE\u8EDF\u6B63\u9ED1\u9AD4","dfkai-sb":"\u6A19\u6977\u9AD4",gulim:"\uAD74\uB9BC",dotum:"\uB3CB\uC6C0",batang:"\uBC14\uD0D5",gungsuh:"\uAD81\uC11C",meiryo:"ms gothic",\u30E1\u30A4\u30EA\u30AA:"ms gothic","yu gothic":"ms gothic",\u6E38\u30B4\u30B7\u30C3\u30AF:"ms gothic","yu mincho":"ms mincho",\u6E38\u660E\u671D:"ms mincho"};function O(n){let e=n.trim().toLowerCase(),t=W[Y[e]??e];if(t)return {googleFont:t.googleFont,cssFallback:t.fallbackStack.map(D).join(", "),originalFont:n,hasGoogleEquivalent:true,singleLineRatio:t.singleLineRatio};let o=J(n),r=q[o];return {googleFont:null,cssFallback:`${D(n)}, ${r}`,originalFont:n,hasGoogleEquivalent:false,singleLineRatio:1.15}}var V={"\n":"\\a ","\r":"\\d ","\f":"\\c "};function X(n){return n.replace(/["\\]/g,"\\$&").replace(/[\n\r\f]/g,e=>V[e])}function D(n){return ["serif","sans-serif","monospace","cursive","fantasy","system-ui"].includes(n.toLowerCase())?n:/[\s,'"()]/.test(n)?`"${X(n)}"`:n}var g=new Set,m=new Map,C=new Set,f=new Map,w=new Set,b=new Set,d=false,L=true,h=new Set,E=new Set;function p(n,e){let t=n instanceof Error?n.message:String(n),o=new Error(`[font] ${e}: ${t}`,{cause:n});if(b.size>0)for(let r of b)try{r(o);}catch(i){console.error("Font error subscriber threw:",i);}else console.warn(o.message);}function M(n,e="normal",t="normal"){return `${n.trim()}|${e}|${t}`}function R(n){return h.has(n)?false:E.has(n)?true:re(n)?(E.add(n),queueMicrotask(()=>T([n])),true):false}function Q(n){let e=`${n}|`,t=[];for(let[o,r]of f)o.startsWith(e)&&t.push(r);return t}function $(n,e){C.add(n),g.add(e),T([e]);}function Z(n,e=[400,700],t=["normal","italic"]){let o=encodeURIComponent(n),r=[];for(let a of t){let s=a==="italic"?1:0;for(let c of e)r.push(`${s},${c}`);}r.sort();let i=r.join(";");return `https://fonts.googleapis.com/css2?family=${o}:ital,wght@${i}&display=swap`}async function A(n,e){if(typeof document>"u")return false;let t=n.trim();if(g.has(t))return true;let o=m.get(t);if(o)return o;if(!e&&R(t))return true;let r=Q(t);if(!L&&r.length===0)return false;let i=(async()=>{d=true;try{if(r.length>0&&(await Promise.all(r),g.has(t)||!e&&R(t)))return !0;if(!L)return !1;let a=Z(t,e?.weights,e?.styles),s=document.createElement("link");return s.rel="stylesheet",s.href=a,await new Promise(l=>{let u=setTimeout(()=>l(!1),5e3);s.onload=()=>{clearTimeout(u),l(!0);},s.onerror=()=>{clearTimeout(u),l(!1);},document.head.appendChild(s);})?(await I(t,3e3),g.add(t),T([t]),!0):!1}catch(a){return p(a,`failed to load "${t}"`),false}finally{m.delete(t),m.size===0&&f.size===0&&(d=false);}})();return m.set(t,i),i}async function ee(n,e){let t=n.filter(o=>!g.has(o.trim()));t.length!==0&&await Promise.all(t.map(o=>A(o,e)));}function ne(n){let e=n.trim();return g.has(e)||E.has(e)}function Re(n){L=n;}function Ae(){return L}function Ce(){return d}function Te(){return Array.from(new Set([...g,...E]))}function Ie(n){return w.add(n),()=>{w.delete(n);}}function T(n){for(let e of w)try{e(n);}catch(t){p(t,"load callback threw");}}function _e(n){return b.add(n),()=>{b.delete(n);}}async function I(n,e){if("fonts"in document)try{let t=`400 16px "${n}"`;return await Promise.race([document.fonts.load(t),new Promise(o=>setTimeout(o,e))]),document.fonts.check(t)}catch{}return await new Promise(t=>setTimeout(t,100)),true}var F,U=new Map,te="abcdefghijklmnopqrstuvwxyz0123456789";function oe(){return F===void 0&&(F=document.createElement("canvas").getContext("2d"),F&&(F.textBaseline="top")),F}function re(n,e="sans-serif"){if(typeof document>"u")return false;let t=oe();if(!t)return false;let o=i=>(t.font=i,t.measureText(te).width),r=i=>{let a=U.get(i);return a===void 0&&(a=o(`72px ${i}`),U.set(i,a)),o(`72px "${n}", ${i}`)!==a};return r(e)||r(e==="sans-serif"?"serif":"sans-serif")}var ie={"<":"\\3c ",">":"\\3e ","\n":"\\a ","\r":"\\d ","\f":"\\c "},K=n=>n.replace(/["\\]/g,"\\$&").replace(/[<>\n\r\f]/g,e=>ie[e]);async function H(n,e,t){if(typeof document>"u")return false;let o=n.trim(),r=t?.style??"normal",i=M(o,t?.weight,r);if(h.add(o),C.has(i))return true;let a=f.get(i);if(a)return a;let s=(async()=>{d=true;try{let c=new Blob([e],{type:"font/ttf"}),l=URL.createObjectURL(c),u=document.createElement("style");return u.textContent=`
2
2
  @font-face {
3
3
  font-family: "${K(o)}";
4
4
  src: url(${l}) format('truetype');