@json-to-office/core-docx 7.5.1 → 7.6.4

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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The `embed` export mode's share of the DOCX pipeline: whether this render
3
+ * embeds at all, which faces it embeds, and the one summary warning.
4
+ *
5
+ * Face selection itself — slots, formats, licence bits — lives in
6
+ * `@json-to-office/shared/fonts/node` (`toEmbeddedFontFaces`), beside the
7
+ * other encoders of resolved fonts. What stays here is what only the DOCX
8
+ * pipeline knows: which renderer will write the package, and what the
9
+ * document references, which is what the summary has to answer for.
10
+ *
11
+ * Nothing here imports `docx`: the faces travel to the adapter as a render
12
+ * option (`DocxRenderOptions.embeddedFonts`), and only the docx.js adapter
13
+ * reads it.
14
+ */
15
+ import type { EmbeddedFontFace, FontRuntimeOpts, GenerationWarning, ResolvedFont } from '@json-to-office/shared';
16
+ import { type DocxRendererId } from '../renderers/types';
17
+ /**
18
+ * Whether this render embeds fonts: `embed` was asked for, and the renderer
19
+ * that will write the package can do it.
20
+ *
21
+ * `office-open` cannot yet. Its published docx embedding names the part after
22
+ * the family, which is the defect docx.js fixed in 9.7.0, writes the regular
23
+ * face only and leaves `embedTrueTypeFonts` off, so a request for it falls
24
+ * back to `custom` — references kept, nothing embedded — and says so rather
25
+ * than shipping a package Word repairs or drops the fonts from on re-save.
26
+ */
27
+ export declare function embedsFonts(fonts: FontRuntimeOpts | undefined, renderer: DocxRendererId | undefined, warnings: GenerationWarning[]): boolean;
28
+ /**
29
+ * The faces to embed, with one `FONT_MODE_EMBED` summary for the document.
30
+ *
31
+ * The summary walks the resolved fonts rather than the report alone: the
32
+ * resolver hands back one entry per family the document and its theme
33
+ * reference — the same set `FONT_MODE_CUSTOM` is gated on — so a non-safe
34
+ * family that resolved to nothing still gets its line, instead of vanishing
35
+ * from a message that claims to account for the document's fonts. A document
36
+ * that references only safe fonts gets no summary, as in custom mode.
37
+ */
38
+ export declare function selectEmbeddedFonts(resolved: readonly ResolvedFont[], warnings: GenerationWarning[]): EmbeddedFontFace[];
39
+ //# sourceMappingURL=fontEmbedding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontEmbedding.d.ts","sourceRoot":"","sources":["../../src/core/fontEmbedding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,YAAY,EACb,MAAM,wBAAwB,CAAC;AAMhC,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAY5B;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,GAAG,SAAS,EAClC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,OAAO,CAaT;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,gBAAgB,EAAE,CAKpB"}
@@ -1,18 +1,20 @@
1
1
  /**
2
- * Shared font-resolution helper used by BOTH entry paths into renderDocument:
2
+ * Font resolution for a DOCX render: validation, SAFE_FONTS awareness, Google
3
+ * Fonts materialization, and the `onResolved` side-channel consumed by the
4
+ * LibreOffice preview font stager.
3
5
  *
4
- * - generator.ts → generateDocumentWithCustomThemes (non-plugin)
5
- * - plugin/createDocumentGenerator.ts → generate (plugin-aware)
6
- *
7
- * Keeping it in one place ensures the two paths behave identically around
8
- * validation, SAFE_FONTS awareness, Google Fonts materialization, and the
9
- * `onResolved` side-channel consumed by the LibreOffice preview font stager.
6
+ * One call site, `compileDocumentScoped` in `generateFromIr.ts`, which both
7
+ * public entry paths — `generator.ts` and the plugin-aware
8
+ * `plugin/createDocumentGenerator.ts` — reach, so they cannot disagree about
9
+ * any of the above.
10
10
  *
11
11
  * Materialization has two triggers: an `onResolved` listener, or the
12
12
  * caller's `forceMaterialize` flag (set when the document contains a
13
13
  * `visual`, whose PNG is produced by an out-of-process LibreOffice that
14
- * needs real font files). Both entry paths return the resolved fonts so the
15
- * caller can forward them to the rasterizer.
14
+ * needs real font files, a `highcharts`, whose export server needs them
15
+ * likewise, or when `embed` mode will write them into the package). The
16
+ * resolved fonts are returned so the caller can forward them to whichever
17
+ * of those needs them.
16
18
  */
