reamkit 1.29.0 → 1.31.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 (106) hide show
  1. package/README.md +60 -24
  2. package/dist/esm/core/converter/project.js +3 -1
  3. package/dist/esm/core/crypto/offcrypto.js +1 -1
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +65 -0
  6. package/dist/esm/core/drawingml/shape-render.js +13 -1
  7. package/dist/esm/core/font/index.d.ts +2 -0
  8. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  9. package/dist/esm/core/font/ttf-build.js +1224 -0
  10. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  11. package/dist/esm/core/font/ttf-subset.js +14 -1
  12. package/dist/esm/core/fonts/index.d.ts +1 -1
  13. package/dist/esm/core/fonts/provider.d.ts +17 -0
  14. package/dist/esm/core/fonts/provider.js +27 -2
  15. package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
  16. package/dist/esm/core/fonts/remote-fonts.js +108 -18
  17. package/dist/esm/core/ir/flow.d.ts +88 -1
  18. package/dist/esm/core/numbering/index.d.ts +1 -1
  19. package/dist/esm/core/numbering/state.d.ts +11 -1
  20. package/dist/esm/core/numbering/state.js +10 -1
  21. package/dist/esm/core/style-cascade/resolver.js +35 -4
  22. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  23. package/dist/esm/core/style-cascade/types.js +3 -0
  24. package/dist/esm/index.d.ts +2 -2
  25. package/dist/esm/layout/page-doc.d.ts +10 -3
  26. package/dist/esm/layout/styled-layout.d.ts +48 -7
  27. package/dist/esm/layout/styled-layout.js +881 -119
  28. package/dist/esm/layout/turned-section.d.ts +38 -0
  29. package/dist/esm/layout/turned-section.js +193 -0
  30. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  31. package/dist/esm/pdf-reader/annot-draw.js +93 -1
  32. package/dist/esm/pdf-reader/annots.d.ts +18 -0
  33. package/dist/esm/pdf-reader/annots.js +86 -9
  34. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  35. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  36. package/dist/esm/pdf-reader/cmap.js +5 -2
  37. package/dist/esm/pdf-reader/content.d.ts +70 -4
  38. package/dist/esm/pdf-reader/content.js +172 -12
  39. package/dist/esm/pdf-reader/display.d.ts +36 -0
  40. package/dist/esm/pdf-reader/display.js +82 -1
  41. package/dist/esm/pdf-reader/document.js +5 -1
  42. package/dist/esm/pdf-reader/embedded-fonts.d.ts +25 -0
  43. package/dist/esm/pdf-reader/embedded-fonts.js +78 -9
  44. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  45. package/dist/esm/pdf-reader/encodings.js +25 -3
  46. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  47. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  48. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  49. package/dist/esm/pdf-reader/figures.js +433 -0
  50. package/dist/esm/pdf-reader/flow-build.d.ts +224 -7
  51. package/dist/esm/pdf-reader/flow-build.js +545 -39
  52. package/dist/esm/pdf-reader/font.d.ts +27 -1
  53. package/dist/esm/pdf-reader/font.js +632 -63
  54. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  55. package/dist/esm/pdf-reader/glyf-outline.js +148 -3
  56. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  57. package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
  58. package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
  59. package/dist/esm/pdf-reader/image-decode.js +70 -4
  60. package/dist/esm/pdf-reader/images.d.ts +5 -0
  61. package/dist/esm/pdf-reader/images.js +4 -2
  62. package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
  63. package/dist/esm/pdf-reader/jbig2.js +78 -16
  64. package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
  65. package/dist/esm/pdf-reader/jpeg.js +21 -1
  66. package/dist/esm/pdf-reader/layout.d.ts +119 -2
  67. package/dist/esm/pdf-reader/layout.js +2219 -184
  68. package/dist/esm/pdf-reader/lexer.d.ts +10 -0
  69. package/dist/esm/pdf-reader/lexer.js +17 -0
  70. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  71. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  72. package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
  73. package/dist/esm/pdf-reader/pattern-tint.js +21 -3
  74. package/dist/esm/pdf-reader/regions.d.ts +25 -0
  75. package/dist/esm/pdf-reader/regions.js +167 -0
  76. package/dist/esm/pdf-reader/shading.d.ts +58 -2
  77. package/dist/esm/pdf-reader/shading.js +181 -11
  78. package/dist/esm/pdf-reader/struct-tree.js +112 -8
  79. package/dist/esm/pdf-reader/tagged.js +207 -28
  80. package/dist/esm/pdf-reader/text-rules.js +1 -1
  81. package/dist/esm/pdf-reader/text.d.ts +6 -3
  82. package/dist/esm/pdf-reader/text.js +106 -22
  83. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  84. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  85. package/dist/esm/pdf-reader/vector.d.ts +8 -2
  86. package/dist/esm/pdf-reader/vector.js +105 -6
  87. package/dist/esm/word/doc/doc-reader.js +6 -2
  88. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  89. package/dist/esm/word/doc/doc-text.js +19 -1
  90. package/dist/esm/word/document-parser.d.ts +2 -2
  91. package/dist/esm/word/document-parser.js +11 -1
  92. package/dist/esm/word/docx-reader.js +5 -3
  93. package/dist/esm/word/docx-writer.js +341 -54
  94. package/dist/esm/word/drawing-parser.d.ts +5 -3
  95. package/dist/esm/word/drawing-parser.js +56 -10
  96. package/dist/esm/word/font-embed.d.ts +30 -0
  97. package/dist/esm/word/font-embed.js +173 -0
  98. package/dist/esm/word/font-table.d.ts +10 -0
  99. package/dist/esm/word/font-table.js +13 -1
  100. package/dist/esm/word/index.js +1 -1
  101. package/dist/esm/word/numbering-parser.d.ts +3 -1
  102. package/dist/esm/word/numbering-parser.js +2 -1
  103. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  104. package/dist/esm/word/paragraph-properties.js +16 -2
  105. package/dist/esm/word/run-properties.js +26 -0
  106. package/package.json +12 -5
@@ -6,6 +6,7 @@ import { buildOpcPackage } from "../core/opc/opc-writer.js";
6
6
  import "../core/opc/index.js";
7
7
  import { omathXml } from "./omml-serializer.js";
8
8
  import { chartSpaceXml } from "../core/drawingml/chart-serializer.js";
9
+ import { OBFUSCATED_FONT_CONTENT_TYPE, embedFaces } from "./font-embed.js";
9
10
  //#region src/word/docx-writer.ts
10
11
  var encoder = new TextEncoder();
11
12
  var DEFAULT_RUN = resolveRunProperties({}, {}, EMPTY_STYLE_SHEET);
@@ -17,6 +18,15 @@ var REL_NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relat
17
18
  var REL_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
18
19
  var REL_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
19
20
  var NUMBERING_PART = "word/numbering.xml";
21
+ var REL_FONT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
22
+ var FONT_TABLE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml";
23
+ var FONT_TABLE_PART = "word/fontTable.xml";
24
+ var REL_SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings";
25
+ var SETTINGS_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml";
26
+ var SETTINGS_PART = "word/settings.xml";
27
+ var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
28
+ var STYLES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
29
+ var STYLES_PART = "word/styles.xml";
20
30
  var REL_FOOTNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
