@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
@@ -111,6 +111,40 @@ var leaderWidth = (fontSizePx) => Math.max(1, fontSizePx / 14);
111
111
  var TOC_LEADER_DASH = [1, 4];
112
112
  var TOC_LEADER_GAP_PX = 8;
113
113
  var FOOTNOTE_RULE_WIDTH_FRACTION = 1 / 3;
114
+ var COLUMN_SEPARATOR_COLOR = "#c0c4c9";
115
+ var PAGE_BORDER_DEFAULT_SPACE = 24;
116
+ function pageBorderSegments(borders, pageW, pageH, margin) {
117
+ const fromText = borders.offsetFrom === "text";
118
+ const sp = (e) => e?.spacePx ?? PAGE_BORDER_DEFAULT_SPACE;
119
+ const xL = fromText ? margin.left - sp(borders.left) : sp(borders.left);
120
+ const xR = fromText ? pageW - margin.right + sp(borders.right) : pageW - sp(borders.right);
121
+ const yT = fromText ? margin.top - sp(borders.top) : sp(borders.top);
122
+ const yB = fromText ? pageH - margin.bottom + sp(borders.bottom) : pageH - sp(borders.bottom);
123
+ const out = [];
124
+ const push = (e, x1, y1, x2, y2) => {
125
+ if (!e || e.style === "none") return;
126
+ const w = e.style === "thick" ? Math.max(2, e.widthPx) : Math.max(0.5, e.widthPx);
127
+ const color = e.color === "auto" ? "#000000" : e.color;
128
+ const dash = e.style === "dashed" ? [w * 3, w * 2] : e.style === "dotted" ? [w, w * 1.5] : [];
129
+ out.push({ x1, y1, x2, y2, widthPx: w, color, dash });
130
+ if (e.style === "double") {
131
+ const g = w + 1;
132
+ const horizontal = y1 === y2;
133
+ if (horizontal) {
134
+ const dy = y1 < pageH / 2 ? g : -g;
135
+ out.push({ x1, y1: y1 + dy, x2, y2: y2 + dy, widthPx: w, color, dash: [] });
136
+ } else {
137
+ const dx = x1 < pageW / 2 ? g : -g;
138
+ out.push({ x1: x1 + dx, y1, x2: x2 + dx, y2, widthPx: w, color, dash: [] });
139
+ }
140
+ }
141
+ };
142
+ push(borders.top, xL, yT, xR, yT);
143
+ push(borders.bottom, xL, yB, xR, yB);
144
+ push(borders.left, xL, yT, xL, yB);
145
+ push(borders.right, xR, yT, xR, yB);
146
+ return out;
147
+ }
114
148
  var cellBorderWidth = (widthPx) => Math.max(0.5, widthPx);
