@json-to-office/core-docx 6.1.0 → 6.2.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.
@@ -12970,6 +12970,7 @@ async function expandBlocksWithPlugins(document, theme, plugins, render, preserv
12970
12970
  init_styleHelpers();
12971
12971
  init_defaults();
12972
12972
  init_widthUtils();
12973
+ import probe2 from "probe-image-size";
12973
12974
 
12974
12975
  // src/quality/text-inventory.ts
12975
12976
  function tocDepth(depth) {
@@ -13220,6 +13221,92 @@ function collectDocxTextInventory(children, basePath = "/children") {
13220
13221
  return entries;
13221
13222
  }
13222
13223
 
13224
+ // src/quality/text-metrics.ts
13225
+ var DEFAULT_ADVANCE = 556;
13226
+ var ADVANCE = {
13227
+ " ": 278,
13228
+ A: 667,
13229
+ B: 667,
13230
+ C: 722,
13231
+ D: 722,
13232
+ E: 667,
13233
+ F: 611,
13234
+ G: 778,
13235
+ H: 722,
13236
+ I: 278,
13237
+ J: 500,
13238
+ K: 667,
13239
+ L: 556,
13240
+ M: 833,
13241
+ N: 722,
13242
+ O: 778,
13243
+ P: 667,
13244
+ Q: 778,
13245
+ R: 722,
13246
+ S: 667,
13247
+ T: 611,
13248
+ U: 722,
13249
+ V: 667,
13250
+ W: 944,
13251
+ X: 667,
13252
+ Y: 667,
13253
+ Z: 611,
13254
+ a: 556,
13255
+ b: 556,
13256
+ c: 500,
13257
+ d: 556,
13258
+ e: 556,
13259
+ f: 278,
13260
+ g: 556,
13261
+ h: 556,
13262
+ i: 222,
13263
+ j: 222,
13264
+ k: 500,
13265
+ l: 222,
13266
+ m: 833,
13267
+ n: 556,
13268
+ o: 556,
13269
+ p: 556,
13270
+ q: 556,
13271
+ r: 333,
13272
+ s: 500,
13273
+ t: 278,
13274
+ u: 556,
13275
+ v: 500,
13276
+ w: 722,
13277
+ x: 500,
13278
+ y: 500,
13279
+ z: 500,
13280
+ ".": 278,
13281
+ ",": 278,
13282
+ ":": 278,
13283
+ ";": 278,
13284
+ "!": 278,
13285
+ "?": 556,
13286
+ "'": 191,
13287
+ '"': 355,
13288
+ "-": 333,
13289
+ "\u2013": 556,
13290
+ "\u2014": 1e3,
13291
+ "&": 667,
13292
+ "%": 889,
13293
+ "(": 333,
13294
+ ")": 333,
13295
+ "/": 278,
13296
+ "@": 1015,
13297
+ "#": 556,
13298
+ "+": 584,
13299
+ "=": 584
13300
+ };
13301
+ function estimateTextWidthPt(text, fontSizePt, trackingPt = 0) {
13302
+ let units = 0;
13303
+ for (const character of text) {
13304
+ units += ADVANCE[character] ?? DEFAULT_ADVANCE;
13305
+ }
13306
+ const width = units / 1e3 * fontSizePt + text.length * trackingPt;
13307
+ return Math.max(0, width);
13308
+ }
13309
+
13223
13310
  // src/quality/facts.ts
13224
13311
  function asRecord2(value) {
13225
13312
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
@@ -14031,14 +14118,29 @@ function prepareDocxQualityDocument(document, options = {}) {
14031
14118
  }
14032
14119
  if (node.name === "image" || node.name === "visual") {
14033
14120
  for (const fact of svgTextFacts(props, path3)) addFact(fact);
14121
+ const drawn = drawnRatio(props);
14122
+ const natural = readableRatio(props);
14123
+ addFact({
14124
+ id: `docx:image:${path3}`,
14125
+ kind: "docx/image",
14126
+ path: path3,
14127
+ ...typeof props.alt === "string" && props.alt.trim() !== "" && { alt: props.alt },
14128
+ captioned: isCaptioned(facts, authoredPath(path3)),
14129
+ ...drawn !== void 0 && { drawnRatio: drawn },
14130
+ ...natural !== void 0 && { naturalRatio: natural }
14131
+ });
14034
14132
  }
14035
14133
  if (node.name === "heading") {
14036
14134
  const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
14135
+ const styleKeepNext = asRecord2(
14136
+ typography.styles[`heading${level}`]
14137
+ )?.keepNext;
14037
14138
  addFact({
14038
14139
  id: `docx:heading:${path3}`,
14039
14140
  kind: "docx/heading",
14040
14141
  path: `${path3}/props/level`,
14041
14142
  level,
14143
+ keepNext: props.keepNext === true || props.keepNext === void 0 && styleKeepNext === true,
14042
14144
  ...previousHeadingLevel !== void 0 && {
14043
14145
  previousLevel: previousHeadingLevel
14044
14146
  }
@@ -14053,6 +14155,65 @@ function prepareDocxQualityDocument(document, options = {}) {
14053
14155
  const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
14054
14156
  walkActive(component, `/children/${index}`, page, visit);
14055
14157
  });
14158
+ const wordsOf = (text) => text.split(/\s+/).filter(Boolean).length;
14159
+ const bodyRoles = /* @__PURE__ */ new Set([
14160
+ "body",
14161
+ "list-item",
14162
+ "table-header",
14163
+ "table-cell",
14164
+ "statistic",
14165
+ "caption"
14166
+ ]);
14167
+ const exhibitKinds = /* @__PURE__ */ new Set(["docx/table", "docx/chart", "docx/image"]);
14168
+ resolved.children.forEach((component, index) => {
14169
+ const rec = component;
14170
+ if (rec.name !== "section") return;
14171
+ const prefix = `/children/${index}/`;
14172
+ const own = facts.filter((fact) => fact.path.startsWith(prefix));
14173
+ const texts = own.filter(
14174
+ (fact) => fact.kind === "docx/text"
14175
+ );
14176
+ addFact({
14177
+ id: `docx:section:${index}`,
14178
+ kind: "docx/section",
14179
+ path: `/children/${index}`,
14180
+ index,
14181
+ words: texts.filter((fact) => bodyRoles.has(fact.role)).reduce((total, fact) => total + wordsOf(fact.text), 0),
14182
+ exhibits: own.filter((fact) => exhibitKinds.has(fact.kind)).length,
14183
+ headings: texts.filter((fact) => fact.role === "heading").length
14184
+ });
14185
+ if (hasColumnsComponent(rec)) return;
14186
+ const page = pageBox(
14187
+ resolved.theme,
14188
+ context.themeName,
14189
+ rec.props?.page
14190
+ );
14191
+ const fontSizePt = effectiveFontSize({ name: "paragraph" }, {}, typography)?.fontSizePt ?? 11;
14192
+ const widthPt = page.availableWidthTwips / 20;
14193
+ const averageAdvancePt = estimateTextWidthPt(MEASURE_SAMPLE, fontSizePt) / MEASURE_SAMPLE.length;
14194
+ if (averageAdvancePt > 0)
14195
+ addFact({
14196
+ id: `docx:measure:${index}`,
14197
+ kind: "docx/measure",
14198
+ path: `/children/${index}`,
14199
+ index,
14200
+ charactersPerLine: Math.round(widthPt / averageAdvancePt),
14201
+ widthTwips: page.availableWidthTwips,
14202
+ fontSizePt
14203
+ });
14204
+ });
14205
+ const headingCount = facts.filter(
14206
+ (fact) => fact.kind === "docx/text" && fact.role === "heading"
14207
+ ).length;
14208
+ addFact({
14209
+ id: "docx:outline",
14210
+ kind: "docx/outline",
14211
+ path: "/props",
14212
+ headings: headingCount,
14213
+ contents: facts.some(
14214
+ (fact) => fact.kind === "docx/text" && fact.role === "toc-entry"
14215
+ )
14216
+ });
14056
14217
  return {
14057
14218
  format: "docx",
14058
14219
  model: {
@@ -14083,6 +14244,52 @@ function prepareDocxQualityDocument(document, options = {}) {
14083
14244
  }
14084
14245
  };
14085
14246
  }
14247
+ function hasColumnsComponent(node) {
14248
+ if (Array.isArray(node)) return node.some(hasColumnsComponent);
14249
+ const rec = asRecord2(node);
14250
+ if (!rec) return false;
14251
+ if (rec.name === "columns") return true;
14252
+ return Object.values(rec).some(hasColumnsComponent);
14253
+ }
14254
+ var CAPTION_LABEL = /^\s*(?:\*\*)?\s*(figure|exhibit|table|chart|image)\b/i;
14255
+ function isCaptioned(facts, image) {
14256
+ return facts.some((fact) => {
14257
+ if (fact.kind !== "docx/text") return false;
14258
+ const text = fact;
14259
+ const near = siblingOf(text.path) === siblingOf(image) || text.path.startsWith(`${image}/`) || image.startsWith(`${text.path}/`);
14260
+ return near && (text.role === "caption" || CAPTION_LABEL.test(text.text));
14261
+ });
14262
+ }
14263
+ function siblingOf(pointer) {
14264
+ return pointer.slice(0, pointer.lastIndexOf("/"));
14265
+ }
14266
+ var MEASURE_SAMPLE = "the quick brown fox jumps over the lazy dog and then settles under it ";
14267
+ function drawnRatio(props) {
14268
+ const width = finiteNumber(props.width);
14269
+ const height = finiteNumber(props.height);
14270
+ return width !== void 0 && height !== void 0 && height > 0 ? width / height : void 0;
14271
+ }
14272
+ function readableRatio(props) {
14273
+ if (typeof props.svg === "string") {
14274
+ const box = /viewBox\s*=\s*["']\s*[-\d.]+[ ,]+[-\d.]+[ ,]+([\d.]+)[ ,]+([\d.]+)/.exec(
14275
+ props.svg
14276
+ );
14277
+ const width = box ? Number(box[1]) : NaN;
14278
+ const height = box ? Number(box[2]) : NaN;
14279
+ return Number.isFinite(width) && Number.isFinite(height) && height > 0 ? width / height : void 0;
14280
+ }
14281
+ if (typeof props.base64 !== "string") return void 0;
14282
+ const comma = props.base64.indexOf(",");
14283
+ if (comma < 0) return void 0;
14284
+ try {
14285
+ const size = probe2.sync(
14286
+ Buffer.from(props.base64.slice(comma + 1), "base64")
14287
+ );
14288
+ return size && size.height > 0 ? size.width / size.height : void 0;
14289
+ } catch {
14290
+ return void 0;
14291
+ }
14292
+ }
14086
14293
  function slotIsFilled(value) {
14087
14294
  if (typeof value === "string") return value.trim() !== "";
14088
14295
  return value !== void 0 && value !== null && value !== false && (!Array.isArray(value) || value.length > 0);