@json-to-office/core-docx 0.24.0 → 0.26.1

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
 
@@ -3776,7 +3859,7 @@ import {
3776
3859
  } from "docx";
3777
3860
 
3778
3861
  // src/utils/textParser.ts
3779
- import { TextRun, ExternalHyperlink, InternalHyperlink } from "docx";
3862
+ import { TextRun, ExternalHyperlink, InternalHyperlink, Tab } from "docx";
3780
3863
  function escapeRegExp(s) {
3781
3864
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3782
3865
  }
@@ -3879,40 +3962,53 @@ function parseTextWithDecorators(text, baseStyle = {}, options = {}) {
3879
3962
  }
3880
3963
  return runs;
3881
3964
  }
3882
- function createTextRunsWithNewlines(text, baseStyle, options, overrideStyle) {
3883
- const runs = [];
3884
- const lines = text.split("\n");
3885
- for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
3886
- const line = lines[lineIndex];
3887
- const needsLineBreak = lineIndex > 0;
3888
- if (line || needsLineBreak) {
3889
- const runStyle = {
3890
- bold: overrideStyle?.bold ?? baseStyle.bold,
3891
- italics: overrideStyle?.italics ?? baseStyle.italics
3892
- };
3893
- const commonProps = {
3894
- ...baseStyle.font && { font: baseStyle.font },
3895
- ...baseStyle.size && { size: baseStyle.size },
3896
- ...baseStyle.color && {
3897
- color: runStyle.bold && options.boldColor ? options.boldColor : baseStyle.color
3898
- },
3899
- ...runStyle.bold !== void 0 && { bold: runStyle.bold },
3900
- ...runStyle.italics !== void 0 && { italics: runStyle.italics },
3901
- ...baseStyle.underline && { underline: baseStyle.underline },
3902
- ...baseStyle.language && { language: baseStyle.language }
3903
- };
3904
- const wholeRunNoProof = baseStyle.noProof === true;
3905
- const wordsForLine = wholeRunNoProof ? void 0 : options.noProofWords;
3906
- let firstSegment = true;
3907
- const lineRuns = splitByNoProofWords(
3908
- line,
3965
+ function buildRunCommonProps(baseStyle, overrides) {
3966
+ const bold = overrides?.bold ?? baseStyle.bold;
3967
+ const italics = overrides?.italics ?? baseStyle.italics;
3968
+ return {
3969
+ ...baseStyle.font && { font: baseStyle.font },
3970
+ ...baseStyle.size && { size: baseStyle.size },
3971
+ ...baseStyle.color && {
3972
+ color: bold && overrides?.boldColor ? overrides.boldColor : baseStyle.color
3973
+ },
3974
+ ...bold !== void 0 && { bold },
3975
+ ...italics !== void 0 && { italics },
3976
+ ...baseStyle.underline && { underline: baseStyle.underline },
3977
+ ...baseStyle.scale && { scale: baseStyle.scale },
3978
+ ...baseStyle.characterSpacing && {
3979
+ characterSpacing: baseStyle.characterSpacing
3980
+ },
3981
+ ...baseStyle.language && { language: baseStyle.language }
3982
+ };
3983
+ }
3984
+ function buildLineRuns(line, commonProps, opts) {
3985
+ const { needsLineBreak, noProof } = opts;
3986
+ const wholeRunNoProof = noProof === true;
3987
+ const wordsForLine = wholeRunNoProof ? void 0 : opts.noProofWords;
3988
+ let firstSegment = true;
3989
+ const lineRuns = [];
3990
+ const tabSegments = line.split(" ");
3991
+ tabSegments.forEach((tabSegment, tabIndex) => {
3992
+ if (tabIndex > 0) {
3993
+ lineRuns.push(
3994
+ new TextRun({
3995
+ children: [new Tab()],
3996
+ ...commonProps,
3997
+ ...needsLineBreak && firstSegment && { break: 1 }
3998
+ })
3999
+ );
4000
+ firstSegment = false;
4001
+ }
4002
+ if (!tabSegment && tabSegments.length > 1) return;
4003
+ lineRuns.push(
4004
+ ...splitByNoProofWords(
4005
+ tabSegment,
3909
4006
  (segment, matched) => {
3910
- const segNoProof = matched || wholeRunNoProof;
3911
4007
  const run = new TextRun({
3912
4008
  text: segment,
3913
4009
  ...commonProps,
3914
- ...(matched || baseStyle.noProof !== void 0) && {
3915
- noProof: segNoProof
4010
+ ...(matched || noProof !== void 0) && {
4011
+ noProof: matched || wholeRunNoProof
3916
4012
  },
3917
4013
  ...needsLineBreak && firstSegment && { break: 1 }
3918
4014
  });
@@ -3920,12 +4016,36 @@ function createTextRunsWithNewlines(text, baseStyle, options, overrideStyle) {
3920
4016
  return run;
3921
4017
  },
3922
4018
  wordsForLine
4019
+ )
4020
+ );
4021
+ });
4022
+ return lineRuns;
4023
+ }
4024
+ function buildTextRuns(text, commonProps, opts = {}) {
4025
+ const runs = [];
4026
+ const lines = text.split("\n");
4027
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
4028
+ const line = lines[lineIndex];
4029
+ const needsLineBreak = lineIndex > 0;
4030
+ if (line || needsLineBreak) {
4031
+ runs.push(
4032
+ ...buildLineRuns(line, commonProps, { needsLineBreak, ...opts })
3923
4033
  );
3924
- runs.push(...lineRuns);
3925
4034
  }
3926
4035
  }
3927
4036
  return runs;
3928
4037
  }
4038
+ function createTextRunsWithNewlines(text, baseStyle, options, overrideStyle) {
4039
+ return buildTextRuns(
4040
+ text,
4041
+ buildRunCommonProps(baseStyle, {
4042
+ bold: overrideStyle?.bold,
4043
+ italics: overrideStyle?.italics,
4044
+ boldColor: options.boldColor
4045
+ }),
4046
+ { noProof: baseStyle.noProof, noProofWords: options.noProofWords }
4047
+ );
4048
+ }
3929
4049
  function parseTextWithHyperlinks(text, baseStyle = {}, options = {}) {
3930
4050
  const normalizedText = normalizeUnicodeText(text);
3931
4051
  const runs = [];
@@ -4142,44 +4262,10 @@ function processTextWithPlaceholders(text, baseStyle = {}, context = {}, noProof
4142
4262
  return result;
4143
4263
  }
4144
4264
  function createTextRunsWithNewlines2(text, baseStyle, noProofWords) {
4145
- const runs = [];
4146
- const lines = text.split("\n");
4147
- for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
4148
- const line = lines[lineIndex];
4149
- const needsLineBreak = lineIndex > 0;
4150
- if (line || needsLineBreak) {
4151
- const commonProps = {
4152
- font: baseStyle.font,
4153
- size: baseStyle.size,
4154
- color: baseStyle.color,
4155
- bold: baseStyle.bold,
4156
- italics: baseStyle.italics,
4157
- underline: baseStyle.underline,
4158
- ...baseStyle.language && { language: baseStyle.language }
4159
- };
4160
- const wholeRunNoProof = baseStyle.noProof === true;
4161
- const wordsForLine = wholeRunNoProof ? void 0 : noProofWords;
4162
- 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
- });
4174
- firstSegment = false;
4175
- return run;
4176
- },
4177
- wordsForLine
4178
- );
4179
- runs.push(...lineRuns);
4180
- }
4181
- }
4182
- return runs;
4265
+ return buildTextRuns(text, buildRunCommonProps(baseStyle), {
4266
+ noProof: baseStyle.noProof,
4267
+ noProofWords
4268
+ });
4183
4269
  }