21
31
  var REL_ENDNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes";
22
32
  var FOOTNOTES_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml";
@@ -30,6 +40,20 @@ var REL_COMMENTS_EXTENDED = "http://schemas.microsoft.com/office/2011/relationsh
30
40
  var COMMENTS_EXTENDED_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml";
31
41
  var COMMENTS_EXTENDED_PART = "word/commentsExtended.xml";
32
42
  var W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml";
43
+ var MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
44
+ /**
45
+ * The namespaces a part's root declares: WordprocessingML's and the
46
+ * relationships', and — where the part uses one — Word 2010's `w14`, which a
47
+ * reader that does not know it is told to pass over (ECMA-376 Part 3
48
+ * `mc:Ignorable`) rather than to refuse the file.
49
+ *
50
+ * @param inner The part's content, to see whether it uses `w14`.
51
+ * @returns The attributes, each with its leading space.
52
+ */
53
+ function rootNamespaces(inner) {
54
+ const base = " xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"";
55
+ return inner.includes("<w14:") ? `${base} xmlns:w14="${W14_NS}" xmlns:mc="${MC_NS}" mc:Ignorable="w14"` : base;
56
+ }
33
57
  var W15_NS = "http://schemas.microsoft.com/office/word/2012/wordml";
34
58
  var REL_CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
35
59
  var CHART_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
@@ -127,7 +151,12 @@ function writeDocx(flow) {
127
151
  drawingSeq: 0,
128
152
  chartParts: [],
129
153
  chartSeq: 0,
130
- ...flow.charts ? { charts: flow.charts } : {}
154
+ ...flow.charts ? { charts: flow.charts } : {},
155
+ ...flow.faceFamilies ? { faceFamilies: flow.faceFamilies } : {},
156
+ familiesUsed: /* @__PURE__ */ new Map(),
157
+ ...flow.faceOutlines ? { faceOutlines: flow.faceOutlines } : {},
158
+ facesUsed: /* @__PURE__ */ new Set(),
159
+ zRanks: zRanksOf(flow)
131
160
  };
132
161
  const docScope = newScope();
133
162
  const extraParts = [];
@@ -147,7 +176,8 @@ function writeDocx(flow) {
147
176
  });
148
177
  emitBody(body, flow.body, losses, state, docScope, sectPrByClosingIndex);
149
178
  if (finalSectPr) body.push(finalSectPr);
150
- const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:body>${body.join("")}</w:body></w:document>`;
179
+ const bodyXml = body.join("");
180
+ const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document${rootNamespaces(bodyXml)}><w:body>${bodyXml}</w:body></w:document>`;
151
181
  const numberingPart = flow.body.some((el) => el.kind === "paragraph" && el.paragraph.properties.numbering !== void 0) && flow.numbering ? {
152
182
  path: NUMBERING_PART,
153
183
  data: encoder.encode(numberingXml(flow.numbering)),
@@ -179,6 +209,45 @@ function writeDocx(flow) {
179
209
  }, state, losses, docScope, extraParts, extraPartRels);
180
210
  const commentParaIds = emitComments(flow.comments, state, losses, docScope, extraParts, extraPartRels);
181
211
  emitCommentsExtended(flow.comments, commentParaIds, docScope, extraParts);
