@json-to-office/core-docx 2.5.0 → 2.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.
Files changed (39) hide show
  1. package/dist/blocks/cover.d.ts +22 -0
  2. package/dist/blocks/cover.d.ts.map +1 -0
  3. package/dist/blocks/index.d.ts +72 -0
  4. package/dist/blocks/index.d.ts.map +1 -0
  5. package/dist/blocks/keyTakeaways.d.ts +20 -0
  6. package/dist/blocks/keyTakeaways.d.ts.map +1 -0
  7. package/dist/blocks/recipe.d.ts +31 -0
  8. package/dist/blocks/recipe.d.ts.map +1 -0
  9. package/dist/blocks/runningHead.d.ts +69 -0
  10. package/dist/blocks/runningHead.d.ts.map +1 -0
  11. package/dist/blocks/sectionOpener.d.ts +26 -0
  12. package/dist/blocks/sectionOpener.d.ts.map +1 -0
  13. package/dist/blocks/types.d.ts +15 -0
  14. package/dist/blocks/types.d.ts.map +1 -0
  15. package/dist/components/highcharts.d.ts +2 -2
  16. package/dist/components/highcharts.d.ts.map +1 -1
  17. package/dist/core/componentTransform.d.ts +8 -0
  18. package/dist/core/componentTransform.d.ts.map +1 -1
  19. package/dist/core/desugarExternals.d.ts +2 -0
  20. package/dist/core/desugarExternals.d.ts.map +1 -1
  21. package/dist/core/generateFromIr.d.ts.map +1 -1
  22. package/dist/index.d.ts +3 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1267 -129
  25. package/dist/index.js.map +1 -1
  26. package/dist/ir/compiler.d.ts.map +1 -1
  27. package/dist/plugin/example/index.js +1233 -130
  28. package/dist/plugin/example/index.js.map +1 -1
  29. package/dist/quality/facts.d.ts +23 -1
  30. package/dist/quality/facts.d.ts.map +1 -1
  31. package/dist/quality/rules.d.ts +6 -0
  32. package/dist/quality/rules.d.ts.map +1 -1
  33. package/dist/styles/index.d.ts +1 -1
  34. package/dist/styles/index.d.ts.map +1 -1
  35. package/dist/templates/themes/consulting.docx.theme.json +307 -0
  36. package/dist/templates/themes/index.d.ts +1755 -0
  37. package/dist/templates/themes/index.d.ts.map +1 -1
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +4 -4
@@ -790,6 +790,320 @@ var init_vermilion_docx_theme = __esm({
790
790
  }
791
791
  });
792
792
 
793
+ // src/templates/themes/consulting.docx.theme.json
794
+ var consulting_docx_theme_default;
795
+ var init_consulting_docx_theme = __esm({
796
+ "src/templates/themes/consulting.docx.theme.json"() {
797
+ consulting_docx_theme_default = {
798
+ name: "consulting",
799
+ displayName: "Consulting House",
800
+ description: "The house style for client and technical reports \u2014 near-black ink, three greys and one deep-blue accent, Calibri body under Arial headings, hairline rules, no fills behind body text, safe fonts only",
801
+ version: "1.0.0",
802
+ colors: {
803
+ primary: "#1A1F26",
804
+ secondary: "#4B5563",
805
+ accent: "#1B4F8A",
806
+ text: "#1A1F26",
807
+ background: "#FFFFFF",
808
+ border: "#C9CED6",
809
+ textPrimary: "#1A1F26",
810
+ textSecondary: "#4B5563",
811
+ textMuted: "#7B8794",
812
+ borderPrimary: "#C9CED6",
813
+ borderSecondary: "#E4E7EB",
814
+ backgroundPrimary: "#FFFFFF",
815
+ backgroundSecondary: "#F2F4F7",
816
+ accent4: "#5B8DC9",
817
+ accent5: "#A9C4E4",
818
+ accent6: "#7B8794"
819
+ },
820
+ palette: {
821
+ rule: "#C9CED6",
822
+ textMuted: "#7B8794",
823
+ onPrimary: "#FFFFFF",
824
+ surfaceInverse: "#1A1F26",
825
+ positive: "#1E7F4F",
826
+ negative: "#B42318",
827
+ chart: ["accent", "secondary", "accent4", "textMuted", "accent5", "rule"]
828
+ },
829
+ fonts: {
830
+ heading: { family: "Arial", size: 16 },
831
+ body: { family: "Calibri", size: 10.5 },
832
+ mono: { family: "Consolas", size: 9.5 },
833
+ light: { family: "Arial", size: 16 }
834
+ },
835
+ page: {
836
+ size: "A4",
837
+ margins: {
838
+ top: 1440,
839
+ bottom: 1440,
840
+ left: 1440,
841
+ right: 1440,
842
+ header: 680,
843
+ footer: 680,
844
+ gutter: 0
845
+ }
846
+ },
847
+ typography: {
848
+ roles: {
849
+ eyebrow: {
850
+ face: "heading",
851
+ size: 8,
852
+ weight: 700,
853
+ case: "upper",
854
+ tracking: 8,
855
+ color: "accent",
856
+ spaceAfter: 4
857
+ },
858
+ display: {
859
+ face: "heading",
860
+ weight: 700,
861
+ color: "primary",
862
+ lineHeight: 1.05,
863
+ spaceAfter: 8
864
+ },
865
+ stat: {
866
+ face: "heading",
867
+ weight: 700,
868
+ color: "accent",
869
+ lineHeight: 1
870
+ },
871
+ quote: {
872
+ face: "body",
873
+ color: "textSecondary",
874
+ lineHeight: 1.3
875
+ },
876
+ label: {
877
+ face: "body",
878
+ size: 9,
879
+ weight: 700,
880
+ color: "textSecondary"
881
+ },
882
+ footer: { face: "body", size: 8, color: "textMuted" },
883
+ tableHeader: {
884
+ face: "body",
885
+ size: 9,
886
+ weight: 700,
887
+ color: "primary"
888
+ },
889
+ tableCell: { face: "body", size: 9.5, color: "textPrimary" },
890
+ chartLabel: {
891
+ face: "body",
892
+ size: 9,
893
+ weight: 400,
894
+ color: "textSecondary"
895
+ },
896
+ tracker: {
897
+ face: "body",
898
+ size: 8,
899
+ case: "upper",
900
+ tracking: 6,
901
+ color: "textMuted"
902
+ },
903
+ source: { face: "body", size: 8, color: "textMuted" }
904
+ },
905
+ scale: {
906
+ a4: { base: 10.5, ratio: 1.2, baselinePt: 2 },
907
+ letter: { base: 10.5, ratio: 1.2, baselinePt: 2 }
908
+ }
909
+ },
910
+ spacing: {
911
+ basePt: 8,
912
+ blockGap: { tight: 4, normal: 8, loose: 16 },
913
+ canvas: {
914
+ a4: { safeAreaIn: 1, gutterIn: 0, columns: 12 },
915
+ letter: { safeAreaIn: 1, gutterIn: 0, columns: 12 }
916
+ }
917
+ },
918
+ chrome: {
919
+ runningHead: {
920
+ type: "tracker",
921
+ color: "textMuted",
922
+ rule: { weightPt: 0.5, color: "rule" },
923
+ alignment: "right"
924
+ },
925
+ tracker: {
926
+ type: "tracker",
927
+ color: "textMuted",
928
+ alignment: "right"
929
+ },
930
+ actionTitle: { type: "display", color: "primary" },
931
+ keyTakeaways: {
932
+ type: "label",
933
+ color: "primary",
934
+ rule: { weightPt: 2, color: "accent" },
935
+ padPt: 8
936
+ },
937
+ sourceLine: { type: "source", color: "textMuted" },
938
+ confidentialFooter: {
939
+ type: "footer",
940
+ color: "textMuted",
941
+ rule: { weightPt: 0.5, color: "rule" },
942
+ alignment: "left"
943
+ },
944
+ logoSlot: { alignment: "right" },
945
+ cover: {
946
+ type: "display",
947
+ color: "primary",
948
+ rule: { weightPt: 3, color: "accent" }
949
+ }
950
+ },
951
+ motif: {
952
+ kind: "rule",
953
+ color: "accent",
954
+ weightPt: 3,
955
+ placement: "top"
956
+ },
957
+ styles: {
958
+ normal: {
959
+ font: "body",
960
+ color: "textPrimary",
961
+ lineSpacing: { type: "multiple", value: 1.2 },
962
+ alignment: "left",
963
+ spacing: { after: 8 }
964
+ },
965
+ title: {
966
+ font: "heading",
967
+ size: 26,
968
+ bold: true,
969
+ color: "primary",
970
+ alignment: "left",
971
+ lineSpacing: { type: "multiple", value: 1.05 },
972
+ spacing: { before: 0, after: 6 }
973
+ },
974
+ subtitle: {
975
+ font: "body",
976
+ size: 13,
977
+ color: "textSecondary",
978
+ alignment: "left",
979
+ lineSpacing: { type: "multiple", value: 1.2 },
980
+ spacing: { before: 0, after: 18 }
981
+ },
982
+ heading1: {
983
+ font: "heading",
984
+ size: 16,
985
+ bold: true,
986
+ color: "primary",
987
+ lineSpacing: { type: "multiple", value: 1.1 },
988
+ spacing: { before: 24, after: 8 },
989
+ keepNext: true,
990
+ alignment: "left",
991
+ borders: {
992
+ bottom: { style: "single", size: 4, color: "rule", space: 4 }
993
+ }
994
+ },
995
+ heading2: {
996
+ font: "heading",
997
+ size: 12.5,
998
+ bold: true,
999
+ color: "primary",
1000
+ spacing: { before: 16, after: 6 },
1001
+ keepNext: true,
1002
+ alignment: "left"
1003
+ },
1004
+ heading3: {
1005
+ font: "body",
1006
+ size: 11,
1007
+ bold: true,
1008
+ color: "primary",
1009
+ spacing: { before: 12, after: 4 },
1010
+ keepNext: true,
1011
+ alignment: "left"
1012
+ },
1013
+ heading4: {
1014
+ font: "body",
1015
+ size: 10.5,
1016
+ bold: true,
1017
+ color: "textSecondary",
1018
+ spacing: { before: 10, after: 4 },
1019
+ keepNext: true,
1020
+ alignment: "left"
1021
+ },
1022
+ heading5: {
1023
+ font: "body",
1024
+ size: 10.5,
1025
+ bold: false,
1026
+ italic: true,
1027
+ color: "textSecondary",
1028
+ spacing: { before: 8, after: 4 },
1029
+ keepNext: true,
1030
+ alignment: "left"
1031
+ },
1032
+ heading6: {
1033
+ font: "body",
1034
+ size: 9,
1035
+ bold: true,
1036
+ case: "upper",
1037
+ color: "textMuted",
1038
+ characterSpacing: { type: "expanded", value: 20 },
1039
+ spacing: { before: 8, after: 4 },
1040
+ keepNext: true,
1041
+ alignment: "left"
1042
+ },
1043
+ TOC1: {
1044
+ font: "body",
1045
+ size: 10.5,
1046
+ bold: true,
1047
+ color: "textPrimary",
1048
+ spacing: { before: 4, after: 3 },
1049
+ tabStops: [{ type: "right", position: 9026, leader: "none" }]
1050
+ },
1051
+ TOC2: {
1052
+ font: "body",
1053
+ size: 10.5,
1054
+ color: "textSecondary",
1055
+ indent: { left: 240 },
1056
+ spacing: { after: 2 },
1057
+ tabStops: [{ type: "right", position: 9026, leader: "none" }]
1058
+ },
1059
+ TOC3: {
1060
+ font: "body",
1061
+ size: 10,
1062
+ color: "textMuted",
1063
+ indent: { left: 480 },
1064
+ spacing: { after: 2 },
1065
+ tabStops: [{ type: "right", position: 9026, leader: "none" }]
1066
+ }
1067
+ },
1068
+ componentDefaults: {
1069
+ table: {
1070
+ width: 100,
1071
+ borderColor: "#C9CED6",
1072
+ borderSize: 0.5,
1073
+ hideBorders: { left: true, right: true, insideVertical: true },
1074
+ cellDefaults: {
1075
+ font: { size: 9.5 },
1076
+ padding: { top: 4, bottom: 4 },
1077
+ verticalAlignment: "middle"
1078
+ },
1079
+ headerCellDefaults: {
1080
+ color: "#1A1F26",
1081
+ font: { size: 9, bold: true },
1082
+ padding: { top: 0, bottom: 5 },
1083
+ verticalAlignment: "bottom",
1084
+ borderColor: { bottom: "#1A1F26" },
1085
+ borderSize: { bottom: 0.75 }
1086
+ }
1087
+ },
1088
+ list: {
1089
+ format: "bullet",
1090
+ bullet: "\u2022",
1091
+ spacing: { item: 2 }
1092
+ },
1093
+ image: {
1094
+ alignment: "center"
1095
+ },
1096
+ statistic: {
1097
+ alignment: "left"
1098
+ },
1099
+ section: {
1100
+ pageBreak: false
1101
+ }
1102
+ }
1103
+ };
1104
+ }
1105
+ });
1106
+
793
1107
  // src/templates/themes/index.ts
