@json-to-office/core-docx 0.8.0 → 0.12.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.
@@ -1,5 +1,5 @@
1
1
  import { type ThemeConfig } from '../styles';
2
- import type { ServicesConfig } from '@json-to-office/shared';
2
+ import type { ServicesConfig, FontRuntimeOpts } from '@json-to-office/shared';
3
3
  import type { DocumentGeneratorBuilder } from './types';
4
4
  /**
5
5
  * Options for creating a document generator
@@ -15,6 +15,8 @@ export interface DocumentGeneratorOptions {
15
15
  debug?: boolean;
16
16
  /** External service configuration (e.g. Highcharts export server) */
17
17
  services?: ServicesConfig;
18
+ /** Font resolution options — extraEntries, Google Fonts config, onResolved hook. */
19
+ fonts?: FontRuntimeOpts;
18
20
  }
19
21
  /**
20
22
  * Create a document generator with chainable component registration.
@@ -1 +1 @@
1
- {"version":3,"file":"createDocumentGenerator.d.ts","sourceRoot":"","sources":["../../src/plugin/createDocumentGenerator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,WAAW,EAAwB,MAAM,WAAW,CAAC;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAEV,wBAAwB,EAKzB,MAAM,SAAS,CAAC;AAcjB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAkcD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAAC,SAAS,EAAE,CAAC,CAYvC"}
1
+ {"version":3,"file":"createDocumentGenerator.d.ts","sourceRoot":"","sources":["../../src/plugin/createDocumentGenerator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,WAAW,EAAwB,MAAM,WAAW,CAAC;AAEnE,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9E,OAAO,KAAK,EAEV,wBAAwB,EAKzB,MAAM,SAAS,CAAC;AAcjB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,oFAAoF;IACpF,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAseD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAAC,SAAS,EAAE,CAAC,CAavC"}
@@ -1813,6 +1813,67 @@ import { Packer as Packer2 } from "docx";
1813
1813
  // src/plugin/createDocumentGenerator.ts
1814
1814
  init_styles();
1815
1815
  import { Packer } from "docx";
1816
+ import { applyExportMode, scopedThemeName } from "@json-to-office/shared";
1817
+
1818
+ // src/core/fontResolution.ts
1819
+ import {
1820
+ collectFontNamesFromDocx,
1821
+ validateFontReferences,
1822
+ FontRegistry
1823
+ } from "@json-to-office/shared";
1824
+ import {
1825
+ loadFileFontSource,
1826
+ FontDiskCache,
1827
+ fetchVariableFontSource
1828
+ } from "@json-to-office/shared/fonts/node";
1829
+ async function resolveDocumentFonts(document, theme, fonts, warnings) {
1830
+ const emit = (code, message) => {
1831
+ if (warnings) {
1832
+ warnings.push({
1833
+ component: "fontRegistry",
1834
+ message,
1835
+ severity: "warning",
1836
+ context: { code }
1837
+ });
1838
+ } else {
1839
+ console.warn(`[json-to-docx] ${code}: ${message}`);
1840
+ }
1841
+ };
1842
+ const names = /* @__PURE__ */ new Set();
1843
+ for (const n of collectFontNamesFromDocx(document)) names.add(n);
1844
+ for (const n of collectFontNamesFromDocx(theme)) names.add(n);
1845
+ if (names.size === 0) return [];
1846
+ const validation = validateFontReferences({
1847
+ referencedNames: names,
1848
+ registeredEntries: fonts?.extraEntries
1849
+ });
1850
+ if (validation.warnings.length > 0) {
1851
+ if (fonts?.strict) {
1852
+ throw new Error(
1853
+ `Unresolved font references (strict mode):
1854
+ ` + validation.warnings.map((w) => ` - ${w.message}`).join("\n")
1855
+ );
1856
+ }
1857
+ for (const w of validation.warnings) {
1858
+ emit(w.code, w.message);
1859
+ }
1860
+ }
1861
+ if (!fonts?.onResolved) return [];
1862
+ const registry = new FontRegistry({
1863
+ opts: fonts,
1864
+ fileLoader: loadFileFontSource,
1865
+ variableLoader: fetchVariableFontSource,
1866
+ diskCache: fonts?.googleFonts?.cacheDir ? new FontDiskCache(fonts.googleFonts.cacheDir) : void 0
1867
+ });
1868
+ const resolved = await registry.resolveMany(names);
1869
+ for (const r of resolved) {
1870
+ for (const msg of r.warnings) {
1871
+ emit("FONT_UNRESOLVED", msg);
1872
+ }
1873
+ }
1874
+ fonts.onResolved(resolved);
1875
+ return resolved;
1876
+ }
1816
1877
 
