@json-to-office/core-docx 3.3.0 → 4.1.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.
@@ -193,6 +193,7 @@ var init_minimal_docx_theme = __esm({
193
193
  name: "minimal",
194
194
  displayName: "Minimal Clean",
195
195
  description: "A quiet, warm minimal theme \u2014 Calibri throughout, sage-green ink on ivory neutrals, a large tracked-tight bold title, wide margins and hairline rules",
196
+ whenToUse: "Quiet documents where the type carries the tone: notes, briefs, drafts and internal papers that want warmth without decoration.",
196
197
  version: "4.0.0",
197
198
  colors: {
198
199
  primary: "#2B302B",
@@ -382,6 +383,7 @@ var init_devportal_docx_theme = __esm({
382
383
  name: "devportal",
383
384
  displayName: "Field Editorial",
384
385
  description: "A compact editorial theme \u2014 Helvetica in near-black ink with a burnt-orange accent, tight condensed titles, letterspaced labels, warm tinted fills and hairline rules",
386
+ whenToUse: "Developer and product documentation, API guides and technical handbooks: dense text, code and tables that need compact, letterspaced structure.",
385
387
  version: "4.0.0",
386
388
  colors: {
387
389
  primary: "#12191F",
@@ -580,6 +582,7 @@ var init_vermilion_docx_theme = __esm({
580
582
  name: "vermilion",
581
583
  displayName: "Vermilion Editorial",
582
584
  description: "Poster-red editorial system \u2014 vermilion display headings, ink text, warm creams and hairline rules",
585
+ whenToUse: "Editorial and annual-report work that wants a strong red display voice: covers, feature-style sections, reports built around a few large headings.",
583
586
  version: "1.0.0",
584
587
  colors: {
585
588
  primary: "#282829",
@@ -798,6 +801,7 @@ var init_consulting_docx_theme = __esm({
798
801
  name: "consulting",
799
802
  displayName: "Consulting House",
800
803
  description: "The house style for client and technical reports \u2014 near-black ink, three greys and one deep-blue accent, Calibri body under Arial headings, hairline rules, no fills behind body text, safe fonts only",
804
+ whenToUse: "Client and technical reports, memos and anything a client will read as the house's own work; the default choice for a report unless the brief names a brand.",
801
805
  version: "1.0.0",
802
806
  colors: {
803
807
  primary: "#1A1F26",
@@ -12132,8 +12136,10 @@ import {
12132
12136
  } from "@json-to-office/quality";
12133
12137
  import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
12134
12138
  import {
12139
+ designCanvas as designCanvas2,
12135
12140
  designColors as designColors2,
12136
- resolveDesignColor as resolveDesignColor2
12141
+ resolveDesignColor as resolveDesignColor2,
12142
+ typeScaleSizes
12137
12143
  } from "@json-to-office/shared";
12138
12144
 
12139
12145
  // src/json/normalizer.ts
@@ -12476,9 +12482,176 @@ async function expandBlocksWithPlugins(document, theme, plugins, render, preserv
12476
12482
  init_styleHelpers();
12477
12483
  init_defaults();
12478
12484
  init_widthUtils();
12485
+
12486
+ // src/quality/text-inventory.ts
12479
12487
  function asRecord(value) {
12480
12488
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
12481
12489
  }
12490
+ function twipsToPt(value) {
12491
+ return typeof value === "number" && Number.isFinite(value) ? value / 20 : void 0;
12492
+ }
12493
+ function frameOf(props) {
12494
+ const widthPt = twipsToPt(asRecord(props.floating)?.width);
12495
+ return widthPt === void 0 ? void 0 : { widthPt };
12496
+ }
12497
+ function collectDocxTextInventory(children, basePath = "/children") {
12498
+ const entries = [];
12499
+ const add = (path3, text, role, extra = {}) => {
12500
+ if (typeof text !== "string" || text.trim() === "") return;
12501
+ entries.push({
12502
+ id: `docx:text:${path3}`,
12503
+ kind: "docx/text",
12504
+ path: path3,
12505
+ text,
12506
+ role,
12507
+ order: entries.length,
12508
+ ...extra
12509
+ });
12510
+ };
12511
+ const visitCell = (cell, path3, role, inherited) => {
12512
+ const cellRole = inherited.repeats ? "chrome" : role;
12513
+ const extra = inherited.repeats ? { repeats: true } : {};
12514
+ if (typeof cell === "string") {
12515
+ add(path3, cell, cellRole, extra);
12516
+ return;
12517
+ }
12518
+ const rec = asRecord(cell);
12519
+ if (!rec) return;
12520
+ if ("content" in rec) {
12521
+ if (typeof rec.content === "string")
12522
+ add(`${path3}/content`, rec.content, cellRole, extra);
12523
+ else {
12524
+ const inner = asRecord(rec.content);
12525
+ if (inner) visitNode(inner, `${path3}/content`, inherited);
12526
+ }
12527
+ return;
12528
+ }
12529
+ if (typeof rec.name === "string") visitNode(rec, path3, inherited);
12530
+ };
12531
+ const visitNode = (node, path3, inherited) => {
12532
+ if (node.enabled === false) return;
12533
+ const props = asRecord(node.props) ?? {};
12534
+ const name = typeof node.name === "string" ? node.name : "";
12535
+ const extra = {
12536
+ ...inherited.repeats && { repeats: true }
12537
+ };
12538
+ switch (name) {
12539
+ case "heading": {
12540
+ if (inherited.repeats) {
12541
+ add(`${path3}/props/text`, props.text, "chrome", extra);
12542
+ break;
12543
+ }
12544
+ const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
12545
+ add(`${path3}/props/text`, props.text, "heading", { ...extra, level });
12546
+ break;
12547
+ }
12548
+ case "paragraph": {
12549
+ const frame = frameOf(props);
12550
+ add(
12551
+ `${path3}/props/text`,
12552
+ props.text,
12553
+ inherited.repeats ? "chrome" : "body",
12554
+ {
12555
+ ...extra,
12556
+ ...frame && { frame }
12557
+ }
12558
+ );
12559
+ break;
12560
+ }
12561
+ case "list": {
12562
+ if (Array.isArray(props.items)) {
12563
+ props.items.forEach((item, index) => {
12564
+ const itemPath = `${path3}/props/items/${index}`;
12565
+ if (typeof item === "string")
12566
+ add(itemPath, item, "list-item", extra);
12567
+ else {
12568
+ const rec = asRecord(item);
12569
+ if (rec) add(`${itemPath}/text`, rec.text, "list-item", extra);
12570
+ }
12571
+ });
12572
+ }
12573
+ break;
12574
+ }
12575
+ case "table": {
12576
+ const columns = Array.isArray(props.columns) ? props.columns.map(asRecord) : [];
12577
+ columns.forEach((column, columnIndex) => {
12578
+ if (!column) return;
12579
+ visitCell(
12580
+ column.header,
12581
+ `${path3}/props/columns/${columnIndex}/header`,
12582
+ "table-header",
12583
+ inherited
12584
+ );
12585
+ });
12586
+ const rows = Math.max(
12587
+ 0,
12588
+ ...columns.map(
12589
+ (column) => Array.isArray(column?.cells) ? column.cells.length : 0
12590
+ )
12591
+ );
12592
+ for (let row = 0; row < rows; row++) {
12593
+ columns.forEach((column, columnIndex) => {
12594
+ if (!column || !Array.isArray(column.cells)) return;
12595
+ if (row >= column.cells.length) return;
12596
+ visitCell(
12597
+ column.cells[row],
12598
+ `${path3}/props/columns/${columnIndex}/cells/${row}`,
12599
+ "table-cell",
12600
+ inherited
12601
+ );
12602
+ });
12603
+ }
12604
+ break;
12605
+ }
12606
+ case "statistic": {
12607
+ add(`${path3}/props/number`, props.number, "statistic", extra);
12608
+ add(`${path3}/props/description`, props.description, "statistic", extra);
12609
+ break;
12610
+ }
12611
+ case "image":
12612
+ case "chart":
12613
+ case "highcharts":
12614
+ case "visual":
12615
+ case "visual-native": {
12616
+ add(`${path3}/props/caption`, props.caption, "caption", extra);
12617
+ break;
12618
+ }
12619
+ case "section": {
12620
+ for (const part of ["header", "footer"]) {
12621
+ const components = props[part];
12622
+ if (!Array.isArray(components)) continue;
12623
+ components.forEach((component, index) => {
12624
+ const rec = asRecord(component);
12625
+ if (rec) {
12626
+ visitNode(rec, `${path3}/props/${part}/${index}`, {
12627
+ repeats: true
12628
+ });
12629
+ }
12630
+ });
12631
+ }
12632
+ break;
12633
+ }
12634
+ default:
12635
+ break;
12636
+ }
12637
+ if (Array.isArray(node.children)) {
12638
+ node.children.forEach((child, index) => {
12639
+ const rec = asRecord(child);
12640
+ if (rec) visitNode(rec, `${path3}/children/${index}`, inherited);
12641
+ });
12642
+ }
12643
+ };
12644
+ children.forEach((child, index) => {
12645
+ const rec = asRecord(child);
12646
+ if (rec) visitNode(rec, `${basePath}/${index}`, {});
12647
+ });
12648
+ return entries;
12649
+ }
12650
+
12651
+ // src/quality/facts.ts
12652
+ function asRecord2(value) {
12653
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
12654
+ }
12482
12655
  function pageBox(theme, themeName, pageOverride) {
12483
12656
  const page = createSectionProperties(
12484
12657
  getColumnSettings("single"),
@@ -12504,7 +12677,7 @@ function tableFact(props, path3, availableWidthTwips) {
12504
12677
  let percentSum = 0;
12505
12678
  const explicitWidths = [];
12506
12679
  columns.forEach((column, index) => {
12507
- const width = asRecord(column)?.width;
12680
+ const width = asRecord2(column)?.width;
12508
12681
  if (typeof width === "number" && Number.isFinite(width)) {
12509
12682
  hasExplicitWidth = true;
12510
12683
  explicitWidths.push({ index, width });
@@ -12585,7 +12758,7 @@ function chartFact(node, props, path3, paletteTokens) {
12585
12758
  const categoryCount = Math.max(
12586
12759
  0,
12587
12760
  ...series.map((entry) => {
12588
- const record = asRecord(entry);
12761
+ const record = asRecord2(entry);
12589
12762
  const labels = Array.isArray(record?.labels) ? record.labels.length : 0;
12590
12763
  const values = Array.isArray(record?.values) ? record.values.length : 0;
12591
12764
  return Math.max(labels, values);
@@ -12627,8 +12800,8 @@ function statesOwnBorders(authored) {
12627
12800
  return true;
12628
12801
  }
12629
12802
  const layers = [
12630
- asRecord(authored.cellDefaults),
12631
- asRecord(authored.headerCellDefaults)
12803
+ asRecord2(authored.cellDefaults),
12804
+ asRecord2(authored.headerCellDefaults)
12632
12805
  ];
12633
12806
  if (layers.some(
12634
12807
  (layer) => layer?.borderSize !== void 0 || layer?.borderColor !== void 0
@@ -12637,8 +12810,8 @@ function statesOwnBorders(authored) {
12637
12810
  }
12638
12811
  const columns = Array.isArray(authored.columns) ? authored.columns : [];
12639
12812
  return columns.some((column) => {
12640
- const record = asRecord(column);
12641
- const defaults = asRecord(record?.cellDefaults);
12813
+ const record = asRecord2(column);
12814
+ const defaults = asRecord2(record?.cellDefaults);
12642
12815
  return defaults?.borderSize !== void 0 || defaults?.borderColor !== void 0;
12643
12816
  });
12644
12817
  }
@@ -12662,7 +12835,7 @@ function tableDesignFact(props, path3, theme, themeName, authored) {
12662
12835
  );
12663
12836
  const columns = authoredColumns.map(
12664
12837
  (column, index) => {
12665
- const authoredColumn = asRecord(column) ?? {};
12838
+ const authoredColumn = asRecord2(column) ?? {};
12666
12839
  const cells = Array.isArray(authoredColumn.cells) ? authoredColumn.cells : [];
12667
12840
  const alignments = /* @__PURE__ */ new Set();
12668
12841
  const values = [];
@@ -12681,11 +12854,11 @@ function tableDesignFact(props, path3, theme, themeName, authored) {
12681
12854
  },
12682
12855
  values,
12683
12856
  alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
12684
- hasCellDefaults: asRecord(authoredColumn.cellDefaults) !== void 0,
12685
- hasHeader: asRecord(authoredColumn.header) !== void 0,
12857
+ hasCellDefaults: asRecord2(authoredColumn.cellDefaults) !== void 0,
12858
+ hasHeader: asRecord2(authoredColumn.header) !== void 0,
12686
12859
  generated: false,
12687
12860
  cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
12688
- const alignment2 = asRecord(cell)?.horizontalAlignment;
12861
+ const alignment2 = asRecord2(cell)?.horizontalAlignment;
12689
12862
  return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
12690
12863
  })
12691
12864
  };
@@ -12706,7 +12879,7 @@ function finiteNumber(value) {
12706
12879
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12707
12880
  }
12708
12881
  function lineHeightPt(fontSizePt, spacing) {
12709
- const rule = asRecord(spacing);
12882
+ const rule = asRecord2(spacing);
12710
12883
  const value = finiteNumber(rule?.value);
12711
12884
  switch (rule?.type) {
12712
12885
  // `exactly` is an absolute line box, and display type routinely sets it
@@ -12723,7 +12896,7 @@ function lineHeightPt(fontSizePt, spacing) {
12723
12896
  }
12724
12897
  }
12725
12898
  function characterSpacingPt(spacing) {
12726
- const rule = asRecord(spacing);
12899
+ const rule = asRecord2(spacing);
12727
12900
  const value = finiteNumber(rule?.value);
12728
12901
  if (rule === void 0 || value === void 0) return 0;
12729
12902
  return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
@@ -12737,7 +12910,7 @@ function nodeAtPointer(root, pointer) {
12737
12910
  current = current[index];
12738
12911
  continue;
12739
12912
  }
12740
- const record = asRecord(current);
12913
+ const record = asRecord2(current);
12741
12914
  if (!record) return void 0;
12742
12915
  current = record[token];
12743
12916
  }
@@ -12753,10 +12926,10 @@ function styleKey(node, props) {
12753
12926
  return typeof props.themeStyle === "string" && props.themeStyle !== "" ? props.themeStyle : "normal";
12754
12927
  }
12755
12928
  function effectiveFontSize(node, props, typography) {
12756
- const authored = finiteNumber(asRecord(props.font)?.size);
12929
+ const authored = finiteNumber(asRecord2(props.font)?.size);
12757
12930
  if (authored !== void 0) return { fontSizePt: authored, authored: true };
12758
12931
  const { styles, theme } = typography;
12759
- const style = asRecord(styles[styleKey(node, props)]) ?? asRecord(styles.normal);
12932
+ const style = asRecord2(styles[styleKey(node, props)]) ?? asRecord2(styles.normal);
12760
12933
  const stated = finiteNumber(style?.size);
12761
12934
  if (stated !== void 0) return { fontSizePt: stated, authored: false };
12762
12935
  const reference = style?.font === "heading" || style?.font === "body" || style?.font === "mono" || style?.font === "light" ? style.font : void 0;
@@ -12766,11 +12939,11 @@ function effectiveFontSize(node, props, typography) {
12766
12939
  function pinnedLineBox(props, path3) {
12767
12940
  const candidates = [
12768
12941
  [props.lineSpacing, `${path3}/props/lineSpacing`],
12769
- [asRecord(props.font)?.lineSpacing, `${path3}/props/font/lineSpacing`]
12942
+ [asRecord2(props.font)?.lineSpacing, `${path3}/props/font/lineSpacing`]
12770
12943
  ];
12771
12944
  for (const [spacing, pointer] of candidates) {
12772
12945
  if (spacing === void 0) continue;
12773
- const rule = asRecord(spacing);
12946
+ const rule = asRecord2(spacing);
12774
12947
  if (rule?.type !== "exactly") return void 0;
12775
12948
  const lineBoxPt = finiteNumber(rule.value);
12776
12949
  return lineBoxPt === void 0 ? void 0 : { lineBoxPt, pointer };
@@ -12796,8 +12969,8 @@ function lineBoxFact(node, props, path3, typography, authored) {
12796
12969
  };
12797
12970
  }
12798
12971
  function frameSignature(floating) {
12799
- const horizontal = asRecord(floating.horizontalPosition);
12800
- const vertical = asRecord(floating.verticalPosition);
12972
+ const horizontal = asRecord2(floating.horizontalPosition);
12973
+ const vertical = asRecord2(floating.verticalPosition);
12801
12974
  return JSON.stringify([
12802
12975
  floating.width ?? null,
12803
12976
  floating.height ?? null,
@@ -12807,24 +12980,24 @@ function frameSignature(floating) {
12807
12980
  vertical?.offset ?? null,
12808
12981
  vertical?.relative ?? null,
12809
12982
  vertical?.align ?? null,
12810
- asRecord(floating.wrap)?.type ?? null
12983
+ asRecord2(floating.wrap)?.type ?? null
12811
12984
  ]);
12812
12985
  }
12813
12986
  function frameTextFact(props, path3, page, frameChainId, flowIndex) {
12814
- const floating = asRecord(props.floating);
12987
+ const floating = asRecord2(props.floating);
12815
12988
  const frameWidthTwips = finiteNumber(floating?.width);
12816
12989
  if (frameWidthTwips === void 0) return void 0;
12817
12990
  const text = typeof props.text === "string" ? props.text : "";
12818
12991
  if (text.trim() === "") return void 0;
12819
- const font = asRecord(props.font);
12992
+ const font = asRecord2(props.font);
12820
12993
  const fontSizePt = finiteNumber(font?.size);
12821
12994
  if (fontSizePt === void 0) return void 0;
12822
12995
  const longestWord = text.split(/\s+/).reduce(
12823
12996
  (longest, word) => word.length > longest.length ? word : longest,
12824
12997
  ""
12825
12998
  );
12826
- const horizontal = asRecord(floating?.horizontalPosition);
12827
- const vertical = asRecord(floating?.verticalPosition);
12999
+ const horizontal = asRecord2(floating?.horizontalPosition);
13000
+ const vertical = asRecord2(floating?.verticalPosition);
12828
13001
  const offsetX = finiteNumber(horizontal?.offset);
12829
13002
  const offsetY = finiteNumber(vertical?.offset);
12830
13003
  const absolutelyPinned = horizontal?.offset !== void 0 || vertical?.offset !== void 0;
@@ -12895,7 +13068,7 @@ function svgTextFacts(props, path3) {
12895
13068
  return facts;
12896
13069
  }
12897
13070
  function walkActive(node, path3, page, visit) {
12898
- const rec = asRecord(node);
13071
+ const rec = asRecord2(node);
12899
13072
  if (!rec || rec.enabled === false) return;
12900
13073
  visit(rec, path3, page);
12901
13074
  const children = Array.isArray(rec.children) ? rec.children : [];
@@ -12903,6 +13076,93 @@ function walkActive(node, path3, page, visit) {
12903
13076
  (child, index) => walkActive(child, `${path3}/children/${index}`, page, visit)
12904
13077
  );
12905
13078
  }
13079
+ function textSizeFact(node, props, path3, typography, role) {
13080
+ const size = effectiveFontSize(node, props, typography);
13081
+ if (size === void 0) return void 0;
13082
+ return {
13083
+ id: `docx:text-size:${path3}`,
13084
+ kind: "docx/text-size",
13085
+ path: path3,
13086
+ role: role ?? styleKey(node, props),
13087
+ fontSizePt: size.fontSizePt,
13088
+ authored: size.authored,
13089
+ ...size.authored && { sizePath: `${path3}/props/font/size` }
13090
+ };
13091
+ }
13092
+ function tableTextSizeFacts(props, authored, path3, typography, page) {
13093
+ const facts = [];
13094
+ const sizeOf = (holder) => finiteNumber(asRecord2(asRecord2(holder)?.font)?.size);
13095
+ const authoredSize = (holder, pointer, role) => {
13096
+ const size = sizeOf(holder);
13097
+ if (size === void 0) return;
13098
+ facts.push({
13099
+ id: `docx:text-size:${pointer}`,
13100
+ kind: "docx/text-size",
13101
+ path: pointer,
13102
+ role,
13103
+ fontSizePt: size,
13104
+ authored: true,
13105
+ sizePath: `${pointer}/font/size`
13106
+ });
13107
+ };
13108
+ const cellContent = (holder, pointer, role) => {
13109
+ const content = asRecord2(holder)?.content;
13110
+ if (asRecord2(content) === void 0) return;
13111
+ walkActive(content, `${pointer}/content`, page, (node, nodePath) => {
13112
+ if (node.name !== "paragraph" && node.name !== "heading") return;
13113
+ const nodeProps = asRecord2(node.props) ?? {};
13114
+ if (typeof nodeProps.text !== "string" || nodeProps.text.trim() === "")
13115
+ return;
13116
+ const fact = textSizeFact(node, nodeProps, nodePath, typography, role);
13117
+ if (fact) facts.push(fact);
13118
+ });
13119
+ };
13120
+ if (authored) {
13121
+ authoredSize(
13122
+ authored.cellDefaults,
13123
+ `${path3}/props/cellDefaults`,
13124
+ "tableCell"
13125
+ );
13126
+ authoredSize(
13127
+ authored.headerCellDefaults,
13128
+ `${path3}/props/headerCellDefaults`,
13129
+ "tableHeader"
13130
+ );
13131
+ const columns = Array.isArray(authored.columns) ? authored.columns : [];
13132
+ columns.forEach((column, index) => {
13133
+ const record = asRecord2(column) ?? {};
13134
+ const columnPath = `${path3}/props/columns/${index}`;
13135
+ authoredSize(
13136
+ record.cellDefaults,
13137
+ `${columnPath}/cellDefaults`,
13138
+ "tableCell"
13139
+ );
13140
+ authoredSize(record.header, `${columnPath}/header`, "tableHeader");
13141
+ cellContent(record.header, `${columnPath}/header`, "tableHeader");
13142
+ const cells = Array.isArray(record.cells) ? record.cells : [];
13143
+ cells.forEach((cell, cellIndex) => {
13144
+ authoredSize(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
13145
+ cellContent(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
13146
+ });
13147
+ });
13148
+ }
13149
+ for (const [key, role] of [
13150
+ ["cellDefaults", "tableCell"],
13151
+ ["headerCellDefaults", "tableHeader"]
13152
+ ]) {
13153
+ const size = sizeOf(props[key]);
13154
+ if (size === void 0) continue;
13155
+ facts.push({
13156
+ id: `docx:text-size:${path3}:${role}`,
13157
+ kind: "docx/text-size",
13158
+ path: path3,
13159
+ role,
13160
+ fontSizePt: size,
13161
+ authored: false
13162
+ });
13163
+ }
13164
+ return facts;
13165
+ }
12906
13166
  function prepareDocxQualityDocument(document, options = {}) {
12907
13167
  const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
12908
13168
  customThemes: options.customThemes,
@@ -12967,11 +13227,48 @@ function prepareDocxQualityDocument(document, options = {}) {
12967
13227
  let inherited = {};
12968
13228
  topLevel.forEach((node, index) => {
12969
13229
  if (node?.name !== "section") return;
12970
- const props = asRecord(node.props) ?? {};
13230
+ const props = asRecord2(node.props) ?? {};
12971
13231
  const part = (kind) => props[kind] === "linkToPrevious" ? inherited[kind] : props[kind];
12972
13232
  const header = part("header");
12973
13233
  const footer = part("footer");
12974
13234
  inherited = { header, footer };
13235
+ for (const kind of ["header", "footer"]) {
13236
+ if (!Array.isArray(props[kind])) continue;
13237
+ const drawnByBlock = !Array.isArray(
13238
+ asRecord2(
13239
+ asRecord2(
13240
+ (Array.isArray(themed.document.children) ? themed.document.children : [])[index]
13241
+ )?.props
13242
+ )?.[kind]
13243
+ );
13244
+ const part2 = props[kind];
13245
+ const visitChrome = (child, childPath) => {
13246
+ if (child.name !== "paragraph" && child.name !== "heading") return;
13247
+ const childProps = asRecord2(child.props) ?? {};
13248
+ if (typeof childProps.text !== "string" || childProps.text.trim() === "")
13249
+ return;
13250
+ const fact = textSizeFact(
13251
+ child,
13252
+ childProps,
13253
+ childPath,
13254
+ typography,
13255
+ kind
13256
+ );
13257
+ if (fact)
13258
+ addFact({
13259
+ ...fact,
13260
+ generated: drawnByBlock || authoredPath(childPath) !== childPath
13261
+ });
13262
+ };
13263
+ part2.forEach(
13264
+ (child, childIndex) => walkActive(
13265
+ child,
13266
+ `/children/${index}/props/${kind}/${childIndex}`,
13267
+ basePage,
13268
+ visitChrome
13269
+ )
13270
+ );
13271
+ }
12975
13272
  addFact({
12976
13273
  id: `docx:section-chrome:${index}`,
12977
13274
  kind: "docx/section-chrome",
@@ -13004,13 +13301,35 @@ function prepareDocxQualityDocument(document, options = {}) {
13004
13301
  const paletteTokens = resolved.theme.palette?.chart?.map(
13005
13302
  (value) => `#${resolveDesignColor2(value, visualColors)}`
13006
13303
  ) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
13007
- const authoredPropsAt = (pointer) => asRecord(asRecord(nodeAtPointer(context.document, pointer))?.props);
13304
+ const authoredPropsAt = (pointer) => asRecord2(asRecord2(nodeAtPointer(context.document, pointer))?.props);
13305
+ const roleSizesPt = {};
13306
+ for (const key of Object.keys(typography.styles)) {
13307
+ const size = effectiveFontSize(
13308
+ { name: "paragraph" },
13309
+ { themeStyle: key },
13310
+ typography
13311
+ );
13312
+ if (size) roleSizesPt[key] = size.fontSizePt;
13313
+ }
13314
+ const scale = resolved.theme.typography?.scale?.[designCanvas2("docx", resolved.theme.page?.size)];
13315
+ const typeScalePt = [
13316
+ .../* @__PURE__ */ new Set([
13317
+ ...Object.values(roleSizesPt),
13318
+ ...["heading", "body", "mono", "light"].flatMap((role) => {
13319
+ const size = resolveFontSize(resolved.theme, role);
13320
+ return size === void 0 ? [] : [size];
13321
+ }),
13322
+ ...scale ? typeScaleSizes(scale) : []
13323
+ ])
13324
+ ].sort((a, b) => a - b);
13008
13325
  addFact({
13009
13326
  id: "docx:theme",
13010
13327
  kind: "docx/theme",
13011
13328
  path: "/props",
13012
13329
  themeName: context.themeName,
13013
13330
  paletteHexes,
13331
+ typeScalePt,
13332
+ roleSizesPt,
13014
13333
  // `heading` and `body` only. A theme also names `mono` and `light`, but
13015
13334
  // those paint nothing until a component asks for them — counting an
13016
13335
  // unused `Courier New` against a document's family budget would flag a
@@ -13018,7 +13337,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13018
13337
  fontFamilies: [
13019
13338
  ...new Set(
13020
13339
  ["heading", "body"].flatMap((role) => {
13021
- const family = asRecord(
13340
+ const family = asRecord2(
13022
13341
  resolved.theme.fonts?.[role]
13023
13342
  )?.family;
13024
13343
  return typeof family === "string" && family.trim() !== "" ? [family] : [];
@@ -13054,15 +13373,27 @@ function prepareDocxQualityDocument(document, options = {}) {
13054
13373
  excerpt: occurrence.match.excerpt
13055
13374
  });
13056
13375
  });
13376
+ for (const entry of collectDocxTextInventory(resolved.children)) {
13377
+ addFact({ ...entry, generated: authoredPath(entry.path) !== entry.path });
13378
+ }
13057
13379
  let previousVisitPath;
13058
13380
  let lastFrame;
13059
13381
  let flowIndex = 0;
13060
13382
  const parentOf = (path3) => path3.slice(0, path3.lastIndexOf("/"));
13061
13383
  const visit = (node, path3, page) => {
13062
- const props = asRecord(node.props) ?? {};
13384
+ const props = asRecord2(node.props) ?? {};
13063
13385
  if (node.name === "table") {
13064
13386
  const fact = tableFact(props, path3, page.availableWidthTwips);
13065
13387
  if (fact) addFact(fact);
13388
+ for (const size of tableTextSizeFacts(
13389
+ props,
13390
+ authoredPropsAt(path3),
13391
+ path3,
13392
+ typography,
13393
+ page
13394
+ )) {
13395
+ addFact({ ...size, generated: authoredPath(size.path) !== size.path });
13396
+ }
13066
13397
  const design = tableDesignFact(
13067
13398
  props,
13068
13399
  path3,
@@ -13079,7 +13410,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13079
13410
  return {
13080
13411
  ...column,
13081
13412
  path: authored,
13082
- generated: table2 === void 0 || asRecord(nodeAtPointer(themed.document, table2))?.name !== "table"
13413
+ generated: table2 === void 0 || asRecord2(nodeAtPointer(themed.document, table2))?.name !== "table"
13083
13414
  };
13084
13415
  })
13085
13416
  });
@@ -13095,7 +13426,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13095
13426
  addFact({
13096
13427
  ...fact,
13097
13428
  generated: !["chart", "highcharts"].includes(
13098
- String(asRecord(nodeAtPointer(themed.document, authored))?.name)
13429
+ String(asRecord2(nodeAtPointer(themed.document, authored))?.name)
13099
13430
  ),
13100
13431
  seriesColorsPath: authoredPath(fact.seriesColorsPath),
13101
13432
  ...fact.annotation && {
@@ -13109,7 +13440,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13109
13440
  }
13110
13441
  }
13111
13442
  if (node.name === "paragraph" || node.name === "text-box") {
13112
- const floating = asRecord(props.floating);
13443
+ const floating = asRecord2(props.floating);
13113
13444
  if (floating) {
13114
13445
  const signature = frameSignature(floating);
13115
13446
  const chainId = lastFrame !== void 0 && previousVisitPath === lastFrame.path && parentOf(lastFrame.path) === parentOf(path3) && lastFrame.signature === signature ? lastFrame.chainId : `docx:frame-chain:${path3}`;
@@ -13121,6 +13452,10 @@ function prepareDocxQualityDocument(document, options = {}) {
13121
13452
  if (node.name === "paragraph" || node.name === "heading") {
13122
13453
  const fact = lineBoxFact(node, props, path3, typography, context.document);
13123
13454
  if (fact) addFact(fact);
13455
+ if (typeof props.text === "string" && props.text.trim() !== "") {
13456
+ const fact2 = textSizeFact(node, props, path3, typography);
13457
+ if (fact2) addFact({ ...fact2, generated: authoredPath(path3) !== path3 });
13458
+ }
13124
13459
  }
13125
13460
  if (node.name === "image" || node.name === "visual") {
13126
13461
  for (const fact of svgTextFacts(props, path3)) addFact(fact);