17
19
  import type { FontRuntimeOpts, ResolvedFont, GenerationWarning } from '@json-to-office/shared';
18
20
  import type { ThemeConfig } from '../styles';
@@ -24,6 +26,8 @@ export declare function resolveDocumentFonts(document: ReportComponentDefinition
24
26
  * whose PNG is rendered by an out-of-process LibreOffice that needs the
25
27
  * actual font files — there is no listener on the plain CLI path, so
26
28
  * without this a visual-bearing build silently renders with fallbacks.
29
+ * `embed` mode sets it for the same reason: without it there would be
30
+ * nothing to embed.
27
31
  */
28
32
  forceMaterialize?: boolean): Promise<ResolvedFont[]>;
29
33
  //# sourceMappingURL=fontResolution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fontResolution.d.ts","sourceRoot":"","sources":["../../src/core/fontResolution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAchC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAE1D,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,yBAAyB,EACnC,KAAK,EAAE,WAAW,EAClB,KAAK,CAAC,EAAE,eAAe,EACvB,QAAQ,CAAC,EAAE,iBAAiB,EAAE;AAC9B;;;;;;GAMG;AACH,gBAAgB,UAAQ,GACvB,OAAO,CAAC,YAAY,EAAE,CAAC,CAkFzB"}
1
+ {"version":3,"file":"fontResolution.d.ts","sourceRoot":"","sources":["../../src/core/fontResolution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAehC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAE1D,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,yBAAyB,EACnC,KAAK,EAAE,WAAW,EAClB,KAAK,CAAC,EAAE,eAAe,EACvB,QAAQ,CAAC,EAAE,iBAAiB,EAAE;AAC9B;;;;;;;;GAQG;AACH,gBAAgB,UAAQ,GACvB,OAAO,CAAC,YAAY,EAAE,CAAC,CAyFzB"}
@@ -15,7 +15,7 @@
15
15
  /// <reference types="node" />
16
16
  /// <reference types="node" />
17
17
  import type { GenerationWarning, ServicesConfig } from '@json-to-office/shared';
18
- import type { FontRuntimeOpts } from '@json-to-office/shared';
18
+ import type { EmbeddedFontFace, FontRuntimeOpts } from '@json-to-office/shared';
19
19
  import { compileDocument, type UnsupportedComponent } from '../ir/compiler';
20
20
  import type { DocxIR } from '../ir/types';
21
21
  import type { DocxRendererId } from '../renderers/types';
@@ -76,6 +76,13 @@ export interface CompiledDocx {
76
76
  warnings: GenerationWarning[];
77
77
  required: ReturnType<typeof compileDocument>['required'];
78
78
  unsupported: UnsupportedComponent[];
79
+ /**
80
+ * The font faces `embed` mode writes into the package; empty in every other
81
+ * mode. Kept beside the IR rather than in it: they are a resource of this
82
+ * render, chosen for the renderer that will write it, not something the
83
+ * document says, and the IR stays the same in every mode.
84
+ */
85
+ embeddedFonts: EmbeddedFontFace[];
79
86
  }
80
87
  /**
81
88
  * Compile a report definition to DocxIR without rendering it.
@@ -1 +1 @@
1
- {"version":3,"file":"generateFromIr.d.ts","sourceRoot":"","sources":["../../src/core/generateFromIr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;;;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAU9D,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAKlE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,gBAAgB,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,SAAgB,IAAI,0BAA0B;IAC9C,SAAgB,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;gBAEhD,UAAU,EAAE,SAAS,oBAAoB,EAAE;CAiBxD;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,QAAQ,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IACzD,WAAW,EAAE,oBAAoB,EAAE,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,uBAA4B,EACrC,SAAS,CAAC,EAAE,iBAAiB,EAAE,GAC9B,OAAO,CAAC,YAAY,CAAC,CAOvB;AA4GD,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,sBAAsB,CAAC,CAcjC"}
1
+ {"version":3,"file":"generateFromIr.d.ts","sourceRoot":"","sources":["../../src/core/generateFromIr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;;;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAWhF,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAKlE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,gBAAgB,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,SAAgB,IAAI,0BAA0B;IAC9C,SAAgB,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;gBAEhD,UAAU,EAAE,SAAS,oBAAoB,EAAE;CAiBxD;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,QAAQ,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IACzD,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC;;;;;OAKG;IACH,aAAa,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,uBAA4B,EACrC,SAAS,CAAC,EAAE,iBAAiB,EAAE,GAC9B,OAAO,CAAC,YAAY,CAAC,CAOvB;AAsHD,0EAA0E;AAC1E,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,sBAAsB,CAAC,CAcjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"generationContext.d.ts","sourceRoot":"","sources":["../../src/core/generationContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAMhC,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC;IAC9C,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,CAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACzC,QAAQ,EAAE,OAAO,KACd,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,QAAQ,EAAE,yBAAyB,CAAC;IACpC,KAAK,EAAE,WAAW,CAAC;IACnB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,yBAAyB,EACrC,OAAO,GAAE,mBAAwB,GAChC,sBAAsB,CA+DxB"}
1
+ {"version":3,"file":"generationContext.d.ts","sourceRoot":"","sources":["../../src/core/generationContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAMhC,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC;IAC9C,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,CAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,iBAAiB,EAAE,GAAG,SAAS,EACzC,QAAQ,EAAE,OAAO,KACd,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,QAAQ,EAAE,yBAAyB,CAAC;IACpC,KAAK,EAAE,WAAW,CAAC;IACnB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,yBAAyB,EACrC,OAAO,GAAE,mBAAwB,GAChC,sBAAsB,CAgExB"}
package/dist/index.js CHANGED
@@ -14,6 +14,46 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
+ // src/utils/generationContext.ts
18
+ import { AsyncLocalStorage } from "async_hooks";
19
+ import { isAbsolute, resolve } from "path";
20
+ function runWithBaseDir(baseDir, callback) {
21
+ return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
22
+ }
23
+ function getBaseDir() {
24
+ return baseDirStorage.getStore();
25
+ }
26
+ function resolveFromBaseDir(filePath) {
27
+ const base = baseDirStorage.getStore();
28
+ if (!base || isAbsolute(filePath)) return filePath;
29
+ return resolve(base, filePath);
30
+ }
31
+ function runWithWarnings(warnings, callback) {
32
+ return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
33
+ }
34
+ function reportWarning(component, code, message, context) {
35
+ const warnings = warningsStorage.getStore();
36
+ if (warnings) {
37
+ warnings.push({
38
+ component,
39
+ message,
40
+ severity: "warning",
41
+ context: { code, ...context }
42
+ });
43
+ return;
44
+ }
45
+ console.warn(`[json-to-docx] [${code}] ${message}`);
46
+ }
47
+ var generationDateStorage, baseDirStorage, warningsStorage;
48
+ var init_generationContext = __esm({
49
+ "src/utils/generationContext.ts"() {
50
+ "use strict";
51
+ generationDateStorage = new AsyncLocalStorage();
52
+ baseDirStorage = new AsyncLocalStorage();
53
+ warningsStorage = new AsyncLocalStorage();
54
+ }
55
+ });
56
+
17
57
  // src/themes/defaults.ts
18
58
  import { designColors } from "@json-to-office/shared";
19
59
  function ensureThemeDefaults(theme) {
@@ -2447,43 +2487,6 @@ var init_widthUtils = __esm({
2447
2487
  }
2448
2488
  });
2449
2489
 
2450
- // src/utils/generationContext.ts
2451
- import { AsyncLocalStorage } from "async_hooks";
2452
- import { isAbsolute, resolve } from "path";
2453
- function runWithBaseDir(baseDir, callback) {
2454
- return baseDir === void 0 ? callback() : baseDirStorage.run(resolve(baseDir), callback);
2455
- }
2456
- function resolveFromBaseDir(filePath) {
2457
- const base = baseDirStorage.getStore();
2458
- if (!base || isAbsolute(filePath)) return filePath;
2459
- return resolve(base, filePath);
2460
- }
2461
- function runWithWarnings(warnings, callback) {
2462
- return warnings === void 0 ? callback() : warningsStorage.run(warnings, callback);
2463
- }
2464
- function reportWarning(component, code, message, context) {
2465
- const warnings = warningsStorage.getStore();
2466
- if (warnings) {
2467
- warnings.push({
2468
- component,
2469
- message,
2470
- severity: "warning",
2471
- context: { code, ...context }
2472
- });
2473
- return;
2474
- }
2475
- console.warn(`[json-to-docx] [${code}] ${message}`);
2476
- }
2477
- var generationDateStorage, baseDirStorage, warningsStorage;
2478
- var init_generationContext = __esm({
2479
- "src/utils/generationContext.ts"() {
2480
- "use strict";
2481
- generationDateStorage = new AsyncLocalStorage();
2482
- baseDirStorage = new AsyncLocalStorage();
2483
- warningsStorage = new AsyncLocalStorage();
2484
- }
2485
- });
2486
-
2487
2490
  // src/utils/imageUtils.ts
2488
2491
  import { readFileSync } from "fs";
2489
2492
  import { availableParallelism } from "os";
@@ -4265,6 +4268,8 @@ var init_normalizeTextCase = __esm({
4265
4268
 
4266
4269
  // src/utils/packageDocument.ts
4267
4270
  import AdmZip from "adm-zip";
4271
+ import { createHash as createHash3 } from "crypto";
4272
+ import { posix } from "path";
4268
4273
  function resolveGenerationDate(options) {
4269
4274
  if (options?.generatedAt !== void 0) {
4270
4275
  const date = new Date(options.generatedAt);
@@ -4332,6 +4337,70 @@ function canonicalizeRelationshipIds(zip) {
4332
4337
  if (entry) zip.updateFile(entry, Buffer.from(update.xml, "utf8"));
4333
4338
  }
4334
4339
  }
4340
+ function xorFontKey(bytes, guid) {
4341
+ const key = Buffer.from(guid.replace(/-/g, ""), "hex").reverse();
4342
+ const out = Buffer.from(bytes);
4343
+ for (let i = 0; i < Math.min(32, out.length); i++) out[i] ^= key[i % 16];
4344
+ return out;
4345
+ }
4346
+ function canonicalizeEmbeddedFontKeys(zip) {
4347
+ const table2 = zip.getEntry("word/fontTable.xml");
4348
+ if (!table2) return;
4349
+ const xml = table2.getData().toString("utf8");
4350
+ const embeds = [...xml.matchAll(FONT_EMBED)].flatMap(([element]) => {
4351
+ const id = /\br:id="([^"]+)"/.exec(element)?.[1];
4352
+ const key = /\bw:fontKey="\{([0-9A-Fa-f-]{36})\}"/.exec(element)?.[1];
4353
+ return id && key ? [{ id, key }] : [];
4354
+ });
4355
+ if (embeds.length === 0) return;
4356
+ const rels = zip.getEntry("word/_rels/fontTable.xml.rels")?.getData().toString("utf8") ?? "";
4357
+ const targets = /* @__PURE__ */ new Map();
4358
+ for (const [relationship] of rels.matchAll(/<Relationship\b[^>]*>/g)) {
4359
+ const id = /\bId="([^"]+)"/.exec(relationship)?.[1];
4360
+ const target = /\bTarget="([^"]+)"/.exec(relationship)?.[1];
4361
+ if (id && target) targets.set(id, target);
4362
+ }
4363
+ const rekey = /* @__PURE__ */ new Map();
4364
+ const parts = [];
4365
+ for (const { id, key } of embeds) {
4366
+ const target = targets.get(id);
4367
+ if (!target) continue;
4368
+ const name = target.startsWith("/") ? target.slice(1) : posix.normalize(posix.join("word", target));
4369
+ const part = zip.getEntry(name);
4370
+ if (!part) continue;
4371
+ const font = xorFontKey(part.getData(), key);
4372
+ const hex = createHash3("sha256").update(font).digest("hex").slice(0, 32).toUpperCase();
4373
+ const fresh = [
4374
+ hex.slice(0, 8),
4375
+ hex.slice(8, 12),
4376
+ hex.slice(12, 16),
4377
+ hex.slice(16, 20),
4378
+ hex.slice(20)
4379
+ ].join("-");
4380
+ rekey.set(key.toUpperCase(), fresh);
4381
+ parts.push({ name, data: xorFontKey(font, fresh) });
4382
+ }
4383
+ if (rekey.size === 0) return;
4384
+ for (const { name, data } of parts) {
4385
+ const entry = zip.getEntry(name);
4386
+ if (entry) zip.updateFile(entry, data);
4387
+ }
4388
+ const fontTable = zip.getEntry("word/fontTable.xml");
4389
+ if (!fontTable) return;
4390
+ zip.updateFile(
4391
+ fontTable,
4392
+ Buffer.from(
4393
+ xml.replace(
4394
+ /\bw:fontKey="\{([0-9A-Fa-f-]{36})\}"/g,
4395
+ (whole, key) => {
4396
+ const fresh = rekey.get(key.toUpperCase());
4397
+ return fresh ? `w:fontKey="{${fresh}}"` : whole;
4398
+ }
4399
+ ),
4400
+ "utf8"
4401
+ )
4402
+ );
4403
+ }
4335
4404
  function canonicalizeEmbeddedPackages(zip, generatedAt, depth = 1) {
4336
4405
  const names = zip.getEntries().map((entry) => entry.entryName).filter((name) => EMBEDDED_OFFICE_PACKAGE.test(name));
4337
4406
  let changed = false;
@@ -4384,6 +4453,7 @@ function canonicalizeDocxBuffer(buffer, generatedAt = DEFAULT_GENERATION_DATE) {
4384
4453
  normalizeTextCase(zip);
4385
4454
  canonicalizeRelationshipIds(zip);
4386
4455
  canonicalizeEmbeddedPackages(zip, generatedAt);
4456
+ canonicalizeEmbeddedFontKeys(zip);
4387
4457
  const isoTimestamp = generatedAt.toISOString();
4388
4458
  const coreProperties3 = zip.getEntry("docProps/core.xml");
4389
4459
  if (coreProperties3) {
@@ -4400,13 +4470,14 @@ function normalizeDocxCaseBuffer(buffer) {
4400
4470
  const zip = new AdmZip(buffer);
4401
4471
  return normalizeTextCase(zip) ? zip.toBuffer() : buffer;
4402
4472
  }
4403
- var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID, CORE_TIMESTAMP, EMBEDDED_OFFICE_PACKAGE, MAX_EMBEDDED_DEPTH;
4473
+ var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID, FONT_EMBED, CORE_TIMESTAMP, EMBEDDED_OFFICE_PACKAGE, MAX_EMBEDDED_DEPTH;
4404
4474
  var init_packageDocument = __esm({
4405
4475
  "src/utils/packageDocument.ts"() {
4406
4476
  "use strict";
4407
4477
  init_normalizeTextCase();
4408
4478
  DEFAULT_GENERATION_DATE = /* @__PURE__ */ new Date("2000-01-01T00:00:00.000Z");
4409
4479
  STABLE_RELATIONSHIP_ID = /^rId\d+$/;
4480
+ FONT_EMBED = /<w:embed(?:Regular|Bold|Italic|BoldItalic)\b[^>]*>/g;
4410
4481
  CORE_TIMESTAMP = /(<dcterms:(?:created|modified)\b[^>]*>)[^<]*(<\/dcterms:(?:created|modified)>)/g;
4411
4482
  EMBEDDED_OFFICE_PACKAGE = /\.(?:docx|pptx|xlsx|xlsm)$/i;
4412
4483
  MAX_EMBEDDED_DEPTH = 3;
@@ -5007,7 +5078,12 @@ function createDocxJsRenderer() {
5007
5078
  });
5008
5079
  }
5009
5080
  const pageNumbers = needsPageNumbers(ir) ? await createPageNumberEstimator(ir) : void 0;
5010
- const document = buildDocument(ir, resources, pageNumbers);
5081
+ const document = buildDocument(
5082
+ ir,
5083
+ resources,
5084
+ pageNumbers,
5085
+ renderOptions?.embeddedFonts
5086
+ );
5011
5087
  const packed = await Packer.toBuffer(document);
5012
5088
  const fixed = fixFloatingImageIdsInBuffer(packed);
5013
5089
  const repaired = markers ? repairDrawingGroupsInBuffer(fixed, markers) : fixed;
@@ -5025,10 +5101,17 @@ function createDocxJsRenderer() {
5025
5101
  }
5026
5102
  };
5027
5103
  }
5028
- function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers) {
5104
+ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers, embeddedFonts) {
5029
5105
  return new Document({
5030
5106
  ...pageNumbers ? { pageNumbers } : {},
5031
5107
  styles: emitStyles(ir.styles),
5108
+ // word/fonts/font<N>.odttf, their `w:embed*` entries in the font table and
5109
+ // `w:embedTrueTypeFonts` in the settings, all from this one key. Passed
5110
+ // only when a face embeds, so a document in any other mode is built from
5111
+ // exactly the options it always was. `docx/layout` measures the text of an
5112
+ // embedded family from these files, so a contents field's page numbers
5113
+ // need nothing more.
5114
+ ...embeddedFonts && embeddedFonts.length > 0 ? { fonts: embeddedFonts.map(fontOptions) } : {},
5032
5115
  ...ir.theme ? { theme: themeOptions(ir.theme) } : {},
5033
5116
  sections: ir.sections.map((section2) => sectionOptions(section2, resources)),
5034
5117
  ...coreProperties(ir),
@@ -5059,6 +5142,15 @@ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers) {
5059
5142
  } : {}
5060
5143
  });
5061
5144
  }