4184
4270
  function initializeBuiltinPlaceholders() {
4185
4271
  PlaceholderRegistry.register("PAGE", (context) => {
@@ -4330,12 +4416,12 @@ function componentHasRevision(component) {
4330
4416
  }
4331
4417
 
4332
4418
  // src/core/cached-render.ts
4333
- import { createHash as createHash2 } from "crypto";
4419
+ import { createHash as createHash3 } from "crypto";
4334
4420
  var componentCache = null;
4335
4421
  var cacheKeyGen = null;
4336
4422
  function createThemeHash(theme) {
4337
4423
  const themeString = JSON.stringify(theme);
4338
- return createHash2("sha256").update(themeString).digest("hex").substring(0, 8);
4424
+ return createHash3("sha256").update(themeString).digest("hex").substring(0, 8);
4339
4425
  }
4340
4426
  function initializeComponentCache(cache) {
4341
4427
  if (cache) {
@@ -4572,6 +4658,12 @@ function createText(content, theme, themeName, options = {}) {
4572
4658
  ...options.underline !== void 0 && {
4573
4659
  underline: options.underline ? { type: "single" } : void 0
4574
4660
  },
4661
+ // Character width scaling in percent (w:w)
4662
+ ...options.scale && { scale: options.scale },
4663
+ // Letter tracking (w:spacing, twentieths of a point; negative = condensed)
4664
+ ...options.characterSpacing && {
4665
+ characterSpacing: options.characterSpacing.type === "condensed" ? -options.characterSpacing.value : options.characterSpacing.value
4666
+ },
4575
4667
  // Proofing: per-run language and/or no-proof. Omitting language lets the
4576
4668
  // run inherit the document default set on docDefaults.
4577
4669
  ...options.language && { language: { value: options.language } },
@@ -4596,7 +4688,7 @@ function createText(content, theme, themeName, options = {}) {
4596
4688
  }
4597
4689
  } else {
4598
4690
  const textRuns = parseTextWithDecorators(normalizedContent, baseTextStyle, {
4599
- boldColor: options.boldColor,
4691
+ boldColor: options.boldColor ? resolveColor(options.boldColor, theme) : void 0,
4600
4692
  enableHyperlinks: true,
4601
4693
  noProofWords: resolveNoProofWords(theme, options.noProofWords)
4602
4694
  });
@@ -4628,7 +4720,9 @@ function createText(content, theme, themeName, options = {}) {
4628
4720
  },
4629
4721
  ...frameOptions && { frame: frameOptions },
4630
4722
  ...options.keepNext !== void 0 && { keepNext: options.keepNext },
4631
- ...options.keepLines !== void 0 && { keepLines: options.keepLines }
4723
+ ...options.keepLines !== void 0 && { keepLines: options.keepLines },
4724
+ ...options.indent && { indent: options.indent },
4725
+ ...options.tabStops && options.tabStops.length > 0 && { tabStops: options.tabStops }
4632
4726
  });
4633
4727
  }
4634
4728
  function mapFrameOptions(floating, theme, themeName) {
@@ -4734,6 +4828,12 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4734
4828
  ...options.underline !== void 0 && {
4735
4829
  underline: options.underline ? { type: "single" } : void 0
4736
4830
  },
4831
+ // Character width scaling in percent (w:w)
4832
+ ...options.scale && { scale: options.scale },
4833
+ // Letter tracking (w:spacing, twentieths of a point; negative = condensed)
4834
+ ...options.characterSpacing && {
4835
+ characterSpacing: options.characterSpacing.type === "condensed" ? -options.characterSpacing.value : options.characterSpacing.value
4836
+ },
4737
4837
  // Proofing: per-run language and/or no-proof (see createText).
4738
4838
  ...options.language && { language: { value: options.language } },
4739
4839
  ...options.noProof !== void 0 && { noProof: options.noProof }
@@ -4774,7 +4874,7 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4774
4874
  const headingTextChildren = [];
4775
4875
  if (hasDecorators) {
4776
4876
  const textRuns = parseTextWithDecorators(normalizedText, baseTextStyle, {
4777
- boldColor: options.boldColor,
4877
+ boldColor: options.boldColor ? resolveColor(options.boldColor, theme) : void 0,
4778
4878
  enableHyperlinks: true,
4779
4879
  noProofWords: headingNoProofWords
4780
4880
  });
@@ -4791,7 +4891,7 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4791
4891
  } else {
4792
4892
  if (hasDecorators) {
4793
4893
  const textRuns = parseTextWithDecorators(normalizedText, baseTextStyle, {
4794
- boldColor: options.boldColor,
4894
+ boldColor: options.boldColor ? resolveColor(options.boldColor, theme) : void 0,
4795
4895
  enableHyperlinks: true,
4796
4896
  noProofWords: headingNoProofWords
4797
4897
  });
@@ -4807,7 +4907,8 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4807
4907
  // Only override spacing if explicitly provided
4808
4908
  spacing: hasExplicitSpacing ? spacing : void 0,
4809
4909
  ...options.keepNext !== void 0 && { keepNext: options.keepNext },
4810
- ...options.keepLines !== void 0 && { keepLines: options.keepLines }
4910
+ ...options.keepLines !== void 0 && { keepLines: options.keepLines },
4911
+ ...options.indent && { indent: options.indent }
4811
4912
  });
4812
4913
  }
4813
4914
  async function createImage(path3, theme, themeName, options = {}) {
@@ -5799,6 +5900,8 @@ function renderHeadingComponent(component, theme, themeName) {
5799
5900
  fontWeight: config.font?.fontWeight,
5800
5901
  italic: config.font?.italic,
5801
5902
  underline: config.font?.underline,
5903
+ scale: config.font?.scale,
5904
+ characterSpacing: config.font?.characterSpacing,
5802
5905
  // Proofing: local language override + no-proof toggle + known-words list
5803
5906
  language: config.language,
5804
5907
  noProof: config.noProof,
@@ -5806,6 +5909,8 @@ function renderHeadingComponent(component, theme, themeName) {
5806
5909
  // Pagination control
5807
5910
  keepNext: config.keepNext,
5808
5911
  keepLines: config.keepLines,
5912
+ // Paragraph indentation (w:ind) in twips
5913
+ indent: config.indent,
5809
5914
  // Bookmark ID for internal linking
5810
5915
  bookmarkId,
5811
5916
  // Tracked-change segments (rendered as native Word revisions)
@@ -6086,6 +6191,8 @@ function renderParagraphComponent(component, theme, themeName) {
6086
6191
  fontWeight: resolvedConfig.font?.fontWeight,
6087
6192
  italic: resolvedConfig.font?.italic,
6088
6193
  underline: resolvedConfig.font?.underline,
6194
+ scale: resolvedConfig.font?.scale,
6195
+ characterSpacing: resolvedConfig.font?.characterSpacing,
6089
6196
  // Proofing: local language override + no-proof toggle + known-words list
6090
6197
  language: resolvedConfig.language,
6091
6198
  noProof: resolvedConfig.noProof,
@@ -6098,6 +6205,10 @@ function renderParagraphComponent(component, theme, themeName) {
6098
6205
  keepNext: resolvedConfig.keepNext,
6099
6206
  // Pass keepLines property
6100
6207
  keepLines: resolvedConfig.keepLines,
6208
+ // Paragraph indentation (w:ind) in twips
6209
+ indent: resolvedConfig.indent,
6210
+ // Tab stops (w:tabs); \t characters in text jump to these positions
6211
+ tabStops: resolvedConfig.tabStops,
6101
6212
  // Pass bookmark ID for internal linking
6102
6213
  bookmarkId: resolvedConfig.id,
6103
6214
  // Tracked-change segments (rendered as native Word revisions)
@@ -7947,32 +8058,25 @@ function createBuilderImpl(state) {
7947
8058
  }
7948
8059
  };
7949
8060
  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
8061
+ const {
8062
+ document: modedRoot,
8063
+ theme: modedTheme,
8064
+ themeName
8065
+ } = resolveThemeContext(internalDocument, {
8066
+ customThemes: state.customThemes,
8067
+ fonts: state.fonts,
8068
+ warnings,
8069
+ resolveNamedTheme: resolveDocumentTheme
7956
8070
  });
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
8071
  const processed = await processDocumentComponents(
7968
- mode.doc.children || [],
8072
+ modedRoot.children || [],
7969
8073
  preserveSet,
7970
8074
  warnings,
7971
8075
  modedTheme,
7972
8076
  validateEmitted
7973
8077
  );
7974
8078
  const processedDocument = {
7975
- ...mode.doc,
8079
+ ...modedRoot,
7976
8080
  children: processed.standard
7977
8081
  };
7978
8082
  const [modedDoc] = normalizeDocument(processedDocument);
@@ -7993,7 +8097,7 @@ function createBuilderImpl(state) {
7993
8097
  bypassCache: !state.enableCache
7994
8098
  });
7995
8099
  const preservedDefinition = preserveSet ? {
7996
- ...mode.doc,
8100
+ ...modedRoot,
7997
8101
  children: processed.preserved
7998
8102
  } : void 0;
7999
8103
  return {