@forevka/wordcanvas 0.7.0 → 0.7.2

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 (66) hide show
  1. package/README.md +48 -1
  2. package/dist-lib/assets/{worker-BsOsK39A.js → worker-4HYrkNrG.js} +12276 -11981
  3. package/dist-lib/assets/worker-CJpwUzKh.js +2403 -0
  4. package/dist-lib/blockFactory-B_CLnkUh.js +106 -0
  5. package/dist-lib/browser-p_rkUcqE.js +1339 -0
  6. package/dist-lib/builder.js +365 -109
  7. package/dist-lib/editorApp-mTD0YNv-.js +9863 -0
  8. package/dist-lib/export.js +4670 -4567
  9. package/dist-lib/generate-toc.js +5 -5
  10. package/dist-lib/import.js +4 -1538
  11. package/dist-lib/{lists-BegzmTmD.js → lists-Sc5I40JO.js} +2 -2
  12. package/dist-lib/measure.js +2 -2
  13. package/dist-lib/{measureHost-CMiJKdlk.js → measureHost-B2aKX7lF.js} +1 -1
  14. package/dist-lib/metrics-BUMIC9Mv.js +164 -0
  15. package/dist-lib/pipeline-DD0nxAYM.js +1850 -0
  16. package/dist-lib/recalc-docx.js +2 -2
  17. package/dist-lib/recalcToc-CFCGCgij.js +3985 -0
  18. package/dist-lib/styleProps-DhqDEfvc.js +149 -0
  19. package/dist-lib/stylesheet-B6oczA-L.js +141 -0
  20. package/dist-lib/toc-Dj57zdz-.js +213 -0
  21. package/dist-lib/{webmcp-B7Gvd58E.js → webmcp-CXghUon_.js} +2 -2
  22. package/dist-lib/{wordcanvas-BAxBeErX.js → wordcanvas-D5DI9ZR1.js} +49 -23
  23. package/dist-lib/wordcanvas.js +1 -1
  24. package/dist-node/{chunk-GXFDF7UY.js → chunk-57L5XMCL.js} +327 -59
  25. package/dist-node/chunk-57L5XMCL.js.map +7 -0
  26. package/dist-node/{chunk-TBSXA2I4.js → chunk-EGSAVPCC.js} +1 -1
  27. package/dist-node/chunk-EGSAVPCC.js.map +7 -0
  28. package/dist-node/{chunk-4PDVRJF6.js → chunk-EMY2P524.js} +32 -2
  29. package/dist-node/chunk-EMY2P524.js.map +7 -0
  30. package/dist-node/{chunk-2RCPB7UZ.js → chunk-KJV7GJ3H.js} +1 -1
  31. package/dist-node/chunk-KJV7GJ3H.js.map +7 -0
  32. package/dist-node/{chunk-XDUDPPJW.js → chunk-PIMVQULL.js} +93 -24
  33. package/dist-node/chunk-PIMVQULL.js.map +7 -0
  34. package/dist-node/{chunk-BRAPV465.js → chunk-SA26UVIJ.js} +2 -2
  35. package/dist-node/{chunk-IJLL6IFJ.js → chunk-T3253N22.js} +202 -13
  36. package/dist-node/{chunk-IJLL6IFJ.js.map → chunk-T3253N22.js.map} +2 -2
  37. package/dist-node/{chunk-4ICCHZX4.js → chunk-U27EBKBG.js} +37 -1
  38. package/dist-node/chunk-U27EBKBG.js.map +7 -0
  39. package/dist-node/{engine-FRCX7PXF.js → engine-5F7DUU4W.js} +4 -4
  40. package/dist-node/export.js +178 -52
  41. package/dist-node/export.js.map +2 -2
  42. package/dist-node/generate-toc.js +7 -7
  43. package/dist-node/import.js +3 -3
  44. package/dist-node/measure.js +2 -2
  45. package/dist-node/recalc-docx.js +5 -5
  46. package/dist-node/recalc-docx.js.map +1 -1
  47. package/package.json +1 -1
  48. package/types/model.d.ts +277 -225
  49. package/types/recalc-docx.d.ts +27 -27
  50. package/types/wordcanvas.d.ts +212 -157
  51. package/dist-lib/assets/worker-BbFAIxdy.js +0 -2163
  52. package/dist-lib/blockFactory-Bcy0U40y.js +0 -178
  53. package/dist-lib/browser-5Sv4CuNy.js +0 -1284
  54. package/dist-lib/editorApp-DvjFPHiy.js +0 -7582
  55. package/dist-lib/metrics-CkAo4zh8.js +0 -143
  56. package/dist-lib/recalcToc-uaLWt59L.js +0 -3835
  57. package/dist-lib/styleProps-Db5pEkG8.js +0 -127
  58. package/dist-lib/toc-CZel_Kk3.js +0 -127
  59. package/dist-node/chunk-2RCPB7UZ.js.map +0 -7
  60. package/dist-node/chunk-4ICCHZX4.js.map +0 -7
  61. package/dist-node/chunk-4PDVRJF6.js.map +0 -7
  62. package/dist-node/chunk-GXFDF7UY.js.map +0 -7
  63. package/dist-node/chunk-TBSXA2I4.js.map +0 -7
  64. package/dist-node/chunk-XDUDPPJW.js.map +0 -7
  65. /package/dist-node/{chunk-BRAPV465.js.map → chunk-SA26UVIJ.js.map} +0 -0
  66. /package/dist-node/{engine-FRCX7PXF.js.map → engine-5F7DUU4W.js.map} +0 -0
@@ -111,6 +111,40 @@ var leaderWidth = (fontSizePx) => Math.max(1, fontSizePx / 14);
111
111
  var TOC_LEADER_DASH = [1, 4];
112
112
  var TOC_LEADER_GAP_PX = 8;
113
113
  var FOOTNOTE_RULE_WIDTH_FRACTION = 1 / 3;