5145
+ function fontOptions(face) {
5146
+ return {
5147
+ name: face.family,
5148
+ data: face.regular,
5149
+ ...face.bold ? { bold: face.bold } : {},
5150
+ ...face.italic ? { italic: face.italic } : {},
5151
+ ...face.boldItalic ? { boldItalic: face.boldItalic } : {}
5152
+ };
5153
+ }
5062
5154
  function themeOptions(theme) {
5063
5155
  const colors = {};
5064
5156
  for (const [slot, color] of Object.entries(theme.colors)) {
@@ -6979,6 +7071,7 @@ import {
6979
7071
  import { assertRendererSupports } from "@json-to-office/shared/rendering";
6980
7072
 
6981
7073
  // src/core/fontResolution.ts
7074
+ init_generationContext();
6982
7075
  import {
6983
7076
  collectFontNamesFromDocx,
6984
7077
  validateFontReferences,
@@ -7032,8 +7125,14 @@ async function resolveDocumentFonts(document, theme, fonts, warnings, forceMater
7032
7125
  if (!fonts?.onResolved && !forceMaterialize) return [];
7033
7126
  const registry2 = new FontRegistry({
7034
7127
  // Spread keeps baseDir/googleFonts/mode/substitution intact for
7035
- // materializeSource; extraEntries carries the merged registry.
7036
- opts: { ...fonts, extraEntries: registryEntries },
7128
+ // materializeSource; extraEntries carries the merged registry. Without a
7129
+ // `fonts.baseDir` of its own, kind:"file" sources resolve against the
7130
+ // render's baseDir, as images do — not against process.cwd().
7131
+ opts: {
7132
+ ...fonts,
7133
+ baseDir: fonts?.baseDir ?? getBaseDir(),
7134
+ extraEntries: registryEntries
7135
+ },
7037
7136
  fileLoader: loadFileFontSource,
7038
7137
  variableLoader: fetchVariableFontSource,
7039
7138
  diskCache: fonts?.googleFonts?.cacheDir ? new FontDiskCache(fonts.googleFonts.cacheDir) : void 0
@@ -7054,6 +7153,67 @@ import {
7054
7153
  toRasterizeFontFaces
7055
7154
  } from "@json-to-office/shared/fonts/node";
7056
7155
 
7156
+ // src/core/fontEmbedding.ts
7157
+ import { isSafeFont } from "@json-to-office/shared";
7158
+ import {
7159
+ toEmbeddedFontFaces
7160
+ } from "@json-to-office/shared/fonts/node";
7161
+
7162
+ // src/renderers/types.ts
7163
+ import {
7164
+ DEFAULT_DOCX_RENDERER_ID
7165
+ } from "@json-to-office/shared-docx";
7166
+
7167
+ // src/core/fontEmbedding.ts
7168
+ var EMBEDDING_RENDERER = "docxjs";
7169
+ var warning = (code, message) => ({
7170
+ component: "fontRegistry",
7171
+ message,
7172
+ severity: "warning",
7173
+ context: { code }
7174
+ });
7175
+ function embedsFonts(fonts, renderer, warnings) {
7176
+ if (fonts?.mode !== "embed") return false;
7177
+ const id = renderer ?? DEFAULT_DOCX_RENDERER_ID;
7178
+ if (id === EMBEDDING_RENDERER) return true;
7179
+ warnings.push(
7180
+ warning(
7181
+ "FONT_EMBED_UNSUPPORTED_RENDERER",
7182
+ `Export mode "embed" needs the ${EMBEDDING_RENDERER} renderer; the ${id} renderer does not embed fonts, so references are kept as in custom mode and nothing is embedded.`
7183
+ )
7184
+ );
7185
+ return false;
7186
+ }
7187
+ function selectEmbeddedFonts(resolved, warnings) {
7188
+ const { faces, report } = toEmbeddedFontFaces(resolved, warnings);
7189
+ const summary = embedSummary(resolved, report);
7190
+ if (summary) warnings.push(warning("FONT_MODE_EMBED", summary));
7191
+ return faces;
7192
+ }
7193
+ function embedSummary(resolved, report) {
7194
+ const byFamily = new Map(report.map((r) => [r.family.toLowerCase(), r]));
7195
+ const seen = /* @__PURE__ */ new Set();
7196
+ const lines = [];
7197
+ for (const font of resolved) {
7198
+ const key = font.family.toLowerCase();
7199
+ if (isSafeFont(font.family.trim()) || seen.has(key)) continue;
7200
+ seen.add(key);
7201
+ const entry = byFamily.get(key);
7202
+ if (!entry) {
7203
+ lines.push(`${font.family}: not embedded (unresolved)`);
7204
+ } else if (entry.slots.length === 0) {
7205
+ lines.push(
7206
+ `${font.family}: not embedded (${entry.reason === "no-regular-face" ? "no regular face" : "no TTF/OTF face it may embed"})`
7207
+ );
7208
+ } else {
7209
+ const nearest = entry.nearest.length ? ` (${entry.nearest.map((n) => `${n.slot} \u2190 ${n.weight}`).join(", ")})` : "";
7210
+ lines.push(`${font.family}: ${entry.slots.join(", ")}${nearest}`);
7211
+ }
7212
+ }
7213
+ if (lines.length === 0) return void 0;
7214
+ return `Export mode "embed": ${lines.join("; ")}. Readers without these fonts installed see the embedded faces; any face or family not embedded stays a reference, as in custom mode.`;
7215
+ }
7216
+
7057
7217
  // src/core/prerasterizeVisuals.ts
7058
7218
  import {
7059
7219
  clampVisualDpi,
@@ -7540,7 +7700,7 @@ function assertExportServerAllowed(serverUrl, servicesConfig, warnings) {
7540
7700
  const notice = remoteExportNotice(serverUrl, servicesConfig?.allowRemote);
7541
7701
  if (!notice || !warnings) return;
7542
7702
  const alreadySaid = warnings.some(
7543
- (warning) => warning.context?.code === REMOTE_EXPORT_WARNING && warning.context?.serverUrl === serverUrl
7703
+ (warning2) => warning2.context?.code === REMOTE_EXPORT_WARNING && warning2.context?.serverUrl === serverUrl
7544
7704
  );
7545
7705
  if (alreadySaid) return;
7546
7706
  warnings.push({
@@ -13586,13 +13746,6 @@ function warnOnce(ctx, component, message, code) {
13586
13746
  import {
13587
13747
  RendererRegistry
13588
13748
  } from "@json-to-office/shared/rendering";
13589
-
13590
- // src/renderers/types.ts
13591
- import {
13592
- DEFAULT_DOCX_RENDERER_ID
13593
- } from "@json-to-office/shared-docx";
13594
-
13595
- // src/renderers/registry.ts
13596
13749
  var registry = new RendererRegistry(
13597
13750
  "docx",
13598
13751
  DEFAULT_DOCX_RENDERER_ID
@@ -16240,13 +16393,15 @@ async function compileDocumentScoped(document, options, warnings) {
16240
16393
  const { context } = prepared.model;
16241
16394
  const hasVisual = collectVisualProps(context.document).length > 0;
16242
16395
  const hasHighcharts = containsComponent(context.document, "highcharts");
16396
+ const embedding = embedsFonts(options.fonts, options.renderer, warnings);
16243
16397
  const resolvedFonts = await resolveDocumentFonts(
16244
16398
  context.document,
16245
16399
  context.theme,
16246
16400
  options.fonts,
16247
16401
  warnings,
16248
- hasVisual || hasHighcharts
16402
+ hasVisual || hasHighcharts || embedding
16249
16403
  );
16404
+ const embeddedFonts = embedding ? selectEmbeddedFonts(resolvedFonts, warnings) : [];
16250
16405
  const visualFonts = hasVisual ? toRasterizeFontFaces(resolvedFonts, warnings) : [];
16251
16406
  const chartFonts = hasHighcharts ? toChartFontFaces(resolvedFonts) : [];
16252
16407
  const desugared = await desugarExternals(context.document, {
@@ -16286,7 +16441,8 @@ async function compileDocumentScoped(document, options, warnings) {
16286
16441
  theme: context.theme,
16287
16442
  warnings: compiled.warnings,
16288
16443
  required: compiled.required,
16289
- unsupported: compiled.unsupported
16444
+ unsupported: compiled.unsupported,
16445
+ embeddedFonts
16290
16446
  };
16291
16447
  }
16292
16448
  async function generateBufferViaIr(document, options = {}) {
@@ -16310,6 +16466,7 @@ async function renderScoped(document, options, warnings) {
16310
16466
  ...options.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
16311
16467
  ...options.generatedAt !== void 0 ? { generatedAt: toDate(options.generatedAt) } : {},
16312
16468
  ...options.svgRasterFallback !== void 0 ? { svgRasterFallback: options.svgRasterFallback } : {},
16469
+ ...compiled.embeddedFonts.length > 0 ? { embeddedFonts: compiled.embeddedFonts } : {},
16313
16470
  warnings: compiled.warnings
16314
16471
  });
16315
16472
  return { buffer: Buffer.from(bytes), warnings: compiled.warnings };
@@ -17815,10 +17972,10 @@ function generateWarningsDocument(warnings) {
17815
17972
  }
17816
17973
  ];
17817
17974
  const warningsByComponent = /* @__PURE__ */ new Map();
17818
- for (const warning of warnings) {
17819
- const existing = warningsByComponent.get(warning.component) || [];
17820
- existing.push(warning);
17821
- warningsByComponent.set(warning.component, existing);
17975
+ for (const warning2 of warnings) {
17976
+ const existing = warningsByComponent.get(warning2.component) || [];
17977
+ existing.push(warning2);
17978
+ warningsByComponent.set(warning2.component, existing);
17822
17979
  }
17823
17980
  for (const [
17824
17981
  componentName,
@@ -17832,10 +17989,10 @@ function generateWarningsDocument(warnings) {
17832
17989
  }
17833
17990
  });
17834
17991
  const warningItems = [];
17835
- for (const warning of componentWarnings) {
17836
- let warningText = warning.message;
17837
- if (warning.context && Object.keys(warning.context).length > 0) {
17838
- const contextStr = Object.entries(warning.context).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
17992
+ for (const warning2 of componentWarnings) {
17993
+ let warningText = warning2.message;
17994
+ if (warning2.context && Object.keys(warning2.context).length > 0) {
17995
+ const contextStr = Object.entries(warning2.context).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
17839
17996
  warningText += ` (${contextStr})`;
17840
17997
  }
17841
17998
  warningItems.push(warningText);
@@ -17871,10 +18028,10 @@ function formatWarningsText(warnings) {
17871
18028
  ""
17872
18029
  ];
17873
18030
  const warningsByComponent = /* @__PURE__ */ new Map();
17874
- for (const warning of warnings) {
17875
- const existing = warningsByComponent.get(warning.component) || [];
17876
- existing.push(warning);
17877
- warningsByComponent.set(warning.component, existing);
18031
+ for (const warning2 of warnings) {
18032
+ const existing = warningsByComponent.get(warning2.component) || [];
18033
+ existing.push(warning2);
18034
+ warningsByComponent.set(warning2.component, existing);
17878
18035
  }
17879
18036
  for (const [
17880
18037
  componentName,
@@ -17883,10 +18040,10 @@ function formatWarningsText(warnings) {
17883
18040
  lines.push(`Component: ${componentName}`);
17884
18041
  lines.push("-".repeat(30));
17885
18042
  for (let i = 0; i < componentWarnings.length; i++) {
17886
- const warning = componentWarnings[i];
17887
- lines.push(` ${i + 1}. ${warning.message}`);
17888
- if (warning.context && Object.keys(warning.context).length > 0) {
17889
- lines.push(` Context: ${JSON.stringify(warning.context, null, 2)}`);
18043
+ const warning2 = componentWarnings[i];
18044
+ lines.push(` ${i + 1}. ${warning2.message}`);
18045
+ if (warning2.context && Object.keys(warning2.context).length > 0) {
18046
+ lines.push(` Context: ${JSON.stringify(warning2.context, null, 2)}`);
17890
18047
  }
17891
18048
  }
17892
18049
  lines.push("");