@json-to-office/core-docx 7.0.0 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) 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/desugarExternals.d.ts.map +1 -1
  4. package/dist/core/imageResources.d.ts.map +1 -1
  5. package/dist/core/layout.d.ts +7 -0
  6. package/dist/core/layout.d.ts.map +1 -1
  7. package/dist/core/tableModel.d.ts +10 -8
  8. package/dist/core/tableModel.d.ts.map +1 -1
  9. package/dist/index.js +455 -219
  10. package/dist/index.js.map +1 -1
  11. package/dist/ir/compiler.d.ts.map +1 -1
  12. package/dist/ir/measure.d.ts +44 -0
  13. package/dist/ir/measure.d.ts.map +1 -0
  14. package/dist/ir/types.d.ts +31 -7
  15. package/dist/ir/types.d.ts.map +1 -1
  16. package/dist/ir/validation.d.ts +3 -2
  17. package/dist/ir/validation.d.ts.map +1 -1
  18. package/dist/plugin/example/index.js +455 -219
  19. package/dist/plugin/example/index.js.map +1 -1
  20. package/dist/quality/facts.d.ts.map +1 -1
  21. package/dist/renderers/docxjs/emit.d.ts.map +1 -1
  22. package/dist/renderers/docxjs/styles.d.ts.map +1 -1
  23. package/dist/renderers/office-open/chartParts.d.ts.map +1 -1
  24. package/dist/renderers/office-open/emit.d.ts +14 -0
  25. package/dist/renderers/office-open/emit.d.ts.map +1 -1
  26. package/dist/renderers/office-open/styles.d.ts.map +1 -1
  27. package/dist/styles/index.d.ts +2 -1
  28. package/dist/styles/index.d.ts.map +1 -1
  29. package/dist/styles/themeToStyles.d.ts +1 -1
  30. package/dist/styles/themeToStyles.d.ts.map +1 -1
  31. package/dist/styles/utils/layoutUtils.d.ts +44 -0
  32. package/dist/styles/utils/layoutUtils.d.ts.map +1 -1
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/dist/utils/widthUtils.d.ts +9 -1
  35. package/dist/utils/widthUtils.d.ts.map +1 -1
  36. package/package.json +3 -3
  37. package/dist/styles/utils/borderUtils.d.ts +0 -95
  38. package/dist/styles/utils/borderUtils.d.ts.map +0 -1
@@ -1841,6 +1841,21 @@ function getPageDimensions(size) {
1841
1841
  }
1842
1842
  return { width: size.width, height: size.height };
1843
1843
  }
1844
+ function themeForSectionPage(theme, page) {
1845
+ if (!page || !page.size && !page.margins) return theme;
1846
+ const base = getPageSetup(theme);
1847
+ return {
1848
+ ...theme,
1849
+ page: {
1850
+ ...theme.page,
1851
+ size: page.size ?? theme.page?.size ?? "A4",
1852
+ margins: { ...theme.page?.margins, ...base.margin, ...page.margins }
1853
+ }
1854
+ };
1855
+ }
1856
+ function pageSizeDiffers(a, b) {
1857
+ return a.width !== b.width || a.height !== b.height;
1858
+ }
1844
1859
  var resolveTableParagraphSpacing, PAGE_SIZES, getTableStyle, getDocumentMargins, getPageSetup;