114
+ var COLUMN_SEPARATOR_COLOR = "#c0c4c9";
115
+ var PAGE_BORDER_DEFAULT_SPACE = 24;
116
+ function pageBorderSegments(borders, pageW, pageH, margin) {
117
+ const fromText = borders.offsetFrom === "text";
118
+ const sp = (e) => e?.spacePx ?? PAGE_BORDER_DEFAULT_SPACE;
119
+ const xL = fromText ? margin.left - sp(borders.left) : sp(borders.left);
120
+ const xR = fromText ? pageW - margin.right + sp(borders.right) : pageW - sp(borders.right);
121
+ const yT = fromText ? margin.top - sp(borders.top) : sp(borders.top);
122
+ const yB = fromText ? pageH - margin.bottom + sp(borders.bottom) : pageH - sp(borders.bottom);
123
+ const out = [];
124
+ const push = (e, x1, y1, x2, y2) => {
125
+ if (!e || e.style === "none") return;
126
+ const w = e.style === "thick" ? Math.max(2, e.widthPx) : Math.max(0.5, e.widthPx);
127
+ const color = e.color === "auto" ? "#000000" : e.color;
128
+ const dash = e.style === "dashed" ? [w * 3, w * 2] : e.style === "dotted" ? [w, w * 1.5] : [];
129
+ out.push({ x1, y1, x2, y2, widthPx: w, color, dash });
130
+ if (e.style === "double") {
131
+ const g = w + 1;
132
+ const horizontal = y1 === y2;
133
+ if (horizontal) {
134
+ const dy = y1 < pageH / 2 ? g : -g;
135
+ out.push({ x1, y1: y1 + dy, x2, y2: y2 + dy, widthPx: w, color, dash: [] });
136
+ } else {
137
+ const dx = x1 < pageW / 2 ? g : -g;
138
+ out.push({ x1: x1 + dx, y1, x2: x2 + dx, y2, widthPx: w, color, dash: [] });
139
+ }
140
+ }
141
+ };
142
+ push(borders.top, xL, yT, xR, yT);
143
+ push(borders.bottom, xL, yB, xR, yB);
144
+ push(borders.left, xL, yT, xL, yB);
145
+ push(borders.right, xR, yT, xR, yB);
146
+ return out;
147
+ }
114
148
  var cellBorderWidth = (widthPx) => Math.max(0.5, widthPx);
