@json-to-office/core-docx 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5037,88 +5037,6 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
5037
5037
  return map;
5038
5038
  }
5039
5039
 
5040
- // src/json/normalizer.ts
5041
- function normalizeComponent(component) {
5042
- if (component.name === "columns") {
5043
- return normalizeColumnsComponent(component);
5044
- }
5045
- if ("children" in component && component.children) {
5046
- return {
5047
- ...component,
5048
- // Preserve ALL properties from validated component
5049
- children: component.children.map(normalizeComponent)
5050
- };
5051
- }
5052
- if (component.name === "section") {
5053
- return normalizeSectionComponent(component);
5054
- }
5055
- return component;
5056
- }
5057
- function normalizeColumnsComponent(component) {
5058
- const cfg = component.props || {};
5059
- const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
5060
- const isLast = i === cols.length - 1;
5061
- if (!isLast && c.gap === void 0 && topGap !== void 0) {
5062
- return { ...c, gap: topGap };
5063
- }
5064
- return c;
5065
- });
5066
- const normalizeAutoWidth = (cols) => cols.map((c) => ({
5067
- ...c,
5068
- width: c.width === "auto" ? void 0 : c.width
5069
- }));
5070
- let columnsArray;
5071
- if (typeof cfg.columns === "number" && cfg.columns > 0) {
5072
- const n = cfg.columns;
5073
- const base = Array.from(
5074
- { length: n },
5075
- () => ({})
5076
- );
5077
- const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
5078
- columnsArray = applyTopLevelGap(base, topLevelGap);
5079
- } else if (Array.isArray(cfg.columns)) {
5080
- columnsArray = cfg.columns;
5081
- const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
5082
- columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
5083
- columnsArray = normalizeAutoWidth(columnsArray);
5084
- } else {
5085
- columnsArray = [{}];
5086
- }
5087
- const normalizedProps = {
5088
- columns: columnsArray
5089
- };
5090
- return {
5091
- ...component,
5092
- // Preserve any additional properties
5093
- name: "columns",
5094
- props: normalizedProps,
5095
- children: component.children ? component.children.map(normalizeComponent) : void 0
5096
- };
5097
- }
5098
- function normalizeSectionComponent(component) {
5099
- const props = component.props || {};
5100
- return {
5101
- ...component,
5102
- // Preserve ALL properties
5103
- props: {
5104
- ...props,
5105
- // Preserve ALL props properties
5106
- header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
5107
- footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
5108
- },
5109
- children: component.children ? component.children.map(normalizeComponent) : void 0
5110
- };
5111
- }
5112
- function normalizeDocument(document) {
5113
- if (document.name !== "docx") {
5114
- throw new Error(
5115
- 'Top-level document must be a docx component with name="docx"'
5116
- );
5117
- }
5118
- const normalized = normalizeComponent(document);
5119
- return [normalized];
5120
- }
5121
-
5122
5040
  // src/core/desugarExternals.ts
