@json-to-office/core-docx 0.23.0 → 0.25.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 +1 @@
1
- {"version":3,"file":"createDocumentGenerator.d.ts","sourceRoot":"","sources":["../../src/plugin/createDocumentGenerator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9E,OAAO,KAAK,EAEV,wBAAwB,EAOxB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAmBjB;;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;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AA8nBD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAAC,SAAS,EAAE,CAAC,CAgBvC"}
1
+ {"version":3,"file":"createDocumentGenerator.d.ts","sourceRoot":"","sources":["../../src/plugin/createDocumentGenerator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAI7C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9E,OAAO,KAAK,EAEV,wBAAwB,EAOxB,2BAA2B,EAC5B,MAAM,SAAS,CAAC;AAmBjB;;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;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAqnBD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,wBAAwB,CAAC,SAAS,EAAE,CAAC,CAgBvC"}
@@ -1053,7 +1053,8 @@ function resolveFontProperties(theme, fontRef) {
1053
1053
  alignment: fontDef.alignment,
1054
1054
  lineSpacing: fontDef.lineSpacing,
1055
1055
  spacing: fontDef.spacing,
1056
- characterSpacing: fontDef.characterSpacing
1056
+ characterSpacing: fontDef.characterSpacing,
1057
+ scale: fontDef.scale
1057
1058
  };
1058
1059
  }
1059
1060
  function mergeFontAndStyleProperties(fontProps, styleOverrides) {
@@ -1914,9 +1915,84 @@ function resolveBuiltInTheme(themeName, options = {}) {
1914
1915
  return getThemeWithFallback(themeName);
1915
1916
  }
1916
1917
 
1917
- // src/plugin/createDocumentGenerator.ts
1918
+ // src/core/generationContext.ts
1918
1919
  import { applyExportMode, scopedThemeName } from "@json-to-office/shared";
1919
1920
 
1921
+ // src/themes/overrides.ts
1922
+ import { createHash } from "crypto";
1923
+ function applyThemeOverrides(theme, overrides) {
1924
+ if (!overrides) return theme;
1925
+ const fonts = { ...theme.fonts };
1926
+ for (const [role, def] of Object.entries(overrides.fonts ?? {})) {
1927
+ const key = role;
1928
+ fonts[key] = { ...theme.fonts[key], ...def };
1929
+ }
1930
+ const styles = { ...theme.styles ?? {} };
1931
+ for (const [name, def] of Object.entries(overrides.styles ?? {})) {
1932
+ styles[name] = { ...styles[name], ...def };
1933
+ }
1934
+ return {
1935
+ ...theme,
1936
+ colors: { ...theme.colors, ...overrides.colors ?? {} },
1937
+ fonts,
1938
+ ...Object.keys(styles).length > 0 && { styles }
1939
+ };
1940
+ }
1941
+ function themeOverridesDigest(overrides) {
1942
+ return createHash("sha256").update(JSON.stringify(overrides)).digest("hex").slice(0, 8);
1943
+ }
1944
+
1945
+ // src/core/generationContext.ts
1946
+ function defaultNamedThemeLookup(themeName, customThemes, warnings) {
1947
+ if (customThemes) {
1948
+ if (customThemes[themeName]) return customThemes[themeName];
1949
+ const themeNameLower = themeName.toLowerCase();
1950
+ const matchingThemeKey = Object.keys(customThemes).find(
1951
+ (key) => key.toLowerCase() === themeNameLower
1952
+ );
1953
+ if (matchingThemeKey) return customThemes[matchingThemeKey];
1954
+ return resolveBuiltInTheme(themeName, { customThemes, warnings });
1955
+ }
1956
+ return resolveBuiltInTheme(themeName, { warnings });
1957
+ }
1958
+ function resolveThemeContext(documentIn, options = {}) {
1959
+ const { customThemes, fonts, warnings, resolveNamedTheme } = options;
1960
+ if (documentIn.props === null) {
1961
+ throw new Error(
1962
+ "Document `props` is null. Omit it, or provide an object \u2014 a null props cannot carry a theme."
1963
+ );
1964
+ }
1965
+ const document = documentIn.props === void 0 ? { ...documentIn, props: {} } : documentIn;
1966
+ const baseThemeName = document.props.theme || "minimal";
1967
+ let theme = resolveNamedTheme ? resolveNamedTheme(baseThemeName, warnings) : defaultNamedThemeLookup(baseThemeName, customThemes, warnings);
1968
+ const themeOverrides = document.props.themeOverrides;
1969
+ let themeName = baseThemeName;
1970
+ if (themeOverrides) {
1971
+ theme = applyThemeOverrides(theme, themeOverrides);
1972
+ themeName = `${themeName}+ov:${themeOverridesDigest(themeOverrides)}`;
1973
+ }
1974
+ const mode = applyExportMode({ doc: document, theme, fonts });
1975
+ for (const w of mode.warnings) {
1976
+ if (warnings) {
1977
+ warnings.push({
1978
+ component: "fontRegistry",
1979
+ message: w.message,
1980
+ severity: "warning",
1981
+ context: { code: w.code }
1982
+ });
1983
+ } else {
1984
+ console.warn(`[json-to-docx] [${w.code}] ${w.message}`);
1985
+ }
1986
+ }
1987
+ return {
1988
+ document: mode.doc,
1989
+ theme: mode.theme,
1990
+ // Scope by mode too: substitute rewrites the theme in place, so caches
1991
+ // must not share slots with custom-mode runs keyed on the same name.
1992
+ themeName: scopedThemeName(themeName, fonts?.mode)
1993
+ };
1994
+ }
1995
+
1920
1996
  // src/core/fontResolution.ts
1921
1997
  import {
1922
1998
  collectFontNamesFromDocx,
@@ -3127,7 +3203,8 @@ function convertRunProperties(merged, theme, defaultColor, defaultSize) {
3127
3203
  ...merged.underline !== void 0 && merged.underline && { underline: { type: "single" } },
3128
3204
  ...merged.characterSpacing && {
3129
3205
  characterSpacing: merged.characterSpacing.type === "condensed" ? -merged.characterSpacing.value : merged.characterSpacing.value
3130
- }
3206
+ },
3207
+ ...merged.scale && { scale: merged.scale }
3131
3208
  };
3132
3209
  }
3133
3210
  function convertParagraphProperties(merged, styleProps, theme) {
@@ -3201,7 +3278,8 @@ function createWordStyles(themeNameOrObject = "minimal", language) {
3201
3278
  bold: normalStyle?.bold,
3202
3279
  italic: normalStyle?.italic,
3203
3280
  underline: normalStyle?.underline,
3204
- characterSpacing: normalStyle?.characterSpacing
3281
+ characterSpacing: normalStyle?.characterSpacing,
3282
+ scale: normalStyle?.scale
3205
3283
  });
3206
3284
  return convertRunProperties(merged, theme, theme.colors.text);
3207
3285
  })(),
