@json-to-office/core-docx 0.7.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.
package/dist/index.js CHANGED
@@ -1953,24 +1953,175 @@ var formatDate = (date, formatString = "MMMM d, yyyy") => {
1953
1953
  return format(date, formatString);
1954
1954
  };
1955
1955
 
1956
+ // src/styles/utils/componentDefaults.ts
1957
+ import { mergeWithDefaults } from "@json-to-office/shared";
1958
+ import { mergeWithDefaults as mergeWithDefaults2 } from "@json-to-office/shared";
1959
+ function getComponentDefaults(theme) {
1960
+ return theme.componentDefaults || {};
1961
+ }
1962
+ function getHeadingDefaults(theme) {
1963
+ const defaults = getComponentDefaults(theme);
1964
+ return defaults?.heading || {};
1965
+ }
1966
+ function getHeadingDefaultsForLevel(theme, level) {
1967
+ const defaults = {};
1968
+ if (theme.styles) {
1969
+ const styleKey = `heading${level}`;
1970
+ const headingStyle = theme.styles[styleKey];
1971
+ if (headingStyle?.alignment) {
1972
+ defaults.alignment = headingStyle.alignment;
1973
+ }
1974
+ }
1975
+ return defaults;
1976
+ }
1977
+ function getTextDefaults(theme) {
1978
+ const defaults = getComponentDefaults(theme);
1979
+ return defaults?.paragraph || {};
1980
+ }
1981
+ function getImageDefaults(theme) {
1982
+ const defaults = getComponentDefaults(theme);
1983
+ return defaults?.image || {};
1984
+ }
1985
+ function getStatisticDefaults(theme) {
1986
+ const defaults = getComponentDefaults(theme);
1987
+ return defaults?.statistic || {};
1988
+ }
1989
+ function getTableDefaults(theme) {
1990
+ const defaults = getComponentDefaults(theme);
1991
+ return defaults?.table || {};
1992
+ }
1993
+ function getSectionDefaults(theme) {
1994
+ const defaults = getComponentDefaults(theme);
1995
+ return defaults?.section || {};
1996
+ }
1997
+ function getColumnsDefaults(theme) {
1998
+ const defaults = getComponentDefaults(theme);
1999
+ return defaults?.columns || {};
2000
+ }
2001
+ function getListDefaults(theme) {
2002
+ const defaults = getComponentDefaults(theme);
2003
+ return defaults?.list || {};
2004
+ }
2005
+ function resolveHeadingProps(props, theme) {
2006
+ const defaults = getHeadingDefaults(theme);
2007
+ return mergeWithDefaults(props, defaults);
2008
+ }
2009
+ function resolveParagraphProps(props, theme) {
2010
+ const defaults = getTextDefaults(theme);
2011
+ return mergeWithDefaults(props, defaults);
2012
+ }
2013
+ function resolveImageProps(props, theme) {
2014
+ const defaults = getImageDefaults(theme);
2015
+ return mergeWithDefaults(props, defaults);
2016
+ }
2017
+ function resolveStatisticProps(props, theme) {
2018
+ const defaults = getStatisticDefaults(theme);
2019
+ return mergeWithDefaults(props, defaults);
2020
+ }
2021
+ function resolveTableProps(props, theme) {
2022
+ const defaults = getTableDefaults(theme);
2023
+ return mergeWithDefaults(props, defaults);
2024
+ }
2025
+ function resolveSectionProps(props, theme) {
2026
+ const defaults = getSectionDefaults(theme);
2027
+ return mergeWithDefaults(props, defaults);
2028
+ }
2029
+ function resolveColumnsProps(props, theme) {
2030
+ const defaults = getColumnsDefaults(theme);
2031
+ return mergeWithDefaults(props, defaults);
2032
+ }
2033
+ function resolveListProps(props, theme) {
2034
+ const defaults = getListDefaults(theme);
2035
+ return mergeWithDefaults(props, defaults);
2036
+ }
2037
+ function resolveHighchartsProps(props, _theme) {
2038
+ return props;
2039
+ }
2040
+ function getCustomComponentDefaults(theme, componentName) {
2041
+ const defaults = getComponentDefaults(theme);
2042
+ return defaults?.[componentName] || {};
2043
+ }
2044
+ function resolveCustomComponentProps(props, theme, componentName) {
2045
+ const defaults = getCustomComponentDefaults(theme, componentName);
2046
+ return mergeWithDefaults(props, defaults);
2047
+ }
2048
+
2049
+ // src/styles/utils/resolveComponentTree.ts
2050
+ function resolveHeadingWithLevelDefaults(props, theme) {
2051
+ const resolved = resolveHeadingProps(props, theme);
2052
+ const level = resolved.level || 1;
2053
+ const levelDefaults = getHeadingDefaultsForLevel(theme, level);
2054
+ return {
2055
+ ...resolved,
2056
+ // Only apply level-specific defaults if no explicit alignment in original props
2057
+ ...props.alignment ? {} : levelDefaults
2058
+ };
2059
+ }
2060
+ var RESOLVER_MAP = {
2061
+ heading: resolveHeadingWithLevelDefaults,
2062
+ paragraph: resolveParagraphProps,
2063
+ image: resolveImageProps,
2064
+ statistic: resolveStatisticProps,
2065
+ table: resolveTableProps,
2066
+ section: resolveSectionProps,
2067
+ columns: resolveColumnsProps,
2068
+ list: resolveListProps,
2069
+ highcharts: resolveHighchartsProps
2070
+ };
2071
+ function resolveComponentDefaults(component, theme) {
2072
+ if (!component.props) return component;
2073
+ const resolver = RESOLVER_MAP[component.name];
2074
+ const resolvedProps = resolver ? resolver(component.props, theme) : resolveCustomComponentProps(
2075
+ component.props,
2076
+ theme,
2077
+ component.name
2078
+ );
2079
+ return { ...component, props: resolvedProps };
2080
+ }
2081
+ function resolveComponentTree(components, theme) {
2082
+ return components.map((component) => {
2083
+ const resolved = resolveComponentDefaults(component, theme);
2084
+ const children = resolved.children;
2085
+ if (children && children.length > 0) {
2086
+ return {
2087
+ ...resolved,
2088
+ children: resolveComponentTree(children, theme)
2089
+ };
2090
+ }
2091
+ return resolved;
2092
+ });
2093
+ }
2094
+
1956
2095
  // src/core/structure.ts