5123
5041
  import {
5124
5042
  clampVisualDpi as clampVisualDpi2,
@@ -10568,101 +10486,6 @@ function isDocxRendererId(value) {
10568
10486
 
10569
10487
  // src/core/generateFromIr.ts
10570
10488
  init_packageDocument();
10571
-
10572
- // src/core/generationContext.ts
10573
- import { applyExportMode } from "@json-to-office/shared";
10574
-
10575
- // src/styles/theme-resolver.ts
10576
- init_themes();
10577
- function resolveBuiltInTheme(themeName, options = {}) {
10578
- if (!hasTheme(themeName)) {
10579
- const available = [
10580
- ...getThemeNames(),
10581
- ...Object.keys(options.customThemes ?? {})
10582
- ].sort();
10583
- options.warnings?.push({
10584
- component: "theme",
10585
- message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
10586
- severity: "warning",
10587
- context: { code: "theme_not_found", requested: themeName, available }
10588
- });
10589
- }
10590
- return getThemeWithFallback(themeName);
10591
- }
10592
-
10593
- // src/themes/overrides.ts
10594
- function applyThemeOverrides(theme, overrides) {
10595
- if (!overrides) return theme;
10596
- const fonts = { ...theme.fonts };
10597
- for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
10598
- const key = role;
10599
- fonts[key] = { ...theme.fonts[key], ...def };
10600
- }
10601
- const styles = { ...theme.styles ?? {} };
10602
- for (const [name, def] of Object.entries(overrides.styles ?? {})) {
10603
- styles[name] = { ...styles[name], ...def };
10604
- }
10605
- return {
10606
- ...theme,
10607
- colors: { ...theme.colors, ...overrides.colors ?? {} },
10608
- fonts,
10609
- ...Object.keys(styles).length > 0 && { styles }
10610
- };
10611
- }
10612
-
10613
- // src/core/generationContext.ts
10614
- function defaultNamedThemeLookup(themeName, customThemes, warnings) {
10615
- if (customThemes) {
10616
- if (customThemes[themeName]) return customThemes[themeName];
10617
- const themeNameLower = themeName.toLowerCase();
10618
- const matchingThemeKey = Object.keys(customThemes).find(
10619
- (key) => key.toLowerCase() === themeNameLower
10620
- );
10621
- if (matchingThemeKey) return customThemes[matchingThemeKey];
10622
- return resolveBuiltInTheme(themeName, { customThemes, warnings });
10623
- }
10624
- return resolveBuiltInTheme(themeName, { warnings });
10625
- }
10626
- function resolveThemeContext(documentIn, options = {}) {
10627
- const { customThemes, fonts, warnings, resolveNamedTheme } = options;
10628
- if (documentIn.props === null) {
10629
- throw new Error(
10630
- "Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
10631
- );
10632
- }
10633
- const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
10634
- const authoredThemeName = document.props.theme || void 0;
10635
- const baseThemeName = authoredThemeName ?? "minimal";
10636
- let theme = resolveNamedTheme ? resolveNamedTheme(
10637
- baseThemeName,
10638
- warnings,
10639
- authoredThemeName !== void 0
10640
- ) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
10641
- const themeOverrides = document.props.themeOverrides;
10642
- if (themeOverrides) {
10643
- theme = applyThemeOverrides(theme, themeOverrides);
10644
- }
10645
- const mode = applyExportMode({ doc: document, theme, fonts });
10646
- for (const w of mode.warnings) {
10647
- if (warnings) {
10648
- warnings.push({
10649
- component: "fontRegistry",
10650
- message: w.message,
10651
- severity: "warning",
10652
- context: { code: w.code }
10653
- });
10654
- } else {
10655
- console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
10656
- }
10657
- }
10658
- return {
10659
- document: mode.doc,
10660
- theme: mode.theme,
10661
- themeName: baseThemeName
10662
- };
10663
- }
10664
-
10665
- // src/core/generateFromIr.ts
10666
10489
  init_generationContext();
10667
10490
 
10668
10491
  // src/core/layout.ts
@@ -11116,8 +10939,7 @@ function resolveComponentTree(components, theme) {
11116
10939
  }
11117
10940
 
11118
10941
  // src/core/structure.ts
11119
- async function processDocument(document, theme, themeName, generationDate) {
11120
- const metadata = createDocumentMetadata(document.props, generationDate);
10942
+ function resolveDocumentTree(document, theme) {
11121
10943
  const docDefaults = document.props.componentDefaults;
11122
10944
  const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
11123
10945
  const docNoProofWords = document.props.noProofWords;
@@ -11131,6 +10953,14 @@ async function processDocument(document, theme, themeName, generationDate) {
11131
10953
  },
11132
10954
  ...mergedNoProofWords && { noProofWords: mergedNoProofWords }
11133
10955
  } : theme;
10956
+ return {
10957
+ theme: effectiveTheme,
10958
+ children: resolveComponentTree(document.children || [], effectiveTheme)
10959
+ };
10960
+ }
10961
+ async function processResolvedDocument(document, resolved, themeName, generationDate) {
10962
+ const metadata = createDocumentMetadata(document.props, generationDate);
10963
+ const effectiveTheme = resolved.theme;
11134
10964
  const context = createRenderContext(
11135
10965
  {
11136
10966
  metadata,
@@ -11141,11 +10971,7 @@ async function processDocument(document, theme, themeName, generationDate) {
11141
10971
  effectiveTheme,
11142
10972
  themeName
11143
10973
  );
11144
- const resolvedChildren = resolveComponentTree(
11145
- document.children || [],
11146
- effectiveTheme
11147
- );
11148
- const sections = await extractSections(resolvedChildren, context);
10974
+ const sections = await extractSections(resolved.children, context);
11149
10975
  return {
11150
10976
  metadata,
11151
10977
  sections,
@@ -11245,6 +11071,419 @@ function createRenderContext(document, theme, themeName) {
11245
11071
  };
11246
11072
  }
11247
11073
 
11074
+ // src/quality/facts.ts
11075
+ import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
11076
+
11077
+ // src/core/generationContext.ts
11078
+ import { applyExportMode } from "@json-to-office/shared";
11079
+
11080
+ // src/styles/theme-resolver.ts
11081
+ init_themes();
11082
+ function resolveBuiltInTheme(themeName, options = {}) {
11083
+ if (!hasTheme(themeName)) {
11084
+ const available = [
11085
+ ...getThemeNames(),
11086
+ ...Object.keys(options.customThemes ?? {})
11087
+ ].sort();
11088
+ options.warnings?.push({
11089
+ component: "theme",
11090
+ message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
11091
+ severity: "warning",
11092
+ context: { code: "theme_not_found", requested: themeName, available }
11093
+ });
11094
+ }
11095
+ return getThemeWithFallback(themeName);
11096
+ }
11097
+
11098
+ // src/themes/overrides.ts
11099
+ function applyThemeOverrides(theme, overrides) {
11100
+ if (!overrides) return theme;
11101
+ const fonts = { ...theme.fonts };
11102
+ for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
11103
+ const key = role;
11104
+ fonts[key] = { ...theme.fonts[key], ...def };
11105
+ }
11106
+ const styles = { ...theme.styles ?? {} };
11107
+ for (const [name, def] of Object.entries(overrides.styles ?? {})) {
11108
+ styles[name] = { ...styles[name], ...def };
11109
+ }
11110
+ return {
11111
+ ...theme,
11112
+ colors: { ...theme.colors, ...overrides.colors ?? {} },
11113
+ fonts,
11114
+ ...Object.keys(styles).length > 0 && { styles }
11115
+ };
11116
+ }
11117
+
11118
+ // src/core/generationContext.ts
11119
+ function defaultNamedThemeLookup(themeName, customThemes, warnings) {
11120
+ if (customThemes) {
11121
+ if (customThemes[themeName]) return customThemes[themeName];
11122
+ const themeNameLower = themeName.toLowerCase();
11123
+ const matchingThemeKey = Object.keys(customThemes).find(
11124
+ (key) => key.toLowerCase() === themeNameLower
11125
+ );
11126
+ if (matchingThemeKey) return customThemes[matchingThemeKey];
11127
+ return resolveBuiltInTheme(themeName, { customThemes, warnings });
11128
+ }
11129
+ return resolveBuiltInTheme(themeName, { warnings });
11130
+ }
11131
+ function resolveThemeContext(documentIn, options = {}) {
11132
+ const { customThemes, fonts, warnings, resolveNamedTheme } = options;
11133
+ if (documentIn.props === null) {
11134
+ throw new Error(
11135
+ "Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
11136
+ );
11137
+ }
11138
+ const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
11139
+ const authoredThemeName = document.props.theme || void 0;
11140
+ const baseThemeName = authoredThemeName ?? "minimal";
11141
+ let theme = resolveNamedTheme ? resolveNamedTheme(
11142
+ baseThemeName,
11143
+ warnings,
11144
+ authoredThemeName !== void 0
11145
+ ) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
11146
+ const themeOverrides = document.props.themeOverrides;
11147
+ if (themeOverrides) {
11148
+ theme = applyThemeOverrides(theme, themeOverrides);
11149
+ }
11150
+ const mode = applyExportMode({ doc: document, theme, fonts });
11151
+ for (const w of mode.warnings) {
11152
+ if (warnings) {
11153
+ warnings.push({
11154
+ component: "fontRegistry",
11155
+ message: w.message,
11156
+ severity: "warning",
11157
+ context: { code: w.code }
11158
+ });
11159
+ } else {
11160
+ console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
11161
+ }
11162
+ }
11163
+ return {
11164
+ document: mode.doc,
11165
+ theme: mode.theme,
11166
+ themeName: baseThemeName
11167
+ };
11168
+ }
11169
+
11170
+ // src/json/normalizer.ts
11171
+ function normalizeComponent(component) {
11172
+ if (component.name === "columns") {
11173
+ return normalizeColumnsComponent(component);
11174
+ }
11175
+ if ("children" in component && component.children) {
11176
+ return {
11177
+ ...component,
11178
+ // Preserve ALL properties from validated component
11179
+ children: component.children.map(normalizeComponent)
11180
+ };
11181
+ }
11182
+ if (component.name === "section") {
11183
+ return normalizeSectionComponent(component);
11184
+ }
11185
+ return component;
11186
+ }
11187
+ function normalizeColumnsComponent(component) {
11188
+ const cfg = component.props || {};
11189
+ const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
11190
+ const isLast = i === cols.length - 1;
11191
+ if (!isLast && c.gap === void 0 && topGap !== void 0) {
11192
+ return { ...c, gap: topGap };
11193
+ }
11194
+ return c;
11195
+ });
11196
+ const normalizeAutoWidth = (cols) => cols.map((c) => ({
11197
+ ...c,
11198
+ width: c.width === "auto" ? void 0 : c.width
11199
+ }));
11200
+ let columnsArray;
11201
+ if (typeof cfg.columns === "number" && cfg.columns > 0) {
11202
+ const n = cfg.columns;
11203
+ const base = Array.from(
11204
+ { length: n },
11205
+ () => ({})
11206
+ );
11207
+ const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
11208
+ columnsArray = applyTopLevelGap(base, topLevelGap);
11209
+ } else if (Array.isArray(cfg.columns)) {
11210
+ columnsArray = cfg.columns;
11211
+ const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
11212
+ columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
11213
+ columnsArray = normalizeAutoWidth(columnsArray);
11214
+ } else {
11215
+ columnsArray = [{}];
11216
+ }
11217
+ const normalizedProps = {
11218
+ columns: columnsArray
11219
+ };
11220
+ return {
11221
+ ...component,
11222
+ // Preserve any additional properties
11223
+ name: "columns",
11224
+ props: normalizedProps,
11225
+ children: component.children ? component.children.map(normalizeComponent) : void 0
11226
+ };
11227
+ }
11228
+ function normalizeSectionComponent(component) {
11229
+ const props = component.props || {};
11230
+ return {
11231
+ ...component,
11232
+ // Preserve ALL properties
11233
+ props: {
11234
+ ...props,
11235
+ // Preserve ALL props properties
11236
+ header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
11237
+ footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
11238
+ },
11239
+ children: component.children ? component.children.map(normalizeComponent) : void 0
11240
+ };
11241
+ }
11242
+ function normalizeDocument(document) {
11243
+ if (document.name !== "docx") {
11244
+ throw new Error(
11245
+ 'Top-level document must be a docx component with name="docx"'
11246
+ );
11247
+ }
11248
+ const normalized = normalizeComponent(document);
11249
+ return [normalized];
11250
+ }
11251
+
11252
+ // src/quality/facts.ts
11253
+ init_widthUtils();
11254
+ function asRecord(value) {
11255
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
11256
+ }
11257
+ function pageBox(theme, themeName, pageOverride) {
11258
+ const page = createSectionProperties(
11259
+ getColumnSettings("single"),
11260
+ theme,
11261
+ themeName,
11262
+ "nextPage",
11263
+ pageOverride
11264
+ ).page;
11265
+ return {
11266
+ availableWidthTwips: Math.max(
11267
+ 0,
11268
+ page.size.width - page.margin.left - page.margin.right
11269
+ ),
11270
+ pageBottomTwips: page.size.height
11271
+ };
11272
+ }
11273
+ function tableFact(props, path4, availableWidthTwips) {
11274
+ const columns = Array.isArray(props.columns) ? props.columns : void 0;
11275
+ if (!columns) return void 0;
11276
+ let totalWidthTwips = 0;
11277
+ let hasExplicitWidth = false;
11278
+ let pointSum = 0;
11279
+ let percentSum = 0;
11280
+ const explicitWidths = [];
11281
+ columns.forEach((column, index) => {
11282
+ const width = asRecord(column)?.width;
11283
+ if (typeof width === "number" && Number.isFinite(width)) {
11284
+ hasExplicitWidth = true;
11285
+ explicitWidths.push({ index, width });
11286
+ totalWidthTwips += relativeLengthToTwips(width, availableWidthTwips);
11287
+ pointSum += width;
11288
+ } else if (typeof width === "string") {
11289
+ hasExplicitWidth = true;
11290
+ const percent = width.trim().endsWith("%") ? Number(width.trim().slice(0, -1)) : Number.NaN;
11291
+ if (Number.isFinite(percent)) {
11292
+ explicitWidths.push({ index, width });
11293
+ percentSum += percent;
11294
+ totalWidthTwips += Math.max(
11295
+ 0,
11296
+ Math.round(availableWidthTwips * percent / 100)
11297
+ );
11298
+ }
11299
+ }
11300
+ });
11301
+ return {
11302
+ id: `docx:table-width:${path4}`,
11303
+ kind: "docx/table-width",
11304
+ path: `${path4}/props/columns`,
11305
+ totalWidthTwips,
11306
+ availableWidthTwips,
11307
+ hasExplicitWidth,
11308
+ allColumnsExplicit: explicitWidths.length === columns.length,
11309
+ pointSum,
11310
+ percentSum,
11311
+ explicitWidths
11312
+ };
11313
+ }
11314
+ var TWIPS_PER_POINT3 = 20;
11315
+ var SINGLE_LINE_RATIO = 1.2;
11316
+ function finiteNumber(value) {
11317
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
11318
+ }
11319
+ function lineHeightPt(fontSizePt, spacing) {
11320
+ const rule = asRecord(spacing);
11321
+ const value = finiteNumber(rule?.value);
11322
+ switch (rule?.type) {
11323
+ // `exactly` is an absolute line box, and display type routinely sets it
11324
+ // below the font size — reading it as a multiplier would inflate every
11325
+ // estimate on exactly the headings that matter most.
11326
+ case "exactly":
11327
+ return value ?? fontSizePt * SINGLE_LINE_RATIO;
11328
+ case "atLeast":
11329
+ return Math.max(value ?? 0, fontSizePt * SINGLE_LINE_RATIO);
11330
+ case "multiple":
11331
+ return fontSizePt * SINGLE_LINE_RATIO * (value ?? 1);
11332
+ default:
11333
+ return fontSizePt * SINGLE_LINE_RATIO;
11334
+ }
11335
+ }
11336
+ function characterSpacingPt(spacing) {
11337
+ const rule = asRecord(spacing);
11338
+ const value = finiteNumber(rule?.value);
11339
+ if (rule === void 0 || value === void 0) return 0;
11340
+ return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
11341
+ }
11342
+ function frameTextFact(props, path4, page) {
11343
+ const floating = asRecord(props.floating);
11344
+ const frameWidthTwips = finiteNumber(floating?.width);
11345
+ if (frameWidthTwips === void 0) return void 0;
11346
+ const text = typeof props.text === "string" ? props.text : "";
11347
+ if (text.trim() === "") return void 0;
11348
+ const font = asRecord(props.font);
11349
+ const fontSizePt = finiteNumber(font?.size);
11350
+ if (fontSizePt === void 0) return void 0;
11351
+ const longestWord = text.split(/\s+/).reduce(
11352
+ (longest, word) => word.length > longest.length ? word : longest,
11353
+ ""
11354
+ );
11355
+ const offsetY = finiteNumber(asRecord(floating?.verticalPosition)?.offset);
11356
+ return {
11357
+ id: `docx:frame-text:${path4}`,
11358
+ kind: "docx/frame-text",
11359
+ path: path4,
11360
+ text,
11361
+ fontSizePt,
11362
+ lineHeightPt: lineHeightPt(fontSizePt, font?.lineSpacing),
11363
+ characterSpacingPt: characterSpacingPt(font?.characterSpacing),
11364
+ frameWidthTwips,
11365
+ ...finiteNumber(floating?.height) !== void 0 && {
11366
+ frameHeightTwips: finiteNumber(floating?.height)
11367
+ },
11368
+ ...offsetY !== void 0 && { offsetYTwips: offsetY },
11369
+ pageBottomTwips: page.pageBottomTwips,
11370
+ longestWord
11371
+ };
11372
+ }
11373
+ var SVG_VIEWBOX = /viewBox\s*=\s*"\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*"/;
11374
+ var SVG_TEXT = /<text\b([^>]*)>([^<]*)<\/text>/g;
11375
+ var SVG_ATTR = (name) => new RegExp(`\\b${name}\\s*=\\s*"(-?[\\d.]+)"`);
11376
+ function svgTextFacts(props, path4) {
11377
+ const svg = typeof props.svg === "string" ? props.svg : void 0;
11378
+ if (!svg) return [];
11379
+ const box = SVG_VIEWBOX.exec(svg);
11380
+ if (!box) return [];
11381
+ const viewBoxMinY = Number(box[2]);
11382
+ const viewBoxHeight = Number(box[4]);
11383
+ if (!Number.isFinite(viewBoxMinY) || !Number.isFinite(viewBoxHeight)) {
11384
+ return [];
11385
+ }
11386
+ const facts = [];
11387
+ let match;
11388
+ SVG_TEXT.lastIndex = 0;
11389
+ let index = 0;
11390
+ while ((match = SVG_TEXT.exec(svg)) !== null) {
11391
+ const [, attributes, content] = match;
11392
+ const baselineY = Number(SVG_ATTR("y").exec(attributes)?.[1]);
11393
+ if (!Number.isFinite(baselineY)) {
11394
+ index += 1;
11395
+ continue;
11396
+ }
11397
+ const fontSizeUnits = Number(
11398
+ SVG_ATTR("font-size").exec(attributes)?.[1] ?? 0
11399
+ );
11400
+ facts.push({
11401
+ id: `docx:svg-text:${path4}:${index}`,
11402
+ kind: "docx/svg-text",
11403
+ path: `${path4}/props/svg`,
11404
+ content: content.trim(),
11405
+ baselineY,
11406
+ fontSizeUnits: Number.isFinite(fontSizeUnits) ? fontSizeUnits : 0,
11407
+ viewBoxMinY,
11408
+ viewBoxHeight
11409
+ });
11410
+ index += 1;
11411
+ }
11412
+ return facts;
11413
+ }
11414
+ function walkActive(node, path4, page, visit) {
11415
+ const rec = asRecord(node);
11416
+ if (!rec || rec.enabled === false) return;
11417
+ visit(rec, path4, page);
11418
+ const children = Array.isArray(rec.children) ? rec.children : [];
11419
+ children.forEach(
11420
+ (child, index) => walkActive(child, `${path4}/children/${index}`, page, visit)
11421
+ );
11422
+ }
11423
+ function prepareDocxQualityDocument(document, options = {}) {
11424
+ const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
11425
+ customThemes: options.customThemes,
11426
+ fonts: options.fonts,
11427
+ warnings: options.warnings
11428
+ });
11429
+ const resolved = resolveDocumentTree(context.document, context.theme);
11430
+ const basePage = pageBox(resolved.theme, context.themeName);
11431
+ const facts = [];
11432
+ const provenance = {};
11433
+ let previousHeadingLevel;
11434
+ const addFact = (fact) => {
11435
+ facts.push(fact);
11436
+ provenance[fact.id] = {
11437
+ path: fact.path,
11438
+ ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
11439
+ };
11440
+ };
11441
+ const visit = (node, path4, page) => {
11442
+ const props = asRecord(node.props) ?? {};
11443
+ if (node.name === "table") {
11444
+ const fact = tableFact(props, path4, page.availableWidthTwips);
11445
+ if (fact) addFact(fact);
11446
+ }
11447
+ if (node.name === "paragraph" || node.name === "text-box") {
11448
+ const fact = frameTextFact(props, path4, page);
11449
+ if (fact) addFact(fact);
11450
+ }
11451
+ if (node.name === "image" || node.name === "visual") {
11452
+ for (const fact of svgTextFacts(props, path4)) addFact(fact);
11453
+ }
11454
+ if (node.name === "heading") {
11455
+ const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
11456
+ addFact({
11457
+ id: `docx:heading:${path4}`,
11458
+ kind: "docx/heading",
11459
+ path: `${path4}/props/level`,
11460
+ level,
11461
+ ...previousHeadingLevel !== void 0 && {
11462
+ previousLevel: previousHeadingLevel
11463
+ }
11464
+ });
11465
+ previousHeadingLevel = level;
11466
+ }
11467
+ };
11468
+ resolved.children.forEach((component, index) => {
11469
+ const rec = component;
11470
+ const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
11471
+ walkActive(component, `/children/${index}`, page, visit);
11472
+ });
11473
+ return {
11474
+ format: "docx",
11475
+ model: {
11476
+ authored: document,
11477
+ context,
11478
+ document: resolved,
11479
+ themeName: context.themeName
11480
+ },
11481
+ facts,
11482
+ provenance,
11483
+ renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
11484
+ };
11485
+ }
11486
+
11248
11487
  // src/core/generateFromIr.ts