@@ -3239,7 +3317,8 @@ function createWordStyles(themeNameOrObject = "minimal", language) {
3239
3317
  bold: headingStyle.bold,
3240
3318
  italic: headingStyle.italic,
3241
3319
  underline: headingStyle.underline,
3242
- characterSpacing: headingStyle.characterSpacing
3320
+ characterSpacing: headingStyle.characterSpacing,
3321
+ scale: headingStyle.scale
3243
3322
  });
3244
3323
  return convertRunProperties(merged, theme, theme.colors.primary, 20);
3245
3324
  })(),
@@ -3305,7 +3384,8 @@ function createWordStyles(themeNameOrObject = "minimal", language) {
3305
3384
  bold: headingStyle.bold,
3306
3385
  italic: headingStyle.italic,
3307
3386
  underline: headingStyle.underline,
3308
- characterSpacing: headingStyle.characterSpacing
3387
+ characterSpacing: headingStyle.characterSpacing,
3388
+ scale: headingStyle.scale
3309
3389
  });
3310
3390
  return convertRunProperties(merged, theme, theme.colors.primary, 20);
3311
3391
  })(),
@@ -3372,7 +3452,8 @@ function createWordStyles(themeNameOrObject = "minimal", language) {
3372
3452
  bold: titleStyle.bold,
3373
3453
  italic: titleStyle.italic,
3374
3454
  underline: titleStyle.underline,
3375
- characterSpacing: titleStyle.characterSpacing
3455
+ characterSpacing: titleStyle.characterSpacing,
3456
+ scale: titleStyle.scale
3376
3457
  });
3377
3458
  return convertRunProperties(merged, theme, theme.colors.primary, 20);
3378
3459
  })(),
