@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
package/dist/index.js CHANGED
@@ -1810,15 +1810,15 @@ function convertLineSpacing(lineSpacing) {
1810
1810
  lineRule2 = "auto";
1811
1811
  break;
1812
1812
  case "atLeast":
1813
- line = value !== void 0 ? value * TWIPS_PER_POINT : void 0;
1813
+ line = value !== void 0 ? pointsToTwips(value) : void 0;
1814
1814
  lineRule2 = "atLeast";
1815
1815
  break;
1816
1816
  case "exactly":
1817
- line = value !== void 0 ? value * TWIPS_PER_POINT : void 0;
1817
+ line = value !== void 0 ? pointsToTwips(value) : void 0;
1818
1818
  lineRule2 = "exact";
1819
1819
  break;
1820
1820
  case "multiple":
1821
- line = value !== void 0 ? value * SINGLE_LINE_SPACING_TWIPS : void 0;
1821
+ line = value !== void 0 ? Math.round(value * SINGLE_LINE_SPACING_TWIPS) : void 0;
1822
1822
  lineRule2 = "auto";
1823
1823
  break;
1824
1824
  default:
@@ -2014,6 +2014,7 @@ var init_layoutUtils = __esm({
2014
2014
  if (themeConfig?.page) {
2015
2015
  const margins = themeConfig.page.margins || {};
2016
2016
  const dimensions = getPageDimensions(themeConfig.page.size);
2017
+ const gutter = margins.gutter;
2017
2018
  return {
2018
2019
  size: {
2019
2020
  width: dimensions.width,
@@ -2024,7 +2025,13 @@ var init_layoutUtils = __esm({
2024
2025
  top: margins.top ?? defaultMargins.top ?? 1440,
2025
2026
  right: margins.right ?? defaultMargins.right ?? 1440,
2026
2027
  bottom: margins.bottom ?? defaultMargins.bottom ?? 1440,
2027
- left: margins.left ?? defaultMargins.left ?? 1440
2028
+ left: margins.left ?? defaultMargins.left ?? 1440,
2029
+ // How far the running head and foot sit from the page edge. Left out,
2030
+ // each renderer writes its own default — docx.js 708, office-open 851
2031
+ // and 992 — in place of the distance the theme states.
2032
+ header: margins.header ?? defaultMargins.header ?? 720,
2033
+ footer: margins.footer ?? defaultMargins.footer ?? 720,
2034
+ ...gutter !== void 0 && { gutter }
2028
2035
  }
2029
2036
  };
2030
2037
  }
@@ -2040,50 +2047,15 @@ var init_layoutUtils = __esm({
2040
2047
  top: defaultMargins.top ?? 1440,
2041
2048
  right: defaultMargins.right ?? 1440,
2042
2049
  bottom: defaultMargins.bottom ?? 1440,
2043
- left: defaultMargins.left ?? 1440
2050
+ left: defaultMargins.left ?? 1440,
2051
+ header: defaultMargins.header ?? 720,
2052
+ footer: defaultMargins.footer ?? 720
2044
2053
  }
2045
2054
  };
2046
2055
  };
2047
2056
  }
2048
2057
  });
2049
2058
 
2050
- // src/utils/generationContext.ts
2051
- import { AsyncLocalStorage } from "async_hooks";
2052
- import { isAbsolute, resolve } from "path";
2053
- function runWithBaseDir(baseDir, callback) {
2054
- return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
2055
- }
2056
- function resolveFromBaseDir(filePath) {
2057
- const base = baseDirStorage.getStore();
2058
- if (!base || isAbsolute(filePath)) return filePath;
2059
- return resolve(base, filePath);
2060
- }
2061
- function runWithWarnings(warnings, callback) {
2062
- return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
2063
- }
2064
- function reportWarning(component, code, message, context) {
2065
- const warnings = warningsStorage.getStore();
2066
- if (warnings) {
2067
- warnings.push({
2068
- component,
2069
- message,
2070
- severity: "warning",
2071
- context: { code, ...context }
2072
- });
2073
- return;
2074
- }
2075
- console.warn(`[json-to-docx] [${code}] ${message}`);
2076
- }
2077
- var generationDateStorage, baseDirStorage, warningsStorage;
2078
- var init_generationContext = __esm({
2079
- "src/utils/generationContext.ts"() {
2080
- "use strict";
2081
- generationDateStorage = new AsyncLocalStorage();
2082
- baseDirStorage = new AsyncLocalStorage();
2083
- warningsStorage = new AsyncLocalStorage();
2084
- }
2085
- });
2086
-
2087
2059
  // src/themes/json/parser.ts
2088
2060
  import { validateThemeJson } from "@json-to-office/shared-docx/validation/unified";
2089
2061
  var ThemeValidationError, ThemeParseError, ThemeParser;
@@ -2416,7 +2388,8 @@ function getAvailableWidthTwips(theme, themeName) {
2416
2388
  const width = page?.size?.width || 0;
2417
2389
  const left = page?.margin?.left || 0;
2418
2390
  const right = page?.margin?.right || 0;
2419
- return Math.max(0, width - left - right);
2391
+ const gutter = page?.margin?.gutter || 0;
2392
+ return Math.max(0, width - left - right - gutter);
2420
2393
  }
2421
2394
  function getPageWidthTwips(theme, themeName) {
2422
2395
  const page = getPageSetup(theme, themeName);
@@ -2451,6 +2424,13 @@ function resolveOffsetTwips(value, referenceTwips) {
2451
2424
  }
2452
2425
  return Math.round(referenceTwips * parseFloat(match[1]) / 100);
2453
2426
  }
2427
+ function splitTwips(count, totalTwips) {
2428
+ const edge = (index) => Math.round(totalTwips * index / count);
2429
+ return Array.from(
2430
+ { length: count },
2431
+ (_, index) => edge(index + 1) - edge(index)
2432
+ );
2433
+ }
2454
2434
  function relativeLengthToTwips(value, availableWidthTwips) {
2455
2435
  if (typeof value === "number") {
2456
2436
  return pointsToTwips(value);
@@ -2467,6 +2447,43 @@ var init_widthUtils = __esm({
2467
2447
  }
2468
2448
  });
2469
2449
 
2450
+ // src/utils/generationContext.ts
2451
+ import { AsyncLocalStorage } from "async_hooks";
2452
+ import { isAbsolute, resolve } from "path";
2453
+ function runWithBaseDir(baseDir, callback) {
2454
+ return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
2455
+ }
2456
+ function resolveFromBaseDir(filePath) {
2457
+ const base = baseDirStorage.getStore();
2458
+ if (!base || isAbsolute(filePath)) return filePath;
2459
+ return resolve(base, filePath);
2460
+ }
2461
+ function runWithWarnings(warnings, callback) {
2462
+ return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
2463
+ }
2464
+ function reportWarning(component, code, message, context) {
2465
+ const warnings = warningsStorage.getStore();
2466
+ if (warnings) {
2467
+ warnings.push({
2468
+ component,
2469
+ message,
2470
+ severity: "warning",
2471
+ context: { code, ...context }
2472
+ });
2473
+ return;
2474
+ }
2475
+ console.warn(`[json-to-docx] [${code}] ${message}`);
2476
+ }
2477
+ var generationDateStorage, baseDirStorage, warningsStorage;
2478
+ var init_generationContext = __esm({
2479
+ "src/utils/generationContext.ts"() {
2480
+ "use strict";
2481
+ generationDateStorage = new AsyncLocalStorage();
2482
+ baseDirStorage = new AsyncLocalStorage();
2483
+ warningsStorage = new AsyncLocalStorage();
2484
+ }
2485
+ });
2486
+
2470
2487
  // src/utils/imageUtils.ts
2471
2488
  import { readFileSync } from "fs";
2472
2489
  import { availableParallelism } from "os";
@@ -2762,7 +2779,7 @@ var init_imageUtils = __esm({
2762
2779
  });
2763
2780
 
2764
2781
  // src/ir/types.ts
2765
- var TWIPS_PER_INCH, TWIPS_PER_POINT2, EMU_PER_INCH, EMU_PER_TWIP, DOCX_IR_SCHEMA_VERSION;
2782
+ var TWIPS_PER_INCH, TWIPS_PER_POINT2, EMU_PER_INCH, EMU_PER_TWIP, DOCX_IR_SCHEMA_VERSION, DOCX_IR_BORDER_STYLES;
2766
2783
  var init_types = __esm({
2767
2784
  "src/ir/types.ts"() {
2768
2785
  "use strict";
@@ -2771,6 +2788,35 @@ var init_types = __esm({
2771
2788
  EMU_PER_INCH = 914400;
2772
2789
  EMU_PER_TWIP = EMU_PER_INCH / TWIPS_PER_INCH;
2773
2790
  DOCX_IR_SCHEMA_VERSION = 1;
2791
+ DOCX_IR_BORDER_STYLES = [
2792
+ "single",
2793
+ "dashDotStroked",
2794
+ "dashed",
2795
+ "dashSmallGap",
2796
+ "dotDash",
2797
+ "dotDotDash",
2798
+ "dotted",
2799
+ "double",
2800
+ "doubleWave",
2801
+ "inset",
2802
+ "nil",
2803
+ "none",
2804
+ "outset",
2805
+ "thick",
2806
+ "thickThinLargeGap",
2807
+ "thickThinMediumGap",
2808
+ "thickThinSmallGap",
2809
+ "thinThickLargeGap",
2810
+ "thinThickMediumGap",
2811
+ "thinThickSmallGap",
2812
+ "thinThickThinLargeGap",
2813
+ "thinThickThinMediumGap",
2814
+ "thinThickThinSmallGap",
2815
+ "threeDEmboss",
2816
+ "threeDEngrave",
2817
+ "triple",
2818
+ "wave"
2819
+ ];
2774
2820
  }
2775
2821
  });
2776
2822
 
@@ -2779,6 +2825,9 @@ import { createHash as createHash2 } from "crypto";
2779
2825
  function pointsToTwips2(points) {
2780
2826
  return Math.round(points * TWIPS_PER_POINT2);
2781
2827
  }
2828
+ function wholeTwips(twips2) {
2829
+ return Math.round(twips2);
2830
+ }
2782
2831
  function pointsToHalfPoints(points) {
2783
2832
  return Math.round(points * 2);
2784
2833
  }
@@ -2794,8 +2843,8 @@ function inchesToEmu(inches) {
2794
2843
  function pointsToEmu(points) {
2795
2844
  return Math.round(points * EMU_PER_INCH / 72);
2796
2845
  }
2797
- function twipsToEmu(twips) {
2798
- return Math.round(twips * EMU_PER_TWIP);
2846
+ function twipsToEmu(twips2) {
2847
+ return Math.round(twips2 * EMU_PER_TWIP);
2799
2848
  }
2800
2849
  function pixelsToEmu(pixels) {
2801
2850
  return Math.round(pixels * 9525);
@@ -2803,8 +2852,8 @@ function pixelsToEmu(pixels) {
2803
2852
  function emuToPixels(emu) {
2804
2853
  return emu / 9525;
2805
2854
  }
2806
- function twipsToPixels(twips) {
2807
- return twips / TWIPS_PER_INCH * 96;
2855
+ function twipsToPixels(twips2) {
2856
+ return twips2 / TWIPS_PER_INCH * 96;
2808
2857
  }
2809
2858
  function irColor(hex) {
2810
2859
  return { hex: hex.startsWith("#") ? hex.slice(1) : hex };
@@ -2830,7 +2879,6 @@ import {
2830
2879
  AlignmentType,
2831
2880
  BookmarkEnd,
2832
2881
  BookmarkStart,
2833
- BorderStyle,
2834
2882
  ColumnBreak,
2835
2883
  CommentRangeEnd,
2836
2884
  CommentRangeStart,
@@ -3276,7 +3324,8 @@ function emitTable(block2, resources = /* @__PURE__ */ new Map()) {
3276
3324
  },
3277
3325
  layout: block2.layout === "fixed" ? TableLayoutType.FIXED : TableLayoutType.AUTOFIT,
3278
3326
  // An empty grid is a table with nothing to say about its columns, which is
3279
- // not the same as one whose columns are all zero wide.
3327
+ // not the same as one whose columns are all zero wide. The compiler hands
3328
+ // over every grid in twips, the unit `columnWidths` takes.
3280
3329
  ...block2.columnGrid.values.length > 0 ? { columnWidths: block2.columnGrid.values } : {},
3281
3330
  ...block2.borders ? {
3282
3331
  borders: {
@@ -3370,7 +3419,10 @@ function emitTableCell(cell, resources) {
3370
3419
  function emitBorder(border3) {
3371
3420
  if (!border3) return void 0;
3372
3421
  return {
3373
- style: BORDER_STYLE[border3.style] ?? BorderStyle.SINGLE,
3422
+ // Already OOXML's word, and docx.js's `BorderStyle` values are exactly
3423
+ // those words. Mapping unknown ones to `single` here is what once hid an
3424
+ // authoring word the IR should never have carried.
3425
+ style: border3.style,
3374
3426
  size: border3.sizeEighthPoints ?? 0,
3375
3427
  color: border3.color?.hex ?? "000000",
3376
3428
  // Stated only when the IR states it, so every border that predates the
@@ -3379,7 +3431,7 @@ function emitBorder(border3) {
3379
3431
  ...border3.spacePoints !== void 0 ? { space: border3.spacePoints } : {}
3380
3432
  };
3381
3433
  }
3382
- var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, ThemedTableOfContents, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
3434
+ var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, ThemedTableOfContents, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION;
3383
3435
  var init_emit = __esm({
3384
3436
  "src/renderers/docxjs/emit.ts"() {
3385
3437
  "use strict";
@@ -3428,20 +3480,10 @@ var init_emit = __esm({
3428
3480
  inside: RelativeVerticalPosition.INSIDE,
3429
3481
  outside: RelativeVerticalPosition.OUTSIDE
3430
3482
  };
3431
- BORDER_STYLE = {
3432
- none: BorderStyle.NONE,
3433
- single: BorderStyle.SINGLE,
3434
- double: BorderStyle.DOUBLE,
3435
- dashed: BorderStyle.DASHED,
3436
- dotted: BorderStyle.DOTTED
3437
- };
3438
3483
  }
3439
3484
  });
3440
3485
 
3441
3486
  // src/renderers/docxjs/styles.ts
3442
- import {
3443
- BorderStyle as BorderStyle2
3444
- } from "docx";
3445
3487
  function runProperties(formatting) {
3446
3488
  const out = {};
3447
3489
  if (formatting.fontFamily !== void 0) out.font = formatting.fontFamily;
@@ -3450,7 +3492,7 @@ function runProperties(formatting) {
3450
3492
  }
3451
3493
  if (formatting.color) out.color = formatting.color.hex;
3452
3494
  if (formatting.bold !== void 0) out.bold = formatting.bold;
3453
- if (formatting.italic !== void 0) out.italic = formatting.italic;
3495
+ if (formatting.italic !== void 0) out.italics = formatting.italic;
3454
3496
  if (formatting.underline) {
3455
3497
  out.underline = {
3456
3498
  type: formatting.underline.type,
@@ -3462,7 +3504,9 @@ function runProperties(formatting) {
3462
3504
  if (formatting.subScript) out.subScript = true;
3463
3505
  if (formatting.smallCaps !== void 0) out.smallCaps = formatting.smallCaps;
3464
3506
  if (formatting.allCaps !== void 0) out.allCaps = formatting.allCaps;
3465
- if (formatting.highlight !== void 0) out.highlight = formatting.highlight;
3507
+ if (formatting.highlight !== void 0) {
3508
+ out.highlight = formatting.highlight;
3509
+ }
3466
3510
  if (formatting.characterSpacingTwentieths !== void 0) {
3467
3511
  out.characterSpacing = formatting.characterSpacingTwentieths;
3468
3512
  }
@@ -3476,7 +3520,10 @@ function runProperties(formatting) {
3476
3520
  }
3477
3521
  function border(value) {
3478
3522
  return {
3479
- style: BORDER_STYLE2[value.style] ?? BorderStyle2.SINGLE,
3523
+ // OOXML's word, which is what docx.js's `BorderStyle` values are. A theme
3524
+ // may state any of them; mapping the unlisted ones to `single` drew a
3525
+ // `wave` or `triple` rule as a plain line here and as asked on office-open.
3526
+ style: value.style,
3480
3527
  ...value.sizeEighthPoints !== void 0 ? { size: value.sizeEighthPoints } : {},
3481
3528
  ...value.color ? { color: value.color.hex } : {},
3482
3529
  ...value.spacePoints !== void 0 ? { space: value.spacePoints } : {}
@@ -3589,19 +3636,10 @@ function emitStyles(styles) {
3589
3636
  }
3590
3637
  };
3591
3638
  }
3592
- var BORDER_STYLE2;
3593
3639
  var init_styles2 = __esm({
3594
3640
  "src/renderers/docxjs/styles.ts"() {
3595
3641
  "use strict";
3596
3642
  init_emit();
3597
- BORDER_STYLE2 = {
3598
- none: BorderStyle2.NONE,
3599
- single: BorderStyle2.SINGLE,
3600
- double: BorderStyle2.DOUBLE,
3601
- dashed: BorderStyle2.DASHED,
3602
- dotted: BorderStyle2.DOTTED,
3603
- thick: BorderStyle2.THICK
3604
- };
3605
3643
  }
3606
3644
  });
3607
3645
 
@@ -3780,7 +3818,7 @@ import {
3780
3818
  Header,
3781
3819
  ImageRun,
3782
3820
  Packer,
3783
- LineRuleType as LineRuleType2,
3821
+ LineRuleType,
3784
3822
  Paragraph as Paragraph2
3785
3823
  } from "docx";
3786
3824
  function createDocxJsRenderer() {
@@ -4067,7 +4105,7 @@ function lastBlockIsFrame(section2) {
4067
4105
  function anchorParagraph(anchor) {
4068
4106
  return new Paragraph2({
4069
4107
  children: anchor ? [anchor] : [],
4070
- spacing: { before: 0, after: 0, line: 20, lineRule: LineRuleType2.EXACT }
4108
+ spacing: { before: 0, after: 0, line: 20, lineRule: LineRuleType.EXACT }
4071
4109
  });
4072
4110
  }
4073
4111
  function coreProperties(ir) {
@@ -4240,11 +4278,71 @@ var init_chartParts = __esm({
4240
4278
  }
4241
4279
  });
4242
4280
 
4281
+ // src/renderers/office-open/cellTocs.ts
4282
+ function spliceCellTocs(zip, tocs, stringifyTableOfContents) {
4283
+ if (tocs.length === 0) return;
4284
+ const spliced = /* @__PURE__ */ new Set();
4285
+ for (const entry of zip.getEntries()) {
4286
+ if (!TABLE_PARTS.test(entry.entryName)) continue;
4287
+ const original = entry.getData().toString("utf8");
4288
+ let xml = original;
4289
+ for (const toc of tocs) {
4290
+ const start = markerParagraph(xml, toc.start);
4291
+ if (!start) continue;
4292
+ const end = markerParagraph(xml, toc.end);
4293
+ if (spliced.has(toc) || !end || end.from < start.to) {
4294
+ throw new Error(
4295
+ `the office-open renderer lost track of a table of contents in a table cell (${entry.entryName})`
4296
+ );
4297
+ }
4298
+ xml = xml.slice(0, start.from) + stringifyTableOfContents(
4299
+ toc.alias,
4300
+ toc.options,
4301
+ xml.slice(start.to, end.from)
4302
+ ) + xml.slice(end.to);
4303
+ spliced.add(toc);
4304
+ }
4305
+ if (xml !== original) zip.updateFile(entry, Buffer.from(xml, "utf8"));
4306
+ }
4307
+ if (spliced.size !== tocs.length) {
4308
+ throw new Error(
4309
+ `the office-open renderer placed ${tocs.length} table(s) of contents in table cells and found ${spliced.size}`
4310
+ );
4311
+ }
4312
+ }
4313
+ function markerParagraph(xml, marker) {
4314
+ const at = xml.indexOf(marker);
4315
+ if (at < 0) return void 0;
4316
+ const from = Math.max(
4317
+ xml.lastIndexOf("<w:p>", at),
4318
+ xml.lastIndexOf("<w:p ", at)
4319
+ );
4320
+ const close = xml.indexOf("</w:p>", at);
4321
+ if (xml.indexOf(marker, at + marker.length) >= 0 || from < 0 || close < 0 || xml.lastIndexOf("</w:p>", at) > from) {
4322
+ throw new Error(
4323
+ "the office-open renderer found a table-of-contents marker it did not write"
4324
+ );
4325
+ }
4326
+ return { from, to: close + "</w:p>".length };
4327
+ }
4328
+ var TABLE_PARTS;
4329
+ var init_cellTocs = __esm({
4330
+ "src/renderers/office-open/cellTocs.ts"() {
4331
+ "use strict";
4332
+ TABLE_PARTS = /^word\/(?:document|header\d+|footer\d+)\.xml$/;
4333
+ }
4334
+ });
4335
+
4243
4336
  // src/renderers/office-open/emit.ts
4244
4337
  import { assertNever as assertNever2 } from "@json-to-office/shared/rendering";
4245
4338
  function emptyContext() {
4246
4339
  let next = 1;
4247
- return { pictures: /* @__PURE__ */ new Map(), nextDrawingId: () => next++, charts: [] };
4340
+ return {
4341
+ pictures: /* @__PURE__ */ new Map(),
4342
+ nextDrawingId: () => next++,
4343
+ charts: [],
4344
+ cellTocs: []
4345
+ };
4248
4346
  }
4249
4347
  function simpleField(instruction, cachedText) {
4250
4348
  return {
@@ -4254,16 +4352,26 @@ function simpleField(instruction, cachedText) {
4254
4352
  }
4255
4353
  };
4256
4354
  }
4355
+ function twips(value) {
4356
+ return Math.floor(value);
4357
+ }
4257
4358
  function runProperties2(formatting) {
4258
4359
  if (!formatting) return {};
4259
4360
  const out = {};
4260
4361
  if (formatting.fontFamily) out.font = formatting.fontFamily;
4261
4362
  if (formatting.sizeHalfPoints !== void 0) {
4262
4363
  out.size = formatting.sizeHalfPoints / 2;
4364
+ if (formatting.sizeHalfPoints) out.sizeComplexScript = out.size;
4263
4365
  }
4264
4366
  if (formatting.color) out.color = formatting.color.hex;
4265
- if (formatting.bold !== void 0) out.bold = formatting.bold;
4266
- if (formatting.italic !== void 0) out.italic = formatting.italic;
4367
+ if (formatting.bold !== void 0) {
4368
+ out.bold = formatting.bold;
4369
+ out.boldComplexScript = formatting.bold;
4370
+ }
4371
+ if (formatting.italic !== void 0) {
4372
+ out.italic = formatting.italic;
4373
+ out.italicComplexScript = formatting.italic;
4374
+ }
4267
4375
  if (formatting.underline) {
4268
4376
  out.underline = {
4269
4377
  type: formatting.underline.type,
@@ -4283,7 +4391,9 @@ function runProperties2(formatting) {
4283
4391
  if (formatting.scalePercent !== void 0)
4284
4392
  out.scale = formatting.scalePercent;
4285
4393
  if (formatting.characterSpacingTwentieths !== void 0) {
4286
- out.characterSpacing = formatting.characterSpacingTwentieths;
4394
+ const stated = formatting.characterSpacingTwentieths;
4395
+ const twentieths = twips(stated);
4396
+ out.characterSpacing = twentieths === 0 && stated !== 0 ? "0pt" : twentieths;
4287
4397
  }
4288
4398
  if (formatting.language) out.language = { value: formatting.language };
4289
4399
  if (formatting.noProof !== void 0) out.noProof = formatting.noProof;
@@ -4674,16 +4784,16 @@ function paragraphProperties2(formatting) {
4674
4784
  if (formatting.indent) {
4675
4785
  const indent = {};
4676
4786
  if (formatting.indent.leftTwips !== void 0) {
4677
- indent.left = formatting.indent.leftTwips;
4787
+ indent.left = twips(formatting.indent.leftTwips);
4678
4788
  }
4679
4789
  if (formatting.indent.rightTwips !== void 0) {
4680
- indent.right = formatting.indent.rightTwips;
4790
+ indent.right = twips(formatting.indent.rightTwips);
4681
4791
  }
4682
4792
  if (formatting.indent.firstLineTwips !== void 0) {
4683
- indent.firstLine = formatting.indent.firstLineTwips;
4793
+ indent.firstLine = twips(formatting.indent.firstLineTwips);
4684
4794
  }
4685
4795
  if (formatting.indent.hangingTwips !== void 0) {
4686
- indent.hanging = formatting.indent.hangingTwips;
4796
+ indent.hanging = twips(formatting.indent.hangingTwips);
4687
4797
  }
4688
4798
  out.indent = indent;
4689
4799
  }
@@ -4814,15 +4924,14 @@ function table(value, ctx) {
4814
4924
  // `w:w` is an integer in OOXML. A grid that splits a remainder between
4815
4925
  // unstated columns comes out fractional, and the XML would carry it
4816
4926
  // verbatim; floored here, as the docxjs backend floors it.
4817
- size: value.width.kind === "auto" ? 0 : value.width.kind === "twips" ? Math.floor(value.width.value) : value.width.value,
4927
+ size: value.width.kind === "auto" ? 0 : value.width.kind === "twips" ? twips(value.width.value) : value.width.value,
4818
4928
  type: value.width.kind === "twips" ? "dxa" : value.width.kind === "percent" ? "pct" : "auto"
4819
4929
  },
4820
4930
  layout: value.layout,
4821
4931
  // An empty grid is a table with nothing to say about its columns, which is
4822
- // not the same as one whose columns are all zero wide.
4823
- ...value.columnGrid.values.length > 0 ? {
4824
- columnWidths: value.columnGrid.unit === "twips" ? value.columnGrid.values.map(Math.floor) : value.columnGrid.values
4825
- } : {},
4932
+ // not the same as one whose columns are all zero wide. The compiler hands
4933
+ // over every grid in twips; floored for the same reason as the width.
4934
+ ...value.columnGrid.values.length > 0 ? { columnWidths: value.columnGrid.values.map(twips) } : {},
4826
4935
  ...value.alignment ? { alignment: alignment(value.alignment) } : {},
4827
4936
  ...value.borders ? { borders: borders2(value.borders) } : {},
4828
4937
  ...value.cellMargins ? { margins: cellMargins(value.cellMargins) } : {},
@@ -4833,14 +4942,22 @@ function tableFloat2(floating) {
4833
4942
  return {
4834
4943
  ...floating.horizontalAnchor ? { horizontalAnchor: floating.horizontalAnchor } : {},
4835
4944
  ...floating.verticalAnchor ? { verticalAnchor: floating.verticalAnchor } : {},
4836
- ...floating.absoluteHorizontalPositionTwips !== void 0 ? { absoluteHorizontalPosition: floating.absoluteHorizontalPositionTwips } : {},
4945
+ ...floating.absoluteHorizontalPositionTwips !== void 0 ? {
4946
+ absoluteHorizontalPosition: twips(
4947
+ floating.absoluteHorizontalPositionTwips
4948
+ )
4949
+ } : {},
4837
4950
  ...floating.relativeHorizontalPosition ? { relativeHorizontalPosition: floating.relativeHorizontalPosition } : {},
4838
- ...floating.absoluteVerticalPositionTwips !== void 0 ? { absoluteVerticalPosition: floating.absoluteVerticalPositionTwips } : {},
4951
+ ...floating.absoluteVerticalPositionTwips !== void 0 ? {
4952
+ absoluteVerticalPosition: twips(
4953
+ floating.absoluteVerticalPositionTwips
4954
+ )
4955
+ } : {},
4839
4956
  ...floating.relativeVerticalPosition ? { relativeVerticalPosition: floating.relativeVerticalPosition } : {},
4840
- ...floating.topFromTextTwips !== void 0 ? { topFromText: floating.topFromTextTwips } : {},
4841
- ...floating.rightFromTextTwips !== void 0 ? { rightFromText: floating.rightFromTextTwips } : {},
4842
- ...floating.bottomFromTextTwips !== void 0 ? { bottomFromText: floating.bottomFromTextTwips } : {},
4843
- ...floating.leftFromTextTwips !== void 0 ? { leftFromText: floating.leftFromTextTwips } : {},
4957
+ ...floating.topFromTextTwips !== void 0 ? { topFromText: twips(floating.topFromTextTwips) } : {},
4958
+ ...floating.rightFromTextTwips !== void 0 ? { rightFromText: twips(floating.rightFromTextTwips) } : {},
4959
+ ...floating.bottomFromTextTwips !== void 0 ? { bottomFromText: twips(floating.bottomFromTextTwips) } : {},
4960
+ ...floating.leftFromTextTwips !== void 0 ? { leftFromText: twips(floating.leftFromTextTwips) } : {},
4844
4961
  ...floating.overlap ? { overlap: floating.overlap } : {}
4845
4962
  };
4846
4963
  }
@@ -4848,7 +4965,10 @@ function tableRow(row, ctx) {
4848
4965
  return {
4849
4966
  cells: row.cells.map((cell) => tableCell(cell, ctx)),
4850
4967
  ...row.heightTwips !== void 0 ? {
4851
- height: { value: row.heightTwips, rule: row.heightRule ?? "atLeast" }
4968
+ height: {
4969
+ value: twips(row.heightTwips),
4970
+ rule: row.heightRule ?? "atLeast"
4971
+ }
4852
4972
  } : {},
4853
4973
  ...row.isHeader !== void 0 ? { tableHeader: row.isHeader } : {},
4854
4974
  ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {},
@@ -4856,9 +4976,12 @@ function tableRow(row, ctx) {
4856
4976
  };
4857
4977
  }
4858
4978
  function tableCell(cell, ctx) {
4979
+ const children = cellChildren2(cell, ctx);
4980
+ if (cell.children[cell.children.length - 1]?.kind !== "paragraph")
4981
+ children.push({ paragraph: { children: [] } });
4859
4982
  return {
4860
- children: cell.children.map((child) => block(child, ctx)),
4861
- ...cell.widthTwips !== void 0 ? { width: { size: cell.widthTwips, type: "dxa" } } : {},
4983
+ children,
4984
+ ...cell.widthTwips !== void 0 ? { width: { size: twips(cell.widthTwips), type: "dxa" } } : {},
4862
4985
  ...cell.verticalAlign ? { verticalAlign: cell.verticalAlign } : {},
4863
4986
  ...cell.shading ? { shading: shading(cell.shading) } : {},
4864
4987
  ...cell.margins ? { margins: cellMargins(cell.margins) } : {},
@@ -4868,8 +4991,38 @@ function tableCell(cell, ctx) {
4868
4991
  ...cell.textDirection ? { textDirection: cell.textDirection } : {}
4869
4992
  };
4870
4993
  }
4994
+ function cellChildren2(cell, ctx) {
4995
+ const children = [];
4996
+ for (const child of cell.children) {
4997
+ if (child.kind !== "toc") {
4998
+ children.push(block(child, ctx));
4999
+ continue;
5000
+ }
5001
+ const { alias, entries, ...options } = tableOfContents(child);
5002
+ const index = ctx.cellTocs.length;
5003
+ const toc = {
5004
+ start: cellTocMarker(index, "start"),
5005
+ end: cellTocMarker(index, "end"),
5006
+ alias: String(alias),
5007
+ options
5008
+ };
5009
+ ctx.cellTocs.push(toc);
5010
+ children.push(
5011
+ markerParagraph2(toc.start),
5012
+ ...entries ?? [],
5013
+ markerParagraph2(toc.end)
5014
+ );
5015
+ }
5016
+ return children;
5017
+ }
5018
+ function cellTocMarker(index, edge) {
5019
+ return `\uE000jto-cell-toc-${index}-${edge}\uE000`;
5020
+ }
5021
+ function markerParagraph2(text) {
5022
+ return { paragraph: { children: [{ text }] } };
5023
+ }
4871
5024
  function cellMargins(margins) {
4872
- const side = (value) => value === void 0 ? void 0 : { size: value, type: "dxa" };
5025
+ const side = (value) => value === void 0 ? void 0 : { size: twips(value), type: "dxa" };
4873
5026
  const out = {};
4874
5027
  for (const [name, value] of [
4875
5028
  ["top", margins.topTwips],
@@ -4916,18 +5069,18 @@ function section(value, ctx, closesDocument = false) {
4916
5069
  // Orientation is implied by the width/height pair, which is how this
4917
5070
  // pipeline has always expressed it; stating it as well changes `w:pgSz`.
4918
5071
  size: {
4919
- width: page.widthTwips,
4920
- height: page.heightTwips,
5072
+ width: twips(page.widthTwips),
5073
+ height: twips(page.heightTwips),
4921
5074
  ...page.code !== void 0 ? { code: page.code } : {}
4922
5075
  },
4923
5076
  margin: {
4924
- top: page.margins.topTwips,
4925
- right: page.margins.rightTwips,
4926
- bottom: page.margins.bottomTwips,
4927
- left: page.margins.leftTwips,
4928
- ...page.margins.headerTwips !== void 0 ? { header: page.margins.headerTwips } : {},
4929
- ...page.margins.footerTwips !== void 0 ? { footer: page.margins.footerTwips } : {},
4930
- ...page.margins.gutterTwips !== void 0 ? { gutter: page.margins.gutterTwips } : {}
5077
+ top: twips(page.margins.topTwips),
5078
+ right: twips(page.margins.rightTwips),
5079
+ bottom: twips(page.margins.bottomTwips),
5080
+ left: twips(page.margins.leftTwips),
5081
+ ...page.margins.headerTwips !== void 0 ? { header: twips(page.margins.headerTwips) } : {},
5082
+ ...page.margins.footerTwips !== void 0 ? { footer: twips(page.margins.footerTwips) } : {},
5083
+ ...page.margins.gutterTwips !== void 0 ? { gutter: twips(page.margins.gutterTwips) } : {}
4931
5084
  },
4932
5085
  ...value.properties.pageNumbers ? {
4933
5086
  pageNumbers: {
@@ -4946,17 +5099,23 @@ function section(value, ctx, closesDocument = false) {
4946
5099
  ...columns ? {
4947
5100
  column: {
4948
5101
  count: columns.count,
4949
- ...columns.spaceTwips !== void 0 ? { space: columns.spaceTwips } : {},
5102
+ ...columns.spaceTwips !== void 0 ? { space: twips(columns.spaceTwips) } : {},
4950
5103
  ...columns.separator !== void 0 ? { separate: columns.separator } : {},
4951
5104
  ...columns.equalWidth !== void 0 ? { equalWidth: columns.equalWidth } : {},
4952
5105
  ...columns.widths ? {
4953
5106
  children: columns.widths.map((column) => ({
4954
- width: column.widthTwips,
4955
- ...column.spaceTwips !== void 0 ? { space: column.spaceTwips } : {}
5107
+ width: twips(column.widthTwips),
5108
+ ...column.spaceTwips !== void 0 ? { space: twips(column.spaceTwips) } : {}
4956
5109
  }))
4957
5110
  } : {}
4958
5111
  }
4959
- } : {}
5112
+ } : {},
5113
+ // No document grid, as the IR states none. Left to the backend, every
5114
+ // section gets the one Word's Chinese template sets — a 15.6pt line grid
5115
+ // (`w:type="lines"`, pitch 312) — and every line is laid out on it here
5116
+ // but at its own height on docx.js, which writes no grid type. A field
5117
+ // the backend is not given takes that default, so all three are stated.
5118
+ grid: { type: "default", linePitch: 360, charSpace: 0 }
4960
5119
  },
4961
5120
  children: sectionChildren2(value, ctx, closesDocument),
4962
5121
  ...value.headers ? { headers: headerFooterSet(value.headers, ctx) } : {},
@@ -4974,14 +5133,14 @@ function headerFooterSet(set, ctx) {
4974
5133
  function sectionChildren2(value, ctx, closesDocument = false) {
4975
5134
  const blocks = value.children.map((child) => block(child, ctx));
4976
5135
  const last = value.children[value.children.length - 1];
4977
- if (closesDocument && last?.kind === "paragraph" && last.frame !== void 0)
5136
+ const bookmark = value.bookmark;
5137
+ if (bookmark?.closes && last?.kind !== "paragraph" || closesDocument && last?.kind === "paragraph" && last.frame !== void 0)
4978
5138
  blocks.push({
4979
5139
  paragraph: {
4980
5140
  children: [],
4981
5141
  spacing: { before: 0, after: 0, line: 20, lineRule: "exact" }
4982
5142
  }
4983
5143
  });
4984
- const bookmark = value.bookmark;
4985
5144
  if (!bookmark) return blocks;
4986
5145
  return [
4987
5146
  ...bookmark.opens ? [{ bookmarkStart: { id: bookmark.id, name: bookmark.name } }] : [],
@@ -5004,8 +5163,8 @@ function numberingConfig2(numbering) {
5004
5163
  ...level.indent ? {
5005
5164
  paragraph: {
5006
5165
  indent: {
5007
- ...level.indent.leftTwips !== void 0 ? { left: level.indent.leftTwips } : {},
5008
- ...level.indent.hangingTwips !== void 0 ? { hanging: level.indent.hangingTwips } : {}
5166
+ ...level.indent.leftTwips !== void 0 ? { left: twips(level.indent.leftTwips) } : {},
5167
+ ...level.indent.hangingTwips !== void 0 ? { hanging: twips(level.indent.hangingTwips) } : {}
5009
5168
  }
5010
5169
  }
5011
5170
  } : {},
@@ -5041,12 +5200,11 @@ function builtIn2(style) {
5041
5200
  };
5042
5201
  }
5043
5202
  function emitStyles2(styles) {
5044
- const documentDefaults = {
5045
- ...Object.keys(styles.defaults.run).length > 0 ? { run: runProperties2(styles.defaults.run) } : {},
5046
- ...Object.keys(styles.defaults.paragraph).length > 0 ? { paragraph: paragraphProperties2(styles.defaults.paragraph) } : {}
5047
- };
5048
5203
  const defaults = {
5049
- ...Object.keys(documentDefaults).length > 0 ? { document: documentDefaults } : {}
5204
+ document: {
5205
+ run: Object.keys(styles.defaults.run).length > 0 ? runProperties2(styles.defaults.run) : null,
5206
+ paragraph: Object.keys(styles.defaults.paragraph).length > 0 ? paragraphProperties2(styles.defaults.paragraph) : null
5207
+ }
5050
5208
  };
5051
5209
  for (const slot of [
5052
5210
  "footnoteText",
@@ -5075,7 +5233,7 @@ function emitStyles2(styles) {
5075
5233
  run: runProperties2(style.run)
5076
5234
  }))
5077
5235
  } : {},
5078
- ...Object.keys(defaults).length > 0 ? { default: defaults } : {}
5236
+ default: defaults
5079
5237
  };
5080
5238
  }
5081
5239
  var init_styles3 = __esm({
@@ -5098,10 +5256,15 @@ async function createOfficeOpenDocxRenderer() {
5098
5256
  /* @vite-ignore */
5099
5257
  OFFICE_OPEN_DOCX
5100
5258
  );
5101
- if (typeof backend.generateDocument !== "function") {
5102
- throw new Error(
5103
- `${OFFICE_OPEN_DOCX} does not export generateDocument(); the installed version is not compatible with this adapter.`
5104
- );
5259
+ for (const name of [
5260
+ "generateDocument",
5261
+ "stringifyTableOfContents"
5262
+ ]) {
5263
+ if (typeof backend[name] !== "function") {
5264
+ throw new Error(
5265
+ `${OFFICE_OPEN_DOCX} does not export ${name}(); the installed version is not compatible with this adapter.`
5266
+ );
5267
+ }
5105
5268
  }
5106
5269
  return {
5107
5270
  id: OFFICE_OPEN_DOCX_RENDERER_ID,
@@ -5109,10 +5272,12 @@ async function createOfficeOpenDocxRenderer() {
5109
5272
  capabilities: OFFICE_OPEN_CAPABILITIES,
5110
5273
  async render(ir, options) {
5111
5274
  const charts = [];
5275
+ const cellTocs = [];
5112
5276
  const document = await buildDocumentOptions(
5113
5277
  ir,
5114
5278
  charts,
5115
- options?.svgRasterFallback
5279
+ options?.svgRasterFallback,
5280
+ cellTocs
5116
5281
  );
5117
5282
  const bytes = await backend.generateDocument(document, {
5118
5283
  type: "uint8array"
@@ -5120,9 +5285,10 @@ async function createOfficeOpenDocxRenderer() {
5120
5285
  let raw = Buffer.from(
5121
5286
  bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)
5122
5287
  );
5123
- if (charts.length > 0) {
5288
+ if (charts.length > 0 || cellTocs.length > 0) {
5124
5289
  const zip = new AdmZip4(raw);
5125
5290
  spliceChartParts(zip, charts);
5291
+ spliceCellTocs(zip, cellTocs, backend.stringifyTableOfContents);
5126
5292
  raw = zip.toBuffer();
5127
5293
  }
5128
5294
  if (options?.deterministic === false)
@@ -5139,12 +5305,13 @@ async function createOfficeOpenDocxRenderer() {
5139
5305
  }
5140
5306
  };
5141
5307
  }
5142
- async function buildDocumentOptions(ir, charts = [], svgRasterFallback) {
5308
+ async function buildDocumentOptions(ir, charts = [], svgRasterFallback, cellTocs = []) {
5143
5309
  let nextDrawingId = 1;
5144
5310
  const ctx = {
5145
5311
  pictures: await prepareImages2(ir, svgRasterFallback),
5146
5312
  nextDrawingId: () => nextDrawingId++,
5147
- charts
5313
+ charts,
5314
+ cellTocs
5148
5315
  };
5149
5316
  return {
5150
5317
  styles: emitStyles2(ir.styles),
@@ -5423,6 +5590,7 @@ var init_office_open = __esm({
5423
5590
  "src/renderers/office-open/index.ts"() {
5424
5591
  "use strict";
5425
5592
  init_chartParts();
5593
+ init_cellTocs();
5426
5594
  init_imageUtils();
5427
5595
  init_packageDocument();
5428
5596
  init_emit2();
@@ -6023,7 +6191,7 @@ import {
6023
6191
 
6024
6192
  // src/components/highcharts.ts
6025
6193
  init_colorUtils();
6026
- init_layoutUtils();
6194
+ init_widthUtils();
6027
6195
  import {
6028
6196
  chartFamilyResolver,
6029
6197
  remoteExportNotice,
@@ -6134,8 +6302,7 @@ function placedWidthPt(config, theme) {
6134
6302
  const chart = config.options.chart;
6135
6303
  if (typeof width === "number") return width * POINTS_PER_PIXEL;
6136
6304
  if (typeof width === "string") {
6137
- const page = getPageSetup(theme);
6138
- const contentWidthPt = (page.size.width - page.margin.left - page.margin.right) * POINTS_PER_TWIP;
6305
+ const contentWidthPt = getAvailableWidthTwips(theme) * POINTS_PER_TWIP;
6139
6306
  return parseFloat(width) / 100 * contentWidthPt;
6140
6307
  }
6141
6308
  if (typeof height === "number" && chart.height > 0) {
@@ -6386,6 +6553,7 @@ import {
6386
6553
  assetUnreadableError
6387
6554
  } from "@json-to-office/shared/rendering";
6388
6555
  import { capsFormatting as capsFormatting2, synthesizeFamilyName as synthesizeFamilyName2 } from "@json-to-office/shared";
6556
+ import { unrenderedComponentText } from "@json-to-office/quality";
6389
6557
  import {
6390
6558
  isNativeVisualProps as isNativeVisualProps4
6391
6559
  } from "@json-to-office/shared-docx";
@@ -6899,6 +7067,7 @@ function resolveColors(entries, at, deps) {
6899
7067
 
6900
7068
  // src/styles/themeToStyles.ts
6901
7069
  init_themes();
7070
+ init_types();
6902
7071
  init_colorUtils();
6903
7072
  init_styleHelpers();
6904
7073
  import {
@@ -7065,10 +7234,13 @@ function convertParagraphProperties(merged, styleProps, theme) {
7065
7234
  }
7066
7235
  };
7067
7236
  }
7237
+ function themeBorderStyle(style) {
7238
+ return DOCX_IR_BORDER_STYLES.includes(style) ? style : "single";
7239
+ }
7068
7240
  function convertBorders(borders3, theme) {
7069
7241
  if (!borders3) return void 0;
7070
7242
  const mapSide = (side) => side ? {
7071
- style: side.style,
7243
+ style: themeBorderStyle(side.style),
7072
7244
  sizeEighthPoints: side.size,
7073
7245
  color: { hex: resolveColor(side.color, theme) },
7074
7246
  ...side.space !== void 0 ? { spacePoints: side.space } : {}
@@ -7507,7 +7679,8 @@ function createDocumentStyles(themeNameOrObject = "consulting", language) {
7507
7679
  tabStops: effectiveTabStops.map(
7508
7680
  (ts) => ({
7509
7681
  type: ts.type,
7510
- positionTwips: ts.position === "max" ? RIGHT_MARGIN_TAB_TWIPS : ts.position,
7682
+ // Whole twips: `w:pos` is an integer no backend rounds.
7683
+ positionTwips: ts.position === "max" ? RIGHT_MARGIN_TAB_TWIPS : Math.round(ts.position),
7511
7684
  ...ts.leader && { leader: ts.leader }
7512
7685
  })
7513
7686
  )
@@ -8255,7 +8428,11 @@ function resolveTableModel(source, theme, themeName, options = {}) {
8255
8428
  });
8256
8429
  adjudicateInteriorEdges([...hasHeader ? [header] : [], ...rows]);
8257
8430
  return {
8258
- ...resolveWidths(source, columns, theme, themeName),
8431
+ ...resolveWidths(
8432
+ source,
8433
+ columns,
8434
+ options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
8435
+ ),
8259
8436
  ...hasHeader ? { header } : {},
8260
8437
  rows,
8261
8438
  repeatHeader: source.repeatHeaderOnPageBreak ?? true
@@ -8309,22 +8486,24 @@ function adjudicateInteriorEdges(rows) {
8309
8486
  });
8310
8487
  });
8311
8488
  }
8312
- function resolveWidths(source, columns, theme, themeName) {
8489
+ function resolveWidths(source, columns, measureTwips) {
8313
8490
  const hasExplicitWidths = columns.some((col) => col.width !== void 0);
8314
8491
  if (!hasExplicitWidths) {
8315
- const share = 100 / columns.length;
8492
+ const percent = source.width ?? 100;
8316
8493
  return {
8317
- columnGrid: { unit: "percent", values: columns.map(() => share) },
8318
- width: { size: source.width ?? 100, unit: "percent" }
8494
+ columnGrid: {
8495
+ unit: "twips",
8496
+ values: splitTwips(columns.length, measureTwips * percent / 100)
8497
+ },
8498
+ width: { size: percent, unit: "percent" }
8319
8499
  };
8320
8500
  }
8321
- const available = getAvailableWidthTwips(theme, themeName);
8322
8501
  const stated = columns.map(
8323
- (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, available) : void 0
8502
+ (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
8324
8503
  );
8325
8504
  const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
8326
8505
  const unstatedCount = columns.filter((col) => col.width === void 0).length;
8327
- const remaining = Math.max(0, available - totalStated);
8506
+ const remaining = Math.max(0, measureTwips - totalStated);
8328
8507
  const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
8329
8508
  const values = stated.map((w) => w || shareOfRemainder);
8330
8509
  return {
@@ -8333,7 +8512,7 @@ function resolveWidths(source, columns, theme, themeName) {
8333
8512
  size: values.reduce((sum, w) => sum + w, 0),
8334
8513
  unit: "twips"
8335
8514
  },
8336
- ...totalStated > available ? { overflow: { totalTwips: totalStated, availableTwips: available } } : {}
8515
+ ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
8337
8516
  };
8338
8517
  }
8339
8518
 
@@ -9049,6 +9228,36 @@ function emptyToUndefined(formatting) {
9049
9228
 
9050
9229
  // src/ir/compiler.ts
9051
9230
  init_types();
9231
+
9232
+ // src/ir/measure.ts
9233
+ var DEFAULT_COLUMN_SPACE_TWIPS = 720;
9234
+ function textWidthTwips(page) {
9235
+ const { leftTwips, rightTwips, gutterTwips = 0 } = page.margins;
9236
+ return Math.max(0, page.widthTwips - leftTwips - rightTwips - gutterTwips);
9237
+ }
9238
+ function sectionMeasureTwips(properties) {
9239
+ const textWidth = textWidthTwips(properties.page);
9240
+ const columns = properties.columns;
9241
+ if (!columns || columns.count <= 1) return textWidth;
9242
+ if (columns.equalWidth !== true && columns.widths?.length) {
9243
+ return Math.min(...columns.widths.map((column) => column.widthTwips));
9244
+ }
9245
+ const space = columns.spaceTwips ?? DEFAULT_COLUMN_SPACE_TWIPS;
9246
+ return Math.max(0, (textWidth - space * (columns.count - 1)) / columns.count);
9247
+ }
9248
+ function cellMeasureTwips(widthTwips, margins) {
9249
+ return Math.max(
9250
+ 0,
9251
+ widthTwips - (margins?.leftTwips ?? 0) - (margins?.rightTwips ?? 0)
9252
+ );
9253
+ }
9254
+ function tableWidthTwips(width, measureTwips) {
9255
+ if (width.kind === "twips") return width.value;
9256
+ if (width.kind === "percent") return measureTwips * width.value / 100;
9257
+ return measureTwips;
9258
+ }
9259
+
9260
+ // src/ir/compiler.ts
9052
9261
  init_units();
9053
9262
  var CONTENT_BOOKMARK_ID_BASE = 2e6;
9054
9263
  var SINGLE_LINE_TWIPS = 240;
@@ -9249,6 +9458,42 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9249
9458
  if (column && column.count > 1) {
9250
9459
  ctx.features.require("columns", `${path4}.properties.columns`);
9251
9460
  }
9461
+ const properties = {
9462
+ page: {
9463
+ widthTwips: page.size.width,
9464
+ heightTwips: page.size.height,
9465
+ orientation: page.size.width > page.size.height ? "landscape" : "portrait",
9466
+ ...page.size.code !== void 0 ? { code: page.size.code } : {},
9467
+ margins: {
9468
+ topTwips: page.margin.top,
9469
+ bottomTwips: page.margin.bottom,
9470
+ leftTwips: page.margin.left,
9471
+ rightTwips: page.margin.right,
9472
+ ...page.margin.header !== void 0 ? { headerTwips: page.margin.header } : {},
9473
+ ...page.margin.footer !== void 0 ? { footerTwips: page.margin.footer } : {},
9474
+ ...page.margin.gutter !== void 0 ? { gutterTwips: page.margin.gutter } : {}
9475
+ }
9476
+ },
9477
+ // A chunk that starts on a new page says so; the rest continue the flow.
9478
+ // The layout stage already decided which, so it is carried rather than
9479
+ // re-derived from `breakBefore`: a section can be continuous without the
9480
+ // two agreeing.
9481
+ ...section2.properties.type ? { type: sectionType(section2.properties.type) } : {},
9482
+ ...column ? {
9483
+ columns: {
9484
+ count: column.count,
9485
+ ...column.space !== void 0 ? { spaceTwips: column.space } : {},
9486
+ ...column.equalWidth !== void 0 ? { equalWidth: column.equalWidth } : {},
9487
+ ...column.widths ? {
9488
+ widths: column.widths.map((c) => ({
9489
+ widthTwips: c.width,
9490
+ ...c.space !== void 0 ? { spaceTwips: c.space } : {}
9491
+ }))
9492
+ } : {}
9493
+ }
9494
+ } : {}
9495
+ };
9496
+ const measureTwips = sectionMeasureTwips(properties);
9252
9497
  const children = [];
9253
9498
  const sectionBookmarkName = section2.belongsToUserSection && ordinal.ordinal !== void 0 ? `_Section_${ordinal.ordinal}` : void 0;
9254
9499
  section2.components.forEach((component, i) => {
@@ -9258,6 +9503,7 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9258
9503
  ctx,
9259
9504
  path: `${path4}.children[${i}]`,
9260
9505
  id: blockId(index, [i]),
9506
+ measureTwips,
9261
9507
  ...sectionBookmarkName ? { sectionBookmarkName } : {}
9262
9508
  })
9263
9509
  );
@@ -9270,41 +9516,7 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9270
9516
  id: `s${index}`,
9271
9517
  path: path4,
9272
9518
  children,
9273
- properties: {
9274
- page: {
9275
- widthTwips: page.size.width,
9276
- heightTwips: page.size.height,
9277
- orientation: page.size.width > page.size.height ? "landscape" : "portrait",
9278
- ...page.size.code !== void 0 ? { code: page.size.code } : {},
9279
- margins: {
9280
- topTwips: page.margin.top,
9281
- bottomTwips: page.margin.bottom,
9282
- leftTwips: page.margin.left,
9283
- rightTwips: page.margin.right,
9284
- ...page.margin.header !== void 0 ? { headerTwips: page.margin.header } : {},
9285
- ...page.margin.footer !== void 0 ? { footerTwips: page.margin.footer } : {},
9286
- ...page.margin.gutter !== void 0 ? { gutterTwips: page.margin.gutter } : {}
9287
- }
9288
- },
9289
- // A chunk that starts on a new page says so; the rest continue the flow.
9290
- // The layout stage already decided which, so it is carried rather than
9291
- // re-derived from `breakBefore`: a section can be continuous without the
9292
- // two agreeing.
9293
- ...section2.properties.type ? { type: sectionType(section2.properties.type) } : {},
9294
- ...column ? {
9295
- columns: {
9296
- count: column.count,
9297
- ...column.space !== void 0 ? { spaceTwips: column.space } : {},
9298
- ...column.equalWidth !== void 0 ? { equalWidth: column.equalWidth } : {},
9299
- ...column.widths ? {
9300
- widths: column.widths.map((c) => ({
9301
- widthTwips: c.width,
9302
- ...c.space !== void 0 ? { spaceTwips: c.space } : {}
9303
- }))
9304
- } : {}
9305
- }
9306
- } : {}
9307
- }
9519
+ properties
9308
9520
  };
9309
9521
  if (ordinal.ordinal !== void 0) {
9310
9522
  compiled.bookmark = {
@@ -9315,9 +9527,22 @@ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9315
9527
  };
9316
9528
  ctx.features.require("bookmarks", `${path4}.bookmark`);
9317
9529
  }
9318
- const headers = compilePart(chrome.header, "header", index, ctx);
9530
+ const chromeMeasureTwips = textWidthTwips(properties.page);
9531
+ const headers = compilePart(
9532
+ chrome.header,
9533
+ "header",
9534
+ index,
9535
+ chromeMeasureTwips,
9536
+ ctx
9537
+ );
9319
9538
  if (headers) compiled.headers = { default: headers };
9320
- const footers = compilePart(chrome.footer, "footer", index, ctx);
9539
+ const footers = compilePart(
9540
+ chrome.footer,
9541
+ "footer",
9542
+ index,
9543
+ chromeMeasureTwips,
9544
+ ctx
9545
+ );
9321
9546
  if (footers) compiled.footers = { default: footers };
9322
9547
  if (headers || footers) {
9323
9548
  ctx.features.require("headers-footers", `${path4}.headers`);
@@ -9336,7 +9561,7 @@ function sectionType(value) {
9336
9561
  return void 0;
9337
9562
  }
9338
9563
  }
9339
- function compilePart(part, kind, sectionIndex, ctx) {
9564
+ function compilePart(part, kind, sectionIndex, measureTwips, ctx) {
9340
9565
  if (!part) return void 0;
9341
9566
  const id = `${kind}:s${sectionIndex}:default`;
9342
9567
  const children = [];
@@ -9347,7 +9572,8 @@ function compilePart(part, kind, sectionIndex, ctx) {
9347
9572
  const scope = {
9348
9573
  ctx,
9349
9574
  path: `sections[${sectionIndex}].${kind}s.default.children[${i}]`,
9350
- id: headerFooterBlockId(id, [i])
9575
+ id: headerFooterBlockId(id, [i]),
9576
+ measureTwips
9351
9577
  };
9352
9578
  children.push(
9353
9579
  ...component.name === "paragraph" ? compileChromeParagraph(component, scope) : component.name === "image" ? compileChromeImage(component, scope) : compileComponent(component, scope)
@@ -9423,9 +9649,7 @@ function compileChromeImage(component, scope) {
9423
9649
  if (mediaType === "svg") ctx.features.require("svg-images", path4);
9424
9650
  const page = getPageSetup(ctx.theme);
9425
9651
  const pageWidthPx = Math.round(twipsToPixels(page.size.width));
9426
- const contentWidthPx = Math.round(
9427
- twipsToPixels(page.size.width - page.margin.left - page.margin.right)
9428
- );
9652
+ const contentWidthPx = Math.round(twipsToPixels(scope.measureTwips));
9429
9653
  const pageHeightPx = Math.round(twipsToPixels(page.size.height));
9430
9654
  const contentHeightPx = Math.round(
9431
9655
  twipsToPixels(page.size.height - page.margin.top - page.margin.bottom)
@@ -9633,7 +9857,7 @@ function compileColumns(component, scope) {
9633
9857
  const props = component.props ?? {};
9634
9858
  const configs = columnConfigs(props.columns);
9635
9859
  if (configs.length === 0) return [];
9636
- const available = getAvailableWidthTwips(ctx.theme, ctx.themeName);
9860
+ const available = scope.measureTwips;
9637
9861
  const { widths, gaps } = columnMetrics(configs, props.gap, available);
9638
9862
  const stated = widths.map(
9639
9863
  (width, index) => width + gaps[index] / 2 + (index > 0 ? gaps[index - 1] / 2 : 0)
@@ -9662,11 +9886,18 @@ function compileColumns(component, scope) {
9662
9886
  rows: [
9663
9887
  {
9664
9888
  cells: configs.map((_, index) => {
9889
+ const margins = {
9890
+ topTwips: 0,
9891
+ rightTwips: gaps[index] / 2,
9892
+ bottomTwips: 0,
9893
+ leftTwips: index > 0 ? gaps[index - 1] / 2 : 0
9894
+ };
9665
9895
  const blocks = contents[index].flatMap(
9666
9896
  (child, childIndex) => compileComponent(child, {
9667
9897
  ...scope,
9668
9898
  path: `${path4}.children[${index}][${childIndex}]`,
9669
- id: `${scope.id}:c${index}:${childIndex}`
9899
+ id: `${scope.id}:c${index}:${childIndex}`,
9900
+ measureTwips: cellMeasureTwips(cells[index], margins)
9670
9901
  })
9671
9902
  );
9672
9903
  return {
@@ -9681,12 +9912,7 @@ function compileColumns(component, scope) {
9681
9912
  }
9682
9913
  ],
9683
9914
  widthTwips: cells[index],
9684
- margins: {
9685
- topTwips: 0,
9686
- rightTwips: gaps[index] / 2,
9687
- bottomTwips: 0,
9688
- leftTwips: index > 0 ? gaps[index - 1] / 2 : 0
9689
- },
9915
+ margins,
9690
9916
  verticalAlign: "top",
9691
9917
  borders: {
9692
9918
  top: NO_BORDERS.top,
@@ -9743,6 +9969,16 @@ var DEFAULT_TEXT_BOX_WIDTH_TWIPS = 5e3;
9743
9969
  function compileTextBox(component, scope) {
9744
9970
  const { ctx, path: path4 } = scope;
9745
9971
  const props = component.props ?? {};
9972
+ const style = props.style;
9973
+ const padding = style?.padding;
9974
+ const width = textBoxWidth(props);
9975
+ const widthTwips = Math.round(tableWidthTwips(width, scope.measureTwips));
9976
+ const margins = {
9977
+ topTwips: padding?.top ? pointsToTwips2(padding.top) : 0,
9978
+ rightTwips: padding?.right ? pointsToTwips2(padding.right) : 0,
9979
+ bottomTwips: padding?.bottom ? pointsToTwips2(padding.bottom) : 0,
9980
+ leftTwips: padding?.left ? pointsToTwips2(padding.left) : 0
9981
+ };
9746
9982
  const children = [];
9747
9983
  const contents = component.children ?? [];
9748
9984
  contents.forEach((child, index) => {
@@ -9750,12 +9986,12 @@ function compileTextBox(component, scope) {
9750
9986
  ...compileComponent(child, {
9751
9987
  ...scope,
9752
9988
  path: `${path4}.children[${index}]`,
9753
- id: `${scope.id}:c${index}`
9989
+ id: `${scope.id}:c${index}`,
9990
+ // What the box holds is set against the box less its padding.
9991
+ measureTwips: cellMeasureTwips(widthTwips, margins)
9754
9992
  })
9755
9993
  );
9756
9994
  });
9757
- const style = props.style;
9758
- const padding = style?.padding;
9759
9995
  if (props.renderAs === "shape") {
9760
9996
  const shape = compileShape2(props, children, scope);
9761
9997
  if (shape) return shape;
@@ -9767,9 +10003,11 @@ function compileTextBox(component, scope) {
9767
10003
  kind: "table",
9768
10004
  id: scope.id,
9769
10005
  path: path4,
9770
- // No grid: one cell, whose width the table itself decides.
9771
- columnGrid: { unit: "twips", values: [] },
9772
- width: textBoxWidth(props),
10006
+ // One cell, as wide as the box. Left out, the grid would be each
10007
+ // backend's 100-twip placeholder, which a reader that takes the grid as
10008
+ // the column widths draws.
10009
+ columnGrid: { unit: "twips", values: [widthTwips] },
10010
+ width,
9773
10011
  layout: "fixed",
9774
10012
  // The container is invisible; whatever border the author asked for is
9775
10013
  // drawn by the cell inside it.
@@ -9789,12 +10027,7 @@ function compileTextBox(component, scope) {
9789
10027
  children: []
9790
10028
  }
9791
10029
  ],
9792
- margins: {
9793
- topTwips: padding?.top ? pointsToTwips2(padding.top) : 0,
9794
- rightTwips: padding?.right ? pointsToTwips2(padding.right) : 0,
9795
- bottomTwips: padding?.bottom ? pointsToTwips2(padding.bottom) : 0,
9796
- leftTwips: padding?.left ? pointsToTwips2(padding.left) : 0
9797
- },
10030
+ margins,
9798
10031
  ...style?.shading?.fill ? {
9799
10032
  shading: {
9800
10033
  fill: irColor(
@@ -9813,8 +10046,11 @@ function compileTextBox(component, scope) {
9813
10046
  var EMU_PER_PIXEL = 9525;
9814
10047
  function compileShape2(props, children, scope) {
9815
10048
  const { ctx, path: path4 } = scope;
9816
- const width = shapeSize(props.width, "width", ctx);
9817
- const height = shapeSize(props.height, "height", ctx);
10049
+ const width = shapeSize(props.width, scope.measureTwips);
10050
+ const height = shapeSize(
10051
+ props.height,
10052
+ getAvailableHeightTwips(ctx.theme, ctx.themeName)
10053
+ );
9818
10054
  if (width.pixels === void 0 || height.pixels === void 0) {
9819
10055
  warnOnce(
9820
10056
  ctx,
@@ -9845,7 +10081,7 @@ function compileShape2(props, children, scope) {
9845
10081
  warnOnce(
9846
10082
  ctx,
9847
10083
  "text-box",
9848
- '[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.'
10084
+ '[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.'
9849
10085
  );
9850
10086
  }
9851
10087
  if (outline.ignoredSides.length > 0) {
@@ -9890,7 +10126,7 @@ function compileShape2(props, children, scope) {
9890
10126
  }
9891
10127
  ];
9892
10128
  }
9893
- function shapeSize(value, axis, ctx) {
10129
+ function shapeSize(value, referenceTwips) {
9894
10130
  if (typeof value === "number") {
9895
10131
  return { pixels: Math.round(value), resolvedPercentage: false };
9896
10132
  }
@@ -9899,9 +10135,8 @@ function shapeSize(value, axis, ctx) {
9899
10135
  if (!Number.isFinite(fraction) || fraction <= 0) {
9900
10136
  return { resolvedPercentage: false };
9901
10137
  }
9902
- const availableTwips = axis === "width" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getAvailableHeightTwips(ctx.theme, ctx.themeName);
9903
10138
  return {
9904
- pixels: Math.round(availableTwips * fraction / TWIPS_PER_PIXEL),
10139
+ pixels: Math.round(referenceTwips * fraction / TWIPS_PER_PIXEL),
9905
10140
  resolvedPercentage: true
9906
10141
  };
9907
10142
  }
@@ -9976,10 +10211,20 @@ function textBoxWidth(props) {
9976
10211
  value: typeof raw === "number" ? raw * TWIPS_PER_PIXEL : DEFAULT_TEXT_BOX_WIDTH_TWIPS
9977
10212
  };
9978
10213
  }
10214
+ function borderLineStyle(authored) {
10215
+ switch (authored) {
10216
+ case "dashed":
10217
+ case "dotted":
10218
+ case "double":
10219
+ return authored;
10220
+ default:
10221
+ return "single";
10222
+ }
10223
+ }
9979
10224
  function compileBorders(border3, ctx) {
9980
10225
  if (!border3) return void 0;
9981
10226
  const side = (value) => value ? {
9982
- style: String(value.style ?? "single"),
10227
+ style: value.style === "none" ? "none" : borderLineStyle(value.style),
9983
10228
  // Points to eighths of a point, never thinner than the hairline a
9984
10229
  // reader can actually see.
9985
10230
  sizeEighthPoints: value.width !== void 0 ? Math.max(1, Math.round(value.width * 8)) : 1,
@@ -10055,7 +10300,9 @@ function compileToc(component, scope) {
10055
10300
  formatting: {
10056
10301
  alignment: "left",
10057
10302
  spacing: {
10058
- beforeTwips: ctx.theme.componentDefaults?.heading?.spacing?.before ?? 240,
10303
+ beforeTwips: wholeTwips(
10304
+ ctx.theme.componentDefaults?.heading?.spacing?.before ?? 240
10305
+ ),
10059
10306
  // ~9pt, which separates the title from the list clearly enough to
10060
10307
  // read as a heading rather than a first entry.
10061
10308
  afterTwips: 180
@@ -10083,12 +10330,6 @@ function compileToc(component, scope) {
10083
10330
  var DEFAULT_DIVIDER_SPACING_PT = 6;
10084
10331
  var DEFAULT_DIVIDER_THICKNESS_PT = 1;
10085
10332
  var DIVIDER_LINE_TWIPS = 20;
10086
- var DIVIDER_BORDER_STYLE = {
10087
- solid: "single",
10088
- dashed: "dashed",
10089
- dotted: "dotted",
10090
- double: "double"
10091
- };
10092
10333
  function compileDivider(component, scope) {
10093
10334
  const { ctx, path: path4 } = scope;
10094
10335
  const props = component.props ?? {};
@@ -10117,10 +10358,10 @@ function compileDivider(component, scope) {
10117
10358
  lineTwips: DIVIDER_LINE_TWIPS,
10118
10359
  lineRule: "exact"
10119
10360
  },
10120
- ...dividerIndent(props, ctx, path4),
10361
+ ...dividerIndent(props, scope),
10121
10362
  borders: {
10122
10363
  bottom: {
10123
- style: DIVIDER_BORDER_STYLE[String(props.style)] ?? "single",
10364
+ style: borderLineStyle(props.style),
10124
10365
  sizeEighthPoints: pointsToEighthPoints(thicknessPt),
10125
10366
  color: irColor(
10126
10367
  resolveColor(String(props.color ?? "border"), ctx.theme)
@@ -10134,14 +10375,10 @@ function compileDivider(component, scope) {
10134
10375
  }
10135
10376
  ];
10136
10377
  }
10137
- function dividerIndent(props, ctx, path4) {
10378
+ function dividerIndent(props, scope) {
10379
+ const { ctx, path: path4, measureTwips } = scope;
10138
10380
  const width = props.width;
10139
10381
  if (width === void 0) return {};
10140
- const page = getPageSetup(ctx.theme);
10141
- const measureTwips = Math.max(
10142
- 0,
10143
- page.size.width - page.margin.left - page.margin.right
10144
- );
10145
10382
  let usedTwips;
10146
10383
  if (typeof width === "number" && Number.isFinite(width)) {
10147
10384
  usedTwips = pointsToTwips2(width);
@@ -10185,8 +10422,8 @@ function compileStatistic(component, scope) {
10185
10422
  const alignment2 = compileAlignment(props.alignment) ?? "center";
10186
10423
  ctx.usesStatistic = true;
10187
10424
  const spacing = props.spacing ? {
10188
- ...props.spacing.before !== void 0 ? { beforeTwips: props.spacing.before } : {},
10189
- ...props.spacing.after !== void 0 ? { afterTwips: props.spacing.after } : {}
10425
+ ...props.spacing.before !== void 0 ? { beforeTwips: wholeTwips(props.spacing.before) } : {},
10426
+ ...props.spacing.after !== void 0 ? { afterTwips: wholeTwips(props.spacing.after) } : {}
10190
10427
  } : void 0;
10191
10428
  if (props.format !== void 0) {
10192
10429
  warnOnce(
@@ -10297,7 +10534,7 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
10297
10534
  }
10298
10535
  blocks.push(
10299
10536
  paragraphNode(
10300
- { ctx, path: itemPath, id: `${scope.id}:i${index}` },
10537
+ { ...scope, path: itemPath, id: `${scope.id}:i${index}` },
10301
10538
  revision === void 0 ? parseInline(text, {
10302
10539
  base,
10303
10540
  hyperlinks: true,
@@ -10811,7 +11048,7 @@ function paragraphFormatting(props, ctx, extra = {}) {
10811
11048
  if (Array.isArray(props.tabStops) && props.tabStops.length > 0) {
10812
11049
  formatting.tabStops = props.tabStops.map(
10813
11050
  (stop) => ({
10814
- positionTwips: stop.position,
11051
+ positionTwips: wholeTwips(stop.position),
10815
11052
  type: stop.type ?? "left",
10816
11053
  ...stop.leader ? { leader: stop.leader } : {}
10817
11054
  })
@@ -10860,7 +11097,10 @@ function compileSpacing(props, ctx) {
10860
11097
  }
10861
11098
  function compileLineSpacing(value) {
10862
11099
  if (typeof value === "number") {
10863
- return { lineTwips: value * SINGLE_LINE_TWIPS, lineRule: "auto" };
11100
+ return {
11101
+ lineTwips: wholeTwips(value * SINGLE_LINE_TWIPS),
11102
+ lineRule: "auto"
11103
+ };
10864
11104
  }
10865
11105
  if (typeof value !== "object" || value === null) return void 0;
10866
11106
  const { type, value: amount } = value;
@@ -10881,7 +11121,7 @@ function compileLineSpacing(value) {
10881
11121
  };
10882
11122
  case "multiple":
10883
11123
  return {
10884
- ...amount !== void 0 ? { lineTwips: amount * SINGLE_LINE_TWIPS } : {},
11124
+ ...amount !== void 0 ? { lineTwips: wholeTwips(amount * SINGLE_LINE_TWIPS) } : {},
10885
11125
  lineRule: "auto"
10886
11126
  };
10887
11127
  default:
@@ -10944,7 +11184,7 @@ function compileVisual(component, scope) {
10944
11184
  );
10945
11185
  if (!group) return [];
10946
11186
  const size = visualPlacementPixels(props, group.canvas, {
10947
- widthPx: Math.round(twipsToPixels(getAvailableWidthTwips(ctx.theme))),
11187
+ widthPx: Math.round(twipsToPixels(scope.measureTwips)),
10948
11188
  heightPx: Math.round(twipsToPixels(getAvailableHeightTwips(ctx.theme)))
10949
11189
  });
10950
11190
  const drawing = {
@@ -11059,7 +11299,7 @@ function compileImage2(component, scope) {
11059
11299
  `Alt text is not written to the document: the DOCX pipeline has never emitted \`wp:docPr\` descriptions, so the image at ${path4} will have none. The text is preserved in the IR.`
11060
11300
  );
11061
11301
  }
11062
- const size = imagePixelSize(props, loaded, ctx);
11302
+ const size = imagePixelSize(props, loaded, scope);
11063
11303
  const resourceId = declareResource(loaded, mediaType, ctx);
11064
11304
  const image = {
11065
11305
  kind: "image",
@@ -11169,8 +11409,7 @@ function compileChart(component, scope) {
11169
11409
  return [];
11170
11410
  }
11171
11411
  });
11172
- const page = getPageSetup(ctx.theme);
11173
- const contentWidthInches = (page.size.width - page.margin.left - page.margin.right) / 1440;
11412
+ const contentWidthInches = scope.measureTwips / 1440;
11174
11413
  const widthInches = typeof props.width === "number" ? props.width : contentWidthInches;
11175
11414
  const heightInches = typeof props.height === "number" ? props.height : DEFAULT_CHART_HEIGHT_INCHES;
11176
11415
  const chart = {
@@ -11270,12 +11509,13 @@ function wrapType(value) {
11270
11509
  return "square";
11271
11510
  }
11272
11511
  }
11273
- function imagePixelSize(props, loaded, ctx) {
11512
+ function imagePixelSize(props, loaded, scope) {
11513
+ const { ctx } = scope;
11274
11514
  const widthRef = props.widthRelativeTo === "page" ? "page" : "content";
11275
11515
  const heightRef = props.heightRelativeTo === "page" ? "page" : "content";
11276
11516
  const availableWidthPx = Math.round(
11277
11517
  twipsToPixels(
11278
- widthRef === "page" ? getPageWidthTwips(ctx.theme) : getAvailableWidthTwips(ctx.theme)
11518
+ widthRef === "page" ? getPageWidthTwips(ctx.theme) : scope.measureTwips
11279
11519
  )
11280
11520
  );
11281
11521
  const availableHeightPx = Math.round(
@@ -11342,8 +11582,8 @@ function compileFrame(floating, ctx, path4) {
11342
11582
  const useAbsolute = hasHorizontalOffset || hasVerticalOffset;
11343
11583
  const frame = {
11344
11584
  // 2in by 1in when the author gave no size.
11345
- widthTwips: floating.width || 2880,
11346
- heightTwips: floating.height || 1440,
11585
+ widthTwips: wholeTwips(floating.width || 2880),
11586
+ heightTwips: wholeTwips(floating.height || 1440),
11347
11587
  anchorHorizontal: floating.horizontalPosition?.relative || "page",
11348
11588
  anchorVertical: floating.verticalPosition?.relative || "page",
11349
11589
  ...floating.wrap?.type ? { wrap: floating.wrap.type } : {}
@@ -11356,17 +11596,21 @@ function compileFrame(floating, ctx, path4) {
11356
11596
  if (typeof rawX === "string" || typeof rawY === "string") {
11357
11597
  const hRelative = floating.horizontalPosition?.relative;
11358
11598
  const vRelative = floating.verticalPosition?.relative;
11359
- frame.xTwips = resolveOffsetTwips(
11360
- rawX,
11361
- hRelative && hRelative !== "page" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getPageWidthTwips(ctx.theme, ctx.themeName)
11599
+ frame.xTwips = wholeTwips(
11600
+ resolveOffsetTwips(
11601
+ rawX,
11602
+ hRelative && hRelative !== "page" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getPageWidthTwips(ctx.theme, ctx.themeName)
11603
+ )
11362
11604
  );
11363
- frame.yTwips = resolveOffsetTwips(
11364
- rawY,
11365
- vRelative && vRelative !== "page" ? getAvailableHeightTwips(ctx.theme, ctx.themeName) : getPageHeightTwips(ctx.theme, ctx.themeName)
11605
+ frame.yTwips = wholeTwips(
11606
+ resolveOffsetTwips(
11607
+ rawY,
11608
+ vRelative && vRelative !== "page" ? getAvailableHeightTwips(ctx.theme, ctx.themeName) : getPageHeightTwips(ctx.theme, ctx.themeName)
11609
+ )
11366
11610
  );
11367
11611
  } else {
11368
- frame.xTwips = rawX;
11369
- frame.yTwips = rawY;
11612
+ frame.xTwips = wholeTwips(rawX);
11613
+ frame.yTwips = wholeTwips(rawY);
11370
11614
  }
11371
11615
  return frame;
11372
11616
  }
@@ -11462,7 +11706,10 @@ function compileTable(component, scope) {
11462
11706
  source,
11463
11707
  ctx.theme,
11464
11708
  ctx.themeName,
11465
- { onWarning: (code, message) => warnOnce(ctx, "table", message, code) }
11709
+ {
11710
+ onWarning: (code, message) => warnOnce(ctx, "table", message, code),
11711
+ measureTwips: scope.measureTwips
11712
+ }
11466
11713
  );
11467
11714
  if (model.overflow) {
11468
11715
  warnOnce(
@@ -11480,6 +11727,7 @@ function compileTable(component, scope) {
11480
11727
  ...model.header ? [
11481
11728
  compileTableRow(
11482
11729
  model.header,
11730
+ model.columnGrid.values,
11483
11731
  style.headerParagraph,
11484
11732
  style.tableHeader,
11485
11733
  {
@@ -11492,13 +11740,19 @@ function compileTable(component, scope) {
11492
11740
  )
11493
11741
  ] : [],
11494
11742
  ...model.rows.map(
11495
- (row, index) => compileTableRow(row, style.cellParagraph, style.tableCell, {
11496
- ctx,
11497
- path: `${path4}.rows[${index}]`,
11498
- id: `${scope.id}:r${index}`,
11499
- ...row.tableHeader !== void 0 ? { isHeader: row.tableHeader } : {},
11500
- ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {}
11501
- })
11743
+ (row, index) => compileTableRow(
11744
+ row,
11745
+ model.columnGrid.values,
11746
+ style.cellParagraph,
11747
+ style.tableCell,
11748
+ {
11749
+ ctx,
11750
+ path: `${path4}.rows[${index}]`,
11751
+ id: `${scope.id}:r${index}`,
11752
+ ...row.tableHeader !== void 0 ? { isHeader: row.tableHeader } : {},
11753
+ ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {}
11754
+ }
11755
+ )
11502
11756
  )
11503
11757
  ];
11504
11758
  ctx.features.require("tables", path4);
@@ -11576,7 +11830,7 @@ function tableBlocker(model, path4) {
11576
11830
  }
11577
11831
  return void 0;
11578
11832
  }
11579
- function compileTableRow(row, spacing, baseStyle, scope) {
11833
+ function compileTableRow(row, columnTwips, spacing, baseStyle, scope) {
11580
11834
  const { ctx } = scope;
11581
11835
  const rowRevision = row.revision;
11582
11836
  const rowMark = rowRevision ? {
@@ -11596,6 +11850,7 @@ function compileTableRow(row, spacing, baseStyle, scope) {
11596
11850
  cells: row.cells.map(
11597
11851
  (cell, colIndex) => compileTableCell(
11598
11852
  cell,
11853
+ columnTwips[colIndex] ?? 0,
11599
11854
  spacing,
11600
11855
  baseStyle,
11601
11856
  row.keepNext,
@@ -11617,7 +11872,17 @@ function compileTableRow(row, spacing, baseStyle, scope) {
11617
11872
  ...scope.cantSplit !== void 0 ? { cantSplit: scope.cantSplit } : {}
11618
11873
  };
11619
11874
  }
11620
- function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision, markRevision) {
11875
+ function compileTableCell(cell, columnTwips, spacing, baseStyle, keepNext, partScope, rowRevision, markRevision) {
11876
+ const margins = !cell.missing && cell.padding ? {
11877
+ topTwips: pointsToTwips2(cell.padding.top),
11878
+ bottomTwips: pointsToTwips2(cell.padding.bottom),
11879
+ leftTwips: pointsToTwips2(cell.padding.left),
11880
+ rightTwips: pointsToTwips2(cell.padding.right)
11881
+ } : void 0;
11882
+ const scope = {
11883
+ ...partScope,
11884
+ measureTwips: cellMeasureTwips(columnTwips, margins)
11885
+ };
11621
11886
  const cellLine = compileLineSpacing(cell.font?.lineSpacing);
11622
11887
  const paragraph2 = {
11623
11888
  kind: "paragraph",
@@ -11646,14 +11911,7 @@ function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision
11646
11911
  ...cell.missing ? {} : {
11647
11912
  verticalAlign: verticalAlign(cell.verticalAlignment),
11648
11913
  ...cell.backgroundColor !== void 0 && cell.backgroundColor !== "transparent" ? { shading: { fill: { hex: cell.backgroundColor } } } : {},
11649
- ...cell.padding ? {
11650
- margins: {
11651
- topTwips: pointsToTwips2(cell.padding.top),
11652
- bottomTwips: pointsToTwips2(cell.padding.bottom),
11653
- leftTwips: pointsToTwips2(cell.padding.left),
11654
- rightTwips: pointsToTwips2(cell.padding.right)
11655
- }
11656
- } : {}
11914
+ ...margins ? { margins } : {}
11657
11915
  },
11658
11916
  borders: {
11659
11917
  top: compileTableBorder(cell.borders.top),
@@ -11686,10 +11944,17 @@ function cellChildren(cell, baseStyle, scope, rowRevision) {
11686
11944
  return wrap(cellVisual(content, scope));
11687
11945
  }
11688
11946
  if (content.name !== "paragraph") {
11947
+ const text2 = unrenderedComponentText(content.name);
11948
+ warnOnce(
11949
+ ctx,
11950
+ "table",
11951
+ `${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.`,
11952
+ "W_UNSUPPORTED_CELL_CONTENT"
11953
+ );
11689
11954
  return wrap([
11690
11955
  {
11691
11956
  kind: "text",
11692
- text: `[Unsupported component type: ${content.name}]`,
11957
+ text: text2,
11693
11958
  formatting: placeholderFormatting(base)
11694
11959
  }
11695
11960
  ]);
@@ -12847,9 +13112,10 @@ function docxBlockContext(document, theme, path4, sources) {
12847
13112
  page.size ?? theme.page.size
12848
13113
  );
12849
13114
  const overrides = isBlockRecord(page.margins) ? page.margins : {};
13115
+ const gutter = "gutter" in margins ? margins.gutter : 0;
12850
13116
  const width = Object.keys(page).length ? Math.max(
12851
13117
  0,
12852
- dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440)
13118
+ dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440) - Number(overrides.gutter ?? gutter ?? 0)
12853
13119
  ) : getAvailableWidthTwips(theme);
12854
13120
  const height = Object.keys(page).length ? Math.max(
12855
13121
  0,
@@ -13057,6 +13323,11 @@ init_widthUtils();
13057
13323
  import probe2 from "probe-image-size";
13058
13324
 
13059
13325
  // src/quality/text-inventory.ts
13326
+ import {
13327
+ unrenderedComponentText as unrenderedComponentText2
13328
+ } from "@json-to-office/quality";
13329
+ import { TABLE_CELL_COMPONENTS } from "@json-to-office/shared-docx";
13330
+ var CELL_COMPONENTS = new Set(TABLE_CELL_COMPONENTS);
13060
13331
  function tocDepth(depth) {
13061
13332
  const range = asRecord(depth) ?? {};
13062
13333
  const from = typeof range.from === "number" ? range.from : 1;
@@ -13105,7 +13376,14 @@ function collectDocxTextInventory(children, basePath = "/children") {
13105
13376
  add(`${path4}/content`, rec.content, cellRole, extra);
13106
13377
  else {
13107
13378
  const inner = asRecord(rec.content);
13108
- if (inner)
13379
+ if (inner && typeof inner.name === "string" && !CELL_COMPONENTS.has(inner.name)) {
13380
+ add(
13381
+ `${path4}/content`,
13382
+ unrenderedComponentText2(inner.name),
13383
+ cellRole,
13384
+ { ...extra, optional: true, unrendered: inner.name }
13385
+ );
13386
+ } else if (inner)
13109
13387
  visitNode(inner, `${path4}/content`, { ...inherited, inCell: true });
13110
13388
  }
13111
13389
  return;
@@ -13430,7 +13708,7 @@ function pageBox(theme, themeName, pageOverride) {
13430
13708
  return {
13431
13709
  availableWidthTwips: Math.max(
13432
13710
  0,
13433
- page.size.width - page.margin.left - page.margin.right
13711
+ page.size.width - page.margin.left - page.margin.right - (page.margin.gutter ?? 0)
13434
13712
  ),
13435
13713
  pageBottomTwips: page.size.height
13436
13714
  };