11249
11488
  var UncompiledComponentError = class extends Error {
11250
11489
  code = "UNCOMPILED_COMPONENT";
@@ -11272,11 +11511,14 @@ async function compileDocumentToIr(document, options = {}, collector) {
11272
11511
  );
11273
11512
  }
11274
11513
  async function compileDocumentScoped(document, options, warnings) {
11275
- const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
11514
+ const prepared = options.prepared ?? prepareDocxQualityDocument(document, {
11276
11515
  customThemes: options.customThemes,
11277
11516
  fonts: options.fonts,
11278
- warnings
11517
+ warnings,
11518
+ context: options.context,
11519
+ renderer: options.renderer
11279
11520
  });
11521
+ const { context } = prepared.model;
11280
11522
  const hasVisual = collectVisualProps(context.document).length > 0;
11281
11523
  const resolvedFonts = await resolveDocumentFonts(
11282
11524
  context.document,
@@ -11292,9 +11534,10 @@ async function compileDocumentScoped(document, options, warnings) {
11292
11534
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
11293
11535
  ...visualFonts.length > 0 ? { visualFonts } : {}
11294
11536
  });
11295
- const structure = await processDocument(
11537
+ const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
11538
+ const structure = await processResolvedDocument(
11296
11539
  desugared,
11297
- context.theme,
11540
+ resolved,
11298
11541
  context.themeName,
11299
11542
  resolveGenerationDate(options)
11300
11543
  );
@@ -11459,7 +11702,8 @@ async function generateBufferWithWarnings(jsonConfig, options) {
11459
11702
  ...options?.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
11460
11703
  ...options?.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
11461
11704
  ...options?.generatedAt !== void 0 ? { generatedAt: options.generatedAt } : {},
11462
- ...renderer ? { renderer } : {}
11705
+ ...renderer ? { renderer } : {},
11706
+ ...options?.prepared ? { prepared: options.prepared } : {}
11463
11707
  });
11464
11708
  }