115
149
  function cellBorderDash(style, w) {
116
150
  return style === "dashed" ? [w * 3, w * 2] : style === "dotted" ? [w, w * 1.5] : [];
@@ -175,6 +209,8 @@ export {
175
209
  TOC_LEADER_DASH,
176
210
  TOC_LEADER_GAP_PX,
177
211
  FOOTNOTE_RULE_WIDTH_FRACTION,
212
+ COLUMN_SEPARATOR_COLOR,
213
+ pageBorderSegments,
178
214
  cellBorderWidth,
179
215
  cellBorderDash,
180
216
  doubleBorderGap,
@@ -183,4 +219,4 @@ export {
183
219
  measureTextWidth,
184
220
  fontMetrics
185
221
  };
186
- //# sourceMappingURL=chunk-4ICCHZX4.js.map
222
+ //# sourceMappingURL=chunk-U27EBKBG.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/fonts/clones.ts", "../src/paint/paintStyle.ts", "../src/layout/metrics.ts"],
4
+ "sourcesContent": ["// Single source of truth for font substitution. The editor and both exporters\n// render & measure with these freely-redistributable metric clones instead of\n// the (Windows-only) originals, so layout is identical across the editor, the\n// browser export, and the Node backend \u2014 no system fonts anywhere. The model\n// keeps the original family names; only rendering/measuring/embedding map to a\n// clone. See src/export/shared/fonts/LICENSES.md.\n\n// \"TimesNewRoman\" is the genuine Microsoft Times New Roman (bundled at the user's\n// request), not a clone \u2014 see FONTS.md / fonts/LICENSES.md for the licensing note.\nexport const CLONE_FAMILIES = [\"Carlito\", \"Caladea\", \"Gelasio\", \"Arimo\", \"TimesNewRoman\", \"Cousine\"] as const;\nexport const FALLBACK_CLONE = \"Arimo\";\n\n// Requested family (lowercased, first CSS-stack token) -> bundled clone family.\n// Includes identity entries for the clone names themselves (so already-mapped\n// strings resolve to themselves) and the generic CSS families.\nexport const CLONE_OF: Record<string, string> = {\n // metric clones of common Office/Windows fonts\n calibri: \"Carlito\",\n cambria: \"Caladea\",\n georgia: \"Gelasio\",\n arial: \"Arimo\",\n helvetica: \"Arimo\",\n \"helvetica neue\": \"Arimo\",\n \"times new roman\": \"TimesNewRoman\",\n times: \"TimesNewRoman\",\n \"courier new\": \"Cousine\",\n courier: \"Cousine\",\n // reasonable substitutes for fonts we don't ship\n verdana: \"Arimo\",\n tahoma: \"Arimo\",\n \"segoe ui\": \"Arimo\",\n \"trebuchet ms\": \"Arimo\",\n consolas: \"Cousine\",\n monaco: \"Cousine\",\n \"lucida console\": \"Cousine\",\n garamond: \"TimesNewRoman\",\n \"book antiqua\": \"Caladea\",\n palatino: \"Caladea\",\n // generic CSS families\n serif: \"TimesNewRoman\",\n \"sans-serif\": \"Arimo\",\n sans: \"Arimo\",\n monospace: \"Cousine\",\n mono: \"Cousine\",\n // identity (already a clone)\n carlito: \"Carlito\",\n caladea: \"Caladea\",\n gelasio: \"Gelasio\",\n arimo: \"Arimo\",\n timesnewroman: \"TimesNewRoman\",\n cousine: \"Cousine\",\n};\n\n// Per-clone vertical metrics as a fraction of font size (ascent, descent above/\n// below the baseline). Used by fontMetrics so line heights are computed the SAME\n// way in the editor and both exporters \u2014 otherwise the browser's canvas\n// fontBoundingBox (editor) and fontkit's tables (export) disagree by a hair and\n// pagination drifts by a page. Values measured from the browser's canvas\n// fontBoundingBox at 16px (an exact multiple here), so the editor's appearance is\n// unchanged while the export now matches it on every platform.\nexport const CLONE_METRICS: Record<string, { ascent: number; descent: number }> = {\n Carlito: { ascent: 0.9375, descent: 0.25 },\n Caladea: { ascent: 0.9375, descent: 0.25 },\n Gelasio: { ascent: 0.9375, descent: 0.3125 },\n Arimo: { ascent: 0.875, descent: 0.1875 },\n TimesNewRoman: { ascent: 0.875, descent: 0.1875 },\n Cousine: { ascent: 0.625, descent: 0.1875 },\n};\n\n/** Resolve one CSS-stack family token to its bundled clone family. */\nexport function cloneFamilyFor(family: string): { clone: string; substituted: boolean } {\n const key = family.trim().toLowerCase().replace(/^[\"']|[\"']$/g, \"\");\n const clone = CLONE_OF[key];\n return clone ? { clone, substituted: false } : { clone: FALLBACK_CLONE, substituted: true };\n}\n\n/** First token of a CSS font-family stack (\"Calibri, serif\" -> \"Calibri\"). */\nexport const firstFamilyToken = (stack: string): string => (stack.split(\",\")[0] ?? \"\").trim();\n\nexport const FONT_STYLES = [\"Regular\", \"Bold\", \"Italic\", \"BoldItalic\"] as const;\n\n/** Every bundled face filename. */\nexport const FONT_FILES: string[] = CLONE_FAMILIES.flatMap((fam) =>\n FONT_STYLES.map((style) => `${fam}-${style}.ttf`),\n);\n\n/** Toolbar font list: value is the original family (stored in the model), label\n * shows the clone we actually render \u2014 e.g. \"Calibri (Carlito)\". */\nexport const TOOLBAR_FONTS: { value: string; label: string }[] = [\n { value: \"Calibri, sans-serif\", label: \"Calibri (Carlito)\" },\n { value: \"Cambria, serif\", label: \"Cambria (Caladea)\" },\n { value: \"Georgia, serif\", label: \"Georgia (Gelasio)\" },\n { value: \"Arial, sans-serif\", label: \"Arial (Arimo)\" },\n { value: \"Times New Roman, serif\", label: \"Times New Roman\" },\n { value: \"Verdana, sans-serif\", label: \"Verdana (Arimo)\" },\n { value: \"Consolas, monospace\", label: \"Consolas (Cousine)\" },\n { value: \"Courier New, monospace\", label: \"Courier New (Cousine)\" },\n];\n", "// Shared paint styling \u2014 the constants and per-run styling DECISIONS that the\n// canvas renderer (src/paint/renderer.ts) and the PDF painter\n// (src/export/pdf/paintBlock.ts) must keep in lockstep. Centralising the magic\n// numbers and the link/colour rules stops the two painters from silently\n// drifting; the actual draw calls stay separate (canvas vs pdfkit are different\n// APIs). Pure data + math \u2014 no DOM, no rendering backend.\n\nimport type { CharStyle, PageBorderEdge, PageBorders, TabLeader } from \"@cw/shared\";\n\n// --- colours ---------------------------------------------------------------\n/** Unstyled/native table grid (when a cell carries no explicit borders). */\nexport const DEFAULT_GRID_COLOR = \"#c0c4c9\";\n/** External hyperlinks paint in this blue (Word's affordance). */\nexport const EXTERNAL_LINK_COLOR = \"#0b57d0\";\n/** In-document anchors that inherited a Hyperlink blue read as plain text. */\nexport const ANCHOR_TEXT_COLOR = \"#202124\";\nexport const FOOTNOTE_RULE_COLOR = \"#80868b\";\n/** Non-printing formatting marks (space dots, tab arrows, pilcrows, line breaks)\n * drawn only when \"show formatting marks\" is on. A muted gray-blue, like Word. */\nexport const FORMATTING_MARK_COLOR = \"#7a8aa0\";\nexport const TOC_LEADER_COLOR = \"#9aa0a6\";\nexport const IMAGE_PLACEHOLDER_COLOR = \"#f1f3f4\";\n\n/** Office \"Hyperlink\" character-style blues, normalised to text colour when they\n * arrive on an in-document anchor (TOC/cross-ref) so those read as plain text. */\nexport const HYPERLINK_BLUES = new Set([\n \"#0563c1\", \"#0000ff\", \"#0000ee\", \"#0b57d0\", \"#0066cc\", \"#1155cc\",\n]);\n\n/** A leader/TOC colour that inherited a Hyperlink blue \u2192 text colour. */\nexport const normalizeLinkBlue = (color: string): string =>\n HYPERLINK_BLUES.has(color.toLowerCase()) ? ANCHOR_TEXT_COLOR : color;\n\n// --- sub/superscript -------------------------------------------------------\n/** Sub/superscript is MEASURED and PAINTED at this fraction of the run size\n * (metrics.charStyleToFont scales the font; the painters scale to match). */\nexport const SUB_SUPER_SCALE = 0.65;\nconst SUPER_SHIFT = -0.38;\nconst SUB_SHIFT = 0.16;\n\n/** Baseline shift (px) for a sub/superscript run \u2014 fraction of the ORIGINAL size. */\nexport function verticalShift(vertical: CharStyle[\"verticalAlign\"], fontSizePx: number): number {\n if (vertical === \"super\") return SUPER_SHIFT * fontSizePx;\n if (vertical === \"sub\") return SUB_SHIFT * fontSizePx;\n return 0;\n}\n\n// --- text decorations ------------------------------------------------------\n/** Underline / strikethrough thickness (px). */\nexport const decorationThickness = (fontSizePx: number): number => Math.max(1, fontSizePx / 14);\n/** Underline distance below the baseline (the run's vShift is added by the caller). */\nexport const UNDERLINE_OFFSET_PX = 1.5;\n/** Strikethrough offset relative to the baseline (above it). */\nexport const strikeOffset = (fontSizePx: number): number => -0.28 * fontSizePx;\n\n/** The colour + decoration decision for a run, with link normalisation applied. */\nexport interface RunPaint {\n color: string;\n underline: boolean;\n strike: boolean;\n /** External (non-anchor) link \u2014 paints blue+underlined; the PDF also annotates it. */\n externalLink: boolean;\n}\nexport function runPaint(style: CharStyle): RunPaint {\n const anchor = style.link !== undefined && style.link.startsWith(\"#\");\n const externalLink = style.link !== undefined && !anchor;\n let color = style.color;\n if (externalLink) color = EXTERNAL_LINK_COLOR;\n else if (anchor && HYPERLINK_BLUES.has(style.color.toLowerCase())) color = ANCHOR_TEXT_COLOR;\n return {\n color,\n underline: externalLink || (!!style.underline && !anchor),\n strike: !!style.strikethrough,\n externalLink,\n };\n}\n\n// --- leaders (tab + TOC) ---------------------------------------------------\n/** Tab-leader dash pattern; [] = solid (underscore/none). */\nexport function leaderDash(kind: TabLeader): number[] {\n return kind === \"dot\" ? [1, 3] : kind === \"dash\" ? [4, 3] : [];\n}\nexport const leaderWidth = (fontSizePx: number): number => Math.max(1, fontSizePx / 14);\nexport const TOC_LEADER_DASH: number[] = [1, 4];\n/** Gap (px) between an entry's text/number and its dot leader. */\nexport const TOC_LEADER_GAP_PX = 8;\n\n// --- footnote rule ---------------------------------------------------------\n/** The separator rule spans this fraction of the content width (Word style). */\nexport const FOOTNOTE_RULE_WIDTH_FRACTION = 1 / 3;\n\n// --- column separators -----------------------------------------------------\n/** Thin gray rule drawn between newspaper columns (w:cols/@w:sep). */\nexport const COLUMN_SEPARATOR_COLOR = \"#c0c4c9\";\n\n// --- page borders (w:pgBorders) --------------------------------------------\n/** Default offset (px) of a page border from the page edge / text when an edge\n * omits w:space. ~24pt is Word's typical page-border offset. */\nconst PAGE_BORDER_DEFAULT_SPACE = 24;\n\n/** One stroked line segment of a page border, in page (px) coords. */\nexport interface PageBorderSegment {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n widthPx: number;\n color: string;\n /** [] = solid. */\n dash: number[];\n}\n\n/** Geometry of a page border box \u2192 the line segments to stroke. Shared so the\n * canvas renderer and the PDF painter draw identical borders. \"double\" yields\n * two parallel segments; \"none\"/absent edges are skipped. */\nexport function pageBorderSegments(\n borders: PageBorders,\n pageW: number,\n pageH: number,\n margin: { top: number; right: number; bottom: number; left: number },\n): PageBorderSegment[] {\n const fromText = borders.offsetFrom === \"text\";\n const sp = (e?: PageBorderEdge): number => e?.spacePx ?? PAGE_BORDER_DEFAULT_SPACE;\n // Each edge's distance, then the box bounds (per-edge space; usually uniform).\n const xL = fromText ? margin.left - sp(borders.left) : sp(borders.left);\n const xR = fromText ? pageW - margin.right + sp(borders.right) : pageW - sp(borders.right);\n const yT = fromText ? margin.top - sp(borders.top) : sp(borders.top);\n const yB = fromText ? pageH - margin.bottom + sp(borders.bottom) : pageH - sp(borders.bottom);\n const out: PageBorderSegment[] = [];\n const push = (e: PageBorderEdge | undefined, x1: number, y1: number, x2: number, y2: number): void => {\n if (!e || e.style === \"none\") return;\n const w = e.style === \"thick\" ? Math.max(2, e.widthPx) : Math.max(0.5, e.widthPx);\n const color = e.color === \"auto\" ? \"#000000\" : e.color;\n const dash = e.style === \"dashed\" ? [w * 3, w * 2] : e.style === \"dotted\" ? [w, w * 1.5] : [];\n out.push({ x1, y1, x2, y2, widthPx: w, color, dash });\n if (e.style === \"double\") {\n const g = w + 1; // inset the second line toward the box interior\n const horizontal = y1 === y2;\n if (horizontal) {\n const dy = y1 < pageH / 2 ? g : -g;\n out.push({ x1, y1: y1 + dy, x2, y2: y2 + dy, widthPx: w, color, dash: [] });\n } else {\n const dx = x1 < pageW / 2 ? g : -g;\n out.push({ x1: x1 + dx, y1, x2: x2 + dx, y2, widthPx: w, color, dash: [] });\n }\n }\n };\n push(borders.top, xL, yT, xR, yT);\n push(borders.bottom, xL, yB, xR, yB);\n push(borders.left, xL, yT, xL, yB);\n push(borders.right, xR, yT, xR, yB);\n return out;\n}\n\n// --- cell borders ----------------------------------------------------------\n/** Hairlines clamp up so a thin rule stays visible. */\nexport const cellBorderWidth = (widthPx: number): number => Math.max(0.5, widthPx);\n/** Dash pattern for a cell edge of stroke width w; [] = solid. */\nexport function cellBorderDash(style: string | undefined, w: number): number[] {\n return style === \"dashed\" ? [w * 3, w * 2] : style === \"dotted\" ? [w, w * 1.5] : [];\n}\n/** Offset of the inner stroke of a \"double\" border. */\nexport const doubleBorderGap = (w: number): number => w + 1;\n", "// Vertical font metrics (pretext measures widths; ascent/descent are ours).\n// One hidden canvas, results cached per font string.\n\nimport type { CharStyle } from \"@cw/shared\";\nimport { CLONE_METRICS, cloneFamilyFor, firstFamilyToken } from \"../fonts/clones\";\nimport { SUB_SUPER_SCALE } from \"../paint/paintStyle\";\n\nexport interface FontMetrics {\n ascent: number;\n descent: number;\n}\n\n/** The slice of CanvasRenderingContext2D the layout actually measures through.\n * DOM-free hosts (the export worker, Node backends) inject their own via\n * {@link setMeasureContext}; the browser falls back to a real canvas. */\nexport interface MeasureContext {\n font: string;\n measureText(text: string): {\n width: number;\n fontBoundingBoxAscent: number;\n fontBoundingBoxDescent: number;\n };\n}\n\nlet injectedCtx: MeasureContext | null = null;\nlet browserCtx: MeasureContext | null = null;\n\n/** Install a measurement context (export/Node hosts). Must be called before the\n * engine measures any text. No-op for the browser, which uses a real canvas. */\nexport function setMeasureContext(ctx: MeasureContext): void {\n injectedCtx = ctx;\n}\n\n// Lazily created so importing this module never touches the DOM \u2014 the engine can\n// run under Node once a context is injected (browser path stays byte-identical).\nfunction measureContext(): MeasureContext {\n if (injectedCtx) return injectedCtx;\n if (!browserCtx) {\n browserCtx = document.createElement(\"canvas\").getContext(\"2d\")! as unknown as MeasureContext;\n }\n return browserCtx;\n}\n\nconst metricsCache = new Map<string, FontMetrics>();\n\nexport function charStyleToFont(s: CharStyle): string {\n const italic = s.italic ? \"italic \" : \"\";\n const weight = s.bold ? \"700\" : \"400\";\n // Sub/superscript runs are MEASURED at the scaled size (paint shifts the\n // baseline) \u2014 pretext must measure exactly what paint draws.\n const size = s.verticalAlign ? Math.round(s.fontSizePx * SUB_SUPER_SCALE) : s.fontSizePx;\n // Render & measure with the bundled metric clone (e.g. Calibri -> Carlito), so\n // the editor, the browser export, and the Node backend all lay out identically\n // \u2014 no dependency on system fonts. The model keeps the original family.\n const family = cloneFamilyFor(firstFamilyToken(s.fontFamily)).clone;\n return `${italic}${weight} ${size}px ${family}`;\n}\n\n/** One-off text width at a font \u2014 LAYOUT-time only (paint never measures).\n * Used for paint-only decorations the engine positions (TOC page numbers). */\nexport function measureTextWidth(text: string, font: string): number {\n const ctx = measureContext();\n ctx.font = font;\n return ctx.measureText(text).width;\n}\n\n// Vertical metrics come from baked per-clone ratios (NOT the canvas/fontkit\n// context), so the editor and the exporters compute identical line heights and\n// therefore identical pagination on every platform. charStyleToFont already\n// emits the clone family, so the last token of the font string is it.\nexport function fontMetrics(font: string): FontMetrics {\n let m = metricsCache.get(font);\n if (!m) {\n const sizeMatch = /(\\d+(?:\\.\\d+)?)px/.exec(font);\n const size = sizeMatch ? parseFloat(sizeMatch[1]!) : 16;\n const family = font.slice(font.indexOf(\"px \") + 3).trim();\n const r = CLONE_METRICS[cloneFamilyFor(family).clone] ?? { ascent: 0.9, descent: 0.25 };\n m = { ascent: Math.round(r.ascent * size), descent: Math.round(r.descent * size) };\n metricsCache.set(font, m);\n }\n return m;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -8,13 +8,13 @@ import {
8
8
  createLayoutEngine,
9
9
  effectiveSection,
10
10
  resolveSections
11
- } from "./chunk-RNB336F3.js";
12
- import "./chunk-CDN6WU2P.js";
13
- import "./chunk-4ICCHZX4.js";
11
+ } from "./chunk-T3253N22.js";
12
+ import "./chunk-PIMVQULL.js";
13
+ import "./chunk-U27EBKBG.js";
14
14
  import "./chunk-XSRSBST2.js";
15
15
  export {
16
16
  createLayoutEngine,
17
17
  effectiveSection,
18
18
  resolveSections
19
19
  };
20
- //# sourceMappingURL=engine-5FGDJD2H.js.map
20
+ //# sourceMappingURL=engine-5F7DUU4W.js.map
@@ -16,7 +16,7 @@ import {
16
16
  require_unicode_trie,
17
17
  resolveFont,
18
18
  tslib_es6_exports
19
- } from "./chunk-BRAPV465.js";
19
+ } from "./chunk-SA26UVIJ.js";
20
20
  import {
21
21
  WML_NS,
22
22
  XML_DECL,
@@ -30,8 +30,9 @@ import {
30
30
  pxToEmu,
31
31
  pxToTwips,
32
32
  runPropsXml,
33
+ tableStyleXml,
33
34
  textEl
34
- } from "./chunk-4PDVRJF6.js";
35
+ } from "./chunk-EMY2P524.js";
35
36
  import {
36
37
  pageOfBlockMap
37
38
  } from "./chunk-EGSAVPCC.js";
