@json-to-office/core-docx 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,8 +5837,73 @@ 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);
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;
5850
+ }
5851
+ if (typeof height === "number" && chart.height > 0) {
5852
+ return height / chart.height * chart.width * POINTS_PER_PIXEL;
5853
+ }
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;
5881
+ }
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
+ };
5893
+ }
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
+ );
5433
5907
  const chart = await generateChart(config, servicesConfig);
5434
5908
  const hasConfigDimensions = config.width !== void 0 || config.height !== void 0;
5435
5909
  return {
@@ -5440,74 +5914,6 @@ async function renderChartToImageProps(props, theme, servicesConfig) {
5440
5914
  };
5441
5915
  }
5442
5916
 
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);
5460
- }
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) };
5490
- }
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
- );
5501
- }
5502
- return next;
5503
- }
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;
5509
- }
5510
-
5511
5917
  // src/core/desugarExternals.ts
5512
5918
  async function desugarExternals(document, options) {
5513
5919
  const prerastered = await prerasterizeVisuals(
@@ -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,210 @@ 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 { KEY_TAKEAWAYS_BUDGET } from "@json-to-office/shared-docx";
8707
+
8708
+ // src/blocks/keyTakeaways.ts
8709
+ var MIN_RULE_PT = 0.25;
8710
+ var MAX_RULE_PT = 12;
8711
+ var KEY_TAKEAWAYS_DEFAULT_LABEL = "Key takeaways";
8712
+ var FALLBACK_RECIPE = {
8713
+ ruleWeightPt: 1.5,
8714
+ ruleColor: "accent",
8715
+ padPt: 6,
8716
+ type: "label"
8717
+ };
8718
+ function compileKeyTakeaways(props, theme) {
8719
+ const recipe = theme.chrome?.keyTakeaways;
8720
+ const labelRole = recipe?.type ?? FALLBACK_RECIPE.type;
8721
+ const labelStyle = theme.styles?.[labelRole];
8722
+ const ruleWeightPt = Math.min(
8723
+ MAX_RULE_PT,
8724
+ Math.max(
8725
+ MIN_RULE_PT,
8726
+ recipe?.rule?.weightPt ?? FALLBACK_RECIPE.ruleWeightPt
8727
+ )
8728
+ );
8729
+ const ruleColor = recipe?.rule?.color ?? recipe?.color ?? FALLBACK_RECIPE.ruleColor;
8730
+ const padPt = recipe?.padPt ?? FALLBACK_RECIPE.padPt;
8731
+ const children = [
8732
+ {
8733
+ name: "divider",
8734
+ props: {
8735
+ thickness: ruleWeightPt,
8736
+ color: ruleColor,
8737
+ spacing: { before: 12, after: padPt }
8738
+ }
8739
+ },
8740
+ {
8741
+ name: "paragraph",
8742
+ props: {
8743
+ text: props.label ?? KEY_TAKEAWAYS_DEFAULT_LABEL,
8744
+ keepNext: true,
8745
+ spacing: { after: 4 },
8746
+ // The label role when the theme resolves one; a bold run in the
8747
+ // recipe's colour otherwise, so the label still reads as a label.
8748
+ ...labelStyle ? { themeStyle: labelRole } : {
8749
+ font: {
8750
+ bold: true,
8751
+ ...recipe?.color !== void 0 && { color: recipe.color }
8752
+ }
8753
+ }
8754
+ }
8755
+ },
8756
+ {
8757
+ name: "list",
8758
+ props: { items: [...props.items] }
8759
+ },
8760
+ {
8761
+ name: "divider",
8762
+ props: {
8763
+ thickness: 0.5,
8764
+ color: "borderPrimary",
8765
+ spacing: { before: padPt, after: 12 }
8766
+ }
8767
+ }
8768
+ ];
8769
+ return {
8770
+ children,
8771
+ sourceMap: {
8772
+ "/children/0": "",
8773
+ "/children/1": "",
8774
+ "/children/1/props/text": "/props/label",
8775
+ "/children/2": "",
8776
+ "/children/2/props/items": "/props/items",
8777
+ "/children/3": ""
8778
+ }
8779
+ };
8780
+ }
8781
+
8782
+ // src/blocks/index.ts
8783
+ var BLOCK_NAMES = ["key-takeaways"];
8784
+ var BLOCKS = {
8785
+ "key-takeaways": {
8786
+ compile: (props, theme) => compileKeyTakeaways(props, theme),
8787
+ budgets: (props, pointer) => (Array.isArray(props.items) ? props.items : []).flatMap(
8788
+ (item, index) => typeof item === "string" ? [
8789
+ {
8790
+ block: "key-takeaways",
8791
+ slot: "items",
8792
+ path: `${pointer}/props/items/${index}`,
8793
+ words: wordCount(item),
8794
+ maxWords: KEY_TAKEAWAYS_BUDGET.items.maxWords
8795
+ }
8796
+ ] : []
8797
+ )
8798
+ }
8799
+ };
8800
+ function isBlockName(name) {
8801
+ return BLOCK_NAMES.includes(name);
8802
+ }
8803
+ function isRecord(value) {
8804
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8805
+ }
8806
+ function expandBlocks(document, theme) {
8807
+ const sourceMap = {};
8808
+ const blocks = [];
8809
+ const walk2 = (node, pointer) => {
8810
+ if (Array.isArray(node)) {
8811
+ return node.map((child, index) => walk2(child, `${pointer}/${index}`));
8812
+ }
8813
+ if (!isRecord(node)) return node;
8814
+ if (typeof node.name === "string" && isRecord(node.props)) {
8815
+ if (node.enabled !== false && isBlockName(node.name)) {
8816
+ const compiled = BLOCKS[node.name].compile(node.props, theme);
8817
+ blocks.push(pointer);
8818
+ for (const [emitted, authored] of Object.entries(compiled.sourceMap)) {
8819
+ sourceMap[`${pointer}${emitted}`] = `${pointer}${authored}`;
8820
+ }
8821
+ return { ...node, children: compiled.children };
8822
+ }
8823
+ }
8824
+ const next = { ...node };
8825
+ if (isRecord(node.props)) {
8826
+ const props = { ...node.props };
8827
+ let changed = false;
8828
+ for (const key of ["header", "footer"]) {
8829
+ if (Array.isArray(props[key])) {
8830
+ props[key] = walk2(props[key], `${pointer}/props/${key}`);
8831
+ changed = true;
8832
+ }
8833
+ }
8834
+ if (Array.isArray(props.columns)) {
8835
+ props.columns = props.columns.map((column, index) => {
8836
+ if (!isRecord(column)) return column;
8837
+ const base = `${pointer}/props/columns/${index}`;
8838
+ const out = { ...column };
8839
+ if (isRecord(column.header) && "content" in column.header) {
8840
+ out.header = {
8841
+ ...column.header,
8842
+ content: walk2(column.header.content, `${base}/header/content`)
8843
+ };
8844
+ }
8845
+ if (Array.isArray(column.cells)) {
8846
+ out.cells = column.cells.map(
8847
+ (cell, cellIndex) => isRecord(cell) && "content" in cell ? {
8848
+ ...cell,
8849
+ content: walk2(
8850
+ cell.content,
8851
+ `${base}/cells/${cellIndex}/content`
8852
+ )
8853
+ } : cell
8854
+ );
8855
+ }
8856
+ return out;
8857
+ });
8858
+ changed = true;
8859
+ }
8860
+ if (changed) next.props = props;
8861
+ }
8862
+ if (Array.isArray(node.children)) {
8863
+ next.children = walk2(node.children, `${pointer}/children`);
8864
+ }
8865
+ return next;
8866
+ };
8867
+ const expanded = walk2(document, "");
8868
+ return {
8869
+ document: blocks.length > 0 ? expanded : document,
8870
+ sourceMap,
8871
+ blocks
8872
+ };
8873
+ }
8874
+ function toAuthoredPointer(sourceMap, pointer) {
8875
+ let best;
8876
+ for (const emitted of Object.keys(sourceMap)) {
8877
+ if ((pointer === emitted || pointer.startsWith(`${emitted}/`)) && (best === void 0 || emitted.length > best.length)) {
8878
+ best = emitted;
8879
+ }
8880
+ }
8881
+ if (best === void 0) return pointer;
8882
+ return `${sourceMap[best]}${pointer.slice(best.length)}`;
8883
+ }
8884
+ function blockSlotBudgets(document, blocks) {
8885
+ return blocks.flatMap((pointer) => {
8886
+ const node = nodeAt(document, pointer);
8887
+ if (!isRecord(node) || !isRecord(node.props) || !isBlockName(node.name)) {
8888
+ return [];
8889
+ }
8890
+ return BLOCKS[node.name].budgets(node.props, pointer);
8891
+ });
8892
+ }
8893
+ function wordCount(text) {
8894
+ const trimmed = text.trim();
8895
+ return trimmed === "" ? 0 : trimmed.split(/\s+/).length;
8896
+ }
8897
+ function nodeAt(root, pointer) {
8898
+ if (pointer === "") return root;
8899
+ let current = root;
8900
+ for (const segment of pointer.slice(1).split("/")) {
8901
+ const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
8902
+ if (Array.isArray(current)) current = current[Number(key)];
8903
+ else if (isRecord(current)) current = current[key];
8904
+ else return void 0;
8905
+ }
8906
+ return current;
8907
+ }
8908
+
8298
8909
  // src/ir/compiler.ts
8299
8910
  init_types();
8300
8911
  init_units();
@@ -8706,6 +9317,10 @@ function compileComponent(component, scope) {
8706
9317
  case "table":
8707
9318
  return compileTable(component, scope);
8708
9319
  default:
9320
+ if (isBlockName(component.name)) {
9321
+ if (component.enabled === false) return [];
9322
+ return compileBlock(component, scope);
9323
+ }
8709
9324
  scope.ctx.unsupported.push({
8710
9325
  name: component.name,
8711
9326
  path: scope.path
@@ -8713,6 +9328,20 @@ function compileComponent(component, scope) {
8713
9328
  return [];
8714
9329
  }
8715
9330
  }
9331
+ function compileBlock(component, scope) {
9332
+ const children = component.children ?? [];
9333
+ if (children.length === 0) {
9334
+ scope.ctx.unsupported.push({ name: component.name, path: scope.path });
9335
+ return [];
9336
+ }
9337
+ return children.flatMap(
9338
+ (child, index) => compileComponent(child, {
9339
+ ...scope,
9340
+ path: `${scope.path}.children[${index}]`,
9341
+ id: `${scope.id}:b${index}`
9342
+ })
9343
+ );
9344
+ }
8716
9345
  var MARKDOWN_LIST_LEVELS = {
8717
9346
  unordered: [
8718
9347
  { level: 0, format: "bullet", text: "\u2022", alignment: "left" },
@@ -12157,11 +12786,13 @@ function walkActive(node, path3, page, visit) {
12157
12786
  );
12158
12787
  }
12159
12788
  function prepareDocxQualityDocument(document, options = {}) {
12160
- const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
12789
+ const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
12161
12790
  customThemes: options.customThemes,
12162
12791
  fonts: options.fonts,
12163
12792
  warnings: options.warnings
12164
12793
  });
12794
+ const expanded = expandBlocks(themed.document, themed.theme);
12795
+ const context = expanded.blocks.length > 0 ? { ...themed, document: expanded.document } : themed;
12165
12796
  const resolved = resolveDocumentTree(context.document, context.theme);
12166
12797
  const basePage = pageBox(resolved.theme, context.themeName);
12167
12798
  const typography = {
@@ -12171,13 +12802,28 @@ function prepareDocxQualityDocument(document, options = {}) {
12171
12802
  const facts = [];
12172
12803
  const provenance = {};
12173
12804
  let previousHeadingLevel;
12174
- const addFact = (fact) => {
12805
+ const authoredPath = (path3) => toAuthoredPointer(expanded.sourceMap, path3);
12806
+ const addFact = (raw) => {
12807
+ const fact = {
12808
+ ...raw,
12809
+ path: authoredPath(raw.path),
12810
+ ...raw.relatedPaths && {
12811
+ relatedPaths: raw.relatedPaths.map(authoredPath)
12812
+ }
12813
+ };
12175
12814
  facts.push(fact);
12176
12815
  provenance[fact.id] = {
12177
12816
  path: fact.path,
12178
12817
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
12179
12818
  };
12180
12819
  };
12820
+ for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
12821
+ addFact({
12822
+ id: `docx:block-slot:${budget.path}`,
12823
+ kind: "docx/block-slot",
12824
+ ...budget
12825
+ });
12826
+ }
12181
12827
  const paletteHexes = {};
12182
12828
  const visualColors = designColors2(
12183
12829
  resolved.theme.colors,
@@ -12320,7 +12966,16 @@ function prepareDocxQualityDocument(document, options = {}) {
12320
12966
  },
12321
12967
  facts,
12322
12968
  provenance,
12323
- renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
12969
+ renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2,
12970
+ ...expanded.blocks.length > 0 && {
12971
+ metadata: {
12972
+ blocks: {
12973
+ sourceMap: expanded.sourceMap,
12974
+ blocks: expanded.blocks,
12975
+ document: context.document
12976
+ }
12977
+ }
12978
+ }
12324
12979
  };
12325
12980
  }
12326
12981
 
@@ -12360,19 +13015,22 @@ async function compileDocumentScoped(document, options, warnings) {
12360
13015
  });
12361
13016
  const { context } = prepared.model;
12362
13017
  const hasVisual = collectVisualProps(context.document).length > 0;
13018
+ const hasHighcharts = containsComponent(context.document, "highcharts");
12363
13019
  const resolvedFonts = await resolveDocumentFonts(
12364
13020
  context.document,
12365
13021
  context.theme,
12366
13022
  options.fonts,
12367
13023
  warnings,
12368
- hasVisual
13024
+ hasVisual || hasHighcharts
12369
13025
  );
12370
13026
  const visualFonts = hasVisual ? toRasterizeFontFaces(resolvedFonts, warnings) : [];
13027
+ const chartFonts = hasHighcharts ? toChartFontFaces(resolvedFonts) : [];
12371
13028
  const desugared = await desugarExternals(context.document, {
12372
13029
  theme: context.theme,
12373
13030
  ...options.services ? { services: options.services } : {},
12374
13031
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
12375
- ...visualFonts.length > 0 ? { visualFonts } : {}
13032
+ ...visualFonts.length > 0 ? { visualFonts } : {},
13033
+ ...chartFonts.length > 0 ? { chartFonts } : {}
12376
13034
  });
12377
13035
  const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
12378
13036
  const structure = await processResolvedDocument(