212
+ const embedded = state.faceOutlines && state.faceFamilies ? embedFaces(state.facesUsed, state.faceOutlines, state.faceFamilies, losses) : void 0;
213
+ if (embedded && embedded.relationships.length > 0) extraPartRels.push({
214
+ sourcePart: FONT_TABLE_PART,
215
+ relationships: [...embedded.relationships]
216
+ });
217
+ const fontTablePart = state.familiesUsed.size > 0 ? {
218
+ path: FONT_TABLE_PART,
219
+ data: encoder.encode(fontTableXml(state.familiesUsed, embedded?.elements)),
220
+ contentType: FONT_TABLE_CONTENT_TYPE
221
+ } : void 0;
222
+ if (fontTablePart) docScope.rels.push({
223
+ id: `rId${++docScope.relSeq}`,
224
+ type: REL_FONT_TABLE,
225
+ target: "fontTable.xml",
226
+ targetMode: "Internal"
227
+ });
228
+ const settings = settingsXml(flow, sections, (embedded?.parts.length ?? 0) > 0);
229
+ const settingsPart = settings !== void 0 ? {
230
+ path: SETTINGS_PART,
231
+ data: encoder.encode(settings),
232
+ contentType: SETTINGS_CONTENT_TYPE
233
+ } : void 0;
234
+ if (settingsPart) docScope.rels.push({
235
+ id: `rId${++docScope.relSeq}`,
236
+ type: REL_SETTINGS,
237
+ target: "settings.xml",
238
+ targetMode: "Internal"
239
+ });
240
+ const stylesPart = {
241
+ path: STYLES_PART,
242
+ data: encoder.encode(stylesXml()),
243
+ contentType: STYLES_CONTENT_TYPE
244
+ };
245
+ docScope.rels.push({
246
+ id: `rId${++docScope.relSeq}`,
247
+ type: REL_STYLES,
248
+ target: "styles.xml",
249
+ targetMode: "Internal"
250
+ });
182
251
  const partRelationships = [...docScope.rels.length > 0 ? [{
183
252
  sourcePart: "word/document.xml",
184
253
  relationships: docScope.rels
@@ -191,11 +260,16 @@ function writeDocx(flow) {
191
260
  data: encoder.encode(documentXml),
192
261
  contentType: DOC_CONTENT_TYPE
193
262
  },
263
+ stylesPart,
194
264
  ...numberingPart ? [numberingPart] : [],
265
+ ...fontTablePart ? [fontTablePart] : [],
266
+ ...settingsPart ? [settingsPart] : [],
195
267
  ...extraParts,
196
268
  ...state.chartParts,
197
- ...state.mediaParts
269
+ ...state.mediaParts,
270
+ ...embedded?.parts ?? []
198
271
  ],
272
+ ...(embedded?.parts.length ?? 0) > 0 ? { defaultsByExtension: { odttf: OBFUSCATED_FONT_CONTENT_TYPE } } : {},
199
273
  rootRelationships: [{
200
274
  id: "rId1",
201
275
  type: REL_OFFICE_DOCUMENT,
@@ -207,6 +281,89 @@ function writeDocx(flow) {
207
281
  losses
208
282
  };
209
283
  }
284
+ /**
285
+ * §17.15.1.78 `w:settings` — the document-wide settings the model carries, in
286
+ * the order CT_Settings declares them: `w:embedTrueTypeFonts` and
287
+ * `w:saveSubsetFonts` (§17.15.1.42, .74) where the package embeds its faces,
288
+ * `w:gutterAtTop` (§17.15.1.49), `w:evenAndOddHeaders` (§17.15.1.36, which a
289
+ * section carries in the model), and inside `w:compat` §17.15.3.4's
290
+ * `w:doNotExpandShiftReturn` before the version of Word the document is laid
291
+ * out for ([MS-DOCX] `w:compatSetting` `compatibilityMode`), which CT_Compat
292
+ * keeps last.
293
+ *
294
+ * @param flow The document.
295
+ * @param sections The sections being written.
296
+ * @param embedsFonts Whether the package embeds fonts — which then stay
297
+ * embedded, as subsets, when the document is saved again.
298
+ * @returns The part's XML, or undefined where the document states none.
299
+ */
300
+ function settingsXml(flow, sections, embedsFonts) {
301
+ const parts = [];
302
+ if (embedsFonts) parts.push("<w:embedTrueTypeFonts/><w:saveSubsetFonts/>");
303
+ if (flow.gutterAtTop === true) parts.push("<w:gutterAtTop/>");
304
+ if (sections.some((sec) => sec.properties.evenAndOddHeaders === true)) parts.push("<w:evenAndOddHeaders/>");
305
+ const compat = [...flow.doNotExpandShiftReturn === true ? ["<w:doNotExpandShiftReturn/>"] : [], ...flow.compatibilityMode !== void 0 ? [`<w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" w:val="${String(flow.compatibilityMode)}"/>`] : []];
306
+ if (compat.length > 0) parts.push(`<w:compat>${compat.join("")}</w:compat>`);
307
+ if (parts.length === 0) return void 0;
308
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:settings xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">${parts.join("")}</w:settings>`;
309
+ }
310
+ /**
311
+ * §17.7.2 `w:styles` — what a property the document states nowhere IS: the
312
+ * values the model resolves an empty sheet to (`DEFAULT_RUN`, `DEFAULT_PARA`
313
+ * — the very values a run or paragraph is written without), as §17.7.5
314
+ * `w:docDefaults`, under an empty default paragraph style (§17.7.4.17).
315
+ *
316
+ * A package with no styles part leaves them to its reader, and Word fills
317
+ * them from its own template's Normal: 8pt after every paragraph, lines 1.08
318
+ * apart, runs in 12pt — so every paragraph of a reconstruction stood 8pt
319
+ * further down than its page set it, and a run of the model's own 11pt, which
320
+ * states no size, was set in 12.
321
+ */
322
+ function stylesXml() {
323
+ const size = Math.round(DEFAULT_RUN.fontSizePt * 2);
324
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:docDefaults><w:rPrDefault><w:rPr><w:sz w:val="${size}"/><w:szCs w:val="${size}"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr>${DEFAULT_PARA.widowControl ? "<w:widowControl/>" : "<w:widowControl w:val=\"0\"/>"}${`<w:spacing w:before="${twips(DEFAULT_PARA.spacingBefore)}" w:after="${twips(DEFAULT_PARA.spacingAfter)}" w:line="${twips(DEFAULT_PARA.spacingLine)}" w:lineRule="${DEFAULT_PARA.spacingLineRule}"/>`}</w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style></w:styles>`;
325
+ }
326
+ function fontTableXml(families, embeds) {
327
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:fonts xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${[...families.values()].map((f) => `<w:font w:name="${escapeAttr(f.family)}"><w:family w:val="${f.generic}"/><w:pitch w:val="${f.generic === "modern" ? "fixed" : "variable"}"/>${embeds?.get(f.family) ?? ""}</w:font>`).join("")}</w:fonts>`;
328
+ }
329
+ /**
330
+ * §20.4.2.3 `relativeHeight` — a float's z-order, written the way Word writes
331
+ * it: a rank over every z-order the document states, counted up from Word's
332
+ * own floor.
333
+ *
334
+ * The model's z-order is an ORDER, and may be any number. Written as it stood,
335
+ * a PDF's first two marks got 0 and 1 — which Word, and LibreOffice after it,
336
+ * read as "above everything": an "APPROVED" stamp's blue box came back over
337
+ * its own lettering. A tagged reading counts up from minus a million, and a
338
+ * negative number is not a value the attribute admits at all.
339
+ */
340
+ function relativeHeight(zOrder, state) {
341
+ const at = (zOrder !== void 0 ? state.zRanks.get(zOrder) : void 0) ?? state.zRanks.size + ++state.drawingSeq;
342
+ const step = Math.max(1, Math.min(Z_STEP, Math.floor((Z_CEILING - Z_FLOOR) / Z_ROOM)));
343
+ return Math.min(Z_FLOOR + at * step, Z_CEILING);
344
+ }
345
+ /** Word's first float, and the highest value §20.4.2.3 lets a float take. */
346
+ var Z_FLOOR = 251658240;
347
+ var Z_CEILING = 503316479;
348
+ /** …and the step Word counts up in, while there is room for it. */
349
+ var Z_STEP = 1024;
350
+ var Z_ROOM = 1 << 17;
351
+ /** Every z-order the document's floats state, and each one's rank among them. */
352
+ function zRanksOf(flow) {
353
+ const seen = /* @__PURE__ */ new Set();
354
+ const visit = (blocks) => {
355
+ for (const el of blocks) {
356
+ const z = el.kind === "image" ? el.image.float?.zOrder : el.kind === "shape" ? el.shape.float?.zOrder : el.kind === "chart" ? el.chart.float?.zOrder : void 0;
357
+ if (z !== void 0 && Number.isFinite(z)) seen.add(z);
358
+ if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
359
+ }
360
+ };
361
+ visit(flow.body);
362
+ for (const band of flow.headersFooters?.values() ?? []) visit(band);
363
+ for (const note of flow.footnotes?.values() ?? []) visit(note);
364
+ for (const note of flow.endnotes?.values() ?? []) visit(note);
365
+ return new Map([...seen].sort((a, b) => a - b).map((z, i) => [z, i]));
366
+ }
210
367
  function emitNotes(notes, cfg, state, losses, docScope, extraParts, extraPartRels) {
211
368
  if (!notes || notes.size === 0) return;
212
369
  const scope = newScope();
@@ -218,7 +375,8 @@ function emitNotes(notes, cfg, state, losses, docScope, extraParts, extraPartRel
218
375
  for (const el of content) emitBlock(inner, el, losses, state, scope);
219
376
  noteXmls.push(`<${cfg.noteTag} w:id="${escapeAttr(id)}">${inner.join("") || "<w:p/>"}</${cfg.noteTag}>`);
220
377
  }
221
- const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><${cfg.rootTag} xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">` + stub("separator", -1, "<w:separator/>") + stub("continuationSeparator", 0, "<w:continuationSeparator/>") + noteXmls.join("") + `</${cfg.rootTag}>`;
378
+ const notesXml = noteXmls.join("");
379
+ const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><${cfg.rootTag}${rootNamespaces(notesXml)}>` + stub("separator", -1, "<w:separator/>") + stub("continuationSeparator", 0, "<w:continuationSeparator/>") + notesXml + `</${cfg.rootTag}>`;
222
380
  extraParts.push({
223
381
  path: cfg.partPath,
224
382
  data: encoder.encode(xml),
@@ -324,7 +482,8 @@ function emitHeadersFooters(flow, section, state, docScope, extraParts, extraPar
324
482
  const scope = newScope();
325
483
  const inner = [];
326
484
  for (const el of content) emitBlock(inner, el, losses, state, scope);
327
- const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><${root} xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${inner.join("")}</${root}>`;
485
+ const bandXml = inner.join("");
486
+ const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><${root}${rootNamespaces(bandXml)}>${bandXml}</${root}>`;
328
487
  extraParts.push({
329
488
  path,
330
489
  data: encoder.encode(xml),
@@ -368,7 +527,7 @@ function emitHeadersFooters(flow, section, state, docScope, extraParts, extraPar
368
527
  var docxWriter = {
369
528
  id: "docx",
370
529
  consumes: "flow",
371
- supports: new Set([FEATURES.text]),
530
+ supports: new Set([FEATURES.text, FEATURES.fontsEmbedding]),
372
531
  write: (doc) => writeDocx(doc)
373
532
  };
374
533
  /**
@@ -388,14 +547,17 @@ var docxWriter = {
388
547
  */
389
548
  function emitBody(out, blocks, losses, state, scope, sectPrByClosingIndex) {
390
549
  let carried = [];
550
+ let carrier;
391
551
  const flush = () => {
392
- if (carried.length > 0) out.push(`<w:p>${carried.join("")}</w:p>`);
552
+ if (carried.length > 0) out.push(`<w:p>${pPrWithSect(carrier ?? {})}${carried.join("")}</w:p>`);
393
553
  carried = [];
554
+ carrier = void 0;
394
555
  };
395
556
  blocks.forEach((el, idx) => {
396
557
  const closing = sectPrByClosingIndex.get(idx);
397
558
  const anchored = closing === void 0 ? floatingDrawingRun(el, losses, state, scope) : void 0;
398
559
  if (anchored !== void 0) {
560
+ carrier ??= el.kind === "image" ? el.image.paragraphProperties : el.kind === "shape" ? el.shape.paragraphProperties : void 0;
399
561
  carried.push(anchored);
400
562
  return;
401
563
  }
@@ -411,7 +573,7 @@ function emitBody(out, blocks, losses, state, scope, sectPrByClosingIndex) {
411
573
  */
412
574
  function floatingDrawingRun(el, losses, state, scope) {
413
575
  if (el.kind === "image" && el.image.float) {
414
- const drawing = drawingXml(el.image.resource, el.image.width, el.image.height, el.image.altText, state, scope, el.image.float);
576
+ const drawing = drawingXml(el.image.resource, el.image.width, el.image.height, el.image.altText, state, scope, el.image.float, el.image);
415
577
  if (drawing === "") {
416
578
  losses.push({
417
579
  severity: "dropped",
@@ -435,7 +597,7 @@ function emitBlock(out, el, losses, state, scope, closingSectPr) {
435
597
  return;
436
598
  }
437
599
  if (el.kind === "image") {
438
- const drawing = drawingXml(el.image.resource, el.image.width, el.image.height, el.image.altText, state, scope, el.image.float);
600
+ const drawing = drawingXml(el.image.resource, el.image.width, el.image.height, el.image.altText, state, scope, el.image.float, el.image);
439
601
  if (drawing) out.push(`<w:p>${pPrWithSect(el.image.paragraphProperties, closingSectPr)}<w:r>${drawing}</w:r></w:p>`);
440
602
  else {
441
603
  losses.push({
@@ -514,7 +676,7 @@ function drawingFrame(float, cx, cy, head, graphic, state, rotation60k) {
514
676
  const emu = (pt) => Number.isFinite(pt) ? Math.round((pt ?? 0) * EMU_PER_PT) : 0;
515
677
  const d = float.wrapDist;
516
678
  const dist = ` distT="${String(emu(d?.topPt))}" distB="${String(emu(d?.bottomPt))}" distL="${String(emu(d?.leftPt))}" distR="${String(emu(d?.rightPt))}"`;
517
- const z = float.zOrder ?? ++state.drawingSeq;
679
+ const z = relativeHeight(float.zOrder, state);
518
680
  const attrs = `${dist} simplePos="0" relativeHeight="${String(z)}" behindDoc="${float.behind ? "1" : "0"}" locked="0" layoutInCell="${float.inCell === false ? "0" : "1"}" allowOverlap="1"`;
519
681
  const pos = (tag, p, fallback) => {
520
682
  const from = p?.relativeFrom ?? fallback;
@@ -550,7 +712,7 @@ function effectExtentXml(cx, cy, rotation60k) {
550
712
  const v = overhang(cy, cx * s + cy * c);
551
713
  return h === 0 && v === 0 ? none : `<wp:effectExtent l="${String(h)}" t="${String(v)}" r="${String(h)}" b="${String(v)}"/>`;
552
714
  }
553
- function drawingXml(resource, widthPt, heightPt, altText, state, scope, float) {
715
+ function drawingXml(resource, widthPt, heightPt, altText, state, scope, float, look) {
554
716
  if (resource === void 0) return "";
555
717
  const relId = mediaRelId(resource, state, scope);
556
718
  if (relId === void 0) return "";
@@ -558,8 +720,15 @@ function drawingXml(resource, widthPt, heightPt, altText, state, scope, float) {
558
720
  const cy = Math.round(heightPt * EMU_PER_PT);
559
721
  const id = ++state.drawingSeq;
560
722
  const descr = altText ? ` descr="${escapeAttr(altText)}"` : "";
561
- const graphic = `<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="${id}" name="Image ${id}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${relId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic>`;
562
- return drawingFrame(float, cx, cy, `<wp:docPr id="${id}" name="Image ${id}"${descr}/>`, graphic, state);
723
+ const graphic = `<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="${id}" name="Image ${id}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${relId}">${look?.alpha !== void 0 && look.alpha < 1 ? `<a:alphaModFix amt="${String(Math.round(Math.max(0, look.alpha) * 1e5))}"/>` : ""}</a:blip>${srcRectXml(look?.crop)}<a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr>${xfrmXml(look, cx, cy)}<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic>`;
724
+ return drawingFrame(float, cx, cy, `<wp:docPr id="${id}" name="Image ${id}"${descr}/>`, graphic, state, look?.rotation60k);
725
+ }
726
+ /** §20.1.8.55 `a:srcRect` — each edge cut away, in thousandths of a percent. */
727
+ function srcRectXml(crop) {
728
+ if (!crop) return "";
729
+ const edge = (name, v) => v > 0 ? ` ${name}="${String(Math.round(v * 1e5))}"` : "";
730
+ const attrs = edge("l", crop.left) + edge("t", crop.top) + edge("r", crop.right) + edge("b", crop.bottom);
731
+ return attrs === "" ? "" : `<a:srcRect${attrs}/>`;
563
732
  }
564
733
  var WPS_URI = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
565
734
  function shapeDrawingXml(shape, losses, state, scope) {
@@ -567,13 +736,76 @@ function shapeDrawingXml(shape, losses, state, scope) {
567
736
  const cy = Math.round(shape.height * EMU_PER_PT);
568
737
  const id = ++state.drawingSeq;
569
738
  const descr = shape.altText ? ` descr="${escapeAttr(shape.altText)}"` : "";
570
- const spPr = `<wps:spPr>${xfrmXml(shape.transform, cx, cy)}${geomXml(shape.geometry)}${fillXml(shape.fill)}${shape.line ? lineXml(shape.line) : ""}</wps:spPr>`;
571
- const txbx = shape.text ? txbxXml(shape.text, losses, state, scope) : "";
572
- const graphic = `<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="${WPS_URI}"><wps:wsp xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"><wps:cNvSpPr/>` + spPr + txbx + bodyPrXml(shape.text) + "</wps:wsp></a:graphicData></a:graphic>";
739
+ const members = shape.children ?? [];
740
+ const graphic = "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" + (members.length > 0 ? `<a:graphicData uri="${WPG_URI}">` + groupXml("wpg:wgp", members, {
741
+ x: 0,
742
+ y: 0,
743
+ cx,
744
+ cy
745
+ }, losses, state, scope) : `<a:graphicData uri="${WPS_URI}">` + wspXml(shape, cx, cy, void 0, losses, state, scope)) + "</a:graphicData></a:graphic>";
573
746
  return drawingFrame(shape.float, cx, cy, `<wp:docPr id="${id}" name="Shape ${id}"${descr}/>`, graphic, state, shape.transform?.rotation60k);
574
747
  }
575
- function xfrmXml(t, cx, cy) {
576
- return `<a:xfrm${t?.rotation60k !== void 0 ? ` rot="${t.rotation60k}"` : ""}${t?.flipH ? " flipH=\"1\"" : ""}${t?.flipV ? " flipV=\"1\"" : ""}><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
748
+ /**
749
+ * `wps:wsp` — one shape: its box, geometry, fill and outline, and the text it
750
+ * holds. `id` is its own `wps:cNvPr` inside a group, where the members name
751
+ * themselves; a shape alone is named by the frame's `wp:docPr`.
752
+ */
753
+ function wspXml(shape, cx, cy, member, losses, state, scope) {
754
+ const spPr = `<wps:spPr>${xfrmXml(shape.transform, cx, cy, member)}${geomXml(shape.geometry)}${fillXml(shape.fill)}${shape.line ? lineXml(shape.line) : ""}</wps:spPr>`;
755
+ const txbx = shape.text ? txbxXml(shape.text, losses, state, scope) : "";
756
+ return `<wps:wsp xmlns:wps="${WPS_URI}">` + (member ? `<wps:cNvPr id="${member.id}" name="Shape ${member.id}"/>` : "") + "<wps:cNvSpPr/>" + spPr + txbx + bodyPrXml(shape.text) + "</wps:wsp>";
757
+ }
758
+ var WPG_URI = "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup";
759
+ /**
760
+ * §20.5.2.17 `wpg:wgp` / `wpg:grpSp` — a group: its box, and its members in
761
+ * the order they are painted. The members are placed in a child space the
762
+ * same as the box, so a member's offset from the group's corner is its offset
763
+ * in the file. The inverse of drawing-parser's `groupChildren`.
764
+ *
765
+ * Written as one shape, the group came back an empty box: a figure's paths
766
+ * and labels were thrown away with it.
767
+ */
768
+ function groupXml(tag, members, box, losses, state, scope) {
769
+ return `<${tag}${tag === "wpg:wgp" ? ` xmlns:wpg="${WPG_URI}"` : ""}><wpg:cNvGrpSpPr/><wpg:grpSpPr>${`<a:xfrm><a:off x="${box.x}" y="${box.y}"/><a:ext cx="${box.cx}" cy="${box.cy}"/><a:chOff x="0" y="0"/><a:chExt cx="${box.cx}" cy="${box.cy}"/></a:xfrm>`}</wpg:grpSpPr>${members.map((m) => memberXml(m, losses, state, scope)).join("")}</${tag}>`;
770
+ }
771
+ /**
772
+ * One member of a group: a group of its own, a picture (`pic:pic`, a shape
773
+ * whose fill is the picture — how the reader brings one in), or a shape.
774
+ */
775
+ function memberXml(member, losses, state, scope) {
776
+ const s = member.shape;
777
+ const x = Math.round(member.xPt * EMU_PER_PT);
778
+ const y = Math.round(member.yPt * EMU_PER_PT);
779
+ const cx = Math.round(s.width * EMU_PER_PT);
780
+ const cy = Math.round(s.height * EMU_PER_PT);
781
+ if (s.children !== void 0 && s.children.length > 0) return groupXml("wpg:grpSp", s.children, {
782
+ x,
783
+ y,
784
+ cx,
785
+ cy
786
+ }, losses, state, scope);
787
+ const id = ++state.drawingSeq;
788
+ if (s.fill.kind !== "picture") return wspXml(s, cx, cy, {
789
+ id,
790
+ x,
791
+ y
792
+ }, losses, state, scope);
793
+ const relId = s.fill.imageResource !== void 0 ? mediaRelId(s.fill.imageResource, state, scope) : void 0;
794
+ if (relId === void 0) {
795
+ losses.push({
796
+ severity: "dropped",
797
+ feature: FEATURES.images,
798
+ detail: imageRefusal(s.fill.imageResource, state)
799
+ });
800
+ return "";
801
+ }
802
+ return `<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="${id}" name="Image ${id}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${relId}">${s.fill.alpha !== void 0 && s.fill.alpha < 1 ? `<a:alphaModFix amt="${String(Math.round(Math.max(0, s.fill.alpha) * 1e5))}"/>` : ""}</a:blip>${srcRectXml(s.fill.imageCrop)}<a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr>${xfrmXml(s.transform, cx, cy, {
803
+ x,
804
+ y
805
+ })}<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic>`;
806
+ }
807
+ function xfrmXml(t, cx, cy, at) {
808
+ return `<a:xfrm${t?.rotation60k !== void 0 ? ` rot="${t.rotation60k}"` : ""}${t?.flipH ? " flipH=\"1\"" : ""}${t?.flipV ? " flipV=\"1\"" : ""}><a:off x="${at?.x ?? 0}" y="${at?.y ?? 0}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
577
809
  }
578
810
  /**
579
811
  * The factor that carries a path into whole units, and the space it lands in.
@@ -599,7 +831,7 @@ function pathScale(g) {
599
831
  else if (c.cmd === "quad") values.push(c.x1, c.y1, c.x, c.y);
600
832
  else if (c.cmd === "arc") values.push(c.wR, c.hR);
601
833
  if (values.every((v) => Number.isInteger(v))) return 1;
602
- const span = Math.max(...values.map((v) => Number.isFinite(v) ? Math.abs(v) : 0), 1);
834
+ const span = values.reduce((most, v) => Number.isFinite(v) ? Math.max(most, Math.abs(v)) : most, 1);
603
835
  return Math.max(1, Math.floor(PATH_SPACE / span));
604
836
  }
605
837
  function geomXml(g) {
@@ -624,13 +856,24 @@ function geomXml(g) {
624
856
  return `<a:prstGeom prst="${escapeAttr(g.preset ?? "rect")}"><a:avLst>${gds}</a:avLst></a:prstGeom>`;
625
857
  }
626
858
  function fillXml(f) {
627
- if (f.kind === "solid" && f.colorHex) return `<a:solidFill><a:srgbClr val="${f.colorHex}"/></a:solidFill>`;
628
- if (f.kind === "gradient" && f.gradient) return gradFillXml(f.gradient);
859
+ if (f.kind === "solid" && f.colorHex) return `<a:solidFill>${srgbXml(f.colorHex, f.alpha)}</a:solidFill>`;
860
+ if (f.kind === "gradient" && f.gradient) return gradFillXml(f.gradient, f.alpha);
629
861
  return "<a:noFill/>";
630
862
  }
631
- function gradFillXml(g) {
863
+ /**
864
+ * §20.1.2.3.19 `a:srgbClr`, with the §20.1.2.3.1 `a:alpha` of a fill that is
865
+ * seen through. The model carried the opacity and the writer dropped it:
866
+ * bug1755507.pdf lays a card on a shadow painted at a fifth of full strength,
867
+ * and the shadow came back as a solid black slab around the card.
868
+ */
869
+ function srgbXml(colorHex, alpha) {
870
+ if (alpha === void 0 || !(alpha < 1)) return `<a:srgbClr val="${colorHex}"/>`;
871
+ const val = Math.round(Math.max(0, alpha) * 1e5);
872
+ return `<a:srgbClr val="${colorHex}"><a:alpha val="${String(val)}"/></a:srgbClr>`;
873
+ }
874
+ function gradFillXml(g, alpha) {
632
875
  return `<a:gradFill><a:gsLst>${g.stops.map((s) => {
633
- return `<a:gs pos="${Math.round(Math.max(0, Math.min(1, s.offset)) * 1e5)}"><a:srgbClr val="${s.colorHex}"/></a:gs>`;
876
+ return `<a:gs pos="${Math.round(Math.max(0, Math.min(1, s.offset)) * 1e5)}">${srgbXml(s.colorHex, alpha)}</a:gs>`;
634
877
  }).join("")}</a:gsLst>${g.kind === "radial" ? "<a:path path=\"circle\"/>" : `<a:lin ang="${Math.round(((g.angle ?? 0) % 360 + 360) % 360 * 6e4)}" scaled="1"/>`}</a:gradFill>`;
635
878
  }
636
879
  function lineXml(l) {
@@ -638,8 +881,13 @@ function lineXml(l) {
638
881
  const cap = l.cap === "round" ? " cap=\"rnd\"" : l.cap === "square" ? " cap=\"sq\"" : l.cap === "flat" ? " cap=\"flat\"" : "";
639
882
  const inner = [];
640
883
  if (l.fill === "none") inner.push("<a:noFill/>");
641
- else if (l.colorHex) inner.push(`<a:solidFill><a:srgbClr val="${l.colorHex}"/></a:solidFill>`);
642
- if (l.dash) inner.push(`<a:prstDash val="${l.dash}"/>`);
884
+ else if (l.colorHex) inner.push(`<a:solidFill>${srgbXml(l.colorHex, l.alpha)}</a:solidFill>`);
885
+ const custom = l.customDash ?? [];
886
+ if (custom.length >= 2) {
887
+ const pairs = [];
888
+ for (let i = 0; i + 1 < custom.length; i += 2) pairs.push(`<a:ds d="${Math.round(custom[i] * 1e5)}" sp="${Math.round(custom[i + 1] * 1e5)}"/>`);
889
+ inner.push(`<a:custDash>${pairs.join("")}</a:custDash>`);
890
+ } else if (l.dash) inner.push(`<a:prstDash val="${l.dash}"/>`);
643
891
  return `<a:ln${w}${cap}>${inner.join("")}</a:ln>`;
644
892
  }
645
893
  function txbxXml(text, losses, state, scope) {
@@ -651,7 +899,7 @@ function bodyPrXml(text) {
651
899
  if (!text) return "<wps:bodyPr/>";
652
900
  const ins = (v, name) => v !== void 0 ? ` ${name}="${Math.round(v * EMU_PER_PT)}"` : "";
653
901
  const anchor = text.anchor ? ` anchor="${text.anchor}"` : "";
654
- return "<wps:bodyPr" + ins(text.insetLeft, "lIns") + ins(text.insetTop, "tIns") + ins(text.insetRight, "rIns") + ins(text.insetBottom, "bIns") + anchor + "/>";
902
+ return "<wps:bodyPr" + (text.noWrap === true ? " wrap=\"none\"" : "") + ins(text.insetLeft, "lIns") + ins(text.insetTop, "tIns") + ins(text.insetRight, "rIns") + ins(text.insetBottom, "bIns") + anchor + "/>";
655
903
  }
656
904
  function mediaRelId(resource, state, scope) {
657
905
  const existingRel = scope.relIdByResource.get(resource);
@@ -702,8 +950,10 @@ function tblPrXml(p) {
702
950
  out.push(`<w:tblW w:w="${w}" w:type="${p.widthType}"/>`);
703
951
  }
704
952
  if (p.alignment && p.alignment !== "left") out.push(`<w:jc w:val="${p.alignment}"/>`);
953
+ if (p.indentPt !== void 0 && p.indentPt !== 0) out.push(`<w:tblInd w:w="${twips(p.indentPt)}" w:type="dxa"/>`);
705
954
  const borders = bordersXml("w:tblBorders", p.borders);
706
955
  if (borders) out.push(borders);
956
+ if (p.layout === "fixed") out.push("<w:tblLayout w:type=\"fixed\"/>");
707
957
  const margins = cellMarginsXml("w:tblCellMar", p.defaultCellMargins);
708
958
  if (margins) out.push(margins);
709
959
  return `<w:tblPr>${out.join("")}</w:tblPr>`;
@@ -747,15 +997,20 @@ var BORDER_SIDES = [
747
997
  ];
748
998
  function bordersXml(tag, borders) {
749
999
  if (!borders) return "";
750
- const sides = BORDER_SIDES.map(([key, el]) => {
1000
+ const sides = BORDER_SIDES.map(([key, name]) => {
751
1001
  const b = borders[key];
752
1002
  if (!b) return "";
1003
+ if (tag === "w:pBdr" && key === "insideV") return "";
1004
+ const el = tag === "w:pBdr" && key === "insideH" ? "w:between" : name;
753
1005
  const sz = b.width !== void 0 ? ` w:sz="${Math.round(b.width * 8)}"` : "";
1006
+ const space = b.spacePt !== void 0 && Math.round(b.spacePt) > 0 ? ` w:space="${Math.min(Math.round(b.spacePt), MOST_BORDER_SPACE_PT)}"` : "";
754
1007
  const color = b.colorHex !== void 0 ? ` w:color="${b.colorHex}"` : "";
755
- return `<${el} w:val="${b.style}"${sz}${color}/>`;
1008
+ return `<${el} w:val="${b.style}"${sz}${space}${color}/>`;
756
1009
  }).join("");
757
1010
  return sides ? `<${tag}>${sides}</${tag}>` : "";
758
1011
  }
1012
+ /** §17.3.4 — the farthest a border may stand off its text, in points. */
1013
+ var MOST_BORDER_SPACE_PT = 31;
759
1014
  function cellMarginsXml(tag, margins) {
760
1015
  if (!margins) return "";
761
1016
  const inner = [
@@ -809,10 +1064,10 @@ function hyperlinkXml(run, inner, _state, scope) {
809
1064
  }
810
1065
  function runXml(run, state, scope) {
811
1066
  if (run.math !== void 0) return `<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">${omathXml(run.math)}</m:oMath>`;
812
- const rPr = rPrXml(run.properties);
1067
+ const rPr = rPrXml(run.properties, state);
813
1068
  if (run.inlineImage !== void 0) {
814
1069
  const img = run.inlineImage;
815
- const drawing = drawingXml(img.resource, img.width, img.height, void 0, state, scope);
1070
+ const drawing = drawingXml(img.resource, img.width, img.height, void 0, state, scope, void 0, img);
816
1071
  if (drawing) return `<w:r>${rPr}${drawing}</w:r>`;
817
1072
  if (run.text === "" && !run.pageBreak) return "";
818
1073
  }
@@ -822,27 +1077,41 @@ function runXml(run, state, scope) {
822
1077
  if (run.commentRef !== void 0) return `<w:r>${rPr}<w:commentReference w:id="${escapeAttr(run.commentRef)}"/></w:r>`;
823
1078
  const brk = run.pageBreak ? "<w:br w:type=\"page\"/>" : "";
824
1079
  if (run.field !== void 0 && run.text !== "") {
825
- const inner = `<w:r>${rPr}<w:t xml:space="preserve">${escapeXml(run.text)}</w:t></w:r>`;
826
- return `<w:fldSimple w:instr=" ${run.field} ">${inner}</w:fldSimple>${brk}`;
1080
+ const piece = (inner) => `<w:r>${rPr}${inner}</w:r>`;
1081
+ return piece("<w:fldChar w:fldCharType=\"begin\"/>") + piece(`<w:instrText xml:space="preserve"> ${run.field} </w:instrText>`) + piece("<w:fldChar w:fldCharType=\"separate\"/>") + piece(`<w:t xml:space="preserve">${escapeXml(run.text)}</w:t>`) + piece("<w:fldChar w:fldCharType=\"end\"/>") + brk;
827
1082
  }
828
1083
  if (run.text === "") return brk ? `<w:r>${rPr}${brk}</w:r>` : "";
829
- return `<w:r>${rPr}${run.text.split(" ").map((piece) => piece === "" ? "" : `<w:t xml:space="preserve">${escapeXml(piece)}</w:t>`).join("<w:tab/>")}${brk}</w:r>`;
1084
+ const lineBreak = run.columnBreak ? "<w:br w:type=\"column\"/>" : "<w:br/>";
1085
+ return `<w:r>${rPr}${run.text.split(" ").map((piece) => piece.split("\n").map((line) => line === "" ? "" : `<w:t xml:space="preserve">${escapeXml(line)}</w:t>`).join(lineBreak)).join("<w:tab/>")}${brk}</w:r>`;
830
1086
  }
831
- function rPrXml(r) {
1087
+ function rPrXml(r, state) {
832
1088
  const out = [];
833
1089
  const states = (key) => r[key] !== void 0 && r[key] !== DEFAULT_RUN[key];
834
- const fonts = rFontsXml(r.fontFamily);
1090
+ const fonts = rFontsXml(r.fontFamily, state);
835
1091
  if (fonts) out.push(fonts);
836
- if (states("bold")) out.push(toggle("w:b", r.bold));
837
- if (states("italic")) out.push(toggle("w:i", r.italic));
1092
+ if (states("bold")) out.push(toggle("w:b", r.bold), toggle("w:bCs", r.bold));
1093
+ if (states("italic")) out.push(toggle("w:i", r.italic), toggle("w:iCs", r.italic));
838
1094
  if (states("strike")) out.push(toggle("w:strike", r.strike));
839
1095
  if (states("colorHex")) out.push(`<w:color w:val="${r.colorHex}"/>`);
840
- if (states("fontSizePt")) out.push(`<w:sz w:val="${Math.round(r.fontSizePt * 2)}"/>`);
1096
+ if (r.letterSpacingPt !== void 0 && r.letterSpacingPt !== 0) {
1097
+ const twips = Math.round(r.letterSpacingPt * 20);
1098
+ if (twips !== 0) out.push(`<w:spacing w:val="${String(twips)}"/>`);
1099
+ }
1100
+ if (r.widthScale !== void 0) {
1101
+ const percent = Math.min(600, Math.max(1, Math.round(r.widthScale * 100)));
1102
+ if (percent !== 100) out.push(`<w:w w:val="${String(percent)}"/>`);
1103
+ }
1104
+ if (r.kerningMinPt !== void 0 && states("kerningMinPt")) out.push(`<w:kern w:val="${Math.round(r.kerningMinPt * 2)}"/>`);
1105
+ if (states("fontSizePt")) {
1106
+ const half = Math.round(r.fontSizePt * 2);
1107
+ out.push(`<w:sz w:val="${half}"/>`, `<w:szCs w:val="${half}"/>`);
1108
+ }
841
1109
  if (states("underline")) out.push(underlineXml(r));
842
1110
  if (r.shadingColorHex !== void 0) out.push(runShdXml(r.shadingColorHex));
843
1111
  if (states("verticalAlign")) out.push(`<w:vertAlign w:val="${r.verticalAlign}"/>`);
844
1112
  if (states("rtl")) out.push(toggle("w:rtl", r.rtl));
845
1113
  if (r.lang !== void 0) out.push(`<w:lang w:val="${escapeAttr(r.lang)}"/>`);
1114
+ if (r.ligatures !== void 0 && states("ligatures")) out.push(`<w14:ligatures w14:val="${r.ligatures}"/>`);
846
1115
  return out.length > 0 ? `<w:rPr>${out.join("")}</w:rPr>` : "";
847
1116
  }
848
1117
  function underlineXml(r) {
@@ -854,17 +1123,14 @@ function runShdXml(fill) {
854
1123
  }
855
1124
  function pPrBody(p) {
856
1125
  const out = [];
857
- if (p.numbering) out.push(`<w:numPr><w:ilvl w:val="${p.numbering.ilvl}"/><w:numId w:val="${escapeAttr(p.numbering.numId)}"/></w:numPr>`);
858
- if (p.outlineLevel !== void 0) out.push(`<w:outlineLvl w:val="${p.outlineLevel}"/>`);
1126
+ if (p.keepNext) out.push("<w:keepNext/>");
1127
+ if (p.keepLines) out.push("<w:keepLines/>");
859
1128
  if (p.pageBreakBefore) out.push("<w:pageBreakBefore/>");
860
- if (p.bidi !== DEFAULT_PARA.bidi) out.push(toggle("w:bidi", p.bidi));
861
- const ind = indXml(p);
862
- if (ind) out.push(ind);
863
- const spacing = spacingXml(p);
864
- if (spacing) out.push(spacing);
865
- if (JC.has(p.alignment) && p.alignment !== DEFAULT_PARA.alignment) out.push(`<w:jc w:val="${p.alignment}"/>`);
1129
+ if (p.widowControl === false) out.push("<w:widowControl w:val=\"0\"/>");
1130
+ if (p.numbering) out.push(`<w:numPr><w:ilvl w:val="${p.numbering.ilvl}"/><w:numId w:val="${escapeAttr(p.numbering.numId)}"/></w:numPr>`);
866
1131
  const pBdr = bordersXml("w:pBdr", p.borders);
867
1132
  if (pBdr) out.push(pBdr);
1133
+ if (p.shading) out.push(`<w:shd w:val="clear" w:color="auto" w:fill="${p.shading.colorHex}"/>`);
868
1134
  const tabs = p.tabs ?? [];
869
1135
  if (tabs.length > 0) {
870
1136
  const stops = tabs.map((t) => {
@@ -874,6 +1140,13 @@ function pPrBody(p) {
874
1140
  }).join("");
875
1141
  out.push(`<w:tabs>${stops}</w:tabs>`);
876
1142
  }
1143
+ if (p.bidi !== void 0 && p.bidi !== DEFAULT_PARA.bidi) out.push(toggle("w:bidi", p.bidi));
1144
+ const spacing = spacingXml(p);
1145
+ if (spacing) out.push(spacing);
1146
+ const ind = indXml(p);
1147
+ if (ind) out.push(ind);
1148
+ if (JC.has(p.alignment) && p.alignment !== DEFAULT_PARA.alignment) out.push(`<w:jc w:val="${p.alignment}"/>`);
1149
+ if (p.outlineLevel !== void 0) out.push(`<w:outlineLvl w:val="${p.outlineLevel}"/>`);
877
1150
  return out.join("");
878
1151
  }
879
1152
  function pPrWithSect(p, closingSectPr) {
@@ -914,18 +1187,26 @@ function spacingXml(p) {
914
1187
  const attrs = [];
915
1188
  if (stated(p.spacingBefore) && p.spacingBefore !== DEFAULT_PARA.spacingBefore) attrs.push(`w:before="${twips(p.spacingBefore)}"`);
916
1189
  if (stated(p.spacingAfter) && p.spacingAfter !== DEFAULT_PARA.spacingAfter) attrs.push(`w:after="${twips(p.spacingAfter)}"`);
917
- if ((p.spacingLineRule !== DEFAULT_PARA.spacingLineRule || p.spacingLine !== DEFAULT_PARA.spacingLine) && p.spacingLine > 0) {
918
- const lineVal = p.spacingLineRule === "auto" ? Math.round(p.spacingLine * 12) : twips(p.spacingLine);
919
- attrs.push(`w:line="${lineVal}"`, `w:lineRule="${p.spacingLineRule}"`);
920
- }
1190
+ if ((p.spacingLineRule !== DEFAULT_PARA.spacingLineRule || p.spacingLine !== DEFAULT_PARA.spacingLine) && p.spacingLine > 0) attrs.push(`w:line="${twips(p.spacingLine)}"`, `w:lineRule="${p.spacingLineRule}"`);
921
1191
  return attrs.length > 0 ? `<w:spacing ${attrs.join(" ")}/>` : "";
922
1192
  }
923
- function rFontsXml(fonts) {
1193
+ function rFontsXml(fonts, state) {
1194
+ if (fonts === void 0) return "";
924
1195
  const d = DEFAULT_RUN.fontFamily;
1196
+ const family = (name) => {
1197
+ const known = state?.faceFamilies?.get(name);
1198
+ if (known === void 0) return name;
1199
+ state?.familiesUsed.set(known.family, known);
1200
+ if (state?.faceOutlines?.has(name) === true) state.facesUsed.add(name);
1201
+ return known.family;
1202
+ };
925
1203
  const attrs = [];
926
- if (fonts.ascii && fonts.ascii !== d.ascii) attrs.push(`w:ascii="${escapeAttr(fonts.ascii)}"`);
927
- if (fonts.hAnsi && fonts.hAnsi !== d.hAnsi) attrs.push(`w:hAnsi="${escapeAttr(fonts.hAnsi)}"`);
928
- if (fonts.cs && fonts.cs !== d.cs) attrs.push(`w:cs="${escapeAttr(fonts.cs)}"`);
1204
+ if (fonts.ascii && fonts.ascii !== d.ascii) attrs.push(`w:ascii="${escapeAttr(family(fonts.ascii))}"`);
1205
+ if (fonts.hAnsi && fonts.hAnsi !== d.hAnsi) attrs.push(`w:hAnsi="${escapeAttr(family(fonts.hAnsi))}"`);
1206
+ const face = fonts.ascii !== void 0 && state?.faceFamilies?.has(fonts.ascii) === true ? family(fonts.ascii) : void 0;
1207
+ if (face !== void 0 && fonts.eastAsia === void 0) attrs.push(`w:eastAsia="${escapeAttr(face)}"`);
1208
+ if (fonts.cs && fonts.cs !== d.cs) attrs.push(`w:cs="${escapeAttr(family(fonts.cs))}"`);
1209
+ else if (face !== void 0) attrs.push(`w:cs="${escapeAttr(face)}"`);
929
1210
  return attrs.length > 0 ? `<w:rFonts ${attrs.join(" ")}/>` : "";
930
1211
  }
931
1212
  function toggle(tag, on) {
@@ -1003,8 +1284,14 @@ function sectPrXml(s, hf) {
1003
1284
  const footer = m.footer !== void 0 ? ` w:footer="${twips(m.footer)}"` : "";
1004
1285
  parts.push(`<w:pgMar w:top="${twips(m.top)}" w:right="${twips(m.right)}" w:bottom="${twips(m.bottom)}" w:left="${twips(m.left)}"${header}${footer}/>`);
1005
1286
  }
1287
+ if (s.pageNumberFormat !== void 0 || s.pageNumberStart !== void 0) {
1288
+ const fmt = s.pageNumberFormat !== void 0 ? ` w:fmt="${s.pageNumberFormat}"` : "";
1289
+ const start = s.pageNumberStart !== void 0 ? ` w:start="${String(s.pageNumberStart)}"` : "";
1290
+ parts.push(`<w:pgNumType${fmt}${start}/>`);
1291
+ }
1006
1292
  if (s.columns) parts.push(colsXml(s.columns));
1007
1293
  if (s.titlePg) parts.push("<w:titlePg/>");
1294
+ if (s.textDirection) parts.push(`<w:textDirection w:val="${s.textDirection}"/>`);
1008
1295
  if (parts.length === 0) return "";
1009
1296
  return `<w:sectPr>${parts.join("")}</w:sectPr>`;
1010
1297
  }