@json-to-office/core-docx 1.4.0 → 1.6.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.
@@ -864,6 +864,143 @@ var init_devportal_docx_theme = __esm({
864
864
  }
865
865
  });
866
866
 
867
+ // src/templates/themes/vermilion.docx.theme.json
868
+ var vermilion_docx_theme_default;
869
+ var init_vermilion_docx_theme = __esm({
870
+ "src/templates/themes/vermilion.docx.theme.json"() {
871
+ vermilion_docx_theme_default = {
872
+ name: "vermilion",
873
+ displayName: "Vermilion Editorial",
874
+ description: "Poster-red editorial system \u2014 vermilion display headings, ink text, warm creams and hairline rules",
875
+ version: "1.0.0",
876
+ colors: {
877
+ primary: "#282829",
878
+ secondary: "#58595B",
879
+ accent: "#EF4130",
880
+ text: "#282829",
881
+ background: "#FFFFFF",
882
+ border: "#E8E2DC",
883
+ textPrimary: "#282829",
884
+ textSecondary: "#58595B",
885
+ textMuted: "#939598",
886
+ borderPrimary: "#C7C8CA",
887
+ borderSecondary: "#EFEAE4",
888
+ backgroundPrimary: "#FFFFFF",
889
+ backgroundSecondary: "#FAF7F5"
890
+ },
891
+ fonts: {
892
+ heading: {
893
+ family: "Arial",
894
+ size: 24
895
+ },
896
+ body: {
897
+ family: "Calibri",
898
+ size: 10.5
899
+ },
900
+ mono: {
901
+ family: "Consolas",
902
+ size: 10
903
+ },
904
+ light: {
905
+ family: "Arial",
906
+ size: 24
907
+ }
908
+ },
909
+ page: {
910
+ size: "A4",
911
+ margins: {
912
+ top: 1360,
913
+ bottom: 1360,
914
+ left: 1180,
915
+ right: 1180,
916
+ header: 620,
917
+ footer: 620,
918
+ gutter: 0
919
+ }
920
+ },
921
+ styles: {
922
+ normal: {
923
+ font: "body",
924
+ color: "textPrimary",
925
+ lineSpacing: {
926
+ type: "multiple",
927
+ value: 1.3
928
+ },
929
+ alignment: "left",
930
+ spacing: {
931
+ after: 8
932
+ }
933
+ },
934
+ heading1: {
935
+ font: "heading",
936
+ size: 25,
937
+ bold: false,
938
+ color: "accent",
939
+ characterSpacing: {
940
+ type: "expanded",
941
+ value: 50
942
+ },
943
+ spacing: {
944
+ before: 22,
945
+ after: 12
946
+ },
947
+ keepNext: true,
948
+ alignment: "left"
949
+ },
950
+ heading2: {
951
+ font: "heading",
952
+ size: 12.5,
953
+ bold: true,
954
+ color: "accent",
955
+ characterSpacing: {
956
+ type: "expanded",
957
+ value: 30
958
+ },
959
+ spacing: {
960
+ before: 16,
961
+ after: 7
962
+ },
963
+ keepNext: true,
964
+ alignment: "left"
965
+ },
966
+ heading3: {
967
+ font: "body",
968
+ size: 11,
969
+ bold: true,
970
+ color: "textPrimary",
971
+ spacing: {
972
+ before: 12,
973
+ after: 5
974
+ },
975
+ keepNext: true,
976
+ alignment: "left"
977
+ },
978
+ title: {
979
+ font: "heading",
980
+ size: 38,
981
+ bold: true,
982
+ color: "primary",
983
+ alignment: "left",
984
+ spacing: {
985
+ before: 0,
986
+ after: 14
987
+ }
988
+ },
989
+ subtitle: {
990
+ font: "body",
991
+ size: 14,
992
+ color: "textSecondary",
993
+ alignment: "left",
994
+ spacing: {
995
+ before: 0,
996
+ after: 10
997
+ }
998
+ }
999
+ }
1000
+ };
1001
+ }
1002
+ });
1003
+
867
1004
  // src/templates/themes/index.ts
868
1005
  import fs from "fs";
869
1006
  import path from "path";
@@ -880,7 +1017,8 @@ function loadThemesFromJson() {
880
1017
  corporate: ensureThemeDefaults(corporate_docx_theme_default),
881
1018
  modern: ensureThemeDefaults(modern_docx_theme_default),
882
1019
  apex: ensureThemeDefaults(apex_docx_theme_default),
883
- devportal: ensureThemeDefaults(devportal_docx_theme_default)
1020
+ devportal: ensureThemeDefaults(devportal_docx_theme_default),
1021
+ vermilion: ensureThemeDefaults(vermilion_docx_theme_default)
884
1022
  };