@@ -3433,7 +3514,8 @@ function createWordStyles(themeNameOrObject = "minimal", language) {
3433
3514
  bold: subtitleStyle.bold,
3434
3515
  italic: subtitleStyle.italic,
3435
3516
  underline: subtitleStyle.underline,
3436
- characterSpacing: subtitleStyle.characterSpacing
3517
+ characterSpacing: subtitleStyle.characterSpacing,
3518
+ scale: subtitleStyle.scale
3437
3519
  });
3438
3520
  return convertRunProperties(merged, theme, theme.colors.secondary);
3439
3521
  })(),
@@ -3575,7 +3657,8 @@ function createWordStyles(themeNameOrObject = "minimal", language) {
3575
3657
  bold: customStyle.bold,
3576
3658
  italic: customStyle.italic,
3577
3659
  underline: customStyle.underline,
3578
- characterSpacing: customStyle.characterSpacing
3660
+ characterSpacing: customStyle.characterSpacing,
3661
+ scale: customStyle.scale
3579
3662
  });
3580
3663
  return convertRunProperties(merged, theme, theme.colors.text);
3581
3664
  })(),
@@ -3683,7 +3766,7 @@ __reExport(cache_exports, cache_star);
3683
3766
  import * as cache_star from "@json-to-office/shared/cache";
3684
3767
 
3685
3768
  // src/cache/key-generator.ts
