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

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/core/desugarExternals.d.ts.map +1 -1
  3. package/dist/core/generationContext.d.ts +2 -2
  4. package/dist/core/generationContext.d.ts.map +1 -1
  5. package/dist/core/imageResources.d.ts.map +1 -1
  6. package/dist/core/layout.d.ts +7 -0
  7. package/dist/core/layout.d.ts.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +334 -98
  11. package/dist/index.js.map +1 -1
  12. package/dist/ir/compiler.d.ts.map +1 -1
  13. package/dist/ir/types.d.ts +14 -0
  14. package/dist/ir/types.d.ts.map +1 -1
  15. package/dist/plugin/createDocumentGenerator.d.ts.map +1 -1
  16. package/dist/plugin/example/index.js +257 -59
  17. package/dist/plugin/example/index.js.map +1 -1
  18. package/dist/plugin/index.d.ts +1 -1
  19. package/dist/plugin/index.d.ts.map +1 -1
  20. package/dist/plugin/types.d.ts +27 -0
  21. package/dist/plugin/types.d.ts.map +1 -1
  22. package/dist/quality/facts.d.ts +14 -1
  23. package/dist/quality/facts.d.ts.map +1 -1
  24. package/dist/quality/rules.d.ts +2 -2
  25. package/dist/quality/rules.d.ts.map +1 -1
  26. package/dist/renderers/docxjs/emit.d.ts.map +1 -1
  27. package/dist/renderers/office-open/chartParts.d.ts.map +1 -1
  28. package/dist/renderers/office-open/emit.d.ts.map +1 -1
  29. package/dist/styles/index.d.ts +2 -1
  30. package/dist/styles/index.d.ts.map +1 -1
  31. package/dist/styles/themeToStyles.d.ts +23 -0
  32. package/dist/styles/themeToStyles.d.ts.map +1 -1
  33. package/dist/styles/utils/layoutUtils.d.ts +41 -0
  34. package/dist/styles/utils/layoutUtils.d.ts.map +1 -1
  35. package/dist/templates/themes/consulting.docx.theme.json +2 -1
  36. package/dist/templates/themes/index.d.ts +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +4 -4
