@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
package/dist/index.js CHANGED
@@ -238,43 +238,6 @@ var init_colorUtils = __esm({
238
238
  }
239
239
  });
240
240
 
241
- // src/utils/generationContext.ts
242
- import { AsyncLocalStorage } from "async_hooks";
243
- import { isAbsolute, resolve } from "path";
244
- function runWithBaseDir(baseDir, callback) {
245
- return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
246
- }
247
- function resolveFromBaseDir(filePath) {
248
- const base = baseDirStorage.getStore();
249
- if (!base || isAbsolute(filePath)) return filePath;
250
- return resolve(base, filePath);
251
- }
252
- function runWithWarnings(warnings, callback) {
253
- return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
254
- }
255
- function reportWarning(component, code, message, context) {
256
- const warnings = warningsStorage.getStore();
257
- if (warnings) {
258
- warnings.push({
259
- component,
260
- message,
261
- severity: "warning",
262
- context: { code, ...context }
263
- });
264
- return;
265
- }
266
- console.warn(`[json-to-docx] [${code}] ${message}`);
267
- }
268
- var generationDateStorage, baseDirStorage, warningsStorage;
269
- var init_generationContext = __esm({
270
- "src/utils/generationContext.ts"() {
271
- "use strict";
272
- generationDateStorage = new AsyncLocalStorage();
273
- baseDirStorage = new AsyncLocalStorage();
274
- warningsStorage = new AsyncLocalStorage();
275
- }
276
- });
277
-
278
241
  // src/templates/themes/minimal.docx.theme.json
279
242
  var minimal_docx_theme_default;
