@json-to-office/core-docx 7.0.1 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/blocks/document.d.ts.map +1 -1
  2. package/dist/components/highcharts.d.ts.map +1 -1
  3. package/dist/core/layout.d.ts.map +1 -1
  4. package/dist/core/tableModel.d.ts +10 -8
  5. package/dist/core/tableModel.d.ts.map +1 -1
  6. package/dist/index.js +550 -272
  7. package/dist/index.js.map +1 -1
  8. package/dist/ir/compiler.d.ts.map +1 -1
  9. package/dist/ir/measure.d.ts +44 -0
  10. package/dist/ir/measure.d.ts.map +1 -0
  11. package/dist/ir/types.d.ts +23 -7
  12. package/dist/ir/types.d.ts.map +1 -1
  13. package/dist/ir/units.d.ts +12 -0
  14. package/dist/ir/units.d.ts.map +1 -1
  15. package/dist/ir/validation.d.ts +3 -2
  16. package/dist/ir/validation.d.ts.map +1 -1
  17. package/dist/plugin/example/index.js +550 -272
  18. package/dist/plugin/example/index.js.map +1 -1
  19. package/dist/quality/facts.d.ts.map +1 -1
  20. package/dist/quality/text-inventory.d.ts +13 -3
  21. package/dist/quality/text-inventory.d.ts.map +1 -1
  22. package/dist/renderers/docxjs/emit.d.ts.map +1 -1
  23. package/dist/renderers/docxjs/styles.d.ts.map +1 -1
  24. package/dist/renderers/office-open/cellTocs.d.ts +26 -0
  25. package/dist/renderers/office-open/cellTocs.d.ts.map +1 -0
  26. package/dist/renderers/office-open/emit.d.ts +36 -0
  27. package/dist/renderers/office-open/emit.d.ts.map +1 -1
  28. package/dist/renderers/office-open/index.d.ts +2 -1
  29. package/dist/renderers/office-open/index.d.ts.map +1 -1
  30. package/dist/renderers/office-open/styles.d.ts.map +1 -1
  31. package/dist/styles/themeToStyles.d.ts +1 -1
  32. package/dist/styles/themeToStyles.d.ts.map +1 -1
  33. package/dist/styles/utils/layoutUtils.d.ts +3 -0
  34. package/dist/styles/utils/layoutUtils.d.ts.map +1 -1
  35. package/dist/styles/utils/styleHelpers.d.ts +3 -1
  36. package/dist/styles/utils/styleHelpers.d.ts.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/utils/widthUtils.d.ts +9 -1
  39. package/dist/utils/widthUtils.d.ts.map +1 -1
  40. package/package.json +3 -3
  41. package/dist/styles/utils/borderUtils.d.ts +0 -95
  42. package/dist/styles/utils/borderUtils.d.ts.map +0 -1