@@ -1336,7 +1336,8 @@ var init_consulting_docx_theme = __esm({
1336
1336
  },
1337
1337
  stat: {
1338
1338
  face: "heading",
1339
- weight: 700,
1339
+ size: 22,
1340
+ weight: 400,
1340
1341
  color: "accent",
1341
1342
  lineHeight: 1
1342
1343
  },
@@ -1643,7 +1644,7 @@ var init_themes = __esm({
1643
1644
  getTheme = (themeName) => {
1644
1645
  return themes[themeName];
1645
1646
  };
1646
- getThemeWithFallback = (themeName, fallbackTheme = "minimal") => {
1647
+ getThemeWithFallback = (themeName, fallbackTheme = "consulting") => {
1647
1648
  const theme = getTheme(themeName) || getTheme(fallbackTheme);
1648
1649
  if (!theme) {
1649
1650
  throw new Error(
@@ -1723,7 +1724,7 @@ function resolveTheme(theme, themeName) {
1723
1724
  if (themeName) {
1724
1725
  return getTheme(themeName);
1725
1726
  }
1726
- return getTheme("minimal");
1727
+ return getTheme("consulting");
1727
1728
  }
1728
1729
  function resolveFontFamily(theme, fontRef) {
1729
1730
  if (!theme?.fonts?.body?.family) {
@@ -1840,6 +1841,21 @@ function getPageDimensions(size) {
1840
1841
  }
1841
1842
  return { width: size.width, height: size.height };
1842
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
+ }
1843
1859
  var resolveTableParagraphSpacing, PAGE_SIZES, getTableStyle, getDocumentMargins, getPageSetup;
1844
1860
  var init_layoutUtils = __esm({
1845
1861
  "src/styles/utils/layoutUtils.ts"() {
@@ -3203,7 +3219,7 @@ function emitBlock(block2, resources = /* @__PURE__ */ new Map()) {
3203
3219
  }
3204
3220
  }
3205
3221
  function emitToc(block2) {
3206
- return new TableOfContents(block2.alias ?? "Table of Contents", {
3222
+ return new ThemedTableOfContents(block2.alias ?? "Table of Contents", {
3207
3223
  ...block2.hyperlink !== void 0 ? { hyperlink: block2.hyperlink } : {},
3208
3224
  ...block2.headingRange ? {
3209
3225
  headingStyleRange: `${block2.headingRange.from}-${block2.headingRange.to}`
@@ -3335,7 +3351,7 @@ function emitBorder(border3) {
3335
3351
  ...border3.spacePoints !== void 0 ? { space: border3.spacePoints } : {}
3336
3352
  };
3337
3353
  }
3338
- var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
3354
+ var ALIGNMENT2, PAGE_FIELDS, isSequenceField, WRAP_TYPE, ThemedTableOfContents, VERTICAL_ALIGN, TABLE_ANCHOR, HORIZONTAL_POSITION, VERTICAL_POSITION, BORDER_STYLE;
3339
3355
  var init_emit = __esm({
3340
3356
  "src/renderers/docxjs/emit.ts"() {
3341
3357
  "use strict";
@@ -3355,6 +3371,11 @@ var init_emit = __esm({
3355
3371
  tight: TextWrappingType.TIGHT,
3356
3372
  topAndBottom: TextWrappingType.TOP_AND_BOTTOM
3357
3373
  };
3374
+ ThemedTableOfContents = class extends TableOfContents {
3375
+ };
3376
+ Object.defineProperty(ThemedTableOfContents.prototype, "getTabStopsForLevel", {
3377
+ value: () => []
3378
+ });
3358
3379
  VERTICAL_ALIGN = {
3359
3380
  top: VerticalAlign.TOP,
3360
3381
  center: VerticalAlign.CENTER,
@@ -4115,13 +4136,30 @@ import {
4115
4136
  spliceChartXml
4116
4137
  } from "@json-to-office/shared/rendering";
4117
4138
  function spliceInput(chart) {
4139
+ const { bold, ...rest } = chart.textFont;
4140
+ const textFont = rest;
4141
+ const axisTitleFont = { ...rest, bold };
4142
+ const chartTitleFont = {
4143
+ ...rest,
4144
+ fontSize: Math.round(rest.fontSize * CHART_TITLE_SCALE),
4145
+ bold: true
4146
+ };
4118
4147
  return {
4119
4148
  chartType: chart.chartType,
4120
4149
  series: chart.series,
4121
4150
  colors: chart.colors,
4151
+ textFont,
4152
+ titleFont: chartTitleFont,
4122
4153
  ...chart.legendPosition ? { legendPosition: chart.legendPosition } : {},
4123
- ...chart.categoryAxisTitle ? { categoryAxis: { title: chart.categoryAxisTitle } } : {},
4124
- ...chart.valueAxisTitle ? { valueAxis: { title: chart.valueAxisTitle } } : {}
4154
+ ...chart.categoryAxisTitle ? {
4155
+ categoryAxis: {
4156
+ title: chart.categoryAxisTitle,
4157
+ titleFont: axisTitleFont
4158
+ }
4159
+ } : {},
4160
+ ...chart.valueAxisTitle ? {
4161
+ valueAxis: { title: chart.valueAxisTitle, titleFont: axisTitleFont }
4162
+ } : {}
4125
4163
  };
4126
4164
  }
4127
4165
  function declareWorkbookContentType(zip) {
@@ -4165,10 +4203,12 @@ function spliceChartParts(zip, charts) {
4165
4203
  }
4166
4204
  declareWorkbookContentType(zip);
4167
4205
  }
4206
+ var CHART_TITLE_SCALE;
4168
4207
  var init_chartParts = __esm({
4169
4208
  "src/renderers/office-open/chartParts.ts"() {
4170
4209
  "use strict";
4171
4210
  init_chartWorkbook();
4211
+ CHART_TITLE_SCALE = 1.4;
4172
4212
  }
4173
4213
  });
4174
4214
 
@@ -4714,9 +4754,6 @@ function tocEntry(entry) {
4714
4754
  return {
4715
4755
  paragraph: {
4716
4756
  style: `TOC${entry.level}`,
4717
- tabStops: [
4718
- { type: "right", position: TOC_PAGE_TAB_TWIPS, leader: "dot" }
4719
- ],
4720
4757
  children: [{ text: entry.text }, { children: [{ tab: true }] }]
4721
4758
  }
4722
4759
  };
@@ -4746,13 +4783,18 @@ function table(value, ctx) {
4746
4783
  return {
4747
4784
  rows: value.rows.map((row) => tableRow(row, ctx)),
4748
4785
  width: {
4749
- size: value.width.kind === "auto" ? 0 : value.width.value,
4786
+ // `w:w` is an integer in OOXML. A grid that splits a remainder between
4787
+ // unstated columns comes out fractional, and the XML would carry it
4788
+ // verbatim; floored here, as the docxjs backend floors it.
4789
+ size: value.width.kind === "auto" ? 0 : value.width.kind === "twips" ? Math.floor(value.width.value) : value.width.value,
4750
4790
  type: value.width.kind === "twips" ? "dxa" : value.width.kind === "percent" ? "pct" : "auto"
4751
4791
  },
4752
4792
  layout: value.layout,
4753
4793
  // An empty grid is a table with nothing to say about its columns, which is
4754
4794
  // not the same as one whose columns are all zero wide.
4755
- ...value.columnGrid.values.length > 0 ? { columnWidths: value.columnGrid.values } : {},
4795
+ ...value.columnGrid.values.length > 0 ? {
4796
+ columnWidths: value.columnGrid.unit === "twips" ? value.columnGrid.values.map(Math.floor) : value.columnGrid.values
4797
+ } : {},
4756
4798
  ...value.alignment ? { alignment: alignment(value.alignment) } : {},
4757
4799
  ...value.borders ? { borders: borders2(value.borders) } : {},
4758
4800
  ...value.cellMargins ? { margins: cellMargins(value.cellMargins) } : {},
@@ -4944,7 +4986,7 @@ function numberingConfig2(numbering) {
4944
4986
  }))
4945
4987
  };
4946
4988
  }
4947
- var WRAP_TYPE2, BODY_ANCHOR, TOC_PAGE_TAB_TWIPS;
4989
+ var WRAP_TYPE2, BODY_ANCHOR;
4948
4990
  var init_emit2 = __esm({
4949
4991
  "src/renderers/office-open/emit.ts"() {
4950
4992
  "use strict";
@@ -4960,7 +5002,6 @@ var init_emit2 = __esm({
4960
5002
  middle: "ctr",
4961
5003
  bottom: "b"
4962
5004
  };
4963
- TOC_PAGE_TAB_TWIPS = 9025;
4964
5005
  }
4965
5006
  });
4966
5007
 
@@ -5241,7 +5282,9 @@ function addPngTextChunk(data, marker) {
5241
5282
  }
5242
5283
  offset = chunkEnd;
5243
5284
  }
5244
- return Buffer.concat([data, marker]);
5285
+ throw new Error(
5286
+ "Cannot mark a PNG that has no intact IEND chunk; its bytes were not checked to decode before rendering"
5287
+ );
5245
5288
  }
5246
5289
  function addJpegComment(data, marker) {
5247
5290
  if (data.length < 2 || data[0] !== 255 || data[1] !== 216) {
@@ -5544,7 +5587,7 @@ function resolveThemeContext(documentIn, options = {}) {
5544
5587
  }
5545
5588
  const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
5546
5589
  const authoredThemeName = document.props.theme || void 0;
5547
- const baseThemeName = authoredThemeName ?? "minimal";
5590
+ const baseThemeName = authoredThemeName ?? "consulting";
5548
5591
  let theme = resolveNamedTheme ? resolveNamedTheme(
5549
5592
  baseThemeName,
5550
5593
  warnings,
@@ -6225,6 +6268,7 @@ function containsComponent(root, name) {
6225
6268
  }
6226
6269
 
6227
6270
  // src/core/desugarExternals.ts
6271
+ init_layoutUtils();
6228
6272
  import {
6229
6273
  clampVisualDpi as clampVisualDpi2,
6230
6274
  DEFAULT_VISUAL_DPI as DEFAULT_VISUAL_DPI2,
@@ -6428,8 +6472,18 @@ async function desugarExternals(document, options) {
6428
6472
  }
6429
6473
  ).catch(() => /* @__PURE__ */ new Map());
6430
6474
  const chartCache = /* @__PURE__ */ new Map();
6431
- return transformComponents(document, async (node) => {
6475
+ const replace = (theme, inSection) => async function replaceNode(node) {
6432
6476
  if (node.enabled === false) return void 0;
6477
+ if (node.name === "section" && !inSection) {
6478
+ const page = node.props.page;
6479
+ return transformComponents(
6480
+ node,
6481
+ replace(themeForSectionPage(theme, page), true)
6482
+ );
6483
+ }
6484
+ return replaceExternal(node, theme);
6485
+ };
6486
+ const replaceExternal = async (node, theme) => {
6433
6487
  if (node.name === "visual") {
6434
6488
  if (isNativeVisualProps2(node.props)) return void 0;
6435
6489
  return withNodeIdentity(node, {
@@ -6447,7 +6501,7 @@ async function desugarExternals(document, options) {
6447
6501
  name: "image",
6448
6502
  props: await renderChartToImageProps(
6449
6503
  node.props,
6450
- options.theme,
6504
+ theme,
6451
6505
  options.services?.highcharts,
6452
6506
  options.chartFonts,
6453
6507
  options.warnings,
@@ -6456,7 +6510,8 @@ async function desugarExternals(document, options) {
6456
6510
  });
6457
6511
  }
6458
6512
  return void 0;
6459
- });
6513
+ };
6514
+ return transformComponents(document, replace(options.theme, false));
6460
6515
  }
6461
6516
  async function visualImageProps(props, prerastered, options) {
6462
6517
  const serviceConfig = options.services?.pptx;
@@ -6491,6 +6546,7 @@ init_imageUtils();
6491
6546
  import {
6492
6547
  isNativeVisualProps as isNativeVisualProps3
6493
6548
  } from "@json-to-office/shared-docx";
6549
+ import { imageIntegrityDefect } from "@json-to-office/shared/images/node";
6494
6550
  async function loadImageResources(components) {
6495
6551
  const sources = /* @__PURE__ */ new Set();
6496
6552
  for (const component of components) collectSources(component, sources);
@@ -6500,6 +6556,8 @@ async function loadImageResources(components) {
6500
6556
  [...sources].map(async (source) => {
6501
6557
  try {
6502
6558
  const result = await getImageBuffer(source);
6559
+ const defect = imageIntegrityDefect(result.buffer);
6560
+ if (defect) throw new Error(defect);
6503
6561
  const intrinsic = readIntrinsic(result.buffer, source);
6504
6562
  loaded.set(source, {
6505
6563
  bytes: result.buffer,
@@ -7100,7 +7158,12 @@ function resolveColors(entries, at, deps) {
7100
7158
  init_themes();
7101
7159
  init_colorUtils();
7102
7160
  init_styleHelpers();
7103
- import { capsFormatting, synthesizeFamilyName } from "@json-to-office/shared";
7161
+ import {
7162
+ capsFormatting,
7163
+ designCanvas as designCanvas2,
7164
+ synthesizeFamilyName,
7165
+ typeScaleSizes
7166
+ } from "@json-to-office/shared";
7104
7167
  var RIGHT_MARGIN_TAB_TWIPS = 9026 * 2;
7105
7168
  var STATISTIC_NUMBER_STYLE_ID = "StatisticNumber";
7106
7169
  var STATISTIC_DESCRIPTION_STYLE_ID = "StatisticDescription";
@@ -7109,6 +7172,27 @@ var STATISTIC_SIZE_POINTS = {
7109
7172
  medium: 28,
7110
7173
  large: 40
7111
7174
  };
7175
+ function statisticSizes(theme, size) {
7176
+ const sizeOf = (style) => {
7177
+ const value = theme.styles?.[style]?.size;
7178
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
7179
+ };
7180
+ const role = sizeOf("stat");
7181
+ const medium = sizeOf(STATISTIC_NUMBER_STYLE_ID) ?? role ?? STATISTIC_SIZE_POINTS.medium;
7182
+ const scale = role === void 0 ? void 0 : theme.typography?.scale?.[designCanvas2("docx", theme.page?.size)];
7183
+ const steps = scale ? typeScaleSizes(scale) : [];
7184
+ const below = [...steps].reverse().find((step) => step < medium - 0.01);
7185
+ const above = steps.find((step) => step > medium + 0.01);
7186
+ const number = size === "small" ? role !== void 0 && below !== void 0 ? below : STATISTIC_SIZE_POINTS.small : size === "large" ? role !== void 0 && above !== void 0 ? above : STATISTIC_SIZE_POINTS.large : medium;
7187
+ const label = sizeOf("label");
7188
+ return {
7189
+ number,
7190
+ medium,
7191
+ // Half-points in the document, floored at 6pt.
7192
+ suffix: label ?? Math.max(12, Math.round(number)) / 2,
7193
+ description: sizeOf(STATISTIC_DESCRIPTION_STYLE_ID) ?? label ?? 10
7194
+ };
7195
+ }
7112
7196
  function getStyleSafe(theme, styleName) {
7113
7197
  return theme.styles && Object.hasOwn(theme.styles, styleName) ? theme.styles[styleName] : void 0;
7114
7198
  }
@@ -7284,8 +7368,8 @@ function chromeRun(fontProps, theme, sizeHalfPoints) {
7284
7368
  ...fontProps.italic !== void 0 && { italic: fontProps.italic }
7285
7369
  };
7286
7370
  }
7287
- function createDocumentStyles(themeNameOrObject = "minimal", language) {
7288
- const theme = typeof themeNameOrObject === "string" ? getTheme(themeNameOrObject) || getTheme("minimal") : themeNameOrObject;
7371
+ function createDocumentStyles(themeNameOrObject = "consulting", language) {
7372
+ const theme = typeof themeNameOrObject === "string" ? getTheme(themeNameOrObject) || getTheme("consulting") : themeNameOrObject;
7289
7373
  const paragraphStyles = [
7290
7374
  // Normal body text style
7291
7375
  {
@@ -7756,14 +7840,19 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
7756
7840
  };
7757
7841
  }
7758
7842
  function createStatisticStyles(theme) {
7843
+ const sizes = statisticSizes(theme, "medium");
7844
+ const stat = theme.styles?.stat;
7845
+ const faces = ["heading", "body", "mono", "light"];
7846
+ const face = faces.find((candidate) => candidate === stat?.font) ?? "heading";
7847
+ const weight = typeof stat?.fontWeight === "number" ? stat.fontWeight : void 0;
7759
7848
  const definitions = [
7760
7849
  {
7761
7850
  id: STATISTIC_NUMBER_STYLE_ID,
7762
7851
  name: "Statistic Number",
7763
- font: "heading",
7764
- sizePoints: STATISTIC_SIZE_POINTS.medium,
7765
- color: theme.colors.primary,
7766
- bold: true,
7852
+ font: face,
7853
+ sizePoints: sizes.medium,
7854
+ color: typeof stat?.color === "string" ? stat.color : theme.colors.primary,
7855
+ bold: weight === void 0 ? true : weight >= 600,
7767
7856
  // Space above separates the figure from whatever preceded it; none below,
7768
7857
  // because the description is the other half of the same block.
7769
7858
  spacing: { before: 12, after: 0 },
@@ -7776,7 +7865,7 @@ function createStatisticStyles(theme) {
7776
7865
  id: STATISTIC_DESCRIPTION_STYLE_ID,
7777
7866
  name: "Statistic Description",
7778
7867
  font: "body",
7779
- sizePoints: 10,
7868
+ sizePoints: sizes.description,
7780
7869
  color: theme.colors.textMuted,
7781
7870
  bold: false,
7782
7871
  spacing: { before: 0, after: 12 },
@@ -9395,8 +9484,25 @@ function resolveSectionChrome(sections) {
9395
9484
  });
9396
9485
  }
9397
9486
  function compileSection(section2, index, ordinal, chrome, ctx) {
9487
+ const documentTheme = ctx.theme;
9488
+ ctx.theme = themeForSectionPage(documentTheme, section2.pageOverride);
9489
+ try {
9490
+ return compileSectionBody(section2, index, ordinal, chrome, ctx);
9491
+ } finally {
9492
+ ctx.theme = documentTheme;
9493
+ }
9494
+ }
9495
+ function compileSectionBody(section2, index, ordinal, chrome, ctx) {
9398
9496
  const path3 = `sections[${index}]`;
9399
9497
  const { page, column } = section2.properties;
9498
+ if (section2.forcedPageBreak) {
9499
+ warnOnce(
9500
+ ctx,
9501
+ "section",
9502
+ `${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.`,
9503
+ "W_SECTION_PAGE_BREAK_FORCED"
9504
+ );
9505
+ }
9400
9506
  if (column && column.count > 1) {
9401
9507
  ctx.features.require("columns", `${path3}.properties.columns`);
9402
9508
  }
@@ -10348,9 +10454,10 @@ function compileStatistic(component, scope) {
10348
10454
  );
10349
10455
  }
10350
10456
  const text = (value) => normalizeUnicodeText(String(value ?? ""));
10351
- const numberPoints = STATISTIC_SIZE_POINTS[String(props.size)] ?? STATISTIC_SIZE_POINTS.medium;
10352
- const resized = numberPoints !== STATISTIC_SIZE_POINTS.medium;
10353
- const suffixHalfPoints = Math.max(12, Math.round(numberPoints));
10457
+ const sizes = statisticSizes(ctx.theme, props.size);
10458
+ const numberPoints = sizes.number;
10459
+ const resized = numberPoints !== sizes.medium;
10460
+ const suffixHalfPoints = Math.round(sizes.suffix * 2);
10354
10461
  const numberRuns = [];
10355
10462
  const numberText = text(props.number);
10356
10463
  if (numberText) {
@@ -11250,6 +11357,24 @@ function compileImage2(component, scope) {
11250
11357
  return blocks;
11251
11358
  }
11252
11359
  var DEFAULT_CHART_HEIGHT_INCHES = 3;
11360
+ var MAX_CHART_TEXT_POINTS = 10;
11361
+ function chartTextFont(theme) {
11362
+ const bodySize = getThemeFonts(theme).body?.size;
11363
+ let color = "000000";
11364
+ try {
11365
+ color = resolveColor("textPrimary", theme);
11366
+ } catch {
11367
+ }
11368
+ return {
11369
+ fontFamily: resolveFontFamily(theme, "body"),
11370
+ fontSize: Math.min(
11371
+ typeof bodySize === "number" && bodySize > 0 ? bodySize : 11,
11372
+ MAX_CHART_TEXT_POINTS
11373
+ ),
11374
+ bold: false,
11375
+ color
11376
+ };
11377
+ }
11253
11378
  function compileChart(component, scope) {
11254
11379
  const { ctx, path: path3 } = scope;
11255
11380
  const props = component.props ?? {};
@@ -11318,6 +11443,7 @@ function compileChart(component, scope) {
11318
11443
  ...props.legendPos ? { legendPosition: props.legendPos } : {},
11319
11444
  ...typeof props.catAxisTitle === "string" ? { categoryAxisTitle: props.catAxisTitle } : {},
11320
11445
  ...typeof props.valAxisTitle === "string" ? { valueAxisTitle: props.valAxisTitle } : {},
11446
+ textFont: chartTextFont(ctx.theme),
11321
11447
  ...typeof props.alt === "string" && props.alt ? { altText: props.alt } : {},
11322
11448
  ...props.floating ? { floating: compileFloating(props.floating, ctx, path3) } : {}
11323
11449
  };
@@ -12064,6 +12190,18 @@ init_styles();
12064
12190
  init_widthUtils();
12065
12191
  function applyLayout(sections, theme, themeName) {
12066
12192
  const layoutSections = [];
12193
+ const push = (layoutSection) => {
12194
+ const previous = layoutSections[layoutSections.length - 1];
12195
+ if (previous && layoutSection.properties.type === "continuous" && pageSizeDiffers(
12196
+ previous.properties.page.size,
12197
+ layoutSection.properties.page.size
12198
+ )) {
12199
+ layoutSection.properties.type = "nextPage";
12200
+ layoutSection.breakBefore = true;
12201
+ layoutSection.forcedPageBreak = true;
12202
+ }
12203
+ layoutSections.push(layoutSection);
12204
+ };
12067
12205
  for (let i = 0; i < sections.length; i++) {
12068
12206
  const section2 = sections[i];
12069
12207
  const layoutGroups = analyzeLayoutGroups(section2.components);
@@ -12072,7 +12210,7 @@ function applyLayout(sections, theme, themeName) {
12072
12210
  const isFirstSection = i === 0;
12073
12211
  const breakBefore = isFirstSection || Boolean(section2.pageBreak);
12074
12212
  const sectionType2 = breakBefore ? "nextPage" : "continuous";
12075
- layoutSections.push({
12213
+ push({
12076
12214
  properties: createSectionProperties(
12077
12215
  getColumnSettings("single"),
12078
12216
  theme,
@@ -12094,7 +12232,12 @@ function applyLayout(sections, theme, themeName) {
12094
12232
  for (let j = 0; j < layoutGroups.length; j++) {
12095
12233
  const group = layoutGroups[j];
12096
12234
  const isSingleColumnsGroup = group.components.length === 1 && isColumnsComponent(group.components[0]);
12097
- const columnSettings = isSingleColumnsGroup ? createColumnSettingsFromConfig(group.components[0], theme, themeName) : getColumnSettings(group.layout);
12235
+ const columnSettings = isSingleColumnsGroup ? createColumnSettingsFromConfig(
12236
+ group.components[0],
12237
+ // Percentages are of this section's text measure, not the theme's.
12238
+ theme ? themeForSectionPage(theme, section2.page) : theme,
12239
+ themeName
12240
+ ) : getColumnSettings(group.layout);
12098
12241
  const isFirstSection = i === 0 && j === 0;
12099
12242
  const titlelessSectionBreak = Boolean(section2.pageBreak && j === 0);
12100
12243
  const breakBefore = group.breakBefore || isFirstSection || titlelessSectionBreak;
@@ -12102,7 +12245,7 @@ function applyLayout(sections, theme, themeName) {
12102
12245
  const contentComponents = isSingleColumnsGroup ? processLayoutComponents(
12103
12246
  group.components[0].children || []
12104
12247
  ) : processLayoutComponents(group.components);
12105
- layoutSections.push({
12248
+ push({
12106
12249
  properties: createSectionProperties(
12107
12250
  columnSettings,
12108
12251
  theme,
@@ -12649,10 +12792,10 @@ import {
12649
12792
  } from "@json-to-office/quality";
12650
12793
  import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
12651
12794
  import {
12652
- designCanvas as designCanvas2,
12795
+ designCanvas as designCanvas3,
12653
12796
  designColors as designColors2,
12654
12797
  resolveDesignColor as resolveDesignColor2,
12655
- typeScaleSizes
12798
+ typeScaleSizes as typeScaleSizes2
12656
12799
  } from "@json-to-office/shared";
12657
12800
 
12658
12801
  // src/json/normalizer.ts
@@ -12988,7 +13131,8 @@ async function expandBlocksWithPlugins(document, theme, plugins, render, preserv
12988
13131
  document: second.standard,
12989
13132
  preserved: first.preserved,
12990
13133
  sourceMap: evaluator.sourceMap,
12991
- blocks: evaluator.blocks
13134
+ blocks: evaluator.blocks,
13135
+ pluginOutputs: evaluator.pluginOutputs
12992
13136
  };
12993
13137
  }
12994
13138
 
@@ -13775,10 +13919,7 @@ function textSizeFact(node, props, path3, typography, role) {
13775
13919
  };
13776
13920
  }
13777
13921
  function statisticSizeFacts(props, path3, typography) {
13778
- const named = STATISTIC_SIZE_POINTS[String(props.size)];
13779
- const styled = (id) => finiteNumber(asRecord2(typography.styles[id])?.size);
13780
- const numberPoints = named ?? STATISTIC_SIZE_POINTS.medium;
13781
- const painted = props.size === "small" || props.size === "large" ? numberPoints : styled("StatisticNumber") ?? numberPoints;
13922
+ const sizes = statisticSizes(typography.theme, props.size);
13782
13923
  const present = (value) => value !== void 0 && value !== null && String(value).trim() !== "";
13783
13924
  const facts = [];
13784
13925
  const add = (suffix, role, fontSizePt) => facts.push({
@@ -13789,19 +13930,11 @@ function statisticSizeFacts(props, path3, typography) {
13789
13930
  fontSizePt,
13790
13931
  authored: false
13791
13932
  });
13792
- if (present(props.number)) add("number", "statistic", painted);
13933
+ if (present(props.number)) add("number", "statistic", sizes.number);
13793
13934
  if (present(props.unit) || present(props.trend) || present(props.trendValue))
13794
- add(
13795
- "suffix",
13796
- "statistic-suffix",
13797
- Math.max(12, Math.round(numberPoints)) / 2
13798
- );
13935
+ add("suffix", "statistic-suffix", sizes.suffix);
13799
13936
  if (present(props.description))
13800
- add(
13801
- "description",
13802
- "statistic-description",
13803
- styled("StatisticDescription") ?? 10
13804
- );
13937
+ add("description", "statistic-description", sizes.description);
13805
13938
  return facts;
13806
13939
  }
13807
13940
  function tocSizeFacts(props, path3, typography) {
@@ -13903,8 +14036,8 @@ function prepareDocxQualityDocument(document, options = {}) {
13903
14036
  fonts: options.fonts,
13904
14037
  warnings: options.warnings
13905
14038
  });
13906
- const expanded = expandBlocks(themed.document, themed.theme);
13907
- const context = expanded.blocks.length > 0 ? { ...themed, document: expanded.document } : themed;
14039
+ const expanded = options.expanded ?? expandBlocks(themed.document, themed.theme);
14040
+ const context = options.expanded !== void 0 || expanded.blocks.length > 0 ? { ...themed, document: expanded.document } : themed;
13908
14041
  const resolved = resolveDocumentTree(context.document, context.theme);
13909
14042
  const basePage = pageBox(resolved.theme, context.themeName);
13910
14043
  const typography = {
@@ -13916,7 +14049,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13916
14049
  let previousHeadingLevel;
13917
14050
  const authoredPath = (path3) => toAuthoredPointer(
13918
14051
  themed.sourceMap ?? {},
13919
- toAuthoredPointer(expanded.sourceMap, path3)
14052
+ toAuthoredPointer(expanded.sourceMap, path3, expanded.pluginOutputs)
13920
14053
  );
13921
14054
  const addFact = (raw) => {
13922
14055
  const fact = {
@@ -14055,7 +14188,7 @@ function prepareDocxQualityDocument(document, options = {}) {
14055
14188
  );
14056
14189
  if (size) roleSizesPt[key] = size.fontSizePt;
14057
14190
  }
14058
- const scale = resolved.theme.typography?.scale?.[designCanvas2("docx", resolved.theme.page?.size)];
14191
+ const scale = resolved.theme.typography?.scale?.[designCanvas3("docx", resolved.theme.page?.size)];
14059
14192
  const typeScalePt = [
14060
14193
  .../* @__PURE__ */ new Set([
14061
14194
  ...Object.values(roleSizesPt),
@@ -14063,7 +14196,7 @@ function prepareDocxQualityDocument(document, options = {}) {
14063
14196
  const size = resolveFontSize(resolved.theme, role);
14064
14197
  return size === void 0 ? [] : [size];
14065
14198
  }),
14066
- ...scale ? typeScaleSizes(scale) : []
14199
+ ...scale ? typeScaleSizes2(scale) : []
14067
14200
  ])
14068
14201
  ].sort((a, b) => a - b);
14069
14202
  addFact({
@@ -14219,6 +14352,7 @@ function prepareDocxQualityDocument(document, options = {}) {
14219
14352
  for (const fact of svgTextFacts(props, path3)) addFact(fact);
14220
14353
  const drawn = drawnRatio(props);
14221
14354
  const natural = readableRatio(props);
14355
+ const authored = authoredPropsAt(path3);
14222
14356
  addFact({
14223
14357
  id: `docx:image:${path3}`,
14224
14358
  kind: "docx/image",
@@ -14226,7 +14360,13 @@ function prepareDocxQualityDocument(document, options = {}) {
14226
14360
  ...typeof props.alt === "string" && props.alt.trim() !== "" && { alt: props.alt },
14227
14361
  captioned: isCaptioned(facts, authoredPath(path3)),
14228
14362
  ...drawn !== void 0 && { drawnRatio: drawn },
14229
- ...natural !== void 0 && { naturalRatio: natural }
14363
+ ...natural !== void 0 && { naturalRatio: natural },
14364
+ ...authoredPath(path3) === path3 && finiteNumber(authored?.width) !== void 0 && finiteNumber(authored?.height) !== void 0 && {
14365
+ authoredSizePx: {
14366
+ width: authored.width,
14367
+ height: authored.height
14368
+ }
14369
+ }
14230
14370
  });
14231
14371
  }
14232
14372
  if (node.name === "heading") {
@@ -14715,7 +14855,7 @@ function createBuilderImpl(state) {
14715
14855
  }
14716
14856
  return new Set(list);
14717
14857
  }
14718
- async function expandDocument(document, options) {
14858
+ async function expandDocument(document, options, provenance = false) {
14719
14859
  const preserveSet = resolvePreserveSet(options);
14720
14860
  const rootIn = document;
14721
14861
  const internalDocument = rootIn.props === void 0 ? { ...rootIn, props: {} } : rootIn;
@@ -14775,7 +14915,7 @@ function createBuilderImpl(state) {
14775
14915
  warnings,
14776
14916
  resolveNamedTheme: resolveDocumentTheme
14777
14917
  });
14778
- if (modedRoot.props.blocks !== void 0) {
14918
+ if (modedRoot.props.blocks !== void 0 || provenance) {
14779
14919
  const expanded = await expandBlocksWithPlugins(
14780
14920
  modedRoot,
14781
14921
  modedTheme,
@@ -14823,6 +14963,7 @@ function createBuilderImpl(state) {
14823
14963
  modedDoc: modedDoc2,
14824
14964
  sourceMap: expanded.sourceMap,
14825
14965
  blockPaths: expanded.blocks,
14966
+ pluginOutputs: expanded.pluginOutputs,
14826
14967
  warnings,
14827
14968
  preserveSet,
14828
14969
  processed: {
@@ -14871,8 +15012,64 @@ function createBuilderImpl(state) {
14871
15012
  throw error;
14872
15013
  }
14873
15014
  }
15015
+ async function prepareQuality(document, options) {
15016
+ const {
15017
+ modedRoot,
15018
+ modedTheme,
15019
+ themeName,
15020
+ modedDoc,
15021
+ sourceMap,
15022
+ blockPaths,
15023
+ pluginOutputs,
15024
+ warnings
15025
+ } = await expandDocument(document, options, true);
15026
+ const prepared = prepareDocxQualityDocument(
15027
+ document,
15028
+ {
15029
+ context: { document: modedRoot, theme: modedTheme, themeName },
15030
+ expanded: {
15031
+ document: modedDoc,
15032
+ sourceMap: sourceMap ?? {},
15033
+ blocks: blockPaths ?? [],
15034
+ ...pluginOutputs && { pluginOutputs }
15035
+ },
15036
+ customThemes: state.customThemes,
15037
+ fonts: state.fonts,
15038
+ renderer: options?.renderer ?? state.renderer ?? modedRoot.renderer,
15039
+ warnings
15040
+ }
15041
+ );
15042
+ return { prepared, warnings: warnings.length > 0 ? warnings : null };
15043
+ }
15044
+ async function generateBufferFromPrepared(prepared, options) {
15045
+ const { context } = prepared.model;
15046
+ const warnings = [];
15047
+ const baseDir = options?.baseDir ?? state.baseDir;
15048
+ const deterministic = options?.deterministic ?? state.deterministic;
15049
+ const generatedAt = options?.generatedAt ?? state.generatedAt;
15050
+ const renderer = options?.renderer ?? state.renderer ?? prepared.renderer;
15051
+ const { buffer } = await generateBufferViaIr(context.document, {
15052
+ context,
15053
+ prepared,
15054
+ ...state.services ? { services: state.services } : {},
15055
+ ...state.fonts ? { fonts: state.fonts } : {},
15056
+ ...baseDir !== void 0 ? { baseDir } : {},
15057
+ ...deterministic !== void 0 ? { deterministic } : {},
15058
+ ...generatedAt !== void 0 ? { generatedAt } : {},
15059
+ ...renderer !== void 0 ? { renderer } : {},
15060
+ warnings
15061
+ });
15062
+ return {
15063
+ buffer,
15064
+ warnings: warnings.length > 0 ? warnings : null,
15065
+ standardDefinition: context.document,
15066
+ preservedDefinition: void 0
15067
+ };
15068
+ }
14874
15069
  async function generateBuffer(document, options) {
14875
15070
  try {
15071
+ const reused = options?.prepared && resolvePreserveSet(options) === void 0 ? options.prepared : void 0;
15072
+ if (reused) return await generateBufferFromPrepared(reused, options);
14876
15073
  const {
14877
15074
  modedRoot,
14878
15075
  modedTheme,
@@ -15002,6 +15199,7 @@ function createBuilderImpl(state) {
15002
15199
  generateBuffer,
15003
15200
  generateFile,
15004
15201
  expandStandardDefinition,
15202
+ prepareQuality,
15005
15203
  getComponentNames,
15006
15204
  validate,
15007
15205
  generateSchema,