@json-to-office/core-docx 1.5.0 → 1.7.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";
@@ -6979,6 +7126,17 @@ function mergeBorderSizePerSide(...borders3) {
6979
7126
  left: pick("left")
6980
7127
  };
6981
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
+ }
6982
7140
  function mergePaddingPerSide(...paddings) {
6983
7141
  const normalized = paddings.map(normalizePadding);
6984
7142
  if (!normalized.some((p) => p !== void 0)) return void 0;
@@ -7118,6 +7276,10 @@ function mergeCellDefaults(tableDef, columnDef, cellDef, position, tableOuterBor
7118
7276
  tableDef?.borderSize,
7119
7277
  tableOuterBorder.borderSize
7120
7278
  ),
7279
+ borderExplicit: statedSides(
7280
+ [cellDef?.borderColor, columnDef?.borderColor],
7281
+ [cellDef?.borderSize, columnDef?.borderSize]
7282
+ ),
7121
7283
  padding: mergePaddingPerSide(
7122
7284
  cellDef?.padding,
7123
7285
  columnDef?.padding,
@@ -7175,6 +7337,10 @@ function mergeHeaderCellDefaults(tableDef, headerTableDef, columnDef, headerDef,
7175
7337
  tableDef?.borderSize,
7176
7338
  tableOuterBorder.borderSize
7177
7339
  ),
7340
+ borderExplicit: statedSides(
7341
+ [headerDef?.borderColor, columnDef?.borderColor],
7342
+ [headerDef?.borderSize, columnDef?.borderSize]
7343
+ ),
7178
7344
  padding: mergePaddingPerSide(
7179
7345
  headerDef?.padding,
7180
7346
  headerTableDef?.padding,
@@ -7205,28 +7371,20 @@ function resolveTableModel(source, theme, themeName, options = {}) {
7205
7371
  return position.isLastCol ? hidden.right : hidden.insideVertical;
7206
7372
  }
7207
7373
  };
7208
- const borders3 = (merged, position) => ({
7209
- top: {
7210
- size: merged.borderSize.top,
7211
- color: merged.borderColor.top,
7212
- hidden: isHidden("top", position)
7213
- },
7214
- right: {
7215
- size: merged.borderSize.right,
7216
- color: merged.borderColor.right,
7217
- hidden: isHidden("right", position)
7218
- },
7219
- bottom: {
7220
- size: merged.borderSize.bottom,
7221
- color: merged.borderColor.bottom,
7222
- hidden: isHidden("bottom", position)
7223
- },
7224
- left: {
7225
- size: merged.borderSize.left,
7226
- color: merged.borderColor.left,
7227
- hidden: isHidden("left", position)
7228
- }
7229
- });
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
+ };
7230
7388
  const tallest = (heights) => heights.reduce(
7231
7389
  (max, height) => height === void 0 ? max : max === void 0 ? height : Math.max(max, height),
7232
7390
  void 0
@@ -7336,6 +7494,7 @@ function resolveTableModel(source, theme, themeName, options = {}) {
7336
7494
  };
7337
7495
  return row;
7338
7496
  });
7497
+ adjudicateInteriorEdges([header, ...rows]);
7339
7498
  return {
7340
7499
  ...resolveWidths(source, columns, theme, themeName),
7341
7500
  header,
@@ -7343,6 +7502,54 @@ function resolveTableModel(source, theme, themeName, options = {}) {
7343
7502
  repeatHeader: source.repeatHeaderOnPageBreak ?? true
7344
7503
  };
7345
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
+ }
7346
7553
  function resolveWidths(source, columns, theme, themeName) {
7347
7554
  const hasExplicitWidths = columns.some((col) => col.width !== void 0);
7348
7555
  if (!hasExplicitWidths) {
@@ -10359,6 +10566,7 @@ function compileTableRow(row, spacing, baseStyle, scope) {
10359
10566
  };
10360
10567
  }
10361
10568
  function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision, markRevision) {
10569
+ const cellLine = compileLineSpacing(cell.font?.lineSpacing);
10362
10570
  const paragraph2 = {
10363
10571
  kind: "paragraph",
10364
10572
  id: scope.id,
@@ -10372,7 +10580,10 @@ function compileTableCell(cell, spacing, baseStyle, keepNext, scope, rowRevision
10372
10580
  spacing: {
10373
10581
  beforeTwips: spacing.before,
10374
10582
  afterTwips: spacing.after,
10375
- ...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) } : {}
10376
10587
  },
10377
10588
  ...keepNext ? { keepNext: true } : {}
10378
10589
  },
@@ -11423,7 +11634,22 @@ function characterSpacingPt(spacing) {
11423
11634
  if (rule === void 0 || value === void 0) return 0;
11424
11635
  return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
11425
11636
  }
11426
- function frameTextFact(props, path3, page) {
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) {
11427
11653
  const floating = asRecord(props.floating);
11428
11654
  const frameWidthTwips = finiteNumber(floating?.width);
11429
11655
  if (frameWidthTwips === void 0) return void 0;
@@ -11436,7 +11662,12 @@ function frameTextFact(props, path3, page) {
11436
11662
  (longest, word) => word.length > longest.length ? word : longest,
11437
11663
  ""
11438
11664
  );
11439
- const offsetY = finiteNumber(asRecord(floating?.verticalPosition)?.offset);
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);
11440
11671
  return {
11441
11672
  id: `docx:frame-text:${path3}`,
11442
11673
  kind: "docx/frame-text",
@@ -11451,7 +11682,14 @@ function frameTextFact(props, path3, page) {
11451
11682
  },
11452
11683
  ...offsetY !== void 0 && { offsetYTwips: offsetY },
11453
11684
  pageBottomTwips: page.pageBottomTwips,
11454
- longestWord
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
11455
11693
  };
11456
11694
  }
11457
11695
  var SVG_VIEWBOX = /viewBox\s*=\s*"\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*"/;
@@ -11522,6 +11760,10 @@ function prepareDocxQualityDocument(document, options = {}) {
11522
11760
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
11523
11761
  };
11524
11762
  };
11763
+ let previousVisitPath;
11764
+ let lastFrame;
11765
+ let flowIndex = 0;
11766
+ const parentOf = (path3) => path3.slice(0, path3.lastIndexOf("/"));
11525
11767
  const visit = (node, path3, page) => {
11526
11768
  const props = asRecord(node.props) ?? {};
11527
11769
  if (node.name === "table") {
@@ -11529,8 +11771,14 @@ function prepareDocxQualityDocument(document, options = {}) {
11529
11771
  if (fact) addFact(fact);
11530
11772
  }
11531
11773
  if (node.name === "paragraph" || node.name === "text-box") {
11532
- const fact = frameTextFact(props, path3, page);
11533
- if (fact) addFact(fact);
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
+ }
11534
11782
  }
11535
11783
  if (node.name === "image" || node.name === "visual") {
11536
11784
  for (const fact of svgTextFacts(props, path3)) addFact(fact);
@@ -11548,9 +11796,11 @@ function prepareDocxQualityDocument(document, options = {}) {
11548
11796
  });
11549
11797
  previousHeadingLevel = level;
11550
11798
  }
11799
+ previousVisitPath = path3;
11551
11800
  };
11552
11801
  resolved.children.forEach((component, index) => {
11553
11802
  const rec = component;
11803
+ if (rec.name === "section") flowIndex += 1;
11554
11804
  const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
11555
11805
  walkActive(component, `/children/${index}`, page, visit);
11556
11806
  });