@json-to-office/core-docx 0.8.0 → 0.9.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
@@ -6828,7 +6940,8 @@ function createBuilderImpl(state) {
6828
6940
  customThemes: state.customThemes,
6829
6941
  debug: state.debug,
6830
6942
  enableCache: state.enableCache,
6831
- services: state.services
6943
+ services: state.services,
6944
+ fonts: state.fonts
6832
6945
  };
6833
6946
  return createBuilderImpl(
6834
6947
  newState
@@ -6841,25 +6954,37 @@ function createBuilderImpl(state) {
6841
6954
  internalDocument,
6842
6955
  state.components
6843
6956
  );
6844
- const themeName = internalDocument.props.theme || "minimal";
6845
- const docTheme = resolveDocumentTheme(themeName);
6957
+ const baseThemeName = internalDocument.props.theme || "minimal";
6958
+ const docTheme = resolveDocumentTheme(baseThemeName);
6846
6959
  const warnings = [];
6960
+ const mode = applyExportMode({
6961
+ doc: internalDocument,
6962
+ theme: docTheme,
6963
+ fonts: state.fonts
6964
+ });
6965
+ const modedTheme = mode.theme;
6966
+ const themeName = scopedThemeName(baseThemeName, state.fonts?.mode);
6967
+ for (const w of mode.warnings) {
6968
+ warnings.push({
6969
+ component: "fontRegistry",
6970
+ message: w.message,
6971
+ severity: "warning",
6972
+ context: { code: w.code }
6973
+ });
6974
+ }
6847
6975
  const processedComponents = await processDocumentComponents(
6848
- internalDocument.children || [],
6976
+ mode.doc.children || [],
6849
6977
  warnings,
6850
- docTheme
6978
+ modedTheme
6851
6979
  );
6852
6980
  const processedDocument = {
6853
- ...internalDocument,
6981
+ ...mode.doc,
6854
6982
  children: processedComponents
6855
6983
  };
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);
6984
+ const [modedDoc] = normalizeDocument(processedDocument);
6985
+ await resolveDocumentFonts(modedDoc, modedTheme, state.fonts, warnings);
6986
+ const structure = await processDocument(modedDoc, modedTheme, themeName);
6987
+ const layout = applyLayout(structure.sections, modedTheme, themeName);
6863
6988
  const generatedDocument = await renderDocument(structure, layout, {
6864
6989
  services: state.services
6865
6990
  });
@@ -6940,13 +7065,19 @@ function createBuilderImpl(state) {
6940
7065
  const themeName = internalDocument.props.theme || "minimal";
6941
7066
  const docTheme = resolveDocumentTheme(themeName);
6942
7067
  const warnings = [];
7068
+ const mode = applyExportMode({
7069
+ doc: internalDocument,
7070
+ theme: docTheme,
7071
+ fonts: state.fonts
7072
+ });
7073
+ const modedTheme = mode.theme;
6943
7074
  const processedComponents = await processDocumentComponents(
6944
- internalDocument.children || [],
7075
+ mode.doc.children || [],
6945
7076
  warnings,
6946
- docTheme
7077
+ modedTheme
6947
7078
  );
6948
7079
  const processedDocument = {
6949
- ...internalDocument,
7080
+ ...mode.doc,
6950
7081
  children: processedComponents
6951
7082
  };
6952
7083
  const [finalReportComponent] = normalizeDocument(processedDocument);
@@ -6978,7 +7109,8 @@ function createDocumentGenerator(options) {
6978
7109
  customThemes: options.customThemes,
6979
7110
  debug: options.debug ?? false,
6980
7111
  enableCache: options.enableCache ?? false,
6981
- services: options.services
7112
+ services: options.services,
7113
+ fonts: options.fonts
6982
7114
  };
6983
7115
  return createBuilderImpl(initialState);
6984
7116
  }