1957
2096
  async function processDocument(document, theme, themeName) {
1958
2097
  const metadata = createDocumentMetadata(document.props);
2098
+ const docDefaults = document.props.componentDefaults;
2099
+ const effectiveTheme = docDefaults ? {
2100
+ ...theme,
2101
+ componentDefaults: mergeWithDefaults2(
2102
+ docDefaults,
2103
+ theme.componentDefaults || {}
2104
+ )
2105
+ } : theme;
1959
2106
  const context = createRenderContext(
1960
2107
  {
1961
2108
  metadata,
1962
2109
  sections: [],
1963
- theme,
2110
+ theme: effectiveTheme,
1964
2111
  themeName
1965
2112
  },
1966
- theme,
2113
+ effectiveTheme,
1967
2114
  themeName
1968
2115
  );
1969
- const sections = await extractSections(document.children || [], context);
2116
+ const resolvedChildren = resolveComponentTree(
2117
+ document.children || [],
2118
+ effectiveTheme
2119
+ );
2120
+ const sections = await extractSections(resolvedChildren, context);
1970
2121
  return {
1971
2122
  metadata,
1972
2123
  sections,
1973
- theme,
2124
+ theme: effectiveTheme,
1974
2125
  themeName
1975
2126
  };
1976
2127
  }
@@ -2000,19 +2151,24 @@ async function extractSections(components, context) {
2000
2151
  Math.max(component.props.level || 1, 1),
2001
2152
  6
2002
2153
  );
2003
- sectionComponents.unshift({
2004
- name: "heading",
2005
- props: {
2006
- text: component.props.title,
2007
- level: headingLevel,
2008
- pageBreak: shouldPageBreak,
2009
- // Apply zero-spacing to prevent unwanted initial line
2010
- spacing: {
2011
- before: 0,
2012
- after: 0
2013
- }
2014
- }
2015
- });
2154
+ sectionComponents.unshift(
2155
+ resolveComponentDefaults(
2156
+ {
2157
+ name: "heading",
2158
+ props: {
2159
+ text: component.props.title,
2160
+ level: headingLevel,
2161
+ pageBreak: shouldPageBreak,
2162
+ // Apply zero-spacing to prevent unwanted initial line
2163
+ spacing: {
2164
+ before: 0,
2165
+ after: 0
2166
+ }
2167
+ }
2168
+ },
2169
+ context.fullTheme
2170
+ )
2171
+ );
2016
2172
  }