3686
- import { createHash } from "crypto";
3769
+ import { createHash as createHash2 } from "crypto";
3687
3770
  var CacheKeyGenerator = class {
3688
3771
  version;
3689
3772
  constructor(version = "1.0") {
@@ -3757,7 +3840,7 @@ var CacheKeyGenerator = class {
3757
3840
  * Create hash
3758
3841
  */
3759
3842
  hash(input) {
3760
- return createHash("sha256").update(input).digest("hex").substring(0, 16);
3843
+ return createHash2("sha256").update(input).digest("hex").substring(0, 16);
3761
3844
  }
3762
3845
  };
3763
3846
 
@@ -3772,11 +3855,12 @@ function normalizeUnicodeText(text) {
3772
3855
  // src/utils/placeholderProcessor.ts
3773
3856
  import {
3774
3857
  TextRun as TextRun2,
3775
- PageNumber
3858
+ PageNumber,
3859
+ Tab as Tab2
3776
3860
  } from "docx";
3777
3861
 
3778
3862
  // src/utils/textParser.ts
3779
- import { TextRun, ExternalHyperlink, InternalHyperlink } from "docx";
3863
+ import { TextRun, ExternalHyperlink, InternalHyperlink, Tab } from "docx";
3780
3864
  function escapeRegExp(s) {
3781
3865
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3782
3866
  }
@@ -3899,28 +3983,49 @@ function createTextRunsWithNewlines(text, baseStyle, options, overrideStyle) {
3899
3983
  ...runStyle.bold !== void 0 && { bold: runStyle.bold },
3900
3984
  ...runStyle.italics !== void 0 && { italics: runStyle.italics },
3901
3985
  ...baseStyle.underline && { underline: baseStyle.underline },
3986
+ ...baseStyle.scale && { scale: baseStyle.scale },
3987
+ ...baseStyle.characterSpacing && {
3988
+ characterSpacing: baseStyle.characterSpacing
3989
+ },
3902
3990
  ...baseStyle.language && { language: baseStyle.language }
3903
3991
  };
3904
3992
  const wholeRunNoProof = baseStyle.noProof === true;
3905
3993
  const wordsForLine = wholeRunNoProof ? void 0 : options.noProofWords;
3906
3994
  let firstSegment = true;
3907
- const lineRuns = splitByNoProofWords(
3908
- line,
3909
- (segment, matched) => {
3910
- const segNoProof = matched || wholeRunNoProof;
3911
- const run = new TextRun({
3912
- text: segment,
3913
- ...commonProps,
3914
- ...(matched || baseStyle.noProof !== void 0) && {
3915
- noProof: segNoProof
3916
- },
3917
- ...needsLineBreak && firstSegment && { break: 1 }
3918
- });
3995
+ const lineRuns = [];
3996
+ const tabSegments = line.split(" ");
3997
+ tabSegments.forEach((tabSegment, tabIndex) => {
3998
+ if (tabIndex > 0) {
3999
+ lineRuns.push(
4000
+ new TextRun({
4001
+ children: [new Tab()],
4002
+ ...commonProps,
4003
+ ...needsLineBreak && firstSegment && { break: 1 }
4004
+ })
4005
+ );
3919
4006
  firstSegment = false;
3920
- return run;
3921
- },
3922
- wordsForLine
3923
- );
4007
+ }
4008
+ if (!tabSegment && tabSegments.length > 1) return;
4009
+ lineRuns.push(
4010
+ ...splitByNoProofWords(
4011
+ tabSegment,
4012
+ (segment, matched) => {
4013
+ const segNoProof = matched || wholeRunNoProof;
4014
+ const run = new TextRun({
4015
+ text: segment,
4016
+ ...commonProps,
4017
+ ...(matched || baseStyle.noProof !== void 0) && {
4018
+ noProof: segNoProof
4019
+ },
4020
+ ...needsLineBreak && firstSegment && { break: 1 }
4021
+ });
4022
+ firstSegment = false;
4023
+ return run;
4024
+ },
4025
+ wordsForLine
4026
+ )
4027
+ );
4028
+ });
3924
4029
  runs.push(...lineRuns);
3925
4030
  }
3926
4031
  }
@@ -4155,27 +4260,48 @@ function createTextRunsWithNewlines2(text, baseStyle, noProofWords) {
4155
4260
  bold: baseStyle.bold,
4156
4261
  italics: baseStyle.italics,
4157
4262
  underline: baseStyle.underline,
4263
+ ...baseStyle.scale && { scale: baseStyle.scale },
4264
+ ...baseStyle.characterSpacing && {
4265
+ characterSpacing: baseStyle.characterSpacing
4266
+ },
4158
4267
  ...baseStyle.language && { language: baseStyle.language }
4159
4268
  };
4160
4269
  const wholeRunNoProof = baseStyle.noProof === true;
4161
4270
  const wordsForLine = wholeRunNoProof ? void 0 : noProofWords;
4162
4271
  let firstSegment = true;
4163
- const lineRuns = splitByNoProofWords(
4164
- line,
4165
- (segment, matched) => {
4166
- const run = new TextRun2({
4167
- text: segment,
4168
- ...commonProps,
4169
- ...(matched || baseStyle.noProof !== void 0) && {
4170
- noProof: matched || wholeRunNoProof
4171
- },
4172
- break: needsLineBreak && firstSegment ? 1 : void 0
4173
- });
4272
+ const lineRuns = [];
4273
+ const tabSegments = line.split(" ");
4274
+ tabSegments.forEach((tabSegment, tabIndex) => {
4275
+ if (tabIndex > 0) {
4276
+ lineRuns.push(
4277
+ new TextRun2({
4278
+ children: [new Tab2()],
4279
+ ...commonProps,
4280
+ break: needsLineBreak && firstSegment ? 1 : void 0
4281
+ })
4282
+ );
4174
4283
  firstSegment = false;
4175
- return run;
4176
- },
4177
- wordsForLine
4178
- );
4284
+ }
4285
+ if (!tabSegment && tabSegments.length > 1) return;
4286
+ lineRuns.push(
4287
+ ...splitByNoProofWords(
4288
+ tabSegment,
4289
+ (segment, matched) => {
4290
+ const run = new TextRun2({
4291
+ text: segment,
4292
+ ...commonProps,
4293
+ ...(matched || baseStyle.noProof !== void 0) && {
4294
+ noProof: matched || wholeRunNoProof
4295
+ },
4296
+ break: needsLineBreak && firstSegment ? 1 : void 0
4297
+ });
4298
+ firstSegment = false;
4299
+ return run;
4300
+ },
4301
+ wordsForLine
4302
+ )
4303
+ );
4304
+ });
4179
4305
  runs.push(...lineRuns);
4180
4306
  }
4181
4307
  }
@@ -4330,12 +4456,12 @@ function componentHasRevision(component) {
4330
4456
  }
4331
4457
 
4332
4458
  // src/core/cached-render.ts
4333
- import { createHash as createHash2 } from "crypto";
4459
+ import { createHash as createHash3 } from "crypto";
4334
4460
  var componentCache = null;
4335
4461
  var cacheKeyGen = null;
4336
4462
  function createThemeHash(theme) {
4337
4463
  const themeString = JSON.stringify(theme);
4338
- return createHash2("sha256").update(themeString).digest("hex").substring(0, 8);
4464
+ return createHash3("sha256").update(themeString).digest("hex").substring(0, 8);
4339
4465
  }