@@ -1804,15 +1804,15 @@ function convertLineSpacing(lineSpacing) {
1804
1804
  lineRule2 = "auto";
1805
1805
  break;
1806
1806
  case "atLeast":
1807
- line = value !== void 0 ? value * TWIPS_PER_POINT : void 0;
1807
+ line = value !== void 0 ? pointsToTwips(value) : void 0;
1808
1808
  lineRule2 = "atLeast";
1809
1809
  break;
1810
1810
  case "exactly":
1811
- line = value !== void 0 ? value * TWIPS_PER_POINT : void 0;
1811
+ line = value !== void 0 ? pointsToTwips(value) : void 0;
1812
1812
  lineRule2 = "exact";
1813
1813
  break;
1814
1814
  case "multiple":
1815
- line = value !== void 0 ? value * SINGLE_LINE_SPACING_TWIPS : void 0;
1815
+ line = value !== void 0 ? Math.round(value * SINGLE_LINE_SPACING_TWIPS) : void 0;
1816
1816
  lineRule2 = "auto";
1817
1817
  break;
1818
1818
  default:
@@ -2008,6 +2008,7 @@ var init_layoutUtils = __esm({
2008
2008
  if (themeConfig?.page) {
2009
2009
  const margins = themeConfig.page.margins || {};
2010
2010
  const dimensions = getPageDimensions(themeConfig.page.size);
2011
+ const gutter = margins.gutter;
2011
2012
  return {
2012
2013
  size: {
2013
2014
  width: dimensions.width,
@@ -2018,7 +2019,13 @@ var init_layoutUtils = __esm({
2018
2019
  top: margins.top ?? defaultMargins.top ?? 1440,
2019
2020
  right: margins.right ?? defaultMargins.right ?? 1440,
2020
2021
  bottom: margins.bottom ?? defaultMargins.bottom ?? 1440,
2021
- left: margins.left ?? defaultMargins.left ?? 1440
2022
+ left: margins.left ?? defaultMargins.left ?? 1440,
2023
+ // How far the running head and foot sit from the page edge. Left out,
2024
+ // each renderer writes its own default — docx.js 708, office-open 851
2025
+ // and 992 — in place of the distance the theme states.
2026
+ header: margins.header ?? defaultMargins.header ?? 720,
2027
+ footer: margins.footer ?? defaultMargins.footer ?? 720,
2028
+ ...gutter !== void 0 && { gutter }
2022
2029
  }
2023
2030
  };
2024
2031
  }
@@ -2034,50 +2041,15 @@ var init_layoutUtils = __esm({
2034
2041
  top: defaultMargins.top ?? 1440,
2035
2042
  right: defaultMargins.right ?? 1440,
2036
2043
  bottom: defaultMargins.bottom ?? 1440,
2037
- left: defaultMargins.left ?? 1440
2044
+ left: defaultMargins.left ?? 1440,
2045
+ header: defaultMargins.header ?? 720,
2046
+ footer: defaultMargins.footer ?? 720
2038
2047
  }
2039
2048
  };
2040
2049
  };
2041
2050
  }
2042
2051
  });
2043
2052
 
2044
- // src/utils/generationContext.ts
2045
- import { AsyncLocalStorage } from "async_hooks";
2046
- import { isAbsolute, resolve } from "path";
2047
- function runWithBaseDir(baseDir, callback) {
2048
- return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
2049
- }
2050
- function resolveFromBaseDir(filePath) {
2051
- const base = baseDirStorage.getStore();
2052
- if (!base || isAbsolute(filePath)) return filePath;
2053
- return resolve(base, filePath);
2054
- }
2055
- function runWithWarnings(warnings, callback) {
2056
- return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
2057
- }
2058
- function reportWarning(component, code, message, context) {
2059
- const warnings = warningsStorage.getStore();
2060
- if (warnings) {
2061
- warnings.push({
2062
- component,
2063
- message,
2064
- severity: "warning",
2065
- context: { code, ...context }
2066
- });
2067
- return;
2068
- }
2069
- console.warn(`[json-to-docx] [${code}] ${message}`);
2070
- }
2071
- var generationDateStorage, baseDirStorage, warningsStorage;
2072
- var init_generationContext = __esm({
2073
- "src/utils/generationContext.ts"() {
2074
- "use strict";
2075
- generationDateStorage = new AsyncLocalStorage();
2076
- baseDirStorage = new AsyncLocalStorage();
2077
- warningsStorage = new AsyncLocalStorage();
2078
- }
2079
- });
2080
-
2081
2053
  // src/themes/json/parser.ts
2082
2054
  import { validateThemeJson } from "@json-to-office/shared-docx/validation/unified";
2083
2055
  var ThemeValidationError, ThemeParseError, ThemeParser;
@@ -2388,7 +2360,8 @@ function getAvailableWidthTwips(theme, themeName) {
2388
2360
  const width = page?.size?.width || 0;
2389
2361
  const left = page?.margin?.left || 0;
2390
2362
  const right = page?.margin?.right || 0;
2391
- return Math.max(0, width - left - right);
2363
+ const gutter = page?.margin?.gutter || 0;
2364
+ return Math.max(0, width - left - right - gutter);
2392
2365
  }
2393
2366
  function getPageWidthTwips(theme, themeName) {
2394
2367
  const page = getPageSetup(theme, themeName);
@@ -2423,6 +2396,13 @@ function resolveOffsetTwips(value, referenceTwips) {
2423
2396
  }
2424
2397
  return Math.round(referenceTwips * parseFloat(match[1]) / 100);
2425
2398
  }
2399
+ function splitTwips(count, totalTwips) {
2400
+ const edge = (index) => Math.round(totalTwips * index / count);
2401
+ return Array.from(
2402
+ { length: count },
2403
+ (_, index) => edge(index + 1) - edge(index)
2404
+ );
2405
+ }
2426
2406
  function relativeLengthToTwips(value, availableWidthTwips) {
2427
2407
  if (typeof value === "number") {
2428
2408
  return pointsToTwips(value);
@@ -2439,6 +2419,43 @@ var init_widthUtils = __esm({
2439
2419
  }
2440
2420
  });
2441
2421
 
2422
+ // src/utils/generationContext.ts
2423
+ import { AsyncLocalStorage } from "async_hooks";
2424
+ import { isAbsolute, resolve } from "path";
2425
+ function runWithBaseDir(baseDir, callback) {
2426
+ return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
2427
+ }
2428
+ function resolveFromBaseDir(filePath) {
2429
+ const base = baseDirStorage.getStore();
2430
+ if (!base || isAbsolute(filePath)) return filePath;
2431
+ return resolve(base, filePath);
2432
+ }
2433
+ function runWithWarnings(warnings, callback) {
2434
+ return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
2435
+ }
2436
+ function reportWarning(component, code, message, context) {
2437
+ const warnings = warningsStorage.getStore();
2438
+ if (warnings) {
2439
+ warnings.push({
2440
+ component,
2441
+ message,
2442
+ severity: "warning",
2443
+ context: { code, ...context }
2444
+ });
2445
+ return;
2446
+ }
2447
+ console.warn(`[json-to-docx] [${code}] ${message}`);
2448
+ }
2449
+ var generationDateStorage, baseDirStorage, warningsStorage;
2450
+ var init_generationContext = __esm({
2451
+ "src/utils/generationContext.ts"() {
2452
+ "use strict";
2453
+ generationDateStorage = new AsyncLocalStorage();
2454
+ baseDirStorage = new AsyncLocalStorage();
2455
+ warningsStorage = new AsyncLocalStorage();
2456
+ }
2457
+ });
2458
+
2442
2459
  // src/utils/imageUtils.ts
2443
2460
  import { readFileSync } from "fs";
2444
2461
  import { availableParallelism } from "os";
@@ -2734,7 +2751,7 @@ var init_imageUtils = __esm({
2734
2751
  });
2735
2752
 
2736
2753
  // src/ir/types.ts
2737
- var TWIPS_PER_INCH, TWIPS_PER_POINT2, EMU_PER_INCH, EMU_PER_TWIP, DOCX_IR_SCHEMA_VERSION;
2754
+ var TWIPS_PER_INCH, TWIPS_PER_POINT2, EMU_PER_INCH, EMU_PER_TWIP, DOCX_IR_SCHEMA_VERSION, DOCX_IR_BORDER_STYLES;
2738
2755
  var init_types = __esm({
2739
2756
  "src/ir/types.ts"() {
2740
2757
  "use strict";
@@ -2743,6 +2760,35 @@ var init_types = __esm({
2743
2760
  EMU_PER_INCH = 914400;
2744
2761
  EMU_PER_TWIP = EMU_PER_INCH / TWIPS_PER_INCH;
2745
2762
  DOCX_IR_SCHEMA_VERSION = 1;
2763
+ DOCX_IR_BORDER_STYLES = [
2764
+ "single",
2765
+ "dashDotStroked",
2766
+ "dashed",
2767
+ "dashSmallGap",
2768
+ "dotDash",
2769
+ "dotDotDash",
2770
+ "dotted",
2771
+ "double",
2772
+ "doubleWave",
2773
+ "inset",
2774
+ "nil",
2775
+ "none",
2776
+ "outset",
2777
+ "thick",
2778
+ "thickThinLargeGap",
2779
+ "thickThinMediumGap",
2780
+ "thickThinSmallGap",
2781
+ "thinThickLargeGap",
2782
+ "thinThickMediumGap",
2783
+ "thinThickSmallGap",
2784
+ "thinThickThinLargeGap",
2785
+ "thinThickThinMediumGap",
2786
+ "thinThickThinSmallGap",
2787
+ "threeDEmboss",
2788
+ "threeDEngrave",
2789
+ "triple",
2790
+ "wave"
2791
+ ];
2746
2792
  }
2747
2793
  });
2748
2794
 
@@ -2751,6 +2797,9 @@ import { createHash as createHash2 } from "crypto";
2751
2797
  function pointsToTwips2(points) {
2752
2798
  return Math.round(points * TWIPS_PER_POINT2);
2753
2799
  }
2800
+ function wholeTwips(twips2) {
2801
+ return Math.round(twips2);
2802
+ }
2754
2803
  function pointsToHalfPoints(points) {
2755
2804
  return Math.round(points * 2);
2756
2805
  }
@@ -2766,8 +2815,8 @@ function inchesToEmu(inches) {
2766
2815
  function pointsToEmu(points) {
2767
2816
  return Math.round(points * EMU_PER_INCH / 72);
2768
2817
  }
2769
- function twipsToEmu(twips) {
2770
- return Math.round(twips * EMU_PER_TWIP);
2818
+ function twipsToEmu(twips2) {
2819
+ return Math.round(twips2 * EMU_PER_TWIP);
2771
2820
  }
2772
2821
  function pixelsToEmu(pixels) {
2773
2822
  return Math.round(pixels * 9525);
@@ -2775,8 +2824,8 @@ function pixelsToEmu(pixels) {
2775
2824
  function emuToPixels(emu) {
2776
2825
  return emu / 9525;
2777
2826
  }
2778
- function twipsToPixels(twips) {
2779
- return twips / TWIPS_PER_INCH * 96;
2827
+ function twipsToPixels(twips2) {
2828
+ return twips2 / TWIPS_PER_INCH * 96;
2780
2829
  }
2781
2830
  function irColor(hex) {
2782
2831
  return { hex: hex.startsWith("#") ? hex.slice(1) : hex };
@@ -2802,7 +2851,6 @@ import {
2802
2851
  AlignmentType,
2803
2852
  BookmarkEnd,
2804
2853
  BookmarkStart,
2805
- BorderStyle,
2806
2854
  ColumnBreak,
2807
2855
  CommentRangeEnd,
2808
2856
  CommentRangeStart,
@@ -3248,7 +3296,8 @@ function emitTable(block2, resources = /* @__PURE__ */ new Map()) {
3248
3296
  },
3249
3297
  layout: block2.layout === "fixed" ? TableLayoutType.FIXED : TableLayoutType.AUTOFIT,
3250
3298
  // An empty grid is a table with nothing to say about its columns, which is
3251
- // not the same as one whose columns are all zero wide.
3299
+ // not the same as one whose columns are all zero wide. The compiler hands
3300
+ // over every grid in twips, the unit `columnWidths` takes.
3252
3301
  ...block2.columnGrid.values.length > 0 ? { columnWidths: block2.columnGrid.values } : {},
3253
3302
  ...block2.borders ? {
3254
3303
  borders: {
@@ -3342,7 +3391,10 @@ function emitTableCell(cell, resources) {
3342
3391
  function emitBorder(border3) {
3343
3392
  if (!border3) return void 0;
3344
3393
  return {
3345
- style: BORDER_STYLE[border3.style] ?? BorderStyle.SINGLE,
3394
+ // Already OOXML's word, and docx.js's `BorderStyle` values are exactly
3395
+ // those words. Mapping unknown ones to `single` here is what once hid an
3396
+ // authoring word the IR should never have carried.
3397
+ style: border3.style,
3346
3398
  size: border3.sizeEighthPoints ?? 0,
3347
3399
  color: border3.color?.hex ?? "000000",
3348
3400
  // Stated only when the IR states it, so every border that predates the
@@ -3351,7 +3403,7 @@ function emitBorder(border3) {
3351
3403
  ...border3.spacePoints !== void 0 ? { space: border3.spacePoints } : {}
3352
3404
  };
3353
3405
  }
3354
- var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, ThemedTableOfContents, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
3406
+ var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, ThemedTableOfContents, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION;
3355
3407
  var init_emit = __esm({
3356
3408
  "src/renderers/docxjs/emit.ts"() {
3357
3409
  "use strict";
@@ -3400,20 +3452,10 @@ var init_emit = __esm({
3400
3452
  inside: RelativeVerticalPosition.INSIDE,
3401
3453
  outside: RelativeVerticalPosition.OUTSIDE
3402
3454
  };
3403
- BORDER_STYLE = {
3404
- none: BorderStyle.NONE,
3405
- single: BorderStyle.SINGLE,
3406
- double: BorderStyle.DOUBLE,
3407
- dashed: BorderStyle.DASHED,
3408
- dotted: BorderStyle.DOTTED
3409
- };
3410
3455
  }
3411
3456
  });
3412
3457
 
3413
3458
  // src/renderers/docxjs/styles.ts
3414
- import {
3415
- BorderStyle as BorderStyle2
3416
- } from "docx";
3417
3459
  function runProperties(formatting) {
3418
3460
  const out = {};
3419
3461
  if (formatting.fontFamily !== void 0) out.font = formatting.fontFamily;
@@ -3422,7 +3464,7 @@ function runProperties(formatting) {
3422
3464
  }
3423
3465
  if (formatting.color) out.color = formatting.color.hex;
3424
3466
  if (formatting.bold !== void 0) out.bold = formatting.bold;
3425
- if (formatting.italic !== void 0) out.italic = formatting.italic;
3467
+ if (formatting.italic !== void 0) out.italics = formatting.italic;
3426
3468
  if (formatting.underline) {
3427
3469
  out.underline = {
3428
3470
  type: formatting.underline.type,
@@ -3434,7 +3476,9 @@ function runProperties(formatting) {
3434
3476
  if (formatting.subScript) out.subScript = true;
3435
3477
  if (formatting.smallCaps !== void 0) out.smallCaps = formatting.smallCaps;
3436
3478
  if (formatting.allCaps !== void 0) out.allCaps = formatting.allCaps;
3437
- if (formatting.highlight !== void 0) out.highlight = formatting.highlight;
3479
+ if (formatting.highlight !== void 0) {
3480
+ out.highlight = formatting.highlight;
3481
+ }
3438
3482
  if (formatting.characterSpacingTwentieths !== void 0) {
3439
3483
  out.characterSpacing = formatting.characterSpacingTwentieths;
3440
3484
  }
@@ -3448,7 +3492,10 @@ function runProperties(formatting) {
3448
3492
  }
3449
3493
  function border(value) {
3450
3494
  return {
3451
- style: BORDER_STYLE2[value.style] ?? BorderStyle2.SINGLE,
3495
+ // OOXML's word, which is what docx.js's `BorderStyle` values are. A theme
3496
+ // may state any of them; mapping the unlisted ones to `single` drew a
3497
+ // `wave` or `triple` rule as a plain line here and as asked on office-open.
3498
+ style: value.style,
3452
3499
  ...value.sizeEighthPoints !== void 0 ? { size: value.sizeEighthPoints } : {},
3453
3500
  ...value.color ? { color: value.color.hex } : {},
3454
3501
  ...value.spacePoints !== void 0 ? { space: value.spacePoints } : {}
@@ -3561,19 +3608,10 @@ function emitStyles(styles) {
3561
3608
  }
3562
3609
  };
3563
3610
  }
3564
- var BORDER_STYLE2;
3565
3611
  var init_styles2 = __esm({
3566
3612
  "src/renderers/docxjs/styles.ts"() {
3567
3613
  "use strict";
3568
3614
  init_emit();
3569
- BORDER_STYLE2 = {
3570
- none: BorderStyle2.NONE,
3571
- single: BorderStyle2.SINGLE,
3572
- double: BorderStyle2.DOUBLE,
3573
- dashed: BorderStyle2.DASHED,
3574
- dotted: BorderStyle2.DOTTED,
3575
- thick: BorderStyle2.THICK
3576
- };
3577
3615
  }
3578
3616
  });
3579
3617
 
@@ -3752,7 +3790,7 @@ import {
3752
3790
  Header,
3753
3791
  ImageRun,
3754
3792
  Packer,
3755
- LineRuleType as LineRuleType2,
3793
+ LineRuleType,
3756
3794
  Paragraph as Paragraph2
3757
3795
  } from "docx";
3758
3796
  function createDocxJsRenderer() {
@@ -4039,7 +4077,7 @@ function lastBlockIsFrame(section2) {
4039
4077
  function anchorParagraph(anchor) {
4040
4078
  return new Paragraph2({
4041
4079
  children: anchor ? [anchor] : [],
4042
- spacing: { before: 0, after: 0, line: 20, lineRule: LineRuleType2.EXACT }
4080
+ spacing: { before: 0, after: 0, line: 20, lineRule: LineRuleType.EXACT }
4043
4081
  });
4044
4082
  }
4045
4083
  function coreProperties(ir) {
@@ -4212,11 +4250,71 @@ var init_chartParts = __esm({
4212
4250
  }
4213
4251
  });
4214
4252
 
4253
+ // src/renderers/office-open/cellTocs.ts
4254
+ function spliceCellTocs(zip, tocs, stringifyTableOfContents) {
4255
+ if (tocs.length === 0) return;
4256
+ const spliced = /* @__PURE__ */ new Set();
4257
+ for (const entry of zip.getEntries()) {
4258
+ if (!TABLE_PARTS.test(entry.entryName)) continue;
4259
+ const original = entry.getData().toString("utf8");
4260
+ let xml = original;
4261
+ for (const toc of tocs) {
4262
+ const start = markerParagraph(xml, toc.start);
4263
+ if (!start) continue;
4264
+ const end = markerParagraph(xml, toc.end);
4265
+ if (spliced.has(toc) || !end || end.from < start.to) {
4266
+ throw new Error(
4267
+ `the office-open renderer lost track of a table of contents in a table cell (${entry.entryName})`
4268
+ );
4269
+ }
4270
+ xml = xml.slice(0, start.from) + stringifyTableOfContents(
4271
+ toc.alias,
4272
+ toc.options,
4273
+ xml.slice(start.to, end.from)
4274
+ ) + xml.slice(end.to);
4275
+ spliced.add(toc);
4276
+ }
4277
+ if (xml !== original) zip.updateFile(entry, Buffer.from(xml, "utf8"));
4278
+ }
4279
+ if (spliced.size !== tocs.length) {
4280
+ throw new Error(
4281
+ `the office-open renderer placed ${tocs.length} table(s) of contents in table cells and found ${spliced.size}`
4282
+ );
4283
+ }
4284
+ }
4285
+ function markerParagraph(xml, marker) {
4286
+ const at = xml.indexOf(marker);
4287
+ if (at < 0) return void 0;
4288
+ const from = Math.max(
4289
+ xml.lastIndexOf("<w:p>", at),
4290
+ xml.lastIndexOf("<w:p ", at)
4291
+ );
4292
+ const close = xml.indexOf("</w:p>", at);
4293
+ if (xml.indexOf(marker, at + marker.length) >= 0 || from < 0 || close < 0 || xml.lastIndexOf("</w:p>", at) > from) {
4294
+ throw new Error(
4295
+ "the office-open renderer found a table-of-contents marker it did not write"
4296
+ );
4297
+ }
4298
+ return { from, to: close + "</w:p>".length };
4299
+ }
4300
+ var TABLE_PARTS;
4301
+ var init_cellTocs = __esm({
4302
+ "src/renderers/office-open/cellTocs.ts"() {
4303
+ "use strict";
4304
+ TABLE_PARTS = /^word\/(?:document|header\d+|footer\d+)\.xml$/;
4305
+ }
4306
+ });
4307
+
4215
4308
  // src/renderers/office-open/emit.ts
4216
4309
  import { assertNever as assertNever2 } from "@json-to-office/shared/rendering";
4217
4310
  function emptyContext() {
4218
4311
  let next = 1;
4219
- return { pictures: /* @__PURE__ */ new Map(), nextDrawingId: () => next++, charts: [] };
4312
+ return {
4313
+ pictures: /* @__PURE__ */ new Map(),
4314
+ nextDrawingId: () => next++,
4315
+ charts: [],
4316
+ cellTocs: []
4317
+ };
4220
4318
  }
4221
4319
  function simpleField(instruction, cachedText) {
4222
4320
  return {
@@ -4226,16 +4324,26 @@ function simpleField(instruction, cachedText) {
4226
4324
  }
4227
4325
  };
4228
4326
  }
4327
+ function twips(value) {
4328
+ return Math.floor(value);
4329
+ }
4229
4330
  function runProperties2(formatting) {
4230
4331
  if (!formatting) return {};
4231
4332
  const out = {};
4232
4333
  if (formatting.fontFamily) out.font = formatting.fontFamily;
4233
4334
  if (formatting.sizeHalfPoints !== void 0) {
4234
4335
  out.size = formatting.sizeHalfPoints / 2;
4336
+ if (formatting.sizeHalfPoints) out.sizeComplexScript = out.size;
4235
4337
  }
4236
4338
  if (formatting.color) out.color = formatting.color.hex;
4237
- if (formatting.bold !== void 0) out.bold = formatting.bold;
4238
- if (formatting.italic !== void 0) out.italic = formatting.italic;
4339
+ if (formatting.bold !== void 0) {
4340
+ out.bold = formatting.bold;
4341
+ out.boldComplexScript = formatting.bold;
4342
+ }
4343
+ if (formatting.italic !== void 0) {
4344
+ out.italic = formatting.italic;
4345
+ out.italicComplexScript = formatting.italic;
4346
+ }
4239
4347
  if (formatting.underline) {
4240
4348
  out.underline = {
4241
4349
  type: formatting.underline.type,
@@ -4255,7 +4363,9 @@ function runProperties2(formatting) {
4255
4363
  if (formatting.scalePercent !== void 0)
4256
4364
  out.scale = formatting.scalePercent;
4257
4365
  if (formatting.characterSpacingTwentieths !== void 0) {
4258
- out.characterSpacing = formatting.characterSpacingTwentieths;
4366
+ const stated = formatting.characterSpacingTwentieths;
4367
+ const twentieths = twips(stated);
4368
+ out.characterSpacing = twentieths === 0 && stated !== 0 ? "0pt" : twentieths;
4259
4369
  }
4260
4370
  if (formatting.language) out.language = { value: formatting.language };
4261
4371
  if (formatting.noProof !== void 0) out.noProof = formatting.noProof;
@@ -4646,16 +4756,16 @@ function paragraphProperties2(formatting) {
4646
4756
  if (formatting.indent) {
4647
4757
  const indent = {};
4648
4758
  if (formatting.indent.leftTwips !== void 0) {
4649
- indent.left = formatting.indent.leftTwips;
4759
+ indent.left = twips(formatting.indent.leftTwips);
4650
4760
  }
4651
4761
  if (formatting.indent.rightTwips !== void 0) {
4652
- indent.right = formatting.indent.rightTwips;
4762
+ indent.right = twips(formatting.indent.rightTwips);
4653
4763
  }
4654
4764
  if (formatting.indent.firstLineTwips !== void 0) {
4655
- indent.firstLine = formatting.indent.firstLineTwips;
4765
+ indent.firstLine = twips(formatting.indent.firstLineTwips);
4656
4766
  }
4657
4767
  if (formatting.indent.hangingTwips !== void 0) {
4658
- indent.hanging = formatting.indent.hangingTwips;
4768
+ indent.hanging = twips(formatting.indent.hangingTwips);
4659
4769
  }
4660
4770
  out.indent = indent;
4661
4771
  }
@@ -4786,15 +4896,14 @@ function table(value, ctx) {
4786
4896
  // `w:w` is an integer in OOXML. A grid that splits a remainder between
4787
4897
  // unstated columns comes out fractional, and the XML would carry it
4788
4898
  // verbatim; floored here, as the docxjs backend floors it.
4789
- size: value.width.kind === "auto" ? 0 : value.width.kind === "twips" ? Math.floor(value.width.value) : value.width.value,
4899
+ size: value.width.kind === "auto" ? 0 : value.width.kind === "twips" ? twips(value.width.value) : value.width.value,
4790
4900
  type: value.width.kind === "twips" ? "dxa" : value.width.kind === "percent" ? "pct" : "auto"
4791
4901
  },
4792
4902
  layout: value.layout,
4793
4903
  // An empty grid is a table with nothing to say about its columns, which is
4794
- // not the same as one whose columns are all zero wide.
4795
- ...value.columnGrid.values.length > 0 ? {
4796
- columnWidths: value.columnGrid.unit === "twips" ? value.columnGrid.values.map(Math.floor) : value.columnGrid.values
4797
- } : {},
4904
+ // not the same as one whose columns are all zero wide. The compiler hands
4905
+ // over every grid in twips; floored for the same reason as the width.
4906
+ ...value.columnGrid.values.length > 0 ? { columnWidths: value.columnGrid.values.map(twips) } : {},
4798
4907
  ...value.alignment ? { alignment: alignment(value.alignment) } : {},
4799
4908
  ...value.borders ? { borders: borders2(value.borders) } : {},
4800
4909
  ...value.cellMargins ? { margins: cellMargins(value.cellMargins) } : {},
@@ -4805,14 +4914,22 @@ function tableFloat2(floating) {
4805
4914
  return {
4806
4915
  ...floating.horizontalAnchor ? { horizontalAnchor: floating.horizontalAnchor } : {},
4807
4916
  ...floating.verticalAnchor ? { verticalAnchor: floating.verticalAnchor } : {},
4808
- ...floating.absoluteHorizontalPositionTwips !== void 0 ? { absoluteHorizontalPosition: floating.absoluteHorizontalPositionTwips } : {},
4917
+ ...floating.absoluteHorizontalPositionTwips !== void 0 ? {
4918
+ absoluteHorizontalPosition: twips(
4919
+ floating.absoluteHorizontalPositionTwips
4920
+ )
4921
+ } : {},
4809
4922
  ...floating.relativeHorizontalPosition ? { relativeHorizontalPosition: floating.relativeHorizontalPosition } : {},
4810
- ...floating.absoluteVerticalPositionTwips !== void 0 ? { absoluteVerticalPosition: floating.absoluteVerticalPositionTwips } : {},
4923
+ ...floating.absoluteVerticalPositionTwips !== void 0 ? {
4924
+ absoluteVerticalPosition: twips(
4925
+ floating.absoluteVerticalPositionTwips
4926
+ )
4927
+ } : {},
4811
4928
  ...floating.relativeVerticalPosition ? { relativeVerticalPosition: floating.relativeVerticalPosition } : {},
4812
- ...floating.topFromTextTwips !== void 0 ? { topFromText: floating.topFromTextTwips } : {},
4813
- ...floating.rightFromTextTwips !== void 0 ? { rightFromText: floating.rightFromTextTwips } : {},
4814
- ...floating.bottomFromTextTwips !== void 0 ? { bottomFromText: floating.bottomFromTextTwips } : {},
4815
- ...floating.leftFromTextTwips !== void 0 ? { leftFromText: floating.leftFromTextTwips } : {},
4929
+ ...floating.topFromTextTwips !== void 0 ? { topFromText: twips(floating.topFromTextTwips) } : {},
4930
+ ...floating.rightFromTextTwips !== void 0 ? { rightFromText: twips(floating.rightFromTextTwips) } : {},
4931
+ ...floating.bottomFromTextTwips !== void 0 ? { bottomFromText: twips(floating.bottomFromTextTwips) } : {},
4932
+ ...floating.leftFromTextTwips !== void 0 ? { leftFromText: twips(floating.leftFromTextTwips) } : {},
4816
4933
  ...floating.overlap ? { overlap: floating.overlap } : {}
4817
4934
  };
4818
4935
  }
@@ -4820,7 +4937,10 @@ function tableRow(row, ctx) {
4820
4937
  return {
4821
4938
  cells: row.cells.map((cell) => tableCell(cell, ctx)),
4822
4939
  ...row.heightTwips !== void 0 ? {
4823
- height: { value: row.heightTwips, rule: row.heightRule ?? "atLeast" }
4940
+ height: {
4941
+ value: twips(row.heightTwips),
4942
+ rule: row.heightRule ?? "atLeast"
4943
+ }
4824
4944
  } : {},
4825
4945
  ...row.isHeader !== void 0 ? { tableHeader: row.isHeader } : {},
4826
4946
  ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {},
@@ -4828,9 +4948,12 @@ function tableRow(row, ctx) {
4828
4948
  };
4829
4949
  }
4830
4950
  function tableCell(cell, ctx) {
4951
+ const children = cellChildren2(cell, ctx);
4952
+ if (cell.children[cell.children.length - 1]?.kind !== "paragraph")
4953
+ children.push({ paragraph: { children: [] } });
4831
4954
  return {
4832
- children: cell.children.map((child) => block(child, ctx)),
4833
- ...cell.widthTwips !== void 0 ? { width: { size: cell.widthTwips, type: "dxa" } } : {},
4955
+ children,
4956
+ ...cell.widthTwips !== void 0 ? { width: { size: twips(cell.widthTwips), type: "dxa" } } : {},
4834
4957
  ...cell.verticalAlign ? { verticalAlign: cell.verticalAlign } : {},
4835
4958
  ...cell.shading ? { shading: shading(cell.shading) } : {},
4836
4959
  ...cell.margins ? { margins: cellMargins(cell.margins) } : {},
@@ -4840,8 +4963,38 @@ function tableCell(cell, ctx) {
4840
4963
  ...cell.textDirection ? { textDirection: cell.textDirection } : {}
4841
4964
  };
4842
4965
  }
4966
+ function cellChildren2(cell, ctx) {
4967
+ const children = [];
4968
+ for (const child of cell.children) {
4969
+ if (child.kind !== "toc") {
4970
+ children.push(block(child, ctx));
4971
+ continue;
4972
+ }
4973
+ const { alias, entries, ...options } = tableOfContents(child);
4974
+ const index = ctx.cellTocs.length;
4975
+ const toc = {
4976
+ start: cellTocMarker(index, "start"),
4977
+ end: cellTocMarker(index, "end"),
4978
+ alias: String(alias),
4979
+ options
4980
+ };
4981
+ ctx.cellTocs.push(toc);
4982
+ children.push(
4983
+ markerParagraph2(toc.start),
4984
+ ...entries ?? [],
4985
+ markerParagraph2(toc.end)
4986
+ );
4987
+ }
4988
+ return children;
4989
+ }
4990
+ function cellTocMarker(index, edge) {
4991
+ return `\uE000jto-cell-toc-${index}-${edge}\uE000`;
4992
+ }
4993
+ function markerParagraph2(text) {
4994
+ return { paragraph: { children: [{ text }] } };
4995
+ }
4843
4996
  function cellMargins(margins) {
4844
- const side = (value) => value === void 0 ? void 0 : { size: value, type: "dxa" };
4997
+ const side = (value) => value === void 0 ? void 0 : { size: twips(value), type: "dxa" };
4845
4998
  const out = {};
4846
4999
  for (const [name, value] of [
4847
5000
  ["top", margins.topTwips],
@@ -4888,18 +5041,18 @@ function section(value, ctx, closesDocument = false) {
4888
5041
  // Orientation is implied by the width/height pair, which is how this
4889
5042
  // pipeline has always expressed it; stating it as well changes `w:pgSz`.
4890
5043
  size: {
4891
- width: page.widthTwips,
4892
- height: page.heightTwips,
5044
+ width: twips(page.widthTwips),
5045
+ height: twips(page.heightTwips),
4893
5046
  ...page.code !== void 0 ? { code: page.code } : {}
4894
5047
  },
4895
5048
  margin: {
4896
- top: page.margins.topTwips,
4897
- right: page.margins.rightTwips,
4898
- bottom: page.margins.bottomTwips,
4899
- left: page.margins.leftTwips,
4900
- ...page.margins.headerTwips !== void 0 ? { header: page.margins.headerTwips } : {},
4901
- ...page.margins.footerTwips !== void 0 ? { footer: page.margins.footerTwips } : {},
4902
- ...page.margins.gutterTwips !== void 0 ? { gutter: page.margins.gutterTwips } : {}
5049
+ top: twips(page.margins.topTwips),
5050
+ right: twips(page.margins.rightTwips),
5051
+ bottom: twips(page.margins.bottomTwips),
5052
+ left: twips(page.margins.leftTwips),
5053
+ ...page.margins.headerTwips !== void 0 ? { header: twips(page.margins.headerTwips) } : {},
5054
+ ...page.margins.footerTwips !== void 0 ? { footer: twips(page.margins.footerTwips) } : {},
5055
+ ...page.margins.gutterTwips !== void 0 ? { gutter: twips(page.margins.gutterTwips) } : {}
4903
5056
  },
4904
5057
  ...value.properties.pageNumbers ? {
4905
5058
  pageNumbers: {
@@ -4918,17 +5071,23 @@ function section(value, ctx, closesDocument = false) {
4918
5071
  ...columns ? {
4919
5072
  column: {
4920
5073
  count: columns.count,
4921
- ...columns.spaceTwips !== void 0 ? { space: columns.spaceTwips } : {},
5074
+ ...columns.spaceTwips !== void 0 ? { space: twips(columns.spaceTwips) } : {},
4922
5075
  ...columns.separator !== void 0 ? { separate: columns.separator } : {},
4923
5076
  ...columns.equalWidth !== void 0 ? { equalWidth: columns.equalWidth } : {},
4924
5077
  ...columns.widths ? {
4925
5078
  children: columns.widths.map((column) => ({
4926
- width: column.widthTwips,
4927
- ...column.spaceTwips !== void 0 ? { space: column.spaceTwips } : {}
5079
+ width: twips(column.widthTwips),
5080
+ ...column.spaceTwips !== void 0 ? { space: twips(column.spaceTwips) } : {}
4928
5081
  }))
4929
5082
  } : {}
4930
5083
  }
4931
- } : {}
5084
+ } : {},
5085
+ // No document grid, as the IR states none. Left to the backend, every
5086
+ // section gets the one Word's Chinese template sets — a 15.6pt line grid
5087
+ // (`w:type="lines"`, pitch 312) — and every line is laid out on it here
5088
+ // but at its own height on docx.js, which writes no grid type. A field
5089
+ // the backend is not given takes that default, so all three are stated.
5090
+ grid: { type: "default", linePitch: 360, charSpace: 0 }
4932
5091
  },
4933
5092
  children: sectionChildren2(value, ctx, closesDocument),
4934
5093
  ...value.headers ? { headers: headerFooterSet(value.headers, ctx) } : {},
@@ -4946,14 +5105,14 @@ function headerFooterSet(set, ctx) {
4946
5105
  function sectionChildren2(value, ctx, closesDocument = false) {
4947
5106
  const blocks = value.children.map((child) => block(child, ctx));
4948
5107
  const last = value.children[value.children.length - 1];
4949
- if (closesDocument && last?.kind === "paragraph" && last.frame !== void 0)
5108
+ const bookmark = value.bookmark;
5109
+ if (bookmark?.closes && last?.kind !== "paragraph" || closesDocument && last?.kind === "paragraph" && last.frame !== void 0)
4950
5110
  blocks.push({
4951
5111
  paragraph: {
4952
5112
  children: [],
4953
5113
  spacing: { before: 0, after: 0, line: 20, lineRule: "exact" }
4954
5114
  }
4955
5115
  });
4956
- const bookmark = value.bookmark;
4957
5116
  if (!bookmark) return blocks;
4958
5117
  return [
4959
5118
  ...bookmark.opens ? [{ bookmarkStart: { id: bookmark.id, name: bookmark.name } }] : [],
@@ -4976,8 +5135,8 @@ function numberingConfig2(numbering) {
4976
5135
  ...level.indent ? {
4977
5136
  paragraph: {
4978
5137
  indent: {
4979
- ...level.indent.leftTwips !== void 0 ? { left: level.indent.leftTwips } : {},
4980
- ...level.indent.hangingTwips !== void 0 ? { hanging: level.indent.hangingTwips } : {}
5138
+ ...level.indent.leftTwips !== void 0 ? { left: twips(level.indent.leftTwips) } : {},
5139
+ ...level.indent.hangingTwips !== void 0 ? { hanging: twips(level.indent.hangingTwips) } : {}
4981
5140
  }
4982
5141
  }
4983
5142
  } : {},
@@ -5013,12 +5172,11 @@ function builtIn2(style) {
5013
5172
  };
5014
5173
  }
5015
5174
  function emitStyles2(styles) {
5016
- const documentDefaults = {
5017
- ...Object.keys(styles.defaults.run).length > 0 ? { run: runProperties2(styles.defaults.run) } : {},
5018
- ...Object.keys(styles.defaults.paragraph).length > 0 ? { paragraph: paragraphProperties2(styles.defaults.paragraph) } : {}
5019
- };
5020
5175
  const defaults = {
5021
- ...Object.keys(documentDefaults).length > 0 ? { document: documentDefaults } : {}
5176
+ document: {
5177
+ run: Object.keys(styles.defaults.run).length > 0 ? runProperties2(styles.defaults.run) : null,
5178
+ paragraph: Object.keys(styles.defaults.paragraph).length > 0 ? paragraphProperties2(styles.defaults.paragraph) : null
5179
+ }
5022
5180
  };
5023
5181
  for (const slot of [
5024
5182
  "footnoteText",
@@ -5047,7 +5205,7 @@ function emitStyles2(styles) {
5047
5205
  run: runProperties2(style.run)
5048
5206
  }))
5049
5207
  } : {},
5050
- ...Object.keys(defaults).length > 0 ? { default: defaults } : {}
5208
+ default: defaults
5051
5209
  };
5052
5210
  }
5053
5211
  var init_styles3 = __esm({
@@ -5070,10 +5228,15 @@ async function createOfficeOpenDocxRenderer() {
5070
5228
  /* @vite-ignore */
5071
5229
  OFFICE_OPEN_DOCX
5072
5230
  );
5073
- if (typeof backend.generateDocument !== "function") {
5074
- throw new Error(
5075
- `${OFFICE_OPEN_DOCX} does not export generateDocument(); the installed version is not compatible with this adapter.`
5076
- );
5231
+ for (const name of [
5232
+ "generateDocument",
5233
+ "stringifyTableOfContents"
5234
+ ]) {
5235
+ if (typeof backend[name] !== "function") {
5236
+ throw new Error(
5237
+ `${OFFICE_OPEN_DOCX} does not export ${name}(); the installed version is not compatible with this adapter.`
5238
+ );
5239
+ }
5077
5240
  }
5078
5241
  return {
5079
5242
  id: OFFICE_OPEN_DOCX_RENDERER_ID,
@@ -5081,10 +5244,12 @@ async function createOfficeOpenDocxRenderer() {
5081
5244
  capabilities: OFFICE_OPEN_CAPABILITIES,
5082
5245
  async render(ir, options) {
5083
5246
  const charts = [];
5247
+ const cellTocs = [];
5084
5248
  const document = await buildDocumentOptions(
5085
5249
  ir,
5086
5250
  charts,
5087
- options?.svgRasterFallback
5251
+ options?.svgRasterFallback,
5252
+ cellTocs
5088
5253
  );
5089
5254
  const bytes = await backend.generateDocument(document, {
5090
5255
  type: "uint8array"
@@ -5092,9 +5257,10 @@ async function createOfficeOpenDocxRenderer() {
5092
5257
  let raw = Buffer.from(
5093
5258
  bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)
5094
5259
  );
5095
- if (charts.length > 0) {
5260
+ if (charts.length > 0 || cellTocs.length > 0) {
5096
5261
  const zip = new AdmZip4(raw);
5097
5262
  spliceChartParts(zip, charts);
5263
+ spliceCellTocs(zip, cellTocs, backend.stringifyTableOfContents);
5098
5264
  raw = zip.toBuffer();
5099
5265
  }
5100
5266
  if (options?.deterministic === false)
@@ -5111,12 +5277,13 @@ async function createOfficeOpenDocxRenderer() {
5111
5277
  }
5112
5278
  };
5113
5279
  }
5114
- async function buildDocumentOptions(ir, charts = [], svgRasterFallback) {
5280
+ async function buildDocumentOptions(ir, charts = [], svgRasterFallback, cellTocs = []) {
5115
5281
  let nextDrawingId = 1;
5116
5282
  const ctx = {
5117
5283
  pictures: await prepareImages2(ir, svgRasterFallback),
5118
5284
  nextDrawingId: () => nextDrawingId++,
5119
- charts
5285
+ charts,
5286
+ cellTocs
5120
5287
  };
5121
5288
  return {
5122
5289
  styles: emitStyles2(ir.styles),
@@ -5395,6 +5562,7 @@ var init_office_open = __esm({
5395
5562
  "src/renderers/office-open/index.ts"() {
5396
5563
  "use strict";
5397
5564
  init_chartParts();
5565
+ init_cellTocs();
5398
5566
  init_imageUtils();
5399
5567
  init_packageDocument();
5400
5568
  init_emit2();
@@ -6280,7 +6448,7 @@ import {
6280
6448
 
6281
6449
  // src/components/highcharts.ts
6282
6450
  init_colorUtils();
6283
- init_layoutUtils();
6451
+ init_widthUtils();
6284
6452
  import {
6285
6453
  chartFamilyResolver,
6286
6454
  remoteExportNotice,
@@ -6391,8 +6559,7 @@ function placedWidthPt(config, theme) {
6391
6559
  const chart = config.options.chart;
6392
6560
  if (typeof width === "number") return width * POINTS_PER_PIXEL;
6393
6561
  if (typeof width === "string") {
6394
- const page = getPageSetup(theme);
6395
- const contentWidthPt = (page.size.width - page.margin.left - page.margin.right) * POINTS_PER_TWIP;
6562
+ const contentWidthPt = getAvailableWidthTwips(theme) * POINTS_PER_TWIP;
6396
6563
  return parseFloat(width) / 100 * contentWidthPt;
6397
6564
  }
6398
6565
  if (typeof height === "number" && chart.height > 0) {
@@ -6643,6 +6810,7 @@ import {
6643
6810
  assetUnreadableError
6644
6811
  } from "@json-to-office/shared/rendering";
6645
6812
  import { capsFormatting as capsFormatting2, synthesizeFamilyName as synthesizeFamilyName2 } from "@json-to-office/shared";
6813
+ import { unrenderedComponentText } from "@json-to-office/quality";
6646
6814
  import {
6647
6815
  isNativeVisualProps as isNativeVisualProps4
6648
6816
  } from "@json-to-office/shared-docx";
@@ -7156,6 +7324,7 @@ function resolveColors(entries, at, deps) {
7156
7324
 
7157
7325
  // src/styles/themeToStyles.ts
7158
7326
  init_themes();
7327
+ init_types();
7159
7328
  init_colorUtils();
7160
7329
  init_styleHelpers();
7161
7330
  import {
@@ -7322,10 +7491,13 @@ function convertParagraphProperties(merged, styleProps, theme) {
7322
7491
  }
7323
7492
  };
7324
7493
  }
7494
+ function themeBorderStyle(style) {
7495
+ return DOCX_IR_BORDER_STYLES.includes(style) ? style : "single";
7496
+ }
7325
7497
  function convertBorders(borders3, theme) {
7326
7498
  if (!borders3) return void 0;
7327
7499
  const mapSide = (side) => side ? {
7328
- style: side.style,
7500
+ style: themeBorderStyle(side.style),
7329
7501
  sizeEighthPoints: side.size,
7330
7502
  color: { hex: resolveColor(side.color, theme) },
7331
7503
  ...side.space !== void 0 ? { spacePoints: side.space } : {}
@@ -7764,7 +7936,8 @@ function createDocumentStyles(themeNameOrObject = "consulting", language) {
7764
7936
  tabStops: effectiveTabStops.map(
7765
7937
  (ts) => ({
7766
7938
  type: ts.type,
7767
- positionTwips: ts.position === "max" ? RIGHT_MARGIN_TAB_TWIPS : ts.position,
7939
+ // Whole twips: `w:pos` is an integer no backend rounds.
7940
+ positionTwips: ts.position === "max" ? RIGHT_MARGIN_TAB_TWIPS : Math.round(ts.position),
7768
7941
  ...ts.leader && { leader: ts.leader }
7769
7942
  })
7770
7943
  )
@@ -8512,7 +8685,11 @@ function resolveTableModel(source, theme, themeName, options = {}) {
8512
8685
  });
8513
8686
  adjudicateInteriorEdges([...hasHeader ? [header] : [], ...rows]);
8514
8687
  return {
8515
- ...resolveWidths(source, columns, theme, themeName),
8688
+ ...resolveWidths(
8689
+ source,
8690
+ columns,
8691
+ options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
8692
+ ),
8516
8693
  ...hasHeader ? { header } : {},
8517
8694
  rows,
8518
8695
  repeatHeader: source.repeatHeaderOnPageBreak ?? true
@@ -8566,22 +8743,24 @@ function adjudicateInteriorEdges(rows) {
8566
8743
  });
8567
8744
  });
8568
8745
  }
8569
- function resolveWidths(source, columns, theme, themeName) {
8746
+ function resolveWidths(source, columns, measureTwips) {
8570
8747
  const hasExplicitWidths = columns.some((col) => col.width !== void 0);
8571
8748
  if (!hasExplicitWidths) {
8572
- const share = 100 / columns.length;
8749
+ const percent = source.width ?? 100;
8573
8750
  return {
8574
- columnGrid: { unit: "percent", values: columns.map(() => share) },
8575
- width: { size: source.width ?? 100, unit: "percent" }
8751
+ columnGrid: {
8752
+ unit: "twips",
8753
+ values: splitTwips(columns.length, measureTwips * percent / 100)
8754
+ },
8755
+ width: { size: percent, unit: "percent" }
8576
8756
  };
8577
8757
  }
8578
- const available = getAvailableWidthTwips(theme, themeName);
8579
8758
  const stated = columns.map(
8580
- (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, available) : void 0
8759
+ (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
8581
8760
  );
8582
8761
  const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
8583
8762
  const unstatedCount = columns.filter((col) => col.width === void 0).length;
8584
- const remaining = Math.max(0, available - totalStated);
8763
+ const remaining = Math.max(0, measureTwips - totalStated);
8585
8764
  const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
8586
8765
  const values = stated.map((w) => w || shareOfRemainder);
8587
8766
  return {
@@ -8590,7 +8769,7 @@ function resolveWidths(source, columns, theme, themeName) {
8590
8769
  size: values.reduce((sum, w) => sum + w, 0),
8591
8770
  unit: "twips"
8592
8771
  },
8593
- ...totalStated > available ? { overflow: { totalTwips: totalStated, availableTwips: available } } : {}
8772
+ ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
8594
8773
  };
8595
8774
  }
8596
8775
 
@@ -9306,6 +9485,36 @@ function emptyToUndefined(formatting) {
9306
9485
 
9307
9486
  // src/ir/compiler.ts
9308
9487
  init_types();
9488
+
9489
+ // src/ir/measure.ts
9490
+ var DEFAULT_COLUMN_SPACE_TWIPS = 720;
9491
+ function textWidthTwips(page) {
9492
+ const { leftTwips, rightTwips, gutterTwips = 0 } = page.margins;
9493
+ return Math.max(0, page.widthTwips - leftTwips - rightTwips - gutterTwips);
9494
+ }
9495
+ function sectionMeasureTwips(properties) {
9496
+ const textWidth = textWidthTwips(properties.page);
9497
+ const columns = properties.columns;
9498
+ if (!columns || columns.count <= 1) return textWidth;
9499
+ if (columns.equalWidth !== true && columns.widths?.length) {
9500
+ return Math.min(...columns.widths.map((column) => column.widthTwips));
9501
+ }
9502
+ const space = columns.spaceTwips ?? DEFAULT_COLUMN_SPACE_TWIPS;
9503
+ return Math.max(0, (textWidth - space * (columns.count - 1)) / columns.count);
9504
+ }
9505
+ function cellMeasureTwips(widthTwips, margins) {
9506
+ return Math.max(
9507
+ 0,
9508
+ widthTwips - (margins?.leftTwips ?? 0) - (margins?.rightTwips ?? 0)
9509
+ );
9510
+ }
9511
+ function tableWidthTwips(width, measureTwips) {
9512
+ if (width.kind === "twips") return width.value;
9513
+ if (width.kind === "percent") return measureTwips * width.value / 100;
9514
+ return measureTwips;
9515
+ }
9516
+
9517
+ // src/ir/compiler.ts
9309
9518
  init_units();
9310
9519
  var CONTENT_BOOKMARK_ID_BASE = 2e6;
9311
9520
  var SINGLE_LINE_TWIPS = 240;
@@ -9506,6 +9715,42 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9506
9715
  if (column && column.count > 1) {
9507
9716
  ctx.features.require("columns", `${path3}.properties.columns`);
9508
9717
  }
9718
+ const properties = {
9719
+ page: {
9720
+ widthTwips: page.size.width,
9721
+ heightTwips: page.size.height,
9722
+ orientation: page.size.width > page.size.height ? "landscape" : "portrait",
9723
+ ...page.size.code !== void 0 ? { code: page.size.code } : {},
9724
+ margins: {
9725
+ topTwips: page.margin.top,
9726
+ bottomTwips: page.margin.bottom,
9727
+ leftTwips: page.margin.left,
9728
+ rightTwips: page.margin.right,
9729
+ ...page.margin.header !== void 0 ? { headerTwips: page.margin.header } : {},
9730
+ ...page.margin.footer !== void 0 ? { footerTwips: page.margin.footer } : {},
9731
+ ...page.margin.gutter !== void 0 ? { gutterTwips: page.margin.gutter } : {}
9732
+ }
9733
+ },
9734
+ // A chunk that starts on a new page says so; the rest continue the flow.
9735
+ // The layout stage already decided which, so it is carried rather than
9736
+ // re-derived from `breakBefore`: a section can be continuous without the
9737
+ // two agreeing.
9738
+ ...section2.properties.type ? { type: sectionType(section2.properties.type) } : {},
9739
+ ...column ? {
9740
+ columns: {
9741
+ count: column.count,
9742
+ ...column.space !== void 0 ? { spaceTwips: column.space } : {},
9743
+ ...column.equalWidth !== void 0 ? { equalWidth: column.equalWidth } : {},
9744
+ ...column.widths ? {
9745
+ widths: column.widths.map((c) => ({
9746
+ widthTwips: c.width,
9747
+ ...c.space !== void 0 ? { spaceTwips: c.space } : {}
9748
+ }))
9749
+ } : {}
9750
+ }
9751
+ } : {}
9752
+ };
9753
+ const measureTwips = sectionMeasureTwips(properties);
9509
9754
  const children = [];
9510
9755
  const sectionBookmarkName = section2.belongsToUserSection && ordinal.ordinal !== void 0 ? `_Section_${ordinal.ordinal}` : void 0;
9511
9756
  section2.components.forEach((component, i) => {
@@ -9515,6 +9760,7 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9515
9760
  ctx,
9516
9761
  path: `${path3}.children[${i}]`,
9517
9762
  id: blockId(index, [i]),
9763
+ measureTwips,
9518
9764
  ...sectionBookmarkName ? { sectionBookmarkName } : {}
9519
9765
  })
9520
9766
  );
@@ -9527,41 +9773,7 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9527
9773
  id: `s${index}`,
9528
9774
  path: path3,
9529
9775
  children,
9530
- properties: {
9531
- page: {
9532
- widthTwips: page.size.width,
9533
- heightTwips: page.size.height,
9534
- orientation: page.size.width > page.size.height ? "landscape" : "portrait",
9535
- ...page.size.code !== void 0 ? { code: page.size.code } : {},
9536
- margins: {
9537
- topTwips: page.margin.top,
9538
- bottomTwips: page.margin.bottom,
9539
- leftTwips: page.margin.left,
9540
- rightTwips: page.margin.right,
9541
- ...page.margin.header !== void 0 ? { headerTwips: page.margin.header } : {},
9542
- ...page.margin.footer !== void 0 ? { footerTwips: page.margin.footer } : {},
9543
- ...page.margin.gutter !== void 0 ? { gutterTwips: page.margin.gutter } : {}
9544
- }
9545
- },
9546
- // A chunk that starts on a new page says so; the rest continue the flow.
9547
- // The layout stage already decided which, so it is carried rather than
9548
- // re-derived from `breakBefore`: a section can be continuous without the
9549
- // two agreeing.
9550
- ...section2.properties.type ? { type: sectionType(section2.properties.type) } : {},
9551
- ...column ? {
9552
- columns: {
9553
- count: column.count,
9554
- ...column.space !== void 0 ? { spaceTwips: column.space } : {},
9555
- ...column.equalWidth !== void 0 ? { equalWidth: column.equalWidth } : {},
9556
- ...column.widths ? {
9557
- widths: column.widths.map((c) => ({
9558
- widthTwips: c.width,
9559
- ...c.space !== void 0 ? { spaceTwips: c.space } : {}
9560
- }))
9561
- } : {}
9562
- }
9563
- } : {}
9564
- }
9776
+ properties
9565
9777
  };
9566
9778
  if (ordinal.ordinal !== void 0) {
9567
9779
  compiled.bookmark = {
@@ -9572,9 +9784,22 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9572
9784
  };
9573
9785
  ctx.features.require("bookmarks", `${path3}.bookmark`);
9574
9786
  }
9575
- const headers = compilePart(chrome.header, "header", index, ctx);
9787
+ const chromeMeasureTwips = textWidthTwips(properties.page);
9788
+ const headers = compilePart(
9789
+ chrome.header,
9790
+ "header",
9791
+ index,
9792
+ chromeMeasureTwips,
9793
+ ctx
9794
+ );
9576
9795
  if (headers) compiled.headers = { default: headers };
9577
- const footers = compilePart(chrome.footer, "footer", index, ctx);
9796
+ const footers = compilePart(
9797
+ chrome.footer,
9798
+ "footer",
9799
+ index,
9800
+ chromeMeasureTwips,
9801
+ ctx
9802
+ );
9578
9803
  if (footers) compiled.footers = { default: footers };
9579
9804
  if (headers || footers) {
9580
9805
  ctx.features.require("headers-footers", `${path3}.headers`);
@@ -9593,7 +9818,7 @@ function sectionType(value) {
9593
9818
  return void 0;
9594
9819
  }
9595
9820
  }
9596
- function compilePart(part, kind, sectionIndex, ctx) {
9821
+ function compilePart(part, kind, sectionIndex, measureTwips, ctx) {
9597
9822
  if (!part) return void 0;
9598
9823
  const id = `${kind}:s${sectionIndex}:default`;
9599
9824
  const children = [];
@@ -9604,7 +9829,8 @@ function compilePart(part, kind, sectionIndex, ctx) {
9604
9829
  const scope = {
9605
9830
  ctx,
9606
9831
  path: `sections[${sectionIndex}].${kind}s.default.children[${i}]`,
9607
- id: headerFooterBlockId(id, [i])
9832
+ id: headerFooterBlockId(id, [i]),
9833
+ measureTwips
9608
9834
  };
9609
9835
  children.push(
9610
9836
  ...component.name === "paragraph" ? compileChromeParagraph(component, scope) : component.name === "image" ? compileChromeImage(component, scope) : compileComponent(component, scope)
@@ -9680,9 +9906,7 @@ function compileChromeImage(component, scope) {
9680
9906
  if (mediaType === "svg") ctx.features.require("svg-images", path3);
9681
9907
  const page = getPageSetup(ctx.theme);
9682
9908
  const pageWidthPx = Math.round(twipsToPixels(page.size.width));
9683
- const contentWidthPx = Math.round(
9684
- twipsToPixels(page.size.width - page.margin.left - page.margin.right)
9685
- );
9909
+ const contentWidthPx = Math.round(twipsToPixels(scope.measureTwips));
9686
9910
  const pageHeightPx = Math.round(twipsToPixels(page.size.height));
9687
9911
  const contentHeightPx = Math.round(
9688
9912
  twipsToPixels(page.size.height - page.margin.top - page.margin.bottom)
@@ -9890,7 +10114,7 @@ function compileColumns(component, scope) {
9890
10114
  const props = component.props ?? {};
9891
10115
  const configs = columnConfigs(props.columns);
9892
10116
  if (configs.length === 0) return [];
9893
- const available = getAvailableWidthTwips(ctx.theme, ctx.themeName);
10117
+ const available = scope.measureTwips;
9894
10118
  const { widths, gaps } = columnMetrics(configs, props.gap, available);
9895
10119
  const stated = widths.map(
9896
10120
  (width, index) => width + gaps[index] / 2 + (index > 0 ? gaps[index - 1] / 2 : 0)
@@ -9919,11 +10143,18 @@ function compileColumns(component, scope) {
9919
10143
  rows: [
9920
10144
  {
9921
10145
  cells: configs.map((_, index) => {
10146
+ const margins = {
10147
+ topTwips: 0,
10148
+ rightTwips: gaps[index] / 2,
10149
+ bottomTwips: 0,
10150
+ leftTwips: index > 0 ? gaps[index - 1] / 2 : 0
10151
+ };
9922
10152
  const blocks = contents[index].flatMap(
9923
10153
  (child, childIndex) => compileComponent(child, {
9924
10154
  ...scope,
9925
10155
  path: `${path3}.children[${index}][${childIndex}]`,
9926
- id: `${scope.id}:c${index}:${childIndex}`
10156
+ id: `${scope.id}:c${index}:${childIndex}`,
10157
+ measureTwips: cellMeasureTwips(cells[index], margins)
9927
10158
  })
9928
10159
  );
9929
10160
  return {
@@ -9938,12 +10169,7 @@ function compileColumns(component, scope) {
9938
10169
  }
9939
10170
  ],
9940
10171
  widthTwips: cells[index],
9941
- margins: {
9942
- topTwips: 0,
9943
- rightTwips: gaps[index] / 2,
9944
- bottomTwips: 0,
9945
- leftTwips: index > 0 ? gaps[index - 1] / 2 : 0
9946
- },
10172
+ margins,
9947
10173
  verticalAlign: "top",
9948
10174
  borders: {
9949
10175
  top: NO_BORDERS.top,
@@ -10000,6 +10226,16 @@ var DEFAULT_TEXT_BOX_WIDTH_TWIPS = 5e3;
10000
10226
  function compileTextBox(component, scope) {
10001
10227
  const { ctx, path: path3 } = scope;
10002
10228
  const props = component.props ?? {};
10229
+ const style = props.style;
10230
+ const padding = style?.padding;
10231
+ const width = textBoxWidth(props);
10232
+ const widthTwips = Math.round(tableWidthTwips(width, scope.measureTwips));
10233
+ const margins = {
10234
+ topTwips: padding?.top ? pointsToTwips2(padding.top) : 0,
10235
+ rightTwips: padding?.right ? pointsToTwips2(padding.right) : 0,
10236
+ bottomTwips: padding?.bottom ? pointsToTwips2(padding.bottom) : 0,
10237
+ leftTwips: padding?.left ? pointsToTwips2(padding.left) : 0
10238
+ };
10003
10239
  const children = [];
10004
10240
  const contents = component.children ?? [];
10005
10241
  contents.forEach((child, index) => {
@@ -10007,12 +10243,12 @@ function compileTextBox(component, scope) {
10007
10243
  ...compileComponent(child, {
10008
10244
  ...scope,
10009
10245
  path: `${path3}.children[${index}]`,
10010
- id: `${scope.id}:c${index}`
10246
+ id: `${scope.id}:c${index}`,
10247
+ // What the box holds is set against the box less its padding.
10248
+ measureTwips: cellMeasureTwips(widthTwips, margins)
10011
10249
  })
10012
10250
  );
10013
10251
  });
10014
- const style = props.style;
10015
- const padding = style?.padding;
10016
10252
  if (props.renderAs === "shape") {
10017
10253
  const shape = compileShape2(props, children, scope);
10018
10254
  if (shape) return shape;
@@ -10024,9 +10260,11 @@ function compileTextBox(component, scope) {
10024
10260
  kind: "table",
10025
10261
  id: scope.id,
10026
10262
  path: path3,
10027
- // No grid: one cell, whose width the table itself decides.
10028
- columnGrid: { unit: "twips", values: [] },
10029
- width: textBoxWidth(props),
10263
+ // One cell, as wide as the box. Left out, the grid would be each
10264
+ // backend's 100-twip placeholder, which a reader that takes the grid as
10265
+ // the column widths draws.
10266
+ columnGrid: { unit: "twips", values: [widthTwips] },
10267
+ width,
10030
10268
  layout: "fixed",
10031
10269
  // The container is invisible; whatever border the author asked for is
10032
10270
  // drawn by the cell inside it.
@@ -10046,12 +10284,7 @@ function compileTextBox(component, scope) {
10046
10284
  children: []
10047
10285
  }
10048
10286
  ],
10049
- margins: {
10050
- topTwips: padding?.top ? pointsToTwips2(padding.top) : 0,
10051
- rightTwips: padding?.right ? pointsToTwips2(padding.right) : 0,
10052
- bottomTwips: padding?.bottom ? pointsToTwips2(padding.bottom) : 0,
10053
- leftTwips: padding?.left ? pointsToTwips2(padding.left) : 0
10054
- },
10287
+ margins,
10055
10288
  ...style?.shading?.fill ? {
10056
10289
  shading: {
10057
10290
  fill: irColor(
@@ -10070,8 +10303,11 @@ function compileTextBox(component, scope) {
10070
10303
  var EMU_PER_PIXEL = 9525;
10071
10304
  function compileShape2(props, children, scope) {
10072
10305
  const { ctx, path: path3 } = scope;
10073
- const width = shapeSize(props.width, "width", ctx);
10074
- const height = shapeSize(props.height, "height", ctx);
10306
+ const width = shapeSize(props.width, scope.measureTwips);
10307
+ const height = shapeSize(
10308
+ props.height,
10309
+ getAvailableHeightTwips(ctx.theme, ctx.themeName)
10310
+ );
10075
10311
  if (width.pixels === void 0 || height.pixels === void 0) {
10076
10312
  warnOnce(
10077
10313
  ctx,
@@ -10102,7 +10338,7 @@ function compileShape2(props, children, scope) {
10102
10338
  warnOnce(
10103
10339
  ctx,
10104
10340
  "text-box",
10105
- '[core-docx] text-box renderAs "shape" resolves percentage sizes at generation time, against the current page content box; the shape will not reflow if the page size changes.'
10341
+ '[core-docx] text-box renderAs "shape" resolves percentage sizes at generation time \u2014 a width against what holds it, a height against the page \u2014 so the shape will not reflow if either changes.'
10106
10342
  );
10107
10343
  }
10108
10344
  if (outline.ignoredSides.length > 0) {
@@ -10147,7 +10383,7 @@ function compileShape2(props, children, scope) {
10147
10383
  }
10148
10384
  ];
10149
10385
  }
10150
- function shapeSize(value, axis, ctx) {
10386
+ function shapeSize(value, referenceTwips) {
10151
10387
  if (typeof value === "number") {
10152
10388
  return { pixels: Math.round(value), resolvedPercentage: false };
10153
10389
  }
@@ -10156,9 +10392,8 @@ function shapeSize(value, axis, ctx) {
10156
10392
  if (!Number.isFinite(fraction) || fraction <= 0) {
10157
10393
  return { resolvedPercentage: false };
10158
10394
  }
10159
- const availableTwips = axis === "width" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getAvailableHeightTwips(ctx.theme, ctx.themeName);
10160
10395
  return {
10161
- pixels: Math.round(availableTwips * fraction / TWIPS_PER_PIXEL),
10396
+ pixels: Math.round(referenceTwips * fraction / TWIPS_PER_PIXEL),
10162
10397
  resolvedPercentage: true
10163
10398
  };
10164
10399
  }
@@ -10233,10 +10468,20 @@ function textBoxWidth(props) {
10233
10468
  value: typeof raw === "number" ? raw * TWIPS_PER_PIXEL : DEFAULT_TEXT_BOX_WIDTH_TWIPS
10234
10469
  };
10235
10470
  }
10471
+ function borderLineStyle(authored) {
10472
+ switch (authored) {
10473
+ case "dashed":
10474
+ case "dotted":
10475
+ case "double":
10476
+ return authored;
10477
+ default:
10478
+ return "single";
10479
+ }
10480
+ }
10236
10481
  function compileBorders(border3, ctx) {
10237
10482
  if (!border3) return void 0;
10238
10483
  const side = (value) => value ? {
10239
- style: String(value.style ?? "single"),
10484
+ style: value.style === "none" ? "none" : borderLineStyle(value.style),
10240
10485
  // Points to eighths of a point, never thinner than the hairline a
10241
10486
  // reader can actually see.
10242
10487
  sizeEighthPoints: value.width !== void 0 ? Math.max(1, Math.round(value.width * 8)) : 1,
@@ -10312,7 +10557,9 @@ function compileToc(component, scope) {
10312
10557
  formatting: {
10313
10558
  alignment: "left",
10314
10559
  spacing: {
10315
- beforeTwips: ctx.theme.componentDefaults?.heading?.spacing?.before ?? 240,
10560
+ beforeTwips: wholeTwips(
10561
+ ctx.theme.componentDefaults?.heading?.spacing?.before ?? 240
10562
+ ),
10316
10563
  // ~9pt, which separates the title from the list clearly enough to
10317
10564
  // read as a heading rather than a first entry.
10318
10565
  afterTwips: 180
@@ -10340,12 +10587,6 @@ function compileToc(component, scope) {
10340
10587
  var DEFAULT_DIVIDER_SPACING_PT = 6;
10341
10588
  var DEFAULT_DIVIDER_THICKNESS_PT = 1;
10342
10589
  var DIVIDER_LINE_TWIPS = 20;
10343
- var DIVIDER_BORDER_STYLE = {
10344
- solid: "single",
10345
- dashed: "dashed",
10346
- dotted: "dotted",
10347
- double: "double"
10348
- };
10349
10590
  function compileDivider(component, scope) {
10350
10591
  const { ctx, path: path3 } = scope;
10351
10592
  const props = component.props ?? {};
@@ -10374,10 +10615,10 @@ function compileDivider(component, scope) {
10374
10615
  lineTwips: DIVIDER_LINE_TWIPS,
10375
10616
  lineRule: "exact"
10376
10617
  },
10377
- ...dividerIndent(props, ctx, path3),
10618
+ ...dividerIndent(props, scope),
10378
10619
  borders: {
10379
10620
  bottom: {
10380
- style: DIVIDER_BORDER_STYLE[String(props.style)] ?? "single",
10621
+ style: borderLineStyle(props.style),
10381
10622
  sizeEighthPoints: pointsToEighthPoints(thicknessPt),
10382
10623
  color: irColor(
10383
10624
  resolveColor(String(props.color ?? "border"), ctx.theme)
@@ -10391,14 +10632,10 @@ function compileDivider(component, scope) {
10391
10632
  }
10392
10633
  ];
10393
10634
  }
10394
- function dividerIndent(props, ctx, path3) {
10635
+ function dividerIndent(props, scope) {
10636
+ const { ctx, path: path3, measureTwips } = scope;
10395
10637
  const width = props.width;
10396
10638
  if (width === void 0) return {};
10397
- const page = getPageSetup(ctx.theme);
10398
- const measureTwips = Math.max(
10399
- 0,
10400
- page.size.width - page.margin.left - page.margin.right
10401
- );
10402
10639
  let usedTwips;
10403
10640
  if (typeof width === "number" && Number.isFinite(width)) {
10404
10641
  usedTwips = pointsToTwips2(width);
@@ -10442,8 +10679,8 @@ function compileStatistic(component, scope) {
10442
10679
  const alignment2 = compileAlignment(props.alignment) ?? "center";
10443
10680
  ctx.usesStatistic = true;
10444
10681
  const spacing = props.spacing ? {
10445
- ...props.spacing.before !== void 0 ? { beforeTwips: props.spacing.before } : {},
10446
- ...props.spacing.after !== void 0 ? { afterTwips: props.spacing.after } : {}
10682
+ ...props.spacing.before !== void 0 ? { beforeTwips: wholeTwips(props.spacing.before) } : {},
10683
+ ...props.spacing.after !== void 0 ? { afterTwips: wholeTwips(props.spacing.after) } : {}
10447
10684
  } : void 0;
10448
10685
  if (props.format !== void 0) {
10449
10686
  warnOnce(
@@ -10554,7 +10791,7 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
10554
10791
  }
10555
10792
  blocks.push(
10556
10793
  paragraphNode(
10557
- { ctx, path: itemPath, id: `${scope.id}:i${index}` },
10794
+ { ...scope, path: itemPath, id: `${scope.id}:i${index}` },
10558
10795
  revision === void 0 ? parseInline(text, {
10559
10796
  base,
10560
10797
  hyperlinks: true,
@@ -11068,7 +11305,7 @@ function paragraphFormatting(props, ctx, extra = {}) {
11068
11305
  if (Array.isArray(props.tabStops) && props.tabStops.length > 0) {
11069
11306
  formatting.tabStops = props.tabStops.map(
11070
11307
  (stop) => ({
11071
- positionTwips: stop.position,
11308
+ positionTwips: wholeTwips(stop.position),
11072
11309
  type: stop.type ?? "left",
11073
11310
  ...stop.leader ? { leader: stop.leader } : {}
11074
11311
  })
@@ -11117,7 +11354,10 @@ function compileSpacing(props, ctx) {
11117
11354
  }
11118
11355
  function compileLineSpacing(value) {
11119
11356
  if (typeof value === "number") {
11120
- return { lineTwips: value * SINGLE_LINE_TWIPS, lineRule: "auto" };
11357
+ return {
11358
+ lineTwips: wholeTwips(value * SINGLE_LINE_TWIPS),
11359
+ lineRule: "auto"
11360
+ };
11121
11361
  }
11122
11362
  if (typeof value !== "object" || value === null) return void 0;
11123
11363
  const { type, value: amount } = value;
@@ -11138,7 +11378,7 @@ function compileLineSpacing(value) {
11138
11378
  };
11139
11379
  case "multiple":
11140
11380
  return {
11141
- ...amount !== void 0 ? { lineTwips: amount * SINGLE_LINE_TWIPS } : {},
11381
+ ...amount !== void 0 ? { lineTwips: wholeTwips(amount * SINGLE_LINE_TWIPS) } : {},
11142
11382
  lineRule: "auto"
11143
11383
  };
11144
11384
  default:
@@ -11201,7 +11441,7 @@ function compileVisual(component, scope) {
11201
11441
  );
11202
11442
  if (!group) return [];
11203
11443
  const size = visualPlacementPixels(props, group.canvas, {
11204
- widthPx: Math.round(twipsToPixels(getAvailableWidthTwips(ctx.theme))),
11444
+ widthPx: Math.round(twipsToPixels(scope.measureTwips)),
11205
11445
  heightPx: Math.round(twipsToPixels(getAvailableHeightTwips(ctx.theme)))
11206
11446
  });
11207
11447
  const drawing = {
@@ -11316,7 +11556,7 @@ function compileImage2(component, scope) {
11316
11556
  `Alt text is not written to the document: the DOCX pipeline has never emitted \`wp:docPr\` descriptions, so the image at ${path3} will have none. The text is preserved in the IR.`
11317
11557
  );
11318
11558
  }
11319
- const size = imagePixelSize(props, loaded, ctx);
11559
+ const size = imagePixelSize(props, loaded, scope);
11320
11560
  const resourceId = declareResource(loaded, mediaType, ctx);
11321
11561
  const image = {
11322
11562
  kind: "image",
@@ -11426,8 +11666,7 @@ function compileChart(component, scope) {
11426
11666
  return [];
11427
11667
  }
11428
11668
  });
11429
- const page = getPageSetup(ctx.theme);
11430
- const contentWidthInches = (page.size.width - page.margin.left - page.margin.right) / 1440;
11669
+ const contentWidthInches = scope.measureTwips / 1440;
11431
11670
  const widthInches = typeof props.width === "number" ? props.width : contentWidthInches;
11432
11671
  const heightInches = typeof props.height === "number" ? props.height : DEFAULT_CHART_HEIGHT_INCHES;
11433
11672
  const chart = {
@@ -11527,12 +11766,13 @@ function wrapType(value) {
11527
11766
  return "square";
11528
11767
  }
11529
11768
  }
11530
- function imagePixelSize(props, loaded, ctx) {
11769
+ function imagePixelSize(props, loaded, scope) {
11770
+ const { ctx } = scope;
11531
11771
  const widthRef = props.widthRelativeTo === "page" ? "page" : "content";
11532
11772
  const heightRef = props.heightRelativeTo === "page" ? "page" : "content";
11533
11773
  const availableWidthPx = Math.round(
11534
11774
  twipsToPixels(
11535
- widthRef === "page" ? getPageWidthTwips(ctx.theme) : getAvailableWidthTwips(ctx.theme)
11775
+ widthRef === "page" ? getPageWidthTwips(ctx.theme) : scope.measureTwips
11536
11776
  )
11537
11777
  );
11538
11778
  const availableHeightPx = Math.round(
@@ -11599,8 +11839,8 @@ function compileFrame(floating, ctx, path3) {
11599
11839
  const useAbsolute = hasHorizontalOffset || hasVerticalOffset;
11600
11840
  const frame = {
11601
11841
  // 2in by 1in when the author gave no size.
11602
- widthTwips: floating.width || 2880,
11603
- heightTwips: floating.height || 1440,
11842
+ widthTwips: wholeTwips(floating.width || 2880),
11843
+ heightTwips: wholeTwips(floating.height || 1440),
11604
11844
  anchorHorizontal: floating.horizontalPosition?.relative || "page",
11605
11845
  anchorVertical: floating.verticalPosition?.relative || "page",
11606
11846
  ...floating.wrap?.type ? { wrap: floating.wrap.type } : {}
@@ -11613,17 +11853,21 @@ function compileFrame(floating, ctx, path3) {
11613
11853
  if (typeof rawX === "string" || typeof rawY === "string") {
11614
11854
  const hRelative = floating.horizontalPosition?.relative;
11615
11855
  const vRelative = floating.verticalPosition?.relative;
11616
- frame.xTwips = resolveOffsetTwips(
11617
- rawX,
11618
- hRelative && hRelative !== "page" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getPageWidthTwips(ctx.theme, ctx.themeName)
11856
+ frame.xTwips = wholeTwips(
11857
+ resolveOffsetTwips(
11858
+ rawX,
11859
+ hRelative && hRelative !== "page" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getPageWidthTwips(ctx.theme, ctx.themeName)
11860
+ )
11619
11861
  );
11620
- frame.yTwips = resolveOffsetTwips(
11621
- rawY,
11622
- vRelative && vRelative !== "page" ? getAvailableHeightTwips(ctx.theme, ctx.themeName) : getPageHeightTwips(ctx.theme, ctx.themeName)
11862
+ frame.yTwips = wholeTwips(
11863
+ resolveOffsetTwips(
11864
+ rawY,
11865
+ vRelative && vRelative !== "page" ? getAvailableHeightTwips(ctx.theme, ctx.themeName) : getPageHeightTwips(ctx.theme, ctx.themeName)
11866
+ )
11623
11867
  );
11624
11868
  } else {
11625
- frame.xTwips = rawX;
11626
- frame.yTwips = rawY;
11869
+ frame.xTwips = wholeTwips(rawX);
11870
+ frame.yTwips = wholeTwips(rawY);
11627
11871
  }
11628
11872
  return frame;
11629
11873
  }
@@ -11719,7 +11963,10 @@ function compileTable(component, scope) {
11719
11963
  source,
11720
11964
  ctx.theme,
11721
11965
  ctx.themeName,
11722
- { onWarning: (code, message) => warnOnce(ctx, "table", message, code) }
11966
+ {
11967
+ onWarning: (code, message) => warnOnce(ctx, "table", message, code),
11968
+ measureTwips: scope.measureTwips
11969
+ }
11723
11970
  );
11724
11971
  if (model.overflow) {
11725
11972
  warnOnce(
@@ -11737,6 +11984,7 @@ function compileTable(component, scope) {
11737
11984
  ...model.header ? [
11738
11985
  compileTableRow(
11739
11986
  model.header,
11987
+ model.columnGrid.values,
11740
11988
  style.headerParagraph,
11741
11989
  style.tableHeader,
11742
11990
  {
@@ -11749,13 +11997,19 @@ function compileTable(component, scope) {
11749
11997
  )
11750
11998
  ] : [],
11751
11999
  ...model.rows.map(
11752
- (row, index) => compileTableRow(row, style.cellParagraph, style.tableCell, {
11753
- ctx,
11754
- path: `${path3}.rows[${index}]`,
11755
- id: `${scope.id}:r${index}`,
11756
- ...row.tableHeader !== void 0 ? { isHeader: row.tableHeader } : {},
11757
- ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {}
11758
- })
12000
+ (row, index) => compileTableRow(
12001
+ row,
12002
+ model.columnGrid.values,
12003
+ style.cellParagraph,
12004
+ style.tableCell,
12005
+ {
12006
+ ctx,
12007
+ path: `${path3}.rows[${index}]`,
12008
+ id: `${scope.id}:r${index}`,
12009
+ ...row.tableHeader !== void 0 ? { isHeader: row.tableHeader } : {},
12010
+ ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {}
12011
+ }
12012
+ )
11759
12013
  )
11760
12014
  ];
11761
12015
  ctx.features.require("tables", path3);
@@ -11833,7 +12087,7 @@ function tableBlocker(model, path3) {
11833
12087
  }
11834
12088
  return void 0;
11835
12089
  }
11836
- function compileTableRow(row, spacing, baseStyle, scope) {
12090
+ function compileTableRow(row, columnTwips, spacing, baseStyle, scope) {
11837
12091
  const { ctx } = scope;
11838
12092
  const rowRevision = row.revision;
11839
12093
  const rowMark = rowRevision ? {
@@ -11853,6 +12107,7 @@ function compileTableRow(row, spacing, baseStyle, scope) {
11853
12107
  cells: row.cells.map(
11854
12108
  (cell, colIndex) => compileTableCell(
11855
12109
  cell,
12110
+ columnTwips[colIndex] ?? 0,
11856
12111
  spacing,
11857
12112
  baseStyle,
11858
12113
  row.keepNext,
@@ -11874,7 +12129,17 @@ function compileTableRow(row, spacing, baseStyle, scope) {
11874
12129
  ...scope.cantSplit !== void 0 ? { cantSplit: scope.cantSplit } : {}
11875
12130
  };
11876
12131
  }
11877
- function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision, markRevision) {
12132
+ function compileTableCell(cell, columnTwips, spacing, baseStyle, keepNext, partScope, rowRevision, markRevision) {
12133
+ const margins = !cell.missing && cell.padding ? {
12134
+ topTwips: pointsToTwips2(cell.padding.top),
12135
+ bottomTwips: pointsToTwips2(cell.padding.bottom),
12136
+ leftTwips: pointsToTwips2(cell.padding.left),
12137
+ rightTwips: pointsToTwips2(cell.padding.right)
12138
+ } : void 0;
12139
+ const scope = {
12140
+ ...partScope,
12141
+ measureTwips: cellMeasureTwips(columnTwips, margins)
12142
+ };
11878
12143
  const cellLine = compileLineSpacing(cell.font?.lineSpacing);
11879
12144
  const paragraph2 = {
11880
12145
  kind: "paragraph",
@@ -11903,14 +12168,7 @@ function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision
11903
12168
  ...cell.missing ? {} : {
11904
12169
  verticalAlign: verticalAlign(cell.verticalAlignment),
11905
12170
  ...cell.backgroundColor !== void 0 && cell.backgroundColor !== "transparent" ? { shading: { fill: { hex: cell.backgroundColor } } } : {},
11906
- ...cell.padding ? {
11907
- margins: {
11908
- topTwips: pointsToTwips2(cell.padding.top),
11909
- bottomTwips: pointsToTwips2(cell.padding.bottom),
11910
- leftTwips: pointsToTwips2(cell.padding.left),
11911
- rightTwips: pointsToTwips2(cell.padding.right)
11912
- }
11913
- } : {}
12171
+ ...margins ? { margins } : {}
11914
12172
  },
11915
12173
  borders: {
11916
12174
  top: compileTableBorder(cell.borders.top),
@@ -11943,10 +12201,17 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
11943
12201
  return wrap(cellVisual(content, scope));
11944
12202
  }
11945
12203
  if (content.name !== "paragraph") {
12204
+ const text2 = unrenderedComponentText(content.name);
12205
+ warnOnce(
12206
+ ctx,
12207
+ "table",
12208
+ `${scope.path} holds a "${content.name}", which a table cell cannot render, so it shows "${text2}" instead. A cell takes a string, a paragraph, an image, a visual or a highcharts chart; set statistics side by side with a columns component.`,
12209
+ "W_UNSUPPORTED_CELL_CONTENT"
12210
+ );
11946
12211
  return wrap([
11947
12212
  {
11948
12213
  kind: "text",
11949
- text: `[Unsupported component type: ${content.name}]`,
12214
+ text: text2,
11950
12215
  formatting: placeholderFormatting(base)
11951
12216
  }
11952
12217
  ]);
@@ -12933,9 +13198,10 @@ function docxBlockContext(document, theme, path3, sources) {
12933
13198
  page.size ?? theme.page.size
12934
13199
  );
12935
13200
  const overrides = isBlockRecord(page.margins) ? page.margins : {};
13201
+ const gutter = "gutter" in margins ? margins.gutter : 0;
12936
13202
  const width = Object.keys(page).length ? Math.max(
12937
13203
  0,
12938
- dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440)
13204
+ dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440) - Number(overrides.gutter ?? gutter ?? 0)
12939
13205
  ) : getAvailableWidthTwips(theme);
12940
13206
  const height = Object.keys(page).length ? Math.max(
12941
13207
  0,
@@ -13143,6 +13409,11 @@ init_widthUtils();
13143
13409
  import probe2 from "probe-image-size";
13144
13410
 
13145
13411
  // src/quality/text-inventory.ts
13412
+ import {
13413
+ unrenderedComponentText as unrenderedComponentText2
13414
+ } from "@json-to-office/quality";
13415
+ import { TABLE_CELL_COMPONENTS } from "@json-to-office/shared-docx";
13416
+ var CELL_COMPONENTS = new Set(TABLE_CELL_COMPONENTS);
13146
13417
  function tocDepth(depth) {
13147
13418
  const range = asRecord(depth) ?? {};
13148
13419
  const from = typeof range.from === "number" ? range.from : 1;
@@ -13191,7 +13462,14 @@ function collectDocxTextInventory(children, basePath = "/children") {
13191
13462
  add(`${path3}/content`, rec.content, cellRole, extra);
13192
13463
  else {
13193
13464
  const inner = asRecord(rec.content);
13194
- if (inner)
13465
+ if (inner && typeof inner.name === "string" && !CELL_COMPONENTS.has(inner.name)) {
13466
+ add(
13467
+ `${path3}/content`,
13468
+ unrenderedComponentText2(inner.name),
13469
+ cellRole,
13470
+ { ...extra, optional: true, unrendered: inner.name }
13471
+ );
13472
+ } else if (inner)
13195
13473
  visitNode(inner, `${path3}/content`, { ...inherited, inCell: true });
13196
13474
  }
13197
13475
  return;
@@ -13492,7 +13770,7 @@ function pageBox(theme, themeName, pageOverride) {
13492
13770
  return {
13493
13771
  availableWidthTwips: Math.max(
13494
13772
  0,
13495
- page.size.width - page.margin.left - page.margin.right
13773
+ page.size.width - page.margin.left - page.margin.right - (page.margin.gutter ?? 0)
13496
13774
  ),
13497
13775
  pageBottomTwips: page.size.height
13498
13776
  };