2017
2173
  sections.push({
2018
2174
  title: component.props?.title,
@@ -2052,18 +2208,23 @@ async function flattenComponents(components, context) {
2052
2208
  Math.max(component.props.level || 1, 1),
2053
2209
  6
2054
2210
  );
2055
- flattened.push({
2056
- name: "heading",
2057
- props: {
2058
- text: component.props.title,
2059
- level: headingLevel,
2060
- // Apply zero-spacing to prevent unwanted initial line
2061
- spacing: {
2062
- before: 0,
2063
- after: 0
2064
- }
2065
- }
2066
- });
2211
+ flattened.push(
2212
+ resolveComponentDefaults(
2213
+ {
2214
+ name: "heading",
2215
+ props: {
2216
+ text: component.props.title,
2217
+ level: headingLevel,
2218
+ // Apply zero-spacing to prevent unwanted initial line
2219
+ spacing: {
2220
+ before: 0,
2221
+ after: 0
2222
+ }
2223
+ }
2224
+ },
2225
+ context.fullTheme
2226
+ )
2227
+ );
2067
2228
  }
2068
2229
  flattened.push(...await flattenComponents(component.children, context));
2069
2230
  } else {
@@ -3853,93 +4014,6 @@ function getComponentCacheStats() {
3853
4014
  return componentCache.getStats();
3854
4015
  }
3855
4016
 
3856
- // src/styles/utils/componentDefaults.ts
3857
- function getComponentDefaults(theme) {
3858
- return theme.componentDefaults || {};
3859
- }
3860
- function getHeadingDefaults(theme) {
3861
- const defaults = getComponentDefaults(theme);
3862
- return defaults?.heading || {};
3863
- }
3864
- function getHeadingDefaultsForLevel(theme, level) {
3865
- const defaults = {};
3866
- if (theme.styles) {
3867
- const styleKey = `heading${level}`;
3868
- const headingStyle = theme.styles[styleKey];
3869
- if (headingStyle?.alignment) {
3870
- defaults.alignment = headingStyle.alignment;
3871
- }
3872
- }
3873
- return defaults;
3874
- }
3875
- function getTextDefaults(theme) {
3876
- const defaults = getComponentDefaults(theme);
3877
- return defaults?.paragraph || {};
3878
- }
3879
- function getImageDefaults(theme) {
3880
- const defaults = getComponentDefaults(theme);
3881
- return defaults?.image || {};
3882
- }
3883
- function getStatisticDefaults(theme) {
3884
- const defaults = getComponentDefaults(theme);
3885
- return defaults?.statistic || {};
3886
- }
3887
- function getColumnsDefaults(theme) {
3888
- const defaults = getComponentDefaults(theme);
3889
- return defaults?.columns || {};
3890
- }
3891
- function getListDefaults(theme) {
3892
- const defaults = getComponentDefaults(theme);
3893
- return defaults?.list || {};
3894
- }
3895
- function deepMerge(target, source) {
3896
- const output = { ...target };
3897
- if (isObject(target) && isObject(source)) {
3898
- Object.keys(source).forEach((key) => {
3899
- if (isObject(source[key])) {
3900
- if (!(key in target)) {
3901
- output[key] = source[key];
3902
- } else {
3903
- output[key] = deepMerge(target[key], source[key]);
3904
- }
3905
- } else {
3906
- output[key] = source[key];
3907
- }
3908
- });
3909
- }
3910
- return output;
3911
- }
3912
- function isObject(item) {
3913
- return item !== null && typeof item === "object" && !Array.isArray(item);
3914
- }
3915
- function mergeWithDefaults(userConfig, themeDefaults) {
3916
- return deepMerge(themeDefaults, userConfig);
3917
- }
3918
- function resolveHeadingProps(props, theme) {
3919
- const defaults = getHeadingDefaults(theme);
3920
- return mergeWithDefaults(props, defaults);
3921
- }
3922
- function resolveParagraphProps(props, theme) {
3923
- const defaults = getTextDefaults(theme);
3924
- return mergeWithDefaults(props, defaults);
3925
- }
3926
- function resolveImageProps(props, theme) {
3927
- const defaults = getImageDefaults(theme);
3928
- return mergeWithDefaults(props, defaults);
3929
- }
3930
- function resolveStatisticProps(props, theme) {
3931
- const defaults = getStatisticDefaults(theme);
3932
- return mergeWithDefaults(props, defaults);
3933
- }
3934
- function resolveColumnsProps(props, theme) {
3935
- const defaults = getColumnsDefaults(theme);
3936
- return mergeWithDefaults(props, defaults);
3937
- }
3938
- function resolveListProps(props, theme) {
3939
- const defaults = getListDefaults(theme);
3940
- return mergeWithDefaults(props, defaults);
3941
- }
3942
-
3943
4017
  // src/core/content.ts
3944
4018
  import {
3945
4019
  Paragraph,
@@ -4033,9 +4107,22 @@ var globalBookmarkRegistry = new BookmarkRegistry();
4033
4107
 
4034
4108
  // src/core/content.ts
4035
4109
  init_styleHelpers();
4110
+ import { synthesizeFamilyName } from "@json-to-office/shared";
4036
4111
  init_colorUtils();
4037
4112
  init_styleHelpers();
4038
4113
  init_widthUtils();
4114
+ function applyFontWeightAlias(opts) {
4115
+ if (!opts.fontFamily) {
4116
+ return { font: void 0, bold: opts.bold, italics: opts.italic };
4117
+ }
4118
+ const weight = opts.fontWeight ?? (opts.bold === true ? 700 : void 0);
4119
+ const synth = synthesizeFamilyName(
4120
+ opts.fontFamily,
4121
+ weight,
4122
+ opts.italic === true
4123
+ );
4124
+ return { font: synth.family, bold: synth.bold, italics: synth.italic };
4125
+ }
4039
4126
  function createText(content, theme, themeName, options = {}) {
4040
4127
  const normalizedContent = normalizeUnicodeText(content);
4041
4128
  const style = options.style || "Normal";
@@ -4055,15 +4142,27 @@ function createText(content, theme, themeName, options = {}) {
4055
4142
  if (options.columnBreak) {
4056
4143
  children.push(new ColumnBreak());
4057
4144
  }
4145
+ const hasWeightRequest = options.fontWeight != null || options.bold === true;
4146
+ const effectiveFamily = options.fontFamily ?? (hasWeightRequest ? resolveFontFamily(theme, "body") : void 0);
4147
+ const weighted = applyFontWeightAlias({
4148
+ fontFamily: effectiveFamily,
4149
+ bold: options.bold,
4150
+ italic: options.italic,
4151
+ fontWeight: options.fontWeight
4152
+ });
4058
4153
  const baseTextStyle = {
4059
- ...options.fontFamily && { font: options.fontFamily },
4154
+ // Only emit `font` when it came from the caller or from an alias —
4155
+ // emitting the theme body family on every run would be a behavior change.
4156
+ ...(options.fontFamily || weighted.font && weighted.font !== effectiveFamily) && {
4157
+ font: weighted.font
4158
+ },
4060
4159
  ...options.fontSize && { size: options.fontSize * 2 },
4061
4160
  // Convert points to half-points
4062
4161
  ...options.fontColor && {
4063
4162
  color: resolveColor(options.fontColor, theme)
4064
4163
  },
4065
- ...options.bold !== void 0 && { bold: options.bold },
4066
- ...options.italic !== void 0 && { italics: options.italic },
4164
+ ...weighted.bold !== void 0 && { bold: weighted.bold },
4165
+ ...weighted.italics !== void 0 && { italics: weighted.italics },
4067
4166
  ...options.underline !== void 0 && {
4068
4167
  underline: options.underline ? { type: "single" } : void 0
4069
4168
  }
@@ -4183,13 +4282,25 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4183
4282
  children.push(new ColumnBreak());
4184
4283
  }
4185
4284
  const hasDecorators = /(\*\*\*|___|(\*\*|__)|(\*|_))/.test(normalizedText);
4285
+ const headingHasWeightRequest = options.fontWeight != null || options.bold === true;
4286
+ const headingEffectiveFamily = options.fontFamily ?? (headingHasWeightRequest ? resolveFontFamily(theme, "heading") : void 0);
4287
+ const headingWeighted = applyFontWeightAlias({
4288
+ fontFamily: headingEffectiveFamily,
4289
+ bold: options.bold,
4290
+ italic: options.italic,
4291
+ fontWeight: options.fontWeight
4292
+ });
4186
4293
  const baseTextStyle = {
4187
- ...options.fontFamily && { font: options.fontFamily },
4294
+ ...(options.fontFamily || headingWeighted.font && headingWeighted.font !== headingEffectiveFamily) && {
4295
+ font: headingWeighted.font
4296
+ },
4188
4297
  ...options.fontSize && { size: options.fontSize * 2 },
4189
4298
  // points to half-points
4190
4299
  ...options.fontColor && { color: resolveColor(options.fontColor, theme) },
4191
- ...options.bold !== void 0 && { bold: options.bold },
4192
- ...options.italic !== void 0 && { italics: options.italic },
4300
+ ...headingWeighted.bold !== void 0 && { bold: headingWeighted.bold },
4301
+ ...headingWeighted.italics !== void 0 && {
4302
+ italics: headingWeighted.italics
4303
+ },
4193
4304
  ...options.underline !== void 0 && {
4194
4305
  underline: options.underline ? { type: "single" } : void 0
4195
4306
  }
@@ -4701,12 +4812,18 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
4701
4812
  if (!cell) {
4702
4813
  return cellChildren;
4703
4814
  }
4815
+ const cellWeighted = applyFontWeightAlias({
4816
+ fontFamily: cellDefaults.font?.family || baseCellStyle.font,
4817
+ bold: cellDefaults.font?.bold ?? false,
4818
+ italic: cellDefaults.font?.italic ?? false,
4819
+ fontWeight: cellDefaults.font?.fontWeight
4820
+ });
4704
4821
  const mergedStyle = {
4705
- font: cellDefaults.font?.family || baseCellStyle.font,
4822
+ font: cellWeighted.font,
4706
4823
  size: cellDefaults.font?.size ? cellDefaults.font.size * 2 : baseCellStyle.size,
4707
4824
  // Convert to half-points
4708
- bold: cellDefaults.font?.bold ?? false,
4709
- italics: cellDefaults.font?.italic ?? false,
4825
+ bold: cellWeighted.bold ?? false,
4826
+ italics: cellWeighted.italics ?? false,
4710
4827
  underline: cellDefaults.font?.underline ? { type: "single" } : void 0,
4711
4828
  color: cellDefaults.color || baseCellStyle.color
4712
4829
  };
@@ -4714,15 +4831,21 @@ async function createTable(columns, tableConfig, theme, themeName, _options = {}
4714
4831
  if (isParagraphComponent(cell)) {
4715
4832
  const textComp = cell;
4716
4833
  const paragraphFont = textComp.props.font;
4834
+ const paraWeighted = applyFontWeightAlias({
4835
+ fontFamily: paragraphFont?.family ?? mergedStyle.font,
4836
+ bold: paragraphFont?.bold,
4837
+ italic: paragraphFont?.italic,
4838
+ fontWeight: paragraphFont?.fontWeight
4839
+ });
4717
4840
  const paragraphStyle = {
4718
4841
  ...mergedStyle,
4719
- ...paragraphFont?.family && { font: paragraphFont.family },
4842
+ ...paraWeighted.font && { font: paraWeighted.font },
4720
4843
  ...paragraphFont?.size && { size: paragraphFont.size * 2 },
4721
- ...paragraphFont?.bold !== void 0 && {
4722
- bold: paragraphFont.bold
4844
+ ...paraWeighted.bold !== void 0 && {
4845
+ bold: paraWeighted.bold
4723
4846
  },
4724
- ...paragraphFont?.italic !== void 0 && {
4725
- italics: paragraphFont.italic
4847
+ ...paraWeighted.italics !== void 0 && {
4848
+ italics: paraWeighted.italics
4726
4849
  },
4727
4850
  ...paragraphFont?.underline !== void 0 && {
4728
4851
  underline: paragraphFont.underline ? { type: "single" } : void 0
@@ -5153,35 +5276,29 @@ function createFooterElement(children, _options) {
5153
5276
  // src/components/heading.ts
5154
5277
  function renderHeadingComponent(component, theme, themeName) {
5155
5278
  if (!isHeadingComponent(component)) return [];
5156
- const resolvedConfig = resolveHeadingProps(component.props, theme);
5157
- const level = resolvedConfig.level || 1;
5158
- const levelDefaults = getHeadingDefaultsForLevel(theme, level);
5159
- const finalConfig = {
5160
- ...resolvedConfig,
5161
- // Only apply level defaults if no explicit alignment was provided in the original props
5162
- ...component.props.alignment ? {} : levelDefaults
5163
- };
5164
- const bookmarkId = component.id || globalBookmarkRegistry.generateId(finalConfig.text, "heading");
5279
+ const config = component.props;
5280
+ const bookmarkId = component.id || globalBookmarkRegistry.generateId(config.text, "heading");
5165
5281
  const header = createHeading(
5166
- finalConfig.text,
5167
- finalConfig.level || 1,
5282
+ config.text,
5283
+ config.level || 1,
5168
5284
  theme,
5169
5285
  themeName,
5170
5286
  {
5171
- alignment: finalConfig.alignment,
5172
- spacing: finalConfig.spacing,
5173
- lineSpacing: finalConfig.lineSpacing,
5174
- columnBreak: finalConfig.columnBreak,
5287
+ alignment: config.alignment,
5288
+ spacing: config.spacing,
5289
+ lineSpacing: config.lineSpacing,
5290
+ columnBreak: config.columnBreak,
5175
5291
  // Local font overrides
5176
- fontFamily: finalConfig.font?.family,
5177
- fontSize: finalConfig.font?.size,
5178
- fontColor: finalConfig.font?.color,
5179
- bold: finalConfig.font?.bold,
5180
- italic: finalConfig.font?.italic,
5181
- underline: finalConfig.font?.underline,
5292
+ fontFamily: config.font?.family,
5293
+ fontSize: config.font?.size,
5294
+ fontColor: config.font?.color,
5295
+ bold: config.font?.bold,
5296
+ fontWeight: config.font?.fontWeight,
5297
+ italic: config.font?.italic,
5298
+ underline: config.font?.underline,
5182
5299
  // Pagination control
5183
- keepNext: finalConfig.keepNext,
5184
- keepLines: finalConfig.keepLines,
5300
+ keepNext: config.keepNext,
5301
+ keepLines: config.keepLines,
5185
5302
  // Bookmark ID for internal linking
5186
5303
  bookmarkId
5187
5304
  }
@@ -5226,7 +5343,7 @@ function parseMarkdownList(text) {
5226
5343
  }
5227
5344
  function renderParagraphComponent(component, theme, themeName) {
5228
5345
  if (!isParagraphComponent(component)) return [];
5229
- const resolvedConfig = resolveParagraphProps(component.props, theme);
5346
+ const resolvedConfig = component.props;
5230
5347
  const listData = parseMarkdownList(resolvedConfig.text);
5231
5348
  if (listData) {
5232
5349
  const reference = globalNumberingRegistry.generateReference("markdown-list");
@@ -5292,6 +5409,7 @@ function renderParagraphComponent(component, theme, themeName) {
5292
5409
  fontSize: resolvedConfig.font?.size,
5293
5410
  fontColor: resolvedConfig.font?.color,
5294
5411
  bold: resolvedConfig.font?.bold,
5412
+ fontWeight: resolvedConfig.font?.fontWeight,
5295
5413
  italic: resolvedConfig.font?.italic,
5296
5414
  underline: resolvedConfig.font?.underline,
5297
5415
  // Pass outline level for TOC support
@@ -5411,7 +5529,7 @@ function fillMissingLevels(levels, maxLevel) {
5411
5529
  }
5412
5530
  function renderListComponent(component, theme, themeName) {
5413
5531
  if (!isListComponent(component)) return [];
5414
- const resolvedConfig = resolveListProps(component.props, theme);
5532
+ const resolvedConfig = component.props;
5415
5533
  const maxLevel = getMaxLevelFromItems(resolvedConfig.items);
5416
5534
  const reference = resolvedConfig.reference || globalNumberingRegistry.generateReference("list");
5417
5535
  if (!globalNumberingRegistry.has(reference)) {
@@ -5442,7 +5560,7 @@ function renderListComponent(component, theme, themeName) {
5442
5560
  // src/components/image.ts
5443
5561
  async function renderImageComponent(component, theme, themeName) {
5444
5562
  if (!isImageComponent(component)) return [];
5445
- const resolvedConfig = resolveImageProps(component.props, theme);
5563
+ const resolvedConfig = component.props;
5446
5564
  const imageSource = resolvedConfig.base64 || resolvedConfig.path;
5447
5565
  if (!imageSource) {
5448
5566
  throw new Error(
@@ -5802,7 +5920,6 @@ async function renderColumnsComponent(component, theme, themeName, context) {
5802
5920
  if (context.parent && isTextBoxComponent(context.parent)) {
5803
5921
  return await renderColumnsAsTable(component, theme, themeName, context);
5804
5922
  }
5805
- resolveColumnsProps(component.props, theme);
5806
5923
  const elements = [];
5807
5924
  if (component.children) {
5808
5925
  for (const child of component.children) {
@@ -5914,9 +6031,9 @@ async function renderColumnsAsTable(component, theme, themeName, context) {
5914
6031
  }
5915
6032
 
5916
6033
  // src/components/statistic.ts
5917
- function renderStatisticComponent(component, theme) {
6034
+ function renderStatisticComponent(component, _theme) {
5918
6035
  if (!isStatisticComponent(component)) return [];
5919
- const resolvedConfig = resolveStatisticProps(component.props, theme);
6036
+ const resolvedConfig = component.props;
5920
6037
  return createStatistic(
5921
6038
  {
5922
6039
  number: resolvedConfig.number,
@@ -6564,6 +6681,69 @@ async function renderComponent(component, theme, themeName, context) {
6564
6681
  );
6565
6682
  }
6566
6683
 
6684
+ // src/core/fontResolution.ts
6685
+ import {
6686
+ collectFontNamesFromDocx,
6687
+ validateFontReferences,
6688
+ FontRegistry
6689
+ } from "@json-to-office/shared";
6690
+ import {
6691
+ loadFileFontSource,
6692
+ FontDiskCache,
6693
+ fetchVariableFontSource
6694
+ } from "@json-to-office/shared/fonts/node";
6695
+ async function resolveDocumentFonts(document, theme, fonts, warnings) {
6696
+ const emit = (code, message) => {
6697
+ if (warnings) {
6698
+ warnings.push({
6699
+ component: "fontRegistry",
6700
+ message,
6701
+ severity: "warning",
6702
+ context: { code }
6703
+ });
6704
+ } else {
6705
+ console.warn(`[json-to-docx] ${code}: ${message}`);
6706
+ }
6707
+ };
6708
+ const names = /* @__PURE__ */ new Set();
6709
+ for (const n of collectFontNamesFromDocx(document)) names.add(n);
6710
+ for (const n of collectFontNamesFromDocx(theme)) names.add(n);
6711
+ if (names.size === 0) return [];
6712
+ const validation = validateFontReferences({
6713
+ referencedNames: names,
6714
+ registeredEntries: fonts?.extraEntries
6715
+ });
6716
+ if (validation.warnings.length > 0) {
6717
+ if (fonts?.strict) {
6718
+ throw new Error(
6719
+ `Unresolved font references (strict mode):
6720
+ ` + validation.warnings.map((w) => ` - ${w.message}`).join("\n")
6721
+ );
6722
+ }
6723
+ for (const w of validation.warnings) {
6724
+ emit(w.code, w.message);
6725
+ }
6726
+ }
6727
+ if (!fonts?.onResolved) return [];
6728
+ const registry = new FontRegistry({
6729
+ opts: fonts,
6730
+ fileLoader: loadFileFontSource,
6731
+ variableLoader: fetchVariableFontSource,
6732
+ diskCache: fonts?.googleFonts?.cacheDir ? new FontDiskCache(fonts.googleFonts.cacheDir) : void 0
6733
+ });
6734
+ const resolved = await registry.resolveMany(names);
6735
+ for (const r of resolved) {
6736
+ for (const msg of r.warnings) {
6737
+ emit("FONT_UNRESOLVED", msg);
6738
+ }
6739
+ }
6740
+ fonts.onResolved(resolved);
6741
+ return resolved;
6742
+ }
6743
+
6744
+ // src/core/generator.ts
6745
+ import { applyExportMode, scopedThemeName } from "@json-to-office/shared";
6746
+
6567
6747
  // src/json/parser.ts
6568
6748
  import {
6569
6749
  JsonDocumentParser,
@@ -6723,33 +6903,32 @@ function isReportComponentDefinition(definition) {
6723
6903
  const def = definition;
6724
6904
  return def.name === "docx" && "props" in def;
6725
6905
  }
6726
- async function generateDocument(document) {
6906
+ async function generateDocument(document, options) {
6727
6907
  if (!document || document.name !== "docx") {
6728
6908
  throw new Error("Top-level component must be a docx component");
6729
6909
  }
6730
6910
  if ("$schema" in document) {
6731
- return await generateDocumentFromJson(document);
6911
+ return await generateDocumentFromJson(document, options);
6732
6912
  }
6733
- const themeName = document.props.theme || "minimal";
6734
- const theme = getThemeWithFallback(themeName);
6735
- const structure = await processDocument(document, theme, themeName);
6736
- const layout = applyLayout(structure.sections, theme, themeName);
6737
- const renderedDocument = await renderDocument(structure, layout, {
6738
- bypassCache: false
6739
- // Enable component caching
6740
- });
6741
- return renderedDocument;
6913
+ return await generateDocumentWithCustomThemes(
6914
+ document,
6915
+ options?.customThemes,
6916
+ options?.services,
6917
+ options?.fonts,
6918
+ options?.warnings
6919
+ );
6742
6920
  }
6743
- async function generateFromConfig(props, components) {
6921
+ async function generateFromConfig(props, components, options) {
6744
6922
  const reportComponent = {
6745
6923
  name: "docx",
6746
6924
  props,
6747
6925
  children: components
6748
6926
  };
6749
- return await generateDocument(reportComponent);
6927
+ return await generateDocument(reportComponent, options);
6750
6928
  }
6751
- async function generateDocumentWithCustomThemes(document, customThemes, services) {
6752
- const themeName = document.props.theme || "minimal";
6929
+ async function generateDocumentWithCustomThemes(documentIn, customThemes, services, fonts, warnings) {
6930
+ let document = documentIn;
6931
+ let themeName = document.props.theme || "minimal";
6753
6932
  let theme;
6754
6933
  if (customThemes) {
6755
6934
  if (customThemes[themeName]) {
@@ -6768,6 +6947,23 @@ async function generateDocumentWithCustomThemes(document, customThemes, services
6768
6947
  } else {
6769
6948
  theme = getThemeWithFallback(themeName);
6770
6949
  }
6950
+ const mode = applyExportMode({ doc: document, theme, fonts });
6951
+ document = mode.doc;
6952
+ theme = mode.theme;
6953
+ themeName = scopedThemeName(themeName, fonts?.mode);
6954
+ for (const w of mode.warnings) {
6955
+ if (warnings) {
6956
+ warnings.push({
6957
+ component: "fontRegistry",
6958
+ message: w.message,
6959
+ severity: "warning",
6960
+ context: { code: w.code }
6961
+ });
6962
+ } else {
6963
+ console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
6964
+ }
6965
+ }
6966
+ await resolveDocumentFonts(document, theme, fonts, warnings);
6771
6967
  const structure = await processDocument(document, theme, themeName);
6772
6968
  const layout = applyLayout(structure.sections, theme, themeName);
6773
6969
  const renderedDocument = await renderDocument(structure, layout, {
@@ -6791,7 +6987,9 @@ async function generateDocumentFromJson(jsonConfig, options) {
6791
6987
  return await generateDocumentWithCustomThemes(
6792
6988
  reportComponent,
6793
6989
  options?.customThemes,
6794
- options?.services
6990
+ options?.services,
6991
+ options?.fonts,
6992
+ options?.warnings
6795
6993
  );
6796
6994
  }
6797
6995
  function validateJsonSchema(jsonConfig) {
@@ -7054,6 +7252,7 @@ async function runExample(example, options = {}) {
7054
7252
  // src/plugin/createDocumentGenerator.ts
7055
7253
  init_styles();
7056
7254
  import { Packer as Packer2 } from "docx";
7255
+ import { applyExportMode as applyExportMode2, scopedThemeName as scopedThemeName2 } from "@json-to-office/shared";
7057
7256
 
7058
7257
  // src/plugin/version-resolver.ts
7059
7258
  import { resolveComponentVersion } from "@json-to-office/shared/plugin";
@@ -7422,7 +7621,8 @@ function createBuilderImpl(state) {
7422
7621
  customThemes: state.customThemes,
7423
7622
  debug: state.debug,
7424
7623
  enableCache: state.enableCache,
7425
- services: state.services
7624
+ services: state.services,
7625
+ fonts: state.fonts
7426
7626
  };
7427
7627
  return createBuilderImpl(
7428
7628
  newState
@@ -7435,25 +7635,37 @@ function createBuilderImpl(state) {
7435
7635
  internalDocument,
7436
7636
  state.components
7437
7637
  );
7438
- const themeName = internalDocument.props.theme || "minimal";
7439
- const docTheme = resolveDocumentTheme(themeName);
7638
+ const baseThemeName = internalDocument.props.theme || "minimal";
7639
+ const docTheme = resolveDocumentTheme(baseThemeName);
7440
7640
  const warnings = [];
7641
+ const mode = applyExportMode2({
7642
+ doc: internalDocument,
7643
+ theme: docTheme,
7644
+ fonts: state.fonts
7645
+ });
7646
+ const modedTheme = mode.theme;
7647
+ const themeName = scopedThemeName2(baseThemeName, state.fonts?.mode);
7648
+ for (const w of mode.warnings) {
7649
+ warnings.push({
7650
+ component: "fontRegistry",
7651
+ message: w.message,
7652
+ severity: "warning",
7653
+ context: { code: w.code }
7654
+ });
7655
+ }
7441
7656
  const processedComponents = await processDocumentComponents(
7442
- internalDocument.children || [],
7657
+ mode.doc.children || [],
7443
7658
  warnings,
7444
- docTheme
7659
+ modedTheme
7445
7660
  );
7446
7661
  const processedDocument = {
7447
- ...internalDocument,
7662
+ ...mode.doc,
7448
7663
  children: processedComponents
7449
7664
  };
7450
- const [finalReportComponent] = normalizeDocument(processedDocument);
7451
- const structure = await processDocument(
7452
- finalReportComponent,
7453
- docTheme,
7454
- themeName
7455
- );
7456
- const layout = applyLayout(structure.sections, docTheme, themeName);
7665
+ const [modedDoc] = normalizeDocument(processedDocument);
7666
+ await resolveDocumentFonts(modedDoc, modedTheme, state.fonts, warnings);
7667
+ const structure = await processDocument(modedDoc, modedTheme, themeName);
7668
+ const layout = applyLayout(structure.sections, modedTheme, themeName);
7457
7669
  const generatedDocument = await renderDocument(structure, layout, {
7458
7670
  services: state.services
7459
7671
  });
@@ -7534,13 +7746,19 @@ function createBuilderImpl(state) {
7534
7746
  const themeName = internalDocument.props.theme || "minimal";
7535
7747
  const docTheme = resolveDocumentTheme(themeName);
7536
7748
  const warnings = [];
7749
+ const mode = applyExportMode2({
7750
+ doc: internalDocument,
7751
+ theme: docTheme,
7752
+ fonts: state.fonts
7753
+ });
7754
+ const modedTheme = mode.theme;
7537
7755
  const processedComponents = await processDocumentComponents(
7538
- internalDocument.children || [],
7756
+ mode.doc.children || [],
7539
7757
  warnings,
7540
- docTheme
7758
+ modedTheme
7541
7759
  );
7542
7760
  const processedDocument = {
7543
- ...internalDocument,
7761
+ ...mode.doc,
7544
7762
  children: processedComponents
7545
7763
  };
7546
7764
  const [finalReportComponent] = normalizeDocument(processedDocument);
@@ -7572,7 +7790,8 @@ function createDocumentGenerator(options) {
7572
7790
  customThemes: options.customThemes,
7573
7791
  debug: options.debug ?? false,
7574
7792
  enableCache: options.enableCache ?? false,
7575
- services: options.services
7793
+ services: options.services,
7794
+ fonts: options.fonts
7576
7795
  };
7577
7796
  return createBuilderImpl(initialState);
7578
7797
  }