4340
4466
  function initializeComponentCache(cache) {
4341
4467
  if (cache) {
@@ -4572,6 +4698,12 @@ function createText(content, theme, themeName, options = {}) {
4572
4698
  ...options.underline !== void 0 && {
4573
4699
  underline: options.underline ? { type: "single" } : void 0
4574
4700
  },
4701
+ // Character width scaling in percent (w:w)
4702
+ ...options.scale && { scale: options.scale },
4703
+ // Letter tracking (w:spacing, twentieths of a point; negative = condensed)
4704
+ ...options.characterSpacing && {
4705
+ characterSpacing: options.characterSpacing.type === "condensed" ? -options.characterSpacing.value : options.characterSpacing.value
4706
+ },
4575
4707
  // Proofing: per-run language and/or no-proof. Omitting language lets the
4576
4708
  // run inherit the document default set on docDefaults.
4577
4709
  ...options.language && { language: { value: options.language } },
@@ -4596,7 +4728,7 @@ function createText(content, theme, themeName, options = {}) {
4596
4728
  }
4597
4729
  } else {
4598
4730
  const textRuns = parseTextWithDecorators(normalizedContent, baseTextStyle, {
4599
- boldColor: options.boldColor,
4731
+ boldColor: options.boldColor ? resolveColor(options.boldColor, theme) : void 0,
4600
4732
  enableHyperlinks: true,
4601
4733
  noProofWords: resolveNoProofWords(theme, options.noProofWords)
4602
4734
  });
@@ -4628,7 +4760,9 @@ function createText(content, theme, themeName, options = {}) {
4628
4760
  },
4629
4761
  ...frameOptions && { frame: frameOptions },
4630
4762
  ...options.keepNext !== void 0 && { keepNext: options.keepNext },
4631
- ...options.keepLines !== void 0 && { keepLines: options.keepLines }
4763
+ ...options.keepLines !== void 0 && { keepLines: options.keepLines },
4764
+ ...options.indent && { indent: options.indent },
4765
+ ...options.tabStops && options.tabStops.length > 0 && { tabStops: options.tabStops }
4632
4766
  });
4633
4767
  }
4634
4768
  function mapFrameOptions(floating, theme, themeName) {
@@ -4734,6 +4868,12 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4734
4868
  ...options.underline !== void 0 && {
4735
4869
  underline: options.underline ? { type: "single" } : void 0
4736
4870
  },
4871
+ // Character width scaling in percent (w:w)
4872
+ ...options.scale && { scale: options.scale },
4873
+ // Letter tracking (w:spacing, twentieths of a point; negative = condensed)
4874
+ ...options.characterSpacing && {
4875
+ characterSpacing: options.characterSpacing.type === "condensed" ? -options.characterSpacing.value : options.characterSpacing.value
4876
+ },
4737
4877
  // Proofing: per-run language and/or no-proof (see createText).
4738
4878
  ...options.language && { language: { value: options.language } },
4739
4879
  ...options.noProof !== void 0 && { noProof: options.noProof }
@@ -4774,7 +4914,7 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4774
4914
  const headingTextChildren = [];
4775
4915
  if (hasDecorators) {
4776
4916
  const textRuns = parseTextWithDecorators(normalizedText, baseTextStyle, {
4777
- boldColor: options.boldColor,
4917
+ boldColor: options.boldColor ? resolveColor(options.boldColor, theme) : void 0,
4778
4918
  enableHyperlinks: true,
4779
4919
  noProofWords: headingNoProofWords
4780
4920
  });
@@ -4791,7 +4931,7 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4791
4931
  } else {
4792
4932
  if (hasDecorators) {
4793
4933
  const textRuns = parseTextWithDecorators(normalizedText, baseTextStyle, {
4794
- boldColor: options.boldColor,
4934
+ boldColor: options.boldColor ? resolveColor(options.boldColor, theme) : void 0,
4795
4935
  enableHyperlinks: true,
4796
4936
  noProofWords: headingNoProofWords
4797
4937
  });
@@ -4807,7 +4947,8 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4807
4947
  // Only override spacing if explicitly provided
4808
4948
  spacing: hasExplicitSpacing ? spacing : void 0,
4809
4949
  ...options.keepNext !== void 0 && { keepNext: options.keepNext },
4810
- ...options.keepLines !== void 0 && { keepLines: options.keepLines }
4950
+ ...options.keepLines !== void 0 && { keepLines: options.keepLines },
4951
+ ...options.indent && { indent: options.indent }
4811
4952
  });
4812
4953
  }
