@json-to-office/core-docx 1.3.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.
Files changed (34) hide show
  1. package/dist/core/generateFromIr.d.ts +5 -1
  2. package/dist/core/generateFromIr.d.ts.map +1 -1
  3. package/dist/core/generator.d.ts +4 -0
  4. package/dist/core/generator.d.ts.map +1 -1
  5. package/dist/core/structure.d.ts +13 -0
  6. package/dist/core/structure.d.ts.map +1 -1
  7. package/dist/index.d.ts +5 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1084 -348
  10. package/dist/index.js.map +1 -1
  11. package/dist/ir/compiler.d.ts.map +1 -1
  12. package/dist/plugin/example/index.js +572 -251
  13. package/dist/plugin/example/index.js.map +1 -1
  14. package/dist/quality/facts.d.ts +82 -0
  15. package/dist/quality/facts.d.ts.map +1 -0
  16. package/dist/quality/preflight.d.ts +15 -0
  17. package/dist/quality/preflight.d.ts.map +1 -0
  18. package/dist/quality/rules.d.ts +38 -0
  19. package/dist/quality/rules.d.ts.map +1 -0
  20. package/dist/quality/text-metrics.d.ts +34 -0
  21. package/dist/quality/text-metrics.d.ts.map +1 -0
  22. package/dist/renderers/docxjs/index.d.ts.map +1 -1
  23. package/dist/renderers/office-open/index.d.ts.map +1 -1
  24. package/dist/renderers/registry.d.ts +18 -4
  25. package/dist/renderers/registry.d.ts.map +1 -1
  26. package/dist/styles/themeToStyles.d.ts +35 -1
  27. package/dist/styles/themeToStyles.d.ts.map +1 -1
  28. package/dist/templates/themes/apex.docx.theme.json +1 -2
  29. package/dist/templates/themes/corporate.docx.theme.json +1 -2
  30. package/dist/templates/themes/devportal.docx.theme.json +1 -2
  31. package/dist/templates/themes/minimal.docx.theme.json +1 -2
  32. package/dist/templates/themes/modern.docx.theme.json +1 -2
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +9 -14
package/dist/index.js CHANGED
@@ -428,8 +428,7 @@ var init_minimal_docx_theme = __esm({
428
428
  },
429
429
  list: {
430
430
  format: "bullet",
431
- bullet: "\u2022",
432
- indent: 3
431
+ bullet: "\u2022"
433
432
  },
434
433
  image: {
435
434
  alignment: "left"
@@ -553,8 +552,7 @@ var init_corporate_docx_theme = __esm({
553
552
  },
554
553
  list: {
555
554
  format: "bullet",
556
- bullet: "\u2022",
557
- indent: 3
555
+ bullet: "\u2022"
558
556
  },
559
557
  statistic: {
560
558
  alignment: "center"
@@ -667,8 +665,7 @@ var init_modern_docx_theme = __esm({
667
665
  },
668
666
  list: {
669
667
  format: "bullet",
670
- bullet: "\u2013",
671
- indent: 3
668
+ bullet: "\u2013"
672
669
  },
673
670
  image: {
674
671
  alignment: "center"
@@ -792,8 +789,7 @@ var init_apex_docx_theme = __esm({
792
789
  },
793
790
  list: {
794
791
  format: "bullet",
795
- bullet: "\u2022",
796
- indent: 3
792
+ bullet: "\u2022"
797
793
  },
798
794
  image: {
799
795
  alignment: "center"
@@ -925,8 +921,7 @@ var init_devportal_docx_theme = __esm({
925
921
  },
926
922
  list: {
927
923
  format: "bullet",
928
- bullet: "\u2013",
929
- indent: 3
924
+ bullet: "\u2013"
930
925
  },
931
926
  image: {
932
927
  alignment: "center"
@@ -2779,99 +2774,6 @@ var init_styles2 = __esm({
2779
2774
  }
2780
2775
  });
2781
2776
 
2782
- // src/ir/features.ts
2783
- var DOCX_FEATURES, ALL_DOCX_FEATURES;
2784
- var init_features = __esm({
2785
- "src/ir/features.ts"() {
2786
- "use strict";
2787
- DOCX_FEATURES = [
2788
- /** Paragraphs and text runs — the floor of the format. */
2789
- "paragraphs",
2790
- /** Named paragraph and character styles in `styles.xml`. */
2791
- "styles",
2792
- /** Numbering definitions and numbered paragraphs. */
2793
- "numbering",
2794
- /** Multiple sections with their own page setup. */
2795
- "sections",
2796
- /** Multi-column section layout. */
2797
- "columns",
2798
- /** Header and footer parts, including first/even variants. */
2799
- "headers-footers",
2800
- "tables",
2801
- /** Merged table cells — column span or vertical merge. */
2802
- "table-merged-cells",
2803
- /** A table positioned out of the text flow. */
2804
- "floating-tables",
2805
- /** Inline images. */
2806
- "images",
2807
- /** Anchored images with text wrapping. */
2808
- "floating-images",
2809
- /** SVG images with a raster fallback. */
2810
- "svg-images",
2811
- /** A paragraph positioned as a floating frame. */
2812
- "text-frames",
2813
- /** Native shape text boxes. */
2814
- "text-boxes",
2815
- /**
2816
- * A DrawingML group: shapes, text boxes and pictures sharing one child
2817
- * coordinate space, drawn as a single anchored or inline object.
2818
- *
2819
- * Separate from `text-boxes` because a lone `wps:wsp` run is a far smaller
2820
- * ask than `wpg:wgp` with child transforms, preset geometry and grouped
2821
- * pictures — a backend can plausibly have the first and not the second.
2822
- */
2823
- "drawing-groups",
2824
- /**
2825
- * A native chart part: a `c:chartSpace` with its own embedded workbook.
2826
- *
2827
- * Separate from `images` because a chart is not a picture with extra data —
2828
- * it is its own part, its own relationship and its own workbook, and a
2829
- * backend either writes all three or writes none. docx.js has no chart
2830
- * primitive at all, which is what turns a `chart` component sent to that
2831
- * backend into a named capability error rather than a document missing a
2832
- * figure.
2833
- */
2834
- "charts",
2835
- /** A table-of-contents field. */
2836
- "toc",
2837
- /** TOC entries baked in so an unrefreshed reader still shows them. */
2838
- "cached-toc",
2839
- /** Arbitrary Word fields, e.g. PAGE, NUMPAGES, REF. */
2840
- "fields",
2841
- /** Field results cached for readers that never refresh. */
2842
- "cached-fields",
2843
- "hyperlinks",
2844
- "bookmarks",
2845
- /** Links and REF fields that target a bookmark. */
2846
- "cross-references",
2847
- "comments",
2848
- /** Threaded and resolvable comments. */
2849
- "comment-threads",
2850
- "footnotes",
2851
- "endnotes",
2852
- /** Inserted and deleted content. */
2853
- "revisions",
2854
- /** Page, column and line breaks. */
2855
- "breaks",
2856
- /** Paragraph and table shading. */
2857
- "shading",
2858
- /** Paragraph, table and page borders. */
2859
- "borders",
2860
- /** Tab stops. */
2861
- "tab-stops",
2862
- /** Per-run proofing language and no-proof. */
2863
- "proofing-language",
2864
- /** Custom document properties. */
2865
- "custom-properties",
2866
- /** Right-to-left paragraph direction. */
2867
- "rtl"
2868
- ];
2869
- ALL_DOCX_FEATURES = new Set(
2870
- DOCX_FEATURES
2871
- );
2872
- }
2873
- });
2874
-
2875
2777
  // src/utils/packageDocument.ts
2876
2778
  import AdmZip from "adm-zip";
2877
2779
  function resolveGenerationDate(options) {
@@ -3279,37 +3181,47 @@ function coreProperties(ir) {
3279
3181
  ...metadata.custom?.length ? { customProperties: metadata.custom } : {}
3280
3182
  };
3281
3183
  }
3282
- var DOCXJS_RENDERER_ID, NOT_YET_EMITTED, DOCXJS_CAPABILITIES;
3184
+ var DOCXJS_RENDERER_ID, DOCXJS_CAPABILITIES;
3283
3185
  var init_docxjs = __esm({
3284
3186
  "src/renderers/docxjs/index.ts"() {
3285
3187
  "use strict";
3286
3188
  init_styles2();
3287
3189
  init_imageUtils();
3288
- init_features();
3289
3190
  init_packageDocument();
3290
3191
  init_fixFloatingImageIds();
3291
3192
  init_emit();
3292
3193
  init_units();
3293
3194
  DOCXJS_RENDERER_ID = "docxjs";
3294
- NOT_YET_EMITTED = /* @__PURE__ */ new Set([
3295
- "table-merged-cells",
3296
- "cached-fields",
3297
- "shading",
3298
- "borders",
3299
- "rtl",
3300
- // `drawing-groups` is the one exclusion that is a real backend gap rather
3301
- // than a slice boundary: docx.js has no `wpg:wgp`. Leaving it out is what
3302
- // turns a native `visual` sent to this backend into a named capability
3303
- // error instead of a document with the graphic missing.
3304
- "drawing-groups",
3305
- // `charts` is the second real backend gap: docx.js has no chart primitive at
3306
- // all, so a `chart` component sent here becomes a named capability error
3307
- // rather than a document missing a figure.
3308
- "charts"
3195
+ DOCXJS_CAPABILITIES = /* @__PURE__ */ new Set([
3196
+ "paragraphs",
3197
+ "styles",
3198
+ "numbering",
3199
+ "sections",
3200
+ "columns",
3201
+ "headers-footers",
3202
+ "tables",
3203
+ "floating-tables",
3204
+ "images",
3205
+ "floating-images",
3206
+ "svg-images",
3207
+ "text-frames",
3208
+ "text-boxes",
3209
+ "toc",
3210
+ "cached-toc",
3211
+ "fields",
3212
+ "hyperlinks",
3213
+ "bookmarks",
3214
+ "cross-references",
3215
+ "comments",
3216
+ "comment-threads",
3217
+ "footnotes",
3218
+ "endnotes",
3219
+ "revisions",
3220
+ "breaks",
3221
+ "tab-stops",
3222
+ "proofing-language",
3223
+ "custom-properties"
3309
3224
  ]);
3310
- DOCXJS_CAPABILITIES = new Set(
3311
- [...ALL_DOCX_FEATURES].filter((feature) => !NOT_YET_EMITTED.has(feature))
3312
- );
3313
3225
  }
3314
3226
  });
3315
3227
 
@@ -4567,11 +4479,10 @@ function coreProperties2(ir) {
4567
4479
  ...metadata.custom?.length ? { customProperties: metadata.custom } : {}
4568
4480
  };
4569
4481
  }
4570
- var OFFICE_OPEN_DOCX_RENDERER_ID, OFFICE_OPEN_DOCX, UNSUPPORTED, OFFICE_OPEN_CAPABILITIES;
4482
+ var OFFICE_OPEN_DOCX_RENDERER_ID, OFFICE_OPEN_DOCX, OFFICE_OPEN_CAPABILITIES;
4571
4483
  var init_office_open = __esm({
4572
4484
  "src/renderers/office-open/index.ts"() {
4573
4485
  "use strict";
4574
- init_features();
4575
4486
  init_chartParts();
4576
4487
  init_imageUtils();
4577
4488
  init_packageDocument();
@@ -4579,17 +4490,37 @@ var init_office_open = __esm({
4579
4490
  init_styles3();
4580
4491
  OFFICE_OPEN_DOCX_RENDERER_ID = "office-open";
4581
4492
  OFFICE_OPEN_DOCX = "@office-open/docx";
4582
- UNSUPPORTED = /* @__PURE__ */ new Set([
4583
- "cached-fields",
4584
- "comment-threads",
4585
- "table-merged-cells",
4586
- "shading",
4587
- "borders",
4588
- "rtl"
4493
+ OFFICE_OPEN_CAPABILITIES = /* @__PURE__ */ new Set([
4494
+ "paragraphs",
4495
+ "styles",
4496
+ "numbering",
4497
+ "sections",
4498
+ "columns",
4499
+ "headers-footers",
4500
+ "tables",
4501
+ "floating-tables",
4502
+ "images",
4503
+ "floating-images",
4504
+ "svg-images",
4505
+ "text-frames",
4506
+ "text-boxes",
4507
+ "drawing-groups",
4508
+ "charts",
4509
+ "toc",
4510
+ "cached-toc",
4511
+ "fields",
4512
+ "hyperlinks",
4513
+ "bookmarks",
4514
+ "cross-references",
4515
+ "comments",
4516
+ "footnotes",
4517
+ "endnotes",
4518
+ "revisions",
4519
+ "breaks",
4520
+ "tab-stops",
4521
+ "proofing-language",
4522
+ "custom-properties"
4589
4523
  ]);
4590
- OFFICE_OPEN_CAPABILITIES = new Set(
4591
- [...ALL_DOCX_FEATURES].filter((feature) => !UNSUPPORTED.has(feature))
4592
- );
4593
4524
  }
4594
4525
  });
4595
4526
 
@@ -5106,88 +5037,6 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
5106
5037
  return map;
5107
5038
  }
5108
5039
 
5109
- // src/json/normalizer.ts
5110
- function normalizeComponent(component) {
5111
- if (component.name === "columns") {
5112
- return normalizeColumnsComponent(component);
5113
- }
5114
- if ("children" in component && component.children) {
5115
- return {
5116
- ...component,
5117
- // Preserve ALL properties from validated component
5118
- children: component.children.map(normalizeComponent)
5119
- };
5120
- }
5121
- if (component.name === "section") {
5122
- return normalizeSectionComponent(component);
5123
- }
5124
- return component;
5125
- }
5126
- function normalizeColumnsComponent(component) {
5127
- const cfg = component.props || {};
5128
- const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
5129
- const isLast = i === cols.length - 1;
5130
- if (!isLast && c.gap === void 0 && topGap !== void 0) {
5131
- return { ...c, gap: topGap };
5132
- }
5133
- return c;
5134
- });
5135
- const normalizeAutoWidth = (cols) => cols.map((c) => ({
5136
- ...c,
5137
- width: c.width === "auto" ? void 0 : c.width
5138
- }));
5139
- let columnsArray;
5140
- if (typeof cfg.columns === "number" && cfg.columns > 0) {
5141
- const n = cfg.columns;
5142
- const base = Array.from(
5143
- { length: n },
5144
- () => ({})
5145
- );
5146
- const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
5147
- columnsArray = applyTopLevelGap(base, topLevelGap);
5148
- } else if (Array.isArray(cfg.columns)) {
5149
- columnsArray = cfg.columns;
5150
- const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
5151
- columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
5152
- columnsArray = normalizeAutoWidth(columnsArray);
5153
- } else {
5154
- columnsArray = [{}];
5155
- }
5156
- const normalizedProps = {
5157
- columns: columnsArray
5158
- };
5159
- return {
5160
- ...component,
5161
- // Preserve any additional properties
5162
- name: "columns",
5163
- props: normalizedProps,
5164
- children: component.children ? component.children.map(normalizeComponent) : void 0
5165
- };
5166
- }
5167
- function normalizeSectionComponent(component) {
5168
- const props = component.props || {};
5169
- return {
5170
- ...component,
5171
- // Preserve ALL properties
5172
- props: {
5173
- ...props,
5174
- // Preserve ALL props properties
5175
- header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
5176
- footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
5177
- },
5178
- children: component.children ? component.children.map(normalizeComponent) : void 0
5179
- };
5180
- }
5181
- function normalizeDocument(document) {
5182
- if (document.name !== "docx") {
5183
- throw new Error(
5184
- 'Top-level document must be a docx component with name="docx"'
5185
- );
5186
- }
5187
- const normalized = normalizeComponent(document);
5188
- return [normalized];
5189
- }
5190
-
5191
5040
  // src/core/desugarExternals.ts
5192
5041
  import {
5193
5042
  clampVisualDpi as clampVisualDpi2,
@@ -6023,6 +5872,13 @@ init_themes();
6023
5872
  init_colorUtils();
6024
5873
  init_styleHelpers();
6025
5874
  var RIGHT_MARGIN_TAB_TWIPS = 9026 * 2;
5875
+ var STATISTIC_NUMBER_STYLE_ID = "StatisticNumber";
5876
+ var STATISTIC_DESCRIPTION_STYLE_ID = "StatisticDescription";
5877
+ var STATISTIC_SIZE_POINTS = {
5878
+ small: 20,
5879
+ medium: 28,
5880
+ large: 40
5881
+ };
6026
5882
  function isValidStyleName(name) {
6027
5883
  return [
6028
5884
  "normal",
@@ -6661,6 +6517,67 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6661
6517
  }
6662
6518
  };
6663
6519
  }
6520
+ function createStatisticStyles(theme) {
6521
+ const definitions = [
6522
+ {
6523
+ id: STATISTIC_NUMBER_STYLE_ID,
6524
+ name: "Statistic Number",
6525
+ font: "heading",
6526
+ sizePoints: STATISTIC_SIZE_POINTS.medium,
6527
+ color: theme.colors.primary,
6528
+ bold: true,
6529
+ // Space above separates the figure from whatever preceded it; none below,
6530
+ // because the description is the other half of the same block.
6531
+ spacing: { before: 12, after: 0 },
6532
+ next: STATISTIC_DESCRIPTION_STYLE_ID,
6533
+ // The figure and its caption are one unit; a page break between them
6534
+ // reads as a number with no label.
6535
+ keepNext: true
6536
+ },
6537
+ {
6538
+ id: STATISTIC_DESCRIPTION_STYLE_ID,
6539
+ name: "Statistic Description",
6540
+ font: "body",
6541
+ sizePoints: 10,
6542
+ color: theme.colors.textMuted,
6543
+ bold: false,
6544
+ spacing: { before: 0, after: 12 },
6545
+ next: "Normal",
6546
+ keepNext: false
6547
+ }
6548
+ ];
6549
+ return definitions.filter(
6550
+ (definition) => !(theme.styles && Object.prototype.hasOwnProperty.call(theme.styles, definition.id))
6551
+ ).map((definition) => {
6552
+ const fontProps = resolveFontProperties(theme, definition.font);
6553
+ return {
6554
+ id: definition.id,
6555
+ name: definition.name,
6556
+ basedOn: "Normal",
6557
+ next: definition.next,
6558
+ quickFormat: true,
6559
+ run: {
6560
+ fontFamily: fontProps.family || "Arial",
6561
+ // Pinned, not inherited: the whole point of the style is that the
6562
+ // figure is not body text, so a theme's body size must not decide it.
6563
+ sizeHalfPoints: definition.sizePoints * 2,
6564
+ color: { hex: resolveColor(definition.color, theme) },
6565
+ bold: definition.bold
6566
+ },
6567
+ paragraph: {
6568
+ spacing: {
6569
+ ...resolveSpacing(definition.spacing),
6570
+ ...irLineSpacing(fontProps.lineSpacing)
6571
+ },
6572
+ // Centred, matching the compiler's own alignment default — a KPI
6573
+ // reads as a block, and an author who wants otherwise says so on the
6574
+ // component, which overrides this on the paragraph.
6575
+ alignment: convertAlignment("center"),
6576
+ keepNext: definition.keepNext
6577
+ }
6578
+ };
6579
+ });
6580
+ }
6664
6581
 
6665
6582
  // src/ir/compiler.ts
6666
6583
  init_styleHelpers();
@@ -7971,6 +7888,7 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
7971
7888
  resourcesByHash: /* @__PURE__ */ new Map(),
7972
7889
  nextBookmarkId: CONTENT_BOOKMARK_ID_BASE + 1,
7973
7890
  bookmarkNames: /* @__PURE__ */ new Set(),
7891
+ usesStatistic: false,
7974
7892
  styleIds: /* @__PURE__ */ new Set([
7975
7893
  ...styles.paragraph.map((s) => s.id),
7976
7894
  ...styles.character.map((s) => s.id)
@@ -8019,7 +7937,17 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
8019
7937
  trackRevisions: structure.trackRevisions === true,
8020
7938
  ...noProofWords(structure.theme) ? { noProofWords: noProofWords(structure.theme) } : {}
8021
7939
  },
8022
- styles,
7940
+ // The statistic styles are appended rather than built with the rest: a
7941
+ // document that uses no statistic must keep the exact style set it had
7942
+ // before the component had styles, or every recorded golden moves for a
7943
+ // component the document does not contain.
7944
+ styles: ctx.usesStatistic ? {
7945
+ ...styles,
7946
+ paragraph: [
7947
+ ...styles.paragraph,
7948
+ ...createStatisticStyles(structure.theme)
7949
+ ]
7950
+ } : styles,
8023
7951
  numbering: ctx.numbering,
8024
7952
  resources: ctx.resources,
8025
7953
  sections,
@@ -8034,6 +7962,21 @@ function compileDocument(structure, layout, warnings = [], images = /* @__PURE__
8034
7962
  unsupported: ctx.unsupported
8035
7963
  };
8036
7964
  }
7965
+ var AFTER_TABLE_TWIPS = 120;
7966
+ var BODY_STYLE_IDS = /* @__PURE__ */ new Set([void 0, "Normal"]);
7967
+ function separateFromTables(blocks) {
7968
+ for (let i = 1; i < blocks.length; i += 1) {
7969
+ const block2 = blocks[i];
7970
+ if (blocks[i - 1].kind !== "table") continue;
7971
+ if (block2.kind !== "paragraph") continue;
7972
+ if (!BODY_STYLE_IDS.has(block2.styleId)) continue;
7973
+ if (block2.formatting?.spacing?.beforeTwips !== void 0) continue;
7974
+ block2.formatting = {
7975
+ ...block2.formatting,
7976
+ spacing: { ...block2.formatting?.spacing, beforeTwips: AFTER_TABLE_TWIPS }
7977
+ };
7978
+ }
7979
+ }
8037
7980
  function collectOutline(layout, warnings) {
8038
7981
  try {
8039
7982
  return collectDocumentOutline(layout.sections);
@@ -8110,6 +8053,7 @@ function compileSection(section2, index, ordinal, chrome, ctx) {
8110
8053
  })
8111
8054
  );
8112
8055
  });
8056
+ separateFromTables(children);
8113
8057
  const compiled = {
8114
8058
  id: `s${index}`,
8115
8059
  path: path4,
@@ -8880,35 +8824,83 @@ function compileToc(component, scope) {
8880
8824
  });
8881
8825
  return blocks;
8882
8826
  }
8827
+ var TREND_GLYPHS = {
8828
+ up: "\u25B2",
8829
+ down: "\u25BC",
8830
+ neutral: "\u2013"
8831
+ };
8883
8832
  function compileStatistic(component, scope) {
8884
- const { path: path4 } = scope;
8833
+ const { ctx, path: path4 } = scope;
8885
8834
  const props = component.props ?? {};
8886
8835
  const alignment2 = compileAlignment(props.alignment) ?? "center";
8836
+ ctx.usesStatistic = true;
8887
8837
  const spacing = props.spacing ? {
8888
8838
  ...props.spacing.before !== void 0 ? { beforeTwips: props.spacing.before } : {},
8889
8839
  ...props.spacing.after !== void 0 ? { afterTwips: props.spacing.after } : {}
8890
8840
  } : void 0;
8891
- const line = (text, styleId, suffix, formatting) => {
8892
- const value = normalizeUnicodeText(String(text ?? ""));
8893
- return {
8894
- kind: "paragraph",
8895
- id: `${scope.id}:${suffix}`,
8896
- path: `${path4}.${suffix}`,
8897
- styleId,
8898
- formatting,
8899
- // Nothing to say is not the same as saying nothing: an empty statistic
8900
- // line is a styled blank paragraph, with no run inside it at all.
8901
- children: value ? [{ kind: "text", text: value }] : []
8902
- };
8903
- };
8841
+ if (props.format !== void 0) {
8842
+ warnOnce(
8843
+ ctx,
8844
+ "statistic",
8845
+ 'statistic "format" is not implemented and was ignored \u2014 format the value yourself and pass the result as "number".',
8846
+ "W_STATISTIC_FORMAT_IGNORED"
8847
+ );
8848
+ }
8849
+ const text = (value) => normalizeUnicodeText(String(value ?? ""));
8850
+ const numberPoints = STATISTIC_SIZE_POINTS[String(props.size)] ?? STATISTIC_SIZE_POINTS.medium;
8851
+ const resized = numberPoints !== STATISTIC_SIZE_POINTS.medium;
8852
+ const suffixHalfPoints = Math.max(12, Math.round(numberPoints));
8853
+ const numberRuns = [];
8854
+ const numberText = text(props.number);
8855
+ if (numberText) {
8856
+ numberRuns.push({
8857
+ kind: "text",
8858
+ text: numberText,
8859
+ ...resized ? { formatting: { sizeHalfPoints: numberPoints * 2 } } : {}
8860
+ });
8861
+ }
8862
+ const unitText = text(props.unit);
8863
+ if (unitText) {
8864
+ numberRuns.push({
8865
+ kind: "text",
8866
+ text: unitText,
8867
+ formatting: { sizeHalfPoints: suffixHalfPoints }
8868
+ });
8869
+ }
8870
+ const glyph = typeof props.trend === "string" ? TREND_GLYPHS[props.trend] : void 0;
8871
+ const trendValue = text(props.trendValue);
8872
+ if (glyph || trendValue) {
8873
+ numberRuns.push({
8874
+ kind: "text",
8875
+ text: `${numberRuns.length > 0 ? " " : ""}${[glyph, trendValue].filter(Boolean).join(" ")}`,
8876
+ formatting: {
8877
+ sizeHalfPoints: suffixHalfPoints,
8878
+ color: irColor(resolveColor(ctx.theme.colors.textMuted, ctx.theme))
8879
+ }
8880
+ });
8881
+ }
8882
+ const line = (children, styleId, suffix, formatting) => ({
8883
+ kind: "paragraph",
8884
+ id: `${scope.id}:${suffix}`,
8885
+ path: `${path4}.${suffix}`,
8886
+ styleId,
8887
+ formatting,
8888
+ // Nothing to say is not the same as saying nothing: an empty statistic
8889
+ // line is a styled blank paragraph, with no run inside it at all.
8890
+ children
8891
+ });
8892
+ const descriptionText = text(props.description);
8904
8893
  return [
8905
- line(props.number, "StatisticNumber", "number", {
8894
+ line(numberRuns, STATISTIC_NUMBER_STYLE_ID, "number", {
8906
8895
  alignment: alignment2,
8907
8896
  ...spacing ? { spacing } : {}
8908
8897
  }),
8909
- line(props.description, "StatisticDescription", "description", {
8910
- alignment: alignment2
8911
- })
8898
+ line(
8899
+ descriptionText ? [{ kind: "text", text: descriptionText }] : [],
8900
+ STATISTIC_DESCRIPTION_STYLE_ID,
8901
+ "description",
8902
+ { alignment: alignment2 }
8903
+ )
8912
8904
  ];
8913
8905
  }
8914
8906
  function compileList(component, scope, referencePrefix = "list", explicitLevels) {
@@ -8981,12 +8973,12 @@ function compileList(component, scope, referencePrefix = "list", explicitLevels)
8981
8973
  }
8982
8974
  function itemSpacing(spacing, index, count) {
8983
8975
  const out = {};
8984
- if (index === 0 && spacing?.before) {
8976
+ if (index === 0 && spacing?.before !== void 0) {
8985
8977
  out.beforeTwips = pointsToTwips2(spacing.before);
8986
8978
  }
8987
- if (index === count - 1 && spacing?.after) {
8979
+ if (index === count - 1 && spacing?.after !== void 0) {
8988
8980
  out.afterTwips = pointsToTwips2(spacing.after);
8989
- } else if (spacing?.item) {
8981
+ } else if (spacing?.item !== void 0) {
8990
8982
  out.afterTwips = pointsToTwips2(spacing.item);
8991
8983
  }
8992
8984
  return out;
@@ -10457,7 +10449,9 @@ function warnOnce(ctx, component, message, code) {
10457
10449
  }
10458
10450
 
10459
10451
  // src/renderers/registry.ts
10460
- import { RendererRegistry } from "@json-to-office/shared/rendering";
10452
+ import {
10453
+ RendererRegistry
10454
+ } from "@json-to-office/shared/rendering";
10461
10455
 
10462
10456
  // src/renderers/types.ts
10463
10457
  import {
@@ -10483,108 +10477,16 @@ function resolveDocxRenderer(id) {
10483
10477
  function docxRendererIds() {
10484
10478
  return registry.ids();
10485
10479
  }
10480
+ function docxRendererStatuses() {
10481
+ return registry.statuses();
10482
+ }
10486
10483
  function isDocxRendererId(value) {
10487
10484
  return registry.has(value);
10488
10485
  }
10489
10486
 
10490
10487
  // src/core/generateFromIr.ts
10491
10488
  init_packageDocument();
10492
-
10493
- // src/core/generationContext.ts
10494
- import { applyExportMode } from "@json-to-office/shared";
10495
-
10496
- // src/styles/theme-resolver.ts
10497
- init_themes();
10498
- function resolveBuiltInTheme(themeName, options = {}) {
10499
- if (!hasTheme(themeName)) {
10500
- const available = [
10501
- ...getThemeNames(),
10502
- ...Object.keys(options.customThemes ?? {})
10503
- ].sort();
10504
- options.warnings?.push({
10505
- component: "theme",
10506
- message: `Theme "${themeName}" not found; falling back to "minimal". Available themes: ${available.join(", ")}.`,
10507
- severity: "warning",
10508
- context: { code: "theme_not_found", requested: themeName, available }
10509
- });
10510
- }
10511
- return getThemeWithFallback(themeName);
10512
- }
10513
-
10514
- // src/themes/overrides.ts
10515
- function applyThemeOverrides(theme, overrides) {
10516
- if (!overrides) return theme;
10517
- const fonts = { ...theme.fonts };
10518
- for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
10519
- const key = role;
10520
- fonts[key] = { ...theme.fonts[key], ...def };
10521
- }
10522
- const styles = { ...theme.styles ?? {} };
10523
- for (const [name, def] of Object.entries(overrides.styles ?? {})) {
10524
- styles[name] = { ...styles[name], ...def };
10525
- }
10526
- return {
10527
- ...theme,
10528
- colors: { ...theme.colors, ...overrides.colors ?? {} },
10529
- fonts,
10530
- ...Object.keys(styles).length > 0 && { styles }
10531
- };
10532
- }
10533
-
10534
- // src/core/generationContext.ts
10535
- function defaultNamedThemeLookup(themeName, customThemes, warnings) {
10536
- if (customThemes) {
10537
- if (customThemes[themeName]) return customThemes[themeName];
10538
- const themeNameLower = themeName.toLowerCase();
10539
- const matchingThemeKey = Object.keys(customThemes).find(
10540
- (key) => key.toLowerCase() === themeNameLower
10541
- );
10542
- if (matchingThemeKey) return customThemes[matchingThemeKey];
10543
- return resolveBuiltInTheme(themeName, { customThemes, warnings });
10544
- }
10545
- return resolveBuiltInTheme(themeName, { warnings });
10546
- }
10547
- function resolveThemeContext(documentIn, options = {}) {
10548
- const { customThemes, fonts, warnings, resolveNamedTheme } = options;
10549
- if (documentIn.props === null) {
10550
- throw new Error(
10551
- "Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
10552
- );
10553
- }
10554
- const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
10555
- const authoredThemeName = document.props.theme || void 0;
10556
- const baseThemeName = authoredThemeName ?? "minimal";
10557
- let theme = resolveNamedTheme ? resolveNamedTheme(
10558
- baseThemeName,
10559
- warnings,
10560
- authoredThemeName !== void 0
10561
- ) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
10562
- const themeOverrides = document.props.themeOverrides;
10563
- if (themeOverrides) {
10564
- theme = applyThemeOverrides(theme, themeOverrides);
10565
- }
10566
- const mode = applyExportMode({ doc: document, theme, fonts });
10567
- for (const w of mode.warnings) {
10568
- if (warnings) {
10569
- warnings.push({
10570
- component: "fontRegistry",
10571
- message: w.message,
10572
- severity: "warning",
10573
- context: { code: w.code }
10574
- });
10575
- } else {
10576
- console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
10577
- }
10578
- }
10579
- return {
10580
- document: mode.doc,
10581
- theme: mode.theme,
10582
- themeName: baseThemeName
10583
- };
10584
- }
10585
-
10586
- // src/core/generateFromIr.ts
10587
- init_generationContext();
10489
+ init_generationContext();
10588
10490
 
10589
10491
  // src/core/layout.ts
10590
10492
  init_styles();
@@ -11037,8 +10939,7 @@ function resolveComponentTree(components, theme) {
11037
10939
  }
11038
10940
 
11039
10941
  // src/core/structure.ts
11040
- async function processDocument(document, theme, themeName, generationDate) {
11041
- const metadata = createDocumentMetadata(document.props, generationDate);
10942
+ function resolveDocumentTree(document, theme) {
11042
10943
  const docDefaults = document.props.componentDefaults;
11043
10944
  const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
11044
10945
  const docNoProofWords = document.props.noProofWords;
@@ -11052,6 +10953,14 @@ async function processDocument(document, theme, themeName, generationDate) {
11052
10953
  },
11053
10954
  ...mergedNoProofWords && { noProofWords: mergedNoProofWords }
11054
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;
11055
10964
  const context = createRenderContext(
11056
10965
  {
11057
10966
  metadata,
@@ -11062,11 +10971,7 @@ async function processDocument(document, theme, themeName, generationDate) {
11062
10971
  effectiveTheme,
11063
10972
  themeName
11064
10973
  );
11065
- const resolvedChildren = resolveComponentTree(
11066
- document.children || [],
11067
- effectiveTheme
11068
- );
11069
- const sections = await extractSections(resolvedChildren, context);
10974
+ const sections = await extractSections(resolved.children, context);
11070
10975
  return {
11071
10976
  metadata,
11072
10977
  sections,
@@ -11166,6 +11071,419 @@ function createRenderContext(document, theme, themeName) {
11166
11071
  };
11167
11072
  }
11168
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
+
11169
11487
  // src/core/generateFromIr.ts
11170
11488
  var UncompiledComponentError = class extends Error {
11171
11489
  code = "UNCOMPILED_COMPONENT";
@@ -11193,11 +11511,14 @@ async function compileDocumentToIr(document, options = {}, collector) {
11193
11511
  );
11194
11512
  }
11195
11513
  async function compileDocumentScoped(document, options, warnings) {
11196
- const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
11514
+ const prepared = options.prepared ?? prepareDocxQualityDocument(document, {
11197
11515
  customThemes: options.customThemes,
11198
11516
  fonts: options.fonts,
11199
- warnings
11517
+ warnings,
11518
+ context: options.context,
11519
+ renderer: options.renderer
11200
11520
  });
11521
+ const { context } = prepared.model;
11201
11522
  const hasVisual = collectVisualProps(context.document).length > 0;
11202
11523
  const resolvedFonts = await resolveDocumentFonts(
11203
11524
  context.document,
@@ -11213,9 +11534,10 @@ async function compileDocumentScoped(document, options, warnings) {
11213
11534
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
11214
11535
  ...visualFonts.length > 0 ? { visualFonts } : {}
11215
11536
  });
11216
- const structure = await processDocument(
11537
+ const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
11538
+ const structure = await processResolvedDocument(
11217
11539
  desugared,
11218
- context.theme,
11540
+ resolved,
11219
11541
  context.themeName,
11220
11542
  resolveGenerationDate(options)
11221
11543
  );
@@ -11380,7 +11702,8 @@ async function generateBufferWithWarnings(jsonConfig, options) {
11380
11702
  ...options?.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
11381
11703
  ...options?.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
11382
11704
  ...options?.generatedAt !== void 0 ? { generatedAt: options.generatedAt } : {},
11383
- ...renderer ? { renderer } : {}
11705
+ ...renderer ? { renderer } : {},
11706
+ ...options?.prepared ? { prepared: options.prepared } : {}
11384
11707
  });
11385
11708
  }
11386
11709
  function parseDocument(jsonConfig) {
@@ -11521,6 +11844,411 @@ async function rasterizeVisual(obj, ctx) {
11521
11844
  });
11522
11845
  }
11523
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
+
11524
12252
  // src/utils/warningsDocument.ts
11525
12253
  function generateWarningsDocument(warnings) {
11526
12254
  if (!warnings || warnings.length === 0) {
@@ -12447,18 +13175,24 @@ export {
12447
13175
  ComponentValidationError2 as ComponentValidationError,
12448
13176
  DocumentGenerator as CoreDocumentGenerator,
12449
13177
  DEFAULT_DOCX_RENDERER_ID,
13178
+ DOCX_DEFAULT_QUALITY_PROFILE,
13179
+ DOCX_QUALITY_PROFILES,
13180
+ DOCX_QUALITY_RULES,
12450
13181
  ThemeFileError,
12451
13182
  ThemeParseError,
12452
13183
  ThemeValidationError,
12453
13184
  UncompiledComponentError,
12454
13185
  UnknownPreservedComponentError,
13186
+ analyzeDocxQuality,
12455
13187
  cleanComponentProps,
12456
13188
  corporateTheme,
12457
13189
  createComponent,
12458
13190
  createDocumentGenerator,
12459
13191
  createMinimalTheme,
12460
13192
  createVersion,
13193
+ docxQualityEngine,
12461
13194
  docxRendererIds,
13195
+ docxRendererStatuses,
12462
13196
  examples,
12463
13197
  exportPluginSchema,
12464
13198
  exportThemeToJson,
@@ -12499,7 +13233,9 @@ export {
12499
13233
  mergeSchemas,
12500
13234
  minimalTheme,
12501
13235
  modernTheme,
13236
+ prepareDocxQualityDocument,
12502
13237
  resetVisualPrepassStats,
13238
+ resolveDocxQualityProfile,
12503
13239
  runExample,
12504
13240
  themes,
12505
13241
  validateComponentProps,