1845
1860
  var init_layoutUtils = __esm({
1846
1861
  "src/styles/utils/layoutUtils.ts"() {
@@ -1993,6 +2008,7 @@ var init_layoutUtils = __esm({
1993
2008
  if (themeConfig?.page) {
1994
2009
  const margins = themeConfig.page.margins || {};
1995
2010
  const dimensions = getPageDimensions(themeConfig.page.size);
2011
+ const gutter = margins.gutter;
1996
2012
  return {
1997
2013
  size: {
1998
2014
  width: dimensions.width,
@@ -2003,7 +2019,13 @@ var init_layoutUtils = __esm({
2003
2019
  top: margins.top ?? defaultMargins.top ?? 1440,
2004
2020
  right: margins.right ?? defaultMargins.right ?? 1440,
2005
2021
  bottom: margins.bottom ?? defaultMargins.bottom ?? 1440,
2006
- 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 }
2007
2029
  }
2008
2030
  };
2009
2031
  }
@@ -2019,50 +2041,15 @@ var init_layoutUtils = __esm({
2019
2041
  top: defaultMargins.top ?? 1440,
2020
2042
  right: defaultMargins.right ?? 1440,
2021
2043
  bottom: defaultMargins.bottom ?? 1440,
2022
- left: defaultMargins.left ?? 1440
2044
+ left: defaultMargins.left ?? 1440,
2045
+ header: defaultMargins.header ?? 720,
2046
+ footer: defaultMargins.footer ?? 720
2023
2047
  }
2024
2048
  };
2025
2049
  };
2026
2050
  }
2027
2051
  });
2028
2052
 
2029
- // src/utils/generationContext.ts
2030
- import { AsyncLocalStorage } from "async_hooks";
2031
- import { isAbsolute, resolve } from "path";
2032
- function runWithBaseDir(baseDir, callback) {
2033
- return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
2034
- }
2035
- function resolveFromBaseDir(filePath) {
2036
- const base = baseDirStorage.getStore();
2037
- if (!base || isAbsolute(filePath)) return filePath;
2038
- return resolve(base, filePath);
2039
- }
2040
- function runWithWarnings(warnings, callback) {
2041
- return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
2042
- }
2043
- function reportWarning(component, code, message, context) {
2044
- const warnings = warningsStorage.getStore();
2045
- if (warnings) {
2046
- warnings.push({
2047
- component,
2048
- message,
2049
- severity: "warning",
2050
- context: { code, ...context }
2051
- });
2052
- return;
2053
- }
2054
- console.warn(`[json-to-docx] [${code}] ${message}`);
2055
- }
2056
- var generationDateStorage, baseDirStorage, warningsStorage;
2057
- var init_generationContext = __esm({
2058
- "src/utils/generationContext.ts"() {
2059
- "use strict";
2060
- generationDateStorage = new AsyncLocalStorage();
2061
- baseDirStorage = new AsyncLocalStorage();
2062
- warningsStorage = new AsyncLocalStorage();
2063
- }
2064
- });
2065
-
2066
2053
  // src/themes/json/parser.ts
2067
2054
  import { validateThemeJson } from "@json-to-office/shared-docx/validation/unified";
2068
2055
  var ThemeValidationError, ThemeParseError, ThemeParser;
@@ -2373,7 +2360,8 @@ function getAvailableWidthTwips(theme, themeName) {
2373
2360
  const width = page?.size?.width || 0;
2374
2361
  const left = page?.margin?.left || 0;
2375
2362
  const right = page?.margin?.right || 0;
2376
- return Math.max(0, width - left - right);
2363
+ const gutter = page?.margin?.gutter || 0;
2364
+ return Math.max(0, width - left - right - gutter);
2377
2365
  }
2378
2366
  function getPageWidthTwips(theme, themeName) {
2379
2367
  const page = getPageSetup(theme, themeName);
@@ -2408,6 +2396,13 @@ function resolveOffsetTwips(value, referenceTwips) {
2408
2396
  }
2409
2397
  return Math.round(referenceTwips * parseFloat(match[1]) / 100);
2410
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
+ }
2411
2406
  function relativeLengthToTwips(value, availableWidthTwips) {
2412
2407
  if (typeof value === "number") {
2413
2408
  return pointsToTwips(value);
@@ -2424,6 +2419,43 @@ var init_widthUtils = __esm({
2424
2419
  }
2425
2420
  });
2426
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
+
2427
2459
  // src/utils/imageUtils.ts
2428
2460
  import { readFileSync } from "fs";
2429
2461
  import { availableParallelism } from "os";
@@ -2719,7 +2751,7 @@ var init_imageUtils = __esm({
2719
2751
  });
2720
2752
 
2721
2753
  // src/ir/types.ts
2722
- 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;
2723
2755
  var init_types = __esm({
2724
2756
  "src/ir/types.ts"() {
2725
2757
  "use strict";
@@ -2728,6 +2760,35 @@ var init_types = __esm({
2728
2760
  EMU_PER_INCH = 914400;
2729
2761
  EMU_PER_TWIP = EMU_PER_INCH / TWIPS_PER_INCH;
2730
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
+ ];
2731
2792
  }
2732
2793
  });
2733
2794
 
@@ -2787,7 +2848,6 @@ import {
2787
2848
  AlignmentType,
2788
2849
  BookmarkEnd,
2789
2850
  BookmarkStart,
2790
- BorderStyle,
2791
2851
  ColumnBreak,
2792
2852
  CommentRangeEnd,
2793
2853
  CommentRangeStart,
@@ -3204,7 +3264,7 @@ function emitBlock(block2, resources = /* @__PURE__ */ new Map()) {
3204
3264
  }
3205
3265
  }
3206
3266
  function emitToc(block2) {
3207
- return new TableOfContents(block2.alias ?? "Table of Contents", {
3267
+ return new ThemedTableOfContents(block2.alias ?? "Table of Contents", {
3208
3268
  ...block2.hyperlink !== void 0 ? { hyperlink: block2.hyperlink } : {},
3209
3269
  ...block2.headingRange ? {
3210
3270
  headingStyleRange: `${block2.headingRange.from}-${block2.headingRange.to}`
@@ -3233,7 +3293,8 @@ function emitTable(block2, resources = /* @__PURE__ */ new Map()) {
3233
3293
  },
3234
3294
  layout: block2.layout === "fixed" ? TableLayoutType.FIXED : TableLayoutType.AUTOFIT,
3235
3295
  // An empty grid is a table with nothing to say about its columns, which is
3236
- // not the same as one whose columns are all zero wide.
3296
+ // not the same as one whose columns are all zero wide. The compiler hands
3297
+ // over every grid in twips, the unit `columnWidths` takes.
3237
3298
  ...block2.columnGrid.values.length > 0 ? { columnWidths: block2.columnGrid.values } : {},
3238
3299
  ...block2.borders ? {
3239
3300
  borders: {
@@ -3327,7 +3388,10 @@ function emitTableCell(cell, resources) {
3327
3388
  function emitBorder(border3) {
3328
3389
  if (!border3) return void 0;
3329
3390
  return {
3330
- style: BORDER_STYLE[border3.style] ?? BorderStyle.SINGLE,
3391
+ // Already OOXML's word, and docx.js's `BorderStyle` values are exactly
3392
+ // those words. Mapping unknown ones to `single` here is what once hid an
3393
+ // authoring word the IR should never have carried.
3394
+ style: border3.style,
3331
3395
  size: border3.sizeEighthPoints ?? 0,
3332
3396
  color: border3.color?.hex ?? "000000",
3333
3397
  // Stated only when the IR states it, so every border that predates the
@@ -3336,7 +3400,7 @@ function emitBorder(border3) {
3336
3400
  ...border3.spacePoints !== void 0 ? { space: border3.spacePoints } : {}
3337
3401
  };
3338
3402
  }
3339
- var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
3403
+ var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, ThemedTableOfContents, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION;
3340
3404
  var init_emit = __esm({
3341
3405
  "src/renderers/docxjs/emit.ts"() {
3342
3406
  "use strict";
@@ -3356,6 +3420,11 @@ var init_emit = __esm({
3356
3420
  tight: TextWrappingType.TIGHT,
3357
3421
  topAndBottom: TextWrappingType.TOP_AND_BOTTOM
3358
3422
  };
3423
+ ThemedTableOfContents = class extends TableOfContents {
3424
+ };
3425
+ Object.defineProperty(ThemedTableOfContents.prototype, "getTabStopsForLevel", {
3426
+ value: () => []
3427
+ });
3359
3428
  VERTICAL_ALIGN = {
3360
3429
  top: VerticalAlign.TOP,
3361
3430
  center: VerticalAlign.CENTER,
@@ -3380,20 +3449,10 @@ var init_emit = __esm({
3380
3449
  inside: RelativeVerticalPosition.INSIDE,
3381
3450
  outside: RelativeVerticalPosition.OUTSIDE
3382
3451
  };
3383
- BORDER_STYLE = {
3384
- none: BorderStyle.NONE,
3385
- single: BorderStyle.SINGLE,
3386
- double: BorderStyle.DOUBLE,
3387
- dashed: BorderStyle.DASHED,
3388
- dotted: BorderStyle.DOTTED
3389
- };
3390
3452
  }
3391
3453
  });
3392
3454
 
3393
3455
  // src/renderers/docxjs/styles.ts
3394
- import {
3395
- BorderStyle as BorderStyle2
3396
- } from "docx";
3397
3456
  function runProperties(formatting) {
3398
3457
  const out = {};
3399
3458
  if (formatting.fontFamily !== void 0) out.font = formatting.fontFamily;
@@ -3402,7 +3461,7 @@ function runProperties(formatting) {
3402
3461
  }
3403
3462
  if (formatting.color) out.color = formatting.color.hex;
3404
3463
  if (formatting.bold !== void 0) out.bold = formatting.bold;
3405
- if (formatting.italic !== void 0) out.italic = formatting.italic;
3464
+ if (formatting.italic !== void 0) out.italics = formatting.italic;
3406
3465
  if (formatting.underline) {
3407
3466
  out.underline = {
3408
3467
  type: formatting.underline.type,
@@ -3414,7 +3473,9 @@ function runProperties(formatting) {
3414
3473
  if (formatting.subScript) out.subScript = true;
3415
3474
  if (formatting.smallCaps !== void 0) out.smallCaps = formatting.smallCaps;
3416
3475
  if (formatting.allCaps !== void 0) out.allCaps = formatting.allCaps;
3417
- if (formatting.highlight !== void 0) out.highlight = formatting.highlight;
3476
+ if (formatting.highlight !== void 0) {
3477
+ out.highlight = formatting.highlight;
3478
+ }
3418
3479
  if (formatting.characterSpacingTwentieths !== void 0) {
3419
3480
  out.characterSpacing = formatting.characterSpacingTwentieths;
3420
3481
  }
@@ -3428,7 +3489,10 @@ function runProperties(formatting) {
3428
3489
  }
3429
3490
  function border(value) {
3430
3491
  return {
3431
- style: BORDER_STYLE2[value.style] ?? BorderStyle2.SINGLE,
3492
+ // OOXML's word, which is what docx.js's `BorderStyle` values are. A theme
3493
+ // may state any of them; mapping the unlisted ones to `single` drew a
3494
+ // `wave` or `triple` rule as a plain line here and as asked on office-open.
3495
+ style: value.style,
3432
3496
  ...value.sizeEighthPoints !== void 0 ? { size: value.sizeEighthPoints } : {},
3433
3497
  ...value.color ? { color: value.color.hex } : {},
3434
3498
  ...value.spacePoints !== void 0 ? { space: value.spacePoints } : {}
@@ -3541,19 +3605,10 @@ function emitStyles(styles) {
3541
3605
  }
3542
3606
  };
3543
3607
  }
3544
- var BORDER_STYLE2;
3545
3608
  var init_styles2 = __esm({
3546
3609
  "src/renderers/docxjs/styles.ts"() {
3547
3610
  "use strict";
3548
3611
  init_emit();
3549
- BORDER_STYLE2 = {
3550
- none: BorderStyle2.NONE,
3551
- single: BorderStyle2.SINGLE,
3552
- double: BorderStyle2.DOUBLE,
3553
- dashed: BorderStyle2.DASHED,
3554
- dotted: BorderStyle2.DOTTED,
3555
- thick: BorderStyle2.THICK
3556
- };
3557
3612
  }
3558
3613
  });
3559
3614
 
@@ -3732,7 +3787,7 @@ import {
3732
3787
  Header,
3733
3788
  ImageRun,
3734
3789
  Packer,
3735
- LineRuleType as LineRuleType2,
3790
+ LineRuleType,
3736
3791
  Paragraph as Paragraph2
3737
3792
  } from "docx";
3738
3793
  function createDocxJsRenderer() {
@@ -4019,7 +4074,7 @@ function lastBlockIsFrame(section2) {
4019
4074
  function anchorParagraph(anchor) {
4020
4075
  return new Paragraph2({
4021
4076
  children: anchor ? [anchor] : [],
4022
- spacing: { before: 0, after: 0, line: 20, lineRule: LineRuleType2.EXACT }
4077
+ spacing: { before: 0, after: 0, line: 20, lineRule: LineRuleType.EXACT }
4023
4078
  });
4024
4079
  }
4025
4080
  function coreProperties(ir) {
@@ -4116,13 +4171,30 @@ import {
4116
4171
  spliceChartXml
4117
4172
  } from "@json-to-office/shared/rendering";
4118
4173
  function spliceInput(chart) {
4174
+ const { bold, ...rest } = chart.textFont;
4175
+ const textFont = rest;
4176
+ const axisTitleFont = { ...rest, bold };
4177
+ const chartTitleFont = {
4178
+ ...rest,
4179
+ fontSize: Math.round(rest.fontSize * CHART_TITLE_SCALE),
4180
+ bold: true
4181
+ };
4119
4182
  return {
4120
4183
  chartType: chart.chartType,
4121
4184
  series: chart.series,
4122
4185
  colors: chart.colors,
4186
+ textFont,
4187
+ titleFont: chartTitleFont,
4123
4188
  ...chart.legendPosition ? { legendPosition: chart.legendPosition } : {},
4124
- ...chart.categoryAxisTitle ? { categoryAxis: { title: chart.categoryAxisTitle } } : {},
4125
- ...chart.valueAxisTitle ? { valueAxis: { title: chart.valueAxisTitle } } : {}
4189
+ ...chart.categoryAxisTitle ? {
4190
+ categoryAxis: {
4191
+ title: chart.categoryAxisTitle,
4192
+ titleFont: axisTitleFont
4193
+ }
4194
+ } : {},
4195
+ ...chart.valueAxisTitle ? {
4196
+ valueAxis: { title: chart.valueAxisTitle, titleFont: axisTitleFont }
4197
+ } : {}
4126
4198
  };
4127
4199
  }
4128
4200
  function declareWorkbookContentType(zip) {
@@ -4166,10 +4238,12 @@ function spliceChartParts(zip, charts) {
4166
4238
  }
4167
4239
  declareWorkbookContentType(zip);
4168
4240
  }
4241
+ var CHART_TITLE_SCALE;
4169
4242
  var init_chartParts = __esm({
4170
4243
  "src/renderers/office-open/chartParts.ts"() {
4171
4244
  "use strict";
4172
4245
  init_chartWorkbook();
4246
+ CHART_TITLE_SCALE = 1.4;
4173
4247
  }
4174
4248
  });
4175
4249
 
@@ -4193,10 +4267,17 @@ function runProperties2(formatting) {
4193
4267
  if (formatting.fontFamily) out.font = formatting.fontFamily;
4194
4268
  if (formatting.sizeHalfPoints !== void 0) {
4195
4269
  out.size = formatting.sizeHalfPoints / 2;
4270
+ if (formatting.sizeHalfPoints) out.sizeComplexScript = out.size;
4196
4271
  }
4197
4272
  if (formatting.color) out.color = formatting.color.hex;
4198
- if (formatting.bold !== void 0) out.bold = formatting.bold;
4199
- if (formatting.italic !== void 0) out.italic = formatting.italic;
4273
+ if (formatting.bold !== void 0) {
4274
+ out.bold = formatting.bold;
4275
+ out.boldComplexScript = formatting.bold;
4276
+ }
4277
+ if (formatting.italic !== void 0) {
4278
+ out.italic = formatting.italic;
4279
+ out.italicComplexScript = formatting.italic;
4280
+ }
4200
4281
  if (formatting.underline) {
4201
4282
  out.underline = {
4202
4283
  type: formatting.underline.type,
@@ -4715,9 +4796,6 @@ function tocEntry(entry) {
4715
4796
  return {
4716
4797
  paragraph: {
4717
4798
  style: `TOC${entry.level}`,
4718
- tabStops: [
4719
- { type: "right", position: TOC_PAGE_TAB_TWIPS, leader: "dot" }
4720
- ],
4721
4799
  children: [{ text: entry.text }, { children: [{ tab: true }] }]
4722
4800
  }
4723
4801
  };
@@ -4747,13 +4825,17 @@ function table(value, ctx) {
4747
4825
  return {
4748
4826
  rows: value.rows.map((row) => tableRow(row, ctx)),
4749
4827
  width: {
4750
- size: value.width.kind === "auto" ? 0 : value.width.value,
4828
+ // `w:w` is an integer in OOXML. A grid that splits a remainder between
4829
+ // unstated columns comes out fractional, and the XML would carry it
4830
+ // verbatim; floored here, as the docxjs backend floors it.
4831
+ size: value.width.kind === "auto" ? 0 : value.width.kind === "twips" ? Math.floor(value.width.value) : value.width.value,
4751
4832
  type: value.width.kind === "twips" ? "dxa" : value.width.kind === "percent" ? "pct" : "auto"
4752
4833
  },
4753
4834
  layout: value.layout,
4754
4835
  // An empty grid is a table with nothing to say about its columns, which is
4755
- // not the same as one whose columns are all zero wide.
4756
- ...value.columnGrid.values.length > 0 ? { columnWidths: value.columnGrid.values } : {},
4836
+ // not the same as one whose columns are all zero wide. The compiler hands
4837
+ // over every grid in twips; floored for the same reason as the width.
4838
+ ...value.columnGrid.values.length > 0 ? { columnWidths: value.columnGrid.values.map(Math.floor) } : {},
4757
4839
  ...value.alignment ? { alignment: alignment(value.alignment) } : {},
4758
4840
  ...value.borders ? { borders: borders2(value.borders) } : {},
4759
4841
  ...value.cellMargins ? { margins: cellMargins(value.cellMargins) } : {},
@@ -4887,7 +4969,13 @@ function section(value, ctx, closesDocument = false) {
4887
4969
  }))
4888
4970
  } : {}
4889
4971
  }
4890
- } : {}
4972
+ } : {},
4973
+ // No document grid, as the IR states none. Left to the backend, every
4974
+ // section gets the one Word's Chinese template sets — a 15.6pt line grid
4975
+ // (`w:type="lines"`, pitch 312) — and every line is laid out on it here
4976
+ // but at its own height on docx.js, which writes no grid type. A field
4977
+ // the backend is not given takes that default, so all three are stated.
4978
+ grid: { type: "default", linePitch: 360, charSpace: 0 }
4891
4979
  },
4892
4980
  children: sectionChildren2(value, ctx, closesDocument),
4893
4981
  ...value.headers ? { headers: headerFooterSet(value.headers, ctx) } : {},
@@ -4945,7 +5033,7 @@ function numberingConfig2(numbering) {
4945
5033
  }))
4946
5034
  };
4947
5035
  }
4948
- var WRAP_TYPE2, BODY_ANCHOR, TOC_PAGE_TAB_TWIPS;
5036
+ var WRAP_TYPE2, BODY_ANCHOR;
4949
5037
  var init_emit2 = __esm({
4950
5038
  "src/renderers/office-open/emit.ts"() {
4951
5039
  "use strict";
@@ -4961,7 +5049,6 @@ var init_emit2 = __esm({
4961
5049
  middle: "ctr",
4962
5050
  bottom: "b"
4963
5051
  };
4964
- TOC_PAGE_TAB_TWIPS = 9025;
4965
5052
  }
4966
5053
  });
4967
5054
 
@@ -4973,12 +5060,11 @@ function builtIn2(style) {
4973
5060
  };
4974
5061
  }
4975
5062
  function emitStyles2(styles) {
4976
- const documentDefaults = {
4977
- ...Object.keys(styles.defaults.run).length > 0 ? { run: runProperties2(styles.defaults.run) } : {},
4978
- ...Object.keys(styles.defaults.paragraph).length > 0 ? { paragraph: paragraphProperties2(styles.defaults.paragraph) } : {}
4979
- };
4980
5063
  const defaults = {
4981
- ...Object.keys(documentDefaults).length > 0 ? { document: documentDefaults } : {}
5064
+ document: {
5065
+ run: Object.keys(styles.defaults.run).length > 0 ? runProperties2(styles.defaults.run) : null,
5066
+ paragraph: Object.keys(styles.defaults.paragraph).length > 0 ? paragraphProperties2(styles.defaults.paragraph) : null
5067
+ }
4982
5068
  };
4983
5069
  for (const slot of [
4984
5070
  "footnoteText",
@@ -5007,7 +5093,7 @@ function emitStyles2(styles) {
5007
5093
  run: runProperties2(style.run)
5008
5094
  }))
5009
5095
  } : {},
5010
- ...Object.keys(defaults).length > 0 ? { default: defaults } : {}
5096
+ default: defaults
5011
5097
  };
5012
5098
  }
5013
5099
  var init_styles3 = __esm({
@@ -5242,7 +5328,9 @@ function addPngTextChunk(data, marker) {
5242
5328
  }
5243
5329
  offset = chunkEnd;
5244
5330
  }
5245
- return Buffer.concat([data, marker]);
5331
+ throw new Error(
5332
+ "Cannot mark a PNG that has no intact IEND chunk; its bytes were not checked to decode before rendering"
5333
+ );
5246
5334
  }
5247
5335
  function addJpegComment(data, marker) {
5248
5336
  if (data.length < 2 || data[0] !== 255 || data[1] !== 216) {
@@ -6226,6 +6314,7 @@ function containsComponent(root, name) {
6226
6314
  }
6227
6315
 
6228
6316
  // src/core/desugarExternals.ts
6317
+ init_layoutUtils();
6229
6318
  import {
6230
6319
  clampVisualDpi as clampVisualDpi2,
6231
6320
  DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI2,
@@ -6237,7 +6326,7 @@ import {
6237
6326
 
6238
6327
  // src/components/highcharts.ts
6239
6328
  init_colorUtils();
6240
- init_layoutUtils();
6329
+ init_widthUtils();
6241
6330
  import {
6242
6331
  chartFamilyResolver,
6243
6332
  remoteExportNotice,
@@ -6348,8 +6437,7 @@ function placedWidthPt(config, theme) {
6348
6437
  const chart = config.options.chart;
6349
6438
  if (typeof width === "number") return width * POINTS_PER_PIXEL;
6350
6439
  if (typeof width === "string") {
6351
- const page = getPageSetup(theme);
6352
- const contentWidthPt = (page.size.width - page.margin.left - page.margin.right) * POINTS_PER_TWIP;
6440
+ const contentWidthPt = getAvailableWidthTwips(theme) * POINTS_PER_TWIP;
6353
6441
  return parseFloat(width) / 100 * contentWidthPt;
6354
6442
  }
6355
6443
  if (typeof height === "number" && chart.height > 0) {
@@ -6429,8 +6517,18 @@ async function desugarExternals(document, options) {
6429
6517
  }
6430
6518
  ).catch(() => /* @__PURE__ */ new Map());
6431
6519
  const chartCache = /* @__PURE__ */ new Map();
6432
- return transformComponents(document, async (node) => {
6520
+ const replace = (theme, inSection) => async function replaceNode(node) {
6433
6521
  if (node.enabled === false) return void 0;
6522
+ if (node.name === "section" && !inSection) {
6523
+ const page = node.props.page;
6524
+ return transformComponents(
6525
+ node,
6526
+ replace(themeForSectionPage(theme, page), true)
6527
+ );
6528
+ }
6529
+ return replaceExternal(node, theme);
6530
+ };
6531
+ const replaceExternal = async (node, theme) => {
6434
6532
  if (node.name === "visual") {
6435
6533
  if (isNativeVisualProps2(node.props)) return void 0;
6436
6534
  return withNodeIdentity(node, {
@@ -6448,7 +6546,7 @@ async function desugarExternals(document, options) {
6448
6546
  name: "image",
6449
6547
  props: await renderChartToImageProps(
6450
6548
  node.props,
6451
- options.theme,
6549
+ theme,
6452
6550
  options.services?.highcharts,
6453
6551
  options.chartFonts,
6454
6552
  options.warnings,
@@ -6457,7 +6555,8 @@ async function desugarExternals(document, options) {
6457
6555
  });
6458
6556
  }
6459
6557
  return void 0;
6460
- });
6558
+ };
6559
+ return transformComponents(document, replace(options.theme, false));
6461
6560
  }
6462
6561
  async function visualImageProps(props, prerastered, options) {
6463
6562
  const serviceConfig = options.services?.pptx;
@@ -6492,6 +6591,7 @@ init_imageUtils();
6492
6591
  import {
6493
6592
  isNativeVisualProps as isNativeVisualProps3
6494
6593
  } from "@json-to-office/shared-docx";
6594
+ import { imageIntegrityDefect } from "@json-to-office/shared/images/node";
6495
6595
  async function loadImageResources(components) {
6496
6596
  const sources = /* @__PURE__ */ new Set();
6497
6597
  for (const component of components) collectSources(component, sources);
@@ -6501,6 +6601,8 @@ async function loadImageResources(components) {
6501
6601
  [...sources].map(async (source) => {
6502
6602
  try {
6503
6603
  const result = await getImageBuffer(source);
6604
+ const defect = imageIntegrityDefect(result.buffer);
6605
+ if (defect) throw new Error(defect);
6504
6606
  const intrinsic = readIntrinsic(result.buffer, source);
6505
6607
  loaded.set(source, {
6506
6608
  bytes: result.buffer,
@@ -7099,6 +7201,7 @@ function resolveColors(entries, at, deps) {
7099
7201
 
7100
7202
  // src/styles/themeToStyles.ts
7101
7203
  init_themes();
7204
+ init_types();
7102
7205
  init_colorUtils();
7103
7206
  init_styleHelpers();
7104
7207
  import {
@@ -7265,10 +7368,13 @@ function convertParagraphProperties(merged, styleProps, theme) {
7265
7368
  }
7266
7369
  };
7267
7370
  }
7371
+ function themeBorderStyle(style) {
7372
+ return DOCX_IR_BORDER_STYLES.includes(style) ? style : "single";
7373
+ }
7268
7374
  function convertBorders(borders3, theme) {
7269
7375
  if (!borders3) return void 0;
7270
7376
  const mapSide = (side) => side ? {
7271
- style: side.style,
7377
+ style: themeBorderStyle(side.style),
7272
7378
  sizeEighthPoints: side.size,
7273
7379
  color: { hex: resolveColor(side.color, theme) },
7274
7380
  ...side.space !== void 0 ? { spacePoints: side.space } : {}
@@ -8455,7 +8561,11 @@ function resolveTableModel(source, theme, themeName, options = {}) {
8455
8561
  });
8456
8562
  adjudicateInteriorEdges([...hasHeader ? [header] : [], ...rows]);
8457
8563
  return {
8458
- ...resolveWidths(source, columns, theme, themeName),
8564
+ ...resolveWidths(
8565
+ source,
8566
+ columns,
8567
+ options.measureTwips ?? getAvailableWidthTwips(theme, themeName)
8568
+ ),
8459
8569
  ...hasHeader ? { header } : {},
8460
8570
  rows,
8461
8571
  repeatHeader: source.repeatHeaderOnPageBreak ?? true
@@ -8509,22 +8619,24 @@ function adjudicateInteriorEdges(rows) {
8509
8619
  });
8510
8620
  });
8511
8621
  }
8512
- function resolveWidths(source, columns, theme, themeName) {
8622
+ function resolveWidths(source, columns, measureTwips) {
8513
8623
  const hasExplicitWidths = columns.some((col) => col.width !== void 0);
8514
8624
  if (!hasExplicitWidths) {
8515
- const share = 100 / columns.length;
8625
+ const percent = source.width ?? 100;
8516
8626
  return {
8517
- columnGrid: { unit: "percent", values: columns.map(() => share) },
8518
- width: { size: source.width ?? 100, unit: "percent" }
8627
+ columnGrid: {
8628
+ unit: "twips",
8629
+ values: splitTwips(columns.length, measureTwips * percent / 100)
8630
+ },
8631
+ width: { size: percent, unit: "percent" }
8519
8632
  };
8520
8633
  }
8521
- const available = getAvailableWidthTwips(theme, themeName);
8522
8634
  const stated = columns.map(
8523
- (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, available) : void 0
8635
+ (col) => col.width !== void 0 ? relativeLengthToTwips(col.width, measureTwips) : void 0
8524
8636
  );
8525
8637
  const totalStated = stated.reduce((sum, w) => sum + (w ?? 0), 0);
8526
8638
  const unstatedCount = columns.filter((col) => col.width === void 0).length;
8527
- const remaining = Math.max(0, available - totalStated);
8639
+ const remaining = Math.max(0, measureTwips - totalStated);
8528
8640
  const shareOfRemainder = unstatedCount > 0 ? remaining / unstatedCount : pointsToTwips(72);
8529
8641
  const values = stated.map((w) => w || shareOfRemainder);
8530
8642
  return {
@@ -8533,7 +8645,7 @@ function resolveWidths(source, columns, theme, themeName) {
8533
8645
  size: values.reduce((sum, w) => sum + w, 0),
8534
8646
  unit: "twips"
8535
8647
  },
8536
- ...totalStated > available ? { overflow: { totalTwips: totalStated, availableTwips: available } } : {}
8648
+ ...totalStated > measureTwips ? { overflow: { totalTwips: totalStated, availableTwips: measureTwips } } : {}
8537
8649
  };
8538
8650
  }
8539
8651
 
@@ -9249,6 +9361,36 @@ function emptyToUndefined(formatting) {
9249
9361
 
9250
9362
  // src/ir/compiler.ts
9251
9363
  init_types();
9364
+
9365
+ // src/ir/measure.ts
9366
+ var DEFAULT_COLUMN_SPACE_TWIPS = 720;
9367
+ function textWidthTwips(page) {
9368
+ const { leftTwips, rightTwips, gutterTwips = 0 } = page.margins;
9369
+ return Math.max(0, page.widthTwips - leftTwips - rightTwips - gutterTwips);
9370
+ }
9371
+ function sectionMeasureTwips(properties) {
9372
+ const textWidth = textWidthTwips(properties.page);
9373
+ const columns = properties.columns;
9374
+ if (!columns || columns.count <= 1) return textWidth;
9375
+ if (columns.equalWidth !== true && columns.widths?.length) {
9376
+ return Math.min(...columns.widths.map((column) => column.widthTwips));
9377
+ }
9378
+ const space = columns.spaceTwips ?? DEFAULT_COLUMN_SPACE_TWIPS;
9379
+ return Math.max(0, (textWidth - space * (columns.count - 1)) / columns.count);
9380
+ }
9381
+ function cellMeasureTwips(widthTwips, margins) {
9382
+ return Math.max(
9383
+ 0,
9384
+ widthTwips - (margins?.leftTwips ?? 0) - (margins?.rightTwips ?? 0)
9385
+ );
9386
+ }
9387
+ function tableWidthTwips(width, measureTwips) {
9388
+ if (width.kind === "twips") return width.value;
9389
+ if (width.kind === "percent") return measureTwips * width.value / 100;
9390
+ return measureTwips;
9391
+ }
9392
+
9393
+ // src/ir/compiler.ts
9252
9394
  init_units();
9253
9395
  var CONTENT_BOOKMARK_ID_BASE = 2e6;
9254
9396
  var SINGLE_LINE_TWIPS = 240;
@@ -9427,11 +9569,64 @@ function resolveSectionChrome(sections) {
9427
9569
  });
9428
9570
  }
9429
9571
  function compileSection(section2, index, ordinal, chrome, ctx) {
9572
+ const documentTheme = ctx.theme;
9573
+ ctx.theme = themeForSectionPage(documentTheme, section2.pageOverride);
9574
+ try {
9575
+ return compileSectionBody(section2, index, ordinal, chrome, ctx);
9576
+ } finally {
9577
+ ctx.theme = documentTheme;
9578
+ }
9579
+ }
9580
+ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9430
9581
  const path3 = `sections[${index}]`;
9431
9582
  const { page, column } = section2.properties;
9583
+ if (section2.forcedPageBreak) {
9584
+ warnOnce(
9585
+ ctx,
9586
+ "section",
9587
+ `${path3} is set to continue on the page but changes the paper size or orientation, which a continuous section break cannot do, so it starts on a new page instead. Set pageBreak: true on the section to say so, or keep the page size of the section before it.`,
9588
+ "W_SECTION_PAGE_BREAK_FORCED"
9589
+ );
9590
+ }
9432
9591
  if (column && column.count > 1) {
9433
9592
  ctx.features.require("columns", `${path3}.properties.columns`);
9434
9593
  }
9594
+ const properties = {
9595
+ page: {
9596
+ widthTwips: page.size.width,
9597
+ heightTwips: page.size.height,
9598
+ orientation: page.size.width > page.size.height ? "landscape" : "portrait",
9599
+ ...page.size.code !== void 0 ? { code: page.size.code } : {},
9600
+ margins: {
9601
+ topTwips: page.margin.top,
9602
+ bottomTwips: page.margin.bottom,
9603
+ leftTwips: page.margin.left,
9604
+ rightTwips: page.margin.right,
9605
+ ...page.margin.header !== void 0 ? { headerTwips: page.margin.header } : {},
9606
+ ...page.margin.footer !== void 0 ? { footerTwips: page.margin.footer } : {},
9607
+ ...page.margin.gutter !== void 0 ? { gutterTwips: page.margin.gutter } : {}
9608
+ }
9609
+ },
9610
+ // A chunk that starts on a new page says so; the rest continue the flow.
9611
+ // The layout stage already decided which, so it is carried rather than
9612
+ // re-derived from `breakBefore`: a section can be continuous without the
9613
+ // two agreeing.
9614
+ ...section2.properties.type ? { type: sectionType(section2.properties.type) } : {},
9615
+ ...column ? {
9616
+ columns: {
9617
+ count: column.count,
9618
+ ...column.space !== void 0 ? { spaceTwips: column.space } : {},
9619
+ ...column.equalWidth !== void 0 ? { equalWidth: column.equalWidth } : {},
9620
+ ...column.widths ? {
9621
+ widths: column.widths.map((c) => ({
9622
+ widthTwips: c.width,
9623
+ ...c.space !== void 0 ? { spaceTwips: c.space } : {}
9624
+ }))
9625
+ } : {}
9626
+ }
9627
+ } : {}
9628
+ };
9629
+ const measureTwips = sectionMeasureTwips(properties);
9435
9630
  const children = [];
9436
9631
  const sectionBookmarkName = section2.belongsToUserSection && ordinal.ordinal !== void 0 ? `_Section_${ordinal.ordinal}` : void 0;
9437
9632
  section2.components.forEach((component, i) => {
@@ -9441,6 +9636,7 @@ function compileSection(section2, index, ordinal, chrome, ctx) {
9441
9636
  ctx,
9442
9637
  path: `${path3}.children[${i}]`,
9443
9638
  id: blockId(index, [i]),
9639
+ measureTwips,
9444
9640
  ...sectionBookmarkName ? { sectionBookmarkName } : {}
9445
9641
  })
9446
9642
  );
@@ -9453,41 +9649,7 @@ function compileSection(section2, index, ordinal, chrome, ctx) {
9453
9649
  id: `s${index}`,
9454
9650
  path: path3,
9455
9651
  children,
9456
- properties: {
9457
- page: {
9458
- widthTwips: page.size.width,
9459
- heightTwips: page.size.height,
9460
- orientation: page.size.width > page.size.height ? "landscape" : "portrait",
9461
- ...page.size.code !== void 0 ? { code: page.size.code } : {},
9462
- margins: {
9463
- topTwips: page.margin.top,
9464
- bottomTwips: page.margin.bottom,
9465
- leftTwips: page.margin.left,
9466
- rightTwips: page.margin.right,
9467
- ...page.margin.header !== void 0 ? { headerTwips: page.margin.header } : {},
9468
- ...page.margin.footer !== void 0 ? { footerTwips: page.margin.footer } : {},
9469
- ...page.margin.gutter !== void 0 ? { gutterTwips: page.margin.gutter } : {}
9470
- }
9471
- },
9472
- // A chunk that starts on a new page says so; the rest continue the flow.
9473
- // The layout stage already decided which, so it is carried rather than
9474
- // re-derived from `breakBefore`: a section can be continuous without the
9475
- // two agreeing.
9476
- ...section2.properties.type ? { type: sectionType(section2.properties.type) } : {},
9477
- ...column ? {
9478
- columns: {
9479
- count: column.count,
9480
- ...column.space !== void 0 ? { spaceTwips: column.space } : {},
9481
- ...column.equalWidth !== void 0 ? { equalWidth: column.equalWidth } : {},
9482
- ...column.widths ? {
9483
- widths: column.widths.map((c) => ({
9484
- widthTwips: c.width,
9485
- ...c.space !== void 0 ? { spaceTwips: c.space } : {}
9486
- }))
9487
- } : {}
9488
- }
9489
- } : {}
9490
- }
9652
+ properties
9491
9653
  };
9492
9654
  if (ordinal.ordinal !== void 0) {
9493
9655
  compiled.bookmark = {
@@ -9498,9 +9660,22 @@ function compileSection(section2, index, ordinal, chrome, ctx) {
9498
9660
  };
9499
9661
  ctx.features.require("bookmarks", `${path3}.bookmark`);
9500
9662
  }
9501
- const headers = compilePart(chrome.header, "header", index, ctx);
9663
+ const chromeMeasureTwips = textWidthTwips(properties.page);
9664
+ const headers = compilePart(
9665
+ chrome.header,
9666
+ "header",
9667
+ index,
9668
+ chromeMeasureTwips,
9669
+ ctx
9670
+ );
9502
9671
  if (headers) compiled.headers = { default: headers };
9503
- const footers = compilePart(chrome.footer, "footer", index, ctx);
9672
+ const footers = compilePart(
9673
+ chrome.footer,
9674
+ "footer",
9675
+ index,
9676
+ chromeMeasureTwips,
9677
+ ctx
9678
+ );
9504
9679
  if (footers) compiled.footers = { default: footers };
9505
9680
  if (headers || footers) {
9506
9681
  ctx.features.require("headers-footers", `${path3}.headers`);
@@ -9519,7 +9694,7 @@ function sectionType(value) {
9519
9694
  return void 0;
9520
9695
  }
9521
9696
  }
9522
- function compilePart(part, kind, sectionIndex, ctx) {
9697
+ function compilePart(part, kind, sectionIndex, measureTwips, ctx) {
9523
9698
  if (!part) return void 0;
9524
9699
  const id = `${kind}:s${sectionIndex}:default`;
9525
9700
  const children = [];
@@ -9530,7 +9705,8 @@ function compilePart(part, kind, sectionIndex, ctx) {
9530
9705
  const scope = {
9531
9706
  ctx,
9532
9707
  path: `sections[${sectionIndex}].${kind}s.default.children[${i}]`,
9533
- id: headerFooterBlockId(id, [i])
9708
+ id: headerFooterBlockId(id, [i]),
9709
+ measureTwips
9534
9710
  };
9535
9711
  children.push(
9536
9712
  ...component.name === "paragraph" ? compileChromeParagraph(component, scope) : component.name === "image" ? compileChromeImage(component, scope) : compileComponent(component, scope)
@@ -9606,9 +9782,7 @@ function compileChromeImage(component, scope) {
9606
9782
  if (mediaType === "svg") ctx.features.require("svg-images", path3);
9607
9783
  const page = getPageSetup(ctx.theme);
9608
9784
  const pageWidthPx = Math.round(twipsToPixels(page.size.width));
9609
- const contentWidthPx = Math.round(
9610
- twipsToPixels(page.size.width - page.margin.left - page.margin.right)
9611
- );
9785
+ const contentWidthPx = Math.round(twipsToPixels(scope.measureTwips));
9612
9786
  const pageHeightPx = Math.round(twipsToPixels(page.size.height));
9613
9787
  const contentHeightPx = Math.round(
9614
9788
  twipsToPixels(page.size.height - page.margin.top - page.margin.bottom)
@@ -9816,7 +9990,7 @@ function compileColumns(component, scope) {
9816
9990
  const props = component.props ?? {};
9817
9991
  const configs = columnConfigs(props.columns);
9818
9992
  if (configs.length === 0) return [];
9819
- const available = getAvailableWidthTwips(ctx.theme, ctx.themeName);
9993
+ const available = scope.measureTwips;
9820
9994
  const { widths, gaps } = columnMetrics(configs, props.gap, available);
9821
9995
  const stated = widths.map(
9822
9996
  (width, index) => width + gaps[index] / 2 + (index > 0 ? gaps[index - 1] / 2 : 0)
@@ -9845,11 +10019,18 @@ function compileColumns(component, scope) {
9845
10019
  rows: [
9846
10020
  {
9847
10021
  cells: configs.map((_, index) => {
10022
+ const margins = {
10023
+ topTwips: 0,
10024
+ rightTwips: gaps[index] / 2,
10025
+ bottomTwips: 0,
10026
+ leftTwips: index > 0 ? gaps[index - 1] / 2 : 0
10027
+ };
9848
10028
  const blocks = contents[index].flatMap(
9849
10029
  (child, childIndex) => compileComponent(child, {
9850
10030
  ...scope,
9851
10031
  path: `${path3}.children[${index}][${childIndex}]`,
9852
- id: `${scope.id}:c${index}:${childIndex}`
10032
+ id: `${scope.id}:c${index}:${childIndex}`,
10033
+ measureTwips: cellMeasureTwips(cells[index], margins)
9853
10034
  })
9854
10035
  );
9855
10036
  return {
@@ -9864,12 +10045,7 @@ function compileColumns(component, scope) {
9864
10045
  }
9865
10046
  ],
9866
10047
  widthTwips: cells[index],
9867
- margins: {
9868
- topTwips: 0,
9869
- rightTwips: gaps[index] / 2,
9870
- bottomTwips: 0,
9871
- leftTwips: index > 0 ? gaps[index - 1] / 2 : 0
9872
- },
10048
+ margins,
9873
10049
  verticalAlign: "top",
9874
10050
  borders: {
9875
10051
  top: NO_BORDERS.top,
@@ -9926,6 +10102,16 @@ var DEFAULT_TEXT_BOX_WIDTH_TWIPS = 5e3;
9926
10102
  function compileTextBox(component, scope) {
9927
10103
  const { ctx, path: path3 } = scope;
9928
10104
  const props = component.props ?? {};
10105
+ const style = props.style;
10106
+ const padding = style?.padding;
10107
+ const width = textBoxWidth(props);
10108
+ const widthTwips = Math.round(tableWidthTwips(width, scope.measureTwips));
10109
+ const margins = {
10110
+ topTwips: padding?.top ? pointsToTwips2(padding.top) : 0,
10111
+ rightTwips: padding?.right ? pointsToTwips2(padding.right) : 0,
10112
+ bottomTwips: padding?.bottom ? pointsToTwips2(padding.bottom) : 0,
10113
+ leftTwips: padding?.left ? pointsToTwips2(padding.left) : 0
10114
+ };
9929
10115
  const children = [];
9930
10116
  const contents = component.children ?? [];
9931
10117
  contents.forEach((child, index) => {
@@ -9933,12 +10119,12 @@ function compileTextBox(component, scope) {
9933
10119
  ...compileComponent(child, {
9934
10120
  ...scope,
9935
10121
  path: `${path3}.children[${index}]`,
9936
- id: `${scope.id}:c${index}`
10122
+ id: `${scope.id}:c${index}`,
10123
+ // What the box holds is set against the box less its padding.
10124
+ measureTwips: cellMeasureTwips(widthTwips, margins)
9937
10125
  })
9938
10126
  );
9939
10127
  });
9940
- const style = props.style;
9941
- const padding = style?.padding;
9942
10128
  if (props.renderAs === "shape") {
9943
10129
  const shape = compileShape2(props, children, scope);
9944
10130
  if (shape) return shape;
@@ -9950,9 +10136,11 @@ function compileTextBox(component, scope) {
9950
10136
  kind: "table",
9951
10137
  id: scope.id,
9952
10138
  path: path3,
9953
- // No grid: one cell, whose width the table itself decides.
9954
- columnGrid: { unit: "twips", values: [] },
9955
- width: textBoxWidth(props),
10139
+ // One cell, as wide as the box. Left out, the grid would be each
10140
+ // backend's 100-twip placeholder, which a reader that takes the grid as
10141
+ // the column widths draws.
10142
+ columnGrid: { unit: "twips", values: [widthTwips] },
10143
+ width,
9956
10144
  layout: "fixed",
9957
10145
  // The container is invisible; whatever border the author asked for is
9958
10146
  // drawn by the cell inside it.
@@ -9972,12 +10160,7 @@ function compileTextBox(component, scope) {
9972
10160
  children: []
9973
10161
  }
9974
10162
  ],
9975
- margins: {
9976
- topTwips: padding?.top ? pointsToTwips2(padding.top) : 0,
9977
- rightTwips: padding?.right ? pointsToTwips2(padding.right) : 0,
9978
- bottomTwips: padding?.bottom ? pointsToTwips2(padding.bottom) : 0,
9979
- leftTwips: padding?.left ? pointsToTwips2(padding.left) : 0
9980
- },
10163
+ margins,
9981
10164
  ...style?.shading?.fill ? {
9982
10165
  shading: {
9983
10166
  fill: irColor(
@@ -9996,8 +10179,11 @@ function compileTextBox(component, scope) {
9996
10179
  var EMU_PER_PIXEL = 9525;
9997
10180
  function compileShape2(props, children, scope) {
9998
10181
  const { ctx, path: path3 } = scope;
9999
- const width = shapeSize(props.width, "width", ctx);
10000
- const height = shapeSize(props.height, "height", ctx);
10182
+ const width = shapeSize(props.width, scope.measureTwips);
10183
+ const height = shapeSize(
10184
+ props.height,
10185
+ getAvailableHeightTwips(ctx.theme, ctx.themeName)
10186
+ );
10001
10187
  if (width.pixels === void 0 || height.pixels === void 0) {
10002
10188
  warnOnce(
10003
10189
  ctx,
@@ -10028,7 +10214,7 @@ function compileShape2(props, children, scope) {
10028
10214
  warnOnce(
10029
10215
  ctx,
10030
10216
  "text-box",
10031
- '[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.'
10217
+ '[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.'
10032
10218
  );
10033
10219
  }
10034
10220
  if (outline.ignoredSides.length > 0) {
@@ -10073,7 +10259,7 @@ function compileShape2(props, children, scope) {
10073
10259
  }
10074
10260
  ];
10075
10261
  }
10076
- function shapeSize(value, axis, ctx) {
10262
+ function shapeSize(value, referenceTwips) {
10077
10263
  if (typeof value === "number") {
10078
10264
  return { pixels: Math.round(value), resolvedPercentage: false };
10079
10265
  }
@@ -10082,9 +10268,8 @@ function shapeSize(value, axis, ctx) {
10082
10268
  if (!Number.isFinite(fraction) || fraction <= 0) {
10083
10269
  return { resolvedPercentage: false };
10084
10270
  }
10085
- const availableTwips = axis === "width" ? getAvailableWidthTwips(ctx.theme, ctx.themeName) : getAvailableHeightTwips(ctx.theme, ctx.themeName);
10086
10271
  return {
10087
- pixels: Math.round(availableTwips * fraction / TWIPS_PER_PIXEL),
10272
+ pixels: Math.round(referenceTwips * fraction / TWIPS_PER_PIXEL),
10088
10273
  resolvedPercentage: true
10089
10274
  };
10090
10275
  }
@@ -10159,10 +10344,20 @@ function textBoxWidth(props) {
10159
10344
  value: typeof raw === "number" ? raw * TWIPS_PER_PIXEL : DEFAULT_TEXT_BOX_WIDTH_TWIPS
10160
10345
  };
10161
10346
  }
10347
+ function borderLineStyle(authored) {
10348
+ switch (authored) {
10349
+ case "dashed":
10350
+ case "dotted":
10351
+ case "double":
10352
+ return authored;
10353
+ default:
10354
+ return "single";
10355
+ }
10356
+ }
10162
10357
  function compileBorders(border3, ctx) {
10163
10358
  if (!border3) return void 0;
10164
10359
  const side = (value) => value ? {
10165
- style: String(value.style ?? "single"),
10360
+ style: value.style === "none" ? "none" : borderLineStyle(value.style),
10166
10361
  // Points to eighths of a point, never thinner than the hairline a
10167
10362
  // reader can actually see.
10168
10363
  sizeEighthPoints: value.width !== void 0 ? Math.max(1, Math.round(value.width * 8)) : 1,
@@ -10266,12 +10461,6 @@ function compileToc(component, scope) {
10266
10461
  var DEFAULT_DIVIDER_SPACING_PT = 6;
10267
10462
  var DEFAULT_DIVIDER_THICKNESS_PT = 1;
10268
10463
  var DIVIDER_LINE_TWIPS = 20;
10269
- var DIVIDER_BORDER_STYLE = {
10270
- solid: "single",
10271
- dashed: "dashed",
10272
- dotted: "dotted",
10273
- double: "double"
10274
- };
10275
10464
  function compileDivider(component, scope) {
10276
10465
  const { ctx, path: path3 } = scope;
10277
10466
  const props = component.props ?? {};
@@ -10300,10 +10489,10 @@ function compileDivider(component, scope) {
10300
10489
  lineTwips: DIVIDER_LINE_TWIPS,
10301
10490
  lineRule: "exact"
10302
10491
  },
10303
- ...dividerIndent(props, ctx, path3),
10492
+ ...dividerIndent(props, scope),
10304
10493
  borders: {
10305
10494
  bottom: {
10306
- style: DIVIDER_BORDER_STYLE[String(props.style)] ?? "single",
10495
+ style: borderLineStyle(props.style),
10307
10496
  sizeEighthPoints: pointsToEighthPoints(thicknessPt),
10308
10497
  color: irColor(
10309
10498
  resolveColor(String(props.color ?? "border"), ctx.theme)
@@ -10317,14 +10506,10 @@ function compileDivider(component, scope) {
10317
10506
  }
10318
10507
  ];
10319
10508
  }
10320
- function dividerIndent(props, ctx, path3) {
10509
+ function dividerIndent(props, scope) {
10510
+ const { ctx, path: path3, measureTwips } = scope;
10321
10511
  const width = props.width;
10322
10512
  if (width === void 0) return {};
10323
- const page = getPageSetup(ctx.theme);
10324
- const measureTwips = Math.max(
10325
- 0,
10326
- page.size.width - page.margin.left - page.margin.right
10327
- );
10328
10513
  let usedTwips;
10329
10514
  if (typeof width === "number" && Number.isFinite(width)) {
10330
10515
  usedTwips = pointsToTwips2(width);
@@ -10480,7 +10665,7 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
10480
10665
  }
10481
10666
  blocks.push(
10482
10667
  paragraphNode(
10483
- { ctx, path: itemPath, id: `${scope.id}:i${index}` },
10668
+ { ...scope, path: itemPath, id: `${scope.id}:i${index}` },
10484
10669
  revision === void 0 ? parseInline(text, {
10485
10670
  base,
10486
10671
  hyperlinks: true,
@@ -11127,7 +11312,7 @@ function compileVisual(component, scope) {
11127
11312
  );
11128
11313
  if (!group) return [];
11129
11314
  const size = visualPlacementPixels(props, group.canvas, {
11130
- widthPx: Math.round(twipsToPixels(getAvailableWidthTwips(ctx.theme))),
11315
+ widthPx: Math.round(twipsToPixels(scope.measureTwips)),
11131
11316
  heightPx: Math.round(twipsToPixels(getAvailableHeightTwips(ctx.theme)))
11132
11317
  });
11133
11318
  const drawing = {
@@ -11242,7 +11427,7 @@ function compileImage2(component, scope) {
11242
11427
  `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.`
11243
11428
  );
11244
11429
  }
11245
- const size = imagePixelSize(props, loaded, ctx);
11430
+ const size = imagePixelSize(props, loaded, scope);
11246
11431
  const resourceId = declareResource(loaded, mediaType, ctx);
11247
11432
  const image = {
11248
11433
  kind: "image",
@@ -11283,6 +11468,24 @@ function compileImage2(component, scope) {
11283
11468
  return blocks;
11284
11469
  }
11285
11470
  var DEFAULT_CHART_HEIGHT_INCHES = 3;
11471
+ var MAX_CHART_TEXT_POINTS = 10;
11472
+ function chartTextFont(theme) {
11473
+ const bodySize = getThemeFonts(theme).body?.size;
11474
+ let color = "000000";
11475
+ try {
11476
+ color = resolveColor("textPrimary", theme);
11477
+ } catch {
11478
+ }
11479
+ return {
11480
+ fontFamily: resolveFontFamily(theme, "body"),
11481
+ fontSize: Math.min(
11482
+ typeof bodySize === "number" && bodySize > 0 ? bodySize : 11,
11483
+ MAX_CHART_TEXT_POINTS
11484
+ ),
11485
+ bold: false,
11486
+ color
11487
+ };
11488
+ }
11286
11489
  function compileChart(component, scope) {
11287
11490
  const { ctx, path: path3 } = scope;
11288
11491
  const props = component.props ?? {};
@@ -11334,8 +11537,7 @@ function compileChart(component, scope) {
11334
11537
  return [];
11335
11538
  }
11336
11539
  });
11337
- const page = getPageSetup(ctx.theme);
11338
- const contentWidthInches = (page.size.width - page.margin.left - page.margin.right) / 1440;
11540
+ const contentWidthInches = scope.measureTwips / 1440;
11339
11541
  const widthInches = typeof props.width === "number" ? props.width : contentWidthInches;
11340
11542
  const heightInches = typeof props.height === "number" ? props.height : DEFAULT_CHART_HEIGHT_INCHES;
11341
11543
  const chart = {
@@ -11351,6 +11553,7 @@ function compileChart(component, scope) {
11351
11553
  ...props.legendPos ? { legendPosition: props.legendPos } : {},
11352
11554
  ...typeof props.catAxisTitle === "string" ? { categoryAxisTitle: props.catAxisTitle } : {},
11353
11555
  ...typeof props.valAxisTitle === "string" ? { valueAxisTitle: props.valAxisTitle } : {},
11556
+ textFont: chartTextFont(ctx.theme),
11354
11557
  ...typeof props.alt === "string" && props.alt ? { altText: props.alt } : {},
11355
11558
  ...props.floating ? { floating: compileFloating(props.floating, ctx, path3) } : {}
11356
11559
  };
@@ -11434,12 +11637,13 @@ function wrapType(value) {
11434
11637
  return "square";
11435
11638
  }
11436
11639
  }
11437
- function imagePixelSize(props, loaded, ctx) {
11640
+ function imagePixelSize(props, loaded, scope) {
11641
+ const { ctx } = scope;
11438
11642
  const widthRef = props.widthRelativeTo === "page" ? "page" : "content";
11439
11643
  const heightRef = props.heightRelativeTo === "page" ? "page" : "content";
11440
11644
  const availableWidthPx = Math.round(
11441
11645
  twipsToPixels(
11442
- widthRef === "page" ? getPageWidthTwips(ctx.theme) : getAvailableWidthTwips(ctx.theme)
11646
+ widthRef === "page" ? getPageWidthTwips(ctx.theme) : scope.measureTwips
11443
11647
  )
11444
11648
  );
11445
11649
  const availableHeightPx = Math.round(
@@ -11626,7 +11830,10 @@ function compileTable(component, scope) {
11626
11830
  source,
11627
11831
  ctx.theme,
11628
11832
  ctx.themeName,
11629
- { onWarning: (code, message) => warnOnce(ctx, "table", message, code) }
11833
+ {
11834
+ onWarning: (code, message) => warnOnce(ctx, "table", message, code),
11835
+ measureTwips: scope.measureTwips
11836
+ }
11630
11837
  );
11631
11838
  if (model.overflow) {
11632
11839
  warnOnce(
@@ -11644,6 +11851,7 @@ function compileTable(component, scope) {
11644
11851
  ...model.header ? [
11645
11852
  compileTableRow(
11646
11853
  model.header,
11854
+ model.columnGrid.values,
11647
11855
  style.headerParagraph,
11648
11856
  style.tableHeader,
11649
11857
  {
@@ -11656,13 +11864,19 @@ function compileTable(component, scope) {
11656
11864
  )
11657
11865
  ] : [],
11658
11866
  ...model.rows.map(
11659
- (row, index) => compileTableRow(row, style.cellParagraph, style.tableCell, {
11660
- ctx,
11661
- path: `${path3}.rows[${index}]`,
11662
- id: `${scope.id}:r${index}`,
11663
- ...row.tableHeader !== void 0 ? { isHeader: row.tableHeader } : {},
11664
- ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {}
11665
- })
11867
+ (row, index) => compileTableRow(
11868
+ row,
11869
+ model.columnGrid.values,
11870
+ style.cellParagraph,
11871
+ style.tableCell,
11872
+ {
11873
+ ctx,
11874
+ path: `${path3}.rows[${index}]`,
11875
+ id: `${scope.id}:r${index}`,
11876
+ ...row.tableHeader !== void 0 ? { isHeader: row.tableHeader } : {},
11877
+ ...row.cantSplit !== void 0 ? { cantSplit: row.cantSplit } : {}
11878
+ }
11879
+ )
11666
11880
  )
11667
11881
  ];
11668
11882
  ctx.features.require("tables", path3);
@@ -11740,7 +11954,7 @@ function tableBlocker(model, path3) {
11740
11954
  }
11741
11955
  return void 0;
11742
11956
  }
11743
- function compileTableRow(row, spacing, baseStyle, scope) {
11957
+ function compileTableRow(row, columnTwips, spacing, baseStyle, scope) {
11744
11958
  const { ctx } = scope;
11745
11959
  const rowRevision = row.revision;
11746
11960
  const rowMark = rowRevision ? {
@@ -11760,6 +11974,7 @@ function compileTableRow(row, spacing, baseStyle, scope) {
11760
11974
  cells: row.cells.map(
11761
11975
  (cell, colIndex) => compileTableCell(
11762
11976
  cell,
11977
+ columnTwips[colIndex] ?? 0,
11763
11978
  spacing,
11764
11979
  baseStyle,
11765
11980
  row.keepNext,
@@ -11781,7 +11996,17 @@ function compileTableRow(row, spacing, baseStyle, scope) {
11781
11996
  ...scope.cantSplit !== void 0 ? { cantSplit: scope.cantSplit } : {}
11782
11997
  };
11783
11998
  }
11784
- function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision, markRevision) {
11999
+ function compileTableCell(cell, columnTwips, spacing, baseStyle, keepNext, partScope, rowRevision, markRevision) {
12000
+ const margins = !cell.missing && cell.padding ? {
12001
+ topTwips: pointsToTwips2(cell.padding.top),
12002
+ bottomTwips: pointsToTwips2(cell.padding.bottom),
12003
+ leftTwips: pointsToTwips2(cell.padding.left),
12004
+ rightTwips: pointsToTwips2(cell.padding.right)
12005
+ } : void 0;
12006
+ const scope = {
12007
+ ...partScope,
12008
+ measureTwips: cellMeasureTwips(columnTwips, margins)
12009
+ };
11785
12010
  const cellLine = compileLineSpacing(cell.font?.lineSpacing);
11786
12011
  const paragraph2 = {
11787
12012
  kind: "paragraph",
@@ -11810,14 +12035,7 @@ function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision
11810
12035
  ...cell.missing ? {} : {
11811
12036
  verticalAlign: verticalAlign(cell.verticalAlignment),
11812
12037
  ...cell.backgroundColor !== void 0 && cell.backgroundColor !== "transparent" ? { shading: { fill: { hex: cell.backgroundColor } } } : {},
11813
- ...cell.padding ? {
11814
- margins: {
11815
- topTwips: pointsToTwips2(cell.padding.top),
11816
- bottomTwips: pointsToTwips2(cell.padding.bottom),
11817
- leftTwips: pointsToTwips2(cell.padding.left),
11818
- rightTwips: pointsToTwips2(cell.padding.right)
11819
- }
11820
- } : {}
12038
+ ...margins ? { margins } : {}
11821
12039
  },
11822
12040
  borders: {
11823
12041
  top: compileTableBorder(cell.borders.top),
@@ -12097,6 +12315,18 @@ init_styles();
12097
12315
  init_widthUtils();
12098
12316
  function applyLayout(sections, theme, themeName) {
12099
12317
  const layoutSections = [];
12318
+ const push = (layoutSection) => {
12319
+ const previous = layoutSections[layoutSections.length - 1];
12320
+ if (previous && layoutSection.properties.type === "continuous" && pageSizeDiffers(
12321
+ previous.properties.page.size,
12322
+ layoutSection.properties.page.size
12323
+ )) {
12324
+ layoutSection.properties.type = "nextPage";
12325
+ layoutSection.breakBefore = true;
12326
+ layoutSection.forcedPageBreak = true;
12327
+ }
12328
+ layoutSections.push(layoutSection);
12329
+ };
12100
12330
  for (let i = 0; i < sections.length; i++) {
12101
12331
  const section2 = sections[i];
12102
12332
  const layoutGroups = analyzeLayoutGroups(section2.components);
@@ -12105,7 +12335,7 @@ function applyLayout(sections, theme, themeName) {
12105
12335
  const isFirstSection = i === 0;
12106
12336
  const breakBefore = isFirstSection || Boolean(section2.pageBreak);
12107
12337
  const sectionType2 = breakBefore ? "nextPage" : "continuous";
12108
- layoutSections.push({
12338
+ push({
12109
12339
  properties: createSectionProperties(
12110
12340
  getColumnSettings("single"),
12111
12341
  theme,
@@ -12127,7 +12357,12 @@ function applyLayout(sections, theme, themeName) {
12127
12357
  for (let j = 0; j < layoutGroups.length; j++) {
12128
12358
  const group = layoutGroups[j];
12129
12359
  const isSingleColumnsGroup = group.components.length === 1 && isColumnsComponent(group.components[0]);
12130
- const columnSettings = isSingleColumnsGroup ? createColumnSettingsFromConfig(group.components[0], theme, themeName) : getColumnSettings(group.layout);
12360
+ const columnSettings = isSingleColumnsGroup ? createColumnSettingsFromConfig(
12361
+ group.components[0],
12362
+ // Percentages are of this section's text measure, not the theme's.
12363
+ theme ? themeForSectionPage(theme, section2.page) : theme,
12364
+ themeName
12365
+ ) : getColumnSettings(group.layout);
12131
12366
  const isFirstSection = i === 0 && j === 0;
12132
12367
  const titlelessSectionBreak = Boolean(section2.pageBreak && j === 0);
12133
12368
  const breakBefore = group.breakBefore || isFirstSection || titlelessSectionBreak;
@@ -12135,7 +12370,7 @@ function applyLayout(sections, theme, themeName) {
12135
12370
  const contentComponents = isSingleColumnsGroup ? processLayoutComponents(
12136
12371
  group.components[0].children || []
12137
12372
  ) : processLayoutComponents(group.components);
12138
- layoutSections.push({
12373
+ push({
12139
12374
  properties: createSectionProperties(
12140
12375
  columnSettings,
12141
12376
  theme,
@@ -12823,9 +13058,10 @@ function docxBlockContext(document, theme, path3, sources) {
12823
13058
  page.size ?? theme.page.size
12824
13059
  );
12825
13060
  const overrides = isBlockRecord(page.margins) ? page.margins : {};
13061
+ const gutter = "gutter" in margins ? margins.gutter : 0;
12826
13062
  const width = Object.keys(page).length ? Math.max(
12827
13063
  0,
12828
- dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440)
13064
+ dimensions.width - Number(overrides.left ?? margins.left ?? 1440) - Number(overrides.right ?? margins.right ?? 1440) - Number(overrides.gutter ?? gutter ?? 0)
12829
13065
  ) : getAvailableWidthTwips(theme);
12830
13066
  const height = Object.keys(page).length ? Math.max(
12831
13067
  0,
@@ -13382,7 +13618,7 @@ function pageBox(theme, themeName, pageOverride) {
13382
13618
  return {
13383
13619
  availableWidthTwips: Math.max(
13384
13620
  0,
13385
- page.size.width - page.margin.left - page.margin.right
13621
+ page.size.width - page.margin.left - page.margin.right - (page.margin.gutter ?? 0)
13386
13622
  ),
13387
13623
  pageBottomTwips: page.size.height
13388
13624
  };