11465
11709
  function parseDocument(jsonConfig) {
@@ -11600,6 +11844,411 @@ async function rasterizeVisual(obj, ctx) {
11600
11844
  });
11601
11845
  }
11602
11846
 
11847
+ // src/quality/preflight.ts
11848
+ import {
11849
+ assertValidQualityPolicy,
11850
+ assertValidQualityProfile
11851
+ } from "@json-to-office/quality";
11852
+
11853
+ // src/quality/rules.ts
11854
+ import {
11855
+ mergeQualityProfiles,
11856
+ QUALITY_CODES,
11857
+ QualityEngine
11858
+ } from "@json-to-office/quality";
11859
+
11860
+ // src/quality/text-metrics.ts
11861
+ var DEFAULT_ADVANCE = 556;
11862
+ var ADVANCE = {
11863
+ " ": 278,
11864
+ A: 667,
11865
+ B: 667,
11866
+ C: 722,
11867
+ D: 722,
11868
+ E: 667,
11869
+ F: 611,
11870
+ G: 778,
11871
+ H: 722,
11872
+ I: 278,
11873
+ J: 500,
11874
+ K: 667,
11875
+ L: 556,
11876
+ M: 833,
11877
+ N: 722,
11878
+ O: 778,
11879
+ P: 667,
11880
+ Q: 778,
11881
+ R: 722,
11882
+ S: 667,
11883
+ T: 611,
11884
+ U: 722,
11885
+ V: 667,
11886
+ W: 944,
11887
+ X: 667,
11888
+ Y: 667,
11889
+ Z: 611,
11890
+ a: 556,
11891
+ b: 556,
11892
+ c: 500,
11893
+ d: 556,
11894
+ e: 556,
11895
+ f: 278,
11896
+ g: 556,
11897
+ h: 556,
11898
+ i: 222,
11899
+ j: 222,
11900
+ k: 500,
11901
+ l: 222,
11902
+ m: 833,
11903
+ n: 556,
11904
+ o: 556,
11905
+ p: 556,
11906
+ q: 556,
11907
+ r: 333,
11908
+ s: 500,
11909
+ t: 278,
11910
+ u: 556,
11911
+ v: 500,
11912
+ w: 722,
11913
+ x: 500,
11914
+ y: 500,
11915
+ z: 500,
11916
+ ".": 278,
11917
+ ",": 278,
11918
+ ":": 278,
11919
+ ";": 278,
11920
+ "!": 278,
11921
+ "?": 556,
11922
+ "'": 191,
11923
+ '"': 355,
11924
+ "-": 333,
11925
+ "\u2013": 556,
11926
+ "\u2014": 1e3,
11927
+ "&": 667,
11928
+ "%": 889,
11929
+ "(": 333,
11930
+ ")": 333,
11931
+ "/": 278,
11932
+ "@": 1015,
11933
+ "#": 556,
11934
+ "+": 584,
11935
+ "=": 584
11936
+ };
11937
+ function estimateTextWidthPt(text, fontSizePt, trackingPt = 0) {
11938
+ let units = 0;
11939
+ for (const character of text) {
11940
+ units += ADVANCE[character] ?? DEFAULT_ADVANCE;
11941
+ }
11942
+ const width = units / 1e3 * fontSizePt + text.length * trackingPt;
11943
+ return Math.max(0, width);
11944
+ }
11945
+ function estimateWrappedLines(text, widthPt, fontSizePt, trackingPt = 0) {
11946
+ if (widthPt <= 0) return 1;
11947
+ let lines = 0;
11948
+ for (const paragraph2 of text.split("\n")) {
11949
+ const words = paragraph2.split(/\s+/).filter(Boolean);
11950
+ if (words.length === 0) {
11951
+ lines += 1;
11952
+ continue;
11953
+ }
11954
+ let current = "";
11955
+ let used = 0;
11956
+ for (const word of words) {
11957
+ const candidate = current === "" ? word : `${current} ${word}`;
11958
+ if (estimateTextWidthPt(candidate, fontSizePt, trackingPt) <= widthPt) {
11959
+ current = candidate;
11960
+ } else {
11961
+ if (current !== "") used += 1;
11962
+ current = word;
11963
+ }
11964
+ }
11965
+ lines += used + (current === "" ? 0 : 1);
11966
+ }
11967
+ return Math.max(1, lines);
11968
+ }
11969
+
11970
+ // src/quality/rules.ts
11971
+ var WIDTH_TOLERANCE_TWIPS = 10;
11972
+ function numberParameter(parameters, name, fallback) {
11973
+ const value = parameters[name];
11974
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
11975
+ }
11976
+ var docxTableWidthRule = {
11977
+ id: "docx/table-width",
11978
+ code: QUALITY_CODES.TABLE_WIDTH_OVERFLOW,
11979
+ category: "integrity",
11980
+ defaultSeverity: "warning",
11981
+ defaultCertainty: "deterministic",
11982
+ formats: ["docx"],
11983
+ defaultParameters: { toleranceTwips: WIDTH_TOLERANCE_TWIPS },
11984
+ evaluate: ({ facts, configuration }) => {
11985
+ const toleranceTwips = numberParameter(
11986
+ configuration.parameters,
11987
+ "toleranceTwips",
11988
+ WIDTH_TOLERANCE_TWIPS
11989
+ );
11990
+ return facts.filter(
11991
+ (fact) => fact.kind === "docx/table-width"
11992
+ ).filter(
11993
+ (fact) => fact.hasExplicitWidth && fact.totalWidthTwips > fact.availableWidthTwips + toleranceTwips
11994
+ ).map((fact) => {
11995
+ const totalPt = Math.round(fact.totalWidthTwips / 20 * 10) / 10;
11996
+ const availablePt = Math.round(fact.availableWidthTwips / 20 * 10) / 10;
11997
+ const scale = fact.availableWidthTwips / fact.totalWidthTwips;
11998
+ const fixes = fact.allColumnsExplicit ? fact.explicitWidths.map(({ index, width }) => ({
11999
+ op: "replace",
12000
+ path: `${fact.path}/${index}/width`,
12001
+ value: typeof width === "number" ? Math.floor(width * scale * 10) / 10 : `${Math.floor(Number(width.trim().slice(0, -1)) * scale * 10) / 10}%`
12002
+ })) : [];
12003
+ return {
12004
+ message: `Column widths use ${totalPt}pt, but this section has ${availablePt}pt available \u2014 the table will spill off the right edge.`,
12005
+ path: fact.path,
12006
+ suggestion: "Reduce fixed/percentage widths, widen the page, or leave columns unsized so they share the remainder.",
12007
+ context: {
12008
+ totalWidthPt: totalPt,
12009
+ availableWidthPt: availablePt,
12010
+ pointSum: fact.pointSum,
12011
+ percentSum: fact.percentSum
12012
+ },
12013
+ evidence: {
12014
+ actual: totalPt,
12015
+ expected: availablePt,
12016
+ unit: "pt"
12017
+ },
12018
+ ...fixes.length > 0 && { fixes }
12019
+ };
12020
+ });
12021
+ }
12022
+ };
12023
+ var docxHeadingHierarchyRule = {
12024
+ id: "docx/heading-hierarchy",
12025
+ code: QUALITY_CODES.HEADING_SKIP,
12026
+ category: "hierarchy",
12027
+ defaultSeverity: "info",
12028
+ defaultCertainty: "deterministic",
12029
+ formats: ["docx"],
12030
+ evaluate: ({ facts }) => facts.filter((fact) => fact.kind === "docx/heading").flatMap((fact) => {
12031
+ const previousLevel = fact.previousLevel;
12032
+ if (previousLevel === void 0 || fact.level <= previousLevel + 1) {
12033
+ return [];
12034
+ }
12035
+ return [
12036
+ {
12037
+ message: `Heading level ${fact.level} follows level ${previousLevel} \u2014 the skipped level breaks the document outline.`,
12038
+ path: fact.path,
12039
+ suggestion: `Use level ${previousLevel + 1}, or promote this heading's section.`,
12040
+ context: { level: fact.level, previousLevel },
12041
+ evidence: {
12042
+ actual: fact.level,
12043
+ expected: previousLevel + 1
12044
+ },
12045
+ // The fact path already addresses `.../props/level`; RFC 6902
12046
+ // `add` replaces an existing member, so this works whether the
12047
+ // level was explicit or defaulted.
12048
+ fixes: [
12049
+ {
12050
+ op: "add",
12051
+ path: fact.path,
12052
+ value: previousLevel + 1
12053
+ }
12054
+ ]
12055
+ }
12056
+ ];
12057
+ })
12058
+ };
12059
+ var WIDTH_MODEL_TOLERANCE = 1.08;
12060
+ var TWIPS_PER_POINT4 = 20;
12061
+ function frameTextFacts(facts) {
12062
+ return facts.filter(
12063
+ (fact) => fact.kind === "docx/frame-text"
12064
+ );
12065
+ }
12066
+ var docxTextFitRule = {
12067
+ id: "docx/text-fit",
12068
+ code: QUALITY_CODES.TEXT_OVERFLOW,
12069
+ category: "integrity",
12070
+ defaultSeverity: "warning",
12071
+ defaultCertainty: "estimated",
12072
+ formats: ["docx"],
12073
+ defaultParameters: { widthTolerance: WIDTH_MODEL_TOLERANCE },
12074
+ evaluate: ({ facts, configuration }) => {
12075
+ const tolerance = numberParameter(
12076
+ configuration.parameters,
12077
+ "widthTolerance",
12078
+ WIDTH_MODEL_TOLERANCE
12079
+ );
12080
+ const findings = [];
12081
+ for (const fact of frameTextFacts(facts)) {
12082
+ const frameWidthPt = fact.frameWidthTwips / TWIPS_PER_POINT4;
12083
+ const longestWordPt = estimateTextWidthPt(
12084
+ fact.longestWord,
12085
+ fact.fontSizePt,
12086
+ fact.characterSpacingPt
12087
+ );
12088
+ if (longestWordPt > frameWidthPt * tolerance) {
12089
+ findings.push({
12090
+ message: `"${fact.longestWord}" is estimated at ${Math.round(longestWordPt)}pt in a ${Math.round(frameWidthPt)}pt frame \u2014 with no wrap point it will break mid-word.`,
12091
+ path: `${fact.path}/props/floating/width`,
12092
+ suggestion: "Widen the frame, reduce fontSize, or condense the run further.",
12093
+ context: {
12094
+ longestWord: fact.longestWord,
12095
+ estimatedWidthPt: Math.round(longestWordPt),
12096
+ frameWidthPt: Math.round(frameWidthPt),
12097
+ fontSizePt: fact.fontSizePt
12098
+ },
12099
+ evidence: {
12100
+ actual: Math.round(longestWordPt),
12101
+ expected: Math.round(frameWidthPt),
12102
+ unit: "pt"
12103
+ }
12104
+ });
12105
+ continue;
12106
+ }
12107
+ if (fact.offsetYTwips === void 0) continue;
12108
+ const lines = estimateWrappedLines(
12109
+ fact.text,
12110
+ frameWidthPt,
12111
+ fact.fontSizePt,
12112
+ fact.characterSpacingPt
12113
+ );
12114
+ const heightTwips = lines * fact.lineHeightPt * TWIPS_PER_POINT4;
12115
+ const bottomTwips = fact.offsetYTwips + heightTwips;
12116
+ const overrunTwips = bottomTwips - fact.pageBottomTwips;
12117
+ if (overrunTwips <= fact.lineHeightPt * TWIPS_PER_POINT4) continue;
12118
+ findings.push({
12119
+ message: `Frame starts at ${fact.offsetYTwips} twips and needs ${Math.round(heightTwips)} more for ${lines} line${lines === 1 ? "" : "s"} of ${fact.fontSizePt}pt \u2014 it runs ${Math.round(overrunTwips)} twips past the page, so the text breaks onto the next one.`,
12120
+ path: `${fact.path}/props/floating/verticalPosition/offset`,
12121
+ suggestion: "Raise the frame, shorten the text, or reduce fontSize so the block finishes on the page.",
12122
+ context: {
12123
+ offsetYTwips: fact.offsetYTwips,
12124
+ estimatedHeightTwips: Math.round(heightTwips),
12125
+ pageBottomTwips: fact.pageBottomTwips,
12126
+ lines
12127
+ },
12128
+ evidence: {
12129
+ actual: Math.round(bottomTwips),
12130
+ expected: fact.pageBottomTwips,
12131
+ unit: "twips"
12132
+ }
12133
+ });
12134
+ }
12135
+ return findings;
12136
+ }
12137
+ };
12138
+ var docxSvgTextBoundsRule = {
12139
+ id: "docx/svg-text-bounds",
12140
+ code: QUALITY_CODES.SVG_TEXT_CLIPPED,
12141
+ category: "integrity",
12142
+ defaultSeverity: "warning",
12143
+ defaultCertainty: "deterministic",
12144
+ formats: ["docx"],
12145
+ evaluate: ({ facts }) => facts.filter((fact) => fact.kind === "docx/svg-text").flatMap((fact) => {
12146
+ const canvasBottom = fact.viewBoxMinY + fact.viewBoxHeight;
12147
+ if (fact.baselineY <= canvasBottom) return [];
12148
+ const overflow = Math.round((fact.baselineY - canvasBottom) * 10) / 10;
12149
+ return [
12150
+ {
12151
+ message: `SVG text "${fact.content}" sits ${overflow} units below the viewBox \u2014 it is clipped away and never reaches the document's text layer.`,
12152
+ path: fact.path,
12153
+ suggestion: `Move the baseline above ${Math.round(canvasBottom)}, or grow the viewBox height.`,
12154
+ context: {
12155
+ content: fact.content,
12156
+ baselineY: fact.baselineY,
12157
+ canvasBottom,
12158
+ overflowUnits: overflow
12159
+ },
12160
+ evidence: {
12161
+ actual: fact.baselineY,
12162
+ expected: canvasBottom,
12163
+ unit: "viewBox units"
12164
+ }
12165
+ }
12166
+ ];
12167
+ })
12168
+ };
12169
+ var DOCX_QUALITY_RULES = {
12170
+ id: "docx/default",
12171
+ rules: [
12172
+ docxTableWidthRule,
12173
+ docxHeadingHierarchyRule,
12174
+ docxTextFitRule,
12175
+ docxSvgTextBoundsRule
12176
+ ]
12177
+ };
12178
+ var DOCX_QUALITY_PROFILES = {
12179
+ "executive-report": {
12180
+ id: "executive-report",
12181
+ formats: ["docx"],
12182
+ description: "Short decision document with strict outline continuity.",
12183
+ rules: {
12184
+ "docx/heading-hierarchy": { severity: "warning" }
12185
+ }
12186
+ },
12187
+ "technical-report": {
12188
+ id: "technical-report",
12189
+ formats: ["docx"],
12190
+ description: "Portable professional report defaults."
12191
+ },
12192
+ "legal-appendix": {
12193
+ id: "legal-appendix",
12194
+ formats: ["docx"],
12195
+ description: "Dense appendix: preserve integrity without editorial taste."
12196
+ }
12197
+ };
12198
+ var DOCX_DEFAULT_QUALITY_PROFILE = DOCX_QUALITY_PROFILES["technical-report"];
12199
+ var DOCX_PROFILES_BY_ID = DOCX_QUALITY_PROFILES;
12200
+ function resolveDocxQualityProfile(requested) {
12201
+ if (!requested) return void 0;
12202
+ const registered = DOCX_PROFILES_BY_ID[requested.id];
12203
+ if (!registered) return requested;
12204
+ return mergeQualityProfiles(registered, requested);
12205
+ }
12206
+ var docxQualityEngine = new QualityEngine(DOCX_QUALITY_RULES.rules);
12207
+
12208
+ // src/quality/preflight.ts
12209
+ var PREPARE_RULE_ID = "quality/prepare";
12210
+ function emptyAnalysis(ruleErrors = [], blocked = false) {
12211
+ return {
12212
+ diagnostics: [],
12213
+ counts: { error: 0, warning: 0, info: 0 },
12214
+ blocked,
12215
+ truncated: false,
12216
+ suppressedCount: 0,
12217
+ evaluatedRuleIds: [],
12218
+ ruleErrors
12219
+ };
12220
+ }
12221
+ function analyzeDocxQuality(doc, options = {}) {
12222
+ assertValidQualityPolicy(options.policy);
12223
+ assertValidQualityProfile(options.profile);
12224
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc) || doc.name !== "docx") {
12225
+ return emptyAnalysis();
12226
+ }
12227
+ let prepared = options.prepared;
12228
+ try {
12229
+ prepared ??= prepareDocxQualityDocument(
12230
+ doc,
12231
+ options
12232
+ );
12233
+ } catch (error) {
12234
+ if (options.policy?.onRuleError === "throw") throw error;
12235
+ const gate = options.policy?.gate;
12236
+ return emptyAnalysis(
12237
+ [
12238
+ {
12239
+ ruleId: PREPARE_RULE_ID,
12240
+ message: error instanceof Error ? error.message : String(error)
12241
+ }
12242
+ ],
12243
+ gate !== void 0 && gate !== "none"
12244
+ );
12245
+ }
12246
+ return docxQualityEngine.analyzeSync(prepared, {
12247
+ profile: resolveDocxQualityProfile(options.profile) ?? DOCX_DEFAULT_QUALITY_PROFILE,
12248
+ policy: options.policy
12249
+ });
12250
+ }
12251
+
11603
12252
  // src/utils/warningsDocument.ts