@@ -41,14 +42,16 @@ import {
41
42
  } from "./chunk-5PUCGN47.js";
42
43
  import {
43
44
  createLayoutEngine
44
- } from "./chunk-RNB336F3.js";
45
+ } from "./chunk-T3253N22.js";
45
46
  import {
46
47
  detectTocHeadings,
47
48
  inRange,
48
49
  parseTocInstruction,
50
+ styleType,
49
51
  textOfRuns
50
- } from "./chunk-CDN6WU2P.js";
52
+ } from "./chunk-PIMVQULL.js";
51
53
  import {
54
+ COLUMN_SEPARATOR_COLOR,
52
55
  DEFAULT_GRID_COLOR,
53
56
  FOOTNOTE_RULE_COLOR,
54
57
  FOOTNOTE_RULE_WIDTH_FRACTION,
@@ -65,10 +68,11 @@ import {
65
68
  leaderDash,
66
69
  leaderWidth,
67
70
  normalizeLinkBlue,
71
+ pageBorderSegments,
68
72
  runPaint,
69
73
  strikeOffset,
70
74
  verticalShift
71
- } from "./chunk-4ICCHZX4.js";
75
+ } from "./chunk-U27EBKBG.js";
72
76
  import {
73
77
  __commonJS,
74
78
  __require,
@@ -24545,7 +24549,7 @@ function toBuffer(bytes) {
24545
24549
  var PT = 72 / 96;
24546
24550
  async function renderPdf(doc, opts = {}) {
24547
24551
  await installMeasureHost();
24548
- const { createLayoutEngine: createLayoutEngine2 } = await import("./engine-5FGDJD2H.js");
24552
+ const { createLayoutEngine: createLayoutEngine2 } = await import("./engine-5F7DUU4W.js");
24549
24553
  const tree = createLayoutEngine2().layout(doc);
24550
24554
  const warnings = new WarningSink();
24551
24555
  const images = opts.images ?? {};
@@ -24600,8 +24604,23 @@ async function renderPdf(doc, opts = {}) {
24600
24604
  }
24601
24605
  pdf.save();
24602
24606
  pdf.scale(PT);
24603
- pdf.rect(0, 0, page.widthPx, page.heightPx).fill("#ffffff");
24607
+ pdf.rect(0, 0, page.widthPx, page.heightPx).fill(page.pageColorHex ?? "#ffffff");
24608
+ if (page.pageBorders) {
24609
+ for (const s of pageBorderSegments(page.pageBorders, page.widthPx, page.heightPx, page.marginPx)) {
24610
+ pdf.lineWidth(s.widthPx).strokeColor(s.color);
24611
+ if (s.dash.length) pdf.dash(s.dash[0], { space: s.dash[1] ?? s.dash[0] });
24612
+ else pdf.undash();
24613
+ pdf.moveTo(s.x1, s.y1).lineTo(s.x2, s.y2).stroke();
24614
+ }
24615
+ pdf.undash();
24616
+ }
24604
24617
  for (const block of page.blocks) paintBlock(ctx, block);
24618
+ if (page.columnSeparatorsX) {
24619
+ pdf.undash().lineWidth(1).strokeColor(COLUMN_SEPARATOR_COLOR);
24620
+ for (const x of page.columnSeparatorsX) {
24621
+ pdf.moveTo(x + 0.5, page.contentTopPx).lineTo(x + 0.5, page.contentBottomPx).stroke();
24622
+ }
24623
+ }
24605
24624
  if (page.footnoteRuleY !== void 0) {
24606
24625
  const cw = page.widthPx - page.marginPx.left - page.marginPx.right;
24607
24626
  pdf.undash().lineWidth(1).strokeColor(FOOTNOTE_RULE_COLOR);
@@ -24944,13 +24963,25 @@ function tableXml(table, ctx) {
24944
24963
  col = emitContinues(col, out);
24945
24964
  rowsXml.push(el("w:tr", void 0, out.join("")));
24946
24965
  }
24966
+ const styleRef = table.styleId ? el("w:tblStyle", { "w:val": table.styleId }) : "";
24967
+ const look = table.styleId && table.condOverrides ? tblLookXml(table.condOverrides) : "";
24947
24968
  const tblPr = el(
24948
24969
  "w:tblPr",
24949
24970
  void 0,
24950
- el("w:tblW", { "w:w": 0, "w:type": "auto" }) + el("w:tblLayout", { "w:type": "fixed" })
24971
+ styleRef + el("w:tblW", { "w:w": 0, "w:type": "auto" }) + el("w:tblLayout", { "w:type": "fixed" }) + look
24951
24972
  );
24952
24973
  return el("w:tbl", void 0, tblPr + el("w:tblGrid", void 0, grid) + rowsXml.join(""));
24953
24974
  }
24975
+ function tblLookXml(o) {
24976
+ return el("w:tblLook", {
24977
+ "w:firstRow": o.firstRow ? "1" : "0",
24978
+ "w:lastRow": o.lastRow ? "1" : "0",
24979
+ "w:firstColumn": o.firstCol ? "1" : "0",
24980
+ "w:lastColumn": o.lastCol ? "1" : "0",
24981
+ "w:noHBand": o.bandRows ? "0" : "1",
24982
+ "w:noVBand": o.bandCols ? "0" : "1"
24983
+ });
24984
+ }
24954
24985
  function blockXml(block, ctx) {
24955
24986
  if (block.kind === "paragraph") return paragraphXml(block, ctx);
24956
24987
  if (block.kind === "table") return tableXml(block, ctx);
@@ -25077,6 +25108,20 @@ function imageParagraphXml(img, ctx) {
25077
25108
  const align = img.align === "center" ? "center" : img.align === "right" ? "right" : "left";
25078
25109
  return el("w:p", void 0, el("w:pPr", void 0, el("w:jc", { "w:val": align })) + el("w:r", void 0, drawing));
25079
25110
  }
25111
+ function pgBordersXml(b) {
25112
+ const edge = (name, e) => {
25113
+ if (!e || e.style === "none") return "";
25114
+ const spacePt = Math.min(31, Math.max(0, Math.round((e.spacePx ?? 0) * (72 / 96))));
25115
+ return el("w:" + name, {
25116
+ "w:val": e.style,
25117
+ "w:sz": pxToEighthPoints(e.widthPx),
25118
+ "w:space": spacePt,
25119
+ "w:color": e.color === "auto" ? "auto" : hexColor(e.color)
25120
+ });
25121
+ };
25122
+ const inner = edge("top", b.top) + edge("left", b.left) + edge("bottom", b.bottom) + edge("right", b.right);
25123
+ return el("w:pgBorders", { "w:offsetFrom": b.offsetFrom ?? "page" }, inner);
25124
+ }
25080
25125
  function sectPrXml(s, ctx, addBand, isBody) {
25081
25126
  const c = [];
25082
25127
  const band = (blocks, kind, type) => {
@@ -25092,7 +25137,12 @@ function sectPrXml(s, ctx, addBand, isBody) {
25092
25137
  band(s.footerEven, "footer", "even");
25093
25138
  if (s.headerFirst || s.footerFirst) c.push(el("w:titlePg"));
25094
25139
  if (s.pageWidthPx !== void 0 && s.pageHeightPx !== void 0) {
25095
- c.push(el("w:pgSz", { "w:w": pxToTwips(s.pageWidthPx), "w:h": pxToTwips(s.pageHeightPx) }));
25140
+ const pgSz = {
25141
+ "w:w": pxToTwips(s.pageWidthPx),
25142
+ "w:h": pxToTwips(s.pageHeightPx)
25143
+ };
25144
+ if (s.pageWidthPx > s.pageHeightPx) pgSz["w:orient"] = "landscape";
25145
+ c.push(el("w:pgSz", pgSz));
25096
25146
  }
25097
25147
  if (s.marginPx) {
25098
25148
  const m = s.marginPx;
@@ -25101,13 +25151,23 @@ function sectPrXml(s, ctx, addBand, isBody) {
25101
25151
  "w:right": pxToTwips(m.right),
25102
25152
  "w:bottom": pxToTwips(m.bottom),
25103
25153
  "w:left": pxToTwips(m.left),
25104
- "w:header": 720,
25105
- "w:footer": 720
25154
+ "w:header": s.headerDistancePx !== void 0 ? pxToTwips(s.headerDistancePx) : 720,
25155
+ "w:footer": s.footerDistancePx !== void 0 ? pxToTwips(s.footerDistancePx) : 720
25106
25156
  }));
25107
25157
  }
25108
25158
  if (s.columns && s.columns.count > 1) {
25109
- c.push(el("w:cols", { "w:num": s.columns.count, "w:space": pxToTwips(s.columns.gapPx) }));
25159
+ const cols = s.columns;
25160
+ const attrs = { "w:num": cols.count, "w:space": pxToTwips(cols.gapPx) };
25161
+ if (cols.sep) attrs["w:sep"] = "1";
25162
+ if (cols.cols && cols.cols.length === cols.count) {
25163
+ attrs["w:equalWidth"] = "0";
25164
+ const inner = cols.cols.map((col) => el("w:col", { "w:w": pxToTwips(col.widthPx), "w:space": pxToTwips(col.spaceAfterPx) })).join("");
25165
+ c.push(el("w:cols", attrs, inner));
25166
+ } else {
25167
+ c.push(el("w:cols", attrs));
25168
+ }
25110
25169
  }
25170
+ if (s.pageBorders) c.push(pgBordersXml(s.pageBorders));
25111
25171
  if (s.pageNumberStart !== void 0) c.push(el("w:pgNumType", { "w:start": s.pageNumberStart }));
25112
25172
  if (!isBody) c.push(el("w:type", { "w:val": "nextPage" }));
25113
25173
  return el("w:sectPr", void 0, c.join(""));
@@ -25132,7 +25192,8 @@ function buildDocumentXml(blocks, section, ctx, addBand) {
25132
25192
  }
25133
25193
  }
25134
25194
  const body = parts.join("") + sectPrXml(section, ctx, addBand, true);
25135
- return XML_DECL + el("w:document", WML_NS, el("w:body", void 0, body));
25195
+ const background = section.pageColorHex !== void 0 ? el("w:background", { "w:color": hexColor(section.pageColorHex) }) : "";
25196
+ return XML_DECL + el("w:document", WML_NS, background + el("w:body", void 0, body));
25136
25197
  }
25137
25198
 
25138
25199
  // src/export/docx/contentTypes.ts
@@ -25240,20 +25301,22 @@ function numberingXml(lists, listIdMap) {
25240
25301
  }
25241
25302
 
25242
25303
  // src/export/docx/settingsXml.ts
25243
- function settingsXml(evenAndOdd) {
25244
- const body = evenAndOdd ? el("w:evenAndOddHeadersAndFooters") : "";
25304
+ function settingsXml(evenAndOdd, displayBackgroundShape = false) {
25305
+ const body = (displayBackgroundShape ? el("w:displayBackgroundShape") : "") + (evenAndOdd ? el("w:evenAndOddHeadersAndFooters") : "");
25245
25306
  return XML_DECL + el("w:settings", WML_NS, body);
25246
25307
  }
25247
25308
 
25248
25309
  // src/export/docx/stylesXml.ts
25249
- function stylesXml(sheet) {
25250
- const styles = sheet.styles.map((s) => {
25251
- const attrs = { "w:type": "paragraph", "w:styleId": s.id };
25252
- if (s.id === sheet.defaultStyleId) attrs["w:default"] = "1";
25253
- const body = el("w:name", { "w:val": s.name }) + (s.basedOn ? el("w:basedOn", { "w:val": s.basedOn }) : "") + partialPPrXml(s.para) + partialRPrXml(s.char);
25310
+ function stylesXml(sheet, tableStyles) {
25311
+ const named = (sheet?.styles ?? []).map((s) => {
25312
+ const isChar = styleType(s) === "character";
25313
+ const attrs = { "w:type": isChar ? "character" : "paragraph", "w:styleId": s.id };
25314
+ if (!isChar && sheet && s.id === sheet.defaultStyleId) attrs["w:default"] = "1";
25315
+ const body = el("w:name", { "w:val": s.name }) + (s.basedOn ? el("w:basedOn", { "w:val": s.basedOn }) : "") + (isChar ? "" : partialPPrXml(s.para)) + partialRPrXml(s.char);
25254
25316
  return el("w:style", attrs, body);
25255
25317
  }).join("");
25256
- return XML_DECL + el("w:styles", WML_NS, styles);
25318
+ const tables = Object.values(tableStyles ?? {}).map(tableStyleXml).join("");
25319
+ return XML_DECL + el("w:styles", WML_NS, named + tables);
25257
25320
  }
25258
25321
 
25259
25322
  // src/export/docx/writeDocx.ts
@@ -25343,8 +25406,10 @@ function writeDocx(doc, images = {}, tocPages) {
25343
25406
  return bodyRels.add(kind === "header" ? REL.header : REL.footer, file);
25344
25407
  };
25345
25408
  parts["word/document.xml"] = buildDocumentXml(doc.blocks, doc.section, bodyCtx, addBand);
25346
- if (doc.stylesheet && doc.stylesheet.styles.length > 0) {
25347
- parts["word/styles.xml"] = stylesXml(doc.stylesheet);
25409
+ const hasNamed = !!doc.stylesheet && doc.stylesheet.styles.length > 0;
25410
+ const hasTableStyles = !!doc.tableStyles && Object.keys(doc.tableStyles).length > 0;
25411
+ if (hasNamed || hasTableStyles) {
25412
+ parts["word/styles.xml"] = stylesXml(doc.stylesheet, doc.tableStyles);
25348
25413
  overrides.push(["/word/styles.xml", CT.styles]);
25349
25414
  bodyRels.add(REL.styles, "styles.xml");
25350
25415
  }
@@ -25359,7 +25424,7 @@ function writeDocx(doc, images = {}, tocPages) {
25359
25424
  bodyRels.add(REL.footnotes, "footnotes.xml");
25360
25425
  }
25361
25426
  const evenAndOdd = sectionsHaveEvenBands(doc);
25362
- parts["word/settings.xml"] = settingsXml(evenAndOdd);
25427
+ parts["word/settings.xml"] = settingsXml(evenAndOdd, doc.section.pageColorHex !== void 0);
25363
25428
  overrides.push(["/word/settings.xml", CT.settings]);
25364
25429
  bodyRels.add(REL.settings, "settings.xml");
25365
25430
  for (const [path, bytes2] of bodyMedia.parts()) parts[`word/${path}`] = bytes2;