@json-to-office/core-docx 6.1.0 → 6.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -13917,7 +14004,9 @@ function prepareDocxQualityDocument(document, options = {}) {
13917
14004
  )
13918
14005
  ]
13919
14006
  });
13920
- collectColorLiterals(document).forEach((literal, index) => {
14007
+ const content = { ...document, props: { ...asRecord2(document.props) } };
14008
+ delete content.props.themeOverrides;
14009
+ collectColorLiterals(content).forEach((literal, index) => {
13921
14010
  addFact({
13922
14011
  id: `docx:color:${index}:${literal.path}`,
13923
14012
  kind: "docx/color",
@@ -13926,7 +14015,7 @@ function prepareDocxQualityDocument(document, options = {}) {
13926
14015
  hex: literal.hex
13927
14016
  });
13928
14017
  });
13929
- collectFontFamilies(document).forEach((use, index) => {
14018
+ collectFontFamilies(content).forEach((use, index) => {
13930
14019
  addFact({
13931
14020
  id: `docx:font:${index}:${use.path}`,
13932
14021
  kind: "docx/font-family",
@@ -14031,14 +14120,29 @@ function prepareDocxQualityDocument(document, options = {}) {
14031
14120
  }
14032
14121
  if (node.name === "image" || node.name === "visual") {
14033
14122
  for (const fact of svgTextFacts(props, path3)) addFact(fact);
14123
+ const drawn = drawnRatio(props);
14124
+ const natural = readableRatio(props);
14125
+ addFact({
14126
+ id: `docx:image:${path3}`,
14127
+ kind: "docx/image",
14128
+ path: path3,
14129
+ ...typeof props.alt === "string" && props.alt.trim() !== "" && { alt: props.alt },
14130
+ captioned: isCaptioned(facts, authoredPath(path3)),
14131
+ ...drawn !== void 0 && { drawnRatio: drawn },
14132
+ ...natural !== void 0 && { naturalRatio: natural }
14133
+ });
14034
14134
  }
14035
14135
  if (node.name === "heading") {
14036
14136
  const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
14137
+ const styleKeepNext = asRecord2(
14138
+ typography.styles[`heading${level}`]
14139
+ )?.keepNext;
14037
14140
  addFact({
14038
14141
  id: `docx:heading:${path3}`,
14039
14142
  kind: "docx/heading",
14040
14143
  path: `${path3}/props/level`,
14041
14144
  level,
14145
+ keepNext: props.keepNext === true || props.keepNext === void 0 && styleKeepNext === true,
14042
14146
  ...previousHeadingLevel !== void 0 && {
14043
14147
  previousLevel: previousHeadingLevel
14044
14148
  }
@@ -14053,6 +14157,65 @@ function prepareDocxQualityDocument(document, options = {}) {
14053
14157
  const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
14054
14158
  walkActive(component, `/children/${index}`, page, visit);
14055
14159
  });
14160
+ const wordsOf = (text) => text.split(/\s+/).filter(Boolean).length;
14161
+ const bodyRoles = /* @__PURE__ */ new Set([
14162
+ "body",
14163
+ "list-item",
14164
+ "table-header",
14165
+ "table-cell",
14166
+ "statistic",
14167
+ "caption"
14168
+ ]);
14169
+ const exhibitKinds = /* @__PURE__ */ new Set(["docx/table", "docx/chart", "docx/image"]);
14170
+ resolved.children.forEach((component, index) => {
14171
+ const rec = component;
14172
+ if (rec.name !== "section") return;
14173
+ const prefix = `/children/${index}/`;
14174
+ const own = facts.filter((fact) => fact.path.startsWith(prefix));
14175
+ const texts = own.filter(
14176
+ (fact) => fact.kind === "docx/text"
14177
+ );
14178
+ addFact({
14179
+ id: `docx:section:${index}`,
14180
+ kind: "docx/section",
14181
+ path: `/children/${index}`,
14182
+ index,
14183
+ words: texts.filter((fact) => bodyRoles.has(fact.role)).reduce((total, fact) => total + wordsOf(fact.text), 0),
14184
+ exhibits: own.filter((fact) => exhibitKinds.has(fact.kind)).length,
14185
+ headings: texts.filter((fact) => fact.role === "heading").length
14186
+ });
14187
+ if (hasColumnsComponent(rec)) return;
14188
+ const page = pageBox(
14189
+ resolved.theme,
14190
+ context.themeName,
14191
+ rec.props?.page
14192
+ );
14193
+ const fontSizePt = effectiveFontSize({ name: "paragraph" }, {}, typography)?.fontSizePt ?? 11;
14194
+ const widthPt = page.availableWidthTwips / 20;
14195
+ const averageAdvancePt = estimateTextWidthPt(MEASURE_SAMPLE, fontSizePt) / MEASURE_SAMPLE.length;
14196
+ if (averageAdvancePt > 0)
14197
+ addFact({
14198
+ id: `docx:measure:${index}`,
14199
+ kind: "docx/measure",
14200
+ path: `/children/${index}`,
14201
+ index,
14202
+ charactersPerLine: Math.round(widthPt / averageAdvancePt),
14203
+ widthTwips: page.availableWidthTwips,
14204
+ fontSizePt
14205
+ });
14206
+ });
14207
+ const headingCount = facts.filter(
14208
+ (fact) => fact.kind === "docx/text" && fact.role === "heading"
14209
+ ).length;
14210
+ addFact({
14211
+ id: "docx:outline",
14212
+ kind: "docx/outline",
14213
+ path: "/props",
14214
+ headings: headingCount,
14215
+ contents: facts.some(
14216
+ (fact) => fact.kind === "docx/text" && fact.role === "toc-entry"
14217
+ )
14218
+ });
14056
14219
  return {
14057
14220
  format: "docx",
14058
14221
  model: {
@@ -14083,6 +14246,52 @@ function prepareDocxQualityDocument(document, options = {}) {
14083
14246
  }
14084
14247
  };
14085
14248
  }
14249
+ function hasColumnsComponent(node) {
14250
+ if (Array.isArray(node)) return node.some(hasColumnsComponent);
14251
+ const rec = asRecord2(node);
14252
+ if (!rec) return false;
14253
+ if (rec.name === "columns") return true;
14254
+ return Object.values(rec).some(hasColumnsComponent);
14255
+ }
14256
+ var CAPTION_LABEL = /^\s*(?:\*\*)?\s*(figure|exhibit|table|chart|image)\b/i;
14257
+ function isCaptioned(facts, image) {
14258
+ return facts.some((fact) => {
14259
+ if (fact.kind !== "docx/text") return false;
14260
+ const text = fact;
14261
+ const near = siblingOf(text.path) === siblingOf(image) || text.path.startsWith(`${image}/`) || image.startsWith(`${text.path}/`);
14262
+ return near && (text.role === "caption" || CAPTION_LABEL.test(text.text));
14263
+ });
14264
+ }
14265
+ function siblingOf(pointer) {
14266
+ return pointer.slice(0, pointer.lastIndexOf("/"));
14267
+ }
14268
+ var MEASURE_SAMPLE = "the quick brown fox jumps over the lazy dog and then settles under it ";
14269
+ function drawnRatio(props) {
14270
+ const width = finiteNumber(props.width);
14271
+ const height = finiteNumber(props.height);
14272
+ return width !== void 0 && height !== void 0 && height > 0 ? width / height : void 0;
14273
+ }
14274
+ function readableRatio(props) {
14275
+ if (typeof props.svg === "string") {
14276
+ const box = /viewBox\s*=\s*["']\s*[-\d.]+[ ,]+[-\d.]+[ ,]+([\d.]+)[ ,]+([\d.]+)/.exec(
14277
+ props.svg
14278
+ );
14279
+ const width = box ? Number(box[1]) : NaN;
14280
+ const height = box ? Number(box[2]) : NaN;
14281
+ return Number.isFinite(width) && Number.isFinite(height) && height > 0 ? width / height : void 0;
14282
+ }
14283
+ if (typeof props.base64 !== "string") return void 0;
14284
+ const comma = props.base64.indexOf(",");
14285
+ if (comma < 0) return void 0;
14286
+ try {
14287
+ const size = probe2.sync(
14288
+ Buffer.from(props.base64.slice(comma + 1), "base64")
14289
+ );
14290
+ return size && size.height > 0 ? size.width / size.height : void 0;
14291
+ } catch {
14292
+ return void 0;
14293
+ }
14294
+ }
14086
14295
  function slotIsFilled(value) {
14087
14296
  if (typeof value === "string") return value.trim() !== "";
14088
14297
  return value !== void 0 && value !== null && value !== false && (!Array.isArray(value) || value.length > 0);