115
149
  function cellBorderDash(style, w) {
116
150
  return style === "dashed" ? [w * 3, w * 2] : style === "dotted" ? [w, w * 1.5] : [];
@@ -175,6 +209,8 @@ export {
175
209
  TOC_LEADER_DASH,
176
210
  TOC_LEADER_GAP_PX,
177
211
  FOOTNOTE_RULE_WIDTH_FRACTION,
212
+ COLUMN_SEPARATOR_COLOR,
213
+ pageBorderSegments,
178
214
  cellBorderWidth,
179
215
  cellBorderDash,
180
216
  doubleBorderGap,
@@ -183,4 +219,4 @@ export {
183
219
  measureTextWidth,
184
220
  fontMetrics
185
221
  };
186
- //# sourceMappingURL=chunk-4ICCHZX4.js.map
222
+ //# sourceMappingURL=chunk-U27EBKBG.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/fonts/clones.ts", "../src/paint/paintStyle.ts", "../src/layout/metrics.ts"],
4
+ "sourcesContent": ["// Single source of truth for font substitution. The editor and both exporters\n// render & measure with these freely-redistributable metric clones instead of\n// the (Windows-only) originals, so layout is identical across the editor, the\n// browser export, and the Node backend \u2014 no system fonts anywhere. The model\n// keeps the original family names; only rendering/measuring/embedding map to a\n// clone. See src/export/shared/fonts/LICENSES.md.\n\n// \"TimesNewRoman\" is the genuine Microsoft Times New Roman (bundled at the user's\n// request), not a clone \u2014 see FONTS.md / fonts/LICENSES.md for the licensing note.\nexport const CLONE_FAMILIES = [\"Carlito\", \"Caladea\", \"Gelasio\", \"Arimo\", \"TimesNewRoman\", \"Cousine\"] as const;\nexport const FALLBACK_CLONE = \"Arimo\";\n\n// Requested family (lowercased, first CSS-stack token) -> bundled clone family.\n// Includes identity entries for the clone names themselves (so already-mapped\n// strings resolve to themselves) and the generic CSS families.\nexport const CLONE_OF: Record<string, string> = {\n // metric clones of common Office/Windows fonts\n calibri: \"Carlito\",\n cambria: \"Caladea\",\n georgia: \"Gelasio\",\n arial: \"Arimo\",\n helvetica: \"Arimo\",\n \"helvetica neue\": \"Arimo\",\n \"times new roman\": \"TimesNewRoman\",\n times: \"TimesNewRoman\",\n \"courier new\": \"Cousine\",\n courier: \"Cousine\",\n // reasonable substitutes for fonts we don't ship\n verdana: \"Arimo\",\n tahoma: \"Arimo\",\n \"segoe ui\": \"Arimo\",\n \"trebuchet ms\": \"Arimo\",\n consolas: \"Cousine\",\n monaco: \"Cousine\",\n \"lucida console\": \"Cousine\",\n garamond: \"TimesNewRoman\",\n \"book antiqua\": \"Caladea\",\n palatino: \"Caladea\",\n // generic CSS families\n serif: \"TimesNewRoman\",\n \"sans-serif\": \"Arimo\",\n sans: \"Arimo\",\n monospace: \"Cousine\",\n mono: \"Cousine\",\n // identity (already a clone)\n carlito: \"Carlito\",\n caladea: \"Caladea\",\n gelasio: \"Gelasio\",\n arimo: \"Arimo\",\n timesnewroman: \"TimesNewRoman\",\n cousine: \"Cousine\",\n};\n\n// Per-clone vertical metrics as a fraction of font size (ascent, descent above/\n// below the baseline). Used by fontMetrics so line heights are computed the SAME\n// way in the editor and both exporters \u2014 otherwise the browser's canvas\n// fontBoundingBox (editor) and fontkit's tables (export) disagree by a hair and\n// pagination drifts by a page. Values measured from the browser's canvas\n// fontBoundingBox at 16px (an exact multiple here), so the editor's appearance is\n// unchanged while the export now matches it on every platform.\nexport const CLONE_METRICS: Record<string, { ascent: number; descent: number }> = {\n Carlito: { ascent: 0.9375, descent: 0.25 },\n Caladea: { ascent: 0.9375, descent: 0.25 },\n Gelasio: { ascent: 0.9375, descent: 0.3125 },\n Arimo: { ascent: 0.875, descent: 0.1875 },\n TimesNewRoman: { ascent: 0.875, descent: 0.1875 },\n Cousine: { ascent: 0.625, descent: 0.1875 },\n};\n\n/** Resolve one CSS-stack family token to its bundled clone family. */\nexport function cloneFamilyFor(family: string): { clone: string; substituted: boolean } {\n const key = family.trim().toLowerCase().replace(/^[\"']|[\"']$/g, \"\");\n const clone = CLONE_OF[key];\n return clone ? { clone, substituted: false } : { clone: FALLBACK_CLONE, substituted: true };\n}\n\n/** First token of a CSS font-family stack (\"Calibri, serif\" -> \"Calibri\"). */\nexport const firstFamilyToken = (stack: string): string => (stack.split(\",\")[0] ?? \"\").trim();\n\nexport const FONT_STYLES = [\"Regular\", \"Bold\", \"Italic\", \"BoldItalic\"] as const;\n\n/** Every bundled face filename. */\nexport const FONT_FILES: string[] = CLONE_FAMILIES.flatMap((fam) =>\n FONT_STYLES.map((style) => `${fam}-${style}.ttf`),\n);\n\n/** Toolbar font list: value is the original family (stored in the model), label\n * shows the clone we actually render \u2014 e.g. \"Calibri (Carlito)\". */\nexport const TOOLBAR_FONTS: { value: string; label: string }[] = [\n { value: \"Calibri, sans-serif\", label: \"Calibri (Carlito)\" },\n { value: \"Cambria, serif\", label: \"Cambria (Caladea)\" },\n { value: \"Georgia, serif\", label: \"Georgia (Gelasio)\" },\n { value: \"Arial, sans-serif\", label: \"Arial (Arimo)\" },\n { value: \"Times New Roman, serif\", label: \"Times New Roman\" },\n { value: \"Verdana, sans-serif\", label: \"Verdana (Arimo)\" },\n { value: \"Consolas, monospace\", label: \"Consolas (Cousine)\" },\n { value: \"Courier New, monospace\", label: \"Courier New (Cousine)\" },\n];\n", "// Shared paint styling \u2014 the constants and per-run styling DECISIONS that the\n// canvas renderer (src/paint/renderer.ts) and the PDF painter\n// (src/export/pdf/paintBlock.ts) must keep in lockstep. Centralising the magic\n// numbers and the link/colour rules stops the two painters from silently\n// drifting; the actual draw calls stay separate (canvas vs pdfkit are different\n// APIs). Pure data + math \u2014 no DOM, no rendering backend.\n\nimport type { CharStyle, PageBorderEdge, PageBorders, TabLeader } from \"@cw/shared\";\n\n// --- colours ---------------------------------------------------------------\n/** Unstyled/native table grid (when a cell carries no explicit borders). */\nexport const DEFAULT_GRID_COLOR = \"#c0c4c9\";\n/** External hyperlinks paint in this blue (Word's affordance). */\nexport const EXTERNAL_LINK_COLOR = \"#0b57d0\";\n/** In-document anchors that inherited a Hyperlink blue read as plain text. */\nexport const ANCHOR_TEXT_COLOR = \"#202124\";\nexport const FOOTNOTE_RULE_COLOR = \"#80868b\";\n/** Non-printing formatting marks (space dots, tab arrows, pilcrows, line breaks)\n * drawn only when \"show formatting marks\" is on. A muted gray-blue, like Word. */\nexport const FORMATTING_MARK_COLOR = \"#7a8aa0\";\nexport const TOC_LEADER_COLOR = \"#9aa0a6\";\nexport const IMAGE_PLACEHOLDER_COLOR = \"#f1f3f4\";\n\n/** Office \"Hyperlink\" character-style blues, normalised to text colour when they\n * arrive on an in-document anchor (TOC/cross-ref) so those read as plain text. */\nexport const HYPERLINK_BLUES = new Set([\n \"#0563c1\", \"#0000ff\", \"#0000ee\", \"#0b57d0\", \"#0066cc\", \"#1155cc\",\n]);\n\n/** A leader/TOC colour that inherited a Hyperlink blue \u2192 text colour. */\nexport const normalizeLinkBlue = (color: string): string =>\n HYPERLINK_BLUES.has(color.toLowerCase()) ? ANCHOR_TEXT_COLOR : color;\n\n// --- sub/superscript -------------------------------------------------------\n/** Sub/superscript is MEASURED and PAINTED at this fraction of the run size\n * (metrics.charStyleToFont scales the font; the painters scale to match). */\nexport const SUB_SUPER_SCALE = 0.65;\nconst SUPER_SHIFT = -0.38;\nconst SUB_SHIFT = 0.16;\n\n/** Baseline shift (px) for a sub/superscript run \u2014 fraction of the ORIGINAL size. */\nexport function verticalShift(vertical: CharStyle[\"verticalAlign\"], fontSizePx: number): number {\n if (vertical === \"super\") return SUPER_SHIFT * fontSizePx;\n if (vertical === \"sub\") return SUB_SHIFT * fontSizePx;\n return 0;\n}\n\n// --- text decorations ------------------------------------------------------\n/** Underline / strikethrough thickness (px). */\nexport const decorationThickness = (fontSizePx: number): number => Math.max(1, fontSizePx / 14);\n/** Underline distance below the baseline (the run's vShift is added by the caller). */\nexport const UNDERLINE_OFFSET_PX = 1.5;\n/** Strikethrough offset relative to the baseline (above it). */\nexport const strikeOffset = (fontSizePx: number): number => -0.28 * fontSizePx;\n\n/** The colour + decoration decision for a run, with link normalisation applied. */\nexport interface RunPaint {\n color: string;\n underline: boolean;\n strike: boolean;\n /** External (non-anchor) link \u2014 paints blue+underlined; the PDF also annotates it. */\n externalLink: boolean;\n}\nexport function runPaint(style: CharStyle): RunPaint {\n const anchor = style.link !== undefined && style.link.startsWith(\"#\");\n const externalLink = style.link !== undefined && !anchor;\n let color = style.color;\n if (externalLink) color = EXTERNAL_LINK_COLOR;\n else if (anchor && HYPERLINK_BLUES.has(style.color.toLowerCase())) color = ANCHOR_TEXT_COLOR;\n return {\n color,\n underline: externalLink || (!!style.underline && !anchor),\n strike: !!style.strikethrough,\n externalLink,\n };\n}\n\n// --- leaders (tab + TOC) ---------------------------------------------------\n/** Tab-leader dash pattern; [] = solid (underscore/none). */\nexport function leaderDash(kind: TabLeader): number[] {\n return kind === \"dot\" ? [1, 3] : kind === \"dash\" ? [4, 3] : [];\n}\nexport const leaderWidth = (fontSizePx: number): number => Math.max(1, fontSizePx / 14);\nexport const TOC_LEADER_DASH: number[] = [1, 4];\n/** Gap (px) between an entry's text/number and its dot leader. */\nexport const TOC_LEADER_GAP_PX = 8;\n\n// --- footnote rule ---------------------------------------------------------\n/** The separator rule spans this fraction of the content width (Word style). */\nexport const FOOTNOTE_RULE_WIDTH_FRACTION = 1 / 3;\n\n// --- column separators -----------------------------------------------------\n/** Thin gray rule drawn between newspaper columns (w:cols/@w:sep). */\nexport const COLUMN_SEPARATOR_COLOR = \"#c0c4c9\";\n\n// --- page borders (w:pgBorders) --------------------------------------------\n/** Default offset (px) of a page border from the page edge / text when an edge\n * omits w:space. ~24pt is Word's typical page-border offset. */\nconst PAGE_BORDER_DEFAULT_SPACE = 24;\n\n/** One stroked line segment of a page border, in page (px) coords. */\nexport interface PageBorderSegment {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n widthPx: number;\n color: string;\n /** [] = solid. */\n dash: number[];\n}\n\n/** Geometry of a page border box \u2192 the line segments to stroke. Shared so the\n * canvas renderer and the PDF painter draw identical borders. \"double\" yields\n * two parallel segments; \"none\"/absent edges are skipped. */\nexport function pageBorderSegments(\n borders: PageBorders,\n pageW: number,\n pageH: number,\n margin: { top: number; right: number; bottom: number; left: number },\n): PageBorderSegment[] {\n const fromText = borders.offsetFrom === \"text\";\n const sp = (e?: PageBorderEdge): number => e?.spacePx ?? PAGE_BORDER_DEFAULT_SPACE;\n // Each edge's distance, then the box bounds (per-edge space; usually uniform).\n const xL = fromText ? margin.left - sp(borders.left) : sp(borders.left);\n const xR = fromText ? pageW - margin.right + sp(borders.right) : pageW - sp(borders.right);\n const yT = fromText ? margin.top - sp(borders.top) : sp(borders.top);\n const yB = fromText ? pageH - margin.bottom + sp(borders.bottom) : pageH - sp(borders.bottom);\n const out: PageBorderSegment[] = [];\n const push = (e: PageBorderEdge | undefined, x1: number, y1: number, x2: number, y2: number): void => {\n if (!e || e.style === \"none\") return;\n const w = e.style === \"thick\" ? Math.max(2, e.widthPx) : Math.max(0.5, e.widthPx);\n const color = e.color === \"auto\" ? \"#000000\" : e.color;\n const dash = e.style === \"dashed\" ? [w * 3, w * 2] : e.style === \"dotted\" ? [w, w * 1.5] : [];\n out.push({ x1, y1, x2, y2, widthPx: w, color, dash });\n if (e.style === \"double\") {\n const g = w + 1; // inset the second line toward the box interior\n const horizontal = y1 === y2;\n if (horizontal) {\n const dy = y1 < pageH / 2 ? g : -g;\n out.push({ x1, y1: y1 + dy, x2, y2: y2 + dy, widthPx: w, color, dash: [] });\n } else {\n const dx = x1 < pageW / 2 ? g : -g;\n out.push({ x1: x1 + dx, y1, x2: x2 + dx, y2, widthPx: w, color, dash: [] });\n }\n }\n };\n push(borders.top, xL, yT, xR, yT);\n push(borders.bottom, xL, yB, xR, yB);\n push(borders.left, xL, yT, xL, yB);\n push(borders.right, xR, yT, xR, yB);\n return out;\n}\n\n// --- cell borders ----------------------------------------------------------\n/** Hairlines clamp up so a thin rule stays visible. */\nexport const cellBorderWidth = (widthPx: number): number => Math.max(0.5, widthPx);\n/** Dash pattern for a cell edge of stroke width w; [] = solid. */\nexport function cellBorderDash(style: string | undefined, w: number): number[] {\n return style === \"dashed\" ? [w * 3, w * 2] : style === \"dotted\" ? [w, w * 1.5] : [];\n}\n/** Offset of the inner stroke of a \"double\" border. */\nexport const doubleBorderGap = (w: number): number => w + 1;\n", "// Vertical font metrics (pretext measures widths; ascent/descent are ours).\n// One hidden canvas, results cached per font string.\n\nimport type { CharStyle } from \"@cw/shared\";\nimport { CLONE_METRICS, cloneFamilyFor, firstFamilyToken } from \"../fonts/clones\";\nimport { SUB_SUPER_SCALE } from \"../paint/paintStyle\";\n\nexport interface FontMetrics {\n ascent: number;\n descent: number;\n}\n\n/** The slice of CanvasRenderingContext2D the layout actually measures through.\n * DOM-free hosts (the export worker, Node backends) inject their own via\n * {@link setMeasureContext}; the browser falls back to a real canvas. */\nexport interface MeasureContext {\n font: string;\n measureText(text: string): {\n width: number;\n fontBoundingBoxAscent: number;\n fontBoundingBoxDescent: number;\n };\n}\n\nlet injectedCtx: MeasureContext | null = null;\nlet browserCtx: MeasureContext | null = null;\n\n/** Install a measurement context (export/Node hosts). Must be called before the\n * engine measures any text. No-op for the browser, which uses a real canvas. */\nexport function setMeasureContext(ctx: MeasureContext): void {\n injectedCtx = ctx;\n}\n\n// Lazily created so importing this module never touches the DOM \u2014 the engine can\n// run under Node once a context is injected (browser path stays byte-identical).\nfunction measureContext(): MeasureContext {\n if (injectedCtx) return injectedCtx;\n if (!browserCtx) {\n browserCtx = document.createElement(\"canvas\").getContext(\"2d\")! as unknown as MeasureContext;\n }\n return browserCtx;\n}\n\nconst metricsCache = new Map<string, FontMetrics>();\n\nexport function charStyleToFont(s: CharStyle): string {\n const italic = s.italic ? \"italic \" : \"\";\n const weight = s.bold ? \"700\" : \"400\";\n // Sub/superscript runs are MEASURED at the scaled size (paint shifts the\n // baseline) \u2014 pretext must measure exactly what paint draws.\n const size = s.verticalAlign ? Math.round(s.fontSizePx * SUB_SUPER_SCALE) : s.fontSizePx;\n // Render & measure with the bundled metric clone (e.g. Calibri -> Carlito), so\n // the editor, the browser export, and the Node backend all lay out identically\n // \u2014 no dependency on system fonts. The model keeps the original family.\n const family = cloneFamilyFor(firstFamilyToken(s.fontFamily)).clone;\n return `${italic}${weight} ${size}px ${family}`;\n}\n\n/** One-off text width at a font \u2014 LAYOUT-time only (paint never measures).\n * Used for paint-only decorations the engine positions (TOC page numbers). */\nexport function measureTextWidth(text: string, font: string): number {\n const ctx = measureContext();\n ctx.font = font;\n return ctx.measureText(text).width;\n}\n\n// Vertical metrics come from baked per-clone ratios (NOT the canvas/fontkit\n// context), so the editor and the exporters compute identical line heights and\n// therefore identical pagination on every platform. charStyleToFont already\n// emits the clone family, so the last token of the font string is it.\nexport function fontMetrics(font: string): FontMetrics {\n let m = metricsCache.get(font);\n if (!m) {\n const sizeMatch = /(\\d+(?:\\.\\d+)?)px/.exec(font);\n const size = sizeMatch ? parseFloat(sizeMatch[1]!) : 16;\n const family = font.slice(font.indexOf(\"px \") + 3).trim();\n const r = CLONE_METRICS[cloneFamilyFor(family).clone] ?? { ascent: 0.9, descent: 0.25 };\n m = { ascent: Math.round(r.ascent * size), descent: Math.round(r.descent * size) };\n metricsCache.set(font, m);\n }\n return m;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -8,13 +8,13 @@ import {
8
8
  createLayoutEngine,
9
9
  effectiveSection,
10
10
  resolveSections
11
- } from "./chunk-IJLL6IFJ.js";
12
- import "./chunk-XDUDPPJW.js";
13
- import "./chunk-4ICCHZX4.js";
11
+ } from "./chunk-T3253N22.js";
12
+ import "./chunk-PIMVQULL.js";
13
+ import "./chunk-U27EBKBG.js";
14
14
  import "./chunk-XSRSBST2.js";
15
15
  export {
16
16
  createLayoutEngine,
17
17
  effectiveSection,
18
18
  resolveSections
19
19
  };
20
- //# sourceMappingURL=engine-FRCX7PXF.js.map
20
+ //# sourceMappingURL=engine-5F7DUU4W.js.map
@@ -16,7 +16,7 @@ import {
16
16
  require_unicode_trie,
17
17
  resolveFont,
18
18
  tslib_es6_exports
19
- } from "./chunk-BRAPV465.js";
19
+ } from "./chunk-SA26UVIJ.js";
20
20
  import {
21
21
  WML_NS,
22
22
  XML_DECL,
@@ -30,25 +30,28 @@ import {
30
30
  pxToEmu,
31
31
  pxToTwips,
32
32
  runPropsXml,
33
+ tableStyleXml,
33
34
  textEl
34
- } from "./chunk-4PDVRJF6.js";
35
+ } from "./chunk-EMY2P524.js";
35
36
  import {
36
37
  pageOfBlockMap
37
- } from "./chunk-TBSXA2I4.js";
38
+ } from "./chunk-EGSAVPCC.js";
38
39
  import {
39
40
  strToU8,
40
41
  zipSync
41
42
  } from "./chunk-5PUCGN47.js";
