@forevka/wordcanvas 0.7.1 → 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 (59) hide show
  1. package/dist-lib/assets/{worker-D8rUOKfg.js → worker-4HYrkNrG.js} +10873 -10730
  2. package/dist-lib/assets/worker-CJpwUzKh.js +2403 -0
  3. package/dist-lib/blockFactory-B_CLnkUh.js +106 -0
  4. package/dist-lib/browser-p_rkUcqE.js +1339 -0
  5. package/dist-lib/builder.js +14 -13
  6. package/dist-lib/editorApp-mTD0YNv-.js +9863 -0
  7. package/dist-lib/export.js +4507 -4456
  8. package/dist-lib/generate-toc.js +5 -5
  9. package/dist-lib/import.js +2 -2
  10. package/dist-lib/measure.js +2 -2
  11. package/dist-lib/{measureHost-CMiJKdlk.js → measureHost-B2aKX7lF.js} +1 -1
  12. package/dist-lib/metrics-BUMIC9Mv.js +164 -0
  13. package/dist-lib/pipeline-DD0nxAYM.js +1850 -0
  14. package/dist-lib/recalc-docx.js +2 -2
  15. package/dist-lib/recalcToc-CFCGCgij.js +3985 -0
  16. package/dist-lib/styleProps-DhqDEfvc.js +149 -0
  17. package/dist-lib/stylesheet-B6oczA-L.js +141 -0
  18. package/dist-lib/{toc-B6VpkJge.js → toc-Dj57zdz-.js} +5 -5
  19. package/dist-lib/{webmcp-6WxRNKCc.js → webmcp-CXghUon_.js} +2 -2
  20. package/dist-lib/{wordcanvas-CFqpDK0k.js → wordcanvas-D5DI9ZR1.js} +49 -23
  21. package/dist-lib/wordcanvas.js +1 -1
  22. package/dist-node/{chunk-22JHAERT.js → chunk-57L5XMCL.js} +235 -39
  23. package/dist-node/chunk-57L5XMCL.js.map +7 -0
  24. package/dist-node/{chunk-4PDVRJF6.js → chunk-EMY2P524.js} +32 -2
  25. package/dist-node/chunk-EMY2P524.js.map +7 -0
  26. package/dist-node/{chunk-6AJJMHKY.js → chunk-KJV7GJ3H.js} +1 -1
  27. package/dist-node/chunk-KJV7GJ3H.js.map +7 -0
  28. package/dist-node/{chunk-CDN6WU2P.js → chunk-PIMVQULL.js} +7 -1
  29. package/dist-node/chunk-PIMVQULL.js.map +7 -0
  30. package/dist-node/{chunk-BRAPV465.js → chunk-SA26UVIJ.js} +2 -2
  31. package/dist-node/{chunk-RNB336F3.js → chunk-T3253N22.js} +92 -14
  32. package/dist-node/{chunk-RNB336F3.js.map → chunk-T3253N22.js.map} +2 -2
  33. package/dist-node/{chunk-4ICCHZX4.js → chunk-U27EBKBG.js} +37 -1
  34. package/dist-node/chunk-U27EBKBG.js.map +7 -0
  35. package/dist-node/{engine-5FGDJD2H.js → engine-5F7DUU4W.js} +4 -4
  36. package/dist-node/export.js +89 -24
  37. package/dist-node/export.js.map +2 -2
  38. package/dist-node/generate-toc.js +6 -6
  39. package/dist-node/import.js +3 -3
  40. package/dist-node/measure.js +2 -2
  41. package/dist-node/recalc-docx.js +4 -4
  42. package/package.json +1 -1
  43. package/types/model.d.ts +46 -0
  44. package/types/wordcanvas.d.ts +212 -157
  45. package/dist-lib/assets/worker-wXuuyshQ.js +0 -2258
  46. package/dist-lib/blockFactory-CUI4Rqiq.js +0 -186
  47. package/dist-lib/browser-DkKxreJM.js +0 -1305
  48. package/dist-lib/editorApp-DLXMmtu5.js +0 -8023
  49. package/dist-lib/metrics-CkAo4zh8.js +0 -143
  50. package/dist-lib/pipeline-CbxmCE84.js +0 -1705
  51. package/dist-lib/recalcToc-g1hFKSNs.js +0 -3935
  52. package/dist-lib/styleProps-Db5pEkG8.js +0 -127
  53. package/dist-node/chunk-22JHAERT.js.map +0 -7
  54. package/dist-node/chunk-4ICCHZX4.js.map +0 -7
  55. package/dist-node/chunk-4PDVRJF6.js.map +0 -7
  56. package/dist-node/chunk-6AJJMHKY.js.map +0 -7
  57. package/dist-node/chunk-CDN6WU2P.js.map +0 -7
  58. /package/dist-node/{chunk-BRAPV465.js.map → chunk-SA26UVIJ.js.map} +0 -0
  59. /package/dist-node/{engine-5FGDJD2H.js.map → engine-5F7DUU4W.js.map} +0 -0