1817
1878
  // src/plugin/version-resolver.ts
1818
1879
  import { resolveComponentVersion } from "@json-to-office/shared/plugin";
@@ -4098,9 +4159,22 @@ var globalBookmarkRegistry = new BookmarkRegistry();
4098
4159
 
4099
4160
  // src/core/content.ts
4100
4161
  init_styleHelpers();
4162
+ import { synthesizeFamilyName } from "@json-to-office/shared";
4101
4163
  init_colorUtils();
4102
4164
  init_styleHelpers();
4103
4165
  init_widthUtils();
4166
+ function applyFontWeightAlias(opts) {
4167
+ if (!opts.fontFamily) {
4168
+ return { font: void 0, bold: opts.bold, italics: opts.italic };
4169
+ }
4170
+ const weight = opts.fontWeight ?? (opts.bold === true ? 700 : void 0);
4171
+ const synth = synthesizeFamilyName(
4172
+ opts.fontFamily,
4173
+ weight,
4174
+ opts.italic === true
4175
+ );
4176
+ return { font: synth.family, bold: synth.bold, italics: synth.italic };
4177
+ }
4104
4178
  function createText(content, theme, themeName, options = {}) {
4105
4179
  const normalizedContent = normalizeUnicodeText(content);
4106
4180
  const style = options.style || "Normal";
@@ -4120,15 +4194,27 @@ function createText(content, theme, themeName, options = {}) {
4120
4194
  if (options.columnBreak) {
4121
4195
  children.push(new ColumnBreak());
4122
4196
  }
4197
+ const hasWeightRequest = options.fontWeight != null || options.bold === true;
4198
+ const effectiveFamily = options.fontFamily ?? (hasWeightRequest ? resolveFontFamily(theme, "body") : void 0);
4199
+ const weighted = applyFontWeightAlias({
4200
+ fontFamily: effectiveFamily,
4201
+ bold: options.bold,
4202
+ italic: options.italic,
4203
+ fontWeight: options.fontWeight
4204
+ });
4123
4205
  const baseTextStyle = {
4124
- ...options.fontFamily && { font: options.fontFamily },
4206
+ // Only emit `font` when it came from the caller or from an alias —
4207
+ // emitting the theme body family on every run would be a behavior change.
4208
+ ...(options.fontFamily || weighted.font && weighted.font !== effectiveFamily) && {
4209
+ font: weighted.font
4210
+ },
4125
4211
  ...options.fontSize && { size: options.fontSize * 2 },
4126
4212
  // Convert points to half-points
4127
4213
  ...options.fontColor && {
4128
4214
  color: resolveColor(options.fontColor, theme)
4129
4215
  },
4130
- ...options.bold !== void 0 && { bold: options.bold },
4131
- ...options.italic !== void 0 && { italics: options.italic },
4216
+ ...weighted.bold !== void 0 && { bold: weighted.bold },
4217
+ ...weighted.italics !== void 0 && { italics: weighted.italics },
4132
4218
  ...options.underline !== void 0 && {
4133
4219
  underline: options.underline ? { type: "single" } : void 0
4134
4220
  }
@@ -4248,13 +4334,25 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4248
4334
  children.push(new ColumnBreak());
4249
4335
  }
4250
4336
  const hasDecorators = /(\*\*\*|___|(\*\*|__)|(\*|_))/.test(normalizedText);
4337
+ const headingHasWeightRequest = options.fontWeight != null || options.bold === true;
4338
+ const headingEffectiveFamily = options.fontFamily ?? (headingHasWeightRequest ? resolveFontFamily(theme, "heading") : void 0);
4339
+ const headingWeighted = applyFontWeightAlias({
4340
+ fontFamily: headingEffectiveFamily,
4341
+ bold: options.bold,
4342
+ italic: options.italic,
4343
+ fontWeight: options.fontWeight
4344
+ });
4251
4345
  const baseTextStyle = {
4252
- ...options.fontFamily && { font: options.fontFamily },
4346
+ ...(options.fontFamily || headingWeighted.font && headingWeighted.font !== headingEffectiveFamily) && {
4347
+ font: headingWeighted.font
4348
+ },
4253
4349
  ...options.fontSize && { size: options.fontSize * 2 },
4254
4350
  // points to half-points
4255
4351
  ...options.fontColor && { color: resolveColor(options.fontColor, theme) },
4256
- ...options.bold !== void 0 && { bold: options.bold },
4257
- ...options.italic !== void 0 && { italics: options.italic },
4352
+ ...headingWeighted.bold !== void 0 && { bold: headingWeighted.bold },
4353
+ ...headingWeighted.italics !== void 0 && {
4354
+ italics: headingWeighted.italics
4355
+ },
4258
4356
  ...options.underline !== void 0 && {
4259
4357
  underline: options.underline ? { type: "single" } : void 0
4260
4358
  }
@@ -4766,12 +4864,18 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
4766
4864
  if (!cell) {
4767
4865
  return cellChildren;
4768
4866
  }
4867
+ const cellWeighted = applyFontWeightAlias({
4868
+ fontFamily: cellDefaults.font?.family || baseCellStyle.font,
4869
+ bold: cellDefaults.font?.bold ?? false,
4870
+ italic: cellDefaults.font?.italic ?? false,
4871
+ fontWeight: cellDefaults.font?.fontWeight
4872
+ });
4769
4873
  const mergedStyle = {
4770
- font: cellDefaults.font?.family || baseCellStyle.font,
4874
+ font: cellWeighted.font,
4771
4875
  size: cellDefaults.font?.size ? cellDefaults.font.size * 2 : baseCellStyle.size,
4772
4876
  // Convert to half-points
4773
- bold: cellDefaults.font?.bold ?? false,
4774
- italics: cellDefaults.font?.italic ?? false,
4877
+ bold: cellWeighted.bold ?? false,
4878
+ italics: cellWeighted.italics ?? false,
4775
4879
  underline: cellDefaults.font?.underline ? { type: "single" } : void 0,
4776
4880
  color: cellDefaults.color || baseCellStyle.color
4777
4881
  };
@@ -4779,15 +4883,21 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
4779
4883
  if (isParagraphComponent(cell)) {
4780
4884
  const textComp = cell;
4781
4885
  const paragraphFont = textComp.props.font;
4886
+ const paraWeighted = applyFontWeightAlias({
4887
+ fontFamily: paragraphFont?.family ?? mergedStyle.font,
4888
+ bold: paragraphFont?.bold,
4889
+ italic: paragraphFont?.italic,
4890
+ fontWeight: paragraphFont?.fontWeight
4891
+ });
4782
4892
  const paragraphStyle = {
4783
4893
  ...mergedStyle,
4784
- ...paragraphFont?.family && { font: paragraphFont.family },
4894
+ ...paraWeighted.font && { font: paraWeighted.font },
4785
4895
  ...paragraphFont?.size && { size: paragraphFont.size * 2 },
4786
- ...paragraphFont?.bold !== void 0 && {
4787
- bold: paragraphFont.bold
4896
+ ...paraWeighted.bold !== void 0 && {
4897
+ bold: paraWeighted.bold
4788
4898
  },
4789
- ...paragraphFont?.italic !== void 0 && {
4790
- italics: paragraphFont.italic
4899
+ ...paraWeighted.italics !== void 0 && {
4900
+ italics: paraWeighted.italics
4791
4901
  },
4792
4902
  ...paragraphFont?.underline !== void 0 && {
4793
4903
  underline: paragraphFont.underline ? { type: "single" } : void 0
@@ -5235,6 +5345,7 @@ function renderHeadingComponent(component, theme, themeName) {
5235
5345
  fontSize: config.font?.size,
5236
5346
  fontColor: config.font?.color,
5237
5347
  bold: config.font?.bold,
5348
+ fontWeight: config.font?.fontWeight,
5238
5349
  italic: config.font?.italic,
5239
5350
  underline: config.font?.underline,
5240
5351
  // Pagination control
@@ -5350,6 +5461,7 @@ function renderParagraphComponent(component, theme, themeName) {
5350
5461
  fontSize: resolvedConfig.font?.size,
5351
5462
  fontColor: resolvedConfig.font?.color,
5352
5463
  bold: resolvedConfig.font?.bold,
5464
+ fontWeight: resolvedConfig.font?.fontWeight,
5353
5465
  italic: resolvedConfig.font?.italic,
5354
5466
  underline: resolvedConfig.font?.underline,
5355
5467
  // Pass outline level for TOC support
@@ -6148,9 +6260,10 @@ async function generateChart(config, servicesConfig) {
6148
6260
  b64: true,
6149
6261
  scale: config.scale
6150
6262
  };
6263
+ const resolvedHeaders = typeof servicesConfig?.headers === "function" ? await servicesConfig.headers(requestBody) : servicesConfig?.headers;
6151
6264
  const headers = {
6152
6265
  "Content-Type": "application/json",
6153
- ...servicesConfig?.headers
6266
+ ...resolvedHeaders
6154
6267
  };
6155
6268
  const response = await fetch(`${serverUrl}/export`, {
6156
6269
  method: "POST",
@@ -6828,7 +6941,8 @@ function createBuilderImpl(state) {
6828
6941
  customThemes: state.customThemes,
6829
6942
  debug: state.debug,
6830
6943
  enableCache: state.enableCache,
6831
- services: state.services
6944
+ services: state.services,
6945
+ fonts: state.fonts
6832
6946
  };
6833
6947
  return createBuilderImpl(
6834
6948
  newState
@@ -6841,25 +6955,37 @@ function createBuilderImpl(state) {
6841
6955
  internalDocument,
6842
6956
  state.components
6843
6957
  );
6844
- const themeName = internalDocument.props.theme || "minimal";
6845
- const docTheme = resolveDocumentTheme(themeName);
6958
+ const baseThemeName = internalDocument.props.theme || "minimal";
6959
+ const docTheme = resolveDocumentTheme(baseThemeName);
6846
6960
  const warnings = [];
6961
+ const mode = applyExportMode({
6962
+ doc: internalDocument,
6963
+ theme: docTheme,
6964
+ fonts: state.fonts
6965
+ });
6966
+ const modedTheme = mode.theme;
6967
+ const themeName = scopedThemeName(baseThemeName, state.fonts?.mode);
6968
+ for (const w of mode.warnings) {
6969
+ warnings.push({
6970
+ component: "fontRegistry",
6971
+ message: w.message,
6972
+ severity: "warning",
6973
+ context: { code: w.code }
6974
+ });
6975
+ }
6847
6976
  const processedComponents = await processDocumentComponents(
6848
- internalDocument.children || [],
6977
+ mode.doc.children || [],
6849
6978
  warnings,
6850
- docTheme
6979
+ modedTheme
6851
6980
  );
6852
6981
  const processedDocument = {
6853
- ...internalDocument,
6982
+ ...mode.doc,
6854
6983
  children: processedComponents
6855
6984
  };
6856
- const [finalReportComponent] = normalizeDocument(processedDocument);
6857
- const structure = await processDocument(
6858
- finalReportComponent,
6859
- docTheme,
6860
- themeName
6861
- );
6862
- const layout = applyLayout(structure.sections, docTheme, themeName);
6985
+ const [modedDoc] = normalizeDocument(processedDocument);
6986
+ await resolveDocumentFonts(modedDoc, modedTheme, state.fonts, warnings);
6987
+ const structure = await processDocument(modedDoc, modedTheme, themeName);
6988
+ const layout = applyLayout(structure.sections, modedTheme, themeName);
6863
6989
  const generatedDocument = await renderDocument(structure, layout, {
6864
6990
  services: state.services
6865
6991
  });
@@ -6940,13 +7066,19 @@ function createBuilderImpl(state) {
6940
7066
  const themeName = internalDocument.props.theme || "minimal";
6941
7067
  const docTheme = resolveDocumentTheme(themeName);
6942
7068
  const warnings = [];
7069
+ const mode = applyExportMode({
7070
+ doc: internalDocument,
7071
+ theme: docTheme,
7072
+ fonts: state.fonts
7073
+ });
7074
+ const modedTheme = mode.theme;
6943
7075
  const processedComponents = await processDocumentComponents(
6944
- internalDocument.children || [],
7076
+ mode.doc.children || [],
6945
7077
  warnings,
6946
- docTheme
7078
+ modedTheme
6947
7079
  );
6948
7080
  const processedDocument = {
6949
- ...internalDocument,
7081
+ ...mode.doc,
6950
7082
  children: processedComponents
6951
7083
  };
6952
7084
  const [finalReportComponent] = normalizeDocument(processedDocument);
@@ -6978,7 +7110,8 @@ function createDocumentGenerator(options) {
6978
7110
  customThemes: options.customThemes,
6979
7111
  debug: options.debug ?? false,
6980
7112
  enableCache: options.enableCache ?? false,
6981
- services: options.services
7113
+ services: options.services,
7114
+ fonts: options.fonts
6982
7115
  };
6983
7116
  return createBuilderImpl(initialState);
6984
7117
  }