42
43
  import {
43
44
  createLayoutEngine
44
- } from "./chunk-IJLL6IFJ.js";
45
+ } from "./chunk-T3253N22.js";
45
46
  import {
46
47
  detectTocHeadings,
47
48
  inRange,
48
49
  parseTocInstruction,
50
+ styleType,
49
51
  textOfRuns
50
- } from "./chunk-XDUDPPJW.js";
52
+ } from "./chunk-PIMVQULL.js";
51
53
  import {
54
+ COLUMN_SEPARATOR_COLOR,
52
55
  DEFAULT_GRID_COLOR,
53
56
  FOOTNOTE_RULE_COLOR,
54
57
  FOOTNOTE_RULE_WIDTH_FRACTION,
@@ -65,10 +68,11 @@ import {
65
68
  leaderDash,
66
69
  leaderWidth,
67
70
  normalizeLinkBlue,
71
+ pageBorderSegments,
68
72
  runPaint,
69
73
  strikeOffset,
70
74
  verticalShift
71
- } from "./chunk-4ICCHZX4.js";
75
+ } from "./chunk-U27EBKBG.js";
72
76
  import {
73
77
  __commonJS,
74
78
  __require,
@@ -24545,7 +24549,7 @@ function toBuffer(bytes) {
24545
24549
  var PT = 72 / 96;
24546
24550
  async function renderPdf(doc, opts = {}) {
24547
24551
  await installMeasureHost();
24548
- const { createLayoutEngine: createLayoutEngine2 } = await import("./engine-FRCX7PXF.js");
24552
+ const { createLayoutEngine: createLayoutEngine2 } = await import("./engine-5F7DUU4W.js");
24549
24553
  const tree = createLayoutEngine2().layout(doc);
24550
24554
  const warnings = new WarningSink();
24551
24555
  const images = opts.images ?? {};
@@ -24600,8 +24604,23 @@ async function renderPdf(doc, opts = {}) {
24600
24604
  }
24601
24605
  pdf.save();
24602
24606
  pdf.scale(PT);
24603
- pdf.rect(0, 0, page.widthPx, page.heightPx).fill("#ffffff");
24607
+ pdf.rect(0, 0, page.widthPx, page.heightPx).fill(page.pageColorHex ?? "#ffffff");
24608
+ if (page.pageBorders) {
24609
+ for (const s of pageBorderSegments(page.pageBorders, page.widthPx, page.heightPx, page.marginPx)) {
24610
+ pdf.lineWidth(s.widthPx).strokeColor(s.color);
24611
+ if (s.dash.length) pdf.dash(s.dash[0], { space: s.dash[1] ?? s.dash[0] });
24612
+ else pdf.undash();
24613
+ pdf.moveTo(s.x1, s.y1).lineTo(s.x2, s.y2).stroke();
24614
+ }
24615
+ pdf.undash();
24616
+ }
24604
24617
  for (const block of page.blocks) paintBlock(ctx, block);
24618
+ if (page.columnSeparatorsX) {
24619
+ pdf.undash().lineWidth(1).strokeColor(COLUMN_SEPARATOR_COLOR);
24620
+ for (const x of page.columnSeparatorsX) {
24621
+ pdf.moveTo(x + 0.5, page.contentTopPx).lineTo(x + 0.5, page.contentBottomPx).stroke();
24622
+ }
24623
+ }
24605
24624
  if (page.footnoteRuleY !== void 0) {
24606
24625
  const cw = page.widthPx - page.marginPx.left - page.marginPx.right;
24607
24626
  pdf.undash().lineWidth(1).strokeColor(FOOTNOTE_RULE_COLOR);
@@ -24751,9 +24770,33 @@ function runsXml(runs, ctx) {
24751
24770
  const sdtId = runs[i].style.sdtId;
24752
24771
  if (sdtId) {
24753
24772
  let j = i;
24754
- let inner = "";
24755
- while (j < runs.length && runs[j].style.sdtId === sdtId) inner += singleRun(runs[j++], ctx);
24756
- out += el("w:sdt", void 0, sdtPrXml(ctx.sdts[sdtId]) + el("w:sdtContent", void 0, inner));
24773
+ const seg = [];
24774
+ while (j < runs.length && runs[j].style.sdtId === sdtId) seg.push(runs[j++]);
24775
+ out += el("w:sdt", void 0, sdtPrXml(ctx.sdts[sdtId]) + el("w:sdtContent", void 0, fieldRunsXml(seg, ctx)));
24776
+ i = j;
24777
+ } else {
24778
+ let j = i;
24779
+ const seg = [];
24780
+ while (j < runs.length && !runs[j].style.sdtId) seg.push(runs[j++]);
24781
+ out += fieldRunsXml(seg, ctx);
24782
+ i = j;
24783
+ }
24784
+ }
24785
+ return out;
24786
+ }
24787
+ function fieldRunsXml(runs, ctx) {
24788
+ let out = "";
24789
+ let i = 0;
24790
+ while (i < runs.length) {
24791
+ const fid = runs[i].style.fieldId;
24792
+ const def = fid ? ctx.fields?.[fid] : void 0;
24793
+ if (fid && def && def.kind === "builtin") {
24794
+ let j = i;
24795
+ const seg = [];
24796
+ while (j < runs.length && runs[j].style.fieldId === fid) seg.push(runs[j++]);
24797
+ const isToken = def.spec?.type === "PAGE" || def.spec?.type === "NUMPAGES";
24798
+ const result = seg.map((r) => el("w:r", void 0, runPropsXml(r.style) + (isToken ? "" : runContent(r.text)))).join("");
24799
+ out += fldRun("begin") + instrRun(def.instruction) + fldRun("separate") + result + fldRun("end");
24757
24800
  i = j;
24758
24801
  } else {
24759
24802
  out += singleRun(runs[i], ctx);
@@ -24820,10 +24863,24 @@ function paragraphXml(p, ctx) {
24820
24863
  const flush = (upto) => {
24821
24864
  while (mi < sorted.length && sorted[mi].offset <= upto) body += bookmarkEl(sorted[mi++]);
24822
24865
  };
24823
- for (const run of runs) {
24866
+ let ri = 0;
24867
+ while (ri < runs.length) {
24824
24868
  flush(cum);
24825
- body += singleRun(run, ctx);
24826
- cum += run.text.length;
24869
+ const fid = runs[ri].style.fieldId;
24870
+ const def = fid ? ctx.fields?.[fid] : void 0;
24871
+ if (fid && def && def.kind === "builtin") {
24872
+ const seg = [];
24873
+ while (ri < runs.length && runs[ri].style.fieldId === fid) {
24874
+ seg.push(runs[ri]);
24875
+ cum += runs[ri].text.length;
24876
+ ri++;
24877
+ }
24878
+ body += fieldRunsXml(seg, ctx);
24879
+ } else {
24880
+ body += singleRun(runs[ri], ctx);
24881
+ cum += runs[ri].text.length;
24882
+ ri++;
24883
+ }
24827
24884
  }
24828
24885
  flush(cum);
24829
24886
  while (mi < sorted.length) body += bookmarkEl(sorted[mi++]);
@@ -24906,13 +24963,25 @@ function tableXml(table, ctx) {
24906
24963
  col = emitContinues(col, out);
24907
24964
  rowsXml.push(el("w:tr", void 0, out.join("")));
24908
24965
  }
24966
+ const styleRef = table.styleId ? el("w:tblStyle", { "w:val": table.styleId }) : "";
24967
+ const look = table.styleId && table.condOverrides ? tblLookXml(table.condOverrides) : "";
24909
24968
  const tblPr = el(
24910
24969
  "w:tblPr",
24911
24970
  void 0,
24912
- el("w:tblW", { "w:w": 0, "w:type": "auto" }) + el("w:tblLayout", { "w:type": "fixed" })
24971
+ styleRef + el("w:tblW", { "w:w": 0, "w:type": "auto" }) + el("w:tblLayout", { "w:type": "fixed" }) + look
24913
24972
  );
24914
24973
  return el("w:tbl", void 0, tblPr + el("w:tblGrid", void 0, grid) + rowsXml.join(""));
24915
24974
  }
24975
+ function tblLookXml(o) {
24976
+ return el("w:tblLook", {
24977
+ "w:firstRow": o.firstRow ? "1" : "0",
24978
+ "w:lastRow": o.lastRow ? "1" : "0",
24979
+ "w:firstColumn": o.firstCol ? "1" : "0",
24980
+ "w:lastColumn": o.lastCol ? "1" : "0",
24981
+ "w:noHBand": o.bandRows ? "0" : "1",
24982
+ "w:noVBand": o.bandCols ? "0" : "1"
24983
+ });
24984
+ }
24916
24985
  function blockXml(block, ctx) {
24917
24986
  if (block.kind === "paragraph") return paragraphXml(block, ctx);
24918
24987
  if (block.kind === "table") return tableXml(block, ctx);
@@ -24984,38 +25053,75 @@ function imageParagraphXml(img, ctx) {
24984
25053
  const relId = ctx.rels.add(REL.image, target);
24985
25054
  const cx = pxToEmu(img.widthPx);
24986
25055
  const cy = pxToEmu(img.heightPx);
24987
- const drawing = el(
24988
- "w:drawing",
25056
+ const graphic = el(
25057
+ "a:graphic",
24989
25058
  void 0,
24990
25059
  el(
24991
- "wp:inline",
24992
- { distT: 0, distB: 0, distL: 0, distR: 0 },
24993
- el("wp:extent", { cx, cy }) + el("wp:docPr", { id: ctx.nextId(), name: "image" }) + el(
24994
- "a:graphic",
25060
+ "a:graphicData",
25061
+ { uri: "http://schemas.openxmlformats.org/drawingml/2006/picture" },
25062
+ el(
25063
+ "pic:pic",
24995
25064
  void 0,
24996
25065
  el(
24997
- "a:graphicData",
24998
- { uri: "http://schemas.openxmlformats.org/drawingml/2006/picture" },
24999
- el(
25000
- "pic:pic",
25001
- void 0,
25002
- el(
25003
- "pic:nvPicPr",
25004
- void 0,
25005
- el("pic:cNvPr", { id: 0, name: "image" }) + el("pic:cNvPicPr")
25006
- ) + el("pic:blipFill", void 0, el("a:blip", { "r:embed": relId }) + el("a:stretch", void 0, el("a:fillRect"))) + el(
25007
- "pic:spPr",
25008
- void 0,
25009
- el("a:xfrm", void 0, el("a:off", { x: 0, y: 0 }) + el("a:ext", { cx, cy })) + el("a:prstGeom", { prst: "rect" }, el("a:avLst"))
25010
- )
25011
- )
25066
+ "pic:nvPicPr",
25067
+ void 0,
25068
+ el("pic:cNvPr", { id: 0, name: "image" }) + el("pic:cNvPicPr")
25069
+ ) + el("pic:blipFill", void 0, el("a:blip", { "r:embed": relId }) + el("a:stretch", void 0, el("a:fillRect"))) + el(
25070
+ "pic:spPr",
25071
+ void 0,
25072
+ el("a:xfrm", void 0, el("a:off", { x: 0, y: 0 }) + el("a:ext", { cx, cy })) + el("a:prstGeom", { prst: "rect" }, el("a:avLst"))
25012
25073
  )
25013
25074
  )
25014
25075
  )
25015
25076
  );
25077
+ if (img.anchor) {
25078
+ const a = img.anchor;
25079
+ const anchor = el(
25080
+ "wp:anchor",
25081
+ {
25082
+ distT: 0,
25083
+ distB: 0,
25084
+ distL: 0,
25085
+ distR: 0,
25086
+ simplePos: 0,
25087
+ // relativeHeight is an unsigned int in OOXML; clamp the (possibly
25088
+ // negative, from repeated "send to back") z into the valid range.
25089
+ relativeHeight: Math.max(0, Math.round(a.z ?? 0)),
25090
+ behindDoc: a.behind ? 1 : 0,
25091
+ locked: 0,
25092
+ layoutInCell: 1,
25093
+ allowOverlap: 1
25094
+ },
25095
+ el("wp:simplePos", { x: 0, y: 0 }) + el("wp:positionH", { relativeFrom: a.relFromH }, el("wp:posOffset", void 0, String(pxToEmu(a.offsetXPx)))) + el("wp:positionV", { relativeFrom: a.relFromV }, el("wp:posOffset", void 0, String(pxToEmu(a.offsetYPx)))) + el("wp:extent", { cx, cy }) + el("wp:wrapNone") + el("wp:docPr", { id: ctx.nextId(), name: "image" }) + graphic
25096
+ );
25097
+ return el("w:p", void 0, el("w:r", void 0, el("w:drawing", void 0, anchor)));
25098
+ }
25099
+ const drawing = el(
25100
+ "w:drawing",
25101
+ void 0,
25102
+ el(
25103
+ "wp:inline",
25104
+ { distT: 0, distB: 0, distL: 0, distR: 0 },
25105
+ el("wp:extent", { cx, cy }) + el("wp:docPr", { id: ctx.nextId(), name: "image" }) + graphic
25106
+ )
25107
+ );
25016
25108
  const align = img.align === "center" ? "center" : img.align === "right" ? "right" : "left";
25017
25109
  return el("w:p", void 0, el("w:pPr", void 0, el("w:jc", { "w:val": align })) + el("w:r", void 0, drawing));
25018
25110
  }
25111
+ function pgBordersXml(b) {
25112
+ const edge = (name, e) => {
25113
+ if (!e || e.style === "none") return "";
25114
+ const spacePt = Math.min(31, Math.max(0, Math.round((e.spacePx ?? 0) * (72 / 96))));
25115
+ return el("w:" + name, {
25116
+ "w:val": e.style,
25117
+ "w:sz": pxToEighthPoints(e.widthPx),
25118
+ "w:space": spacePt,
25119
+ "w:color": e.color === "auto" ? "auto" : hexColor(e.color)
25120
+ });
25121
+ };
25122
+ const inner = edge("top", b.top) + edge("left", b.left) + edge("bottom", b.bottom) + edge("right", b.right);
25123
+ return el("w:pgBorders", { "w:offsetFrom": b.offsetFrom ?? "page" }, inner);
25124
+ }
25019
25125
  function sectPrXml(s, ctx, addBand, isBody) {
25020
25126
  const c = [];
25021
25127
  const band = (blocks, kind, type) => {
@@ -25031,7 +25137,12 @@ function sectPrXml(s, ctx, addBand, isBody) {
25031
25137
  band(s.footerEven, "footer", "even");
25032
25138
  if (s.headerFirst || s.footerFirst) c.push(el("w:titlePg"));
25033
25139
  if (s.pageWidthPx !== void 0 && s.pageHeightPx !== void 0) {
25034
- c.push(el("w:pgSz", { "w:w": pxToTwips(s.pageWidthPx), "w:h": pxToTwips(s.pageHeightPx) }));
25140
+ const pgSz = {
25141
+ "w:w": pxToTwips(s.pageWidthPx),
25142
+ "w:h": pxToTwips(s.pageHeightPx)
25143
+ };
25144
+ if (s.pageWidthPx > s.pageHeightPx) pgSz["w:orient"] = "landscape";
25145
+ c.push(el("w:pgSz", pgSz));
25035
25146
  }
25036
25147
  if (s.marginPx) {
25037
25148
  const m = s.marginPx;
@@ -25040,13 +25151,23 @@ function sectPrXml(s, ctx, addBand, isBody) {
25040
25151
  "w:right": pxToTwips(m.right),
25041
25152
  "w:bottom": pxToTwips(m.bottom),
25042
25153
  "w:left": pxToTwips(m.left),
25043
- "w:header": 720,
25044
- "w:footer": 720
25154
+ "w:header": s.headerDistancePx !== void 0 ? pxToTwips(s.headerDistancePx) : 720,
25155
+ "w:footer": s.footerDistancePx !== void 0 ? pxToTwips(s.footerDistancePx) : 720
25045
25156
  }));
25046
25157
  }
25047
25158
  if (s.columns && s.columns.count > 1) {
25048
- c.push(el("w:cols", { "w:num": s.columns.count, "w:space": pxToTwips(s.columns.gapPx) }));
25159
+ const cols = s.columns;
25160
+ const attrs = { "w:num": cols.count, "w:space": pxToTwips(cols.gapPx) };
25161
+ if (cols.sep) attrs["w:sep"] = "1";
25162
+ if (cols.cols && cols.cols.length === cols.count) {
25163
+ attrs["w:equalWidth"] = "0";
25164
+ const inner = cols.cols.map((col) => el("w:col", { "w:w": pxToTwips(col.widthPx), "w:space": pxToTwips(col.spaceAfterPx) })).join("");
25165
+ c.push(el("w:cols", attrs, inner));
25166
+ } else {
25167
+ c.push(el("w:cols", attrs));
25168
+ }
25049
25169
  }
25170
+ if (s.pageBorders) c.push(pgBordersXml(s.pageBorders));
25050
25171
  if (s.pageNumberStart !== void 0) c.push(el("w:pgNumType", { "w:start": s.pageNumberStart }));
25051
25172
  if (!isBody) c.push(el("w:type", { "w:val": "nextPage" }));
25052
25173
  return el("w:sectPr", void 0, c.join(""));
@@ -25071,7 +25192,8 @@ function buildDocumentXml(blocks, section, ctx, addBand) {
25071
25192
  }
25072
25193
  }
25073
25194
  const body = parts.join("") + sectPrXml(section, ctx, addBand, true);
25074
- return XML_DECL + el("w:document", WML_NS, el("w:body", void 0, body));
25195
+ const background = section.pageColorHex !== void 0 ? el("w:background", { "w:color": hexColor(section.pageColorHex) }) : "";
25196
+ return XML_DECL + el("w:document", WML_NS, background + el("w:body", void 0, body));
25075
25197
  }
25076
25198
 
25077
25199
  // src/export/docx/contentTypes.ts
@@ -25179,20 +25301,22 @@ function numberingXml(lists, listIdMap) {
25179
25301
  }
25180
25302
 
25181
25303
  // src/export/docx/settingsXml.ts
25182
- function settingsXml(evenAndOdd) {
25183
- const body = evenAndOdd ? el("w:evenAndOddHeadersAndFooters") : "";
25304
+ function settingsXml(evenAndOdd, displayBackgroundShape = false) {
25305
+ const body = (displayBackgroundShape ? el("w:displayBackgroundShape") : "") + (evenAndOdd ? el("w:evenAndOddHeadersAndFooters") : "");
25184
25306
  return XML_DECL + el("w:settings", WML_NS, body);
25185
25307
  }
25186
25308
 
25187
25309
  // src/export/docx/stylesXml.ts
25188
- function stylesXml(sheet) {
25189
- const styles = sheet.styles.map((s) => {
25190
- const attrs = { "w:type": "paragraph", "w:styleId": s.id };
25191
- if (s.id === sheet.defaultStyleId) attrs["w:default"] = "1";
25192
- const body = el("w:name", { "w:val": s.name }) + (s.basedOn ? el("w:basedOn", { "w:val": s.basedOn }) : "") + partialPPrXml(s.para) + partialRPrXml(s.char);
25310
+ function stylesXml(sheet, tableStyles) {
25311
+ const named = (sheet?.styles ?? []).map((s) => {
25312
+ const isChar = styleType(s) === "character";
25313
+ const attrs = { "w:type": isChar ? "character" : "paragraph", "w:styleId": s.id };
25314
+ if (!isChar && sheet && s.id === sheet.defaultStyleId) attrs["w:default"] = "1";
25315
+ const body = el("w:name", { "w:val": s.name }) + (s.basedOn ? el("w:basedOn", { "w:val": s.basedOn }) : "") + (isChar ? "" : partialPPrXml(s.para)) + partialRPrXml(s.char);
25193
25316
  return el("w:style", attrs, body);
25194
25317
  }).join("");
25195
- return XML_DECL + el("w:styles", WML_NS, styles);
25318
+ const tables = Object.values(tableStyles ?? {}).map(tableStyleXml).join("");
25319
+ return XML_DECL + el("w:styles", WML_NS, named + tables);
25196
25320
  }
25197
25321
 
25198
25322
  // src/export/docx/writeDocx.ts
@@ -25282,8 +25406,10 @@ function writeDocx(doc, images = {}, tocPages) {
25282
25406
  return bodyRels.add(kind === "header" ? REL.header : REL.footer, file);
25283
25407
  };
25284
25408
  parts["word/document.xml"] = buildDocumentXml(doc.blocks, doc.section, bodyCtx, addBand);
25285
- if (doc.stylesheet && doc.stylesheet.styles.length > 0) {
25286
- parts["word/styles.xml"] = stylesXml(doc.stylesheet);
25409
+ const hasNamed = !!doc.stylesheet && doc.stylesheet.styles.length > 0;
25410
+ const hasTableStyles = !!doc.tableStyles && Object.keys(doc.tableStyles).length > 0;
25411
+ if (hasNamed || hasTableStyles) {
25412
+ parts["word/styles.xml"] = stylesXml(doc.stylesheet, doc.tableStyles);
25287
25413
  overrides.push(["/word/styles.xml", CT.styles]);
25288
25414
  bodyRels.add(REL.styles, "styles.xml");
25289
25415
  }
@@ -25298,7 +25424,7 @@ function writeDocx(doc, images = {}, tocPages) {
25298
25424
  bodyRels.add(REL.footnotes, "footnotes.xml");
25299
25425
  }
25300
25426
  const evenAndOdd = sectionsHaveEvenBands(doc);
25301
- parts["word/settings.xml"] = settingsXml(evenAndOdd);
25427
+ parts["word/settings.xml"] = settingsXml(evenAndOdd, doc.section.pageColorHex !== void 0);
25302
25428
  overrides.push(["/word/settings.xml", CT.settings]);
25303
25429
  bodyRels.add(REL.settings, "settings.xml");
25304
25430
  for (const [path, bytes2] of bodyMedia.parts()) parts[`word/${path}`] = bytes2;