@json-to-office/core-docx 2.3.0 → 2.5.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.
Files changed (33) hide show
  1. package/dist/components/highcharts.d.ts.map +1 -1
  2. package/dist/core/generationContext.d.ts.map +1 -1
  3. package/dist/index.js +653 -52
  4. package/dist/index.js.map +1 -1
  5. package/dist/ir/compiler.d.ts.map +1 -1
  6. package/dist/plugin/example/index.js +476 -61
  7. package/dist/plugin/example/index.js.map +1 -1
  8. package/dist/quality/facts.d.ts +56 -2
  9. package/dist/quality/facts.d.ts.map +1 -1
  10. package/dist/quality/rules.d.ts +16 -0
  11. package/dist/quality/rules.d.ts.map +1 -1
  12. package/dist/renderers/docxjs/emit.d.ts.map +1 -1
  13. package/dist/renderers/docxjs/index.d.ts.map +1 -1
  14. package/dist/renderers/office-open/index.d.ts.map +1 -1
  15. package/dist/styles/themeToStyles.d.ts.map +1 -1
  16. package/dist/styles/utils/colorUtils.d.ts +12 -1
  17. package/dist/styles/utils/colorUtils.d.ts.map +1 -1
  18. package/dist/styles/utils/styleHelpers.d.ts +2 -0
  19. package/dist/styles/utils/styleHelpers.d.ts.map +1 -1
  20. package/dist/templates/themes/index.d.ts +1896 -668
  21. package/dist/templates/themes/index.d.ts.map +1 -1
  22. package/dist/themes/defaults.d.ts +196 -181
  23. package/dist/themes/defaults.d.ts.map +1 -1
  24. package/dist/themes/design-system.d.ts +4 -0
  25. package/dist/themes/design-system.d.ts.map +1 -0
  26. package/dist/themes/overrides.d.ts +2 -1
  27. package/dist/themes/overrides.d.ts.map +1 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/dist/utils/normalizeTextCase.d.ts +15 -0
  30. package/dist/utils/normalizeTextCase.d.ts.map +1 -0
  31. package/dist/utils/packageDocument.d.ts +2 -0
  32. package/dist/utils/packageDocument.d.ts.map +1 -1
  33. package/package.json +4 -4