@@ -77,6 +77,7 @@ var hexColor = (c) => c.replace(/^#/, "").toLowerCase();
77
77
  function runPropsXml(s) {
78
78
  const family = (s.fontFamily.split(",")[0] ?? "").trim();
79
79
  const children = [];
80
+ if (s.charStyleId) children.push(el("w:rStyle", { "w:val": s.charStyleId }));
80
81
  if (family) children.push(el("w:rFonts", { "w:ascii": family, "w:hAnsi": family, "w:cs": family }));
81
82
  children.push(el("w:b", { "w:val": s.bold ? "1" : "0" }));
82
83
  children.push(el("w:i", { "w:val": s.italic ? "1" : "0" }));
@@ -177,6 +178,34 @@ function partialPPrXml(p) {
177
178
  }
178
179
  return out.length > 0 ? el("w:pPr", void 0, out.join("")) : "";
179
180
  }
181
+ function condBordersXml(tag, b) {
182
+ const edge = (name, spec) => {
183
+ if (!spec) return "";
184
+ const val = spec.style === "double" ? "double" : spec.style === "dashed" ? "dashed" : spec.style === "dotted" ? "dotted" : "single";
185
+ return el("w:" + name, { "w:val": val, "w:sz": pxToEighthPoints(spec.widthPx), "w:space": 0, "w:color": hexColor(spec.color) });
186
+ };
187
+ return el(tag, void 0, edge("top", b.top) + edge("left", b.left) + edge("bottom", b.bottom) + edge("right", b.right));
188
+ }
189
+ var shdFillXml = (fill) => el("w:shd", { "w:val": "clear", "w:color": "auto", "w:fill": hexColor(fill) });
190
+ function tblStylePrXml(cond, props) {
191
+ const tc = [];
192
+ if (props.borders) tc.push(condBordersXml("w:tcBorders", props.borders));
193
+ if (props.shading) tc.push(shdFillXml(props.shading));
194
+ const tcPr = tc.length > 0 ? el("w:tcPr", void 0, tc.join("")) : "";
195
+ return el("w:tblStylePr", { "w:type": cond }, partialRPrXml(props.char ?? {}) + partialPPrXml(props.para ?? {}) + tcPr);
196
+ }
197
+ function tableStyleXml(style) {
198
+ const base = style.conds.wholeTable ?? {};
199
+ const tblPrKids = [];
200
+ if (style.rowBandSize !== void 0) tblPrKids.push(el("w:tblStyleRowBandSize", { "w:val": style.rowBandSize }));
201
+ if (style.colBandSize !== void 0) tblPrKids.push(el("w:tblStyleColBandSize", { "w:val": style.colBandSize }));
202
+ if (base.borders) tblPrKids.push(condBordersXml("w:tblBorders", base.borders));
203
+ if (base.shading) tblPrKids.push(shdFillXml(base.shading));
204
+ const tblPr = tblPrKids.length > 0 ? el("w:tblPr", void 0, tblPrKids.join("")) : "";
205
+ const conds = Object.entries(style.conds).filter(([c]) => c !== "wholeTable").map(([c, p]) => tblStylePrXml(c, p)).join("");
206
+ const body = el("w:name", { "w:val": style.name }) + (style.basedOn ? el("w:basedOn", { "w:val": style.basedOn }) : "") + partialRPrXml(base.char ?? {}) + partialPPrXml(base.para ?? {}) + tblPr + conds;
207
+ return el("w:style", { "w:type": "table", "w:styleId": style.id }, body);
208
+ }
180
209
 
181
210
  export {
182
211
  pxToTwips,
@@ -191,6 +220,7 @@ export {
191
220
  runPropsXml,
192
221
  paraCoreXml,
193
222
  partialRPrXml,
194
- partialPPrXml
223
+ partialPPrXml,
224
+ tableStyleXml
195
225
  };
196
- //# sourceMappingURL=chunk-4PDVRJF6.js.map
226
+ //# sourceMappingURL=chunk-EMY2P524.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/export/docx/xmlWrite.ts", "../src/export/units.ts", "../src/export/docx/mappings.ts", "../src/export/docx/styleProps.ts"],
4
+ "sourcesContent": ["// Minimal, dependency-free XML string emitter for OOXML parts. Attribute order is\n// insertion order (Word doesn't care); empty children collapse to self-closing.\n\nexport type Attrs = Record<string, string | number | boolean | undefined>;\n\nexport function escapeText(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n}\n\nfunction escapeAttr(s: string): string {\n return escapeText(s).replace(/\"/g, \"&quot;\");\n}\n\nfunction serializeAttrs(attrs: Attrs | undefined): string {\n if (!attrs) return \"\";\n let out = \"\";\n for (const [k, v] of Object.entries(attrs)) {\n if (v === undefined || v === false) continue;\n out += ` ${k}=\"${escapeAttr(String(v))}\"`;\n }\n return out;\n}\n\n/** An element. `children` is pre-serialized XML (caller joins arrays). Pass \"\"\n * or undefined for an empty element (self-closing). */\nexport function el(tag: string, attrs?: Attrs, children?: string): string {\n const a = serializeAttrs(attrs);\n if (children === undefined || children === \"\") return `<${tag}${a}/>`;\n return `<${tag}${a}>${children}</${tag}>`;\n}\n\n/** A w:t, preserving leading/trailing whitespace when present. */\nexport function textEl(text: string): string {\n const needsPreserve = /^\\s|\\s$|\\s\\s/.test(text) || text.includes(\"\\t\");\n return el(\"w:t\", needsPreserve ? { \"xml:space\": \"preserve\" } : undefined, escapeText(text));\n}\n\nexport const XML_DECL = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\\n';\n\n/** The w: + relationships + drawing namespaces Word expects on a document root. */\nexport const WML_NS = {\n \"xmlns:w\": \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n \"xmlns:r\": \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n \"xmlns:wp\": \"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\",\n \"xmlns:a\": \"http://schemas.openxmlformats.org/drawingml/2006/main\",\n \"xmlns:pic\": \"http://schemas.openxmlformats.org/drawingml/2006/picture\",\n \"xmlns:w14\": \"http://schemas.microsoft.com/office/word/2010/wordml\",\n \"xmlns:mc\": \"http://schemas.openxmlformats.org/markup-compatibility/2006\",\n} as const;\n", "// px (CSS, 96dpi) -> OOXML units. Exact inverses of src/import/docx/units.ts.\r\n\r\n// A non-finite value (NaN/Infinity from an undefined style field) would serialize\r\n// to an invalid w:val=\"NaN\" and corrupt the .docx. Coerce to 0 so output stays\r\n// well-formed \u2014 these are pure unit conversions, so NaN never legitimately occurs.\r\nconst finite = (n: number): number => (Number.isFinite(n) ? n : 0);\r\n\r\n/** px -> twips: px \u00D7 15 (twip = 1/20pt, px = tw/15). */\r\nexport const pxToTwips = (px: number): number => Math.round(finite(px) * 15);\r\n\r\n/** px -> half-points: px \u00D7 1.5 (w:sz; px = hp \u00D7 2/3). */\r\nexport const pxToHalfPoints = (px: number): number => Math.round(finite(px) * 1.5);\r\n\r\n/** px -> EMU: px \u00D7 9525 (image extents; 914400 EMU/in \u00F7 96). */\r\nexport const pxToEmu = (px: number): number => Math.round(finite(px) * 9525);\r\n\r\n/** px -> eighth-points: px \u00D7 6 (border w:sz; px = sz/6). */\r\nexport const pxToEighthPoints = (px: number): number => Math.max(2, Math.round(finite(px) * 6));\r\n\r\n/** line-height multiplier -> w:line 240ths (lineRule=\"auto\"). */\r\nexport const multiplierToLine = (m: number): number => Math.round(finite(m) * 240);\r\n", "// Single source for model \u2192 WordprocessingML value mappings shared across the\r\n// exporter (documentXml, styleProps) and the headless TOC generator. Keeping\r\n// these in one place prevents the maps from drifting apart (they already had).\r\n\r\nimport type { ParaStyle } from \"@cw/shared\";\r\n\r\n/** highlight hex \u2192 Word highlight color name (w:highlight w:val). */\r\nexport const HIGHLIGHT_NAME: Record<string, string> = {\r\n \"#ffff00\": \"yellow\", \"#00ff00\": \"green\", \"#00ffff\": \"cyan\", \"#ff00ff\": \"magenta\",\r\n \"#0000ff\": \"blue\", \"#ff0000\": \"red\", \"#000080\": \"darkBlue\", \"#008080\": \"darkCyan\",\r\n \"#008000\": \"darkGreen\", \"#800080\": \"darkMagenta\", \"#800000\": \"darkRed\", \"#808000\": \"darkYellow\",\r\n \"#808080\": \"darkGray\", \"#c0c0c0\": \"lightGray\", \"#000000\": \"black\", \"#ffffff\": \"white\",\r\n};\r\n\r\n/** paragraph align \u2192 w:jc w:val. */\r\nexport const JC: Record<ParaStyle[\"align\"], string> = { left: \"left\", center: \"center\", right: \"right\", justify: \"both\" };\r\n\r\n/** tab align \u2192 w:tab w:val. */\r\nexport const TAB_VAL: Record<string, string> = { left: \"left\", center: \"center\", right: \"right\", decimal: \"decimal\" };\r\n\r\n/** tab leader \u2192 w:tab w:leader. */\r\nexport const TAB_LEADER: Record<string, string> = { dot: \"dot\", dash: \"hyphen\", underscore: \"underscore\" };\r\n\r\n/** strip a leading \"#\" and lowercase a hex color for OOXML w:val attributes. */\r\nexport const hexColor = (c: string): string => c.replace(/^#/, \"\").toLowerCase();\r\n", "// Partial rPr/pPr emitters for style-gallery patches (NamedStyle.char/.para) and\r\n// list-marker run props. Only DEFINED fields are written \u2014 the importer reads\r\n// each w:* element into a patch field, so absent elements stay absent.\r\n\r\nimport type { CellBorders, CharStyle, ParaStyle, TableCond, TableCondProps, TableStyle } from \"@cw/shared\";\r\nimport { multiplierToLine, pxToEighthPoints, pxToHalfPoints, pxToTwips } from \"../units\";\r\nimport { HIGHLIGHT_NAME, hexColor as hex, JC, TAB_LEADER, TAB_VAL } from \"./mappings\";\r\nimport { el } from \"./xmlWrite\";\r\n\r\n// Full rPr serializer: every toggle written explicitly (on/off) so a run's\r\n// direct formatting fully overrides any inherited paragraph/named style on\r\n// re-import. Shared by the main exporter (documentXml) and the headless TOC\r\n// generator (recalc/generateTocDocx) so they can't drift.\r\nexport function runPropsXml(s: CharStyle): string {\r\n const family = (s.fontFamily.split(\",\")[0] ?? \"\").trim();\r\n const children: string[] = [];\r\n // w:rStyle (character-style reference) must be the first child of w:rPr; the\r\n // concrete props below still override it on re-import (Word semantics).\r\n if (s.charStyleId) children.push(el(\"w:rStyle\", { \"w:val\": s.charStyleId }));\r\n if (family) children.push(el(\"w:rFonts\", { \"w:ascii\": family, \"w:hAnsi\": family, \"w:cs\": family }));\r\n children.push(el(\"w:b\", { \"w:val\": s.bold ? \"1\" : \"0\" }));\r\n children.push(el(\"w:i\", { \"w:val\": s.italic ? \"1\" : \"0\" }));\r\n if (s.strikethrough) children.push(el(\"w:strike\", { \"w:val\": \"1\" }));\r\n if (s.hidden) children.push(el(\"w:vanish\", { \"w:val\": \"1\" })); // preserved hidden text\r\n children.push(el(\"w:color\", { \"w:val\": hex(s.color) }));\r\n children.push(el(\"w:sz\", { \"w:val\": pxToHalfPoints(s.fontSizePx) }));\r\n children.push(el(\"w:szCs\", { \"w:val\": pxToHalfPoints(s.fontSizePx) }));\r\n children.push(el(\"w:u\", { \"w:val\": s.underline ? \"single\" : \"none\" }));\r\n if (s.letterSpacingPx !== undefined) children.push(el(\"w:spacing\", { \"w:val\": pxToTwips(s.letterSpacingPx) }));\r\n if (s.highlightColor) {\r\n const name = HIGHLIGHT_NAME[s.highlightColor.toLowerCase()];\r\n if (name) children.push(el(\"w:highlight\", { \"w:val\": name }));\r\n }\r\n if (s.verticalAlign) children.push(el(\"w:vertAlign\", { \"w:val\": s.verticalAlign === \"super\" ? \"superscript\" : \"subscript\" }));\r\n return el(\"w:rPr\", undefined, children.join(\"\"));\r\n}\r\n\r\n// The spacing/indent/jc/tabs core shared by every full pPr: returned as\r\n// concatenated child elements (NOT wrapped in w:pPr) so the main exporter can\r\n// interleave it with pStyle/numPr/breaks/section props, while the TOC generator\r\n// wraps it directly. Emission order matches OOXML's tolerant exporter convention.\r\nexport function paraCoreXml(style: ParaStyle): string {\r\n const c: string[] = [];\r\n c.push(el(\"w:spacing\", {\r\n \"w:before\": pxToTwips(style.spaceBeforePx),\r\n \"w:after\": pxToTwips(style.spaceAfterPx),\r\n \"w:line\": multiplierToLine(style.lineHeight),\r\n \"w:lineRule\": \"auto\",\r\n }));\r\n const ind: Record<string, number> = {};\r\n if (style.indentLeftPx) ind[\"w:left\"] = pxToTwips(style.indentLeftPx);\r\n if (style.indentRightPx) ind[\"w:right\"] = pxToTwips(style.indentRightPx);\r\n if (style.indentFirstLinePx > 0) ind[\"w:firstLine\"] = pxToTwips(style.indentFirstLinePx);\r\n else if (style.indentFirstLinePx < 0) ind[\"w:hanging\"] = pxToTwips(-style.indentFirstLinePx);\r\n if (Object.keys(ind).length > 0) c.push(el(\"w:ind\", ind));\r\n c.push(el(\"w:jc\", { \"w:val\": JC[style.align] }));\r\n if (style.tabStops && style.tabStops.length > 0) {\r\n const tabs = style.tabStops\r\n .map((t) =>\r\n el(\"w:tab\", {\r\n \"w:val\": t.align ? (TAB_VAL[t.align] ?? \"left\") : \"left\",\r\n \"w:pos\": pxToTwips(t.posPx),\r\n \"w:leader\": t.leader && t.leader !== \"none\" ? TAB_LEADER[t.leader] : undefined,\r\n }),\r\n )\r\n .join(\"\");\r\n c.push(el(\"w:tabs\", undefined, tabs));\r\n }\r\n return c.join(\"\");\r\n}\r\n\r\nexport function partialRPrXml(c: Partial<CharStyle>): string {\r\n const out: string[] = [];\r\n if (c.fontFamily) {\r\n const fam = (c.fontFamily.split(\",\")[0] ?? \"\").trim();\r\n if (fam) out.push(el(\"w:rFonts\", { \"w:ascii\": fam, \"w:hAnsi\": fam, \"w:cs\": fam }));\r\n }\r\n if (c.bold !== undefined) out.push(el(\"w:b\", { \"w:val\": c.bold ? \"1\" : \"0\" }));\r\n if (c.italic !== undefined) out.push(el(\"w:i\", { \"w:val\": c.italic ? \"1\" : \"0\" }));\r\n if (c.strikethrough !== undefined) out.push(el(\"w:strike\", { \"w:val\": c.strikethrough ? \"1\" : \"0\" }));\r\n if (c.color) out.push(el(\"w:color\", { \"w:val\": hex(c.color) }));\r\n if (c.fontSizePx !== undefined) {\r\n const sz = Math.round(c.fontSizePx * 1.5);\r\n out.push(el(\"w:sz\", { \"w:val\": sz }));\r\n out.push(el(\"w:szCs\", { \"w:val\": sz }));\r\n }\r\n if (c.underline !== undefined) out.push(el(\"w:u\", { \"w:val\": c.underline ? \"single\" : \"none\" }));\r\n if (c.highlightColor) {\r\n const name = HIGHLIGHT_NAME[c.highlightColor.toLowerCase()];\r\n if (name) out.push(el(\"w:highlight\", { \"w:val\": name }));\r\n }\r\n if (c.verticalAlign) out.push(el(\"w:vertAlign\", { \"w:val\": c.verticalAlign === \"super\" ? \"superscript\" : \"subscript\" }));\r\n return out.length > 0 ? el(\"w:rPr\", undefined, out.join(\"\")) : \"\";\r\n}\r\n\r\nexport function partialPPrXml(p: Partial<ParaStyle>): string {\r\n const out: string[] = [];\r\n if (p.align) out.push(el(\"w:jc\", { \"w:val\": JC[p.align] ?? \"left\" }));\r\n const sp: Record<string, number | string> = {};\r\n if (p.spaceBeforePx !== undefined) sp[\"w:before\"] = pxToTwips(p.spaceBeforePx);\r\n if (p.spaceAfterPx !== undefined) sp[\"w:after\"] = pxToTwips(p.spaceAfterPx);\r\n if (p.lineHeight !== undefined) {\r\n sp[\"w:line\"] = multiplierToLine(p.lineHeight);\r\n sp[\"w:lineRule\"] = \"auto\";\r\n }\r\n if (Object.keys(sp).length > 0) out.push(el(\"w:spacing\", sp));\r\n const ind: Record<string, number> = {};\r\n if (p.indentLeftPx !== undefined) ind[\"w:left\"] = pxToTwips(p.indentLeftPx);\r\n if (p.indentRightPx !== undefined) ind[\"w:right\"] = pxToTwips(p.indentRightPx);\r\n if (p.indentFirstLinePx !== undefined) {\r\n if (p.indentFirstLinePx >= 0) ind[\"w:firstLine\"] = pxToTwips(p.indentFirstLinePx);\r\n else ind[\"w:hanging\"] = pxToTwips(-p.indentFirstLinePx);\r\n }\r\n if (Object.keys(ind).length > 0) out.push(el(\"w:ind\", ind));\r\n if (p.keepWithNext) out.push(el(\"w:keepNext\"));\r\n if (p.keepLinesTogether) out.push(el(\"w:keepLines\"));\r\n if (p.tabStops && p.tabStops.length > 0) {\r\n const tabs = p.tabStops\r\n .map((t) =>\r\n el(\"w:tab\", {\r\n \"w:val\": t.align ? (TAB_VAL[t.align] ?? \"left\") : \"left\",\r\n \"w:pos\": pxToTwips(t.posPx),\r\n \"w:leader\": t.leader && t.leader !== \"none\" ? TAB_LEADER[t.leader] : undefined,\r\n }),\r\n )\r\n .join(\"\");\r\n out.push(el(\"w:tabs\", undefined, tabs));\r\n }\r\n return out.length > 0 ? el(\"w:pPr\", undefined, out.join(\"\")) : \"\";\r\n}\r\n\n// ---------------------------------------------------------------------------\n// Table styles \u2192 w:style[@w:type=\"table\"] with conditional formatting.\n\nfunction condBordersXml(tag: string, b: CellBorders): string {\n const edge = (name: string, spec: CellBorders[\"top\"]): string => {\n if (!spec) return \"\";\n const val = spec.style === \"double\" ? \"double\" : spec.style === \"dashed\" ? \"dashed\" : spec.style === \"dotted\" ? \"dotted\" : \"single\";\n return el(\"w:\" + name, { \"w:val\": val, \"w:sz\": pxToEighthPoints(spec.widthPx), \"w:space\": 0, \"w:color\": hex(spec.color) });\n };\n return el(tag, undefined, edge(\"top\", b.top) + edge(\"left\", b.left) + edge(\"bottom\", b.bottom) + edge(\"right\", b.right));\n}\n\nconst shdFillXml = (fill: string): string => el(\"w:shd\", { \"w:val\": \"clear\", \"w:color\": \"auto\", \"w:fill\": hex(fill) });\n\nfunction tblStylePrXml(cond: TableCond, props: TableCondProps): string {\n const tc: string[] = [];\n if (props.borders) tc.push(condBordersXml(\"w:tcBorders\", props.borders));\n if (props.shading) tc.push(shdFillXml(props.shading));\n const tcPr = tc.length > 0 ? el(\"w:tcPr\", undefined, tc.join(\"\")) : \"\";\n return el(\"w:tblStylePr\", { \"w:type\": cond }, partialRPrXml(props.char ?? {}) + partialPPrXml(props.para ?? {}) + tcPr);\n}\n\n/** One table style. Base (wholeTable) \u2192 rPr/pPr + tblPr(borders/shd/band sizes);\n * each other condition \u2192 a w:tblStylePr. */\nexport function tableStyleXml(style: TableStyle): string {\n const base = style.conds.wholeTable ?? {};\n const tblPrKids: string[] = [];\n if (style.rowBandSize !== undefined) tblPrKids.push(el(\"w:tblStyleRowBandSize\", { \"w:val\": style.rowBandSize }));\n if (style.colBandSize !== undefined) tblPrKids.push(el(\"w:tblStyleColBandSize\", { \"w:val\": style.colBandSize }));\n if (base.borders) tblPrKids.push(condBordersXml(\"w:tblBorders\", base.borders));\n if (base.shading) tblPrKids.push(shdFillXml(base.shading));\n const tblPr = tblPrKids.length > 0 ? el(\"w:tblPr\", undefined, tblPrKids.join(\"\")) : \"\";\n const conds = (Object.entries(style.conds) as [TableCond, TableCondProps][])\n .filter(([c]) => c !== \"wholeTable\")\n .map(([c, p]) => tblStylePrXml(c, p))\n .join(\"\");\n const body =\n el(\"w:name\", { \"w:val\": style.name }) +\n (style.basedOn ? el(\"w:basedOn\", { \"w:val\": style.basedOn }) : \"\") +\n partialRPrXml(base.char ?? {}) +\n partialPPrXml(base.para ?? {}) +\n tblPr +\n conds;\n return el(\"w:style\", { \"w:type\": \"table\", \"w:styleId\": style.id }, body);\n}\n"],
5
+ "mappings": ";;;;;;;;AAKO,SAAS,WAAW,GAAmB;AAC5C,SAAO,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM;AAC5E;AAEA,SAAS,WAAW,GAAmB;AACrC,SAAO,WAAW,CAAC,EAAE,QAAQ,MAAM,QAAQ;AAC7C;AAEA,SAAS,eAAe,OAAkC;AACxD,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM;AACV,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,MAAM,UAAa,MAAM,MAAO;AACpC,WAAO,IAAI,CAAC,KAAK,WAAW,OAAO,CAAC,CAAC,CAAC;AAAA,EACxC;AACA,SAAO;AACT;AAIO,SAAS,GAAG,KAAa,OAAe,UAA2B;AACxE,QAAM,IAAI,eAAe,KAAK;AAC9B,MAAI,aAAa,UAAa,aAAa,GAAI,QAAO,IAAI,GAAG,GAAG,CAAC;AACjE,SAAO,IAAI,GAAG,GAAG,CAAC,IAAI,QAAQ,KAAK,GAAG;AACxC;AAGO,SAAS,OAAO,MAAsB;AAC3C,QAAM,gBAAgB,eAAe,KAAK,IAAI,KAAK,KAAK,SAAS,GAAI;AACrE,SAAO,GAAG,OAAO,gBAAgB,EAAE,aAAa,WAAW,IAAI,QAAW,WAAW,IAAI,CAAC;AAC5F;AAEO,IAAM,WAAW;AAGjB,IAAM,SAAS;AAAA,EACpB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,YAAY;AACd;;;AC3CA,IAAM,SAAS,CAAC,MAAuB,OAAO,SAAS,CAAC,IAAI,IAAI;AAGzD,IAAM,YAAY,CAAC,OAAuB,KAAK,MAAM,OAAO,EAAE,IAAI,EAAE;AAGpE,IAAM,iBAAiB,CAAC,OAAuB,KAAK,MAAM,OAAO,EAAE,IAAI,GAAG;AAG1E,IAAM,UAAU,CAAC,OAAuB,KAAK,MAAM,OAAO,EAAE,IAAI,IAAI;AAGpE,IAAM,mBAAmB,CAAC,OAAuB,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,EAAE,IAAI,CAAC,CAAC;AAGvF,IAAM,mBAAmB,CAAC,MAAsB,KAAK,MAAM,OAAO,CAAC,IAAI,GAAG;;;ACb1E,IAAM,iBAAyC;AAAA,EACpD,WAAW;AAAA,EAAU,WAAW;AAAA,EAAS,WAAW;AAAA,EAAQ,WAAW;AAAA,EACvE,WAAW;AAAA,EAAQ,WAAW;AAAA,EAAO,WAAW;AAAA,EAAY,WAAW;AAAA,EACvE,WAAW;AAAA,EAAa,WAAW;AAAA,EAAe,WAAW;AAAA,EAAW,WAAW;AAAA,EACnF,WAAW;AAAA,EAAY,WAAW;AAAA,EAAa,WAAW;AAAA,EAAS,WAAW;AAChF;AAGO,IAAM,KAAyC,EAAE,MAAM,QAAQ,QAAQ,UAAU,OAAO,SAAS,SAAS,OAAO;AAGjH,IAAM,UAAkC,EAAE,MAAM,QAAQ,QAAQ,UAAU,OAAO,SAAS,SAAS,UAAU;AAG7G,IAAM,aAAqC,EAAE,KAAK,OAAO,MAAM,UAAU,YAAY,aAAa;AAGlG,IAAM,WAAW,CAAC,MAAsB,EAAE,QAAQ,MAAM,EAAE,EAAE,YAAY;;;ACXxE,SAAS,YAAY,GAAsB;AAChD,QAAM,UAAU,EAAE,WAAW,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK;AACvD,QAAM,WAAqB,CAAC;AAG5B,MAAI,EAAE,YAAa,UAAS,KAAK,GAAG,YAAY,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAC3E,MAAI,OAAQ,UAAS,KAAK,GAAG,YAAY,EAAE,WAAW,QAAQ,WAAW,QAAQ,QAAQ,OAAO,CAAC,CAAC;AAClG,WAAS,KAAK,GAAG,OAAO,EAAE,SAAS,EAAE,OAAO,MAAM,IAAI,CAAC,CAAC;AACxD,WAAS,KAAK,GAAG,OAAO,EAAE,SAAS,EAAE,SAAS,MAAM,IAAI,CAAC,CAAC;AAC1D,MAAI,EAAE,cAAe,UAAS,KAAK,GAAG,YAAY,EAAE,SAAS,IAAI,CAAC,CAAC;AACnE,MAAI,EAAE,OAAQ,UAAS,KAAK,GAAG,YAAY,EAAE,SAAS,IAAI,CAAC,CAAC;AAC5D,WAAS,KAAK,GAAG,WAAW,EAAE,SAAS,SAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtD,WAAS,KAAK,GAAG,QAAQ,EAAE,SAAS,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC;AACnE,WAAS,KAAK,GAAG,UAAU,EAAE,SAAS,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC;AACrE,WAAS,KAAK,GAAG,OAAO,EAAE,SAAS,EAAE,YAAY,WAAW,OAAO,CAAC,CAAC;AACrE,MAAI,EAAE,oBAAoB,OAAW,UAAS,KAAK,GAAG,aAAa,EAAE,SAAS,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;AAC7G,MAAI,EAAE,gBAAgB;AACpB,UAAM,OAAO,eAAe,EAAE,eAAe,YAAY,CAAC;AAC1D,QAAI,KAAM,UAAS,KAAK,GAAG,eAAe,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,EAC9D;AACA,MAAI,EAAE,cAAe,UAAS,KAAK,GAAG,eAAe,EAAE,SAAS,EAAE,kBAAkB,UAAU,gBAAgB,YAAY,CAAC,CAAC;AAC5H,SAAO,GAAG,SAAS,QAAW,SAAS,KAAK,EAAE,CAAC;AACjD;AAMO,SAAS,YAAY,OAA0B;AACpD,QAAM,IAAc,CAAC;AACrB,IAAE,KAAK,GAAG,aAAa;AAAA,IACrB,YAAY,UAAU,MAAM,aAAa;AAAA,IACzC,WAAW,UAAU,MAAM,YAAY;AAAA,IACvC,UAAU,iBAAiB,MAAM,UAAU;AAAA,IAC3C,cAAc;AAAA,EAChB,CAAC,CAAC;AACF,QAAM,MAA8B,CAAC;AACrC,MAAI,MAAM,aAAc,KAAI,QAAQ,IAAI,UAAU,MAAM,YAAY;AACpE,MAAI,MAAM,cAAe,KAAI,SAAS,IAAI,UAAU,MAAM,aAAa;AACvE,MAAI,MAAM,oBAAoB,EAAG,KAAI,aAAa,IAAI,UAAU,MAAM,iBAAiB;AAAA,WAC9E,MAAM,oBAAoB,EAAG,KAAI,WAAW,IAAI,UAAU,CAAC,MAAM,iBAAiB;AAC3F,MAAI,OAAO,KAAK,GAAG,EAAE,SAAS,EAAG,GAAE,KAAK,GAAG,SAAS,GAAG,CAAC;AACxD,IAAE,KAAK,GAAG,QAAQ,EAAE,SAAS,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC;AAC/C,MAAI,MAAM,YAAY,MAAM,SAAS,SAAS,GAAG;AAC/C,UAAM,OAAO,MAAM,SAChB;AAAA,MAAI,CAAC,MACJ,GAAG,SAAS;AAAA,QACV,SAAS,EAAE,QAAS,QAAQ,EAAE,KAAK,KAAK,SAAU;AAAA,QAClD,SAAS,UAAU,EAAE,KAAK;AAAA,QAC1B,YAAY,EAAE,UAAU,EAAE,WAAW,SAAS,WAAW,EAAE,MAAM,IAAI;AAAA,MACvE,CAAC;AAAA,IACH,EACC,KAAK,EAAE;AACV,MAAE,KAAK,GAAG,UAAU,QAAW,IAAI,CAAC;AAAA,EACtC;AACA,SAAO,EAAE,KAAK,EAAE;AAClB;AAEO,SAAS,cAAc,GAA+B;AAC3D,QAAM,MAAgB,CAAC;AACvB,MAAI,EAAE,YAAY;AAChB,UAAM,OAAO,EAAE,WAAW,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK;AACpD,QAAI,IAAK,KAAI,KAAK,GAAG,YAAY,EAAE,WAAW,KAAK,WAAW,KAAK,QAAQ,IAAI,CAAC,CAAC;AAAA,EACnF;AACA,MAAI,EAAE,SAAS,OAAW,KAAI,KAAK,GAAG,OAAO,EAAE,SAAS,EAAE,OAAO,MAAM,IAAI,CAAC,CAAC;AAC7E,MAAI,EAAE,WAAW,OAAW,KAAI,KAAK,GAAG,OAAO,EAAE,SAAS,EAAE,SAAS,MAAM,IAAI,CAAC,CAAC;AACjF,MAAI,EAAE,kBAAkB,OAAW,KAAI,KAAK,GAAG,YAAY,EAAE,SAAS,EAAE,gBAAgB,MAAM,IAAI,CAAC,CAAC;AACpG,MAAI,EAAE,MAAO,KAAI,KAAK,GAAG,WAAW,EAAE,SAAS,SAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9D,MAAI,EAAE,eAAe,QAAW;AAC9B,UAAM,KAAK,KAAK,MAAM,EAAE,aAAa,GAAG;AACxC,QAAI,KAAK,GAAG,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC;AACpC,QAAI,KAAK,GAAG,UAAU,EAAE,SAAS,GAAG,CAAC,CAAC;AAAA,EACxC;AACA,MAAI,EAAE,cAAc,OAAW,KAAI,KAAK,GAAG,OAAO,EAAE,SAAS,EAAE,YAAY,WAAW,OAAO,CAAC,CAAC;AAC/F,MAAI,EAAE,gBAAgB;AACpB,UAAM,OAAO,eAAe,EAAE,eAAe,YAAY,CAAC;AAC1D,QAAI,KAAM,KAAI,KAAK,GAAG,eAAe,EAAE,SAAS,KAAK,CAAC,CAAC;AAAA,EACzD;AACA,MAAI,EAAE,cAAe,KAAI,KAAK,GAAG,eAAe,EAAE,SAAS,EAAE,kBAAkB,UAAU,gBAAgB,YAAY,CAAC,CAAC;AACvH,SAAO,IAAI,SAAS,IAAI,GAAG,SAAS,QAAW,IAAI,KAAK,EAAE,CAAC,IAAI;AACjE;AAEO,SAAS,cAAc,GAA+B;AAC3D,QAAM,MAAgB,CAAC;AACvB,MAAI,EAAE,MAAO,KAAI,KAAK,GAAG,QAAQ,EAAE,SAAS,GAAG,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC;AACpE,QAAM,KAAsC,CAAC;AAC7C,MAAI,EAAE,kBAAkB,OAAW,IAAG,UAAU,IAAI,UAAU,EAAE,aAAa;AAC7E,MAAI,EAAE,iBAAiB,OAAW,IAAG,SAAS,IAAI,UAAU,EAAE,YAAY;AAC1E,MAAI,EAAE,eAAe,QAAW;AAC9B,OAAG,QAAQ,IAAI,iBAAiB,EAAE,UAAU;AAC5C,OAAG,YAAY,IAAI;AAAA,EACrB;AACA,MAAI,OAAO,KAAK,EAAE,EAAE,SAAS,EAAG,KAAI,KAAK,GAAG,aAAa,EAAE,CAAC;AAC5D,QAAM,MAA8B,CAAC;AACrC,MAAI,EAAE,iBAAiB,OAAW,KAAI,QAAQ,IAAI,UAAU,EAAE,YAAY;AAC1E,MAAI,EAAE,kBAAkB,OAAW,KAAI,SAAS,IAAI,UAAU,EAAE,aAAa;AAC7E,MAAI,EAAE,sBAAsB,QAAW;AACrC,QAAI,EAAE,qBAAqB,EAAG,KAAI,aAAa,IAAI,UAAU,EAAE,iBAAiB;AAAA,QAC3E,KAAI,WAAW,IAAI,UAAU,CAAC,EAAE,iBAAiB;AAAA,EACxD;AACA,MAAI,OAAO,KAAK,GAAG,EAAE,SAAS,EAAG,KAAI,KAAK,GAAG,SAAS,GAAG,CAAC;AAC1D,MAAI,EAAE,aAAc,KAAI,KAAK,GAAG,YAAY,CAAC;AAC7C,MAAI,EAAE,kBAAmB,KAAI,KAAK,GAAG,aAAa,CAAC;AACnD,MAAI,EAAE,YAAY,EAAE,SAAS,SAAS,GAAG;AACvC,UAAM,OAAO,EAAE,SACZ;AAAA,MAAI,CAAC,MACJ,GAAG,SAAS;AAAA,QACV,SAAS,EAAE,QAAS,QAAQ,EAAE,KAAK,KAAK,SAAU;AAAA,QAClD,SAAS,UAAU,EAAE,KAAK;AAAA,QAC1B,YAAY,EAAE,UAAU,EAAE,WAAW,SAAS,WAAW,EAAE,MAAM,IAAI;AAAA,MACvE,CAAC;AAAA,IACH,EACC,KAAK,EAAE;AACV,QAAI,KAAK,GAAG,UAAU,QAAW,IAAI,CAAC;AAAA,EACxC;AACA,SAAO,IAAI,SAAS,IAAI,GAAG,SAAS,QAAW,IAAI,KAAK,EAAE,CAAC,IAAI;AACjE;AAKA,SAAS,eAAe,KAAa,GAAwB;AAC3D,QAAM,OAAO,CAAC,MAAc,SAAqC;AAC/D,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,MAAM,KAAK,UAAU,WAAW,WAAW,KAAK,UAAU,WAAW,WAAW,KAAK,UAAU,WAAW,WAAW;AAC3H,WAAO,GAAG,OAAO,MAAM,EAAE,SAAS,KAAK,QAAQ,iBAAiB,KAAK,OAAO,GAAG,WAAW,GAAG,WAAW,SAAI,KAAK,KAAK,EAAE,CAAC;AAAA,EAC3H;AACA,SAAO,GAAG,KAAK,QAAW,KAAK,OAAO,EAAE,GAAG,IAAI,KAAK,QAAQ,EAAE,IAAI,IAAI,KAAK,UAAU,EAAE,MAAM,IAAI,KAAK,SAAS,EAAE,KAAK,CAAC;AACzH;AAEA,IAAM,aAAa,CAAC,SAAyB,GAAG,SAAS,EAAE,SAAS,SAAS,WAAW,QAAQ,UAAU,SAAI,IAAI,EAAE,CAAC;AAErH,SAAS,cAAc,MAAiB,OAA+B;AACrE,QAAM,KAAe,CAAC;AACtB,MAAI,MAAM,QAAS,IAAG,KAAK,eAAe,eAAe,MAAM,OAAO,CAAC;AACvE,MAAI,MAAM,QAAS,IAAG,KAAK,WAAW,MAAM,OAAO,CAAC;AACpD,QAAM,OAAO,GAAG,SAAS,IAAI,GAAG,UAAU,QAAW,GAAG,KAAK,EAAE,CAAC,IAAI;AACpE,SAAO,GAAG,gBAAgB,EAAE,UAAU,KAAK,GAAG,cAAc,MAAM,QAAQ,CAAC,CAAC,IAAI,cAAc,MAAM,QAAQ,CAAC,CAAC,IAAI,IAAI;AACxH;AAIO,SAAS,cAAc,OAA2B;AACvD,QAAM,OAAO,MAAM,MAAM,cAAc,CAAC;AACxC,QAAM,YAAsB,CAAC;AAC7B,MAAI,MAAM,gBAAgB,OAAW,WAAU,KAAK,GAAG,yBAAyB,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC;AAC/G,MAAI,MAAM,gBAAgB,OAAW,WAAU,KAAK,GAAG,yBAAyB,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC;AAC/G,MAAI,KAAK,QAAS,WAAU,KAAK,eAAe,gBAAgB,KAAK,OAAO,CAAC;AAC7E,MAAI,KAAK,QAAS,WAAU,KAAK,WAAW,KAAK,OAAO,CAAC;AACzD,QAAM,QAAQ,UAAU,SAAS,IAAI,GAAG,WAAW,QAAW,UAAU,KAAK,EAAE,CAAC,IAAI;AACpF,QAAM,QAAS,OAAO,QAAQ,MAAM,KAAK,EACtC,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,YAAY,EAClC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,EACnC,KAAK,EAAE;AACV,QAAM,OACJ,GAAG,UAAU,EAAE,SAAS,MAAM,KAAK,CAAC,KACnC,MAAM,UAAU,GAAG,aAAa,EAAE,SAAS,MAAM,QAAQ,CAAC,IAAI,MAC/D,cAAc,KAAK,QAAQ,CAAC,CAAC,IAC7B,cAAc,KAAK,QAAQ,CAAC,CAAC,IAC7B,QACA;AACF,SAAO,GAAG,WAAW,EAAE,UAAU,SAAS,aAAa,MAAM,GAAG,GAAG,IAAI;AACzE;",
6
+ "names": []
7
+ }
@@ -276,4 +276,4 @@ export {
276
276
  numAttr,
277
277
  findMainDocumentPart
278
278
  };
279
- //# sourceMappingURL=chunk-6AJJMHKY.js.map
279
+ //# sourceMappingURL=chunk-KJV7GJ3H.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/import/docx/types.ts", "../../node_modules/txml/dist/txml.mjs", "../src/import/docx/xml.ts", "../src/import/docx/contentTypes.ts"],
4
+ "sourcesContent": ["// .docx import \u2014 shared types.\r\n//\r\n// The pipeline is: zip \u2192 XML parts \u2192 IR (intermediate representation) \u2192 model.\r\n// The IR is a faithful-but-minimal decode of document.xml: the parser keeps\r\n// everything it understands (even what the model can't hold yet); mapToModel\r\n// decides what survives and emits an ImportWarning for every lossy decision.\r\n\r\nimport type { Document, FieldDef } from \"@cw/shared\";\r\n\r\nexport type ImportPhase = \"unzip\" | \"styles\" | \"parse\" | \"map\";\r\n\r\nexport type ImportErrorCode = \"NOT_ZIP\" | \"ENCRYPTED\" | \"NO_DOCUMENT_PART\" | \"MALFORMED_XML\";\r\n\r\nexport class ImportError extends Error {\r\n readonly code: ImportErrorCode;\r\n constructor(code: ImportErrorCode, message: string) {\r\n super(message);\r\n this.name = \"ImportError\";\r\n this.code = code;\r\n }\r\n}\r\n\r\nexport interface ImportWarning {\r\n code: string;\r\n message: string;\r\n}\r\n\r\n/** Deduplicating warning collector \u2014 a 70-page doc with 400 tabs should say\r\n * \"tabs converted\" once, not 400 times. */\r\nexport class WarningSink {\r\n readonly list: ImportWarning[] = [];\r\n private readonly seen = new Set<string>();\r\n add(code: string, message: string): void {\r\n if (this.seen.has(code)) return;\r\n this.seen.add(code);\r\n this.list.push({ code, message });\r\n }\r\n}\r\n\r\nexport interface ImportResult {\r\n doc: Document;\r\n /** Every lossy mapping decision \u2014 surfaced, not swallowed. */\r\n warnings: ImportWarning[];\r\n /** blob: URLs created for embedded media; caller revokes when the doc is discarded. */\r\n mediaUrls: string[];\r\n /** Raw embedded image bytes, populated only when runImport is called with\r\n * { collectMediaBytes: true } \u2014 the Node/backend path, which content-addresses\r\n * them into its own media store instead of minting blob: URLs. Each `src`\r\n * matches the ImageBlock.src the mapper assigned, so the caller can swap in a\r\n * mediaId. Empty (and mediaUrls carry blob: URLs) in the browser path. */\r\n media: ImportMedia[];\r\n}\r\n\r\n/** A collected embedded image (Node/backend path). */\r\nexport interface ImportMedia {\r\n /** The synthetic src the mapper put on the ImageBlock (e.g. \"cw-media:0\"). */\r\n src: string;\r\n bytes: Uint8Array;\r\n mime: string;\r\n}\r\n\r\n/** Sink the media store routes raw image bytes to instead of creating a blob:\r\n * URL. Returns the src string to stamp on the ImageBlock. */\r\nexport interface MediaCollector {\r\n add(bytes: Uint8Array, mime: string): string;\r\n}\r\n\r\nexport interface RunImportOpts {\r\n onProgress?: (phase: ImportPhase, pct: number) => void;\r\n /** Collect raw image bytes (and skip blob: URL creation) for Node/backend\r\n * callers that content-address media themselves. */\r\n collectMediaBytes?: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Worker protocol\r\n\r\nexport interface ToWorker {\r\n id: number;\r\n buf: ArrayBuffer; // transferred, not cloned\r\n}\r\n\r\nexport type FromWorker =\r\n | { id: number; type: \"progress\"; phase: ImportPhase; pct: number }\r\n | { id: number; type: \"done\"; result: ImportResult }\r\n | { id: number; type: \"error\"; code: ImportErrorCode; message: string };\r\n\r\n// ---------------------------------------------------------------------------\r\n// IR \u2014 what documentParser produces. Units stay as OOXML units (twips,\r\n// half-points); conversion to px happens in mapToModel via units.ts.\r\n\r\n/** Direct run formatting decoded from w:rPr. Absent field = \"inherit\" \u2014\r\n * resolved through the style cascade in milestone 2, from defaults today. */\r\nexport interface IRRunProps {\r\n /** w:rStyle reference \u2014 recorded now, resolved when StyleResolver lands. */\r\n styleId?: string;\r\n bold?: boolean;\r\n italic?: boolean;\r\n underline?: boolean;\r\n strikethrough?: boolean;\r\n /** Raw w:color value: hex without '#' (\"FF0000\") or \"auto\". */\r\n color?: string;\r\n /** w:sz \u2014 half-points. */\r\n sizeHalfPoints?: number;\r\n /** w:rFonts w:ascii. */\r\n fontAscii?: string;\r\n /** w:rFonts w:asciiTheme \u2014 theme font slot (\"minorHAnsi\", \"majorHAnsi\", \u2026). */\r\n fontThemeAscii?: string;\r\n /** w:color w:themeColor \u2014 theme color slot (\"accent1\", \"text1\", \u2026). */\r\n colorTheme?: string;\r\n /** w:highlight w:val \u2014 named highlight color (\"yellow\", \"green\", \u2026). */\r\n highlight?: string;\r\n /** w:vertAlign w:val \u2014 \"superscript\" | \"subscript\" | \"baseline\". */\r\n vertAlign?: string;\r\n /** w:vanish \u2014 hidden text (Word shows it only with \u00B6 marks on). */\r\n vanish?: boolean;\r\n /** Hyperlink membership (set on runs inside a w:hyperlink). Resolved to a URL\r\n * in mapToModel: relId via the part's rels (external target), or anchor for\r\n * an in-document bookmark (kept as \"#name\"). */\r\n linkRelId?: string;\r\n linkAnchor?: string;\r\n /** w:footnoteReference w:id \u2014 this run is a footnote marker. mapToModel sets\r\n * the run text to the sequential note number and CharStyle.footnoteRef. */\r\n footnoteId?: string;\r\n}\r\n\r\n/** w:sdtPr \u2014 content-control properties, decoded faithfully (mapToModel turns\r\n * these into the model's SdtProps; runs inside the control carry the sdtId). */\r\nexport interface IRSdtProps {\r\n type: \"richText\" | \"plainText\" | \"checkbox\" | \"dropDown\" | \"comboBox\" | \"date\";\r\n alias?: string;\r\n tag?: string;\r\n /** w:showingPlcHdr \u2014 the content is currently the gray placeholder. */\r\n placeholder?: boolean;\r\n listItems?: { display: string; value: string }[];\r\n dateFormat?: string;\r\n checked?: boolean;\r\n lockContent?: boolean;\r\n lockControl?: boolean;\r\n}\r\n\r\nexport type IRInline =\r\n | { kind: \"run\"; text: string; props: IRRunProps; sdtId?: string; fieldId?: string }\r\n /** w:br / w:cr \u2014 soft line break (model has none; mapToModel splits the\r\n * paragraph). page=true for w:br w:type=\"page\" (\u2192 pageBreakBefore on the\r\n * follower); column=true for w:br w:type=\"column\" (\u2192 columnBreakBefore). */\r\n | { kind: \"break\"; page?: boolean; column?: boolean }\r\n /** w:drawing / w:pict \u2014 becomes a block-level ImageBlock (model has no inline images). */\r\n | {\r\n kind: \"image\";\r\n relId: string;\r\n widthEmu?: number;\r\n heightEmu?: number;\r\n anchored: boolean;\r\n /** For wp:anchor: square = text wraps around (maps to ImageBlock.wrap);\r\n * block = wrap mode the model can't express (none/topAndBottom). */\r\n anchorWrap?: \"square\" | \"block\";\r\n /** wp:positionH/wp:align when present. */\r\n anchorAlign?: \"left\" | \"right\" | \"center\";\r\n /** Set for wrapNone anchors (image sits behind/in-front of text, not in\r\n * the flow). Maps to ImageBlock.anchor \u2014 positioned absolutely, no flow\r\n * height, no text reflow. */\r\n anchorFloat?: {\r\n behind: boolean;\r\n offsetXEmu: number;\r\n offsetYEmu: number;\r\n relFromH: \"page\" | \"margin\" | \"column\" | \"leftMargin\" | \"rightMargin\" | \"character\";\r\n relFromV: \"page\" | \"margin\" | \"paragraph\" | \"line\" | \"topMargin\" | \"bottomMargin\";\r\n decorative?: boolean;\r\n /** wp:anchor @relativeHeight \u2014 stacking order within the layer. */\r\n z?: number;\r\n };\r\n };\r\n\r\nexport interface IRParaProps {\r\n /** w:pStyle reference \u2014 recorded now, resolved when StyleResolver lands. */\r\n styleId?: string;\r\n align?: \"left\" | \"center\" | \"right\" | \"justify\";\r\n spaceBeforeTwips?: number;\r\n spaceAfterTwips?: number;\r\n /** Multiplier \u2014 only set when lineRule is \"auto\" (or absent). */\r\n lineHeight?: number;\r\n indentLeftTwips?: number;\r\n /** w:ind/@w:right|@w:end \u2014 right-edge indent. */\r\n indentRightTwips?: number;\r\n /** Negative = hanging indent (w:hanging). */\r\n indentFirstLineTwips?: number;\r\n /** w:keepNext \u2014 maps onto ParaStyle.keepWithNext. */\r\n keepWithNext?: boolean;\r\n /** w:keepLines \u2014 maps onto ParaStyle.keepLinesTogether. */\r\n keepLinesTogether?: boolean;\r\n /** w:pPr/w:tabs \u2014 raw tab stops (pos in twips; val/leader raw OOXML names). */\r\n tabStops?: { posTwips: number; val?: string; leader?: string }[];\r\n /** w:pageBreakBefore \u2014 this paragraph starts a new page. */\r\n pageBreakBefore?: boolean;\r\n /** w:outlineLvl \u2014 0-8. Heading styles set it; drives TOC `\\u`/heading detection. */\r\n outlineLevel?: number;\r\n /** w:numPr \u2014 list membership. numId \"0\" / null = explicitly NOT a list\r\n * (overrides an inherited list from the paragraph style). */\r\n list?: { numId: string; level: number } | null;\r\n /** w:pPr/w:sectPr \u2014 this paragraph ENDS a section. \"page\" (nextPage/odd/even)\r\n * implies the following content starts a new page; \"continuous\" doesn't. */\r\n sectionBreak?: \"page\" | \"continuous\";\r\n /** The ending section's page size (twips), when its w:sectPr declares pgSz.\r\n * A \"page\" break only forces a new page when this differs from the document's\r\n * page size \u2014 geometry-preserving breaks (footer/header switches, which these\r\n * generated reports emit by the dozen) flow instead of leaving half-empty\r\n * pages, matching Word. */\r\n sectionPgSize?: { w: number; h: number };\r\n /** The ending section's margins (twips) \u2014 for a SectionPatch when geometry differs. */\r\n sectionMarginTwips?: { top: number; right: number; bottom: number; left: number };\r\n /** The ending section's newspaper columns (count > 1). */\r\n sectionColumns?: { count: number; spaceTwips?: number; sep?: boolean; cols?: { wTwips: number; spaceTwips: number }[] };\r\n /** The ending section's w:pgNumType w:start (page-number restart). */\r\n sectionPageNumberStart?: number;\r\n /** The ending section's w:pgMar header/footer band distances (twips). */\r\n sectionHeaderDistTwips?: number;\r\n sectionFooterDistTwips?: number;\r\n /** The ending section's w:pgBorders (raw, mapped downstream). */\r\n sectionPgBorders?: IRPageBorders;\r\n /** The ending section's sectPr declares its own header/footer references \u2014\r\n * when such a section is flowed (not page-broken) its bands aren't applied. */\r\n sectionHasBands?: boolean;\r\n /** w:pPr/w:rPr \u2014 the paragraph MARK's run formatting. Word styles empty\r\n * paragraphs (and the \u00B6 itself) with this; we use it for empty-run style. */\r\n markRunProps?: IRRunProps;\r\n}\r\n\r\n/** A w:bookmarkStart / w:bookmarkEnd seen while walking a paragraph: its w:id\r\n * (pairs start\u2194end, possibly across paragraphs), w:name (start only), and the\r\n * UTF-16 offset within the paragraph text where it sits. */\r\nexport interface BookmarkMarker {\r\n id: string;\r\n name?: string;\r\n kind: \"start\" | \"end\";\r\n offset: number;\r\n}\r\n\r\nexport interface IRParagraph {\r\n kind: \"paragraph\";\r\n props: IRParaProps;\r\n inlines: IRInline[];\r\n /** w:bookmarkStart names anchored in this paragraph (TOC/cross-ref heuristics). */\r\n bookmarks?: string[];\r\n /** Start/end markers with offsets \u2014 resolved to model ranges in mapToModel. */\r\n bookmarkMarkers?: BookmarkMarker[];\r\n /** Custom-field result membership \u2014 mapped onto Block.fieldId. */\r\n fieldId?: string;\r\n /** Set when this paragraph holds a `TOC` field \u2014 its verbatim instrText. Lets a\r\n * headless render anchor a freshly-built TOC at this exact block (mapToModel\r\n * records the resulting block id), instead of fragile ordinal counting. */\r\n tocField?: string;\r\n}\r\n\r\n/** One raw border edge (w:top/w:left/\u2026 inside w:tblBorders or w:tcBorders).\r\n * Kept raw (OOXML units) until mapToModel collapses the cascade to px. */\r\nexport interface IRRawBorder {\r\n /** w:val \u2014 \"single\", \"double\", \"dashed\", \"nil\"/\"none\", \u2026 */\r\n val: string;\r\n /** w:sz \u2014 eighths of a point. */\r\n sizeEighthPt?: number;\r\n /** w:color \u2014 hex without '#', or \"auto\". */\r\n color?: string;\r\n}\r\n\r\nexport interface IRBorders {\r\n top?: IRRawBorder;\r\n left?: IRRawBorder;\r\n bottom?: IRRawBorder;\r\n right?: IRRawBorder;\r\n /** Interior horizontal/vertical edges (w:tblBorders only). */\r\n insideH?: IRRawBorder;\r\n insideV?: IRRawBorder;\r\n}\r\n\r\n/** Cell margins in twips, per side. Any side may be absent (inherits the next\r\n * level of the cascade: cell \u2192 table \u2192 Word default). w:tcMar / w:tblCellMar. */\r\nexport interface IRCellMargin {\r\n top?: number;\r\n right?: number;\r\n bottom?: number;\r\n left?: number;\r\n}\r\n\r\nexport interface IRTableCell {\r\n /** Full block content: paragraphs, images (inside paragraphs), nested tables. */\r\n blocks: IRBlock[];\r\n /** w:gridSpan \u2014 columns this cell covers (1 = normal). Maps to TableCell.colSpan. */\r\n gridSpan: number;\r\n /** w:vMerge continuation \u2014 this cell is swallowed by the cell above. */\r\n vMergeContinue: boolean;\r\n /** w:tcPr/w:tcBorders. */\r\n borders?: IRBorders;\r\n /** A w:tcBorders element is present \u2014 even if empty (all edges nil/absent), the\r\n * author explicitly chose this cell's borders, so it must NOT fall back to the\r\n * renderer's default grid. */\r\n bordersSpecified?: boolean;\r\n /** w:tcPr/w:shd \u2192 CSS fill. */\r\n shd?: string;\r\n /** w:tcPr/w:tcMar \u2014 per-side inner padding override (twips). */\r\n marginTwips?: IRCellMargin;\r\n}\r\n\r\nexport interface IRTableRow {\r\n cells: IRTableCell[];\r\n}\r\n\r\nexport interface IRTable {\r\n kind: \"table\";\r\n rows: IRTableRow[];\r\n /** w:tblGrid/w:gridCol widths \u2014 become TableBlock.colFractions. */\r\n colWidthsTwips?: number[];\r\n /** w:tblPr/w:tblStyle \u2014 table style id (its borders/shd are the cascade base). */\r\n styleId?: string;\r\n /** w:tblPr/w:tblLook \u2014 which conditional bands of the table style are active. */\r\n look?: { firstRow?: boolean; lastRow?: boolean; firstCol?: boolean; lastCol?: boolean; bandRows?: boolean; bandCols?: boolean };\r\n /** w:tblPr/w:tblBorders. */\r\n borders?: IRBorders;\r\n /** A w:tblBorders element is present \u2014 even if empty, the table explicitly\r\n * declares its borders (e.g. \"no borders\"), suppressing the default grid. */\r\n bordersSpecified?: boolean;\r\n /** w:tblPr/w:shd \u2192 CSS fill applied to every cell unless overridden. */\r\n shd?: string;\r\n /** w:tblPr/w:tblCellMar \u2014 table-wide cell-margin default (twips), the base each\r\n * cell's own w:tcMar overrides per side. */\r\n cellMarginTwips?: IRCellMargin;\r\n /** Custom-field result membership \u2014 mapped onto Block.fieldId. */\r\n fieldId?: string;\r\n}\r\n\r\nexport type IRBlock = IRParagraph | IRTable;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Numbering (numbering.xml) \u2014 raw decode; mapToModel converts to model lists.\r\n\r\nexport interface IRListLevel {\r\n /** Raw w:numFmt w:val (\"decimal\", \"bullet\", \"lowerRoman\", \"none\", \u2026). */\r\n format: string;\r\n /** Raw w:lvlText w:val \u2014 \"%1.\" pattern, or the bullet glyph for bullets. */\r\n lvlText: string;\r\n start: number;\r\n indentLeftTwips?: number;\r\n hangingTwips?: number;\r\n /** w:lvl/w:rPr \u2014 marker glyph styling (esp. the bullet font). */\r\n markerRunProps?: IRRunProps;\r\n}\r\n\r\nexport interface IRListDefinition {\r\n /** numId (the id space paragraphs reference via w:numPr/w:numId). */\r\n id: string;\r\n levels: IRListLevel[];\r\n}\r\n\r\nexport interface IRSection {\r\n pageWidthTwips?: number;\r\n pageHeightTwips?: number;\r\n marginTwips?: { top: number; right: number; bottom: number; left: number };\r\n /** w:pgMar/@w:header and @w:footer \u2014 band distances from the page edges (twips). */\r\n headerDistTwips?: number;\r\n footerDistTwips?: number;\r\n /** w:headerReference / w:footerReference by type \u2014 r:id into the document\r\n * part's rels; the referenced parts are parsed separately. Variants are\r\n * gated downstream (first by w:titlePg, even by settings evenAndOdd). */\r\n headerRefs?: BandRefs;\r\n footerRefs?: BandRefs;\r\n /** w:titlePg \u2014 this section has a distinct first-page header/footer. */\r\n titlePg?: boolean;\r\n /** w:cols \u2014 newspaper columns (only count > 1 is meaningful). `sep` = a\r\n * separator line; `cols` = explicit per-column widths (w:equalWidth=\"0\"). */\r\n columns?: {\r\n count: number;\r\n spaceTwips?: number;\r\n sep?: boolean;\r\n cols?: { wTwips: number; spaceTwips: number }[];\r\n };\r\n /** w:pgNumType w:start \u2014 restart page numbering at this value. */\r\n pageNumberStart?: number;\r\n /** w:pgBorders \u2014 page border box (raw twips/eighth-points, mapped downstream). */\r\n pageBorders?: IRPageBorders;\r\n}\r\n\r\n/** w:pgBorders edge as parsed (w:sz eighth-points, w:space points). */\r\nexport interface IRPageBorderEdge {\r\n style: string;\r\n sz?: number;\r\n space?: number;\r\n color?: string;\r\n}\r\nexport interface IRPageBorders {\r\n top?: IRPageBorderEdge;\r\n right?: IRPageBorderEdge;\r\n bottom?: IRPageBorderEdge;\r\n left?: IRPageBorderEdge;\r\n offsetFrom?: \"page\" | \"text\";\r\n}\r\n\r\n/** Header/footer relationship ids by Word variant. */\r\nexport interface BandRefs {\r\n default?: string;\r\n first?: string;\r\n even?: string;\r\n}\r\n\r\nexport interface IRDocument {\r\n blocks: IRBlock[];\r\n section: IRSection | null;\r\n /** Content controls found in the body (header/footer parsing extends it). */\r\n sdts: Record<string, IRSdtProps>;\r\n /** Custom (non-built-in) complex fields captured in the body, keyed by id. */\r\n fields?: Record<string, FieldDef>;\r\n /** w:document/w:background @w:color \u2014 document-global page fill (\"RRGGBB\"). */\r\n pageColorHex?: string;\r\n}\r\n", "function t(t){return t.replace(/&(#x[0-9a-fA-F]+|#\\d+|amp|lt|gt|quot|apos);/g,function(t,r){if(\"amp\"===r)return\"&\";if(\"lt\"===r)return\"<\";if(\"gt\"===r)return\">\";if(\"quot\"===r)return'\"';if(\"apos\"===r)return\"'\";if(\"#\"===r[0]){var e=\"x\"===r[1]||\"X\"===r[1],a=parseInt(r.slice(e?2:1),e?16:10);if(!Number.isNaN(a)&&a>=0&&a<=1114111)try{return String.fromCodePoint(a)}catch(r){return t}}return t})}function r(t){return t.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}function e(t){return t.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&apos;\")}function a(r,e){var a=(e=e||{}).pos||0,i=!!e.keepComments,c=!!e.keepWhitespace,f=!!e.decodeEntities,s=\"<\".charCodeAt(0),u=\">\".charCodeAt(0),h=\"-\".charCodeAt(0),l=\"/\".charCodeAt(0),d=\"!\".charCodeAt(0),g=\"?\".charCodeAt(0),A=\"'\".charCodeAt(0),p='\"'.charCodeAt(0),C=\"=\".charCodeAt(0),v=\"[\".charCodeAt(0),y=\"]\".charCodeAt(0),m=\"?\".charCodeAt(0);function b(t){for(var e=[];r[a];)if(r.charCodeAt(a)==s){if(r.charCodeAt(a+1)===l){var n=a+2;if(a=r.indexOf(\">\",a),-1==r.substring(n,a).indexOf(t)){var o=r.substring(0,a).split(\"\\n\");throw new Error(\"Unexpected close tag\\nLine: \"+(o.length-1)+\"\\nColumn: \"+(o[o.length-1].length+1)+\"\\nChar: \"+r[a])}return a+1&&(a+=1),e}if(r.charCodeAt(a+1)===d){if(r.charCodeAt(a+2)==h){const t=a;for(;-1!==a&&(r.charCodeAt(a)!==u||r.charCodeAt(a-1)!=h||r.charCodeAt(a-2)!=h||-1==a);)a=r.indexOf(\">\",a+1);-1===a&&(a=r.length),i&&e.push(r.substring(t,a+1))}else{if(r.charCodeAt(a+2)===v&&r.charCodeAt(a+8)===v&&\"cdata\"===r.substr(a+3,5).toLowerCase()){var f=r.indexOf(\"]]>\",a);-1==f?(e.push(r.substr(a+9)),a=r.length):(e.push(r.substring(a+9,f)),a=f+3);continue}{const t=a+1;a+=2;for(var g=!1;(r.charCodeAt(a)!==u||!0===g)&&r[a];)r.charCodeAt(a)===v?g=!0:!0===g&&r.charCodeAt(a)===y&&(g=!1),a++;e.push(r.substring(t,a))}}a++;continue}var A=k();e.push(A)}else{var p=N();if(c)p.length>0&&e.push(p);else{var C=p.trim();C.length>0&&e.push(C)}a++}return e}function N(){var e=a;-2===(a=r.indexOf(\"<\",a)-1)&&(a=r.length);var n=r.slice(e,a+1);return f?t(n):n}var x=\"\\r\\n\\t>/= \";function O(){for(var t=a;-1===x.indexOf(r[a])&&r[a];)a++;return r.slice(t,a)}var j,E=e.selfClosingTags||e.noChildNodes||[\"img\",\"br\",\"input\",\"meta\",\"link\",\"hr\"];function k(){a++;const e=O(),n=\"?\"===e[0],i=a,o={};let c=[];for(;r[a]&&r.charCodeAt(a)!==u&&(!n||r.charCodeAt(a)!==g||r.charCodeAt(a+1)!==u);){var s=r.charCodeAt(a);if(s>64&&s<91||s>96&&s<123){for(var h=O(),d=null;32===r.charCodeAt(a)||9===r.charCodeAt(a)||10===r.charCodeAt(a)||13===r.charCodeAt(a);)a++;if(r.charCodeAt(a)===C){for(a++;32===r.charCodeAt(a)||9===r.charCodeAt(a)||10===r.charCodeAt(a)||13===r.charCodeAt(a);)a++;var v=r.charCodeAt(a);if(v===A||v===p){if(d=w(),-1===a)return{tagName:e,attributes:o,children:c}}else if(v&&v!==u){for(var y=a;r[a]&&-1===x.indexOf(r[a]);)a++;d=r.slice(y,a),f&&(d=t(d))}}o[h]=d}else a++}if(n){var N=r.slice(i,a).trim();return N.length>0&&0===Object.keys(o).length&&(c=[N]),r.charCodeAt(a)===g&&r.charCodeAt(a+1)===u?a+=2:r.charCodeAt(a)===u&&(a+=1),{tagName:e,attributes:o,children:c}}if(r.charCodeAt(a-1)!==l&&r.charCodeAt(a-1)!==m)if(\"script\"==e){var j=a+1;a=r.indexOf(\"<\\/script>\",a),c=[r.slice(j,a)],a+=9}else if(\"style\"==e){j=a+1;a=r.indexOf(\"</style>\",a),c=[r.slice(j,a)],a+=8}else-1===E.indexOf(e)?(a++,c=b(e)):a++;else a++;return{tagName:e,attributes:o,children:c}}function w(){var e=r[a],n=a+1;a=r.indexOf(e,n);var i=r.slice(n,a);return f?t(i):i}function V(){if(!e||!e.attrName||!e.attrValue)return-1;var t=new RegExp(\"\\\\s\"+e.attrName+\"\\\\s*=['\\\"]\"+e.attrValue+\"['\\\"]\").exec(r);return t?t.index:-1}if(void 0!==e.attrValue)for(e.attrName=e.attrName||\"id\",j=[];-1!==(a=V());)-1!==(a=r.lastIndexOf(\"<\",a))&&j.push(k()),r=r.substr(a),a=0;else j=e.parseNode?k():b(\"\");if(e.filter&&Array.isArray(j)&&(j=o(j,e.filter)),e.simplify){return n(Array.isArray(j)?j:[j])}return e.setPos&&\"object\"==typeof j&&!Array.isArray(j)&&(j.pos=a),j}function n(t){if(\"string\"==typeof t)return n(a(t));if(!Array.isArray(t))throw new TypeError(\"simplify() expects parsed node array or XML string\");var r={};if(!t.length)return\"\";if(1===t.length&&\"string\"==typeof t[0])return t[0];for(var e in t.forEach(function(t){if(\"object\"==typeof t){r[t.tagName]||(r[t.tagName]=[]);var e=n(t.children);r[t.tagName].push(e),Object.keys(t.attributes).length&&\"object\"==typeof e&&!Array.isArray(e)&&(e._attributes=t.attributes)}}),r)1==r[e].length&&(r[e]=r[e][0]);return r}function i(t,r={}){var e={};return t.length?1===t.length&&\"string\"==typeof t[0]?Object.keys(r).length?{_attributes:r,value:t[0]}:t[0]:(t.forEach(function(t){if(\"object\"==typeof t){e[t.tagName]||(e[t.tagName]=[]);var r=i(t.children||[],t.attributes);e[t.tagName].push(r),Object.keys(t.attributes).length&&\"object\"==typeof r&&!Array.isArray(r)&&(r._attributes=t.attributes)}}),e):e}function o(t,r,e=0,a=\"\"){var n=[];return t.forEach(function(t,i){if(\"object\"==typeof t&&r(t,i,e,a)&&n.push(t),\"object\"==typeof t&&t.children){var c=o(t.children,r,e+1,(a?a+\".\":\"\")+i+\".\"+t.tagName);n=n.concat(c)}}),n}function c(t,a){if(!t)return\"\";var n=!!(a=a||{}).encodeEntities,i=\"\";function o(t){if(t)for(var e=0;e<t.length;e++){var a=t[e];if(\"string\"==typeof a){var o=a.trim();i+=n?r(o):o}else a&&c(a)}}function c(t){if(t){for(var r in i+=\"<\"+t.tagName,t.attributes){var a=t.attributes[r];null===a?i+=\" \"+r:n?i+=\" \"+r+'=\"'+e(a.trim())+'\"':-1===a.indexOf('\"')?i+=\" \"+r+'=\"'+a.trim()+'\"':i+=\" \"+r+\"='\"+a.trim()+\"'\"}if(\"?\"!==t.tagName[0])i+=\">\",o(t.children),i+=\"</\"+t.tagName+\">\";else{if(t.children&&t.children.length){var c=t.children.filter(function(t){return\"string\"==typeof t}).join(\" \").trim();c.length>0&&(i+=\" \"+c)}i+=\"?>\"}}}return o(Array.isArray(t)?t:[t]),i}function f(t){if(Array.isArray(t)){var r=\"\";return t.forEach(function(t){r=(r+=\" \"+f(t)).trim()}),r}return\"object\"==typeof t&&null!==t?f(t.children):\" \"+t}function s(t,r,e){var i=a(t,{attrValue:r});return e?n(i):i[0]}function u(t,r,e){const i=a(t,{attrName:\"class\",attrValue:\"[a-zA-Z0-9- ]*\"+r+\"[a-zA-Z0-9- ]*\"});return e?n(i):i}function h(t){return\"string\"==typeof t}function l(t){return\"object\"==typeof t&&null!==t&&\"tagName\"in t}export{o as filter,s as getElementById,u as getElementsByClassName,l as isElementNode,h as isTextNode,a as parse,n as simplify,i as simplifyLostLess,c as stringify,f as toContentString};\n", "// txml helpers. DOMParser is window-only (not available in workers), so we use\n// txml \u2014 a tiny pure-JS parser that returns an ORDERED node tree. Order matters:\n// in document.xml, run order IS the text.\n//\n// OOXML namespace handling: prefixes (w:, a:, wp:) are technically remappable,\n// but Word always emits the conventional ones, so we match qualified names\n// directly \u2014 the same pragmatic choice docx-preview and mammoth make.\n\nimport { isElementNode, parse, type TNode } from \"txml/txml\";\nimport { ImportError } from \"./types\";\n\nexport type XmlNode = TNode;\n\n/** Parse an XML part. keepWhitespace matters: a run whose text is a single\n * space (`<w:t xml:space=\"preserve\"> </w:t>`) must not be dropped. */\nexport function parseXml(text: string, partName: string): XmlNode[] {\n try {\n return parse(text, { keepWhitespace: true, decodeEntities: true }).filter(isElementNode);\n } catch (e) {\n throw new ImportError(\n \"MALFORMED_XML\",\n `Failed to parse ${partName}: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\n/** Top-level element by qualified name (skips the XML declaration / PIs). */\nexport function rootEl(nodes: XmlNode[], tagName: string): XmlNode | undefined {\n return nodes.find((n) => n.tagName === tagName);\n}\n\n/** Element children only (text nodes filtered out). */\nexport function children(node: XmlNode): XmlNode[] {\n return node.children.filter(isElementNode);\n}\n\n/** First child element with the given qualified name. */\nexport function el(node: XmlNode, tagName: string): XmlNode | undefined {\n return node.children.find((c): c is XmlNode => isElementNode(c) && c.tagName === tagName);\n}\n\n/** All child elements with the given qualified name. */\nexport function els(node: XmlNode, tagName: string): XmlNode[] {\n return node.children.filter((c): c is XmlNode => isElementNode(c) && c.tagName === tagName);\n}\n\n/** Attribute value by qualified name; valueless attributes read as \"\". */\nexport function attr(node: XmlNode, name: string): string | undefined {\n const v = node.attributes[name];\n return v === undefined ? undefined : (v ?? \"\");\n}\n\n/** Depth-first search for a descendant element. Used where the exact nesting\n * is deep and producer-dependent (e.g. a:blip inside w:drawing). */\nexport function findDeep(node: XmlNode, tagName: string): XmlNode | undefined {\n for (const c of node.children) {\n if (!isElementNode(c)) continue;\n if (c.tagName === tagName) return c;\n const found = findDeep(c, tagName);\n if (found) return found;\n }\n return undefined;\n}\n\n/** Concatenated text content (direct string children \u2014 w:t never nests). */\nexport function textOf(node: XmlNode): string {\n let out = \"\";\n for (const c of node.children) if (typeof c === \"string\") out += c;\n return out;\n}\n\n/** Shorthand for the ubiquitous `<w:x w:val=\"\u2026\"/>` child-element pattern. */\nexport function val(node: XmlNode, tagName: string): string | undefined {\n const child = el(node, tagName);\n return child ? attr(child, \"w:val\") : undefined;\n}\n\n/** OOXML on/off property: `<w:b/>` is on; w:val of 0/false/none/off is off. */\nexport function onOff(node: XmlNode | undefined): boolean | undefined {\n if (!node) return undefined;\n const v = attr(node, \"w:val\");\n if (v === undefined) return true;\n return ![\"0\", \"false\", \"none\", \"off\"].includes(v);\n}\n\n/** Numeric attribute (OOXML writes plain decimal integers). */\nexport function numAttr(node: XmlNode | undefined, name: string): number | undefined {\n if (!node) return undefined;\n const v = attr(node, name);\n if (v === undefined) return undefined;\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n}\n", "// [Content_Types].xml \u2192 locate the main document part. Usually\n// \"word/document.xml\", but the spec allows any part name (and macro-enabled\n// .docm uses a different content type), so don't hardcode.\n\nimport { attr, children, parseXml, rootEl } from \"./xml\";\n\nconst MAIN_CONTENT_TYPES = new Set([\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\",\n \"application/vnd.ms-word.document.macroEnabled.main+xml\",\n]);\n\nexport function findMainDocumentPart(contentTypesXml: string): string | undefined {\n const types = rootEl(parseXml(contentTypesXml, \"[Content_Types].xml\"), \"Types\");\n if (!types) return undefined;\n for (const child of children(types)) {\n if (child.tagName !== \"Override\") continue;\n const ct = attr(child, \"ContentType\");\n const partName = attr(child, \"PartName\");\n if (ct && partName && MAIN_CONTENT_TYPES.has(ct)) {\n return partName.replace(/^\\//, \"\"); // OPC part names are /-rooted; zip entries aren't\n }\n }\n return undefined;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["t", "a", "r", "a", "l", "t", "e", "n", "o", "f", "g", "A", "p", "C", "i", "c", "s", "h", "d", "v", "y", "N", "j", "t", "a", "n", "t", "n"]
7
+ }
@@ -302,6 +302,11 @@ function tocEntryStyle(opts, level, contentWidthPx, inherit) {
302
302
  return { char, para };
303
303
  }
304
304
 
305
+ // ../shared/src/model/stylesheet.ts
306
+ function styleType(s) {
307
+ return s.type ?? "paragraph";
308
+ }
309
+
305
310
  // ../shared/src/fields.ts
306
311
  var BUILTIN_FIELDS = /* @__PURE__ */ new Set([
307
312
  "TOC",
@@ -429,6 +434,7 @@ function parseFieldSpec(parsed, baseStyle) {
429
434
  export {
430
435
  isHiddenParagraph,
431
436
  textOfRuns,
437
+ styleType,
432
438
  formatListNumber,
433
439
  markerText,
434
440
  normalizeRuns,
@@ -442,4 +448,4 @@ export {
442
448
  isCustomFieldInstruction,
443
449
  parseFieldSpec
444
450
  };
445
- //# sourceMappingURL=chunk-CDN6WU2P.js.map
451
+ //# sourceMappingURL=chunk-PIMVQULL.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../shared/src/model/text.ts", "../../shared/src/model/lists.ts", "../../shared/src/model/ops.ts", "../../shared/src/ids.ts", "../../shared/src/toc.ts", "../../shared/src/model/stylesheet.ts", "../../shared/src/fields.ts", "../../shared/src/fieldEval.ts"],
4
+ "sourcesContent": ["// Shared text helpers over the model: paragraph text access, grapheme/word\r\n// boundaries (Intl.Segmenter \u2014 the same segmentation pretext breaks lines on),\r\n// and style-at-position for Word-style inheritance when typing.\r\n\r\nimport type { BandContainer, Block, CharStyle, Document, Paragraph, Run } from \"./document\";\r\nimport { BAND_CONTAINERS } from \"./document\";\r\n\r\nexport const graphemes = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\r\nexport const words = new Intl.Segmenter(undefined, { granularity: \"word\" });\r\n\r\n/** A paragraph whose every non-empty run is hidden (w:vanish): it has text but\r\n * none is visible. Such a paragraph is never laid out, caret-reachable, or\r\n * deletable \u2014 it's preserved metadata. A truly empty paragraph (no text at all)\r\n * is NOT hidden \u2014 it's a normal blank line. */\r\nexport const isHiddenParagraph = (p: Paragraph): boolean =>\r\n p.runs.some((r) => r.text.length > 0) && p.runs.every((r) => r.text.length === 0 || r.style.hidden === true);\r\n\r\nconst paragraphsInBlocks = (blocks: Block[], includeCells: boolean): Paragraph[] => {\r\n const out: Paragraph[] = [];\r\n for (const b of blocks) {\r\n if (b.kind === \"paragraph\") out.push(b);\r\n else if (b.kind === \"table\" && includeCells) {\r\n for (const row of b.rows) {\r\n for (const cell of row.cells) out.push(...paragraphsInBlocks(cell.blocks, includeCells));\r\n }\r\n }\r\n }\r\n return out;\r\n};\r\n\r\n/** The two band STORIES from the UI's point of view (story mode is entered per\r\n * header/footer area; which variant CONTAINER that resolves to depends on the\r\n * page \u2014 see Page.headerSource/footerSource). */\r\nexport type BandName = \"header\" | \"footer\";\r\n\r\n/** Editable paragraphs of a margin band container, document order \u2014 INCLUDING\r\n * band-table cells (imported footers are routinely tables holding text next\r\n * to a page-number paragraph). */\r\nexport const bandParagraphs = (doc: Document, band: BandContainer): Paragraph[] =>\r\n paragraphsInBlocks(doc.section[band] ?? [], true);\r\n\r\n/** Body paragraphs in document order, INCLUDING table-cell paragraphs (one\r\n * level deep). Excludes margin bands and footnote notes \u2014 the body story only.\r\n * Heading/TOC scans use this so headings inside table cells are found too. */\r\nexport const bodyParagraphs = (doc: Document): Paragraph[] => paragraphsInBlocks(doc.blocks, true);\r\n\r\n/** All editable paragraphs in document order: body (including table cells),\r\n * then every band story (header/footer + first/even variants). This is the\r\n * index space commands use. */\r\nexport const paragraphsOf = (doc: Document): Paragraph[] => [\r\n ...paragraphsInBlocks(doc.blocks, true),\r\n ...BAND_CONTAINERS.flatMap((band) => bandParagraphs(doc, band)),\r\n ...Object.values(doc.footnotes ?? {}).flat(),\r\n];\r\n\r\nexport const blockById = (doc: Document, blockId: string): Paragraph | undefined =>\r\n paragraphsOf(doc).find((b) => b.id === blockId);\r\n\r\nexport const blockIndexOf = (doc: Document, blockId: string): number =>\r\n paragraphsOf(doc).findIndex((b) => b.id === blockId);\r\n\r\n/** Every block id present anywhere in the doc \u2014 top-level blocks (paragraph,\r\n * image, table) in body + bands, table ids, and paragraph/image ids nested in\r\n * table cells. Used by the review layer's structural-suggestion GC, which keys\r\n * on whole-block identity rather than a text offset range. */\r\nconst collectBlockIds = (blocks: Block[], into: Set<string>): void => {\r\n for (const b of blocks) {\r\n into.add(b.id);\r\n if (b.kind === \"table\") {\r\n for (const row of b.rows) for (const cell of row.cells) collectBlockIds(cell.blocks, into);\r\n }\r\n }\r\n};\r\n\r\n/** Does a block with this id exist anywhere in the document (any kind, any\r\n * container, including table cells)? */\r\nexport const blockExists = (doc: Document, blockId: string): boolean => {\r\n const ids = new Set<string>();\r\n collectBlockIds(doc.blocks, ids);\r\n for (const band of BAND_CONTAINERS) collectBlockIds(doc.section[band] ?? [], ids);\r\n for (const paras of Object.values(doc.footnotes ?? {})) for (const p of paras) ids.add(p.id);\r\n return ids.has(blockId);\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Paragraph locator \u2014 content ops work on any paragraph, wherever it lives.\r\n\r\nexport type ParaLocation =\r\n | { kind: \"top\"; bi: number }\r\n | { kind: \"cell\"; where: \"body\" | BandContainer; bi: number; ri: number; ci: number; pi: number }\r\n | { kind: \"band\"; band: BandContainer; bi: number }\r\n | { kind: \"footnote\"; noteId: string; pi: number };\r\n\r\n/** Top-level block list of a container (\"body\" or a band story). */\r\nexport const containerListOf = (doc: Document, where: \"body\" | BandContainer): Block[] =>\r\n where === \"body\" ? doc.blocks : (doc.section[where] ?? []);\r\n\r\nfunction locateInBlocks(\r\n blocks: Block[],\r\n where: \"body\" | BandContainer,\r\n blockId: string,\r\n): ParaLocation | null {\r\n for (let bi = 0; bi < blocks.length; bi++) {\r\n const b = blocks[bi]!;\r\n if (b.kind === \"paragraph\") {\r\n if (b.id === blockId) {\r\n return where === \"body\" ? { kind: \"top\", bi } : { kind: \"band\", band: where, bi };\r\n }\r\n } else if (b.kind === \"table\") {\r\n for (let ri = 0; ri < b.rows.length; ri++) {\r\n const row = b.rows[ri]!;\r\n for (let ci = 0; ci < row.cells.length; ci++) {\r\n const cell = row.cells[ci]!;\r\n for (let pi = 0; pi < cell.blocks.length; pi++) {\r\n const cb = cell.blocks[pi]!;\r\n // One level deep: paragraphs of nested tables stay read-only.\r\n if (cb.kind === \"paragraph\" && cb.id === blockId) {\r\n return { kind: \"cell\", where, bi, ri, ci, pi };\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nexport function locateParagraph(doc: Document, blockId: string): ParaLocation | null {\r\n const body = locateInBlocks(doc.blocks, \"body\", blockId);\r\n if (body) return body;\r\n for (const band of BAND_CONTAINERS) {\r\n const hit = locateInBlocks(doc.section[band] ?? [], band, blockId);\r\n if (hit) return hit;\r\n }\r\n for (const [noteId, paras] of Object.entries(doc.footnotes ?? {})) {\r\n const pi = paras.findIndex((p) => p.id === blockId);\r\n if (pi >= 0) return { kind: \"footnote\", noteId, pi };\r\n }\r\n return null;\r\n}\r\n\r\nexport function paragraphAt(doc: Document, loc: ParaLocation): Paragraph {\r\n if (loc.kind === \"top\") return doc.blocks[loc.bi] as Paragraph;\r\n if (loc.kind === \"band\") return doc.section[loc.band]![loc.bi] as Paragraph;\r\n if (loc.kind === \"footnote\") return doc.footnotes![loc.noteId]![loc.pi]!;\r\n const table = containerListOf(doc, loc.where)[loc.bi] as Extract<Block, { kind: \"table\" }>;\r\n return table.rows[loc.ri]!.cells[loc.ci]!.blocks[loc.pi] as Paragraph;\r\n}\r\n\r\n/** Immutable path-clone replace; bumps the containing table's revision too. */\r\nexport function replaceParagraphAt(doc: Document, loc: ParaLocation, p: Paragraph): Document {\r\n if (loc.kind === \"footnote\") {\r\n const paras = doc.footnotes![loc.noteId]!.slice();\r\n paras[loc.pi] = p;\r\n return { ...doc, footnotes: { ...doc.footnotes, [loc.noteId]: paras } };\r\n }\r\n if (loc.kind === \"band\") {\r\n const blocks = (doc.section[loc.band] ?? []).slice();\r\n blocks[loc.bi] = p;\r\n return { ...doc, section: { ...doc.section, [loc.band]: blocks } };\r\n }\r\n if (loc.kind === \"top\") {\r\n const blocks = doc.blocks.slice();\r\n blocks[loc.bi] = p;\r\n return { ...doc, blocks };\r\n }\r\n const blocks = containerListOf(doc, loc.where).slice();\r\n const table = blocks[loc.bi] as Extract<Block, { kind: \"table\" }>;\r\n const rows = table.rows.slice();\r\n const row = { cells: rows[loc.ri]!.cells.slice() };\r\n const cell = { ...row.cells[loc.ci]!, blocks: row.cells[loc.ci]!.blocks.slice() };\r\n cell.blocks[loc.pi] = p;\r\n row.cells[loc.ci] = cell;\r\n rows[loc.ri] = row;\r\n blocks[loc.bi] = { ...table, rows, revision: table.revision + 1 };\r\n if (loc.where === \"body\") return { ...doc, blocks };\r\n return { ...doc, section: { ...doc.section, [loc.where]: blocks } };\r\n}\r\n\r\nexport const isInCell = (doc: Document, blockId: string): boolean =>\r\n locateParagraph(doc, blockId)?.kind === \"cell\";\r\n\r\nexport const textOfRuns = (runs: Run[]): string => runs.map((r) => r.text).join(\"\");\r\n\r\nexport const textOfBlock = (doc: Document, blockId: string): string => {\r\n const b = blockById(doc, blockId);\r\n return b ? textOfRuns(b.runs) : \"\";\r\n};\r\n\r\nexport function prevGrapheme(text: string, offset: number): number {\r\n let prev = 0;\r\n for (const s of graphemes.segment(text)) {\r\n if (s.index >= offset) break;\r\n prev = s.index;\r\n }\r\n return prev;\r\n}\r\n\r\nexport function nextGrapheme(text: string, offset: number): number {\r\n for (const s of graphemes.segment(text)) {\r\n const end = s.index + s.segment.length;\r\n if (end > offset) return end;\r\n }\r\n return text.length;\r\n}\r\n\r\nexport function prevWordStart(text: string, offset: number): number {\r\n let prev = 0;\r\n for (const s of words.segment(text)) {\r\n if (s.index >= offset) break;\r\n if (s.isWordLike) prev = s.index;\r\n }\r\n return prev;\r\n}\r\n\r\nexport function nextWordEnd(text: string, offset: number): number {\r\n for (const s of words.segment(text)) {\r\n const end = s.index + s.segment.length;\r\n if (s.isWordLike && end > offset) return end;\r\n }\r\n return text.length;\r\n}\r\n\r\n/** Style of the character before `offset` (Word: typing inherits what precedes\r\n * the caret), falling back to the first run's style. */\r\nexport function styleAtRuns(runs: Run[], offset: number): CharStyle | undefined {\r\n let cum = 0;\r\n for (const r of runs) {\r\n const end = cum + r.text.length;\r\n // offset-1 falls inside this run -> its style precedes the caret\r\n if (offset > cum && offset <= end && r.text.length > 0) return r.style;\r\n cum = end;\r\n }\r\n return runs[0]?.style;\r\n}\r\n", "// List definitions \u2014 a direct mirror of OOXML numbering.xml abstractNum levels,\n// so the docx importer can feed Document.lists losslessly (id space = numId).\n// Markers are PAINT-ONLY: they live in the hanging indent and never affect line\n// breaking (matching Word's overlap behavior for very long markers).\n\nimport type { CharStyle } from \"./document\";\n\nexport type ListNumberFormat =\n | \"bullet\"\n | \"decimal\"\n | \"lowerLetter\"\n | \"upperLetter\"\n | \"lowerRoman\"\n | \"upperRoman\";\n\nexport interface ListLevel {\n format: ListNumberFormat;\n /** Marker pattern; %N is the counter of level N-1, formatted per THAT level\n * (OOXML \u00A717.9.11 semantics). E.g. \"%1.\" or \"%1.%2.%3\". Ignored for bullets. */\n text: string;\n bulletChar?: string;\n /** Text indent for paragraphs at this level (added to ParaStyle.indentLeftPx). */\n indentLeftPx: number;\n /** Marker hangs this far left of the text origin. */\n hangingPx: number;\n start: number;\n markerStyle?: Partial<CharStyle>;\n}\n\nexport interface ListDefinition {\n id: string;\n levels: ListLevel[]; // up to 9 (levels 0..8)\n}\n\n// ---------------------------------------------------------------------------\n// Number formatting\n\nconst ROMAN: [number, string][] = [\n [1000, \"m\"], [900, \"cm\"], [500, \"d\"], [400, \"cd\"], [100, \"c\"], [90, \"xc\"],\n [50, \"l\"], [40, \"xl\"], [10, \"x\"], [9, \"ix\"], [5, \"v\"], [4, \"iv\"], [1, \"i\"],\n];\n\nfunction toRoman(n: number): string {\n let out = \"\";\n let v = Math.max(1, n);\n for (const [value, sym] of ROMAN) {\n while (v >= value) {\n out += sym;\n v -= value;\n }\n }\n return out;\n}\n\nfunction toLetter(n: number): string {\n // 1->a .. 26->z, 27->aa (Word repeats: 27 is actually \"aa\")\n let out = \"\";\n let v = Math.max(1, n);\n while (v > 0) {\n v -= 1;\n out = String.fromCharCode(97 + (v % 26)) + out;\n v = Math.floor(v / 26);\n }\n return out;\n}\n\nexport function formatListNumber(n: number, format: ListNumberFormat): string {\n switch (format) {\n case \"decimal\": return String(n);\n case \"lowerLetter\": return toLetter(n);\n case \"upperLetter\": return toLetter(n).toUpperCase();\n case \"lowerRoman\": return toRoman(n);\n case \"upperRoman\": return toRoman(n).toUpperCase();\n case \"bullet\": return \"\";\n }\n}\n\n/** Render a level's marker from the counter stack (counters[k] = level k's value). */\nexport function markerText(def: ListDefinition, level: number, counters: number[]): string {\n const lvl = def.levels[Math.min(level, def.levels.length - 1)];\n if (!lvl) return \"\";\n if (lvl.format === \"bullet\") return lvl.bulletChar ?? \"\u2022\";\n return lvl.text.replace(/%(\\d)/g, (_, d: string) => {\n const k = Number(d) - 1;\n const refLevel = def.levels[Math.min(k, def.levels.length - 1)];\n const value = counters[k] ?? refLevel?.start ?? 1;\n return formatListNumber(value, refLevel?.format ?? \"decimal\");\n });\n}\n\n// ---------------------------------------------------------------------------\n// Default definitions (what the toolbar buttons create)\n\nexport const DEFAULT_BULLET_LIST_ID = \"bullets\";\nexport const DEFAULT_NUMBER_LIST_ID = \"numbers\";\nexport const DEFAULT_MULTILEVEL_LIST_ID = \"multilevel\";\n\nconst BULLETS = [\"\u2022\", \"\u25E6\", \"\u25AA\"];\nconst NUMBER_FORMATS: ListNumberFormat[] = [\"decimal\", \"lowerLetter\", \"lowerRoman\"];\n\nexport function defaultListDefinition(kind: \"bullet\" | \"decimal\"): ListDefinition {\n const levels: ListLevel[] = [];\n for (let i = 0; i < 9; i++) {\n if (kind === \"bullet\") {\n levels.push({\n format: \"bullet\",\n text: \"\",\n bulletChar: BULLETS[i % 3]!,\n indentLeftPx: 24 + i * 24,\n hangingPx: 18,\n start: 1,\n });\n } else {\n levels.push({\n format: NUMBER_FORMATS[i % 3]!,\n text: `%${i + 1}.`,\n indentLeftPx: 24 + i * 24,\n hangingPx: 22,\n start: 1,\n });\n }\n }\n return { id: kind === \"bullet\" ? DEFAULT_BULLET_LIST_ID : DEFAULT_NUMBER_LIST_ID, levels };\n}\n\n/** A single-glyph bullet list (every level uses `char`) \u2014 what the bullet\n * style picker applies. */\nexport function bulletListDefinition(id: string, char: string): ListDefinition {\n const levels: ListLevel[] = [];\n for (let i = 0; i < 9; i++) {\n levels.push({ format: \"bullet\", text: \"\", bulletChar: char, indentLeftPx: 24 + i * 24, hangingPx: 18, start: 1 });\n }\n return { id, levels };\n}\n\n/** A single-format numbered list (every level uses `format`), with `suffix`\n * after each counter (\".\" or \")\") \u2014 what the number style picker applies. */\nexport function numberListDefinition(id: string, format: ListNumberFormat, suffix: string): ListDefinition {\n const levels: ListLevel[] = [];\n for (let i = 0; i < 9; i++) {\n levels.push({ format, text: `%${i + 1}${suffix}`, indentLeftPx: 24 + i * 24, hangingPx: 22, start: 1 });\n }\n return { id, levels };\n}\n\n/** Legal-style multilevel list: every level is decimal and its marker compounds\n * all ancestors \u2014 level 0 \"1.\", level 1 \"1.1.\", level 2 \"1.1.1.\", \u2026 (Word's\n * default Multilevel List). Tab/Shift+Tab walk the levels. */\nexport function multilevelListDefinition(): ListDefinition {\n const levels: ListLevel[] = [];\n for (let i = 0; i < 9; i++) {\n const text = Array.from({ length: i + 1 }, (_, k) => `%${k + 1}`).join(\".\") + \".\";\n levels.push({\n format: \"decimal\",\n text, // \"%1.\", \"%1.%2.\", \"%1.%2.%3.\", \u2026\n indentLeftPx: 24 + i * 24,\n hangingPx: 24 + i * 8, // compound markers widen with depth\n start: 1,\n });\n }\n return { id: DEFAULT_MULTILEVEL_LIST_ID, levels };\n}\n", "// All mutation flows through operations. Applying an op returns the new document\n// (structurally shared), the op's exact INVERSE (undo is free), and a position\n// mapper so selections survive edits. This seam is also where OT/CRDT\n// collaboration would slot in later.\n\nimport type {\n BandContainer,\n Block,\n BookmarkRange,\n CharStyle,\n Document,\n FieldDef,\n ImageBlock,\n ParaStyle,\n Paragraph,\n Run,\n SdtProps,\n TableBlock,\n TableCell,\n TableRow,\n} from \"./document\";\nimport { BAND_CONTAINERS } from \"./document\";\nimport type { DocPosition } from \"./position\";\nimport {\n locateParagraph,\n paragraphAt,\n replaceParagraphAt,\n styleAtRuns,\n textOfRuns,\n type ParaLocation,\n} from \"./text\";\n\n/** setImageProps payload. `wrap` and `anchor` are mutually-exclusive states, so\n * each accepts `null` as an explicit \"clear this field\" sentinel (plain\n * `undefined` means \"leave unchanged\", which exactOptionalPropertyTypes also\n * needs to keep these distinguishable). */\nexport interface ImagePropsPatch {\n widthPx?: number;\n heightPx?: number;\n align?: ImageBlock[\"align\"];\n wrap?: ImageBlock[\"wrap\"] | null;\n anchor?: ImageBlock[\"anchor\"] | null;\n}\n\nexport type Op =\n | { type: \"insertText\"; at: DocPosition; text: string; style?: CharStyle }\n | { type: \"insertRuns\"; at: DocPosition; runs: Run[] }\n | { type: \"deleteRange\"; blockId: string; start: number; end: number }\n | { type: \"setRuns\"; blockId: string; runs: Run[] }\n | { type: \"setParaStyle\"; blockId: string; patch: Partial<ParaStyle> }\n | { type: \"splitParagraph\"; at: DocPosition; newBlockId: string; newStyle?: ParaStyle }\n | { type: \"mergeParagraphs\"; firstBlockId: string }\n | { type: \"insertBlock\"; index: number; block: Block; where?: Container }\n | { type: \"removeBlock\"; blockId: string }\n | { type: \"setImageProps\"; blockId: string; patch: ImagePropsPatch }\n | { type: \"setTableRow\"; tableId: string; rowIndex: number; row: TableRow }\n | { type: \"setTableStructure\"; tableId: string; rows: TableRow[]; colFractions?: number[] }\n | { type: \"setTableStyleRef\"; tableId: string; styleId: string | null; condOverrides?: import(\"./document\").TableCondOverrides | null }\n | { type: \"setTableColFractions\"; blockId: string; fractions: number[] }\n | { type: \"insertTableRow\"; tableId: string; rowIndex: number; row: TableRow }\n | { type: \"removeTableRow\"; tableId: string; rowIndex: number }\n | { type: \"insertTableColumn\"; tableId: string; colIndex: number; cells: TableCell[]; fractions?: number[] }\n | { type: \"removeTableColumn\"; tableId: string; colIndex: number }\n | { type: \"setStylesheet\"; stylesheet: import(\"./stylesheet\").Stylesheet }\n | { type: \"setTableStyleSheet\"; tableStyles: Record<string, import(\"./tableStyles\").TableStyle> }\n | { type: \"setListDefinition\"; listId: string; def: import(\"./lists\").ListDefinition | null }\n | { type: \"setSectionProps\"; geometry: SectionGeometry }\n | { type: \"setSectionBand\"; band: BandContainer; blocks: Block[] | null }\n | { type: \"setFootnote\"; noteId: string; paras: Paragraph[] | null }\n | { type: \"setSdtProps\"; id: string; props: SdtProps | null }\n | { type: \"setField\"; id: string; def: FieldDef | null }\n | { type: \"setTocInstruction\"; instruction: string | null }\n | { type: \"setBookmark\"; name: string; range: BookmarkRange | null };\n\n/** Page-setup fields of the final section (`doc.section`). Bands are NOT here \u2014\n * they change through container ops; mid-document sections change through\n * setParaStyle on their break paragraph's `sectionBreak.props`. */\nexport interface SectionGeometry {\n pageWidthPx: number;\n pageHeightPx: number;\n marginPx: { top: number; right: number; bottom: number; left: number };\n /** `null` = single column (the explicit \"off\" \u2014 SectionPatch distinguishes it\n * from \"inherit\"). `sep`/`cols` carry the separator line and per-column\n * widths respectively. */\n columns: { count: number; gapPx: number; sep?: boolean; cols?: import(\"./document\").ColumnEntry[] } | null;\n /** `null` = continue numbering from the previous section. */\n pageNumberStart: number | null;\n /** `null` = inherit (center band in margin). px from page top to header top. */\n headerDistancePx: number | null;\n /** `null` = inherit (center band in margin). px from page bottom to footer bottom. */\n footerDistancePx: number | null;\n /** `null` = no page fill. \"#rrggbb\". */\n pageColorHex: string | null;\n /** `null` = no page border. */\n pageBorders: import(\"./document\").PageBorders | null;\n}\n\n/** Top-level block containers: the body, or one of the six margin-band stories\n * (default header/footer + first/even variants). */\nexport type Container = \"body\" | BandContainer;\n\nexport interface ApplyResult {\n doc: Document;\n inverse: Op;\n /** Remaps any stored position across this edit (selection, bookmarks). */\n mapPosition(pos: DocPosition): DocPosition;\n dirtyBlockIds: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Run-list surgery (pure helpers)\n\nexport function styleEq(a: CharStyle, b: CharStyle): boolean {\n return (\n a.fontFamily === b.fontFamily &&\n a.fontSizePx === b.fontSizePx &&\n a.bold === b.bold &&\n a.italic === b.italic &&\n a.underline === b.underline &&\n a.strikethrough === b.strikethrough &&\n a.color === b.color &&\n !!a.hidden === !!b.hidden && // hidden runs must never merge with visible ones\n (a.letterSpacingPx ?? 0) === (b.letterSpacingPx ?? 0) &&\n a.highlightColor === b.highlightColor &&\n a.verticalAlign === b.verticalAlign &&\n a.link === b.link &&\n a.footnoteRef === b.footnoteRef && // adjacent refs must never merge into one run\n a.sdtId === b.sdtId && // content-control boundaries survive normalization\n a.fieldId === b.fieldId // inline-field boundaries survive normalization\n );\n}\n\n/** Merge equal-styled neighbors, drop empties. An all-empty paragraph keeps ONE\n * empty run so the paragraph mark still carries a style (Word behavior). */\nexport function normalizeRuns(runs: Run[], fallback: CharStyle): Run[] {\n const nonEmpty = runs.filter((r) => r.text.length > 0);\n if (nonEmpty.length === 0) return [{ text: \"\", style: runs[0]?.style ?? fallback }];\n const out: Run[] = [{ ...nonEmpty[0]! }];\n for (let i = 1; i < nonEmpty.length; i++) {\n const r = nonEmpty[i]!;\n const last = out[out.length - 1]!;\n if (styleEq(last.style, r.style)) last.text += r.text;\n else out.push({ ...r });\n }\n return out;\n}\n\n/** Split a run list at a UTF-16 offset. */\nexport function splitRunsAt(runs: Run[], offset: number): [Run[], Run[]] {\n const head: Run[] = [];\n const tail: Run[] = [];\n let cum = 0;\n for (const r of runs) {\n const end = cum + r.text.length;\n if (end <= offset) head.push(r);\n else if (cum >= offset) tail.push(r);\n else {\n head.push({ text: r.text.slice(0, offset - cum), style: r.style });\n tail.push({ text: r.text.slice(offset - cum), style: r.style });\n }\n cum = end;\n }\n return [head, tail];\n}\n\nexport function sliceRuns(runs: Run[], start: number, end: number): Run[] {\n const [, fromStart] = splitRunsAt(runs, start);\n const [middle] = splitRunsAt(fromStart, end - start);\n return middle;\n}\n\nfunction fallbackStyle(runs: Run[], offset: number): CharStyle {\n return (\n styleAtRuns(runs, offset) ?? {\n fontFamily: \"Georgia, serif\",\n fontSizePx: 16,\n bold: false,\n italic: false,\n underline: false,\n strikethrough: false,\n color: \"#202124\",\n }\n );\n}\n\nexport function insertTextInRuns(runs: Run[], offset: number, text: string, style?: CharStyle): Run[] {\n const st = style ?? fallbackStyle(runs, offset);\n const [head, tail] = splitRunsAt(runs, offset);\n return normalizeRuns([...head, { text, style: st }, ...tail], st);\n}\n\nexport function insertRunsInRuns(runs: Run[], offset: number, inserted: Run[]): Run[] {\n const [head, tail] = splitRunsAt(runs, offset);\n return normalizeRuns([...head, ...inserted, ...tail], fallbackStyle(runs, offset));\n}\n\nexport function deleteInRuns(runs: Run[], start: number, end: number): Run[] {\n const [head, fromStart] = splitRunsAt(runs, start);\n const [, tail] = splitRunsAt(fromStart, end - start);\n return normalizeRuns([...head, ...tail], fallbackStyle(runs, start));\n}\n\nexport function applyStylePatchToRuns(\n runs: Run[],\n start: number,\n end: number,\n patch: Partial<CharStyle>,\n): Run[] {\n const [head, fromStart] = splitRunsAt(runs, start);\n const [middle, tail] = splitRunsAt(fromStart, end - start);\n const styled = middle.map((r) => ({ text: r.text, style: { ...r.style, ...patch } }));\n return normalizeRuns([...head, ...styled, ...tail], fallbackStyle(runs, start));\n}\n\n/** Rewrite the TEXT of runs in [start,end) through `fn`, preserving each run's\n * style. `fn` receives the whole covered string (so it can do context-aware\n * transforms like sentence case) and must return a string of the SAME length \u2014\n * if the length changes the slice is left untouched (run offsets must stay\n * stable). Used by change-case; transforms here are 1:1 per character. */\nexport function mapTextInRuns(\n runs: Run[],\n start: number,\n end: number,\n fn: (covered: string) => string,\n): Run[] {\n const [head, fromStart] = splitRunsAt(runs, start);\n const [middle, tail] = splitRunsAt(fromStart, end - start);\n const original = middle.map((r) => r.text).join(\"\");\n const next = fn(original);\n let i = 0;\n const out =\n next.length === original.length\n ? middle.map((r) => {\n const text = next.slice(i, i + r.text.length);\n i += r.text.length;\n return { text, style: r.style };\n })\n : middle;\n return normalizeRuns([...head, ...out, ...tail], fallbackStyle(runs, start));\n}\n\n// ---------------------------------------------------------------------------\n// applyOp\n\nconst identity = (p: DocPosition): DocPosition => p;\n\n/** Content ops accept ANY paragraph (top-level or table cell). */\nfunction mustLocate(doc: Document, blockId: string): { loc: ParaLocation; block: Paragraph } {\n const loc = locateParagraph(doc, blockId);\n if (!loc) throw new Error(`paragraph ${blockId} not found`);\n return { loc, block: paragraphAt(doc, loc) };\n}\n\n// ---- container plumbing: structural ops work on the body OR a band story ----\n\nexport function containerBlocks(doc: Document, where: Container): Block[] {\n return where === \"body\" ? doc.blocks : (doc.section[where] ?? []);\n}\n\nfunction withContainerBlocks(doc: Document, where: Container, blocks: Block[]): Document {\n if (where === \"body\") return { ...doc, blocks };\n return { ...doc, section: { ...doc.section, [where]: blocks } };\n}\n\n/** Which container holds this TOP-LEVEL block (paragraphs in table cells are\n * not top-level and return null). */\nexport function containerOf(doc: Document, blockId: string): { where: Container; index: number } | null {\n for (const where of [\"body\", ...BAND_CONTAINERS] as Container[]) {\n const index = containerBlocks(doc, where).findIndex((b) => b.id === blockId);\n if (index >= 0) return { where, index };\n }\n return null;\n}\n\nfunction mustTable(doc: Document, blockId: string): { where: Container; bi: number; block: TableBlock } {\n const found = containerOf(doc, blockId);\n const block = found ? containerBlocks(doc, found.where)[found.index] : undefined;\n if (!found || !block || block.kind !== \"table\") throw new Error(`table ${blockId} not found`);\n return { where: found.where, bi: found.index, block };\n}\n\nfunction replaceTable(doc: Document, where: Container, bi: number, table: TableBlock): Document {\n const blocks = containerBlocks(doc, where).slice();\n blocks[bi] = { ...table, revision: table.revision + 1 };\n return withContainerBlocks(doc, where, blocks);\n}\n\n/** Path-clone a table (body or band) replacing one cell's block list\n * (revision bumped). */\nfunction replaceCellBlocks(\n doc: Document,\n where: Container,\n bi: number,\n ri: number,\n ci: number,\n cellBlocks: Block[],\n): Document {\n const blocks = containerBlocks(doc, where).slice();\n const table = blocks[bi] as TableBlock;\n const rows = table.rows.slice();\n const row = { cells: rows[ri]!.cells.slice() };\n row.cells[ci] = { ...row.cells[ci]!, blocks: cellBlocks };\n rows[ri] = row;\n blocks[bi] = { ...table, rows, revision: table.revision + 1 };\n return withContainerBlocks(doc, where, blocks);\n}\n\n/** First caret-capable paragraph in a set of rows (cells may start with images). */\nexport function firstParagraphInRows(rows: TableRow[]): Paragraph | undefined {\n for (const row of rows) {\n for (const cell of row.cells) {\n for (const b of cell.blocks) if (b.kind === \"paragraph\") return b;\n }\n }\n return undefined;\n}\n\n/** Find an image block anywhere editable: top-level containers or table cells. */\nexport type ImageLocation =\n | { kind: \"top\"; where: Container; index: number; image: ImageBlock }\n | { kind: \"cell\"; bi: number; ri: number; ci: number; ii: number; image: ImageBlock };\n\nexport function locateImage(doc: Document, blockId: string): ImageLocation | null {\n const found = containerOf(doc, blockId);\n if (found) {\n const block = containerBlocks(doc, found.where)[found.index];\n if (block?.kind === \"image\") return { kind: \"top\", where: found.where, index: found.index, image: block };\n return null;\n }\n for (let bi = 0; bi < doc.blocks.length; bi++) {\n const b = doc.blocks[bi]!;\n if (b.kind !== \"table\") continue;\n for (let ri = 0; ri < b.rows.length; ri++) {\n const row = b.rows[ri]!;\n for (let ci = 0; ci < row.cells.length; ci++) {\n const cell = row.cells[ci]!;\n for (let ii = 0; ii < cell.blocks.length; ii++) {\n const cb = cell.blocks[ii]!;\n if (cb.kind === \"image\" && cb.id === blockId) return { kind: \"cell\", bi, ri, ci, ii, image: cb };\n }\n }\n }\n }\n return null;\n}\n\n/** The table's true grid-column count: the widest row measured in GRID columns,\n * not cells \u2014 colSpan widens a cell and a rowSpan from an earlier row leaves a\n * hole that this row's cells shift past (HTML table model). Counting cells\n * alone undercounts any row with a colSpan or a vertical-merge hole. */\nexport function gridColumnCount(t: TableBlock): number {\n let maxCols = 1;\n const rowsRemaining: number[] = [];\n for (const row of t.rows) {\n let col = 0;\n for (const cell of row.cells) {\n while ((rowsRemaining[col] ?? 0) > 0) col++;\n const span = Math.max(1, cell.colSpan ?? 1);\n const rowSpan = Math.max(1, cell.rowSpan ?? 1);\n if (rowSpan > 1) for (let k = 0; k < span; k++) rowsRemaining[col + k] = rowSpan;\n col += span;\n }\n maxCols = Math.max(maxCols, col);\n for (let c = 0; c < rowsRemaining.length; c++) if (rowsRemaining[c]! > 0) rowsRemaining[c]!--;\n }\n return maxCols;\n}\n\n/** Column fractions normalized to the table's column count. */\nexport function effectiveFractions(t: TableBlock): number[] {\n const n = gridColumnCount(t);\n if (t.colFractions && t.colFractions.length === n) return t.colFractions;\n return Array.from({ length: n }, () => 1 / n);\n}\n\nfunction bump(block: Paragraph, runs: Run[]): Paragraph {\n return { ...block, runs, revision: block.revision + 1 };\n}\n\nexport function applyOp(doc: Document, op: Op): ApplyResult {\n switch (op.type) {\n case \"insertText\": {\n const { loc, block } = mustLocate(doc, op.at.blockId);\n const runs = insertTextInRuns(block.runs, op.at.offset, op.text, op.style);\n const len = op.text.length;\n return {\n doc: replaceParagraphAt(doc, loc, bump(block, runs)),\n inverse: { type: \"deleteRange\", blockId: block.id, start: op.at.offset, end: op.at.offset + len },\n mapPosition: (p) =>\n p.blockId === block.id && p.offset >= op.at.offset\n ? { blockId: p.blockId, offset: p.offset + len }\n : p,\n dirtyBlockIds: [block.id],\n };\n }\n\n case \"insertRuns\": {\n const { loc, block } = mustLocate(doc, op.at.blockId);\n const len = textOfRuns(op.runs).length;\n const runs = insertRunsInRuns(block.runs, op.at.offset, op.runs);\n return {\n doc: replaceParagraphAt(doc, loc, bump(block, runs)),\n inverse: { type: \"deleteRange\", blockId: block.id, start: op.at.offset, end: op.at.offset + len },\n mapPosition: (p) =>\n p.blockId === block.id && p.offset >= op.at.offset\n ? { blockId: p.blockId, offset: p.offset + len }\n : p,\n dirtyBlockIds: [block.id],\n };\n }\n\n case \"deleteRange\": {\n const { loc, block } = mustLocate(doc, op.blockId);\n const removed = sliceRuns(block.runs, op.start, op.end);\n const runs = deleteInRuns(block.runs, op.start, op.end);\n const len = op.end - op.start;\n return {\n doc: replaceParagraphAt(doc, loc, bump(block, runs)),\n inverse: { type: \"insertRuns\", at: { blockId: block.id, offset: op.start }, runs: removed },\n mapPosition: (p) => {\n if (p.blockId !== block.id || p.offset <= op.start) return p;\n return { blockId: p.blockId, offset: p.offset >= op.end ? p.offset - len : op.start };\n },\n dirtyBlockIds: [block.id],\n };\n }\n\n case \"setRuns\": {\n const { loc, block } = mustLocate(doc, op.blockId);\n return {\n doc: replaceParagraphAt(doc, loc, bump(block, op.runs)),\n inverse: { type: \"setRuns\", blockId: block.id, runs: block.runs },\n mapPosition: identity, // callers use setRuns for length-preserving restyles\n dirtyBlockIds: [block.id],\n };\n }\n\n case \"setParaStyle\": {\n const { loc, block } = mustLocate(doc, op.blockId);\n const oldPatch: Partial<ParaStyle> = {};\n for (const key of Object.keys(op.patch) as (keyof ParaStyle)[]) {\n // @ts-expect-error \u2014 keyed copy of the previous values for the inverse\n oldPatch[key] = block.style[key];\n }\n const styled: Paragraph = {\n ...block,\n style: { ...block.style, ...op.patch },\n revision: block.revision + 1,\n };\n return {\n doc: replaceParagraphAt(doc, loc, styled),\n inverse: { type: \"setParaStyle\", blockId: block.id, patch: oldPatch },\n mapPosition: identity,\n dirtyBlockIds: [block.id],\n };\n }\n\n case \"splitParagraph\": {\n const loc = locateParagraph(doc, op.at.blockId);\n if (!loc) throw new Error(`paragraph ${op.at.blockId} not found`);\n const block = paragraphAt(doc, loc);\n const [headRuns, tailRuns] = splitRunsAt(block.runs, op.at.offset);\n const carry = fallbackStyle(block.runs, op.at.offset);\n const head = bump(block, normalizeRuns(headRuns, carry));\n const tail: Paragraph = {\n kind: \"paragraph\",\n id: op.newBlockId,\n revision: 0,\n runs: normalizeRuns(tailRuns, carry),\n style: op.newStyle ?? { ...block.style },\n };\n let next: Document;\n if (loc.kind === \"cell\") {\n const table = containerBlocks(doc, loc.where)[loc.bi] as TableBlock;\n const cellBlocks = table.rows[loc.ri]!.cells[loc.ci]!.blocks.slice();\n cellBlocks.splice(loc.pi, 1, head, tail);\n next = replaceCellBlocks(doc, loc.where, loc.bi, loc.ri, loc.ci, cellBlocks);\n } else if (loc.kind === \"footnote\") {\n const paras = doc.footnotes![loc.noteId]!.slice();\n paras.splice(loc.pi, 1, head, tail);\n next = { ...doc, footnotes: { ...doc.footnotes, [loc.noteId]: paras } };\n } else {\n const where: Container = loc.kind === \"band\" ? loc.band : \"body\";\n const blocks = containerBlocks(doc, where).slice();\n blocks.splice(loc.bi, 1, head, tail);\n next = withContainerBlocks(doc, where, blocks);\n }\n return {\n doc: next,\n inverse: { type: \"mergeParagraphs\", firstBlockId: block.id },\n mapPosition: (p) =>\n p.blockId === block.id && p.offset >= op.at.offset\n ? { blockId: op.newBlockId, offset: p.offset - op.at.offset }\n : p,\n dirtyBlockIds: [block.id, op.newBlockId],\n };\n }\n\n case \"mergeParagraphs\": {\n const loc = locateParagraph(doc, op.firstBlockId);\n if (!loc) throw new Error(`paragraph ${op.firstBlockId} not found`);\n const block = paragraphAt(doc, loc);\n let nextPara: Paragraph;\n if (loc.kind === \"cell\") {\n const table = containerBlocks(doc, loc.where)[loc.bi] as TableBlock;\n const candidate = table.rows[loc.ri]!.cells[loc.ci]!.blocks[loc.pi + 1];\n if (!candidate || candidate.kind !== \"paragraph\") {\n throw new Error(\"mergeParagraphs: no next paragraph in cell\");\n }\n nextPara = candidate;\n } else if (loc.kind === \"footnote\") {\n const candidate = doc.footnotes![loc.noteId]![loc.pi + 1];\n if (!candidate) throw new Error(\"mergeParagraphs: no next paragraph in footnote\");\n nextPara = candidate;\n } else {\n const where: Container = loc.kind === \"band\" ? loc.band : \"body\";\n const candidate = containerBlocks(doc, where)[loc.bi + 1];\n if (!candidate || candidate.kind !== \"paragraph\") {\n throw new Error(\"mergeParagraphs: no next paragraph\");\n }\n nextPara = candidate;\n }\n const headLen = textOfRuns(block.runs).length;\n const merged = bump(\n block,\n normalizeRuns([...block.runs, ...nextPara.runs], fallbackStyle(block.runs, headLen)),\n );\n let next: Document;\n if (loc.kind === \"cell\") {\n const table = containerBlocks(doc, loc.where)[loc.bi] as TableBlock;\n const cellBlocks = table.rows[loc.ri]!.cells[loc.ci]!.blocks.slice();\n cellBlocks.splice(loc.pi, 2, merged);\n next = replaceCellBlocks(doc, loc.where, loc.bi, loc.ri, loc.ci, cellBlocks);\n } else if (loc.kind === \"footnote\") {\n const paras = doc.footnotes![loc.noteId]!.slice();\n paras.splice(loc.pi, 2, merged);\n next = { ...doc, footnotes: { ...doc.footnotes, [loc.noteId]: paras } };\n } else {\n const where: Container = loc.kind === \"band\" ? loc.band : \"body\";\n const blocks = containerBlocks(doc, where).slice();\n blocks.splice(loc.bi, 2, merged);\n next = withContainerBlocks(doc, where, blocks);\n }\n return {\n doc: next,\n inverse: {\n type: \"splitParagraph\",\n at: { blockId: block.id, offset: headLen },\n newBlockId: nextPara.id,\n newStyle: nextPara.style,\n },\n mapPosition: (p) =>\n p.blockId === nextPara.id ? { blockId: block.id, offset: headLen + p.offset } : p,\n dirtyBlockIds: [block.id, nextPara.id],\n };\n }\n\n case \"insertBlock\": {\n const where = op.where ?? \"body\";\n const blocks = containerBlocks(doc, where).slice();\n blocks.splice(op.index, 0, op.block);\n return {\n doc: withContainerBlocks(doc, where, blocks),\n inverse: { type: \"removeBlock\", blockId: op.block.id },\n mapPosition: identity,\n dirtyBlockIds: [op.block.id],\n };\n }\n\n case \"removeBlock\": {\n const found = containerOf(doc, op.blockId);\n if (!found) throw new Error(`block ${op.blockId} not found`);\n const blocks = containerBlocks(doc, found.where).slice();\n const block = blocks[found.index]!;\n blocks.splice(found.index, 1);\n const neighbor = blocks[Math.min(found.index, blocks.length - 1)];\n return {\n doc: withContainerBlocks(doc, found.where, blocks),\n inverse: { type: \"insertBlock\", index: found.index, block, where: found.where },\n mapPosition: (p) =>\n p.blockId === op.blockId && neighbor\n ? { blockId: neighbor.id, offset: 0 }\n : p,\n dirtyBlockIds: [op.blockId],\n };\n }\n\n case \"setImageProps\": {\n const loc = locateImage(doc, op.blockId);\n if (!loc) throw new Error(`image ${op.blockId} not found`);\n const block = loc.image;\n // Inverse restores prior values; a field absent before is cleared (null)\n // on undo. wrap/anchor use null to mean \"clear\" (they're exclusive states).\n const oldPatch: ImagePropsPatch = {};\n if (op.patch.widthPx !== undefined) oldPatch.widthPx = block.widthPx;\n if (op.patch.heightPx !== undefined) oldPatch.heightPx = block.heightPx;\n if (op.patch.align !== undefined) oldPatch.align = block.align;\n if (op.patch.wrap !== undefined) oldPatch.wrap = block.wrap ?? null;\n if (op.patch.anchor !== undefined) oldPatch.anchor = block.anchor ?? null;\n const updated: ImageBlock = { ...block, revision: block.revision + 1 };\n if (op.patch.widthPx !== undefined) updated.widthPx = op.patch.widthPx;\n if (op.patch.heightPx !== undefined) updated.heightPx = op.patch.heightPx;\n if (op.patch.align !== undefined) updated.align = op.patch.align;\n if (op.patch.wrap !== undefined) {\n if (op.patch.wrap === null) delete updated.wrap;\n else updated.wrap = op.patch.wrap;\n }\n if (op.patch.anchor !== undefined) {\n if (op.patch.anchor === null) delete updated.anchor;\n else updated.anchor = op.patch.anchor;\n }\n let next: Document;\n if (loc.kind === \"top\") {\n const blocks = containerBlocks(doc, loc.where).slice();\n blocks[loc.index] = updated;\n next = withContainerBlocks(doc, loc.where, blocks);\n } else {\n const table = doc.blocks[loc.bi] as TableBlock;\n const cellBlocks = table.rows[loc.ri]!.cells[loc.ci]!.blocks.slice();\n cellBlocks[loc.ii] = updated;\n next = replaceCellBlocks(doc, \"body\", loc.bi, loc.ri, loc.ci, cellBlocks); // locateImage cells are body-only\n\n }\n return {\n doc: next,\n inverse: { type: \"setImageProps\", blockId: op.blockId, patch: oldPatch },\n mapPosition: identity,\n dirtyBlockIds: [op.blockId],\n };\n }\n\n case \"setTableColFractions\": {\n const { where, bi, block } = mustTable(doc, op.blockId);\n const old = effectiveFractions(block);\n return {\n doc: replaceTable(doc, where, bi, { ...block, colFractions: op.fractions }),\n inverse: { type: \"setTableColFractions\", blockId: op.blockId, fractions: old },\n mapPosition: identity,\n dirtyBlockIds: [op.blockId],\n };\n }\n\n case \"setTableRow\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const old = block.rows[op.rowIndex];\n if (!old) throw new Error(\"setTableRow: no such row\");\n const removedIds = new Set(old.cells.flatMap((c) => c.blocks.map((p) => p.id)));\n const rows = block.rows.slice();\n rows[op.rowIndex] = op.row;\n const fallback = firstParagraphInRows([op.row]);\n return {\n doc: replaceTable(doc, where, bi, { ...block, rows }),\n inverse: { type: \"setTableRow\", tableId: op.tableId, rowIndex: op.rowIndex, row: old },\n mapPosition: (p) => {\n if (!removedIds.has(p.blockId)) return p;\n // position survives if its paragraph still exists in the new row\n const kept = op.row.cells.some((c) => c.blocks.some((b) => b.id === p.blockId));\n return kept || !fallback ? p : { blockId: fallback.id, offset: 0 };\n },\n dirtyBlockIds: [op.tableId],\n };\n }\n\n case \"insertTableRow\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const rows = block.rows.slice();\n rows.splice(op.rowIndex, 0, op.row);\n return {\n doc: replaceTable(doc, where, bi, { ...block, rows }),\n inverse: { type: \"removeTableRow\", tableId: op.tableId, rowIndex: op.rowIndex },\n mapPosition: identity,\n dirtyBlockIds: [op.tableId],\n };\n }\n\n case \"removeTableRow\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const removed = block.rows[op.rowIndex];\n if (!removed) throw new Error(\"removeTableRow: no such row\");\n const rows = block.rows.slice();\n rows.splice(op.rowIndex, 1);\n const removedIds = new Set(removed.cells.flatMap((c) => c.blocks.map((p) => p.id)));\n const fallbackRow = rows[Math.min(op.rowIndex, rows.length - 1)];\n const fallback = fallbackRow ? firstParagraphInRows([fallbackRow]) : undefined;\n return {\n doc: replaceTable(doc, where, bi, { ...block, rows }),\n inverse: { type: \"insertTableRow\", tableId: op.tableId, rowIndex: op.rowIndex, row: removed },\n mapPosition: (p) =>\n removedIds.has(p.blockId) && fallback ? { blockId: fallback.id, offset: 0 } : p,\n dirtyBlockIds: [op.tableId],\n };\n }\n\n case \"setListDefinition\": {\n const old = doc.lists?.[op.listId] ?? null;\n const lists = { ...(doc.lists ?? {}) };\n if (op.def) lists[op.listId] = op.def;\n else delete lists[op.listId];\n return {\n doc: { ...doc, lists },\n inverse: { type: \"setListDefinition\", listId: op.listId, def: old },\n mapPosition: identity,\n // Indents come from the definition \u2192 every paragraph in the list must\n // re-measure; their (revision,width) line-cache keys change with width.\n dirtyBlockIds: [],\n };\n }\n\n case \"setTableStructure\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const oldIds = new Set(\n block.rows.flatMap((r) => r.cells.flatMap((c) => c.blocks.map((p) => p.id))),\n );\n const fallback = firstParagraphInRows(op.rows);\n const next: TableBlock = { ...block, rows: op.rows };\n if (op.colFractions) next.colFractions = op.colFractions;\n else delete next.colFractions;\n const inverse: Op = { type: \"setTableStructure\", tableId: op.tableId, rows: block.rows };\n if (block.colFractions) inverse.colFractions = block.colFractions;\n return {\n doc: replaceTable(doc, where, bi, next),\n inverse,\n mapPosition: (p) => {\n if (!oldIds.has(p.blockId)) return p;\n const kept = op.rows.some((r) =>\n r.cells.some((c) => c.blocks.some((b) => b.id === p.blockId)),\n );\n return kept || !fallback ? p : { blockId: fallback.id, offset: 0 };\n },\n dirtyBlockIds: [op.tableId],\n };\n }\n\n case \"insertTableColumn\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const snapshot: Op = { type: \"setTableStructure\", tableId: op.tableId, rows: block.rows };\n if (block.colFractions) snapshot.colFractions = block.colFractions;\n // Span-aware: a merged cell covering the insertion point grows by one\n // column instead of having a new cell slotted into its middle.\n const rows = block.rows.map((row, ri) => {\n const cells: TableCell[] = [];\n let col = 0;\n let inserted = false;\n for (const cell of row.cells) {\n const span = cell.colSpan ?? 1;\n if (!inserted && op.colIndex > col && op.colIndex < col + span) {\n cells.push({ ...cell, colSpan: span + 1 });\n inserted = true;\n } else if (!inserted && op.colIndex === col) {\n const fresh = op.cells[ri];\n if (fresh) cells.push(fresh);\n cells.push(cell);\n inserted = true;\n } else {\n cells.push(cell);\n }\n col += span;\n }\n if (!inserted) {\n const fresh = op.cells[ri];\n if (fresh) cells.push(fresh); // append at the right edge\n }\n return { cells };\n });\n let fractions = op.fractions;\n if (!fractions) {\n const old = effectiveFractions(block);\n const fresh = 1 / (old.length + 1);\n fractions = old.map((f) => f * (1 - fresh));\n fractions.splice(op.colIndex, 0, fresh);\n }\n return {\n doc: replaceTable(doc, where, bi, { ...block, rows, colFractions: fractions }),\n inverse: snapshot,\n mapPosition: identity,\n dirtyBlockIds: [op.tableId],\n };\n }\n\n case \"setStylesheet\": {\n const old = doc.stylesheet ?? { styles: [], defaultStyleId: \"Normal\" };\n return {\n doc: { ...doc, stylesheet: op.stylesheet },\n inverse: { type: \"setStylesheet\", stylesheet: old },\n mapPosition: identity,\n dirtyBlockIds: [], // restyling paragraphs happens via setRuns/setParaStyle ops\n };\n }\n\n case \"setTableStyleSheet\": {\n const old = doc.tableStyles ?? {};\n return {\n doc: { ...doc, tableStyles: op.tableStyles },\n inverse: { type: \"setTableStyleSheet\", tableStyles: old },\n mapPosition: identity,\n dirtyBlockIds: [], // re-baking cells happens via the table-replace ops\n };\n }\n\n case \"setTableStyleRef\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const oldStyleId = block.styleId ?? null;\n const oldCond = block.condOverrides ?? null;\n const next: TableBlock = { ...block };\n if (op.styleId === null) delete next.styleId;\n else next.styleId = op.styleId;\n if (op.condOverrides !== undefined) {\n if (op.condOverrides === null) delete next.condOverrides;\n else next.condOverrides = op.condOverrides;\n }\n return {\n doc: replaceTable(doc, where, bi, next),\n inverse: { type: \"setTableStyleRef\", tableId: op.tableId, styleId: oldStyleId, condOverrides: oldCond },\n mapPosition: identity,\n dirtyBlockIds: [op.tableId],\n };\n }\n\n case \"setSectionProps\": {\n const s = doc.section;\n const old: SectionGeometry = {\n pageWidthPx: s.pageWidthPx,\n pageHeightPx: s.pageHeightPx,\n marginPx: { ...s.marginPx },\n columns: s.columns\n ? { ...s.columns, ...(s.columns.cols ? { cols: s.columns.cols.map((c) => ({ ...c })) } : {}) }\n : null,\n pageNumberStart: s.pageNumberStart ?? null,\n headerDistancePx: s.headerDistancePx ?? null,\n footerDistancePx: s.footerDistancePx ?? null,\n pageColorHex: s.pageColorHex ?? null,\n pageBorders: s.pageBorders ?? null,\n };\n const next = {\n ...s,\n pageWidthPx: op.geometry.pageWidthPx,\n pageHeightPx: op.geometry.pageHeightPx,\n marginPx: op.geometry.marginPx,\n };\n if (op.geometry.columns) next.columns = op.geometry.columns;\n else delete next.columns;\n if (op.geometry.pageNumberStart !== null) next.pageNumberStart = op.geometry.pageNumberStart;\n else delete next.pageNumberStart;\n if (op.geometry.headerDistancePx !== null) next.headerDistancePx = op.geometry.headerDistancePx;\n else delete next.headerDistancePx;\n if (op.geometry.footerDistancePx !== null) next.footerDistancePx = op.geometry.footerDistancePx;\n else delete next.footerDistancePx;\n if (op.geometry.pageColorHex !== null) next.pageColorHex = op.geometry.pageColorHex;\n else delete next.pageColorHex;\n if (op.geometry.pageBorders !== null) next.pageBorders = op.geometry.pageBorders;\n else delete next.pageBorders;\n return {\n doc: { ...doc, section: next },\n inverse: { type: \"setSectionProps\", geometry: old },\n mapPosition: identity,\n dirtyBlockIds: [], // geometry change \u2192 full re-walk; line caches keyed by width\n };\n }\n\n case \"setFootnote\": {\n const old = doc.footnotes?.[op.noteId] ?? null;\n const footnotes = { ...(doc.footnotes ?? {}) };\n if (op.paras) footnotes[op.noteId] = op.paras;\n else delete footnotes[op.noteId];\n const removedIds = new Set((old ?? []).map((p) => p.id));\n const fallback = doc.blocks.find((b) => b.kind === \"paragraph\");\n return {\n doc: { ...doc, footnotes },\n inverse: { type: \"setFootnote\", noteId: op.noteId, paras: old },\n mapPosition: (p) => {\n if (!removedIds.has(p.blockId)) return p;\n const kept = (op.paras ?? []).some((b) => b.id === p.blockId);\n return kept || !fallback ? p : { blockId: fallback.id, offset: 0 };\n },\n dirtyBlockIds: [],\n };\n }\n\n case \"setSdtProps\": {\n const old = doc.sdts?.[op.id] ?? null;\n const sdts = { ...(doc.sdts ?? {}) };\n if (op.props) sdts[op.id] = op.props;\n else delete sdts[op.id];\n return {\n doc: { ...doc, sdts },\n inverse: { type: \"setSdtProps\", id: op.id, props: old },\n mapPosition: identity,\n dirtyBlockIds: [], // run markers change via setRuns/applyStylePatch ops\n };\n }\n\n case \"setField\": {\n const old = doc.fields?.[op.id] ?? null;\n const fields = { ...(doc.fields ?? {}) };\n if (op.def) fields[op.id] = op.def;\n else delete fields[op.id];\n return {\n doc: { ...doc, fields },\n inverse: { type: \"setField\", id: op.id, def: old },\n mapPosition: identity,\n dirtyBlockIds: [], // run/block fieldId markers change via setRuns/insert ops\n };\n }\n\n case \"setTocInstruction\": {\n const old = doc.tocInstruction ?? null;\n const next = { ...doc };\n if (op.instruction !== null) next.tocInstruction = op.instruction;\n else delete next.tocInstruction;\n return {\n doc: next,\n inverse: { type: \"setTocInstruction\", instruction: old },\n mapPosition: identity,\n dirtyBlockIds: [], // entries are regenerated via insert/removeBlock ops in the same tx\n };\n }\n\n case \"setBookmark\": {\n const old = doc.bookmarks?.[op.name] ?? null;\n const bookmarks = { ...(doc.bookmarks ?? {}) };\n if (op.range) bookmarks[op.name] = op.range;\n else delete bookmarks[op.name];\n return {\n doc: { ...doc, bookmarks },\n inverse: { type: \"setBookmark\", name: op.name, range: old },\n mapPosition: identity,\n dirtyBlockIds: [],\n };\n }\n\n case \"setSectionBand\": {\n const old = doc.section[op.band] ?? null;\n const section = { ...doc.section };\n if (op.blocks) section[op.band] = op.blocks;\n else delete section[op.band];\n const removedIds = new Set(\n (old ?? []).filter((b) => b.kind === \"paragraph\").map((b) => b.id),\n );\n const fallback = doc.blocks.find((b) => b.kind === \"paragraph\");\n return {\n doc: { ...doc, section },\n inverse: { type: \"setSectionBand\", band: op.band, blocks: old },\n mapPosition: (p) => {\n if (!removedIds.has(p.blockId)) return p;\n const kept = (op.blocks ?? []).some((b) => b.id === p.blockId);\n return kept || !fallback ? p : { blockId: fallback.id, offset: 0 };\n },\n dirtyBlockIds: [],\n };\n }\n\n case \"removeTableColumn\": {\n const { where, bi, block } = mustTable(doc, op.tableId);\n const snapshot: Op = { type: \"setTableStructure\", tableId: op.tableId, rows: block.rows };\n if (block.colFractions) snapshot.colFractions = block.colFractions;\n const oldFractions = effectiveFractions(block);\n const removedIds = new Set<string>();\n // Span-aware: a merged cell covering the removed column shrinks; an\n // unmerged cell at that column is dropped.\n const rows = block.rows.map((row) => {\n const cells: TableCell[] = [];\n let col = 0;\n for (const cell of row.cells) {\n const span = cell.colSpan ?? 1;\n if (op.colIndex >= col && op.colIndex < col + span) {\n if (span > 1) {\n const shrunk: TableCell = { ...cell };\n if (span - 1 > 1) shrunk.colSpan = span - 1;\n else delete shrunk.colSpan;\n cells.push(shrunk);\n } else {\n for (const p of cell.blocks) removedIds.add(p.id);\n }\n } else {\n cells.push(cell);\n }\n col += span;\n }\n return { cells };\n });\n const rest = oldFractions.filter((_, i) => i !== op.colIndex);\n const sum = rest.reduce((s, f) => s + f, 0) || 1;\n const fractions = rest.map((f) => f / sum);\n const fallback = firstParagraphInRows(rows);\n return {\n doc: replaceTable(doc, where, bi, { ...block, rows, colFractions: fractions }),\n inverse: snapshot,\n mapPosition: (p) =>\n removedIds.has(p.blockId) && fallback ? { blockId: fallback.id, offset: 0 } : p,\n dirtyBlockIds: [op.tableId],\n };\n }\n }\n}\n", "// Globally-unique id minting for blocks, cells, and other model nodes.\n//\n// In single-user mode any unique string works. For multi-user collaboration two\n// clients must NEVER mint the same id (a collision would make one client's\n// paragraph silently alias another's), so ids are namespaced by a per-session\n// `siteId`:\n//\n// <siteId>-<counter base36> e.g. \"a3f9c1-0\", \"a3f9c1-1\", \u2026\n//\n// The counter is monotonic within a session; the siteId makes the whole space\n// disjoint from every other session. Replaying an op stream is deterministic\n// because generated ids are captured IN the op (splitParagraph.newBlockId,\n// insertBlock.block.id), so the consumer never re-mints them.\n//\n// Randomness lives at the edges (the frontend mints a siteId via crypto), not\n// here \u2014 this module stays pure and deterministic so it is trivially testable.\n\nexport interface IdGenerator {\n readonly siteId: string;\n /** Next unique id in this site's space. */\n next(): string;\n /** Current counter value (for persisting/resuming a session's id space). */\n count(): number;\n}\n\nexport function createIdGenerator(siteId: string, start = 0): IdGenerator {\n let n = start;\n return {\n siteId,\n next: () => `${siteId}-${(n++).toString(36)}`,\n count: () => n,\n };\n}\n\n// A process-wide default generator. The frontend reconfigures it at startup with\n// a real siteId (see configureIds); tests and any unconfigured context get the\n// deterministic \"local\" space.\nlet active: IdGenerator = createIdGenerator(\"local\");\n\n/** Point the default generator at a session's siteId. Call once at startup,\n * before any ids are minted. `start` resumes a prior session's counter. */\nexport function configureIds(siteId: string, start = 0): void {\n active = createIdGenerator(siteId, start);\n}\n\n/** Mint a fresh unique id from the active generator. */\nexport function freshId(): string {\n return active.next();\n}\n\n/** The active session's siteId (tags Changes so the server can order them). */\nexport function currentSiteId(): string {\n return active.siteId;\n}\n", "// Table-of-contents field instruction parsing + generation options. Pure and\r\n// layout-free, so import, export, and the backend route all share it.\r\n//\r\n// The OOXML `TOC` field instruction (e.g. ` TOC \\o \"1-3\" \\h \\z \\u `) governs WHICH\r\n// paragraphs become entries and some behaviors; the visual leader/right-tab live in\r\n// the TOC paragraph styles, not the instruction (see TocOptions).\r\n\r\nimport type { CharStyle, Document, ParaStyle, Paragraph } from \"./model/document\";\r\nimport { bodyParagraphs, textOfRuns } from \"./model/text\";\r\nimport { sliceRuns } from \"./model/ops\";\r\nimport { freshId } from \"./ids\";\r\n\r\n/** Parsed `TOC` field switches. See ECMA-376 \u00A717.16.5.68. */\r\nexport interface TocSwitches {\r\n /** \\o \"1-3\" \u2014 include built-in heading levels in this (inclusive) range. */\r\n outlineRange?: { from: number; to: number };\r\n /** \\u \u2014 also include paragraphs with a directly-applied outline level. */\r\n useOutlineLevels: boolean;\r\n /** \\t \"StyleName,level,\u2026\" \u2014 custom paragraph styles mapped to TOC levels. */\r\n customStyles?: Record<string, number>;\r\n /** \\h \u2014 emit each entry as a hyperlink to its heading. */\r\n hyperlinks: boolean;\r\n /** \\n \"1-3\" \u2014 omit page numbers for levels in this (inclusive) range. */\r\n hidePageNumberRange?: { from: number; to: number };\r\n /** \\p \"sep\" \u2014 text between the entry label and the page number (default tab). */\r\n separator?: string;\r\n /** \\z \u2014 hide tab/leader/page numbers in Web layout view (display-only). */\r\n hideInWeb: boolean;\r\n}\r\n\r\nconst rangeOf = (s: string | undefined): { from: number; to: number } | undefined => {\r\n if (!s) return undefined;\r\n const m = s.match(/^(\\d+)\\s*-\\s*(\\d+)$/);\r\n if (m) return { from: Number(m[1]), to: Number(m[2]) };\r\n const n = s.match(/^\\d+$/) ? Number(s) : NaN;\r\n return Number.isNaN(n) ? undefined : { from: n, to: n };\r\n};\r\n\r\n/** Parse a `TOC` field instruction string into its switches. */\r\nexport function parseTocInstruction(instr: string): TocSwitches {\r\n const sw: TocSwitches = {\r\n useOutlineLevels: /\\\\u\\b/.test(instr),\r\n hyperlinks: /\\\\h\\b/.test(instr),\r\n hideInWeb: /\\\\z\\b/.test(instr),\r\n };\r\n const oRange = rangeOf(instr.match(/\\\\o\\s+\"([^\"]+)\"/)?.[1]);\r\n if (oRange) sw.outlineRange = oRange;\r\n const nRange = rangeOf(instr.match(/\\\\n\\s+\"?([0-9-]+)\"?/)?.[1]);\r\n if (nRange) sw.hidePageNumberRange = nRange;\r\n const sep = instr.match(/\\\\p\\s+\"([^\"]*)\"/)?.[1];\r\n if (sep !== undefined) sw.separator = sep;\r\n const t = instr.match(/\\\\t\\s+\"([^\"]+)\"/)?.[1];\r\n if (t) {\r\n const pairs = t.split(\",\");\r\n const map: Record<string, number> = {};\r\n for (let i = 0; i + 1 < pairs.length; i += 2) {\r\n const name = pairs[i]!.trim();\r\n const lvl = Number(pairs[i + 1]!.trim());\r\n if (name && lvl >= 1) map[name] = lvl;\r\n }\r\n if (Object.keys(map).length > 0) sw.customStyles = map;\r\n }\r\n return sw;\r\n}\r\n\r\n/** Synthesize a `TOC` field instruction from switches (inverse of\r\n * parseTocInstruction; round-trips the switches it models). Switch order follows\r\n * Word's own emit so a re-parse is stable. */\r\nexport function buildTocInstruction(sw: TocSwitches): string {\r\n const parts: string[] = [\"TOC\"];\r\n if (sw.outlineRange) parts.push(`\\\\o \"${sw.outlineRange.from}-${sw.outlineRange.to}\"`);\r\n if (sw.useOutlineLevels) parts.push(\"\\\\u\");\r\n if (sw.customStyles && Object.keys(sw.customStyles).length > 0) {\r\n const t = Object.entries(sw.customStyles).map(([name, lvl]) => `${name},${lvl}`).join(\",\");\r\n parts.push(`\\\\t \"${t}\"`);\r\n }\r\n if (sw.hyperlinks) parts.push(\"\\\\h\");\r\n if (sw.hidePageNumberRange) parts.push(`\\\\n \"${sw.hidePageNumberRange.from}-${sw.hidePageNumberRange.to}\"`);\r\n if (sw.separator !== undefined) parts.push(`\\\\p \"${sw.separator}\"`);\r\n if (sw.hideInWeb) parts.push(\"\\\\z\");\r\n return ` ${parts.join(\" \")} `;\r\n}\r\n\r\n/** Is `level` covered by an inclusive range (undefined range = no). */\r\nexport const inRange = (r: { from: number; to: number } | undefined, level: number): boolean =>\r\n !!r && level >= r.from && level <= r.to;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Heading detection (shared by the editor's TOC generation and the headless\r\n// route) \u2014 robust to opaque style ids: tries the effective outline level, then\r\n// the resolved style NAME (\"Heading 1\"), then a `namedStyle` like \"heading1\",\r\n// then \\t custom-style mappings.\r\n\r\n/** The friendly name of a style id, following basedOn (for opaque numeric ids). */\r\nfunction styleNameOf(doc: Document, id: string | undefined): string | undefined {\r\n if (!id || !doc.stylesheet) return undefined;\r\n const byId = new Map(doc.stylesheet.styles.map((s) => [s.id, s]));\r\n let cur = byId.get(id);\r\n const seen = new Set<string>();\r\n while (cur && !seen.has(cur.id)) {\r\n if (cur.name && /(?:^|\\s)heading\\s*\\d/i.test(cur.name)) return cur.name;\r\n seen.add(cur.id);\r\n cur = cur.basedOn ? byId.get(cur.basedOn) : undefined;\r\n }\r\n return byId.get(id)?.name;\r\n}\r\n\r\n/** TOC level (1-based) for a paragraph, or null if it's not a heading. */\r\nexport function tocHeadingLevel(p: Paragraph, doc: Document, sw?: TocSwitches): number | null {\r\n const named = p.style.namedStyle;\r\n // \\t custom styles (match by opaque id or by friendly name).\r\n if (sw?.customStyles && named) {\r\n const lv = sw.customStyles[named] ?? sw.customStyles[styleNameOf(doc, named) ?? \"\"];\r\n if (lv) return lv;\r\n }\r\n if (p.style.outlineLevel !== undefined) return p.style.outlineLevel + 1;\r\n const direct = named?.match(/^heading\\s*(\\d)$/i);\r\n if (direct) return Number(direct[1]);\r\n const m = styleNameOf(doc, named)?.match(/(?:^|\\s)heading\\s*(\\d)/i);\r\n return m ? Number(m[1]) : null;\r\n}\r\n\r\n/** All TOC-eligible headings in document order (body + table cells), honoring the\r\n * switches' `\\o` range (when present) and `maxLevel`. */\r\nexport function detectTocHeadings(\r\n doc: Document,\r\n sw?: TocSwitches,\r\n maxLevel = 3,\r\n): { block: Paragraph; level: number }[] {\r\n const cap = sw?.outlineRange?.to ?? maxLevel;\r\n const out: { block: Paragraph; level: number }[] = [];\r\n for (const p of bodyParagraphs(doc)) {\r\n const level = tocHeadingLevel(p, doc, sw);\r\n if (level === null || level < 1 || level > cap) continue;\r\n if (sw?.outlineRange && (level < sw.outlineRange.from || level > sw.outlineRange.to)) continue;\r\n out.push({ block: p, level });\r\n }\r\n return out;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Imported-TOC unification: an imported TOC field flattens to plain paragraphs\r\n// (label + \"#anchor\" hyperlink + cached \"\\t<number>\" text). To round-trip it as a\r\n// LIVE field on export, mark those paragraphs as tocEntry and STRIP the cached\r\n// number text \u2014 so the number becomes layout-driven (paint decoration), exactly\r\n// like an editor-created TOC, and export wraps it in a real TOC/PAGEREF field.\r\n\r\n/** Mark a document's imported TOC entries as tocEntry and strip their cached page\r\n * numbers. Gated by the caller (only when a TOC field exists). Returns the count.\r\n * An entry is a body paragraph with a \"#anchor\" link resolving to a bookmarked\r\n * block plus a trailing \"\\t<number>\"; level is inferred from its left indent. */\r\nexport function markImportedTocEntries(doc: Document, indentStepPx = 20): number {\r\n if (!doc.bookmarks) return 0;\r\n const contentWidthPx = doc.section.pageWidthPx - doc.section.marginPx.left - doc.section.marginPx.right;\r\n let count = 0;\r\n for (const b of doc.blocks) {\r\n if (b.kind !== \"paragraph\" || b.style.tocEntry) continue;\r\n const anchor = b.runs.find((r) => r.style.link?.startsWith(\"#\"))?.style.link?.slice(1);\r\n if (!anchor) continue;\r\n const targetId = doc.bookmarks[anchor]?.start.blockId;\r\n if (!targetId) continue;\r\n const full = textOfRuns(b.runs);\r\n const m = full.match(/\\t\\d+\\s*$/); // trailing tab + page number\r\n if (!m) continue;\r\n b.runs = sliceRuns(b.runs, 0, full.length - m[0].length); // drop \"\\t<number>\"\r\n if (b.runs.length === 0) b.runs = [{ text: \"\", style: { ...TOC_BASE_CHAR } }];\r\n const level = Math.min(9, Math.max(1, Math.round((b.style.indentLeftPx || 0) / indentStepPx) + 1));\r\n b.style.tocEntry = { targetId, level };\r\n // Right-aligned dot leader so the painted/exported number aligns (if none set).\r\n if (!b.style.tabStops?.length && contentWidthPx > 0) {\r\n b.style.tabStops = [{ posPx: contentWidthPx, align: \"right\", leader: \"dot\" }];\r\n }\r\n b.revision++;\r\n count++;\r\n }\r\n return count;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Generation options \u2014 everything that used to be hardcoded in\r\n// buildTocParagraphs, exposed so the editor AND the backend route can pass it.\r\n\r\nexport interface TocLevelStyle {\r\n char?: Partial<CharStyle>;\r\n para?: Partial<ParaStyle>;\r\n}\r\n\r\nconst TOC_BASE_CHAR: CharStyle = {\r\n fontFamily: \"Georgia, serif\", fontSizePx: 13, bold: false, italic: false,\r\n underline: false, strikethrough: false, color: \"#202124\",\r\n};\r\n\r\n/** Resolve the char + paragraph style for a TOC entry at `level`. Precedence:\r\n * built-in defaults < the document's own TOC style (`inherit`, from its TOC field\r\n * paragraph) < caller `TocOptions`. When `inherit` is given we DON'T impose the\r\n * built-in level-1 bold / size bumps \u2014 we respect the document's look (matching the\r\n * source Word doc); without it, the editor's default look is used. The para carries\r\n * the right-aligned leader tab stop (callers add `tocEntry`). */\r\nexport function tocEntryStyle(\r\n opts: TocOptions,\r\n level: number,\r\n contentWidthPx: number,\r\n inherit?: { char?: CharStyle | undefined; para?: ParaStyle | undefined },\r\n): { char: CharStyle; para: ParaStyle } {\r\n const indentStep = opts.indentStepPx ?? 20;\r\n const leader = opts.leader ?? \"dot\";\r\n let char: CharStyle;\r\n let para: ParaStyle;\r\n if (inherit && (inherit.char || inherit.para)) {\r\n char = { ...TOC_BASE_CHAR, ...inherit.char, ...opts.baseChar, ...opts.levels?.[level]?.char };\r\n const baseIndent = inherit.para?.indentLeftPx ?? 0;\r\n para = {\r\n align: \"left\", lineHeight: 1.5, spaceBeforePx: 0, spaceAfterPx: 2, indentFirstLinePx: 0,\r\n ...inherit.para,\r\n indentLeftPx: baseIndent + (level - 1) * indentStep,\r\n ...opts.levels?.[level]?.para,\r\n };\r\n } else {\r\n const base = { ...TOC_BASE_CHAR, ...opts.baseChar };\r\n char = { ...base, fontSizePx: level === 1 ? 14 : 13, bold: level === 1, ...opts.levels?.[level]?.char };\r\n para = {\r\n align: \"left\", lineHeight: 1.5, spaceBeforePx: 0, spaceAfterPx: 2,\r\n indentFirstLinePx: 0, indentLeftPx: (level - 1) * indentStep, ...opts.levels?.[level]?.para,\r\n };\r\n }\r\n if (leader !== \"none\" && contentWidthPx > 0) para.tabStops = [{ posPx: contentWidthPx, align: \"right\", leader }];\r\n else delete para.tabStops; // leader:\"none\" overrides any inherited leader tab\r\n return { char, para };\r\n}\r\n\r\n/** The TOC title paragraph's resolved styles, or null when titles are disabled. */\r\nexport function tocTitleStyle(opts: TocOptions): { text: string; char: CharStyle; para: ParaStyle } | null {\r\n if (opts.title === null) return null;\r\n const t = opts.title ?? {};\r\n const base = { ...TOC_BASE_CHAR, ...opts.baseChar };\r\n return {\r\n text: t.text ?? \"Table of Contents\",\r\n char: { ...base, fontSizePx: 20, bold: true, ...t.char },\r\n para: {\r\n align: \"left\", lineHeight: 1.4, spaceBeforePx: 8, spaceAfterPx: 12,\r\n indentFirstLinePx: 0, indentLeftPx: 0, namedStyle: t.namedStyle ?? \"tocTitle\", ...t.para,\r\n },\r\n };\r\n}\r\n\r\nexport interface TocOptions {\r\n /** TOC title paragraph; null/omit to skip a title. */\r\n title?: { text?: string; char?: Partial<CharStyle>; para?: Partial<ParaStyle>; namedStyle?: string } | null;\r\n /** Base char style applied to every entry before per-level overrides. */\r\n baseChar?: Partial<CharStyle>;\r\n /** Per-level (1-based) style overrides. */\r\n levels?: Record<number, TocLevelStyle>;\r\n /** Deepest heading level to include (default 3). */\r\n maxLevel?: number;\r\n /** Per-level left indent step in px (default 20). */\r\n indentStepPx?: number;\r\n /** Dot-leader between label and page number (default \"dot\"). */\r\n leader?: \"dot\" | \"dash\" | \"underscore\" | \"none\";\r\n /** Show page numbers (default true). */\r\n includePageNumbers?: boolean;\r\n /** Emit entries as hyperlinks (default true). */\r\n hyperlink?: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// TOC generation \u2014 entries are REAL paragraphs tagged with `tocEntry` (so they\r\n// lay out, paint, hit-test, and export as a live field for free). Page numbers\r\n// are paint-only: the layout engine resolves each entry's number per relayout,\r\n// so they never go stale. Shared by the editor (insertTocCmd) and the headless\r\n// PDF/DOCX render path.\r\n\r\nconst TOC_LEVELS = 3;\r\n\r\n/** Build TOC paragraphs (optional title + one entry per heading) from a document's\r\n * headings. Every hardcoded look is an overridable `TocOptions` default. The page\r\n * number is paint-only (layout post-pass); a right-aligned dot-leader tab stop\r\n * right-aligns it. Mutates nothing. */\r\nexport function buildTocParagraphs(doc: Document, opts: TocOptions = {}): Paragraph[] {\r\n // Honor the document's own TOC field switches (\\o level range, \\t custom styles, \u2026)\r\n // so a C#-emitted ` TOC \\o \"1-5\" ` lists all five levels, not the default three.\r\n const sw = doc.tocInstruction ? parseTocInstruction(doc.tocInstruction) : undefined;\r\n const maxLevel = opts.maxLevel ?? sw?.outlineRange?.to ?? TOC_LEVELS;\r\n const contentWidthPx = doc.section.pageWidthPx - doc.section.marginPx.left - doc.section.marginPx.right;\r\n\r\n // Inherit the document's own TOC styling from the TOC field's paragraph (its pStyle,\r\n // e.g. \"TOC 1\", resolved on import \u2192 the doc's intended font/size/indent). Absent for\r\n // an editor-inserted TOC in a doc with no TOC field \u2192 the built-in default look.\r\n const anchor = doc.tocAnchorBlockId\r\n ? doc.blocks.find((b): b is Paragraph => b.kind === \"paragraph\" && b.id === doc.tocAnchorBlockId)\r\n : undefined;\r\n const inherit = anchor ? { char: anchor.runs[0]?.style, para: anchor.style } : undefined;\r\n\r\n const out: Paragraph[] = [];\r\n\r\n // Title: an explicit opts.title wins; otherwise add the default title only when we're\r\n // NOT filling an existing TOC field (a doc with a TOC field usually has its own title\r\n // heading already, so a generated one would duplicate it).\r\n const wantTitle = opts.title !== undefined ? opts.title !== null : anchor === undefined;\r\n if (wantTitle) {\r\n const title = tocTitleStyle(opts);\r\n if (title) {\r\n out.push({\r\n kind: \"paragraph\", id: freshId(), revision: 0,\r\n runs: [{ text: title.text, style: title.char }],\r\n style: title.para,\r\n });\r\n }\r\n }\r\n\r\n // Scan body AND table-cell paragraphs so headings inside tables are listed.\r\n for (const { block, level } of detectTocHeadings(doc, sw, maxLevel)) {\r\n const text = textOfRuns(block.runs).replace(/\\v/g, \" \").trim();\r\n if (text.length === 0) continue;\r\n const { char, para } = tocEntryStyle(opts, level, contentWidthPx, inherit);\r\n out.push({\r\n kind: \"paragraph\", id: freshId(), revision: 0,\r\n runs: [{ text, style: char }],\r\n style: { ...para, tocEntry: { targetId: block.id, level } },\r\n });\r\n }\r\n return out;\r\n}\r\n\r\n/** Fill an EMPTY TOC field: build entries from the document's headings and splice\r\n * them at the captured field location (`doc.tocAnchorBlockId`). Page numbers stay\r\n * paint-only \u2014 the layout engine resolves them on render. Returns a NEW doc (input\r\n * not mutated).\r\n *\r\n * If the TOC field ALREADY has entries (imported and marked as `tocEntry`), they are\r\n * PRESERVED untouched \u2014 they carry the source document's own styling and their page\r\n * numbers are recomputed by layout, so rebuilding from headings (with our defaults)\r\n * would only throw the author's TOC away. No headings / nowhere to anchor \u2192 unchanged. */\r\nexport function generateTocIntoDoc(\r\n doc: Document,\r\n opts: TocOptions = {},\r\n): { doc: Document; generated: number; headings: number } {\r\n // Already populated (e.g. a Word/C#-rendered TOC) \u2192 keep it as-is.\r\n if (doc.blocks.some((b) => b.kind === \"paragraph\" && b.style.tocEntry)) {\r\n return { doc, generated: 0, headings: 0 };\r\n }\r\n if (doc.tocAnchorBlockId === undefined) return { doc, generated: 0, headings: 0 };\r\n const at = doc.blocks.findIndex((b) => b.id === doc.tocAnchorBlockId);\r\n if (at < 0) return { doc, generated: 0, headings: 0 };\r\n\r\n const fresh = buildTocParagraphs(doc, opts);\r\n const headings = fresh.reduce((n, p) => n + (p.style.tocEntry ? 1 : 0), 0);\r\n if (headings === 0) return { doc, generated: 0, headings: 0 };\r\n\r\n const blocks = [...doc.blocks];\r\n blocks.splice(at, 1, ...fresh); // replace the empty TOC field paragraph\r\n return { doc: { ...doc, blocks }, generated: headings, headings };\r\n}\r\n", "// Named styles (Word's style gallery). The editor model keeps runs CONCRETE \u2014\n// styles here are templates + references, not a live cascade at render time:\n// - applying a style patches exactly the fields the style defines (direct\n// formatting on other fields survives, like Word),\n// - modifying a style re-patches every paragraph that references it,\n// - resolution walks the basedOn chain (editor-simple override semantics;\n// OOXML XOR-toggle fidelity lives in import/docx/styles.ts, which resolves\n// to concrete values at import time and preserves the styleId reference \u2014\n// the two systems share the same id space).\n\nimport type { CharStyle, Document, ParaStyle, Paragraph } from \"./document\";\nimport { paragraphsOf } from \"./text\";\n\nexport type NamedStyleType = \"paragraph\" | \"character\";\n\nexport interface NamedStyle {\n id: string; // shares the docx styleId space (\"Normal\", \"Heading1\", ...)\n name: string; // display name\n /** Paragraph vs character style (Word's two text-level style kinds). Optional\n * for back-compat: legacy stylesheets and defaultStylesheet() omit it and read\n * as \"paragraph\" via styleType(). Character styles store only `char` (empty\n * `para`) and apply to runs, not paragraphs. */\n type?: NamedStyleType;\n basedOn?: string;\n char: Partial<CharStyle>;\n para: Partial<ParaStyle>;\n}\n\nexport interface Stylesheet {\n styles: NamedStyle[];\n defaultStyleId: string;\n}\n\n/** A style's kind, defaulting untyped (legacy) styles to \"paragraph\". Read this\n * rather than `.type` directly so old documents keep working. */\nexport function styleType(s: NamedStyle): NamedStyleType {\n return s.type ?? \"paragraph\";\n}\n\nexport function styleById(sheet: Stylesheet, id: string): NamedStyle | undefined {\n return sheet.styles.find((s) => s.id === id);\n}\n\n/** Resolved templates: basedOn chain root\u2192leaf, defined fields override. */\nexport function resolveStyle(\n sheet: Stylesheet,\n id: string,\n): { char: Partial<CharStyle>; para: Partial<ParaStyle> } {\n const chain: NamedStyle[] = [];\n const seen = new Set<string>();\n for (let cur = styleById(sheet, id); cur && !seen.has(cur.id); cur = cur.basedOn ? styleById(sheet, cur.basedOn) : undefined) {\n seen.add(cur.id);\n chain.unshift(cur);\n }\n const char: Partial<CharStyle> = {};\n const para: Partial<ParaStyle> = {};\n for (const s of chain) {\n Object.assign(char, definedOnly(s.char));\n Object.assign(para, definedOnly(s.para));\n }\n return { char, para };\n}\n\n/** Resolved character template only: basedOn chain root\u2192leaf, defined `char`\n * fields override. Character styles have no `para`, so this is the run-level\n * equivalent of resolveStyle for type===\"character\" styles. */\nexport function resolveCharStyle(sheet: Stylesheet, id: string): Partial<CharStyle> {\n const chain: NamedStyle[] = [];\n const seen = new Set<string>();\n for (let cur = styleById(sheet, id); cur && !seen.has(cur.id); cur = cur.basedOn ? styleById(sheet, cur.basedOn) : undefined) {\n seen.add(cur.id);\n chain.unshift(cur);\n }\n const char: Partial<CharStyle> = {};\n for (const s of chain) Object.assign(char, definedOnly(s.char));\n return char;\n}\n\n/** Every style id that (transitively) lists `id` as its basedOn ancestor. Used\n * to keep a style out of its own descendants' \"Based on\" pickers. */\nexport function descendantsOf(sheet: Stylesheet, id: string): Set<string> {\n const out = new Set<string>();\n let grew = true;\n while (grew) {\n grew = false;\n for (const s of sheet.styles) {\n if (out.has(s.id)) continue;\n if (s.basedOn !== undefined && (s.basedOn === id || out.has(s.basedOn))) {\n out.add(s.id);\n grew = true;\n }\n }\n }\n return out;\n}\n\n/** Would setting `styleId`'s basedOn to `newBasedOn` create a cycle? True if\n * newBasedOn is the style itself or one of its descendants. A backstop for the\n * UI, which already excludes these from the picker. */\nexport function wouldCycle(sheet: Stylesheet, styleId: string, newBasedOn: string | undefined): boolean {\n if (!newBasedOn) return false;\n if (newBasedOn === styleId) return true;\n return descendantsOf(sheet, styleId).has(newBasedOn);\n}\n\n/** A canonical signature of a style's FORMATTING (everything but id + name):\n * type, basedOn, and its defined char/para deltas with keys sorted \u2014 so two\n * styles that differ only by name (or id) produce the same string. */\nfunction styleSignature(s: NamedStyle): string {\n const norm = (o: object): [string, unknown][] =>\n Object.entries(definedOnly(o)).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return JSON.stringify([styleType(s), s.basedOn ?? \"\", norm(s.char), norm(s.para)]);\n}\n\nexport interface StyleMergeResult {\n sheet: Stylesheet;\n /** mergedId \u2192 survivingId, for every style that was collapsed away. Empty when\n * there were no duplicates. */\n remap: Map<string, string>;\n}\n\n/** Collapse named styles that are identical except for their name/id: each group\n * of same-signature styles keeps ONE survivor (the default style if the group\n * contains it, otherwise the first in document order) and the rest are dropped,\n * their ids remapped to the survivor. basedOn pointers (and defaultStyleId) are\n * rewritten through the remap. Callers remap content references (paragraph\n * namedStyle / run charStyleId) using `remap`. */\nexport function mergeDuplicateNamedStyles(sheet: Stylesheet): StyleMergeResult {\n const remap = new Map<string, string>();\n const survivorBySig = new Map<string, string>();\n for (const s of sheet.styles) {\n const sig = styleSignature(s);\n const survivor = survivorBySig.get(sig);\n if (survivor === undefined) {\n survivorBySig.set(sig, s.id);\n } else if (s.id === sheet.defaultStyleId) {\n // The default style must survive \u2014 promote it and fold the prior survivor.\n survivorBySig.set(sig, s.id);\n remap.set(survivor, s.id);\n for (const [k, v] of remap) if (v === survivor) remap.set(k, s.id);\n } else {\n remap.set(s.id, survivor);\n }\n }\n if (remap.size === 0) return { sheet, remap };\n\n const resolveId = (id: string): string => remap.get(id) ?? id;\n const styles: NamedStyle[] = [];\n for (const s of sheet.styles) {\n if (remap.has(s.id)) continue; // collapsed away\n const next: NamedStyle = { ...s };\n if (next.basedOn !== undefined) {\n const rebased = resolveId(next.basedOn);\n if (rebased === next.id) delete next.basedOn; // self-reference after merge\n else next.basedOn = rebased;\n }\n styles.push(next);\n }\n return { sheet: { styles, defaultStyleId: resolveId(sheet.defaultStyleId) }, remap };\n}\n\nfunction definedOnly<T extends object>(o: T): Partial<T> {\n const out: Partial<T> = {};\n for (const [k, v] of Object.entries(o)) {\n if (v !== undefined) (out as Record<string, unknown>)[k] = v;\n }\n return out;\n}\n\n/** Every editable paragraph referencing a style \u2014 paragraphsOf already covers\n * body, table cells, and both margin bands in document order. */\nexport function paragraphsWithStyle(doc: Document, styleId: string): Paragraph[] {\n return paragraphsOf(doc).filter((p) => p.style.namedStyle === styleId);\n}\n\nexport function defaultStylesheet(): Stylesheet {\n const base: Partial<ParaStyle> = { lineHeight: 1.5, spaceBeforePx: 0, spaceAfterPx: 12 };\n return {\n defaultStyleId: \"Normal\",\n styles: [\n {\n id: \"Normal\",\n name: \"Normal\",\n char: { fontFamily: \"Georgia, serif\", fontSizePx: 16, bold: false, italic: false, color: \"#202124\" },\n para: { ...base, align: \"left\", indentFirstLinePx: 0 },\n },\n {\n id: \"Title\",\n name: \"Title\",\n basedOn: \"Normal\",\n char: { fontFamily: \"Arial, sans-serif\", fontSizePx: 32, bold: true, color: \"#1a1a2e\" },\n para: { align: \"center\", spaceAfterPx: 4 },\n },\n {\n id: \"Subtitle\",\n name: \"Subtitle\",\n basedOn: \"Normal\",\n char: { italic: true, color: \"#5f6368\" },\n para: { align: \"center\", spaceAfterPx: 28 },\n },\n {\n id: \"Heading1\",\n name: \"Heading 1\",\n basedOn: \"Normal\",\n char: { fontFamily: \"Arial, sans-serif\", fontSizePx: 24, bold: true, color: \"#1a1a2e\" },\n para: { spaceBeforePx: 18, spaceAfterPx: 8, keepWithNext: true },\n },\n {\n id: \"Heading2\",\n name: \"Heading 2\",\n basedOn: \"Heading1\",\n char: { fontSizePx: 19 },\n para: { spaceBeforePx: 14, spaceAfterPx: 6 },\n },\n {\n id: \"Quote\",\n name: \"Quote\",\n basedOn: \"Normal\",\n char: { italic: true, color: \"#5f6368\" },\n para: { indentLeftPx: 36, spaceBeforePx: 8, spaceAfterPx: 8 },\n },\n {\n id: \"Code\",\n name: \"Code\",\n basedOn: \"Normal\",\n char: { fontFamily: \"Consolas, monospace\", fontSizePx: 14, color: \"#0b57d0\" },\n para: { lineHeight: 1.35 },\n },\n ],\n };\n}\n", "// Generic OOXML field-instruction parsing. Pure and layout-free, so import,\n// export, and the editor command layer all share it.\n//\n// A \"field\" in OOXML is a region delimited by w:fldChar begin/separate/end runs\n// with a w:instrText instruction (e.g. ` TOC \\o \"1-3\" \\h `, ` PAGEREF _Toc1 \\h `,\n// ` MYCHART \"sales-2026\" `). Word ships a fixed set of BUILT-IN field types it\n// knows how to render; everything else is a developer-defined CUSTOM field whose\n// result the host computes (see Document.fields + the editor's resolveField hook).\n// This module only classifies an instruction and splits out its name + arguments;\n// TOC has its own richer parser in ./toc.\n\n/** A field instruction split into its keyword and arguments. */\nexport interface ParsedFieldInstruction {\n /** Field keyword, uppercased (e.g. \"TOC\", \"MYCHART\"). \"\" when the instruction\n * is blank. */\n name: string;\n /** Whitespace/quote-delimited tokens after the keyword (surrounding double\n * quotes stripped). Switches like `\\o` appear here verbatim. */\n args: string[];\n /** The instruction exactly as given (untrimmed). */\n raw: string;\n}\n\n/** Field keywords Word renders itself \u2014 NOT treated as host-resolvable custom\n * fields. TOC is built-in here too (the editor handles it on its own tocEntry\n * path), so it is never captured as a generic custom field. */\nconst BUILTIN_FIELDS = new Set([\n \"TOC\", \"PAGE\", \"NUMPAGES\", \"PAGEREF\", \"HYPERLINK\", \"REF\", \"SEQ\", \"STYLEREF\",\n \"DATE\", \"TIME\", \"CREATEDATE\", \"SAVEDATE\", \"PRINTDATE\", \"EDITTIME\", \"FILENAME\",\n \"FILESIZE\", \"AUTHOR\", \"LASTSAVEDBY\", \"TITLE\", \"SUBJECT\", \"KEYWORDS\", \"COMMENTS\",\n \"NUMWORDS\", \"NUMCHARS\", \"TOA\", \"INDEX\", \"XE\", \"TC\", \"TA\", \"RD\",\n \"FORMTEXT\", \"FORMCHECKBOX\", \"FORMDROPDOWN\", \"SYMBOL\", \"QUOTE\", \"IF\",\n \"DOCPROPERTY\", \"DOCVARIABLE\", \"MERGEFIELD\", \"ADDRESSBLOCK\", \"GREETINGLINE\",\n \"FILLIN\", \"ASK\", \"SET\", \"GOTOBUTTON\", \"MACROBUTTON\", \"EQ\", \"LISTNUM\",\n \"BIBLIOGRAPHY\", \"CITATION\", \"SECTION\", \"SECTIONPAGES\", \"AUTONUM\", \"AUTONUMLGL\",\n \"AUTONUMOUT\", \"INCLUDETEXT\", \"INCLUDEPICTURE\", \"LINK\", \"NOTEREF\",\n]);\n\n/** Is `name` one of Word's built-in field types (so NOT a custom field)? */\nexport function isBuiltinField(name: string): boolean {\n return BUILTIN_FIELDS.has(name.trim().toUpperCase());\n}\n\n/** Tokenize a field instruction, honoring double-quoted arguments. */\nfunction tokenizeField(instr: string): string[] {\n const out: string[] = [];\n const re = /\"([^\"]*)\"|(\\S+)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(instr)) !== null) out.push(m[1] !== undefined ? m[1] : m[2]!);\n return out;\n}\n\n/** Parse a field instruction into its keyword + arguments. */\nexport function parseFieldInstruction(instr: string): ParsedFieldInstruction {\n const tokens = tokenizeField(instr);\n return { name: tokens.length > 0 ? tokens[0]!.toUpperCase() : \"\", args: tokens.slice(1), raw: instr };\n}\n\n/** Is this instruction a host-resolvable custom field (has a keyword and isn't a\n * Word built-in)? Drives import capture + the editor's \"Update Field\" menu. */\nexport function isCustomFieldInstruction(instr: string): boolean {\n const name = parseFieldInstruction(instr).name;\n return name.length > 0 && !isBuiltinField(name);\n}\n", "// Field evaluation \u2014 pure, layout-free, so import, the editor, and the headless\n// render all agree. Two result shapes:\n// \u2022 \"token\" \u2014 layout-dependent fields (PAGE/NUMPAGES). The result run holds a\n// `{page}`/`{pages}` token the layout engine re-substitutes per page (see\n// engine.ts substituteTokens). The run's CharStyle.fieldId is what marks it a\n// real field vs literal text.\n// \u2022 \"runs\" \u2014 static fields (DATE/TIME/IF), materialized once on insert/update.\n//\n// `spec` \u21C4 `instruction` round-trips via parseFieldSpec / buildInstruction; the\n// verbatim instruction stays the export source of truth (unknown switches survive).\n\nimport type { CharStyle, FieldSpec, IfOp, PageNumFmt, Run } from \"./model/document\";\nimport { parseFieldInstruction, type ParsedFieldInstruction } from \"./fields\";\nimport { textOfRuns } from \"./model/text\";\n\nexport interface FieldEvalCtx {\n /** \"Now\" for DATE/TIME \u2014 passed in so evaluation stays pure/deterministic. */\n now: Date;\n}\n\nexport type FieldEvalResult =\n | { kind: \"runs\"; runs: Run[] }\n | { kind: \"token\"; token: string };\n\nconst MONTHS = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\nconst DAYS = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n\nconst cloneRuns = (runs: Run[]): Run[] => runs.map((r) => ({ text: r.text, style: { ...r.style } }));\n\n/** Token suffix for a page-number format (matches engine.ts `{page:fmt}` grammar). */\nconst numFmtToken = (f: PageNumFmt | undefined): string =>\n f && f !== \"arabic\" ? `:${f}` : \"\";\n\n/** OOXML `\\* <fmt>` switch for a page-number format (matches export fieldInstr). */\nconst numFmtSwitch = (f: PageNumFmt | undefined): string =>\n f === \"roman\" ? \" \\\\* roman\"\n : f === \"Roman\" ? \" \\\\* ROMAN\"\n : f === \"alpha\" ? \" \\\\* alphabetic\"\n : f === \"Alpha\" ? \" \\\\* ALPHABETIC\"\n : \"\";\n\n/** Format a Date with a Word `\\@` picture (subset): yyyy/yy, MMMM/MMM/MM/M,\n * dddd/ddd/dd/d, HH/H, hh/h, mm/m, ss/s, AM/PM, am/pm. `'literal'` passes through. */\nexport function formatFieldDate(d: Date, fmt: string): string {\n const pad = (n: number, w = 2): string => String(n).padStart(w, \"0\");\n const h12 = ((d.getHours() + 11) % 12) + 1;\n let out = \"\";\n let i = 0;\n while (i < fmt.length) {\n if (fmt[i] === \"'\") {\n const end = fmt.indexOf(\"'\", i + 1);\n if (end < 0) { out += fmt.slice(i + 1); break; }\n out += fmt.slice(i + 1, end);\n i = end + 1;\n continue;\n }\n const rest = fmt.slice(i);\n const tok = (t: string): boolean => rest.startsWith(t);\n if (tok(\"yyyy\")) { out += pad(d.getFullYear(), 4); i += 4; }\n else if (tok(\"yy\")) { out += pad(d.getFullYear() % 100); i += 2; }\n else if (tok(\"MMMM\")) { out += MONTHS[d.getMonth()]; i += 4; }\n else if (tok(\"MMM\")) { out += MONTHS[d.getMonth()]!.slice(0, 3); i += 3; }\n else if (tok(\"MM\")) { out += pad(d.getMonth() + 1); i += 2; }\n else if (tok(\"M\")) { out += String(d.getMonth() + 1); i += 1; }\n else if (tok(\"dddd\")) { out += DAYS[d.getDay()]; i += 4; }\n else if (tok(\"ddd\")) { out += DAYS[d.getDay()]!.slice(0, 3); i += 3; }\n else if (tok(\"dd\")) { out += pad(d.getDate()); i += 2; }\n else if (tok(\"d\")) { out += String(d.getDate()); i += 1; }\n else if (tok(\"HH\")) { out += pad(d.getHours()); i += 2; }\n else if (tok(\"H\")) { out += String(d.getHours()); i += 1; }\n else if (tok(\"hh\")) { out += pad(h12); i += 2; }\n else if (tok(\"h\")) { out += String(h12); i += 1; }\n else if (tok(\"mm\")) { out += pad(d.getMinutes()); i += 2; }\n else if (tok(\"m\")) { out += String(d.getMinutes()); i += 1; }\n else if (tok(\"ss\")) { out += pad(d.getSeconds()); i += 2; }\n else if (tok(\"s\")) { out += String(d.getSeconds()); i += 1; }\n else if (tok(\"AM/PM\")) { out += d.getHours() < 12 ? \"AM\" : \"PM\"; i += 5; }\n else if (tok(\"am/pm\")) { out += d.getHours() < 12 ? \"am\" : \"pm\"; i += 5; }\n else { out += fmt[i]; i += 1; }\n }\n return out;\n}\n\n/** Compare two literal operands; numeric when both parse as finite numbers, else string. */\nfunction compare(a: string, op: IfOp, b: string): boolean {\n const na = Number(a);\n const nb = Number(b);\n const numeric = a.trim() !== \"\" && b.trim() !== \"\" && Number.isFinite(na) && Number.isFinite(nb);\n const [x, y]: [number | string, number | string] = numeric ? [na, nb] : [a, b];\n switch (op) {\n case \"=\": return x === y;\n case \"<>\": return x !== y;\n case \"<\": return x < y;\n case \">\": return x > y;\n case \"<=\": return x <= y;\n case \">=\": return x >= y;\n }\n}\n\n/** The chosen branch of an IF field, as fresh (cloned) runs. */\nexport function evaluateIf(spec: Extract<FieldSpec, { type: \"IF\" }>): Run[] {\n return cloneRuns(compare(spec.operandA, spec.op, spec.operandB) ? spec.trueRuns : spec.falseRuns);\n}\n\n/** Evaluate a built-in field to its display result. `baseStyle` styles DATE/TIME text. */\nexport function evaluateField(spec: FieldSpec, baseStyle: CharStyle, ctx: FieldEvalCtx): FieldEvalResult {\n switch (spec.type) {\n case \"PAGE\": return { kind: \"token\", token: `{page${numFmtToken(spec.numFmt)}}` };\n case \"NUMPAGES\": return { kind: \"token\", token: \"{pages}\" };\n case \"DATE\": return { kind: \"runs\", runs: [{ text: formatFieldDate(ctx.now, spec.format), style: { ...baseStyle } }] };\n case \"TIME\": return { kind: \"runs\", runs: [{ text: formatFieldDate(ctx.now, spec.format), style: { ...baseStyle } }] };\n case \"IF\": return { kind: \"runs\", runs: evaluateIf(spec) };\n }\n}\n\n// --- spec \u21C4 instruction --------------------------------------------------------\n\nconst IF_OPS = new Set<string>([\"=\", \"<>\", \"<\", \">\", \"<=\", \">=\"]);\n\nfunction numFmtFromInstr(raw: string): PageNumFmt | undefined {\n if (/\\\\\\*\\s*ROMAN\\b/.test(raw)) return \"Roman\";\n if (/\\\\\\*\\s*roman\\b/.test(raw)) return \"roman\";\n if (/\\\\\\*\\s*ALPHABETIC\\b/.test(raw)) return \"Alpha\";\n if (/\\\\\\*\\s*alphabetic\\b/.test(raw)) return \"alpha\";\n return undefined;\n}\n\n/** The `\\@ \"...\"` (or `\\@ token`) date/time picture from an instruction. */\nfunction atFormat(raw: string): string | undefined {\n return raw.match(/\\\\@\\s*\"([^\"]*)\"/)?.[1] ?? raw.match(/\\\\@\\s*(\\S+)/)?.[1];\n}\n\n/** Parse a built-in field instruction into a typed spec, or undefined (custom /\n * unsupported / unparseable). IF result runs use `baseStyle` (plain text). */\nexport function parseFieldSpec(parsed: ParsedFieldInstruction, baseStyle: CharStyle): FieldSpec | undefined {\n switch (parsed.name) {\n case \"PAGE\": { const f = numFmtFromInstr(parsed.raw); return f ? { type: \"PAGE\", numFmt: f } : { type: \"PAGE\" }; }\n case \"NUMPAGES\": { const f = numFmtFromInstr(parsed.raw); return f ? { type: \"NUMPAGES\", numFmt: f } : { type: \"NUMPAGES\" }; }\n case \"DATE\": return { type: \"DATE\", format: atFormat(parsed.raw) ?? \"M/d/yyyy\" };\n case \"TIME\": return { type: \"TIME\", format: atFormat(parsed.raw) ?? \"h:mm AM/PM\" };\n case \"IF\": {\n const [operandA, op, operandB, trueText, falseText] = parsed.args;\n if (operandA === undefined || op === undefined || operandB === undefined || !IF_OPS.has(op)) return undefined;\n return {\n type: \"IF\", operandA, op: op as IfOp, operandB,\n trueRuns: [{ text: trueText ?? \"\", style: { ...baseStyle } }],\n falseRuns: [{ text: falseText ?? \"\", style: { ...baseStyle } }],\n };\n }\n default: return undefined;\n }\n}\n\n/** Synthesize the verbatim instruction for a typed spec (inverse of parseFieldSpec). */\nexport function buildInstruction(spec: FieldSpec): string {\n switch (spec.type) {\n case \"PAGE\": return ` PAGE${numFmtSwitch(spec.numFmt)} \\\\* MERGEFORMAT `;\n case \"NUMPAGES\": return ` NUMPAGES \\\\* MERGEFORMAT `;\n case \"DATE\": return ` DATE \\\\@ \"${spec.format}\" `;\n case \"TIME\": return ` TIME \\\\@ \"${spec.format}\" `;\n case \"IF\": return ` IF ${spec.operandA} ${spec.op} ${spec.operandB} \"${textOfRuns(spec.trueRuns)}\" \"${textOfRuns(spec.falseRuns)}\" `;\n }\n}\n\n/** Convenience: build a field name from a spec (uppercased keyword). */\nexport function fieldName(spec: FieldSpec): string {\n return spec.type;\n}\n\nexport { parseFieldInstruction };\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -8,7 +8,7 @@ import {
8
8
  FONT_FILES,
9
9
  cloneFamilyFor,
10
10
  setMeasureContext
11
- } from "./chunk-4ICCHZX4.js";
11
+ } from "./chunk-U27EBKBG.js";
12
12
  import {
13
13
  __commonJS,
14
14
  __esm,
@@ -140911,4 +140911,4 @@ export {
140911
140911
  resolveFont,
140912
140912
  installMeasureHost
140913
140913
  };
140914
- //# sourceMappingURL=chunk-BRAPV465.js.map
140914
+ //# sourceMappingURL=chunk-SA26UVIJ.js.map