794
1108
  import fs from "fs";
795
1109
  import path from "path";
@@ -804,7 +1118,8 @@ function loadThemesFromJson() {
804
1118
  const themes2 = {
805
1119
  minimal: ensureThemeDefaults(minimal_docx_theme_default),
806
1120
  devportal: ensureThemeDefaults(devportal_docx_theme_default),
807
- vermilion: ensureThemeDefaults(vermilion_docx_theme_default)
1121
+ vermilion: ensureThemeDefaults(vermilion_docx_theme_default),
1122
+ consulting: ensureThemeDefaults(consulting_docx_theme_default)
808
1123
  };
809
1124
  try {
810
1125
  const __filename2 = fileURLToPath(import.meta.url);
@@ -842,7 +1157,7 @@ function loadThemesFromJson() {
842
1157
  _themesCache = themes2;
843
1158
  return themes2;
844
1159
  }
845
- var _themesCache, themes, getTheme, getThemeWithFallback, hasTheme, getThemeNames, minimalTheme, devportalTheme, vermilionTheme;
1160
+ var _themesCache, themes, getTheme, getThemeWithFallback, hasTheme, getThemeNames, minimalTheme, devportalTheme, vermilionTheme, consultingTheme;
846
1161
  var init_themes = __esm({
847
1162
  "src/templates/themes/index.ts"() {
848
1163
  "use strict";
@@ -850,6 +1165,7 @@ var init_themes = __esm({
850
1165
  init_minimal_docx_theme();
851
1166
  init_devportal_docx_theme();
852
1167
  init_vermilion_docx_theme();
1168
+ init_consulting_docx_theme();
853
1169
  _themesCache = null;
854
1170
  themes = loadThemesFromJson();
855
1171
  getTheme = (themeName) => {
@@ -873,6 +1189,7 @@ var init_themes = __esm({
873
1189
  minimalTheme = themes["minimal"];
874
1190
  devportalTheme = themes["devportal"];
875
1191
  vermilionTheme = themes["vermilion"];
1192
+ consultingTheme = themes["consulting"];
876
1193
  }
877
1194
  });
878
1195
 
@@ -923,43 +1240,6 @@ var init_colorUtils = __esm({
923
1240
  }
924
1241
  });
925
1242
 
926
- // src/utils/generationContext.ts
927
- import { AsyncLocalStorage } from "async_hooks";
928
- import { isAbsolute, resolve } from "path";
929
- function runWithBaseDir(baseDir, callback) {
930
- return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
931
- }
932
- function resolveFromBaseDir(filePath) {
933
- const base = baseDirStorage.getStore();
934
- if (!base || isAbsolute(filePath)) return filePath;
935
- return resolve(base, filePath);
936
- }
937
- function runWithWarnings(warnings, callback) {
938
- return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
939
- }
940
- function reportWarning(component, code, message, context) {
941
- const warnings = warningsStorage.getStore();
942
- if (warnings) {
943
- warnings.push({
944
- component,
945
- message,
946
- severity: "warning",
947
- context: { code, ...context }
948
- });
949
- return;
950
- }
951
- console.warn(`[json-to-docx] [${code}] ${message}`);
952
- }
953
- var generationDateStorage, baseDirStorage, warningsStorage;
954
- var init_generationContext = __esm({
955
- "src/utils/generationContext.ts"() {
956
- "use strict";
957
- generationDateStorage = new AsyncLocalStorage();
958
- baseDirStorage = new AsyncLocalStorage();
959
- warningsStorage = new AsyncLocalStorage();
960
- }
961
- });
962
-
963
1243
  // src/styles/utils/styleHelpers.ts
964
1244
  function pointsToTwips(points) {
965
1245
  return Math.round(points * TWIPS_PER_POINT);
@@ -1273,6 +1553,43 @@ var init_layoutUtils = __esm({
1273
1553
  }
1274
1554
  });
1275
1555
 
1556
+ // src/utils/generationContext.ts
1557
+ import { AsyncLocalStorage } from "async_hooks";
1558
+ import { isAbsolute, resolve } from "path";
1559
+ function runWithBaseDir(baseDir, callback) {
1560
+ return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
1561
+ }
1562
+ function resolveFromBaseDir(filePath) {
1563
+ const base = baseDirStorage.getStore();
1564
+ if (!base || isAbsolute(filePath)) return filePath;
1565
+ return resolve(base, filePath);
1566
+ }
1567
+ function runWithWarnings(warnings, callback) {
1568
+ return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
1569
+ }
1570
+ function reportWarning(component, code, message, context) {
1571
+ const warnings = warningsStorage.getStore();
1572
+ if (warnings) {
1573
+ warnings.push({
1574
+ component,
1575
+ message,
1576
+ severity: "warning",
1577
+ context: { code, ...context }
1578
+ });
1579
+ return;
1580
+ }
1581
+ console.warn(`[json-to-docx] [${code}] ${message}`);
1582
+ }
1583
+ var generationDateStorage, baseDirStorage, warningsStorage;
1584
+ var init_generationContext = __esm({
1585
+ "src/utils/generationContext.ts"() {
1586
+ "use strict";
1587
+ generationDateStorage = new AsyncLocalStorage();
1588
+ baseDirStorage = new AsyncLocalStorage();
1589
+ warningsStorage = new AsyncLocalStorage();
1590
+ }
1591
+ });
1592
+
1276
1593
  // src/themes/json/parser.ts
1277
1594
  import { validateThemeJson } from "@json-to-office/shared-docx/validation/unified";
1278
1595
  var ThemeValidationError, ThemeParseError, ThemeParser;
@@ -4951,7 +5268,10 @@ async function resolveDocumentFonts(document, theme, fonts, warnings, forceMater
4951
5268
  }
4952
5269
 
4953
5270
  // src/core/generateFromIr.ts
4954
- import { toRasterizeFontFaces } from "@json-to-office/shared/fonts/node";
5271
+ import {
5272
+ toChartFontFaces,
5273
+ toRasterizeFontFaces
5274
+ } from "@json-to-office/shared/fonts/node";
4955
5275
 
4956
5276
  // src/core/prerasterizeVisuals.ts
4957
5277
  import {
@@ -5354,6 +5674,87 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
5354
5674
  return map;
5355
5675
  }
5356
5676
 
5677
+ // src/core/componentTransform.ts
5678
+ async function transformComponents(doc, replace) {
5679
+ return await walk(doc, replace);
5680
+ }
5681
+ async function walk(node, replace) {
5682
+ if (Array.isArray(node)) {
5683
+ return Promise.all(node.map((child) => walk(child, replace)));
5684
+ }
5685
+ if (!node || typeof node !== "object") return node;
5686
+ const obj = node;
5687
+ if (typeof obj.name === "string" && obj.props) {
5688
+ const replacement = await replace(obj);
5689
+ if (replacement) return replacement;
5690
+ }
5691
+ const next = { ...obj };
5692
+ if (Array.isArray(obj.children)) {
5693
+ next.children = await walk(obj.children, replace);
5694
+ }
5695
+ if (obj.props && typeof obj.props === "object") {
5696
+ const props = obj.props;
5697
+ let changed = false;
5698
+ const nextProps = { ...props };
5699
+ for (const key of ["header", "footer"]) {
5700
+ if (Array.isArray(props[key])) {
5701
+ nextProps[key] = await walk(props[key], replace);
5702
+ changed = true;
5703
+ }
5704
+ }
5705
+ if (Array.isArray(props.columns)) {
5706
+ nextProps.columns = await Promise.all(
5707
+ props.columns.map(
5708
+ (column) => walkColumn(column, replace)
5709
+ )
5710
+ );
5711
+ changed = true;
5712
+ }
5713
+ if (changed) next.props = nextProps;
5714
+ }
5715
+ return next;
5716
+ }
5717
+ async function walkColumn(column, replace) {
5718
+ if (!column || typeof column !== "object") return column;
5719
+ const source = column;
5720
+ const next = { ...source };
5721
+ const header = source.header;
5722
+ if (header && typeof header === "object" && "content" in header) {
5723
+ next.header = { ...header, content: await walk(header.content, replace) };
5724
+ }
5725
+ if (Array.isArray(source.cells)) {
5726
+ next.cells = await Promise.all(
5727
+ source.cells.map(async (cell) => {
5728
+ if (cell && typeof cell === "object" && "content" in cell) {
5729
+ const source2 = cell;
5730
+ return { ...source2, content: await walk(source2.content, replace) };
5731
+ }
5732
+ return cell;
5733
+ })
5734
+ );
5735
+ }
5736
+ return next;
5737
+ }
5738
+ function withNodeIdentity(original, replacement) {
5739
+ const out = { ...replacement };
5740
+ if (original.id !== void 0) out.id = original.id;
5741
+ if (original.enabled !== void 0) out.enabled = original.enabled;
5742
+ return out;
5743
+ }
5744
+ function containsComponent(root, name) {
5745
+ const seen = /* @__PURE__ */ new WeakSet();
5746
+ const visit = (node) => {
5747
+ if (!node || typeof node !== "object" || seen.has(node)) return false;
5748
+ seen.add(node);
5749
+ if (Array.isArray(node)) return node.some(visit);
5750
+ const obj = node;
5751
+ if (typeof obj.name === "string" && obj.enabled === false) return false;
5752
+ if (obj.name === name && obj.props !== void 0) return true;
5753
+ return Object.values(obj).some(visit);
5754
+ };
5755
+ return visit(root);
5756
+ }
5757
+
5357
5758
  // src/core/desugarExternals.ts
5358
5759
  import {
5359
5760
  clampVisualDpi as clampVisualDpi2,
@@ -5365,6 +5766,14 @@ import {
5365
5766
 
5366
5767
  // src/components/highcharts.ts
5367
5768
  init_colorUtils();
5769
+ init_layoutUtils();
5770
+ import {
5771
+ chartFamilyResolver,
5772
+ chartPointsPerPixel,
5773
+ POINTS_PER_PIXEL_96DPI,
5774
+ withChartFontFaceCss,
5775
+ withChartTypography
5776
+ } from "@json-to-office/shared";
5368
5777
  var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
5369
5778
  async function generateChart(config, servicesConfig) {
5370
5779
  if (!isNodeEnvironment()) {
@@ -5428,84 +5837,81 @@ function withThemeColors(config, theme) {
5428
5837
  options: { ...config.options, colors: palette }
5429
5838
  };
5430
5839
  }
5431
- async function renderChartToImageProps(props, theme, servicesConfig) {
5432
- const config = withThemeColors(props, theme);
5433
- const chart = await generateChart(config, servicesConfig);
5434
- const hasConfigDimensions = config.width !== void 0 || config.height !== void 0;
5435
- return {
5436
- base64: chart.base64DataUri,
5437
- width: hasConfigDimensions ? config.width : chart.width,
5438
- height: hasConfigDimensions ? config.height : chart.height,
5439
- alignment: "center"
5440
- };
5441
- }
5442
-
5443
- // src/core/componentTransform.ts
5444
- async function transformComponents(doc, replace) {
5445
- return await walk(doc, replace);
5446
- }
5447
- async function walk(node, replace) {
5448
- if (Array.isArray(node)) {
5449
- return Promise.all(node.map((child) => walk(child, replace)));
5450
- }
5451
- if (!node || typeof node !== "object") return node;
5452
- const obj = node;
5453
- if (typeof obj.name === "string" && obj.props) {
5454
- const replacement = await replace(obj);
5455
- if (replacement) return replacement;
5456
- }
5457
- const next = { ...obj };
5458
- if (Array.isArray(obj.children)) {
5459
- next.children = await walk(obj.children, replace);
5840
+ var POINTS_PER_TWIP = 1 / 20;
5841
+ var POINTS_PER_PIXEL = POINTS_PER_PIXEL_96DPI;
5842
+ function placedWidthPt(config, theme) {
5843
+ const { width, height } = config;
5844
+ const chart = config.options.chart;
5845
+ if (typeof width === "number") return width * POINTS_PER_PIXEL;
5846
+ if (typeof width === "string") {
5847
+ const page = getPageSetup(theme);
5848
+ const contentWidthPt = (page.size.width - page.margin.left - page.margin.right) * POINTS_PER_TWIP;
5849
+ return parseFloat(width) / 100 * contentWidthPt;
5460
5850
  }
5461
- if (obj.props && typeof obj.props === "object") {
5462
- const props = obj.props;
5463
- let changed = false;
5464
- const nextProps = { ...props };
5465
- for (const key of ["header", "footer"]) {
5466
- if (Array.isArray(props[key])) {
5467
- nextProps[key] = await walk(props[key], replace);
5468
- changed = true;
5469
- }
5470
- }
5471
- if (Array.isArray(props.columns)) {
5472
- nextProps.columns = await Promise.all(
5473
- props.columns.map(
5474
- (column) => walkColumn(column, replace)
5475
- )
5476
- );
5477
- changed = true;
5478
- }
5479
- if (changed) next.props = nextProps;
5480
- }
5481
- return next;
5482
- }
5483
- async function walkColumn(column, replace) {
5484
- if (!column || typeof column !== "object") return column;
5485
- const source = column;
5486
- const next = { ...source };
5487
- const header = source.header;
5488
- if (header && typeof header === "object" && "content" in header) {
5489
- next.header = { ...header, content: await walk(header.content, replace) };
5851
+ if (typeof height === "number" && chart.height > 0) {
5852
+ return height / chart.height * chart.width * POINTS_PER_PIXEL;
5490
5853
  }
5491
- if (Array.isArray(source.cells)) {
5492
- next.cells = await Promise.all(
5493
- source.cells.map(async (cell) => {
5494
- if (cell && typeof cell === "object" && "content" in cell) {
5495
- const source2 = cell;
5496
- return { ...source2, content: await walk(source2.content, replace) };
5497
- }
5498
- return cell;
5499
- })
5500
- );
5854
+ return void 0;
5855
+ }
5856
+ function toHex(token, theme) {
5857
+ return `#${resolveColor(token, theme)}`;
5858
+ }
5859
+ function themeChartTypography(theme) {
5860
+ const styles = theme.styles ?? {};
5861
+ const family = chartFamilyResolver(theme);
5862
+ const bodyPt = styles.normal?.size ?? theme.fonts.body.size ?? 11;
5863
+ const label = styles.chartLabel;
5864
+ const heading = styles.heading3;
5865
+ const labelPt = label?.size ?? Math.max(bodyPt - 1, 6);
5866
+ return {
5867
+ bodyFamily: family(theme.fonts.body.family),
5868
+ headingFamily: family(theme.fonts.heading.family),
5869
+ textColor: toHex("textPrimary", theme),
5870
+ mutedColor: toHex("textSecondary", theme),
5871
+ labelPt,
5872
+ labelWeight: label?.fontWeight ?? (label?.bold ? 700 : void 0),
5873
+ titlePt: heading?.size ?? bodyPt + 2,
5874
+ titleWeight: heading?.fontWeight ?? (heading?.bold === false ? void 0 : 700),
5875
+ sourcePt: styles.source?.size ?? Math.max(labelPt - 1, 6)
5876
+ };
5877
+ }
5878
+ function withThemeTypography(config, theme) {
5879
+ if (!theme?.fonts?.body?.family || !theme.fonts.heading?.family) {
5880
+ return config;
5501
5881
  }
5502
- return next;
5882
+ return {
5883
+ ...config,
5884
+ options: withChartTypography(
5885
+ config.options,
5886
+ themeChartTypography(theme),
5887
+ chartPointsPerPixel(
5888
+ config.options.chart.width,
5889
+ placedWidthPt(config, theme)
5890
+ )
5891
+ )
5892
+ };
5503
5893
  }
5504
- function withNodeIdentity(original, replacement) {
5505
- const out = { ...replacement };
5506
- if (original.id !== void 0) out.id = original.id;
5507
- if (original.enabled !== void 0) out.enabled = original.enabled;
5508
- return out;
5894
+ function withChartFontFaces(config, theme, faces) {
5895
+ if (!faces?.length || !theme?.fonts) return config;
5896
+ return withChartFontFaceCss(config, faces, [
5897
+ theme.fonts.body.family,
5898
+ theme.fonts.heading.family
5899
+ ]);
5900
+ }
5901
+ async function renderChartToImageProps(props, theme, servicesConfig, chartFonts) {
5902
+ const config = withChartFontFaces(
5903
+ withThemeTypography(withThemeColors(props, theme), theme),
5904
+ theme,
5905
+ chartFonts
5906
+ );
5907
+ const chart = await generateChart(config, servicesConfig);
5908
+ const hasConfigDimensions = config.width !== void 0 || config.height !== void 0;
5909
+ return {
5910
+ base64: chart.base64DataUri,
5911
+ width: hasConfigDimensions ? config.width : chart.width,
5912
+ height: hasConfigDimensions ? config.height : chart.height,
5913
+ alignment: "center"
5914
+ };
5509
5915
  }
5510
5916
 
5511
5917
  // src/core/desugarExternals.ts
@@ -5537,7 +5943,8 @@ async function desugarExternals(document, options) {
5537
5943
  props: await renderChartToImageProps(
5538
5944
  node.props,
5539
5945
  options.theme,
5540
- options.services?.highcharts
5946
+ options.services?.highcharts,
5947
+ options.chartFonts
5541
5948
  )
5542
5949
  });
5543
5950
  }
@@ -8295,6 +8702,641 @@ function emptyToUndefined(formatting) {
8295
8702
  return entries.length === 0 ? void 0 : Object.fromEntries(entries);
8296
8703
  }
8297
8704
 
8705
+ // src/blocks/index.ts
8706
+ import {
8707
+ COVER_BUDGET,
8708
+ KEY_TAKEAWAYS_BUDGET,
8709
+ RUNNING_HEAD_BUDGET,
8710
+ SECTION_OPENER_BUDGET
8711
+ } from "@json-to-office/shared-docx";
8712
+
8713
+ // src/blocks/cover.ts
8714
+ init_widthUtils();
8715
+
8716
+ // src/blocks/recipe.ts
8717
+ var MIN_RULE_PT = 0.25;
8718
+ var MAX_RULE_PT = 12;
8719
+ function clampRule(weightPt) {
8720
+ return Math.min(MAX_RULE_PT, Math.max(MIN_RULE_PT, weightPt));
8721
+ }
8722
+ var FALLBACK_EYEBROW_FONT = {
8723
+ size: 9,
8724
+ bold: true,
8725
+ color: "accent",
8726
+ case: "upper"
8727
+ };
8728
+ function hasStyle(theme, role) {
8729
+ const styles = theme.styles;
8730
+ return styles?.[role] !== void 0;
8731
+ }
8732
+ function roleProps(theme, role, fallbackFont, color) {
8733
+ if (hasStyle(theme, role)) {
8734
+ return {
8735
+ themeStyle: role,
8736
+ ...color !== void 0 && { font: { color } }
8737
+ };
8738
+ }
8739
+ return { font: { ...fallbackFont, ...color !== void 0 && { color } } };
8740
+ }
8741
+
8742
+ // src/blocks/cover.ts
8743
+ var DROP_SHARE = 0.3;
8744
+ var DROP_SHARE_WITH_LOGO = 0.22;
8745
+ var DEFAULT_LOGO_WIDTH = "25%";
8746
+ var FALLBACK = {
8747
+ ruleWeightPt: 3,
8748
+ ruleColor: "accent",
8749
+ padPt: 12,
8750
+ metaFont: { size: 9, color: "textSecondary" }
8751
+ };
8752
+ function compileCover(props, theme) {
8753
+ const recipe = theme.chrome?.cover;
8754
+ const ruleWeightPt = recipe?.rule?.weightPt ?? FALLBACK.ruleWeightPt;
8755
+ const padPt = recipe?.padPt ?? FALLBACK.padPt;
8756
+ const heightPt = getAvailableHeightTwips(theme) / 20;
8757
+ const dropPt = Math.round(
8758
+ heightPt * (props.logo ? DROP_SHARE_WITH_LOGO : DROP_SHARE)
8759
+ );
8760
+ const children = [];
8761
+ const sourceMap = {};
8762
+ const emit = (child, slots) => {
8763
+ const index = children.push(child) - 1;
8764
+ sourceMap[`/children/${index}`] = "";
8765
+ for (const [emitted, authored] of Object.entries(slots)) {
8766
+ sourceMap[`/children/${index}${emitted}`] = authored;
8767
+ }
8768
+ };
8769
+ if (props.logo) {
8770
+ const { width, height, ...source } = props.logo;
8771
+ emit(
8772
+ {
8773
+ name: "image",
8774
+ props: {
8775
+ ...source,
8776
+ width: width ?? DEFAULT_LOGO_WIDTH,
8777
+ ...height !== void 0 && { height },
8778
+ alignment: theme.chrome?.logoSlot?.alignment ?? "left",
8779
+ spacing: { before: 0, after: 0 }
8780
+ }
8781
+ },
8782
+ { "/props": "/props/logo" }
8783
+ );
8784
+ }
8785
+ const drawsRule = ruleWeightPt > 0;
8786
+ if (drawsRule) {
8787
+ emit(
8788
+ {
8789
+ name: "divider",
8790
+ props: {
8791
+ thickness: clampRule(ruleWeightPt),
8792
+ color: recipe?.rule?.color ?? FALLBACK.ruleColor,
8793
+ spacing: { before: dropPt, after: padPt }
8794
+ }
8795
+ },
8796
+ {}
8797
+ );
8798
+ }
8799
+ let dropPending = !drawsRule;
8800
+ const before = () => {
8801
+ if (!dropPending) return {};
8802
+ dropPending = false;
8803
+ return { before: dropPt };
8804
+ };
8805
+ if (props.client) {
8806
+ emit(
8807
+ {
8808
+ name: "paragraph",
8809
+ props: {
8810
+ text: props.client,
8811
+ keepNext: true,
8812
+ spacing: { ...before(), after: 4 },
8813
+ ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
8814
+ }
8815
+ },
8816
+ { "/props/text": "/props/client" }
8817
+ );
8818
+ }
8819
+ const titleRole = recipe?.type && hasStyle(theme, recipe.type) ? recipe.type : "title";
8820
+ emit(
8821
+ {
8822
+ name: "paragraph",
8823
+ props: {
8824
+ text: props.title,
8825
+ keepNext: true,
8826
+ ...dropPending && { spacing: before() },
8827
+ ...roleProps(theme, titleRole, { size: 26, bold: true }, recipe?.color)
8828
+ }
8829
+ },
8830
+ { "/props/text": "/props/title" }
8831
+ );
8832
+ if (props.subtitle) {
8833
+ emit(
8834
+ {
8835
+ name: "paragraph",
8836
+ props: {
8837
+ text: props.subtitle,
8838
+ keepNext: true,
8839
+ ...roleProps(theme, "subtitle", { size: 13, color: "textSecondary" })
8840
+ }
8841
+ },
8842
+ { "/props/text": "/props/subtitle" }
8843
+ );
8844
+ }
8845
+ const meta = [props.date, props.confidentiality].filter(
8846
+ (part) => typeof part === "string" && part !== ""
8847
+ );
8848
+ if (meta.length > 0) {
8849
+ emit(
8850
+ {
8851
+ name: "paragraph",
8852
+ props: {
8853
+ text: meta.join(" \xB7 "),
8854
+ spacing: { before: padPt, after: 0 },
8855
+ ...roleProps(theme, "label", FALLBACK.metaFont)
8856
+ }
8857
+ },
8858
+ {
8859
+ "/props/text": props.date ? "/props/date" : "/props/confidentiality"
8860
+ }
8861
+ );
8862
+ }
8863
+ return { children, sourceMap };
8864
+ }
8865
+
8866
+ // src/blocks/keyTakeaways.ts
8867
+ var KEY_TAKEAWAYS_DEFAULT_LABEL = "Key takeaways";
8868
+ var FALLBACK_RECIPE = {
8869
+ ruleWeightPt: 1.5,
8870
+ ruleColor: "accent",
8871
+ padPt: 6,
8872
+ type: "label"
8873
+ };
8874
+ function compileKeyTakeaways(props, theme) {
8875
+ const recipe = theme.chrome?.keyTakeaways;
8876
+ const labelRole = recipe?.type ?? FALLBACK_RECIPE.type;
8877
+ const labelStyle = hasStyle(theme, labelRole);
8878
+ const ruleWeightPt = clampRule(
8879
+ recipe?.rule?.weightPt ?? FALLBACK_RECIPE.ruleWeightPt
8880
+ );
8881
+ const ruleColor = recipe?.rule?.color ?? recipe?.color ?? FALLBACK_RECIPE.ruleColor;
8882
+ const padPt = recipe?.padPt ?? FALLBACK_RECIPE.padPt;
8883
+ const children = [
8884
+ {
8885
+ name: "divider",
8886
+ props: {
8887
+ thickness: ruleWeightPt,
8888
+ color: ruleColor,
8889
+ spacing: { before: 12, after: padPt }
8890
+ }
8891
+ },
8892
+ {
8893
+ name: "paragraph",
8894
+ props: {
8895
+ text: props.label ?? KEY_TAKEAWAYS_DEFAULT_LABEL,
8896
+ keepNext: true,
8897
+ spacing: { after: 4 },
8898
+ // The label role when the theme resolves one; a bold run in the
8899
+ // recipe's colour otherwise, so the label still reads as a label.
8900
+ ...labelStyle ? { themeStyle: labelRole } : {
8901
+ font: {
8902
+ bold: true,
8903
+ ...recipe?.color !== void 0 && { color: recipe.color }
8904
+ }
8905
+ }
8906
+ }
8907
+ },
8908
+ {
8909
+ name: "list",
8910
+ props: { items: [...props.items] }
8911
+ },
8912
+ {
8913
+ name: "divider",
8914
+ props: {
8915
+ thickness: 0.5,
8916
+ color: "borderPrimary",
8917
+ spacing: { before: padPt, after: 12 }
8918
+ }
8919
+ }
8920
+ ];
8921
+ return {
8922
+ children,
8923
+ sourceMap: {
8924
+ "/children/0": "",
8925
+ "/children/1": "",
8926
+ "/children/1/props/text": "/props/label",
8927
+ "/children/2": "",
8928
+ "/children/2/props/items": "/props/items",
8929
+ "/children/3": ""
8930
+ }
8931
+ };
8932
+ }
8933
+
8934
+ // src/blocks/runningHead.ts
8935
+ init_styles();
8936
+ init_widthUtils();
8937
+ var FALLBACK2 = {
8938
+ headFont: { size: 8, color: "textMuted", case: "upper" },
8939
+ footFont: { size: 8, color: "textMuted" },
8940
+ ruleWeightPt: 0.5,
8941
+ ruleColor: "border"
8942
+ };
8943
+ var PAGE_OF_TOTAL = "{PAGE} / {TOTAL_PAGES}";
8944
+ function sectionMeasureTwips(theme, page) {
8945
+ if (!page || page.size === void 0 && page.margins === void 0) {
8946
+ return getAvailableWidthTwips(theme);
8947
+ }
8948
+ const width = page.size ? getPageDimensions(page.size).width : getPageDimensions(theme.page.size).width;
8949
+ const margins = getDocumentMargins(theme);
8950
+ const left = page.margins?.left ?? margins.left ?? 1440;
8951
+ const right = page.margins?.right ?? margins.right ?? 1440;
8952
+ return Math.max(0, width - left - right);
8953
+ }
8954
+ function compileRunningHead(props, theme, scope) {
8955
+ const measure = sectionMeasureTwips(theme, scope.page);
8956
+ const head = theme.chrome?.runningHead;
8957
+ const foot = theme.chrome?.confidentialFooter;
8958
+ const slot = (name) => props[name] !== void 0 ? `${scope.block}/props/${name}` : void 0;
8959
+ const header = [];
8960
+ const headerMap = {};
8961
+ const title = props.title ?? scope.documentTitle ?? "";
8962
+ const tracker = scope.opener?.text ?? props.tracker ?? "";
8963
+ if (title !== "" || tracker !== "") {
8964
+ const both = title !== "" && tracker !== "";
8965
+ header.push({
8966
+ name: "paragraph",
8967
+ props: {
8968
+ text: both ? `${title} ${tracker}` : title || tracker,
8969
+ ...both ? { tabStops: [{ type: "right", position: measure }] } : { alignment: title !== "" ? "left" : head?.alignment ?? "right" },
8970
+ spacing: { before: 0, after: 0 },
8971
+ ...roleProps(
8972
+ theme,
8973
+ head?.type ?? "tracker",
8974
+ FALLBACK2.headFont,
8975
+ head?.color
8976
+ )
8977
+ }
8978
+ });
8979
+ headerMap["/0"] = scope.block;
8980
+ headerMap["/0/props/text"] = (tracker !== "" ? scope.opener?.slot ?? slot("tracker") : slot("title")) ?? scope.block;
8981
+ }
8982
+ const headRule = head?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8983
+ if (headRule > 0) {
8984
+ header.push({
8985
+ name: "divider",
8986
+ props: {
8987
+ thickness: clampRule(headRule),
8988
+ color: head?.rule?.color ?? FALLBACK2.ruleColor,
8989
+ spacing: { before: 2, after: 0 }
8990
+ }
8991
+ });
8992
+ headerMap[`/${header.length - 1}`] = scope.block;
8993
+ }
8994
+ const footer = [];
8995
+ const footerMap = {};
8996
+ const footRule = foot?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8997
+ if (footRule > 0) {
8998
+ footer.push({
8999
+ name: "divider",
9000
+ props: {
9001
+ thickness: clampRule(footRule),
9002
+ color: foot?.rule?.color ?? FALLBACK2.ruleColor,
9003
+ spacing: { before: 0, after: 4 }
9004
+ }
9005
+ });
9006
+ footerMap["/0"] = scope.block;
9007
+ }
9008
+ const parts = [
9009
+ props.confidentiality ?? "",
9010
+ props.pageNumbers === false ? "" : PAGE_OF_TOTAL,
9011
+ props.date ?? ""
9012
+ ];
9013
+ const present = parts.filter((part) => part !== "");
9014
+ if (present.length > 0) {
9015
+ const single = present.length === 1;
9016
+ const alignment2 = present[0] === PAGE_OF_TOTAL ? "center" : foot?.alignment ?? "left";
9017
+ footer.push({
9018
+ name: "paragraph",
9019
+ props: {
9020
+ text: single ? present[0] : parts.join(" ").replace(/\t+$/, ""),
9021
+ ...single ? { alignment: alignment2 } : {
9022
+ tabStops: [
9023
+ { type: "center", position: Math.round(measure / 2) },
9024
+ { type: "right", position: measure }
9025
+ ]
9026
+ },
9027
+ spacing: { before: 0, after: 0 },
9028
+ ...roleProps(
9029
+ theme,
9030
+ foot?.type ?? "footer",
9031
+ FALLBACK2.footFont,
9032
+ foot?.color
9033
+ )
9034
+ }
9035
+ });
9036
+ const index = footer.length - 1;
9037
+ footerMap[`/${index}`] = scope.block;
9038
+ footerMap[`/${index}/props/text`] = slot("confidentiality") ?? slot("date") ?? scope.block;
9039
+ }
9040
+ return {
9041
+ header: { children: header, sourceMap: headerMap },
9042
+ footer: { children: footer, sourceMap: footerMap }
9043
+ };
9044
+ }
9045
+
9046
+ // src/blocks/sectionOpener.ts
9047
+ function compileSectionOpener(props, theme) {
9048
+ const children = [];
9049
+ const sourceMap = {};
9050
+ const pageBreak = props.pageBreak === true;
9051
+ if (props.number !== void 0) {
9052
+ children.push({
9053
+ name: "paragraph",
9054
+ props: {
9055
+ text: String(props.number),
9056
+ keepNext: true,
9057
+ spacing: { after: 2 },
9058
+ ...pageBreak && { pageBreak: true },
9059
+ ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
9060
+ }
9061
+ });
9062
+ sourceMap["/children/0"] = "";
9063
+ sourceMap["/children/0/props/text"] = "/props/number";
9064
+ }
9065
+ const index = children.length;
9066
+ children.push({
9067
+ name: "heading",
9068
+ props: {
9069
+ text: props.title,
9070
+ level: 1,
9071
+ ...pageBreak && index === 0 && { pageBreak: true }
9072
+ }
9073
+ });
9074
+ sourceMap[`/children/${index}`] = "";
9075
+ sourceMap[`/children/${index}/props/text`] = "/props/title";
9076
+ return { children, sourceMap };
9077
+ }
9078
+ function sectionTracker(section2) {
9079
+ if (!Array.isArray(section2.children)) return void 0;
9080
+ for (const [index, child] of section2.children.entries()) {
9081
+ if (typeof child !== "object" || child === null || child.name !== "section-opener" || child.enabled === false) {
9082
+ continue;
9083
+ }
9084
+ const props = child.props;
9085
+ if (!props) continue;
9086
+ if (typeof props.tracker === "string" && props.tracker !== "") {
9087
+ return { text: props.tracker, slot: `/children/${index}/props/tracker` };
9088
+ }
9089
+ if (typeof props.title === "string" && props.title !== "") {
9090
+ return { text: props.title, slot: `/children/${index}/props/title` };
9091
+ }
9092
+ }
9093
+ return void 0;
9094
+ }
9095
+
9096
+ // src/blocks/index.ts
9097
+ var BLOCK_NAMES = [
9098
+ "key-takeaways",
9099
+ "cover",
9100
+ "section-opener",
9101
+ "running-head"
9102
+ ];
9103
+ function slotBudget(block2, props, pointer, slot, maxWords) {
9104
+ const value = props[slot];
9105
+ return typeof value === "string" ? [
9106
+ {
9107
+ block: block2,
9108
+ slot,
9109
+ path: `${pointer}/props/${slot}`,
9110
+ words: wordCount(value),
9111
+ maxWords
9112
+ }
9113
+ ] : [];
9114
+ }
9115
+ function budgetsOf(block2, budget) {
9116
+ return (props, pointer) => Object.entries(budget).flatMap(
9117
+ ([slot, { maxWords }]) => slotBudget(block2, props, pointer, slot, maxWords)
9118
+ );
9119
+ }
9120
+ var BLOCKS = {
9121
+ "key-takeaways": {
9122
+ kind: "flow",
9123
+ compile: (props, theme) => compileKeyTakeaways(props, theme),
9124
+ budgets: (props, pointer) => (Array.isArray(props.items) ? props.items : []).flatMap(
9125
+ (item, index) => typeof item === "string" ? [
9126
+ {
9127
+ block: "key-takeaways",
9128
+ slot: "items",
9129
+ path: `${pointer}/props/items/${index}`,
9130
+ words: wordCount(item),
9131
+ maxWords: KEY_TAKEAWAYS_BUDGET.items.maxWords
9132
+ }
9133
+ ] : []
9134
+ )
9135
+ },
9136
+ cover: {
9137
+ kind: "flow",
9138
+ compile: (props, theme) => compileCover(props, theme),
9139
+ budgets: budgetsOf("cover", COVER_BUDGET)
9140
+ },
9141
+ "section-opener": {
9142
+ kind: "flow",
9143
+ compile: (props, theme) => compileSectionOpener(props, theme),
9144
+ budgets: budgetsOf("section-opener", SECTION_OPENER_BUDGET)
9145
+ },
9146
+ "running-head": {
9147
+ kind: "chrome",
9148
+ budgets: budgetsOf("running-head", RUNNING_HEAD_BUDGET)
9149
+ }
9150
+ };
9151
+ function isBlockName(name) {
9152
+ return BLOCK_NAMES.includes(name);
9153
+ }
9154
+ function isChromeBlockName(name) {
9155
+ return isBlockName(name) && BLOCKS[name].kind === "chrome";
9156
+ }
9157
+ function isRecord(value) {
9158
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9159
+ }
9160
+ var SECTION_CHILD = /^\/children\/\d+\/children\/\d+$/;
9161
+ var SECTION = /^\/children\/(\d+)$/;
9162
+ function planChrome(document, theme) {
9163
+ const plans = /* @__PURE__ */ new Map();
9164
+ if (!isRecord(document) || document.name !== "docx" || !Array.isArray(document.children)) {
9165
+ return plans;
9166
+ }
9167
+ const metadata = isRecord(document.props) ? document.props.metadata : void 0;
9168
+ const documentTitle = isRecord(metadata) && typeof metadata.title === "string" ? metadata.title : void 0;
9169
+ let inForce;
9170
+ document.children.forEach((section2, index) => {
9171
+ if (!isRecord(section2) || section2.name !== "section" || section2.enabled === false) {
9172
+ return;
9173
+ }
9174
+ const sectionPointer = `/children/${index}`;
9175
+ const children = Array.isArray(section2.children) ? section2.children : [];
9176
+ children.forEach((child, childIndex) => {
9177
+ if (isRecord(child) && isChromeBlockName(child.name) && child.enabled !== false) {
9178
+ inForce = {
9179
+ block: `${sectionPointer}/children/${childIndex}`,
9180
+ props: isRecord(child.props) ? child.props : {}
9181
+ };
9182
+ }
9183
+ });
9184
+ if (!inForce) return;
9185
+ const opener = sectionTracker(section2);
9186
+ const sectionProps = isRecord(section2.props) ? section2.props : {};
9187
+ plans.set(
9188
+ index,
9189
+ compileRunningHead(inForce.props, theme, {
9190
+ block: inForce.block,
9191
+ documentTitle,
9192
+ ...opener && {
9193
+ opener: {
9194
+ text: opener.text,
9195
+ slot: `${sectionPointer}${opener.slot}`
9196
+ }
9197
+ },
9198
+ ...isRecord(sectionProps.page) && {
9199
+ page: sectionProps.page
9200
+ }
9201
+ })
9202
+ );
9203
+ });
9204
+ return plans;
9205
+ }
9206
+ function expandBlocks(document, theme) {
9207
+ const sourceMap = {};
9208
+ const blocks = [];
9209
+ const chrome = planChrome(document, theme);
9210
+ const walk2 = (node, pointer) => {
9211
+ if (Array.isArray(node)) {
9212
+ return node.map((child, index) => walk2(child, `${pointer}/${index}`));
9213
+ }
9214
+ if (!isRecord(node)) return node;
9215
+ if (typeof node.name === "string" && node.enabled !== false && isBlockName(node.name)) {
9216
+ const definition = BLOCKS[node.name];
9217
+ if (definition.kind === "chrome") {
9218
+ if (!SECTION_CHILD.test(pointer)) return node;
9219
+ blocks.push(pointer);
9220
+ return { ...node, children: [] };
9221
+ }
9222
+ if (isRecord(node.props)) {
9223
+ const compiled = definition.compile(node.props, theme);
9224
+ blocks.push(pointer);
9225
+ for (const [emitted, authored] of Object.entries(compiled.sourceMap)) {
9226
+ sourceMap[`${pointer}${emitted}`] = `${pointer}${authored}`;
9227
+ }
9228
+ return { ...node, children: compiled.children };
9229
+ }
9230
+ }
9231
+ const next = { ...node };
9232
+ if (isRecord(node.props)) {
9233
+ const props = { ...node.props };
9234
+ let changed = false;
9235
+ for (const key of ["header", "footer"]) {
9236
+ if (Array.isArray(props[key])) {
9237
+ props[key] = walk2(props[key], `${pointer}/props/${key}`);
9238
+ changed = true;
9239
+ }
9240
+ }
9241
+ if (Array.isArray(props.columns)) {
9242
+ props.columns = props.columns.map((column, index) => {
9243
+ if (!isRecord(column)) return column;
9244
+ const base = `${pointer}/props/columns/${index}`;
9245
+ const out = { ...column };
9246
+ if (isRecord(column.header) && "content" in column.header) {
9247
+ out.header = {
9248
+ ...column.header,
9249
+ content: walk2(column.header.content, `${base}/header/content`)
9250
+ };
9251
+ }
9252
+ if (Array.isArray(column.cells)) {
9253
+ out.cells = column.cells.map(
9254
+ (cell, cellIndex) => isRecord(cell) && "content" in cell ? {
9255
+ ...cell,
9256
+ content: walk2(
9257
+ cell.content,
9258
+ `${base}/cells/${cellIndex}/content`
9259
+ )
9260
+ } : cell
9261
+ );
9262
+ }
9263
+ return out;
9264
+ });
9265
+ changed = true;
9266
+ }
9267
+ if (changed) next.props = props;
9268
+ }
9269
+ if (Array.isArray(node.children)) {
9270
+ next.children = walk2(node.children, `${pointer}/children`);
9271
+ }
9272
+ const section2 = SECTION.exec(pointer);
9273
+ if (section2 && node.name === "section" && node.enabled !== false) {
9274
+ const plan = chrome.get(Number(section2[1]));
9275
+ if (plan) {
9276
+ const props = isRecord(next.props) ? { ...next.props } : {};
9277
+ let changed = false;
9278
+ for (const part of ["header", "footer"]) {
9279
+ const { children, sourceMap: partMap } = plan[part];
9280
+ if (props[part] !== void 0 || children.length === 0) continue;
9281
+ props[part] = children;
9282
+ changed = true;
9283
+ const base = `${pointer}/props/${part}`;
9284
+ sourceMap[base] = partMap["/0"] ?? pointer;
9285
+ for (const [emitted, authored] of Object.entries(partMap)) {
9286
+ sourceMap[`${base}${emitted}`] = authored;
9287
+ }
9288
+ }
9289
+ if (props.pageBreak === void 0) {
9290
+ props.pageBreak = true;
9291
+ changed = true;
9292
+ }
9293
+ if (changed) next.props = props;
9294
+ }
9295
+ }
9296
+ return next;
9297
+ };
9298
+ const expanded = walk2(document, "");
9299
+ return {
9300
+ document: blocks.length > 0 ? expanded : document,
9301
+ sourceMap,
9302
+ blocks
9303
+ };
9304
+ }
9305
+ function toAuthoredPointer(sourceMap, pointer) {
9306
+ let best;
9307
+ for (const emitted of Object.keys(sourceMap)) {
9308
+ if ((pointer === emitted || pointer.startsWith(`${emitted}/`)) && (best === void 0 || emitted.length > best.length)) {
9309
+ best = emitted;
9310
+ }
9311
+ }
9312
+ if (best === void 0) return pointer;
9313
+ return `${sourceMap[best]}${pointer.slice(best.length)}`;
9314
+ }
9315
+ function blockSlotBudgets(document, blocks) {
9316
+ return blocks.flatMap((pointer) => {
9317
+ const node = nodeAt(document, pointer);
9318
+ if (!isRecord(node) || !isRecord(node.props) || !isBlockName(node.name)) {
9319
+ return [];
9320
+ }
9321
+ return BLOCKS[node.name].budgets(node.props, pointer);
9322
+ });
9323
+ }
9324
+ function wordCount(text) {
9325
+ const trimmed = text.trim();
9326
+ return trimmed === "" ? 0 : trimmed.split(/\s+/).length;
9327
+ }
9328
+ function nodeAt(root, pointer) {
9329
+ if (pointer === "") return root;
9330
+ let current = root;
9331
+ for (const segment of pointer.slice(1).split("/")) {
9332
+ const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
9333
+ if (Array.isArray(current)) current = current[Number(key)];
9334
+ else if (isRecord(current)) current = current[key];
9335
+ else return void 0;
9336
+ }
9337
+ return current;
9338
+ }
9339
+
8298
9340
  // src/ir/compiler.ts
8299
9341
  init_types();
8300
9342
  init_units();
@@ -8592,33 +9634,47 @@ function compileChromeParagraph(component, scope) {
8592
9634
  const text = String(props.text ?? "");
8593
9635
  if (reportUnlowered(component, props, text, scope)) return [];
8594
9636
  const font = props.font ?? {};
8595
- const normal = getNormalStyle(ctx.theme);
9637
+ const named = chromeStyle(ctx.theme, props.themeStyle);
9638
+ const base = { ...getNormalStyle(ctx.theme), ...named };
9639
+ const styleId = named ? paragraphStyleId(props.themeStyle) ?? "Normal" : "Normal";
9640
+ if (styleId !== "Normal" && !ctx.styleIds.has(styleId)) {
9641
+ ctx.styleIds.add(styleId);
9642
+ }
8596
9643
  const chromeProps = {
8597
9644
  text,
8598
- ...props.alignment ? { alignment: props.alignment } : {},
9645
+ ...named ? { themeStyle: props.themeStyle } : {},
9646
+ ...props.alignment ? { alignment: props.alignment } : named?.alignment ? { alignment: named.alignment } : {},
8599
9647
  ...props.spacing ? { spacing: props.spacing } : {},
9648
+ ...props.indent ? { indent: props.indent } : {},
9649
+ ...props.tabStops ? { tabStops: props.tabStops } : {},
8600
9650
  ...props.boldColor ? { boldColor: props.boldColor } : {},
8601
9651
  font: {
8602
- family: font.family || resolveFontFamily(ctx.theme, normal.font) || getThemeFonts(ctx.theme).body.family,
8603
- size: font.size ?? normal.size ?? 11,
8604
- color: font.color || normal.color || "textPrimary",
8605
- bold: font.bold ?? false,
8606
- italic: font.italic ?? false,
9652
+ family: font.family || resolveFontFamily(ctx.theme, base.font) || getThemeFonts(ctx.theme).body.family,
9653
+ size: font.size ?? base.size ?? 11,
9654
+ color: font.color || base.color || "textPrimary",
9655
+ bold: font.bold ?? base.bold ?? false,
9656
+ italic: font.italic ?? base.italic ?? false,
8607
9657
  ...font.underline !== void 0 ? { underline: font.underline } : {},
8608
- ...font.fontWeight !== void 0 ? { fontWeight: font.fontWeight } : {},
8609
- ...font.case !== void 0 ? { case: font.case } : {},
9658
+ ...(font.fontWeight ?? base.fontWeight) !== void 0 ? { fontWeight: font.fontWeight ?? base.fontWeight } : {},
9659
+ ...(font.case ?? base.case) !== void 0 ? { case: font.case ?? base.case } : {},
9660
+ ...(font.characterSpacing ?? base.characterSpacing) !== void 0 ? { characterSpacing: font.characterSpacing ?? base.characterSpacing } : {},
8610
9661
  ...font.lineSpacing !== void 0 ? { lineSpacing: font.lineSpacing } : {}
8611
9662
  }
8612
9663
  };
8613
9664
  return [
8614
9665
  paragraphNode(scope, compileRuns(chromeProps, text, ctx, scope.path), {
8615
- styleId: "Normal",
9666
+ styleId,
8616
9667
  formatting: paragraphFormatting(chromeProps, ctx, {
8617
9668
  alwaysSpacing: true
8618
9669
  })
8619
9670
  })
8620
9671
  ];
8621
9672
  }
9673
+ function chromeStyle(theme, themeStyle) {
9674
+ if (typeof themeStyle !== "string" || !themeStyle) return void 0;
9675
+ const styles = getThemeStyles(theme);
9676
+ return styles[themeStyle] ?? styles[themeStyle.toLowerCase()];
9677
+ }
8622
9678
  function compileChromeImage(component, scope) {
8623
9679
  const { ctx, path: path3 } = scope;
8624
9680
  const props = component.props ?? {};
@@ -8706,6 +9762,10 @@ function compileComponent(component, scope) {
8706
9762
  case "table":
8707
9763
  return compileTable(component, scope);
8708
9764
  default:
9765
+ if (isBlockName(component.name)) {
9766
+ if (component.enabled === false) return [];
9767
+ return compileBlock(component, scope);
9768
+ }
8709
9769
  scope.ctx.unsupported.push({
8710
9770
  name: component.name,
8711
9771
  path: scope.path
@@ -8713,6 +9773,20 @@ function compileComponent(component, scope) {
8713
9773
  return [];
8714
9774
  }
8715
9775
  }
9776
+ function compileBlock(component, scope) {
9777
+ const children = component.children;
9778
+ if (!Array.isArray(children)) {
9779
+ scope.ctx.unsupported.push({ name: component.name, path: scope.path });
9780
+ return [];
9781
+ }
9782
+ return children.flatMap(
9783
+ (child, index) => compileComponent(child, {
9784
+ ...scope,
9785
+ path: `${scope.path}.children[${index}]`,
9786
+ id: `${scope.id}:b${index}`
9787
+ })
9788
+ );
9789
+ }
8716
9790
  var MARKDOWN_LIST_LEVELS = {
8717
9791
  unordered: [
8718
9792
  { level: 0, format: "bullet", text: "\u2022", alignment: "left" },
@@ -12157,11 +13231,13 @@ function walkActive(node, path3, page, visit) {
12157
13231
  );
12158
13232
  }
12159
13233
  function prepareDocxQualityDocument(document, options = {}) {
12160
- const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
13234
+ const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
12161
13235
  customThemes: options.customThemes,
12162
13236
  fonts: options.fonts,
12163
13237
  warnings: options.warnings
12164
13238
  });
13239
+ const expanded = expandBlocks(themed.document, themed.theme);
13240
+ const context = expanded.blocks.length > 0 ? { ...themed, document: expanded.document } : themed;
12165
13241
  const resolved = resolveDocumentTree(context.document, context.theme);
12166
13242
  const basePage = pageBox(resolved.theme, context.themeName);
12167
13243
  const typography = {
@@ -12171,13 +13247,28 @@ function prepareDocxQualityDocument(document, options = {}) {
12171
13247
  const facts = [];
12172
13248
  const provenance = {};
12173
13249
  let previousHeadingLevel;
12174
- const addFact = (fact) => {
13250
+ const authoredPath = (path3) => toAuthoredPointer(expanded.sourceMap, path3);
13251
+ const addFact = (raw) => {
13252
+ const fact = {
13253
+ ...raw,
13254
+ path: authoredPath(raw.path),
13255
+ ...raw.relatedPaths && {
13256
+ relatedPaths: raw.relatedPaths.map(authoredPath)
13257
+ }
13258
+ };
12175
13259
  facts.push(fact);
12176
13260
  provenance[fact.id] = {
12177
13261
  path: fact.path,
12178
13262
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
12179
13263
  };
12180
13264
  };
13265
+ for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
13266
+ addFact({
13267
+ id: `docx:block-slot:${budget.path}`,
13268
+ kind: "docx/block-slot",
13269
+ ...budget
13270
+ });
13271
+ }
12181
13272
  const paletteHexes = {};
12182
13273
  const visualColors = designColors2(
12183
13274
  resolved.theme.colors,
@@ -12320,7 +13411,16 @@ function prepareDocxQualityDocument(document, options = {}) {
12320
13411
  },
12321
13412
  facts,
12322
13413
  provenance,
12323
- renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
13414
+ renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2,
13415
+ ...expanded.blocks.length > 0 && {
13416
+ metadata: {
13417
+ blocks: {
13418
+ sourceMap: expanded.sourceMap,
13419
+ blocks: expanded.blocks,
13420
+ document: context.document
13421
+ }
13422
+ }
13423
+ }
12324
13424
  };
12325
13425
  }
12326
13426
 
@@ -12360,19 +13460,22 @@ async function compileDocumentScoped(document, options, warnings) {
12360
13460
  });
12361
13461
  const { context } = prepared.model;
12362
13462
  const hasVisual = collectVisualProps(context.document).length > 0;
13463
+ const hasHighcharts = containsComponent(context.document, "highcharts");
12363
13464
  const resolvedFonts = await resolveDocumentFonts(
12364
13465
  context.document,
12365
13466
  context.theme,
12366
13467
  options.fonts,
12367
13468
  warnings,
12368
- hasVisual
13469
+ hasVisual || hasHighcharts
12369
13470
  );
12370
13471
  const visualFonts = hasVisual ? toRasterizeFontFaces(resolvedFonts, warnings) : [];
13472
+ const chartFonts = hasHighcharts ? toChartFontFaces(resolvedFonts) : [];
12371
13473
  const desugared = await desugarExternals(context.document, {
12372
13474
  theme: context.theme,
12373
13475
  ...options.services ? { services: options.services } : {},
12374
13476
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
12375
- ...visualFonts.length > 0 ? { visualFonts } : {}
13477
+ ...visualFonts.length > 0 ? { visualFonts } : {},
13478
+ ...chartFonts.length > 0 ? { chartFonts } : {}
12376
13479
  });
12377
13480
  const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
12378
13481
  const structure = await processResolvedDocument(