@@ -9,8 +9,10 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/themes/defaults.ts
12
+ import { designColors } from "@json-to-office/shared";
12
13
  function ensureThemeDefaults(theme) {
13
14
  return {
15
+ ...theme,
14
16
  $schema: theme.$schema,
15
17
  name: theme.name || "default",
16
18
  displayName: theme.displayName || "Default Theme",
@@ -21,6 +23,7 @@ function ensureThemeDefaults(theme) {
21
23
  ...theme.colors || {}
22
24
  },
23
25
  fonts: {
26
+ ...theme.fonts || {},
24
27
  heading: { ...DEFAULT_FONTS.heading, ...theme.fonts?.heading || {} },
25
28
  body: { ...DEFAULT_FONTS.body, ...theme.fonts?.body || {} },
26
29
  mono: { ...DEFAULT_FONTS.mono, ...theme.fonts?.mono || {} },
@@ -39,10 +42,13 @@ function ensureThemeDefaults(theme) {
39
42
  };
40
43
  }
41
44
  function getThemeColors(theme) {
42
- return {
43
- ...DEFAULT_COLORS,
44
- ...theme.colors || {}
45
- };
45
+ return designColors(
46
+ {
47
+ ...DEFAULT_COLORS,
48
+ ...theme.colors || {}
49
+ },
50
+ theme.palette
51
+ );
46
52
  }
47
53
  function getThemeFonts(theme) {
48
54
  return {
@@ -871,6 +877,10 @@ var init_themes = __esm({
871
877
  });
872
878
 
873
879
  // src/styles/utils/colorUtils.ts
880
+ import {
881
+ DEFAULT_CHART_THEME_COLORS,
882
+ resolveDesignColor
883
+ } from "@json-to-office/shared";
874
884
  function resolveColor(colorValue, theme) {
875
885
  if (colorValue.startsWith("#")) {
876
886
  const hexValue = colorValue.slice(1);
@@ -884,7 +894,11 @@ function resolveColor(colorValue, theme) {
884
894
  const colors = getThemeColors(theme);
885
895
  const resolvedColor = colors[colorValue];
886
896
  if (typeof resolvedColor === "string") {
887
- return resolveColor(resolvedColor, theme);
897
+ const hex = resolveDesignColor(colorValue, colors);
898
+ if (hex) return hex.toUpperCase();
899
+ throw new Error(
900
+ `Unresolvable theme color: "${colorValue}" (missing reference or cycle).`
901
+ );
888
902
  }
889
903
  if (/^[0-9A-Fa-f]{6}$/.test(colorValue)) {
890
904
  return colorValue.toUpperCase();
@@ -893,6 +907,15 @@ function resolveColor(colorValue, theme) {
893
907
  `Invalid color value: "${colorValue}". Must be a hex color with # prefix (e.g., "#000000") or a valid theme color name.`
894
908
  );
895
909
  }
910
+ function chartPaletteValues(theme) {
911
+ const explicit = theme.palette?.chart;
912
+ if (explicit) return [...explicit];
913
+ const colors = getThemeColors(theme);
914
+ return DEFAULT_CHART_THEME_COLORS.flatMap((token) => {
915
+ const value = colors[token];
916
+ return typeof value === "string" && value.length > 0 ? [value] : [];
917
+ });
918
+ }
896
919
  var init_colorUtils = __esm({
897
920
  "src/styles/utils/colorUtils.ts"() {
898
921
  "use strict";
@@ -982,6 +1005,8 @@ function resolveFontProperties(theme, fontRef) {
982
1005
  size: fontDef.size,
983
1006
  color: fontDef.color,
984
1007
  bold: fontDef.bold,
1008
+ fontWeight: fontDef.fontWeight,
1009
+ case: fontDef.case,
985
1010
  italic: fontDef.italic,
986
1011
  underline: fontDef.underline,
987
1012
  alignment: fontDef.alignment,
@@ -2042,6 +2067,9 @@ function runOptions(formatting) {
2042
2067
  options.underline = formatting.underline ? { type: formatting.underline.type } : void 0;
2043
2068
  }
2044
2069
  if (formatting.strike !== void 0) options.strike = formatting.strike;
2070
+ if (formatting.allCaps !== void 0) options.allCaps = formatting.allCaps;
2071
+ if (formatting.smallCaps !== void 0)
2072
+ options.smallCaps = formatting.smallCaps;
2045
2073
  if (formatting.scalePercent !== void 0) {
2046
2074
  options.scale = formatting.scalePercent;
2047
2075
  }
@@ -2732,6 +2760,44 @@ var init_styles2 = __esm({
2732
2760
  }
2733
2761
  });
2734
2762
 
2763
+ // src/utils/normalizeTextCase.ts
2764
+ function normalizeTextCase(zip) {
2765
+ let changed = false;
2766
+ for (const entry of zip.getEntries()) {
2767
+ if (!WORD_XML.test(entry.entryName)) continue;
2768
+ const xml = entry.getData().toString("utf8");
2769
+ if (!ANY_CASE_FLAG.test(xml)) continue;
2770
+ const normalized = xml.replace(RUN_PROPERTIES, completeCaseFlags);
2771
+ if (normalized === xml) continue;
2772
+ zip.updateFile(entry, Buffer.from(normalized, "utf8"));
2773
+ changed = true;
2774
+ }
2775
+ return changed;
2776
+ }
2777
+ function completeCaseFlags(runProperties3) {
2778
+ const flag = CASE_FLAG.exec(runProperties3);
2779
+ if (!flag) return runProperties3;
2780
+ const isCaps = flag[1] === "caps";
2781
+ const opposite = `<w:${isCaps ? "smallCaps" : "caps"} w:val="false"/>`;
2782
+ if (runProperties3.includes(`<w:${isCaps ? "smallCaps" : "caps"}`)) {
2783
+ return runProperties3;
2784
+ }
2785
+ return runProperties3.replace(
2786
+ flag[0],
2787
+ isCaps ? `${flag[0]}${opposite}` : `${opposite}${flag[0]}`
2788
+ );
2789
+ }
2790
+ var WORD_XML, ANY_CASE_FLAG, RUN_PROPERTIES, CASE_FLAG;
2791
+ var init_normalizeTextCase = __esm({
2792
+ "src/utils/normalizeTextCase.ts"() {
2793
+ "use strict";
2794
+ WORD_XML = /^word\/.*\.xml$/;
2795
+ ANY_CASE_FLAG = /<w:(?:caps|smallCaps)\b/;
2796
+ RUN_PROPERTIES = /<w:rPr\b[^>]*>[\s\S]*?<\/w:rPr>/g;
2797
+ CASE_FLAG = /<w:(caps|smallCaps)\b[^>]*(?:\/>|>[\s\S]*?<\/w:\1>)/;
2798
+ }
2799
+ });
2800
+
2735
2801
  // src/utils/packageDocument.ts
2736
2802
  import AdmZip from "adm-zip";
2737
2803
  function resolveGenerationDate(options) {
@@ -2800,6 +2866,7 @@ function canonicalizeRelationshipIds(zip) {
2800
2866
  }
2801
2867
  function canonicalizeDocxBuffer(buffer, generatedAt = DEFAULT_GENERATION_DATE) {
2802
2868
  const zip = new AdmZip(buffer);
2869
+ normalizeTextCase(zip);
2803
2870
  canonicalizeRelationshipIds(zip);
2804
2871
  const isoTimestamp = generatedAt.toISOString();
2805
2872
  const coreProperties3 = zip.getEntry("docProps/core.xml");
@@ -2816,10 +2883,15 @@ function canonicalizeDocxBuffer(buffer, generatedAt = DEFAULT_GENERATION_DATE) {
2816
2883
  }
2817
2884
  return zip.toBuffer();
2818
2885
  }
2886
+ function normalizeDocxCaseBuffer(buffer) {
2887
+ const zip = new AdmZip(buffer);
2888
+ return normalizeTextCase(zip) ? zip.toBuffer() : buffer;
2889
+ }
2819
2890
  var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID;
2820
2891
  var init_packageDocument = __esm({
2821
2892
  "src/utils/packageDocument.ts"() {
2822
2893
  "use strict";
2894
+ init_normalizeTextCase();
2823
2895
  DEFAULT_GENERATION_DATE = /* @__PURE__ */ new Date("2000-01-01T00:00:00.000Z");
2824
2896
  STABLE_RELATIONSHIP_ID = /^rId\d+$/;
2825
2897
  }
@@ -2878,7 +2950,8 @@ function createDocxJsRenderer() {
2878
2950
  const document = buildDocument(ir, resources);
2879
2951
  const packed = await Packer.toBuffer(document);
2880
2952
  const fixed = fixFloatingImageIdsInBuffer(packed);
2881
- if (renderOptions?.deterministic === false) return new Uint8Array(fixed);
2953
+ if (renderOptions?.deterministic === false)
2954
+ return new Uint8Array(normalizeDocxCaseBuffer(fixed));
2882
2955
  return new Uint8Array(
2883
2956
  canonicalizeDocxBuffer(
2884
2957
  fixed,
@@ -4157,7 +4230,8 @@ async function createOfficeOpenDocxRenderer() {
4157
4230
  spliceChartParts(zip, charts);
4158
4231
  raw = zip.toBuffer();
4159
4232
  }
4160
- if (options?.deterministic === false) return new Uint8Array(raw);
4233
+ if (options?.deterministic === false)
4234
+ return new Uint8Array(normalizeDocxCaseBuffer(raw));
4161
4235
  return new Uint8Array(
4162
4236
  canonicalizeDocxBuffer(
4163
4237
  raw,
@@ -4516,6 +4590,7 @@ init_themes();
4516
4590
  import { applyExportMode } from "@json-to-office/shared";
4517
4591
 
4518
4592
  // src/themes/overrides.ts
4593
+ import { mergeWithDefaults } from "@json-to-office/shared";
4519
4594
  function applyThemeOverrides(theme, overrides) {
4520
4595
  if (!overrides) return theme;
4521
4596
  const fonts = { ...theme.fonts };
@@ -4529,12 +4604,87 @@ function applyThemeOverrides(theme, overrides) {
4529
4604
  }
4530
4605
  return {
4531
4606
  ...theme,
4607
+ ...Object.fromEntries(
4608
+ ["palette", "typography", "spacing", "chrome", "motif"].flatMap(
4609
+ (name) => {
4610
+ const key = name;
4611
+ return overrides[key] === void 0 ? [] : [[key, mergeWithDefaults(overrides[key], theme[key] ?? {})]];
4612
+ }
4613
+ )
4614
+ ),
4532
4615
  colors: { ...theme.colors, ...overrides.colors ?? {} },
4533
4616
  fonts,
4534
4617
  ...Object.keys(styles).length > 0 && { styles }
4535
4618
  };
4536
4619
  }
4537
4620
 
4621
+ // src/themes/design-system.ts
4622
+ init_defaults();
4623
+ import {
4624
+ designCanvas,
4625
+ resolveTypeRoles,
4626
+ validateDesignColors
4627
+ } from "@json-to-office/shared";
4628
+ function resolveDocxDesignSystem(theme) {
4629
+ validateDesignColors(theme, getThemeColors(theme));
4630
+ if (!theme.typography && !theme.spacing) return theme;
4631
+ const canvas = designCanvas("docx", theme.page.size);
4632
+ const roles = resolveTypeRoles(theme, canvas, theme.fonts.body.size ?? 11);
4633
+ const styles = { ...theme.styles };
4634
+ for (const [name, role] of Object.entries(roles)) {
4635
+ const spacing = {
4636
+ ...role.spaceBefore !== void 0 && { before: role.spaceBefore },
4637
+ ...role.spaceAfter !== void 0 && { after: role.spaceAfter }
4638
+ };
4639
+ styles[name] = {
4640
+ font: role.face ?? "body",
4641
+ size: role.size,
4642
+ ...role.weight !== void 0 && { fontWeight: role.weight },
4643
+ ...role.color !== void 0 && { color: role.color },
4644
+ ...role.case !== void 0 && { case: role.case },
4645
+ ...role.lineHeight !== void 0 && {
4646
+ lineSpacing: { type: "multiple", value: role.lineHeight }
4647
+ },
4648
+ ...role.tracking !== void 0 && {
4649
+ characterSpacing: {
4650
+ type: role.tracking < 0 ? "condensed" : "expanded",
4651
+ value: Math.abs(role.tracking) * role.size / 5
4652
+ }
4653
+ },
4654
+ ...Object.keys(spacing).length && { spacing },
4655
+ ...styles[name]
4656
+ };
4657
+ }
4658
+ const gap = theme.spacing?.blockGap?.normal ?? theme.spacing?.basePt;
4659
+ const scale = theme.typography?.scale?.[canvas];
4660
+ if (scale || gap !== void 0) {
4661
+ styles.normal = {
4662
+ ...scale && { size: scale.base },
4663
+ ...gap !== void 0 && { spacing: { after: gap } },
4664
+ ...styles.normal
4665
+ };
4666
+ }
4667
+ const space = theme.spacing?.canvas?.[canvas];
4668
+ const safe = space?.safeAreaIn;
4669
+ return {
4670
+ ...theme,
4671
+ styles,
4672
+ page: {
4673
+ ...theme.page,
4674
+ margins: {
4675
+ ...theme.page.margins,
4676
+ ...safe !== void 0 && {
4677
+ top: safe * 1440,
4678
+ bottom: safe * 1440,
4679
+ left: safe * 1440,
4680
+ right: safe * 1440
4681
+ },
4682
+ ...space?.gutterIn !== void 0 && { gutter: space.gutterIn * 1440 }
4683
+ }
4684
+ }
4685
+ };
4686
+ }
4687
+
4538
4688
  // src/core/generationContext.ts
4539
4689
  function defaultNamedThemeLookup(themeName, customThemes, warnings) {
4540
4690
  if (customThemes) {
@@ -4567,6 +4717,7 @@ function resolveThemeContext(documentIn, options = {}) {
4567
4717
  if (themeOverrides) {
4568
4718
  theme = applyThemeOverrides(theme, themeOverrides);
4569
4719
  }
4720
+ theme = resolveDocxDesignSystem(theme);
4570
4721
  const mode = applyExportMode({ doc: document, theme, fonts });
4571
4722
  for (const w of mode.warnings) {
4572
4723
  if (warnings) {
@@ -5214,7 +5365,6 @@ import {
5214
5365
 
5215
5366
  // src/components/highcharts.ts
5216
5367
  init_colorUtils();
5217
- import { DEFAULT_CHART_THEME_COLORS } from "@json-to-office/shared";
5218
5368
  var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
5219
5369
  async function generateChart(config, servicesConfig) {
5220
5370
  if (!isNodeEnvironment()) {
@@ -5268,11 +5418,10 @@ function toChartColor(value, theme) {
5268
5418
  function withThemeColors(config, theme) {
5269
5419
  const options = config.options;
5270
5420
  if (!options || options.colors || !theme?.colors) return config;
5271
- const themeColors = theme.colors;
5272
- const palette = DEFAULT_CHART_THEME_COLORS.map((token) => {
5273
- const value = themeColors[token];
5274
- return typeof value === "string" && value.length > 0 ? toChartColor(value, theme) : void 0;
5275
- }).filter((c) => c !== void 0);
5421
+ const palette = chartPaletteValues(theme).flatMap((value) => {
5422
+ const color = toChartColor(value, theme);
5423
+ return color ? [color] : [];
5424
+ });
5276
5425
  if (palette.length === 0) return config;
5277
5426
  return {
5278
5427
  ...config,
@@ -5518,10 +5667,7 @@ init_colorUtils();
5518
5667
  import {
5519
5668
  FeatureRequirementCollector
5520
5669
  } from "@json-to-office/shared/rendering";
5521
- import {
5522
- synthesizeFamilyName,
5523
- DEFAULT_CHART_THEME_COLORS as DEFAULT_CHART_THEME_COLORS2
5524
- } from "@json-to-office/shared";
5670
+ import { capsFormatting as capsFormatting2, synthesizeFamilyName as synthesizeFamilyName2 } from "@json-to-office/shared";
5525
5671
  import {
5526
5672
  isNativeVisualProps as isNativeVisualProps4
5527
5673
  } from "@json-to-office/shared-docx";
@@ -6037,6 +6183,7 @@ function resolveColors(entries, at, deps) {
6037
6183
  init_themes();
6038
6184
  init_colorUtils();
6039
6185
  init_styleHelpers();
6186
+ import { capsFormatting, synthesizeFamilyName } from "@json-to-office/shared";
6040
6187
  var RIGHT_MARGIN_TAB_TWIPS = 9026 * 2;
6041
6188
  var STATISTIC_NUMBER_STYLE_ID = "StatisticNumber";
6042
6189
  var STATISTIC_DESCRIPTION_STYLE_ID = "StatisticDescription";
@@ -6045,25 +6192,8 @@ var STATISTIC_SIZE_POINTS = {
6045
6192
  medium: 28,
6046
6193
  large: 40
6047
6194
  };
6048
- function isValidStyleName(name) {
6049
- return [
6050
- "normal",
6051
- "heading1",
6052
- "heading2",
6053
- "heading3",
6054
- "heading4",
6055
- "heading5",
6056
- "heading6",
6057
- "title",
6058
- "subtitle"
6059
- ].includes(name);
6060
- }
6061
6195
  function getStyleSafe(theme, styleName) {
6062
- if (!isValidStyleName(styleName)) {
6063
- console.warn(`Invalid style name: ${styleName}`);
6064
- return void 0;
6065
- }
6066
- return theme.styles?.[styleName];
6196
+ return theme.styles && Object.hasOwn(theme.styles, styleName) ? theme.styles[styleName] : void 0;
6067
6197
  }
6068
6198
  function resolveStyleWithBaseStyle(theme, styleName, visited = /* @__PURE__ */ new Set()) {
6069
6199
  if (visited.has(styleName)) {
@@ -6120,9 +6250,17 @@ function resolveSpacing(spacing) {
6120
6250
  ...spacing?.after ? { afterTwips: pointsToTwips(spacing.after) } : {}
6121
6251
  };
6122
6252
  }
6253
+ function weightedFamily(props) {
6254
+ return props.fontWeight === void 0 ? void 0 : synthesizeFamilyName(
6255
+ props.family || "Arial",
6256
+ props.fontWeight,
6257
+ props.italic === true
6258
+ );
6259
+ }
6123
6260
  function convertRunProperties(merged, theme, defaultColor, defaultSize) {
6261
+ const weighted = weightedFamily(merged);
6124
6262
  return {
6125
- fontFamily: merged.family || "Arial",
6263
+ fontFamily: weighted?.family ?? merged.family ?? "Arial",
6126
6264
  sizeHalfPoints: (merged.size || defaultSize || 11) * 2,
6127
6265
  color: {
6128
6266
  hex: resolveColor(
@@ -6132,6 +6270,8 @@ function convertRunProperties(merged, theme, defaultColor, defaultSize) {
6132
6270
  },
6133
6271
  ...merged.bold !== void 0 && { bold: merged.bold },
6134
6272
  ...merged.italic !== void 0 && { italic: merged.italic },
6273
+ ...weighted && { bold: weighted.bold, italic: weighted.italic },
6274
+ ...merged.case !== void 0 && capsFormatting(merged.case),
6135
6275
  ...merged.underline !== void 0 && merged.underline && { underline: { type: "single" } },
6136
6276
  ...merged.characterSpacing && {
6137
6277
  characterSpacingTwentieths: merged.characterSpacing.type === "condensed" ? -merged.characterSpacing.value : merged.characterSpacing.value
@@ -6204,12 +6344,15 @@ function convertBorders(borders3, theme) {
6204
6344
  return Object.keys(converted).length > 0 ? converted : void 0;
6205
6345
  }
6206
6346
  function fallbackRun(fontProps, theme, defaultColor, defaultSize) {
6347
+ const weighted = weightedFamily(fontProps);
6207
6348
  return {
6208
- fontFamily: fontProps.family || "Arial",
6349
+ fontFamily: weighted?.family ?? fontProps.family ?? "Arial",
6209
6350
  sizeHalfPoints: (fontProps.size || defaultSize) * 2,
6210
6351
  color: { hex: resolveColor(fontProps.color || defaultColor, theme) },
6211
6352
  ...fontProps.bold !== void 0 && { bold: fontProps.bold },
6212
6353
  ...fontProps.italic !== void 0 && { italic: fontProps.italic },
6354
+ ...weighted && { bold: weighted.bold, italic: weighted.italic },
6355
+ ...fontProps.case !== void 0 && capsFormatting(fontProps.case),
6213
6356
  ...fontProps.underline !== void 0 && fontProps.underline && { underline: { type: "single" } }
6214
6357
  };
6215
6358
  }
@@ -6239,6 +6382,8 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6239
6382
  size: normalStyle?.size,
6240
6383
  color: normalStyle?.color,
6241
6384
  bold: normalStyle?.bold,
6385
+ fontWeight: normalStyle?.fontWeight,
6386
+ case: normalStyle?.case,
6242
6387
  italic: normalStyle?.italic,
6243
6388
  underline: normalStyle?.underline,
6244
6389
  characterSpacing: normalStyle?.characterSpacing,
@@ -6278,6 +6423,8 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6278
6423
  size: headingStyle.size,
6279
6424
  color: headingStyle.color,
6280
6425
  bold: headingStyle.bold,
6426
+ fontWeight: headingStyle.fontWeight,
6427
+ case: headingStyle.case,
6281
6428
  italic: headingStyle.italic,
6282
6429
  underline: headingStyle.underline,
6283
6430
  characterSpacing: headingStyle.characterSpacing,
@@ -6338,6 +6485,8 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6338
6485
  size: headingStyle.size,
6339
6486
  color: headingStyle.color,
6340
6487
  bold: headingStyle.bold,
6488
+ fontWeight: headingStyle.fontWeight,
6489
+ case: headingStyle.case,
6341
6490
  italic: headingStyle.italic,
6342
6491
  underline: headingStyle.underline,
6343
6492
  characterSpacing: headingStyle.characterSpacing,
@@ -6399,6 +6548,8 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6399
6548
  size: titleStyle.size,
6400
6549
  color: titleStyle.color,
6401
6550
  bold: titleStyle.bold,
6551
+ fontWeight: titleStyle.fontWeight,
6552
+ case: titleStyle.case,
6402
6553
  italic: titleStyle.italic,
6403
6554
  underline: titleStyle.underline,
6404
6555
  characterSpacing: titleStyle.characterSpacing,
@@ -6454,6 +6605,8 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6454
6605
  size: subtitleStyle.size,
6455
6606
  color: subtitleStyle.color,
6456
6607
  bold: subtitleStyle.bold,
6608
+ fontWeight: subtitleStyle.fontWeight,
6609
+ case: subtitleStyle.case,
6457
6610
  italic: subtitleStyle.italic,
6458
6611
  underline: subtitleStyle.underline,
6459
6612
  characterSpacing: subtitleStyle.characterSpacing,
@@ -6576,6 +6729,8 @@ function createDocumentStyles(themeNameOrObject = "minimal", language) {
6576
6729
  size: customStyle.size,
6577
6730
  color: customStyle.color,
6578
6731
  bold: customStyle.bold,
6732
+ fontWeight: customStyle.fontWeight,
6733
+ case: customStyle.case,
6579
6734
  italic: customStyle.italic,
6580
6735
  underline: customStyle.underline,
6581
6736
  characterSpacing: customStyle.characterSpacing,
@@ -8451,6 +8606,7 @@ function compileChromeParagraph(component, scope) {
8451
8606
  italic: font.italic ?? false,
8452
8607
  ...font.underline !== void 0 ? { underline: font.underline } : {},
8453
8608
  ...font.fontWeight !== void 0 ? { fontWeight: font.fontWeight } : {},
8609
+ ...font.case !== void 0 ? { case: font.case } : {},
8454
8610
  ...font.lineSpacing !== void 0 ? { lineSpacing: font.lineSpacing } : {}
8455
8611
  }
8456
8612
  };
@@ -9754,7 +9910,11 @@ function isoDateTime(date) {
9754
9910
  }
9755
9911
  function runFormatting3(font, props, ctx) {
9756
9912
  const hasWeightRequest = font.fontWeight != null || font.bold === true;
9757
- const effectiveFamily = font.family ?? (hasWeightRequest ? resolveBodyFamily(ctx.theme) : void 0);
9913
+ const roleName = props.themeStyle;
9914
+ const role = roleName ? ctx.theme.typography?.roles?.[roleName] : void 0;
9915
+ const roleStyles = ctx.theme.styles;
9916
+ const roleFamily = role && roleName ? resolveFontFamily(ctx.theme, roleStyles?.[roleName]?.font ?? role.face) : void 0;
9917
+ const effectiveFamily = font.family ?? (hasWeightRequest ? roleFamily ?? resolveBodyFamily(ctx.theme) : void 0);
9758
9918
  const weighted = applyFontWeightAlias({
9759
9919
  fontFamily: effectiveFamily,
9760
9920
  bold: font.bold,
@@ -9762,7 +9922,7 @@ function runFormatting3(font, props, ctx) {
9762
9922
  fontWeight: font.fontWeight
9763
9923
  });
9764
9924
  const formatting = {};
9765
- if (font.family || weighted.font && weighted.font !== effectiveFamily) {
9925
+ if (font.family || hasWeightRequest && roleFamily || weighted.font && weighted.font !== effectiveFamily) {
9766
9926
  formatting.fontFamily = weighted.font;
9767
9927
  }
9768
9928
  if (font.size) formatting.sizeHalfPoints = pointsToHalfPoints(font.size);
@@ -9774,6 +9934,8 @@ function runFormatting3(font, props, ctx) {
9774
9934
  formatting.underline = font.underline ? { type: "single" } : void 0;
9775
9935
  }
9776
9936
  if (font.scale) formatting.scalePercent = font.scale;
9937
+ if (font.case !== void 0)
9938
+ Object.assign(formatting, capsFormatting2(font.case));
9777
9939
  if (font.characterSpacing) {
9778
9940
  const { type, value } = font.characterSpacing;
9779
9941
  formatting.characterSpacingTwentieths = type === "condensed" ? -value : value;
@@ -9787,7 +9949,7 @@ function applyFontWeightAlias(opts) {
9787
9949
  return { bold: opts.bold, italic: opts.italic };
9788
9950
  }
9789
9951
  const weight = opts.fontWeight ?? (opts.bold === true ? 700 : void 0);
9790
- const synth = synthesizeFamilyName(
9952
+ const synth = synthesizeFamilyName2(
9791
9953
  opts.fontFamily,
9792
9954
  weight,
9793
9955
  opts.italic === true
@@ -10126,15 +10288,13 @@ function compileChart(component, scope) {
10126
10288
  }
10127
10289
  const colors = Array.isArray(props.chartColors) ? props.chartColors.map(
10128
10290
  (color) => resolveColor(String(color), ctx.theme)
10129
- ) : DEFAULT_CHART_THEME_COLORS2.map((token) => {
10130
- const value = ctx.theme?.colors?.[token];
10131
- if (typeof value !== "string" || value.length === 0) return void 0;
10291
+ ) : chartPaletteValues(ctx.theme).flatMap((value) => {
10132
10292
  try {
10133
- return resolveColor(token, ctx.theme);
10293
+ return [resolveColor(value, ctx.theme)];
10134
10294
  } catch {
10135
- return void 0;
10295
+ return [];
10136
10296
  }
10137
- }).filter((color) => color !== void 0);
10297
+ });
10138
10298
  const page = getPageSetup(ctx.theme);
10139
10299
  const contentWidthInches = (page.size.width - page.margin.left - page.margin.right) / 1440;
10140
10300
  const widthInches = typeof props.width === "number" ? props.width : contentWidthInches;
@@ -11201,8 +11361,8 @@ var formatDate = (date, formatString = "MMMM d, yyyy") => {
11201
11361
  };
11202
11362
 
11203
11363
  // src/styles/utils/componentDefaults.ts
11204
- import { mergeWithDefaults } from "@json-to-office/shared";
11205
11364
  import { mergeWithDefaults as mergeWithDefaults2 } from "@json-to-office/shared";
11365
+ import { mergeWithDefaults as mergeWithDefaults3 } from "@json-to-office/shared";
11206
11366
  function getComponentDefaults(theme) {
11207
11367
  return theme.componentDefaults || {};
11208
11368
  }
@@ -11251,35 +11411,35 @@ function getListDefaults(theme) {
11251
11411
  }
11252
11412
  function resolveHeadingProps(props, theme) {
11253
11413
  const defaults = getHeadingDefaults(theme);
11254
- return mergeWithDefaults(props, defaults);
11414
+ return mergeWithDefaults2(props, defaults);
11255
11415
  }
11256
11416
  function resolveParagraphProps(props, theme) {
11257
11417
  const defaults = getTextDefaults(theme);
11258
- return mergeWithDefaults(props, defaults);
11418
+ return mergeWithDefaults2(props, defaults);
11259
11419
  }
11260
11420
  function resolveImageProps(props, theme) {
11261
11421
  const defaults = getImageDefaults(theme);
11262
- return mergeWithDefaults(props, defaults);
11422
+ return mergeWithDefaults2(props, defaults);
11263
11423
  }
11264
11424
  function resolveStatisticProps(props, theme) {
11265
11425
  const defaults = getStatisticDefaults(theme);
11266
- return mergeWithDefaults(props, defaults);
11426
+ return mergeWithDefaults2(props, defaults);
11267
11427
  }
11268
11428
  function resolveTableProps(props, theme) {
11269
11429
  const defaults = getTableDefaults(theme);
11270
- return mergeWithDefaults(props, defaults);
11430
+ return mergeWithDefaults2(props, defaults);
11271
11431
  }
11272
11432
  function resolveSectionProps(props, theme) {
11273
11433
  const defaults = getSectionDefaults(theme);
11274
- return mergeWithDefaults(props, defaults);
11434
+ return mergeWithDefaults2(props, defaults);
11275
11435
  }
11276
11436
  function resolveColumnsProps(props, theme) {
11277
11437
  const defaults = getColumnsDefaults(theme);
11278
- return mergeWithDefaults(props, defaults);
11438
+ return mergeWithDefaults2(props, defaults);
11279
11439
  }
11280
11440
  function resolveListProps(props, theme) {
11281
11441
  const defaults = getListDefaults(theme);
11282
- return mergeWithDefaults(props, defaults);
11442
+ return mergeWithDefaults2(props, defaults);
11283
11443
  }
11284
11444
  function resolveHighchartsProps(props, _theme) {
11285
11445
  return props;
@@ -11293,7 +11453,7 @@ function getCustomComponentDefaults(theme, componentName) {
11293
11453
  }
11294
11454
  function resolveCustomComponentProps(props, theme, componentName) {
11295
11455
  const defaults = getCustomComponentDefaults(theme, componentName);
11296
- return mergeWithDefaults(props, defaults);
11456
+ return mergeWithDefaults2(props, defaults);
11297
11457
  }
11298
11458
 
11299
11459
  // src/styles/utils/resolveComponentTree.ts
@@ -11346,7 +11506,7 @@ function resolveComponentTree(components, theme) {
11346
11506
  // src/core/structure.ts
11347
11507
  function resolveDocumentTree(document, theme) {
11348
11508
  const docDefaults = document.props.componentDefaults;
11349
- const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
11509
+ const mergedComponentDefaults = docDefaults ? mergeWithDefaults3(docDefaults, theme.componentDefaults || {}) : void 0;
11350
11510
  const docNoProofWords = document.props.noProofWords;
11351
11511
  const mergedNoProofWords = docNoProofWords || theme.noProofWords ? Array.from(
11352
11512
  /* @__PURE__ */ new Set([...theme.noProofWords || [], ...docNoProofWords || []])
@@ -11477,7 +11637,17 @@ function createRenderContext(document, theme, themeName) {
11477
11637
  }
11478
11638
 
11479
11639
  // src/quality/facts.ts
11640
+ import {
11641
+ chartEncodingFor,
11642
+ collectColorLiterals,
11643
+ collectFontFamilies,
11644
+ collectPlaceholders,
11645
+ hasUnitMarker,
11646
+ normalizeHex,
11647
+ normalizeHighchartsChart
11648
+ } from "@json-to-office/quality";
11480
11649
  import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
11650
+ import { designColors as designColors2, resolveDesignColor as resolveDesignColor2 } from "@json-to-office/shared";
11481
11651
 
11482
11652
  // src/json/normalizer.ts
11483
11653
  function normalizeComponent(component) {
@@ -11625,6 +11795,164 @@ function tableFact(props, path3, availableWidthTwips) {
11625
11795
  explicitWidths
11626
11796
  };
11627
11797
  }
11798
+ var SERIES_COLOR_TOKENS = [
11799
+ "primary",
11800
+ "accent",
11801
+ "secondary",
11802
+ "accent4",
11803
+ "accent5",
11804
+ "accent6"
11805
+ ];
11806
+ function chartFact(node, props, path3, paletteTokens) {
11807
+ const base = {
11808
+ id: `docx:chart:${path3}`,
11809
+ kind: "docx/chart",
11810
+ path: path3,
11811
+ componentName: typeof node.name === "string" ? node.name : "",
11812
+ paletteTokens
11813
+ };
11814
+ if (node.name === "highcharts") {
11815
+ const shape = normalizeHighchartsChart(props.options);
11816
+ return {
11817
+ ...base,
11818
+ chartType: shape.chartType,
11819
+ encoding: shape.encoding,
11820
+ threeD: shape.threeD,
11821
+ seriesCount: shape.seriesCount,
11822
+ categoryCount: shape.categoryCount,
11823
+ seriesColorsStated: shape.seriesColorsStated,
11824
+ seriesColorsPath: `${path3}/props/options`,
11825
+ ...shape.valueAxisMin !== void 0 && {
11826
+ valueAxisMin: shape.valueAxisMin
11827
+ },
11828
+ unitStated: shape.unitStated,
11829
+ annotation: {
11830
+ stated: shape.annotationStated,
11831
+ path: path3,
11832
+ slot: "props.options.caption.text"
11833
+ }
11834
+ };
11835
+ }
11836
+ const chartType = typeof props.type === "string" ? props.type : "";
11837
+ if (chartType === "") return void 0;
11838
+ const series = Array.isArray(props.data) ? props.data : [];
11839
+ const categoryCount = Math.max(
11840
+ 0,
11841
+ ...series.map((entry) => {
11842
+ const record = asRecord(entry);
11843
+ const labels = Array.isArray(record?.labels) ? record.labels.length : 0;
11844
+ const values = Array.isArray(record?.values) ? record.values.length : 0;
11845
+ return Math.max(labels, values);
11846
+ })
11847
+ );
11848
+ const caption = typeof props.caption === "string" ? props.caption : "";
11849
+ const title = typeof props.title === "string" ? props.title : "";
11850
+ return {
11851
+ ...base,
11852
+ chartType,
11853
+ encoding: chartEncodingFor(chartType),
11854
+ // No 3D type exists in the DOCX chart schema — `office-open` draws none —
11855
+ // so this is always false here, and stays a field rather than an omission
11856
+ // so both formats answer the same questions.
11857
+ threeD: false,
11858
+ seriesCount: series.length,
11859
+ categoryCount,
11860
+ seriesColorsStated: Array.isArray(props.chartColors) && props.chartColors.length > 0,
11861
+ seriesColorsPath: `${path3}/props/chartColors`,
11862
+ // No `valueAxisMin`: a DOCX chart has no axis-floor property, so the
11863
+ // baseline is always the renderer's, which starts a bar at zero.
11864
+ unitStated: hasUnitMarker(
11865
+ typeof props.valAxisTitle === "string" ? props.valAxisTitle : ""
11866
+ ) || hasUnitMarker(title) || hasUnitMarker(caption),
11867
+ annotation: {
11868
+ stated: caption.trim() !== "",
11869
+ path: path3,
11870
+ slot: "props.caption"
11871
+ }
11872
+ };
11873
+ }
11874
+ var DOCX_ALIGNMENTS = /* @__PURE__ */ new Set(["left", "center", "right", "justify"]);
11875
+ function resolvedCellText(content) {
11876
+ return typeof content === "string" ? content : "[component]";
11877
+ }
11878
+ function statesOwnBorders(authored) {
11879
+ if (!authored) return false;
11880
+ if (authored.borderSize !== void 0 || authored.borderColor !== void 0 || authored.hideBorders !== void 0) {
11881
+ return true;
11882
+ }
11883
+ const layers = [
11884
+ asRecord(authored.cellDefaults),
11885
+ asRecord(authored.headerCellDefaults)
11886
+ ];
11887
+ if (layers.some(
11888
+ (layer) => layer?.borderSize !== void 0 || layer?.borderColor !== void 0
11889
+ )) {
11890
+ return true;
11891
+ }
11892
+ const columns = Array.isArray(authored.columns) ? authored.columns : [];
11893
+ return columns.some((column) => {
11894
+ const record = asRecord(column);
11895
+ const defaults = asRecord(record?.cellDefaults);
11896
+ return defaults?.borderSize !== void 0 || defaults?.borderColor !== void 0;
11897
+ });
11898
+ }
11899
+ function tableDesignFact(props, path3, theme, themeName, authored) {
11900
+ const authoredColumns = Array.isArray(props.columns) ? props.columns : [];
11901
+ if (authoredColumns.length === 0) return void 0;
11902
+ const model = resolveTableModel(
11903
+ props,
11904
+ theme,
11905
+ themeName
11906
+ );
11907
+ const bodyRows = model.rows;
11908
+ const drawnRows = bodyRows.length + (model.header ? 1 : 0);
11909
+ if (drawnRows === 0) return void 0;
11910
+ const fullGrid = statesOwnBorders(authored) && [...model.header ? [model.header] : [], ...bodyRows].every(
11911
+ (row) => row.cells.every(
11912
+ (cell) => ["top", "right", "bottom", "left"].every(
11913
+ (side) => !cell.borders[side].hidden && cell.borders[side].size > 0
11914
+ )
11915
+ )
11916
+ );
11917
+ const columns = authoredColumns.map(
11918
+ (column, index) => {
11919
+ const authoredColumn = asRecord(column) ?? {};
11920
+ const cells = Array.isArray(authoredColumn.cells) ? authoredColumn.cells : [];
11921
+ const alignments = /* @__PURE__ */ new Set();
11922
+ const values = [];
11923
+ bodyRows.forEach((row) => {
11924
+ const cell = row.cells[index];
11925
+ if (!cell || cell.missing) return;
11926
+ values.push(resolvedCellText(cell.content));
11927
+ alignments.add(cell.horizontalAlignment);
11928
+ });
11929
+ const header = model.header?.cells[index];
11930
+ return {
11931
+ index,
11932
+ path: `${path3}/props/columns/${index}`,
11933
+ ...header?.content !== void 0 && {
11934
+ header: resolvedCellText(header.content)
11935
+ },
11936
+ values,
11937
+ alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
11938
+ hasCellDefaults: asRecord(authoredColumn.cellDefaults) !== void 0,
11939
+ hasHeader: asRecord(authoredColumn.header) !== void 0,
11940
+ cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
11941
+ const alignment2 = asRecord(cell)?.horizontalAlignment;
11942
+ return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
11943
+ })
11944
+ };
11945
+ }
11946
+ );
11947
+ return {
11948
+ id: `docx:table-design:${path3}`,
11949
+ kind: "docx/table",
11950
+ path: path3,
11951
+ columns,
11952
+ rowCount: drawnRows,
11953
+ fullGrid
11954
+ };
11955
+ }
11628
11956
  var TWIPS_PER_POINT3 = 20;
11629
11957
  var SINGLE_LINE_RATIO = 1.2;
11630
11958
  function finiteNumber(value) {
@@ -11653,20 +11981,23 @@ function characterSpacingPt(spacing) {
11653
11981
  if (rule === void 0 || value === void 0) return 0;
11654
11982
  return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
11655
11983
  }
11656
- function pointerExists(root, pointer) {
11984
+ function nodeAtPointer(root, pointer) {
11657
11985
  let current = root;
11658
11986
  for (const token of pointer.split("/").slice(1)) {
11659
11987
  if (Array.isArray(current)) {
11660
11988
  const index = Number(token);
11661
- if (!Number.isInteger(index)) return false;
11989
+ if (!Number.isInteger(index)) return void 0;
11662
11990
  current = current[index];
11663
11991
  continue;
11664
11992
  }
11665
11993
  const record = asRecord(current);
11666
- if (!record) return false;
11994
+ if (!record) return void 0;
11667
11995
  current = record[token];
11668
11996
  }
11669
- return current !== void 0;
11997
+ return current;
11998
+ }
11999
+ function pointerExists(root, pointer) {
12000
+ return nodeAtPointer(root, pointer) !== void 0;
11670
12001
  }
11671
12002
  function styleKey(node, props) {
11672
12003
  if (node.name === "heading") {
@@ -11847,6 +12178,78 @@ function prepareDocxQualityDocument(document, options = {}) {
11847
12178
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
11848
12179
  };
11849
12180
  };
12181
+ const paletteHexes = {};
12182
+ const visualColors = designColors2(
12183
+ resolved.theme.colors,
12184
+ resolved.theme.palette
12185
+ );
12186
+ const entries = {
12187
+ ...visualColors,
12188
+ ...Object.fromEntries(
12189
+ (resolved.theme.palette?.chart ?? []).map((value) => {
12190
+ const hex = resolveDesignColor2(value, visualColors);
12191
+ return [hex ? `#${hex}` : value, value];
12192
+ })
12193
+ )
12194
+ };
12195
+ for (const [token, value] of Object.entries(entries)) {
12196
+ if (typeof value !== "string") continue;
12197
+ const hex = normalizeHex(resolveDesignColor2(value, visualColors) ?? value);
12198
+ if (hex) paletteHexes[token] = hex;
12199
+ }
12200
+ const paletteTokens = resolved.theme.palette?.chart?.map(
12201
+ (value) => `#${resolveDesignColor2(value, visualColors)}`
12202
+ ) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
12203
+ const authoredPropsAt = (pointer) => asRecord(asRecord(nodeAtPointer(context.document, pointer))?.props);
12204
+ addFact({
12205
+ id: "docx:theme",
12206
+ kind: "docx/theme",
12207
+ path: "/props",
12208
+ themeName: context.themeName,
12209
+ paletteHexes,
12210
+ // `heading` and `body` only. A theme also names `mono` and `light`, but
12211
+ // those paint nothing until a component asks for them — counting an
12212
+ // unused `Courier New` against a document's family budget would flag a
12213
+ // report that only ever uses one typeface.
12214
+ fontFamilies: [
12215
+ ...new Set(
12216
+ ["heading", "body"].flatMap((role) => {
12217
+ const family = asRecord(
12218
+ resolved.theme.fonts?.[role]
12219
+ )?.family;
12220
+ return typeof family === "string" && family.trim() !== "" ? [family] : [];
12221
+ })
12222
+ )
12223
+ ]
12224
+ });
12225
+ collectColorLiterals(document).forEach((literal, index) => {
12226
+ addFact({
12227
+ id: `docx:color:${index}:${literal.path}`,
12228
+ kind: "docx/color",
12229
+ path: literal.path,
12230
+ raw: literal.raw,
12231
+ hex: literal.hex
12232
+ });
12233
+ });
12234
+ collectFontFamilies(document).forEach((use, index) => {
12235
+ addFact({
12236
+ id: `docx:font:${index}:${use.path}`,
12237
+ kind: "docx/font-family",
12238
+ path: use.path,
12239
+ family: use.family
12240
+ });
12241
+ });
12242
+ collectPlaceholders(document).forEach((occurrence, index) => {
12243
+ addFact({
12244
+ id: `docx:placeholder:${index}:${occurrence.path}`,
12245
+ kind: "docx/placeholder",
12246
+ path: occurrence.path,
12247
+ text: occurrence.text,
12248
+ placeholderKind: occurrence.match.kind,
12249
+ pattern: occurrence.match.pattern,
12250
+ excerpt: occurrence.match.excerpt
12251
+ });
12252
+ });
11850
12253
  let previousVisitPath;
11851
12254
  let lastFrame;
11852
12255
  let flowIndex = 0;
@@ -11856,6 +12259,18 @@ function prepareDocxQualityDocument(document, options = {}) {
11856
12259
  if (node.name === "table") {
11857
12260
  const fact = tableFact(props, path3, page.availableWidthTwips);
11858
12261
  if (fact) addFact(fact);
12262
+ const design = tableDesignFact(
12263
+ props,
12264
+ path3,
12265
+ resolved.theme,
12266
+ context.themeName,
12267
+ authoredPropsAt(path3)
12268
+ );
12269
+ if (design) addFact(design);
12270
+ }
12271
+ if (node.name === "chart" || node.name === "highcharts") {
12272
+ const fact = chartFact(node, props, path3, paletteTokens);
12273
+ if (fact) addFact(fact);
11859
12274
  }
11860
12275
  if (node.name === "paragraph" || node.name === "text-box") {
11861
12276
  const floating = asRecord(props.floating);