11604
12253
  function generateWarningsDocument(warnings) {
11605
12254
  if (!warnings || warnings.length === 0) {
@@ -12526,17 +13175,22 @@ export {
12526
13175
  ComponentValidationError2 as ComponentValidationError,
12527
13176
  DocumentGenerator as CoreDocumentGenerator,
12528
13177
  DEFAULT_DOCX_RENDERER_ID,
13178
+ DOCX_DEFAULT_QUALITY_PROFILE,
13179
+ DOCX_QUALITY_PROFILES,
13180
+ DOCX_QUALITY_RULES,
12529
13181
  ThemeFileError,
12530
13182
  ThemeParseError,
12531
13183
  ThemeValidationError,
12532
13184
  UncompiledComponentError,
12533
13185
  UnknownPreservedComponentError,
13186
+ analyzeDocxQuality,
12534
13187
  cleanComponentProps,
12535
13188
  corporateTheme,
12536
13189
  createComponent,
12537
13190
  createDocumentGenerator,
12538
13191
  createMinimalTheme,
12539
13192
  createVersion,
13193
+ docxQualityEngine,
12540
13194
  docxRendererIds,
12541
13195
  docxRendererStatuses,
12542
13196
  examples,
@@ -12579,7 +13233,9 @@ export {
12579
13233
  mergeSchemas,
12580
13234
  minimalTheme,
12581
13235
  modernTheme,
13236
+ prepareDocxQualityDocument,
12582
13237
  resetVisualPrepassStats,
13238
+ resolveDocxQualityProfile,
12583
13239
  runExample,
12584
13240
  themes,
12585
13241
  validateComponentProps,