280
243
  var init_minimal_docx_theme = __esm({
@@ -880,6 +843,320 @@ var init_vermilion_docx_theme = __esm({
880
843
  }
881
844
  });
882
845
 
846
+ // src/templates/themes/consulting.docx.theme.json
847
+ var consulting_docx_theme_default;
848
+ var init_consulting_docx_theme = __esm({
849
+ "src/templates/themes/consulting.docx.theme.json"() {
850
+ consulting_docx_theme_default = {
851
+ name: "consulting",
852
+ displayName: "Consulting House",
853
+ 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",
854
+ version: "1.0.0",
855
+ colors: {
856
+ primary: "#1A1F26",
857
+ secondary: "#4B5563",
858
+ accent: "#1B4F8A",
859
+ text: "#1A1F26",
860
+ background: "#FFFFFF",
861
+ border: "#C9CED6",
862
+ textPrimary: "#1A1F26",
863
+ textSecondary: "#4B5563",
864
+ textMuted: "#7B8794",
865
+ borderPrimary: "#C9CED6",
866
+ borderSecondary: "#E4E7EB",
867
+ backgroundPrimary: "#FFFFFF",
868
+ backgroundSecondary: "#F2F4F7",
869
+ accent4: "#5B8DC9",
870
+ accent5: "#A9C4E4",
871
+ accent6: "#7B8794"
872
+ },
873
+ palette: {
874
+ rule: "#C9CED6",
875
+ textMuted: "#7B8794",
876
+ onPrimary: "#FFFFFF",
877
+ surfaceInverse: "#1A1F26",
878
+ positive: "#1E7F4F",
879
+ negative: "#B42318",
880
+ chart: ["accent", "secondary", "accent4", "textMuted", "accent5", "rule"]
881
+ },
882
+ fonts: {
883
+ heading: { family: "Arial", size: 16 },
884
+ body: { family: "Calibri", size: 10.5 },
885
+ mono: { family: "Consolas", size: 9.5 },
886
+ light: { family: "Arial", size: 16 }
887
+ },
888
+ page: {
889
+ size: "A4",
890
+ margins: {
891
+ top: 1440,
892
+ bottom: 1440,
893
+ left: 1440,
894
+ right: 1440,
895
+ header: 680,
896
+ footer: 680,
897
+ gutter: 0
898
+ }
899
+ },
900
+ typography: {
901
+ roles: {
902
+ eyebrow: {
903
+ face: "heading",
904
+ size: 8,
905
+ weight: 700,
906
+ case: "upper",
907
+ tracking: 8,
908
+ color: "accent",
909
+ spaceAfter: 4
910
+ },
911
+ display: {
912
+ face: "heading",
913
+ weight: 700,
914
+ color: "primary",
915
+ lineHeight: 1.05,
916
+ spaceAfter: 8
917
+ },
918
+ stat: {
919
+ face: "heading",
920
+ weight: 700,
921
+ color: "accent",
922
+ lineHeight: 1
923
+ },
924
+ quote: {
925
+ face: "body",
926
+ color: "textSecondary",
927
+ lineHeight: 1.3
928
+ },
929
+ label: {
930
+ face: "body",
931
+ size: 9,
932
+ weight: 700,
933
+ color: "textSecondary"
934
+ },
935
+ footer: { face: "body", size: 8, color: "textMuted" },
936
+ tableHeader: {
937
+ face: "body",
938
+ size: 9,
939
+ weight: 700,
940
+ color: "primary"
941
+ },
942
+ tableCell: { face: "body", size: 9.5, color: "textPrimary" },
943
+ chartLabel: {
944
+ face: "body",
945
+ size: 9,
946
+ weight: 400,
947
+ color: "textSecondary"
948
+ },
949
+ tracker: {
950
+ face: "body",
951
+ size: 8,
952
+ case: "upper",
953
+ tracking: 6,
954
+ color: "textMuted"
955
+ },
956
+ source: { face: "body", size: 8, color: "textMuted" }
957
+ },
958
+ scale: {
959
+ a4: { base: 10.5, ratio: 1.2, baselinePt: 2 },
960
+ letter: { base: 10.5, ratio: 1.2, baselinePt: 2 }
961
+ }
962
+ },
963
+ spacing: {
964
+ basePt: 8,
965
+ blockGap: { tight: 4, normal: 8, loose: 16 },
966
+ canvas: {
967
+ a4: { safeAreaIn: 1, gutterIn: 0, columns: 12 },
968
+ letter: { safeAreaIn: 1, gutterIn: 0, columns: 12 }
969
+ }
970
+ },
971
+ chrome: {
972
+ runningHead: {
973
+ type: "tracker",
974
+ color: "textMuted",
975
+ rule: { weightPt: 0.5, color: "rule" },
976
+ alignment: "right"
977
+ },
978
+ tracker: {
979
+ type: "tracker",
980
+ color: "textMuted",
981
+ alignment: "right"
982
+ },
983
+ actionTitle: { type: "display", color: "primary" },
984
+ keyTakeaways: {
985
+ type: "label",
986
+ color: "primary",
987
+ rule: { weightPt: 2, color: "accent" },
988
+ padPt: 8
989
+ },
990
+ sourceLine: { type: "source", color: "textMuted" },
991
+ confidentialFooter: {
992
+ type: "footer",
993
+ color: "textMuted",
994
+ rule: { weightPt: 0.5, color: "rule" },
995
+ alignment: "left"
996
+ },
997
+ logoSlot: { alignment: "right" },
998
+ cover: {
999
+ type: "display",
1000
+ color: "primary",
1001
+ rule: { weightPt: 3, color: "accent" }
1002
+ }
1003
+ },
1004
+ motif: {
1005
+ kind: "rule",
1006
+ color: "accent",
1007
+ weightPt: 3,
1008
+ placement: "top"
1009
+ },
1010
+ styles: {
1011
+ normal: {
1012
+ font: "body",
1013
+ color: "textPrimary",
1014
+ lineSpacing: { type: "multiple", value: 1.2 },
1015
+ alignment: "left",
1016
+ spacing: { after: 8 }
1017
+ },
1018
+ title: {
1019
+ font: "heading",
1020
+ size: 26,
1021
+ bold: true,
1022
+ color: "primary",
1023
+ alignment: "left",
1024
+ lineSpacing: { type: "multiple", value: 1.05 },
1025
+ spacing: { before: 0, after: 6 }
1026
+ },
1027
+ subtitle: {
1028
+ font: "body",
1029
+ size: 13,
1030
+ color: "textSecondary",
1031
+ alignment: "left",
1032
+ lineSpacing: { type: "multiple", value: 1.2 },
1033
+ spacing: { before: 0, after: 18 }
1034
+ },
1035
+ heading1: {
1036
+ font: "heading",
1037
+ size: 16,
1038
+ bold: true,
1039
+ color: "primary",
1040
+ lineSpacing: { type: "multiple", value: 1.1 },
1041
+ spacing: { before: 24, after: 8 },
1042
+ keepNext: true,
1043
+ alignment: "left",
1044
+ borders: {
1045
+ bottom: { style: "single", size: 4, color: "rule", space: 4 }
1046
+ }
1047
+ },
1048
+ heading2: {
1049
+ font: "heading",
1050
+ size: 12.5,
1051
+ bold: true,
1052
+ color: "primary",
1053
+ spacing: { before: 16, after: 6 },
1054
+ keepNext: true,
1055
+ alignment: "left"
1056
+ },
1057
+ heading3: {
1058
+ font: "body",
1059
+ size: 11,
1060
+ bold: true,
1061
+ color: "primary",
1062
+ spacing: { before: 12, after: 4 },
1063
+ keepNext: true,
1064
+ alignment: "left"
1065
+ },
1066
+ heading4: {
1067
+ font: "body",
1068
+ size: 10.5,
1069
+ bold: true,
1070
+ color: "textSecondary",
1071
+ spacing: { before: 10, after: 4 },
1072
+ keepNext: true,
1073
+ alignment: "left"
1074
+ },
1075
+ heading5: {
1076
+ font: "body",
1077
+ size: 10.5,
1078
+ bold: false,
1079
+ italic: true,
1080
+ color: "textSecondary",
1081
+ spacing: { before: 8, after: 4 },
1082
+ keepNext: true,
1083
+ alignment: "left"
1084
+ },
1085
+ heading6: {
1086
+ font: "body",
1087
+ size: 9,
1088
+ bold: true,
1089
+ case: "upper",
1090
+ color: "textMuted",
1091
+ characterSpacing: { type: "expanded", value: 20 },
1092
+ spacing: { before: 8, after: 4 },
1093
+ keepNext: true,
1094
+ alignment: "left"
1095
+ },
1096
+ TOC1: {
1097
+ font: "body",
1098
+ size: 10.5,
1099
+ bold: true,
1100
+ color: "textPrimary",
1101
+ spacing: { before: 4, after: 3 },
1102
+ tabStops: [{ type: "right", position: 9026, leader: "none" }]
1103
+ },
1104
+ TOC2: {
1105
+ font: "body",
1106
+ size: 10.5,
1107
+ color: "textSecondary",
1108
+ indent: { left: 240 },
1109
+ spacing: { after: 2 },
1110
+ tabStops: [{ type: "right", position: 9026, leader: "none" }]
1111
+ },
1112
+ TOC3: {
1113
+ font: "body",
1114
+ size: 10,
1115
+ color: "textMuted",
1116
+ indent: { left: 480 },
1117
+ spacing: { after: 2 },
1118
+ tabStops: [{ type: "right", position: 9026, leader: "none" }]
1119
+ }
1120
+ },
1121
+ componentDefaults: {
1122
+ table: {
1123
+ width: 100,
1124
+ borderColor: "#C9CED6",
1125
+ borderSize: 0.5,
1126
+ hideBorders: { left: true, right: true, insideVertical: true },
1127
+ cellDefaults: {
1128
+ font: { size: 9.5 },
1129
+ padding: { top: 4, bottom: 4 },
1130
+ verticalAlignment: "middle"
1131
+ },
1132
+ headerCellDefaults: {
1133
+ color: "#1A1F26",
1134
+ font: { size: 9, bold: true },
1135
+ padding: { top: 0, bottom: 5 },
1136
+ verticalAlignment: "bottom",
1137
+ borderColor: { bottom: "#1A1F26" },
1138
+ borderSize: { bottom: 0.75 }
1139
+ }
1140
+ },
1141
+ list: {
1142
+ format: "bullet",
1143
+ bullet: "\u2022",
1144
+ spacing: { item: 2 }
1145
+ },
1146
+ image: {
1147
+ alignment: "center"
1148
+ },
1149
+ statistic: {
1150
+ alignment: "left"
1151
+ },
1152
+ section: {
1153
+ pageBreak: false
1154
+ }
1155
+ }
1156
+ };
1157
+ }
1158
+ });
1159
+
883
1160
  // src/templates/themes/index.ts
884
1161
  import fs from "fs";
885
1162
  import path from "path";
@@ -894,7 +1171,8 @@ function loadThemesFromJson() {
894
1171
  const themes2 = {
895
1172
  minimal: ensureThemeDefaults(minimal_docx_theme_default),
896
1173
  devportal: ensureThemeDefaults(devportal_docx_theme_default),
897
- vermilion: ensureThemeDefaults(vermilion_docx_theme_default)
1174
+ vermilion: ensureThemeDefaults(vermilion_docx_theme_default),
1175
+ consulting: ensureThemeDefaults(consulting_docx_theme_default)
898
1176
  };
899
1177
  try {
900
1178
  const __filename = fileURLToPath(import.meta.url);
@@ -932,7 +1210,7 @@ function loadThemesFromJson() {
932
1210
  _themesCache = themes2;
933
1211
  return themes2;
934
1212
  }
935
- var _themesCache, themes, getTheme, getThemeWithFallback, hasTheme, getThemeNames, minimalTheme, devportalTheme, vermilionTheme;
1213
+ var _themesCache, themes, getTheme, getThemeWithFallback, hasTheme, getThemeNames, minimalTheme, devportalTheme, vermilionTheme, consultingTheme;
936
1214
  var init_themes = __esm({
937
1215
  "src/templates/themes/index.ts"() {
938
1216
  "use strict";
@@ -940,6 +1218,7 @@ var init_themes = __esm({
940
1218
  init_minimal_docx_theme();
941
1219
  init_devportal_docx_theme();
942
1220
  init_vermilion_docx_theme();
1221
+ init_consulting_docx_theme();
943
1222
  _themesCache = null;
944
1223
  themes = loadThemesFromJson();
945
1224
  getTheme = (themeName) => {
@@ -963,6 +1242,7 @@ var init_themes = __esm({
963
1242
  minimalTheme = themes["minimal"];
964
1243
  devportalTheme = themes["devportal"];
965
1244
  vermilionTheme = themes["vermilion"];
1245
+ consultingTheme = themes["consulting"];
966
1246
  }
967
1247
  });
968
1248
 
@@ -1279,6 +1559,43 @@ var init_layoutUtils = __esm({
1279
1559
  }
1280
1560
  });
1281
1561
 
1562
+ // src/utils/generationContext.ts
1563
+ import { AsyncLocalStorage } from "async_hooks";
1564
+ import { isAbsolute, resolve } from "path";
1565
+ function runWithBaseDir(baseDir, callback) {
1566
+ return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
1567
+ }
1568
+ function resolveFromBaseDir(filePath) {
1569
+ const base = baseDirStorage.getStore();
1570
+ if (!base || isAbsolute(filePath)) return filePath;
1571
+ return resolve(base, filePath);
1572
+ }
1573
+ function runWithWarnings(warnings, callback) {
1574
+ return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
1575
+ }
1576
+ function reportWarning(component, code, message, context) {
1577
+ const warnings = warningsStorage.getStore();
1578
+ if (warnings) {
1579
+ warnings.push({
1580
+ component,
1581
+ message,
1582
+ severity: "warning",
1583
+ context: { code, ...context }
1584
+ });
1585
+ return;
1586
+ }
1587
+ console.warn(`[json-to-docx] [${code}] ${message}`);
1588
+ }
1589
+ var generationDateStorage, baseDirStorage, warningsStorage;
1590
+ var init_generationContext = __esm({
1591
+ "src/utils/generationContext.ts"() {
1592
+ "use strict";
1593
+ generationDateStorage = new AsyncLocalStorage();
1594
+ baseDirStorage = new AsyncLocalStorage();
1595
+ warningsStorage = new AsyncLocalStorage();
1596
+ }
1597
+ });
1598
+
1282
1599
  // src/themes/json/parser.ts
1283
1600
  import { validateThemeJson } from "@json-to-office/shared-docx/validation/unified";
1284
1601
  var ThemeValidationError, ThemeParseError, ThemeParser;
@@ -4688,7 +5005,10 @@ async function resolveDocumentFonts(document, theme, fonts, warnings, forceMater
4688
5005
  }
4689
5006
 
4690
5007
  // src/core/generateFromIr.ts
4691
- import { toRasterizeFontFaces } from "@json-to-office/shared/fonts/node";
5008
+ import {
5009
+ toChartFontFaces,
5010
+ toRasterizeFontFaces
5011
+ } from "@json-to-office/shared/fonts/node";
4692
5012
 
4693
5013
  // src/core/prerasterizeVisuals.ts
4694
5014
  import {
@@ -5106,6 +5426,87 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
5106
5426
  return map;
5107
5427
  }
5108
5428
 
5429
+ // src/core/componentTransform.ts
5430
+ async function transformComponents(doc, replace) {
5431
+ return await walk(doc, replace);
5432
+ }
5433
+ async function walk(node, replace) {
5434
+ if (Array.isArray(node)) {
5435
+ return Promise.all(node.map((child) => walk(child, replace)));
5436
+ }
5437
+ if (!node || typeof node !== "object") return node;
5438
+ const obj = node;
5439
+ if (typeof obj.name === "string" && obj.props) {
5440
+ const replacement = await replace(obj);
5441
+ if (replacement) return replacement;
5442
+ }
5443
+ const next = { ...obj };
5444
+ if (Array.isArray(obj.children)) {
5445
+ next.children = await walk(obj.children, replace);
5446
+ }
5447
+ if (obj.props && typeof obj.props === "object") {
5448
+ const props = obj.props;
5449
+ let changed = false;
5450
+ const nextProps = { ...props };
5451
+ for (const key of ["header", "footer"]) {
5452
+ if (Array.isArray(props[key])) {
5453
+ nextProps[key] = await walk(props[key], replace);
5454
+ changed = true;
5455
+ }
5456
+ }
5457
+ if (Array.isArray(props.columns)) {
5458
+ nextProps.columns = await Promise.all(
5459
+ props.columns.map(
5460
+ (column) => walkColumn(column, replace)
5461
+ )
5462
+ );
5463
+ changed = true;
5464
+ }
5465
+ if (changed) next.props = nextProps;
5466
+ }
5467
+ return next;
5468
+ }
5469
+ async function walkColumn(column, replace) {
5470
+ if (!column || typeof column !== "object") return column;
5471
+ const source = column;
5472
+ const next = { ...source };
5473
+ const header = source.header;
5474
+ if (header && typeof header === "object" && "content" in header) {
5475
+ next.header = { ...header, content: await walk(header.content, replace) };
5476
+ }
5477
+ if (Array.isArray(source.cells)) {
5478
+ next.cells = await Promise.all(
5479
+ source.cells.map(async (cell) => {
5480
+ if (cell && typeof cell === "object" && "content" in cell) {
5481
+ const source2 = cell;
5482
+ return { ...source2, content: await walk(source2.content, replace) };
5483
+ }
5484
+ return cell;
5485
+ })
5486
+ );
5487
+ }
5488
+ return next;
5489
+ }
5490
+ function withNodeIdentity(original, replacement) {
5491
+ const out = { ...replacement };
5492
+ if (original.id !== void 0) out.id = original.id;
5493
+ if (original.enabled !== void 0) out.enabled = original.enabled;
5494
+ return out;
5495
+ }
5496
+ function containsComponent(root, name) {
5497
+ const seen = /* @__PURE__ */ new WeakSet();
5498
+ const visit = (node) => {
5499
+ if (!node || typeof node !== "object" || seen.has(node)) return false;
5500
+ seen.add(node);
5501
+ if (Array.isArray(node)) return node.some(visit);
5502
+ const obj = node;
5503
+ if (typeof obj.name === "string" && obj.enabled === false) return false;
5504
+ if (obj.name === name && obj.props !== void 0) return true;
5505
+ return Object.values(obj).some(visit);
5506
+ };
5507
+ return visit(root);
5508
+ }
5509
+
5109
5510
  // src/core/desugarExternals.ts
5110
5511
  import {
5111
5512
  clampVisualDpi as clampVisualDpi2,
@@ -5117,6 +5518,14 @@ import {
5117
5518
 
5118
5519
  // src/components/highcharts.ts
5119
5520
  init_colorUtils();
5521
+ init_layoutUtils();
5522
+ import {
5523
+ chartFamilyResolver,
5524
+ chartPointsPerPixel,
5525
+ POINTS_PER_PIXEL_96DPI,
5526
+ withChartFontFaceCss,
5527
+ withChartTypography
5528
+ } from "@json-to-office/shared";
5120
5529
  var DEFAULT_EXPORT_SERVER_URL = "http://localhost:7801";
5121
5530
  async function generateChart(config, servicesConfig) {
5122
5531
  if (!isNodeEnvironment()) {
@@ -5180,84 +5589,81 @@ function withThemeColors(config, theme) {
5180
5589
  options: { ...config.options, colors: palette }
5181
5590
  };
5182
5591
  }
5183
- async function renderChartToImageProps(props, theme, servicesConfig) {
5184
- const config = withThemeColors(props, theme);
5185
- const chart = await generateChart(config, servicesConfig);
5186
- const hasConfigDimensions = config.width !== void 0 || config.height !== void 0;
5187
- return {
5188
- base64: chart.base64DataUri,
5189
- width: hasConfigDimensions ? config.width : chart.width,
5190
- height: hasConfigDimensions ? config.height : chart.height,
5191
- alignment: "center"
5192
- };
5193
- }
5194
-
5195
- // src/core/componentTransform.ts
5196
- async function transformComponents(doc, replace) {
5197
- return await walk(doc, replace);
5198
- }
5199
- async function walk(node, replace) {
5200
- if (Array.isArray(node)) {
5201
- return Promise.all(node.map((child) => walk(child, replace)));
5202
- }
5203
- if (!node || typeof node !== "object") return node;
5204
- const obj = node;
5205
- if (typeof obj.name === "string" && obj.props) {
5206
- const replacement = await replace(obj);
5207
- if (replacement) return replacement;
5592
+ var POINTS_PER_TWIP = 1 / 20;
5593
+ var POINTS_PER_PIXEL = POINTS_PER_PIXEL_96DPI;
5594
+ function placedWidthPt(config, theme) {
5595
+ const { width, height } = config;
5596
+ const chart = config.options.chart;
5597
+ if (typeof width === "number") return width * POINTS_PER_PIXEL;
5598
+ if (typeof width === "string") {
5599
+ const page = getPageSetup(theme);
5600
+ const contentWidthPt = (page.size.width - page.margin.left - page.margin.right) * POINTS_PER_TWIP;
5601
+ return parseFloat(width) / 100 * contentWidthPt;
5208
5602
  }
5209
- const next = { ...obj };
5210
- if (Array.isArray(obj.children)) {
5211
- next.children = await walk(obj.children, replace);
5603
+ if (typeof height === "number" && chart.height > 0) {
5604
+ return height / chart.height * chart.width * POINTS_PER_PIXEL;
5212
5605
  }
5213
- if (obj.props && typeof obj.props === "object") {
5214
- const props = obj.props;
5215
- let changed = false;
5216
- const nextProps = { ...props };
5217
- for (const key of ["header", "footer"]) {
5218
- if (Array.isArray(props[key])) {
5219
- nextProps[key] = await walk(props[key], replace);
5220
- changed = true;
5221
- }
5222
- }
5223
- if (Array.isArray(props.columns)) {
5224
- nextProps.columns = await Promise.all(
5225
- props.columns.map(
5226
- (column) => walkColumn(column, replace)
5227
- )
5228
- );
5229
- changed = true;
5230
- }
5231
- if (changed) next.props = nextProps;
5232
- }
5233
- return next;
5606
+ return void 0;
5234
5607
  }
5235
- async function walkColumn(column, replace) {
5236
- if (!column || typeof column !== "object") return column;
5237
- const source = column;
5238
- const next = { ...source };
5239
- const header = source.header;
5240
- if (header && typeof header === "object" && "content" in header) {
5241
- next.header = { ...header, content: await walk(header.content, replace) };
5242
- }
5243
- if (Array.isArray(source.cells)) {
5244
- next.cells = await Promise.all(
5245
- source.cells.map(async (cell) => {
5246
- if (cell && typeof cell === "object" && "content" in cell) {
5247
- const source2 = cell;
5248
- return { ...source2, content: await walk(source2.content, replace) };
5249
- }
5250
- return cell;
5251
- })
5252
- );
5608
+ function toHex(token, theme) {
5609
+ return `#${resolveColor(token, theme)}`;
5610
+ }
5611
+ function themeChartTypography(theme) {
5612
+ const styles = theme.styles ?? {};
5613
+ const family = chartFamilyResolver(theme);
5614
+ const bodyPt = styles.normal?.size ?? theme.fonts.body.size ?? 11;
5615
+ const label = styles.chartLabel;
5616
+ const heading = styles.heading3;
5617
+ const labelPt = label?.size ?? Math.max(bodyPt - 1, 6);
5618
+ return {
5619
+ bodyFamily: family(theme.fonts.body.family),
5620
+ headingFamily: family(theme.fonts.heading.family),
5621
+ textColor: toHex("textPrimary", theme),
5622
+ mutedColor: toHex("textSecondary", theme),
5623
+ labelPt,
5624
+ labelWeight: label?.fontWeight ?? (label?.bold ? 700 : void 0),
5625
+ titlePt: heading?.size ?? bodyPt + 2,
5626
+ titleWeight: heading?.fontWeight ?? (heading?.bold === false ? void 0 : 700),
5627
+ sourcePt: styles.source?.size ?? Math.max(labelPt - 1, 6)
5628
+ };
5629
+ }
5630
+ function withThemeTypography(config, theme) {
5631
+ if (!theme?.fonts?.body?.family || !theme.fonts.heading?.family) {
5632
+ return config;
5253
5633
  }
5254
- return next;
5634
+ return {
5635
+ ...config,
5636
+ options: withChartTypography(
5637
+ config.options,
5638
+ themeChartTypography(theme),
5639
+ chartPointsPerPixel(
5640
+ config.options.chart.width,
5641
+ placedWidthPt(config, theme)
5642
+ )
5643
+ )
5644
+ };
5255
5645
  }
5256
- function withNodeIdentity(original, replacement) {
5257
- const out = { ...replacement };
5258
- if (original.id !== void 0) out.id = original.id;
5259
- if (original.enabled !== void 0) out.enabled = original.enabled;
5260
- return out;
5646
+ function withChartFontFaces(config, theme, faces) {
5647
+ if (!faces?.length || !theme?.fonts) return config;
5648
+ return withChartFontFaceCss(config, faces, [
5649
+ theme.fonts.body.family,
5650
+ theme.fonts.heading.family
5651
+ ]);
5652
+ }
5653
+ async function renderChartToImageProps(props, theme, servicesConfig, chartFonts) {
5654
+ const config = withChartFontFaces(
5655
+ withThemeTypography(withThemeColors(props, theme), theme),
5656
+ theme,
5657
+ chartFonts
5658
+ );
5659
+ const chart = await generateChart(config, servicesConfig);
5660
+ const hasConfigDimensions = config.width !== void 0 || config.height !== void 0;
5661
+ return {
5662
+ base64: chart.base64DataUri,
5663
+ width: hasConfigDimensions ? config.width : chart.width,
5664
+ height: hasConfigDimensions ? config.height : chart.height,
5665
+ alignment: "center"
5666
+ };
5261
5667
  }
5262
5668
 
5263
5669
  // src/core/desugarExternals.ts
@@ -5289,7 +5695,8 @@ async function desugarExternals(document, options) {
5289
5695
  props: await renderChartToImageProps(
5290
5696
  node.props,
5291
5697
  options.theme,
5292
- options.services?.highcharts
5698
+ options.services?.highcharts,
5699
+ options.chartFonts
5293
5700
  )
5294
5701
  });
5295
5702
  }
@@ -8047,6 +8454,641 @@ function emptyToUndefined(formatting) {
8047
8454
  return entries.length === 0 ? void 0 : Object.fromEntries(entries);
8048
8455
  }
8049
8456
 
8457
+ // src/blocks/index.ts
8458
+ import {
8459
+ COVER_BUDGET,
8460
+ KEY_TAKEAWAYS_BUDGET,
8461
+ RUNNING_HEAD_BUDGET,
8462
+ SECTION_OPENER_BUDGET
8463
+ } from "@json-to-office/shared-docx";
8464
+
8465
+ // src/blocks/cover.ts
8466
+ init_widthUtils();
8467
+
8468
+ // src/blocks/recipe.ts
8469
+ var MIN_RULE_PT = 0.25;
8470
+ var MAX_RULE_PT = 12;
8471
+ function clampRule(weightPt) {
8472
+ return Math.min(MAX_RULE_PT, Math.max(MIN_RULE_PT, weightPt));
8473
+ }
8474
+ var FALLBACK_EYEBROW_FONT = {
8475
+ size: 9,
8476
+ bold: true,
8477
+ color: "accent",
8478
+ case: "upper"
8479
+ };
8480
+ function hasStyle(theme, role) {
8481
+ const styles = theme.styles;
8482
+ return styles?.[role] !== void 0;
8483
+ }
8484
+ function roleProps(theme, role, fallbackFont, color) {
8485
+ if (hasStyle(theme, role)) {
8486
+ return {
8487
+ themeStyle: role,
8488
+ ...color !== void 0 && { font: { color } }
8489
+ };
8490
+ }
8491
+ return { font: { ...fallbackFont, ...color !== void 0 && { color } } };
8492
+ }
8493
+
8494
+ // src/blocks/cover.ts
8495
+ var DROP_SHARE = 0.3;
8496
+ var DROP_SHARE_WITH_LOGO = 0.22;
8497
+ var DEFAULT_LOGO_WIDTH = "25%";
8498
+ var FALLBACK = {
8499
+ ruleWeightPt: 3,
8500
+ ruleColor: "accent",
8501
+ padPt: 12,
8502
+ metaFont: { size: 9, color: "textSecondary" }
8503
+ };
8504
+ function compileCover(props, theme) {
8505
+ const recipe = theme.chrome?.cover;
8506
+ const ruleWeightPt = recipe?.rule?.weightPt ?? FALLBACK.ruleWeightPt;
8507
+ const padPt = recipe?.padPt ?? FALLBACK.padPt;
8508
+ const heightPt = getAvailableHeightTwips(theme) / 20;
8509
+ const dropPt = Math.round(
8510
+ heightPt * (props.logo ? DROP_SHARE_WITH_LOGO : DROP_SHARE)
8511
+ );
8512
+ const children = [];
8513
+ const sourceMap = {};
8514
+ const emit = (child, slots) => {
8515
+ const index = children.push(child) - 1;
8516
+ sourceMap[`/children/${index}`] = "";
8517
+ for (const [emitted, authored] of Object.entries(slots)) {
8518
+ sourceMap[`/children/${index}${emitted}`] = authored;
8519
+ }
8520
+ };
8521
+ if (props.logo) {
8522
+ const { width, height, ...source } = props.logo;
8523
+ emit(
8524
+ {
8525
+ name: "image",
8526
+ props: {
8527
+ ...source,
8528
+ width: width ?? DEFAULT_LOGO_WIDTH,
8529
+ ...height !== void 0 && { height },
8530
+ alignment: theme.chrome?.logoSlot?.alignment ?? "left",
8531
+ spacing: { before: 0, after: 0 }
8532
+ }
8533
+ },
8534
+ { "/props": "/props/logo" }
8535
+ );
8536
+ }
8537
+ const drawsRule = ruleWeightPt > 0;
8538
+ if (drawsRule) {
8539
+ emit(
8540
+ {
8541
+ name: "divider",
8542
+ props: {
8543
+ thickness: clampRule(ruleWeightPt),
8544
+ color: recipe?.rule?.color ?? FALLBACK.ruleColor,
8545
+ spacing: { before: dropPt, after: padPt }
8546
+ }
8547
+ },
8548
+ {}
8549
+ );
8550
+ }
8551
+ let dropPending = !drawsRule;
8552
+ const before = () => {
8553
+ if (!dropPending) return {};
8554
+ dropPending = false;
8555
+ return { before: dropPt };
8556
+ };
8557
+ if (props.client) {
8558
+ emit(
8559
+ {
8560
+ name: "paragraph",
8561
+ props: {
8562
+ text: props.client,
8563
+ keepNext: true,
8564
+ spacing: { ...before(), after: 4 },
8565
+ ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
8566
+ }
8567
+ },
8568
+ { "/props/text": "/props/client" }
8569
+ );
8570
+ }
8571
+ const titleRole = recipe?.type && hasStyle(theme, recipe.type) ? recipe.type : "title";
8572
+ emit(
8573
+ {
8574
+ name: "paragraph",
8575
+ props: {
8576
+ text: props.title,
8577
+ keepNext: true,
8578
+ ...dropPending && { spacing: before() },
8579
+ ...roleProps(theme, titleRole, { size: 26, bold: true }, recipe?.color)
8580
+ }
8581
+ },
8582
+ { "/props/text": "/props/title" }
8583
+ );
8584
+ if (props.subtitle) {
8585
+ emit(
8586
+ {
8587
+ name: "paragraph",
8588
+ props: {
8589
+ text: props.subtitle,
8590
+ keepNext: true,
8591
+ ...roleProps(theme, "subtitle", { size: 13, color: "textSecondary" })
8592
+ }
8593
+ },
8594
+ { "/props/text": "/props/subtitle" }
8595
+ );
8596
+ }
8597
+ const meta = [props.date, props.confidentiality].filter(
8598
+ (part) => typeof part === "string" && part !== ""
8599
+ );
8600
+ if (meta.length > 0) {
8601
+ emit(
8602
+ {
8603
+ name: "paragraph",
8604
+ props: {
8605
+ text: meta.join(" \xB7 "),
8606
+ spacing: { before: padPt, after: 0 },
8607
+ ...roleProps(theme, "label", FALLBACK.metaFont)
8608
+ }
8609
+ },
8610
+ {
8611
+ "/props/text": props.date ? "/props/date" : "/props/confidentiality"
8612
+ }
8613
+ );
8614
+ }
8615
+ return { children, sourceMap };
8616
+ }
8617
+
8618
+ // src/blocks/keyTakeaways.ts
8619
+ var KEY_TAKEAWAYS_DEFAULT_LABEL = "Key takeaways";
8620
+ var FALLBACK_RECIPE = {
8621
+ ruleWeightPt: 1.5,
8622
+ ruleColor: "accent",
8623
+ padPt: 6,
8624
+ type: "label"
8625
+ };
8626
+ function compileKeyTakeaways(props, theme) {
8627
+ const recipe = theme.chrome?.keyTakeaways;
8628
+ const labelRole = recipe?.type ?? FALLBACK_RECIPE.type;
8629
+ const labelStyle = hasStyle(theme, labelRole);
8630
+ const ruleWeightPt = clampRule(
8631
+ recipe?.rule?.weightPt ?? FALLBACK_RECIPE.ruleWeightPt
8632
+ );
8633
+ const ruleColor = recipe?.rule?.color ?? recipe?.color ?? FALLBACK_RECIPE.ruleColor;
8634
+ const padPt = recipe?.padPt ?? FALLBACK_RECIPE.padPt;
8635
+ const children = [
8636
+ {
8637
+ name: "divider",
8638
+ props: {
8639
+ thickness: ruleWeightPt,
8640
+ color: ruleColor,
8641
+ spacing: { before: 12, after: padPt }
8642
+ }
8643
+ },
8644
+ {
8645
+ name: "paragraph",
8646
+ props: {
8647
+ text: props.label ?? KEY_TAKEAWAYS_DEFAULT_LABEL,
8648
+ keepNext: true,
8649
+ spacing: { after: 4 },
8650
+ // The label role when the theme resolves one; a bold run in the
8651
+ // recipe's colour otherwise, so the label still reads as a label.
8652
+ ...labelStyle ? { themeStyle: labelRole } : {
8653
+ font: {
8654
+ bold: true,
8655
+ ...recipe?.color !== void 0 && { color: recipe.color }
8656
+ }
8657
+ }
8658
+ }
8659
+ },
8660
+ {
8661
+ name: "list",
8662
+ props: { items: [...props.items] }
8663
+ },
8664
+ {
8665
+ name: "divider",
8666
+ props: {
8667
+ thickness: 0.5,
8668
+ color: "borderPrimary",
8669
+ spacing: { before: padPt, after: 12 }
8670
+ }
8671
+ }
8672
+ ];
8673
+ return {
8674
+ children,
8675
+ sourceMap: {
8676
+ "/children/0": "",
8677
+ "/children/1": "",
8678
+ "/children/1/props/text": "/props/label",
8679
+ "/children/2": "",
8680
+ "/children/2/props/items": "/props/items",
8681
+ "/children/3": ""
8682
+ }
8683
+ };
8684
+ }
8685
+
8686
+ // src/blocks/runningHead.ts
8687
+ init_styles();
8688
+ init_widthUtils();
8689
+ var FALLBACK2 = {
8690
+ headFont: { size: 8, color: "textMuted", case: "upper" },
8691
+ footFont: { size: 8, color: "textMuted" },
8692
+ ruleWeightPt: 0.5,
8693
+ ruleColor: "border"
8694
+ };
8695
+ var PAGE_OF_TOTAL = "{PAGE} / {TOTAL_PAGES}";
8696
+ function sectionMeasureTwips(theme, page) {
8697
+ if (!page || page.size === void 0 && page.margins === void 0) {
8698
+ return getAvailableWidthTwips(theme);
8699
+ }
8700
+ const width = page.size ? getPageDimensions(page.size).width : getPageDimensions(theme.page.size).width;
8701
+ const margins = getDocumentMargins(theme);
8702
+ const left = page.margins?.left ?? margins.left ?? 1440;
8703
+ const right = page.margins?.right ?? margins.right ?? 1440;
8704
+ return Math.max(0, width - left - right);
8705
+ }
8706
+ function compileRunningHead(props, theme, scope) {
8707
+ const measure = sectionMeasureTwips(theme, scope.page);
8708
+ const head = theme.chrome?.runningHead;
8709
+ const foot = theme.chrome?.confidentialFooter;
8710
+ const slot = (name) => props[name] !== void 0 ? `${scope.block}/props/${name}` : void 0;
8711
+ const header = [];
8712
+ const headerMap = {};
8713
+ const title = props.title ?? scope.documentTitle ?? "";
8714
+ const tracker = scope.opener?.text ?? props.tracker ?? "";
8715
+ if (title !== "" || tracker !== "") {
8716
+ const both = title !== "" && tracker !== "";
8717
+ header.push({
8718
+ name: "paragraph",
8719
+ props: {
8720
+ text: both ? `${title} ${tracker}` : title || tracker,
8721
+ ...both ? { tabStops: [{ type: "right", position: measure }] } : { alignment: title !== "" ? "left" : head?.alignment ?? "right" },
8722
+ spacing: { before: 0, after: 0 },
8723
+ ...roleProps(
8724
+ theme,
8725
+ head?.type ?? "tracker",
8726
+ FALLBACK2.headFont,
8727
+ head?.color
8728
+ )
8729
+ }
8730
+ });
8731
+ headerMap["/0"] = scope.block;
8732
+ headerMap["/0/props/text"] = (tracker !== "" ? scope.opener?.slot ?? slot("tracker") : slot("title")) ?? scope.block;
8733
+ }
8734
+ const headRule = head?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8735
+ if (headRule > 0) {
8736
+ header.push({
8737
+ name: "divider",
8738
+ props: {
8739
+ thickness: clampRule(headRule),
8740
+ color: head?.rule?.color ?? FALLBACK2.ruleColor,
8741
+ spacing: { before: 2, after: 0 }
8742
+ }
8743
+ });
8744
+ headerMap[`/${header.length - 1}`] = scope.block;
8745
+ }
8746
+ const footer = [];
8747
+ const footerMap = {};
8748
+ const footRule = foot?.rule?.weightPt ?? FALLBACK2.ruleWeightPt;
8749
+ if (footRule > 0) {
8750
+ footer.push({
8751
+ name: "divider",
8752
+ props: {
8753
+ thickness: clampRule(footRule),
8754
+ color: foot?.rule?.color ?? FALLBACK2.ruleColor,
8755
+ spacing: { before: 0, after: 4 }
8756
+ }
8757
+ });
8758
+ footerMap["/0"] = scope.block;
8759
+ }
8760
+ const parts = [
8761
+ props.confidentiality ?? "",
8762
+ props.pageNumbers === false ? "" : PAGE_OF_TOTAL,
8763
+ props.date ?? ""
8764
+ ];
8765
+ const present = parts.filter((part) => part !== "");
8766
+ if (present.length > 0) {
8767
+ const single = present.length === 1;
8768
+ const alignment2 = present[0] === PAGE_OF_TOTAL ? "center" : foot?.alignment ?? "left";
8769
+ footer.push({
8770
+ name: "paragraph",
8771
+ props: {
8772
+ text: single ? present[0] : parts.join(" ").replace(/\t+$/, ""),
8773
+ ...single ? { alignment: alignment2 } : {
8774
+ tabStops: [
8775
+ { type: "center", position: Math.round(measure / 2) },
8776
+ { type: "right", position: measure }
8777
+ ]
8778
+ },
8779
+ spacing: { before: 0, after: 0 },
8780
+ ...roleProps(
8781
+ theme,
8782
+ foot?.type ?? "footer",
8783
+ FALLBACK2.footFont,
8784
+ foot?.color
8785
+ )
8786
+ }
8787
+ });
8788
+ const index = footer.length - 1;
8789
+ footerMap[`/${index}`] = scope.block;
8790
+ footerMap[`/${index}/props/text`] = slot("confidentiality") ?? slot("date") ?? scope.block;
8791
+ }
8792
+ return {
8793
+ header: { children: header, sourceMap: headerMap },
8794
+ footer: { children: footer, sourceMap: footerMap }
8795
+ };
8796
+ }
8797
+
8798
+ // src/blocks/sectionOpener.ts
8799
+ function compileSectionOpener(props, theme) {
8800
+ const children = [];
8801
+ const sourceMap = {};
8802
+ const pageBreak = props.pageBreak === true;
8803
+ if (props.number !== void 0) {
8804
+ children.push({
8805
+ name: "paragraph",
8806
+ props: {
8807
+ text: String(props.number),
8808
+ keepNext: true,
8809
+ spacing: { after: 2 },
8810
+ ...pageBreak && { pageBreak: true },
8811
+ ...roleProps(theme, "eyebrow", FALLBACK_EYEBROW_FONT)
8812
+ }
8813
+ });
8814
+ sourceMap["/children/0"] = "";
8815
+ sourceMap["/children/0/props/text"] = "/props/number";
8816
+ }
8817
+ const index = children.length;
8818
+ children.push({
8819
+ name: "heading",
8820
+ props: {
8821
+ text: props.title,
8822
+ level: 1,
8823
+ ...pageBreak && index === 0 && { pageBreak: true }
8824
+ }
8825
+ });
8826
+ sourceMap[`/children/${index}`] = "";
8827
+ sourceMap[`/children/${index}/props/text`] = "/props/title";
8828
+ return { children, sourceMap };
8829
+ }
8830
+ function sectionTracker(section2) {
8831
+ if (!Array.isArray(section2.children)) return void 0;
8832
+ for (const [index, child] of section2.children.entries()) {
8833
+ if (typeof child !== "object" || child === null || child.name !== "section-opener" || child.enabled === false) {
8834
+ continue;
8835
+ }
8836
+ const props = child.props;
8837
+ if (!props) continue;
8838
+ if (typeof props.tracker === "string" && props.tracker !== "") {
8839
+ return { text: props.tracker, slot: `/children/${index}/props/tracker` };
8840
+ }
8841
+ if (typeof props.title === "string" && props.title !== "") {
8842
+ return { text: props.title, slot: `/children/${index}/props/title` };
8843
+ }
8844
+ }
8845
+ return void 0;
8846
+ }
8847
+
8848
+ // src/blocks/index.ts
8849
+ var BLOCK_NAMES = [
8850
+ "key-takeaways",
8851
+ "cover",
8852
+ "section-opener",
8853
+ "running-head"
8854
+ ];
8855
+ function slotBudget(block2, props, pointer, slot, maxWords) {
8856
+ const value = props[slot];
8857
+ return typeof value === "string" ? [
8858
+ {
8859
+ block: block2,
8860
+ slot,
8861
+ path: `${pointer}/props/${slot}`,
8862
+ words: wordCount(value),
8863
+ maxWords
8864
+ }
8865
+ ] : [];
8866
+ }
8867
+ function budgetsOf(block2, budget) {
8868
+ return (props, pointer) => Object.entries(budget).flatMap(
8869
+ ([slot, { maxWords }]) => slotBudget(block2, props, pointer, slot, maxWords)
8870
+ );
8871
+ }
8872
+ var BLOCKS = {
8873
+ "key-takeaways": {
8874
+ kind: "flow",
8875
+ compile: (props, theme) => compileKeyTakeaways(props, theme),
8876
+ budgets: (props, pointer) => (Array.isArray(props.items) ? props.items : []).flatMap(
8877
+ (item, index) => typeof item === "string" ? [
8878
+ {
8879
+ block: "key-takeaways",
8880
+ slot: "items",
8881
+ path: `${pointer}/props/items/${index}`,
8882
+ words: wordCount(item),
8883
+ maxWords: KEY_TAKEAWAYS_BUDGET.items.maxWords
8884
+ }
8885
+ ] : []
8886
+ )
8887
+ },
8888
+ cover: {
8889
+ kind: "flow",
8890
+ compile: (props, theme) => compileCover(props, theme),
8891
+ budgets: budgetsOf("cover", COVER_BUDGET)
8892
+ },
8893
+ "section-opener": {
8894
+ kind: "flow",
8895
+ compile: (props, theme) => compileSectionOpener(props, theme),
8896
+ budgets: budgetsOf("section-opener", SECTION_OPENER_BUDGET)
8897
+ },
8898
+ "running-head": {
8899
+ kind: "chrome",
8900
+ budgets: budgetsOf("running-head", RUNNING_HEAD_BUDGET)
8901
+ }
8902
+ };
8903
+ function isBlockName(name) {
8904
+ return BLOCK_NAMES.includes(name);
8905
+ }
8906
+ function isChromeBlockName(name) {
8907
+ return isBlockName(name) && BLOCKS[name].kind === "chrome";
8908
+ }
8909
+ function isRecord(value) {
8910
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8911
+ }
8912
+ var SECTION_CHILD = /^\/children\/\d+\/children\/\d+$/;
8913
+ var SECTION = /^\/children\/(\d+)$/;
8914
+ function planChrome(document, theme) {
8915
+ const plans = /* @__PURE__ */ new Map();
8916
+ if (!isRecord(document) || document.name !== "docx" || !Array.isArray(document.children)) {
8917
+ return plans;
8918
+ }
8919
+ const metadata = isRecord(document.props) ? document.props.metadata : void 0;
8920
+ const documentTitle = isRecord(metadata) && typeof metadata.title === "string" ? metadata.title : void 0;
8921
+ let inForce;
8922
+ document.children.forEach((section2, index) => {
8923
+ if (!isRecord(section2) || section2.name !== "section" || section2.enabled === false) {
8924
+ return;
8925
+ }
8926
+ const sectionPointer = `/children/${index}`;
8927
+ const children = Array.isArray(section2.children) ? section2.children : [];
8928
+ children.forEach((child, childIndex) => {
8929
+ if (isRecord(child) && isChromeBlockName(child.name) && child.enabled !== false) {
8930
+ inForce = {
8931
+ block: `${sectionPointer}/children/${childIndex}`,
8932
+ props: isRecord(child.props) ? child.props : {}
8933
+ };
8934
+ }
8935
+ });
8936
+ if (!inForce) return;
8937
+ const opener = sectionTracker(section2);
8938
+ const sectionProps = isRecord(section2.props) ? section2.props : {};
8939
+ plans.set(
8940
+ index,
8941
+ compileRunningHead(inForce.props, theme, {
8942
+ block: inForce.block,
8943
+ documentTitle,
8944
+ ...opener && {
8945
+ opener: {
8946
+ text: opener.text,
8947
+ slot: `${sectionPointer}${opener.slot}`
8948
+ }
8949
+ },
8950
+ ...isRecord(sectionProps.page) && {
8951
+ page: sectionProps.page
8952
+ }
8953
+ })
8954
+ );
8955
+ });
8956
+ return plans;
8957
+ }
8958
+ function expandBlocks(document, theme) {
8959
+ const sourceMap = {};
8960
+ const blocks = [];
8961
+ const chrome = planChrome(document, theme);
8962
+ const walk2 = (node, pointer) => {
8963
+ if (Array.isArray(node)) {
8964
+ return node.map((child, index) => walk2(child, `${pointer}/${index}`));
8965
+ }
8966
+ if (!isRecord(node)) return node;
8967
+ if (typeof node.name === "string" && node.enabled !== false && isBlockName(node.name)) {
8968
+ const definition = BLOCKS[node.name];
8969
+ if (definition.kind === "chrome") {
8970
+ if (!SECTION_CHILD.test(pointer)) return node;
8971
+ blocks.push(pointer);
8972
+ return { ...node, children: [] };
8973
+ }
8974
+ if (isRecord(node.props)) {
8975
+ const compiled = definition.compile(node.props, theme);
8976
+ blocks.push(pointer);
8977
+ for (const [emitted, authored] of Object.entries(compiled.sourceMap)) {
8978
+ sourceMap[`${pointer}${emitted}`] = `${pointer}${authored}`;
8979
+ }
8980
+ return { ...node, children: compiled.children };
8981
+ }
8982
+ }
8983
+ const next = { ...node };
8984
+ if (isRecord(node.props)) {
8985
+ const props = { ...node.props };
8986
+ let changed = false;
8987
+ for (const key of ["header", "footer"]) {
8988
+ if (Array.isArray(props[key])) {
8989
+ props[key] = walk2(props[key], `${pointer}/props/${key}`);
8990
+ changed = true;
8991
+ }
8992
+ }
8993
+ if (Array.isArray(props.columns)) {
8994
+ props.columns = props.columns.map((column, index) => {
8995
+ if (!isRecord(column)) return column;
8996
+ const base = `${pointer}/props/columns/${index}`;
8997
+ const out = { ...column };
8998
+ if (isRecord(column.header) && "content" in column.header) {
8999
+ out.header = {
9000
+ ...column.header,
9001
+ content: walk2(column.header.content, `${base}/header/content`)
9002
+ };
9003
+ }
9004
+ if (Array.isArray(column.cells)) {
9005
+ out.cells = column.cells.map(
9006
+ (cell, cellIndex) => isRecord(cell) && "content" in cell ? {
9007
+ ...cell,
9008
+ content: walk2(
9009
+ cell.content,
9010
+ `${base}/cells/${cellIndex}/content`
9011
+ )
9012
+ } : cell
9013
+ );
9014
+ }
9015
+ return out;
9016
+ });
9017
+ changed = true;
9018
+ }
9019
+ if (changed) next.props = props;
9020
+ }
9021
+ if (Array.isArray(node.children)) {
9022
+ next.children = walk2(node.children, `${pointer}/children`);
9023
+ }
9024
+ const section2 = SECTION.exec(pointer);
9025
+ if (section2 && node.name === "section" && node.enabled !== false) {
9026
+ const plan = chrome.get(Number(section2[1]));
9027
+ if (plan) {
9028
+ const props = isRecord(next.props) ? { ...next.props } : {};
9029
+ let changed = false;
9030
+ for (const part of ["header", "footer"]) {
9031
+ const { children, sourceMap: partMap } = plan[part];
9032
+ if (props[part] !== void 0 || children.length === 0) continue;
9033
+ props[part] = children;
9034
+ changed = true;
9035
+ const base = `${pointer}/props/${part}`;
9036
+ sourceMap[base] = partMap["/0"] ?? pointer;
9037
+ for (const [emitted, authored] of Object.entries(partMap)) {
9038
+ sourceMap[`${base}${emitted}`] = authored;
9039
+ }
9040
+ }
9041
+ if (props.pageBreak === void 0) {
9042
+ props.pageBreak = true;
9043
+ changed = true;
9044
+ }
9045
+ if (changed) next.props = props;
9046
+ }
9047
+ }
9048
+ return next;
9049
+ };
9050
+ const expanded = walk2(document, "");
9051
+ return {
9052
+ document: blocks.length > 0 ? expanded : document,
9053
+ sourceMap,
9054
+ blocks
9055
+ };
9056
+ }
9057
+ function toAuthoredPointer(sourceMap, pointer) {
9058
+ let best;
9059
+ for (const emitted of Object.keys(sourceMap)) {
9060
+ if ((pointer === emitted || pointer.startsWith(`${emitted}/`)) && (best === void 0 || emitted.length > best.length)) {
9061
+ best = emitted;
9062
+ }
9063
+ }
9064
+ if (best === void 0) return pointer;
9065
+ return `${sourceMap[best]}${pointer.slice(best.length)}`;
9066
+ }
9067
+ function blockSlotBudgets(document, blocks) {
9068
+ return blocks.flatMap((pointer) => {
9069
+ const node = nodeAt(document, pointer);
9070
+ if (!isRecord(node) || !isRecord(node.props) || !isBlockName(node.name)) {
9071
+ return [];
9072
+ }
9073
+ return BLOCKS[node.name].budgets(node.props, pointer);
9074
+ });
9075
+ }
9076
+ function wordCount(text) {
9077
+ const trimmed = text.trim();
9078
+ return trimmed === "" ? 0 : trimmed.split(/\s+/).length;
9079
+ }
9080
+ function nodeAt(root, pointer) {
9081
+ if (pointer === "") return root;
9082
+ let current = root;
9083
+ for (const segment of pointer.slice(1).split("/")) {
9084
+ const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
9085
+ if (Array.isArray(current)) current = current[Number(key)];
9086
+ else if (isRecord(current)) current = current[key];
9087
+ else return void 0;
9088
+ }
9089
+ return current;
9090
+ }
9091
+
8050
9092
  // src/ir/compiler.ts
8051
9093
  init_types();
8052
9094
  init_units();
@@ -8344,33 +9386,47 @@ function compileChromeParagraph(component, scope) {
8344
9386
  const text = String(props.text ?? "");
8345
9387
  if (reportUnlowered(component, props, text, scope)) return [];
8346
9388
  const font = props.font ?? {};
8347
- const normal = getNormalStyle(ctx.theme);
9389
+ const named = chromeStyle(ctx.theme, props.themeStyle);
9390
+ const base = { ...getNormalStyle(ctx.theme), ...named };
9391
+ const styleId = named ? paragraphStyleId(props.themeStyle) ?? "Normal" : "Normal";
9392
+ if (styleId !== "Normal" && !ctx.styleIds.has(styleId)) {
9393
+ ctx.styleIds.add(styleId);
9394
+ }
8348
9395
  const chromeProps = {
8349
9396
  text,
8350
- ...props.alignment ? { alignment: props.alignment } : {},
9397
+ ...named ? { themeStyle: props.themeStyle } : {},
9398
+ ...props.alignment ? { alignment: props.alignment } : named?.alignment ? { alignment: named.alignment } : {},
8351
9399
  ...props.spacing ? { spacing: props.spacing } : {},
9400
+ ...props.indent ? { indent: props.indent } : {},
9401
+ ...props.tabStops ? { tabStops: props.tabStops } : {},
8352
9402
  ...props.boldColor ? { boldColor: props.boldColor } : {},
8353
9403
  font: {
8354
- family: font.family || resolveFontFamily(ctx.theme, normal.font) || getThemeFonts(ctx.theme).body.family,
8355
- size: font.size ?? normal.size ?? 11,
8356
- color: font.color || normal.color || "textPrimary",
8357
- bold: font.bold ?? false,
8358
- italic: font.italic ?? false,
9404
+ family: font.family || resolveFontFamily(ctx.theme, base.font) || getThemeFonts(ctx.theme).body.family,
9405
+ size: font.size ?? base.size ?? 11,
9406
+ color: font.color || base.color || "textPrimary",
9407
+ bold: font.bold ?? base.bold ?? false,
9408
+ italic: font.italic ?? base.italic ?? false,
8359
9409
  ...font.underline !== void 0 ? { underline: font.underline } : {},
8360
- ...font.fontWeight !== void 0 ? { fontWeight: font.fontWeight } : {},
8361
- ...font.case !== void 0 ? { case: font.case } : {},
9410
+ ...(font.fontWeight ?? base.fontWeight) !== void 0 ? { fontWeight: font.fontWeight ?? base.fontWeight } : {},
9411
+ ...(font.case ?? base.case) !== void 0 ? { case: font.case ?? base.case } : {},
9412
+ ...(font.characterSpacing ?? base.characterSpacing) !== void 0 ? { characterSpacing: font.characterSpacing ?? base.characterSpacing } : {},
8362
9413
  ...font.lineSpacing !== void 0 ? { lineSpacing: font.lineSpacing } : {}
8363
9414
  }
8364
9415
  };
8365
9416
  return [
8366
9417
  paragraphNode(scope, compileRuns(chromeProps, text, ctx, scope.path), {
8367
- styleId: "Normal",
9418
+ styleId,
8368
9419
  formatting: paragraphFormatting(chromeProps, ctx, {
8369
9420
  alwaysSpacing: true
8370
9421
  })
8371
9422
  })
8372
9423
  ];
8373
9424
  }
9425
+ function chromeStyle(theme, themeStyle) {
9426
+ if (typeof themeStyle !== "string" || !themeStyle) return void 0;
9427
+ const styles = getThemeStyles(theme);
9428
+ return styles[themeStyle] ?? styles[themeStyle.toLowerCase()];
9429
+ }
8374
9430
  function compileChromeImage(component, scope) {
8375
9431
  const { ctx, path: path4 } = scope;
8376
9432
  const props = component.props ?? {};
@@ -8458,6 +9514,10 @@ function compileComponent(component, scope) {
8458
9514
  case "table":
8459
9515
  return compileTable(component, scope);
8460
9516
  default:
9517
+ if (isBlockName(component.name)) {
9518
+ if (component.enabled === false) return [];
9519
+ return compileBlock(component, scope);
9520
+ }
8461
9521
  scope.ctx.unsupported.push({
8462
9522
  name: component.name,
8463
9523
  path: scope.path
@@ -8465,6 +9525,20 @@ function compileComponent(component, scope) {
8465
9525
  return [];
8466
9526
  }
8467
9527
  }
9528
+ function compileBlock(component, scope) {
9529
+ const children = component.children;
9530
+ if (!Array.isArray(children)) {
9531
+ scope.ctx.unsupported.push({ name: component.name, path: scope.path });
9532
+ return [];
9533
+ }
9534
+ return children.flatMap(
9535
+ (child, index) => compileComponent(child, {
9536
+ ...scope,
9537
+ path: `${scope.path}.children[${index}]`,
9538
+ id: `${scope.id}:b${index}`
9539
+ })
9540
+ );
9541
+ }
8468
9542
  var MARKDOWN_LIST_LEVELS = {
8469
9543
  unordered: [
8470
9544
  { level: 0, format: "bullet", text: "\u2022", alignment: "left" },
@@ -12071,11 +13145,13 @@ function walkActive(node, path4, page, visit) {
12071
13145
  );
12072
13146
  }
12073
13147
  function prepareDocxQualityDocument(document, options = {}) {
12074
- const context = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
13148
+ const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
12075
13149
  customThemes: options.customThemes,
12076
13150
  fonts: options.fonts,
12077
13151
  warnings: options.warnings
12078
13152
  });
13153
+ const expanded = expandBlocks(themed.document, themed.theme);
13154
+ const context = expanded.blocks.length > 0 ? { ...themed, document: expanded.document } : themed;
12079
13155
  const resolved = resolveDocumentTree(context.document, context.theme);
12080
13156
  const basePage = pageBox(resolved.theme, context.themeName);
12081
13157
  const typography = {
@@ -12085,13 +13161,28 @@ function prepareDocxQualityDocument(document, options = {}) {
12085
13161
  const facts = [];
12086
13162
  const provenance = {};
12087
13163
  let previousHeadingLevel;
12088
- const addFact = (fact) => {
13164
+ const authoredPath = (path4) => toAuthoredPointer(expanded.sourceMap, path4);
13165
+ const addFact = (raw) => {
13166
+ const fact = {
13167
+ ...raw,
13168
+ path: authoredPath(raw.path),
13169
+ ...raw.relatedPaths && {
13170
+ relatedPaths: raw.relatedPaths.map(authoredPath)
13171
+ }
13172
+ };
12089
13173
  facts.push(fact);
12090
13174
  provenance[fact.id] = {
12091
13175
  path: fact.path,
12092
13176
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
12093
13177
  };
12094
13178
  };
13179
+ for (const budget of blockSlotBudgets(context.document, expanded.blocks)) {
13180
+ addFact({
13181
+ id: `docx:block-slot:${budget.path}`,
13182
+ kind: "docx/block-slot",
13183
+ ...budget
13184
+ });
13185
+ }
12095
13186
  const paletteHexes = {};
12096
13187
  const visualColors = designColors2(
12097
13188
  resolved.theme.colors,
@@ -12234,7 +13325,16 @@ function prepareDocxQualityDocument(document, options = {}) {
12234
13325
  },
12235
13326
  facts,
12236
13327
  provenance,
12237
- renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2
13328
+ renderer: options.renderer ?? DEFAULT_DOCX_RENDERER_ID2,
13329
+ ...expanded.blocks.length > 0 && {
13330
+ metadata: {
13331
+ blocks: {
13332
+ sourceMap: expanded.sourceMap,
13333
+ blocks: expanded.blocks,
13334
+ document: context.document
13335
+ }
13336
+ }
13337
+ }
12238
13338
  };
12239
13339
  }
12240
13340
 
@@ -12274,19 +13374,22 @@ async function compileDocumentScoped(document, options, warnings) {
12274
13374
  });
12275
13375
  const { context } = prepared.model;
12276
13376
  const hasVisual = collectVisualProps(context.document).length > 0;
13377
+ const hasHighcharts = containsComponent(context.document, "highcharts");
12277
13378
  const resolvedFonts = await resolveDocumentFonts(
12278
13379
  context.document,
12279
13380
  context.theme,
12280
13381
  options.fonts,
12281
13382
  warnings,
12282
- hasVisual
13383
+ hasVisual || hasHighcharts
12283
13384
  );
12284
13385
  const visualFonts = hasVisual ? toRasterizeFontFaces(resolvedFonts, warnings) : [];
13386
+ const chartFonts = hasHighcharts ? toChartFontFaces(resolvedFonts) : [];
12285
13387
  const desugared = await desugarExternals(context.document, {
12286
13388
  theme: context.theme,
12287
13389
  ...options.services ? { services: options.services } : {},
12288
13390
  ...options.baseDir !== void 0 ? { baseDir: options.baseDir } : {},
12289
- ...visualFonts.length > 0 ? { visualFonts } : {}
13391
+ ...visualFonts.length > 0 ? { visualFonts } : {},
13392
+ ...chartFonts.length > 0 ? { chartFonts } : {}
12290
13393
  });
12291
13394
  const resolved = desugared === context.document ? prepared.model.document : resolveDocumentTree(desugared, context.theme);
12292
13395
  const structure = await processResolvedDocument(
@@ -13102,6 +14205,28 @@ var docxPlaceholderRule = {
13102
14205
  })
13103
14206
  )
13104
14207
  };
14208
+ var docxSlotBudgetRule = {
14209
+ id: "docx/slot-budget",
14210
+ code: QUALITY_CODES.SLOT_BUDGET,
14211
+ category: "composition",
14212
+ defaultSeverity: "warning",
14213
+ defaultCertainty: "deterministic",
14214
+ formats: ["docx"],
14215
+ evaluate: ({ facts }) => facts.filter(
14216
+ (fact) => fact.kind === "docx/block-slot"
14217
+ ).filter((fact) => fact.words > fact.maxWords).map((fact) => ({
14218
+ path: fact.path,
14219
+ message: `${fact.block} ${fact.slot} entry runs to ${fact.words} words; the slot holds ${fact.maxWords} \u2014 one claim, one sentence.`,
14220
+ suggestion: "Cut it to the conclusion. Move the evidence into the body it summarises.",
14221
+ evidence: {
14222
+ summary: "words in the slot against its budget",
14223
+ actual: fact.words,
14224
+ expected: fact.maxWords,
14225
+ unit: "words"
14226
+ },
14227
+ context: { block: fact.block, slot: fact.slot }
14228
+ }))
14229
+ };
13105
14230
  var DEFAULT_MAX_FONT_FAMILIES = 3;
13106
14231
  var docxFontCountRule = {
13107
14232
  id: "docx/font-count",
@@ -13267,6 +14392,7 @@ var DOCX_QUALITY_RULES = {
13267
14392
  docxSvgTextBoundsRule,
13268
14393
  docxLineBoxRule,
13269
14394
  docxPlaceholderRule,
14395
+ docxSlotBudgetRule,
13270
14396
  docxChartRule,
13271
14397
  docxTableDesignRule,
13272
14398
  docxFontCountRule,
@@ -14274,6 +15400,7 @@ function getCoreVersion() {
14274
15400
  return "Core v1.0.0";
14275
15401
  }
14276
15402
  export {
15403
+ BLOCK_NAMES,
14277
15404
  ComponentValidationError2 as ComponentValidationError,
14278
15405
  DocumentGenerator as CoreDocumentGenerator,
14279
15406
  DEFAULT_DOCX_RENDERER_ID,
@@ -14286,7 +15413,13 @@ export {
14286
15413
  UncompiledComponentError,
14287
15414
  UnknownPreservedComponentError,
14288
15415
  analyzeDocxQuality,
15416
+ blockSlotBudgets,
14289
15417
  cleanComponentProps,
15418
+ compileCover,
15419
+ compileKeyTakeaways,
15420
+ compileRunningHead,
15421
+ compileSectionOpener,
15422
+ consultingTheme,
14290
15423
  createComponent,
14291
15424
  createDocumentGenerator,
14292
15425
  createMinimalTheme,
@@ -14296,6 +15429,7 @@ export {
14296
15429
  docxRendererIds,
14297
15430
  docxRendererStatuses,
14298
15431
  examples,
15432
+ expandBlocks,
14299
15433
  exportPluginSchema,
14300
15434
  exportThemeToJson,
14301
15435
  flattenVisuals,
@@ -14315,6 +15449,7 @@ export {
14315
15449
  getExampleNames,
14316
15450
  getVisualPrepassStats,
14317
15451
  hasNodeBuiltins,
15452
+ isChromeBlockName,
14318
15453
  isColumnsComponent,
14319
15454
  isDocxRendererId,
14320
15455
  isHeadingComponent,
@@ -14338,7 +15473,10 @@ export {
14338
15473
  resetVisualPrepassStats,
14339
15474
  resolveDocxQualityProfile,
14340
15475
  runExample,
15476
+ sectionMeasureTwips,
15477
+ sectionTracker,
14341
15478
  themes,
15479
+ toAuthoredPointer,
14342
15480
  validateComponentProps,
14343
15481
  validateDocument,
14344
15482
  validateJsonSchema,