885
1023
  try {
886
1024
  const __filename2 = fileURLToPath(import.meta.url);
@@ -928,6 +1066,7 @@ var init_themes = __esm({
928
1066
  init_modern_docx_theme();
929
1067
  init_apex_docx_theme();
930
1068
  init_devportal_docx_theme();
1069
+ init_vermilion_docx_theme();
931
1070
  _themesCache = null;
932
1071
  themes = loadThemesFromJson();
933
1072
  getTheme = (themeName) => {
@@ -1718,6 +1857,14 @@ async function rasterizeSvgFallback(svg, transformation) {
1718
1857
  }
1719
1858
  try {
1720
1859
  const markup = svg.toString("utf-8");
1860
+ if (/<(?:[A-Za-z][\w.-]*:)?text[\s>/]/.test(markup)) {
1861
+ reportWarning(
1862
+ "image",
1863
+ "IMAGE_SVG_RASTER_SKIPPED",
1864
+ "Inline SVG contains <text>, which would rasterize with machine-dependent fonts, so its fallback only renders in Word 2016+. Convert the text to paths for a portable fallback."
1865
+ );
1866
+ return void 0;
1867
+ }
1721
1868
  const probeImage = new Resvg(markup);
1722
1869
  const wide = probeImage.width / probeImage.height > transformation.width / transformation.height;
1723
1870
  const mode = wide ? "height" : "width";
@@ -5206,88 +5353,6 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
5206
5353
  return map;
5207
5354
  }
5208
5355
 
5209
- // src/json/normalizer.ts
5210
- function normalizeComponent(component) {
5211
- if (component.name === "columns") {
5212
- return normalizeColumnsComponent(component);
5213
- }
5214
- if ("children" in component && component.children) {
5215
- return {
5216
- ...component,
5217
- // Preserve ALL properties from validated component
5218
- children: component.children.map(normalizeComponent)
5219
- };
5220
- }
5221
- if (component.name === "section") {
5222
- return normalizeSectionComponent(component);
5223
- }
5224
- return component;
5225
- }
5226
- function normalizeColumnsComponent(component) {
5227
- const cfg = component.props || {};
5228
- const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
5229
- const isLast = i === cols.length - 1;
5230
- if (!isLast && c.gap === void 0 && topGap !== void 0) {
5231
- return { ...c, gap: topGap };
5232
- }
5233
- return c;
5234
- });
5235
- const normalizeAutoWidth = (cols) => cols.map((c) => ({
5236
- ...c,
5237
- width: c.width === "auto" ? void 0 : c.width
5238
- }));
5239
- let columnsArray;
5240
- if (typeof cfg.columns === "number" && cfg.columns > 0) {
5241
- const n = cfg.columns;
5242
- const base = Array.from(
5243
- { length: n },
5244
- () => ({})
5245
- );
5246
- const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
5247
- columnsArray = applyTopLevelGap(base, topLevelGap);
5248
- } else if (Array.isArray(cfg.columns)) {
5249
- columnsArray = cfg.columns;
5250
- const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
5251
- columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
5252
- columnsArray = normalizeAutoWidth(columnsArray);
5253
- } else {
5254
- columnsArray = [{}];
5255
- }
5256
- const normalizedProps = {
5257
- columns: columnsArray
5258
- };
5259
- return {
5260
- ...component,
5261
- // Preserve any additional properties
5262
- name: "columns",
5263
- props: normalizedProps,
5264
- children: component.children ? component.children.map(normalizeComponent) : void 0
5265
- };
5266
- }
5267
- function normalizeSectionComponent(component) {
5268
- const props = component.props || {};
5269
- return {
5270
- ...component,
5271
- // Preserve ALL properties
5272
- props: {
5273
- ...props,
5274
- // Preserve ALL props properties
5275
- header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
5276
- footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
5277
- },
5278
- children: component.children ? component.children.map(normalizeComponent) : void 0
5279
- };
5280
- }
5281
- function normalizeDocument(document) {
5282
- if (document.name !== "docx") {
5283
- throw new Error(
5284
- 'Top-level document must be a docx component with name="docx"'
5285
- );
5286
- }
5287
- const normalized = normalizeComponent(document);
5288
- return [normalized];
5289
- }
5290
-
5291
5356
  // src/core/desugarExternals.ts
5292
5357
  import {
5293
5358
  clampVisualDpi as clampVisualDpi2,
@@ -7061,6 +7126,17 @@ function mergeBorderSizePerSide(...borders3) {
7061
7126
  left: pick("left")
7062
7127
  };
7063
7128
  }
7129
+ function statedSides(colors, sizes) {
7130
+ const namedColors = colors.filter((value) => typeof value === "object").map(normalizeBorderColor);
7131
+ const namedSizes = sizes.filter((value) => typeof value === "object").map(normalizeBorderSize);
7132
+ const stated = (side) => namedColors.some((c) => c !== void 0 && c[side] !== UNSET_COLOR) || namedSizes.some((s) => s !== void 0 && s[side] !== UNSET_SIZE);
7133
+ return {
7134
+ top: stated("top"),
7135
+ right: stated("right"),
7136
+ bottom: stated("bottom"),
7137
+ left: stated("left")
7138
+ };
7139
+ }
7064
7140
  function mergePaddingPerSide(...paddings) {
7065
7141
  const normalized = paddings.map(normalizePadding);
7066
7142
  if (!normalized.some((p) => p !== void 0)) return void 0;
@@ -7200,6 +7276,10 @@ function mergeCellDefaults(tableDef, columnDef, cellDef, position, tableOuterBor
7200
7276
  tableDef?.borderSize,
7201
7277
  tableOuterBorder.borderSize
7202
7278
  ),
7279
+ borderExplicit: statedSides(
7280
+ [cellDef?.borderColor, columnDef?.borderColor],
7281
+ [cellDef?.borderSize, columnDef?.borderSize]
7282
+ ),
7203
7283
  padding: mergePaddingPerSide(
7204
7284
  cellDef?.padding,
7205
7285
  columnDef?.padding,
@@ -7257,6 +7337,10 @@ function mergeHeaderCellDefaults(tableDef, headerTableDef, columnDef, headerDef,
7257
7337
  tableDef?.borderSize,
7258
7338
  tableOuterBorder.borderSize
7259
7339
  ),
7340
+ borderExplicit: statedSides(
7341
+ [headerDef?.borderColor, columnDef?.borderColor],
7342
+ [headerDef?.borderSize, columnDef?.borderSize]
7343
+ ),
7260
7344
  padding: mergePaddingPerSide(
7261
7345
  headerDef?.padding,
7262
7346
  headerTableDef?.padding,
@@ -7287,28 +7371,20 @@ function resolveTableModel(source, theme, themeName, options = {}) {
7287
7371
  return position.isLastCol ? hidden.right : hidden.insideVertical;
7288
7372
  }
7289
7373
  };
7290
- const borders3 = (merged, position) => ({
7291
- top: {
7292
- size: merged.borderSize.top,
7293
- color: merged.borderColor.top,
7294
- hidden: isHidden("top", position)
7295
- },
7296
- right: {
7297
- size: merged.borderSize.right,
7298
- color: merged.borderColor.right,
7299
- hidden: isHidden("right", position)
7300
- },
7301
- bottom: {
7302
- size: merged.borderSize.bottom,
7303
- color: merged.borderColor.bottom,
7304
- hidden: isHidden("bottom", position)
7305
- },
7306
- left: {
7307
- size: merged.borderSize.left,
7308
- color: merged.borderColor.left,
7309
- hidden: isHidden("left", position)
7310
- }
7311
- });
7374
+ const borders3 = (merged, position) => {
7375
+ const side = (name) => ({
7376
+ size: merged.borderSize[name],
7377
+ color: merged.borderColor[name],
7378
+ hidden: isHidden(name, position) && !merged.borderExplicit[name],
7379
+ explicit: merged.borderExplicit[name]
7380
+ });
7381
+ return {
7382
+ top: side("top"),
7383
+ right: side("right"),
7384
+ bottom: side("bottom"),
7385
+ left: side("left")
7386
+ };
7387
+ };
7312
7388
  const tallest = (heights) => heights.reduce(
7313
7389
  (max, height) => height === void 0 ? max : max === void 0 ? height : Math.max(max, height),
7314
7390
  void 0
@@ -7418,6 +7494,7 @@ function resolveTableModel(source, theme, themeName, options = {}) {
7418
7494
  };
7419
7495
  return row;
7420
7496
  });
7497
+ adjudicateInteriorEdges([header, ...rows]);
7421
7498
  return {
7422
7499
  ...resolveWidths(source, columns, theme, themeName),
7423
7500
  header,
@@ -7425,6 +7502,54 @@ function resolveTableModel(source, theme, themeName, options = {}) {
7425
7502
  repeatHeader: source.repeatHeaderOnPageBreak ?? true
7426
7503
  };
7427
7504
  }
7505
+ function drawsNothing(border3) {
7506
+ return border3.hidden || border3.size <= 0;
7507
+ }
7508
+ function channels(hex) {
7509
+ const digits = hex.startsWith("#") ? hex.slice(1) : hex;
7510
+ return [
7511
+ parseInt(digits.slice(0, 2), 16) || 0,
7512
+ parseInt(digits.slice(2, 4), 16) || 0,
7513
+ parseInt(digits.slice(4, 6), 16) || 0
7514
+ ];
7515
+ }
7516
+ function outweighs(a, b) {
7517
+ if (drawsNothing(a)) return false;
7518
+ if (drawsNothing(b)) return true;
7519
+ if (a.size !== b.size) return a.size > b.size;
7520
+ const [redA, greenA, blueA] = channels(a.color);
7521
+ const [redB, greenB, blueB] = channels(b.color);
7522
+ const brightness = [
7523
+ [redA + blueA + 2 * greenA, redB + blueB + 2 * greenB],
7524
+ [blueA + 2 * greenA, blueB + 2 * greenB],
7525
+ [greenA, greenB]
7526
+ ];
7527
+ for (const [ours, theirs] of brightness) {
7528
+ if (ours !== theirs) return ours < theirs;
7529
+ }
7530
+ return false;
7531
+ }
7532
+ function adjudicateInteriorEdges(rows) {
7533
+ const settle = (earlier, later) => {
7534
+ if (drawsNothing(earlier) && drawsNothing(later)) return;
7535
+ if (earlier.hidden === later.hidden && earlier.size === later.size && earlier.color === later.color) {
7536
+ return;
7537
+ }
7538
+ const winner = earlier.explicit !== later.explicit ? earlier.explicit ? earlier : later : outweighs(later, earlier) ? later : earlier;
7539
+ const loser = winner === earlier ? later : earlier;
7540
+ loser.size = winner.size;
7541
+ loser.color = winner.color;
7542
+ loser.hidden = winner.hidden;
7543
+ };
7544
+ rows.forEach((row, rowIndex) => {
7545
+ row.cells.forEach((cell, colIndex) => {
7546
+ const right = row.cells[colIndex + 1];
7547
+ if (right) settle(cell.borders.right, right.borders.left);
7548
+ const below = rows[rowIndex + 1]?.cells[colIndex];
7549
+ if (below) settle(cell.borders.bottom, below.borders.top);
7550
+ });
7551
+ });
7552
+ }
7428
7553
  function resolveWidths(source, columns, theme, themeName) {
7429
7554
  const hasExplicitWidths = columns.some((col) => col.width !== void 0);
7430
7555
  if (!hasExplicitWidths) {
@@ -10441,6 +10566,7 @@ function compileTableRow(row, spacing, baseStyle, scope) {
10441
10566
  };
10442
10567
  }
10443
10568
  function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision, markRevision) {
10569
+ const cellLine = compileLineSpacing(cell.font?.lineSpacing);
10444
10570
  const paragraph2 = {
10445
10571
  kind: "paragraph",
10446
10572
  id: scope.id,
@@ -10454,7 +10580,10 @@ function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision
10454
10580
  spacing: {
10455
10581
  beforeTwips: spacing.before,
10456
10582
  afterTwips: spacing.after,
10457
- ...spacing.line !== void 0 ? { lineTwips: spacing.line, lineRule: lineRule(spacing.lineRule) } : {}
10583
+ ...cellLine ? {
10584
+ ...cellLine.lineTwips !== void 0 ? { lineTwips: cellLine.lineTwips } : {},
10585
+ lineRule: cellLine.lineRule
10586
+ } : spacing.line !== void 0 ? { lineTwips: spacing.line, lineRule: lineRule(spacing.lineRule) } : {}
10458
10587
  },
10459
10588
  ...keepNext ? { keepNext: true } : {}
10460
10589
  },
@@ -11198,8 +11327,7 @@ function resolveComponentTree(components, theme) {
11198
11327
  }
11199
11328
 
11200
11329
  // src/core/structure.ts
11201
- async function processDocument(document, theme, themeName, generationDate) {
11202
- const metadata = createDocumentMetadata(document.props, generationDate);
11330
+ function resolveDocumentTree(document, theme) {
11203
11331
  const docDefaults = document.props.componentDefaults;
11204
11332
  const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
11205
11333
  const docNoProofWords = document.props.noProofWords;
@@ -11213,6 +11341,14 @@ async function processDocument(document, theme, themeName, generationDate) {
11213
11341
  },
11214
11342
  ...mergedNoProofWords && { noProofWords: mergedNoProofWords }
11215
11343
  } : theme;
11344
+ return {
11345
+ theme: effectiveTheme,
11346
+ children: resolveComponentTree(document.children || [], effectiveTheme)
11347
+ };
11348
+ }
11349
+ async function processResolvedDocument(document, resolved, themeName, generationDate) {
11350
+ const metadata = createDocumentMetadata(document.props, generationDate);
11351
+ const effectiveTheme = resolved.theme;
11216
11352
  const context = createRenderContext(
11217
11353
  {
11218
11354
  metadata,
@@ -11223,11 +11359,7 @@ async function processDocument(document, theme, themeName, generationDate) {
11223
11359
  effectiveTheme,
11224
11360
  themeName
11225
11361
  );
11226
- const resolvedChildren = resolveComponentTree(
11227
- document.children || [],
11228
- effectiveTheme
11229
- );
11230
- const sections = await extractSections(resolvedChildren, context);
11362
+ const sections = await extractSections(resolved.children, context);
11231
11363
  return {
11232
11364
  metadata,
11233
11365
  sections,
@@ -11327,6 +11459,365 @@ function createRenderContext(document, theme, themeName) {
11327
11459
  };
11328
11460
  }
11329
11461
 
11462
+ // src/quality/facts.ts
11463
+ import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
11464
+
11465
+ // src/json/normalizer.ts
11466
+ function normalizeComponent(component) {
11467
+ if (component.name === "columns") {
11468
+ return normalizeColumnsComponent(component);
11469
+ }
11470
+ if ("children" in component && component.children) {
11471
+ return {
11472
+ ...component,
11473
+ // Preserve ALL properties from validated component
11474
+ children: component.children.map(normalizeComponent)
11475
+ };
11476
+ }
11477
+ if (component.name === "section") {
11478
+ return normalizeSectionComponent(component);
11479
+ }
11480
+ return component;
11481
+ }
11482
+ function normalizeColumnsComponent(component) {
11483
+ const cfg = component.props || {};
11484
+ const applyTopLevelGap = (cols, topGap) => cols.map((c, i) => {
11485
+ const isLast = i === cols.length - 1;
11486
+ if (!isLast && c.gap === void 0 && topGap !== void 0) {
11487
+ return { ...c, gap: topGap };
11488
+ }
11489
+ return c;
11490
+ });
11491
+ const normalizeAutoWidth = (cols) => cols.map((c) => ({
11492
+ ...c,
11493
+ width: c.width === "auto" ? void 0 : c.width
11494
+ }));
11495
+ let columnsArray;
11496
+ if (typeof cfg.columns === "number" && cfg.columns > 0) {
11497
+ const n = cfg.columns;
11498
+ const base = Array.from(
11499
+ { length: n },
11500
+ () => ({})
11501
+ );
11502
+ const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
11503
+ columnsArray = applyTopLevelGap(base, topLevelGap);
11504
+ } else if (Array.isArray(cfg.columns)) {
11505
+ columnsArray = cfg.columns;
11506
+ const topLevelGap = cfg.gap !== void 0 ? cfg.gap : "5%";
11507
+ columnsArray = applyTopLevelGap(columnsArray, topLevelGap);
11508
+ columnsArray = normalizeAutoWidth(columnsArray);
11509
+ } else {
11510
+ columnsArray = [{}];
11511
+ }
11512
+ const normalizedProps = {
11513
+ columns: columnsArray
11514
+ };
11515
+ return {
11516
+ ...component,
11517
+ // Preserve any additional properties
11518
+ name: "columns",
11519
+ props: normalizedProps,
11520
+ children: component.children ? component.children.map(normalizeComponent) : void 0
11521
+ };
11522
+ }
11523
+ function normalizeSectionComponent(component) {
11524
+ const props = component.props || {};
11525
+ return {
11526
+ ...component,
11527
+ // Preserve ALL properties
11528
+ props: {
11529
+ ...props,
11530
+ // Preserve ALL props properties
11531
+ header: props.header ? props.header === "linkToPrevious" ? "linkToPrevious" : props.header.map((m) => normalizeComponent(m)) : void 0,
11532
+ footer: props.footer ? props.footer === "linkToPrevious" ? "linkToPrevious" : props.footer.map((m) => normalizeComponent(m)) : void 0
11533
+ },
11534
+ children: component.children ? component.children.map(normalizeComponent) : void 0
11535
+ };
11536
+ }
11537
+ function normalizeDocument(document) {
11538
+ if (document.name !== "docx") {
11539
+ throw new Error(
11540
+ 'Top-level document must be a docx component with name="docx"'
11541
+ );
11542
+ }
11543
+ const normalized = normalizeComponent(document);
11544
+ return [normalized];
11545
+ }
11546
+
11547
+ // src/quality/facts.ts
11548
+ init_widthUtils();
11549
+ function asRecord(value) {
11550
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
11551
+ }
11552
+ function pageBox(theme, themeName, pageOverride) {
11553
+ const page = createSectionProperties(
11554
+ getColumnSettings("single"),
11555
+ theme,
11556
+ themeName,
11557
+ "nextPage",
11558
+ pageOverride
11559
+ ).page;
11560
+ return {
11561
+ availableWidthTwips: Math.max(
11562
+ 0,
11563
+ page.size.width - page.margin.left - page.margin.right
11564
+ ),
11565
+ pageBottomTwips: page.size.height
11566
+ };
11567
+ }
11568
+ function tableFact(props, path3, availableWidthTwips) {
11569
+ const columns = Array.isArray(props.columns) ? props.columns : void 0;
11570
+ if (!columns) return void 0;
11571
+ let totalWidthTwips = 0;
11572
+ let hasExplicitWidth = false;
11573
+ let pointSum = 0;
11574
+ let percentSum = 0;
11575
+ const explicitWidths = [];
11576
+ columns.forEach((column, index) => {
11577
+ const width = asRecord(column)?.width;
11578
+ if (typeof width === "number" && Number.isFinite(width)) {
11579
+ hasExplicitWidth = true;
11580
+ explicitWidths.push({ index, width });
11581
+ totalWidthTwips += relativeLengthToTwips(width, availableWidthTwips);
11582
+ pointSum += width;
11583
+ } else if (typeof width === "string") {
11584
+ hasExplicitWidth = true;
11585
+ const percent = width.trim().endsWith("%") ? Number(width.trim().slice(0, -1)) : Number.NaN;
11586
+ if (Number.isFinite(percent)) {
11587
+ explicitWidths.push({ index, width });
11588
+ percentSum += percent;
11589
+ totalWidthTwips += Math.max(
11590
+ 0,
11591
+ Math.round(availableWidthTwips * percent / 100)
11592
+ );
11593
+ }
11594
+ }
11595
+ });
11596
+ return {
11597
+ id: `docx:table-width:${path3}`,
11598
+ kind: "docx/table-width",
11599
+ path: `${path3}/props/columns`,
11600
+ totalWidthTwips,
11601
+ availableWidthTwips,
11602
+ hasExplicitWidth,
11603
+ allColumnsExplicit: explicitWidths.length === columns.length,
11604
+ pointSum,
11605
+ percentSum,
11606
+ explicitWidths
11607
+ };
11608
+ }
11609
+ var TWIPS_PER_POINT3 = 20;
11610
+ var SINGLE_LINE_RATIO = 1.2;
11611
+ function finiteNumber(value) {
11612
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
11613
+ }
11614
+ function lineHeightPt(fontSizePt, spacing) {
11615
+ const rule = asRecord(spacing);
11616
+ const value = finiteNumber(rule?.value);
11617
+ switch (rule?.type) {
11618
+ // `exactly` is an absolute line box, and display type routinely sets it
11619
+ // below the font size — reading it as a multiplier would inflate every
11620
+ // estimate on exactly the headings that matter most.
11621
+ case "exactly":
11622
+ return value ?? fontSizePt * SINGLE_LINE_RATIO;
11623
+ case "atLeast":
11624
+ return Math.max(value ?? 0, fontSizePt * SINGLE_LINE_RATIO);
11625
+ case "multiple":
11626
+ return fontSizePt * SINGLE_LINE_RATIO * (value ?? 1);
11627
+ default:
11628
+ return fontSizePt * SINGLE_LINE_RATIO;
11629
+ }
11630
+ }
11631
+ function characterSpacingPt(spacing) {
11632
+ const rule = asRecord(spacing);
11633
+ const value = finiteNumber(rule?.value);
11634
+ if (rule === void 0 || value === void 0) return 0;
11635
+ return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
11636
+ }
11637
+ function frameSignature(floating) {
11638
+ const horizontal = asRecord(floating.horizontalPosition);
11639
+ const vertical = asRecord(floating.verticalPosition);
11640
+ return JSON.stringify([
11641
+ floating.width ?? null,
11642
+ floating.height ?? null,
11643
+ horizontal?.offset ?? null,
11644
+ horizontal?.relative ?? null,
11645
+ horizontal?.align ?? null,
11646
+ vertical?.offset ?? null,
11647
+ vertical?.relative ?? null,
11648
+ vertical?.align ?? null,
11649
+ asRecord(floating.wrap)?.type ?? null
11650
+ ]);
11651
+ }
11652
+ function frameTextFact(props, path3, page, frameChainId, flowIndex) {
11653
+ const floating = asRecord(props.floating);
11654
+ const frameWidthTwips = finiteNumber(floating?.width);
11655
+ if (frameWidthTwips === void 0) return void 0;
11656
+ const text = typeof props.text === "string" ? props.text : "";
11657
+ if (text.trim() === "") return void 0;
11658
+ const font = asRecord(props.font);
11659
+ const fontSizePt = finiteNumber(font?.size);
11660
+ if (fontSizePt === void 0) return void 0;
11661
+ const longestWord = text.split(/\s+/).reduce(
11662
+ (longest, word) => word.length > longest.length ? word : longest,
11663
+ ""
11664
+ );
11665
+ const horizontal = asRecord(floating?.horizontalPosition);
11666
+ const vertical = asRecord(floating?.verticalPosition);
11667
+ const offsetX = finiteNumber(horizontal?.offset);
11668
+ const offsetY = finiteNumber(vertical?.offset);
11669
+ const absolutelyPinned = horizontal?.offset !== void 0 || vertical?.offset !== void 0;
11670
+ const resolvable = (horizontal?.offset === void 0 || offsetX !== void 0) && (vertical?.offset === void 0 || offsetY !== void 0);
11671
+ return {
11672
+ id: `docx:frame-text:${path3}`,
11673
+ kind: "docx/frame-text",
11674
+ path: path3,
11675
+ text,
11676
+ fontSizePt,
11677
+ lineHeightPt: lineHeightPt(fontSizePt, font?.lineSpacing),
11678
+ characterSpacingPt: characterSpacingPt(font?.characterSpacing),
11679
+ frameWidthTwips,
11680
+ ...finiteNumber(floating?.height) !== void 0 && {
11681
+ frameHeightTwips: finiteNumber(floating?.height)
11682
+ },
11683
+ ...offsetY !== void 0 && { offsetYTwips: offsetY },
11684
+ pageBottomTwips: page.pageBottomTwips,
11685
+ longestWord,
11686
+ ...absolutelyPinned && resolvable && {
11687
+ absoluteOffsetTwips: { x: offsetX ?? 0, y: offsetY ?? 0 }
11688
+ },
11689
+ anchorHorizontal: typeof horizontal?.relative === "string" ? horizontal.relative : "page",
11690
+ anchorVertical: typeof vertical?.relative === "string" ? vertical.relative : "page",
11691
+ frameChainId,
11692
+ flowIndex
11693
+ };
11694
+ }
11695
+ var SVG_VIEWBOX = /viewBox\s*=\s*"\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*"/;
11696
+ var SVG_TEXT = /<text\b([^>]*)>([^<]*)<\/text>/g;
11697
+ var SVG_ATTR = (name) => new RegExp(`\\b${name}\\s*=\\s*"(-?[\\d.]+)"`);
11698
+ function svgTextFacts(props, path3) {
11699
+ const svg = typeof props.svg === "string" ? props.svg : void 0;
11700
+ if (!svg) return [];
11701
+ const box = SVG_VIEWBOX.exec(svg);
11702
+ if (!box) return [];
11703
+ const viewBoxMinY = Number(box[2]);
11704
+ const viewBoxHeight = Number(box[4]);
11705
+ if (!Number.isFinite(viewBoxMinY) || !Number.isFinite(viewBoxHeight)) {
11706
+ return [];
11707
+ }
11708
+ const facts = [];
11709
+ let match;
11710
+ SVG_TEXT.lastIndex = 0;
11711
+ let index = 0;
11712
+ while ((match = SVG_TEXT.exec(svg)) !== null) {
11713
+ const [, attributes, content] = match;
11714
+ const baselineY = Number(SVG_ATTR("y").exec(attributes)?.[1]);
11715
+ if (!Number.isFinite(baselineY)) {
11716
+ index += 1;
11717
+ continue;
11718
+ }
11719
+ const fontSizeUnits = Number(
11720
+ SVG_ATTR("font-size").exec(attributes)?.[1] ?? 0
11721
+ );
11722
+ facts.push({
11723
+ id: `docx:svg-text:${path3}:${index}`,
11724
+ kind: "docx/svg-text",
11725
+ path: `${path3}/props/svg`,
11726
+ content: content.trim(),
11727
+ baselineY,
11728
+ fontSizeUnits: Number.isFinite(fontSizeUnits) ? fontSizeUnits : 0,
11729
+ viewBoxMinY,
11730
+ viewBoxHeight
11731
+ });
11732
+ index += 1;
11733
+ }
11734
+ return facts;
11735
+ }
11736
+ function walkActive(node, path3, page, visit) {
11737
+ const rec = asRecord(node);
11738
+ if (!rec || rec.enabled === false) return;
11739
+ visit(rec, path3, page);
11740
+ const children = Array.isArray(rec.children) ? rec.children : [];
11741
+ children.forEach(
11742
+ (child, index) => walkActive(child, `${path3}/children/${index}`, page, visit)
11743
+ );
11744
+ }
11745
+ function prepareDocxQualityDocument(document, options = {}) {
11746
+ const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
11747
+ customThemes: options.customThemes,
11748
+ fonts: options.fonts,
11749
+ warnings: options.warnings
11750
+ });
11751
+ const resolved = resolveDocumentTree(context.document, context.theme);
11752
+ const basePage = pageBox(resolved.theme, context.themeName);
11753
+ const facts = [];
11754
+ const provenance = {};
11755
+ let previousHeadingLevel;
11756
+ const addFact = (fact) => {
11757
+ facts.push(fact);
11758
+ provenance[fact.id] = {
11759
+ path: fact.path,
11760
+ ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
11761
+ };
11762
+ };
11763
+ let previousVisitPath;
11764
+ let lastFrame;
11765
+ let flowIndex = 0;
11766
+ const parentOf = (path3) => path3.slice(0, path3.lastIndexOf("/"));
11767
+ const visit = (node, path3, page) => {
11768
+ const props = asRecord(node.props) ?? {};
11769
+ if (node.name === "table") {
11770
+ const fact = tableFact(props, path3, page.availableWidthTwips);
11771
+ if (fact) addFact(fact);
11772
+ }
11773
+ if (node.name === "paragraph" || node.name === "text-box") {
11774
+ const floating = asRecord(props.floating);
11775
+ if (floating) {
11776
+ const signature = frameSignature(floating);
11777
+ const chainId = lastFrame !== void 0 && previousVisitPath === lastFrame.path && parentOf(lastFrame.path) === parentOf(path3) && lastFrame.signature === signature ? lastFrame.chainId : `docx:frame-chain:${path3}`;
11778
+ lastFrame = { path: path3, signature, chainId };
11779
+ const fact = frameTextFact(props, path3, page, chainId, flowIndex);
11780
+ if (fact) addFact(fact);
11781
+ }
11782
+ }
11783
+ if (node.name === "image" || node.name === "visual") {
11784
+ for (const fact of svgTextFacts(props, path3)) addFact(fact);
11785
+ }
11786
+ if (node.name === "heading") {
11787
+ const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
11788
+ addFact({
11789
+ id: `docx:heading:${path3}`,
11790
+ kind: "docx/heading",
11791
+ path: `${path3}/props/level`,
11792
+ level,
11793
+ ...previousHeadingLevel !== void 0 && {
11794
+ previousLevel: previousHeadingLevel
11795
+ }
11796
+ });
11797
+ previousHeadingLevel = level;
11798
+ }
11799
+ previousVisitPath = path3;
11800
+ };
11801
+ resolved.children.forEach((component, index) => {
11802
+ const rec = component;
11803
+ if (rec.name === "section") flowIndex += 1;
11804
+ const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
11805
+ walkActive(component, `/children/${index}`, page, visit);
11806
+ });
11807
+ return {
11808
+ format: "docx",
11809
+ model: {
11810
+ authored: document,
11811
+ context,
11812
+ document: resolved,
11813
+ themeName: context.themeName
11814
+ },
11815
+ facts,
11816
+ provenance,
11817
+ renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
11818
+ };
11819
+ }
11820
+
11330
11821
  // src/core/generateFromIr.ts
11331
11822
  var UncompiledComponentError = class extends Error {
11332
11823
  code = "UNCOMPILED_COMPONENT";
@@ -11354,11 +11845,14 @@ async function compileDocumentToIr(document, options = {}, collector) {
11354
11845
  );
11355
11846
  }
11356
11847
  async function compileDocumentScoped(document, options, warnings) {
11357
- const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
11848
+ const prepared = options.prepared ?? prepareDocxQualityDocument(document, {
11358
11849
  customThemes: options.customThemes,
11359
11850
  fonts: options.fonts,
11360
- warnings
11851
+ warnings,
11852
+ context: options.context,
11853
+ renderer: options.renderer
11361
11854
  });
11855
+ const { context } = prepared.model;
11362
11856
  const hasVisual = collectVisualProps(context.document).length > 0;
11363
11857
  const resolvedFonts = await resolveDocumentFonts(
11364
11858
  context.document,
@@ -11374,9 +11868,10 @@ async function compileDocumentScoped(document, options, warnings) {
11374
11868
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
11375
11869
  ...visualFonts.length > 0 ? { visualFonts } : {}
11376
11870
  });
11377
- const structure = await processDocument(
11871
+ const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
11872
+ const structure = await processResolvedDocument(
11378
11873
  desugared,
11379
- context.theme,
11874
+ resolved,
11380
11875
  context.themeName,
11381
11876
  resolveGenerationDate(options)
11382
11877
  );