4813
4954
  async function createImage(path3, theme, themeName, options = {}) {
@@ -5799,6 +5940,8 @@ function renderHeadingComponent(component, theme, themeName) {
5799
5940
  fontWeight: config.font?.fontWeight,
5800
5941
  italic: config.font?.italic,
5801
5942
  underline: config.font?.underline,
5943
+ scale: config.font?.scale,
5944
+ characterSpacing: config.font?.characterSpacing,
5802
5945
  // Proofing: local language override + no-proof toggle + known-words list
5803
5946
  language: config.language,
5804
5947
  noProof: config.noProof,
@@ -5806,6 +5949,8 @@ function renderHeadingComponent(component, theme, themeName) {
5806
5949
  // Pagination control
5807
5950
  keepNext: config.keepNext,
5808
5951
  keepLines: config.keepLines,
5952
+ // Paragraph indentation (w:ind) in twips
5953
+ indent: config.indent,
5809
5954
  // Bookmark ID for internal linking
5810
5955
  bookmarkId,
5811
5956
  // Tracked-change segments (rendered as native Word revisions)
@@ -6086,6 +6231,8 @@ function renderParagraphComponent(component, theme, themeName) {
6086
6231
  fontWeight: resolvedConfig.font?.fontWeight,
6087
6232
  italic: resolvedConfig.font?.italic,
6088
6233
  underline: resolvedConfig.font?.underline,
6234
+ scale: resolvedConfig.font?.scale,
6235
+ characterSpacing: resolvedConfig.font?.characterSpacing,
6089
6236
  // Proofing: local language override + no-proof toggle + known-words list
6090
6237
  language: resolvedConfig.language,
6091
6238
  noProof: resolvedConfig.noProof,
@@ -6098,6 +6245,10 @@ function renderParagraphComponent(component, theme, themeName) {
6098
6245
  keepNext: resolvedConfig.keepNext,
6099
6246
  // Pass keepLines property
6100
6247
  keepLines: resolvedConfig.keepLines,
6248
+ // Paragraph indentation (w:ind) in twips
6249
+ indent: resolvedConfig.indent,
6250
+ // Tab stops (w:tabs); \t characters in text jump to these positions
6251
+ tabStops: resolvedConfig.tabStops,
6101
6252
  // Pass bookmark ID for internal linking
6102
6253
  bookmarkId: resolvedConfig.id,
6103
6254
  // Tracked-change segments (rendered as native Word revisions)
@@ -7947,32 +8098,25 @@ function createBuilderImpl(state) {
7947
8098
  }
7948
8099
  };
7949
8100
  const warnings = [];
7950
- const baseThemeName = internalDocument.props.theme || "minimal";
7951
- const docTheme = resolveDocumentTheme(baseThemeName, warnings);
7952
- const mode = applyExportMode({
7953
- doc: internalDocument,
7954
- theme: docTheme,
7955
- fonts: state.fonts
8101
+ const {
8102
+ document: modedRoot,
8103
+ theme: modedTheme,
8104
+ themeName
8105
+ } = resolveThemeContext(internalDocument, {
8106
+ customThemes: state.customThemes,
8107
+ fonts: state.fonts,
8108
+ warnings,
8109
+ resolveNamedTheme: resolveDocumentTheme
7956
8110
  });
7957
- const modedTheme = mode.theme;
7958
- const themeName = scopedThemeName(baseThemeName, state.fonts?.mode);
7959
- for (const w of mode.warnings) {
7960
- warnings.push({
7961
- component: "fontRegistry",
7962
- message: w.message,
7963
- severity: "warning",
7964
- context: { code: w.code }
7965
- });
7966
- }
7967
8111
  const processed = await processDocumentComponents(
7968
- mode.doc.children || [],
8112
+ modedRoot.children || [],
7969
8113
  preserveSet,
7970
8114
  warnings,
7971
8115
  modedTheme,
7972
8116
  validateEmitted
7973
8117
  );
7974
8118
  const processedDocument = {
7975
- ...mode.doc,
8119
+ ...modedRoot,
7976
8120
  children: processed.standard
7977
8121
  };
7978
8122
  const [modedDoc] = normalizeDocument(processedDocument);
@@ -7993,7 +8137,7 @@ function createBuilderImpl(state) {
7993
8137
  bypassCache: !state.enableCache
7994
8138
  });
7995
8139
  const preservedDefinition = preserveSet ? {
7996
- ...mode.doc,
8140
+ ...modedRoot,
7997
8141
  children: processed.preserved
7998
8142
  } : void 0;
7999
8143
  return {