@json-to-office/core-docx 7.5.1 → 7.6.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.
@@ -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;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;;;;;;;;GAQG;AACH,gBAAgB,UAAQ,GACvB,OAAO,CAAC,YAAY,EAAE,CAAC,CAmFzB"}
@@ -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
@@ -4265,6 +4265,8 @@ var init_normalizeTextCase = __esm({
4265
4265
 
4266
4266
  // src/utils/packageDocument.ts
4267
4267
  import AdmZip from "adm-zip";
4268
+ import { createHash as createHash3 } from "crypto";
4269
+ import { posix } from "path";
4268
4270
  function resolveGenerationDate(options) {
4269
4271
  if (options?.generatedAt !== void 0) {
4270
4272
  const date = new Date(options.generatedAt);
@@ -4332,6 +4334,70 @@ function canonicalizeRelationshipIds(zip) {
4332
4334
  if (entry) zip.updateFile(entry, Buffer.from(update.xml, "utf8"));
4333
4335
  }
4334
4336
  }
4337
+ function xorFontKey(bytes, guid) {
4338
+ const key = Buffer.from(guid.replace(/-/g, ""), "hex").reverse();
4339
+ const out = Buffer.from(bytes);
4340
+ for (let i = 0; i < Math.min(32, out.length); i++) out[i] ^= key[i % 16];
4341
+ return out;
4342
+ }
4343
+ function canonicalizeEmbeddedFontKeys(zip) {
4344
+ const table2 = zip.getEntry("word/fontTable.xml");
4345
+ if (!table2) return;
4346
+ const xml = table2.getData().toString("utf8");
4347
+ const embeds = [...xml.matchAll(FONT_EMBED)].flatMap(([element]) => {
4348
+ const id = /\br:id="([^"]+)"/.exec(element)?.[1];
4349
+ const key = /\bw:fontKey="\{([0-9A-Fa-f-]{36})\}"/.exec(element)?.[1];
4350
+ return id && key ? [{ id, key }] : [];
4351
+ });
4352
+ if (embeds.length === 0) return;
4353
+ const rels = zip.getEntry("word/_rels/fontTable.xml.rels")?.getData().toString("utf8") ?? "";
4354
+ const targets = /* @__PURE__ */ new Map();
4355
+ for (const [relationship] of rels.matchAll(/<Relationship\b[^>]*>/g)) {
4356
+ const id = /\bId="([^"]+)"/.exec(relationship)?.[1];
4357
+ const target = /\bTarget="([^"]+)"/.exec(relationship)?.[1];
4358
+ if (id && target) targets.set(id, target);
4359
+ }
4360
+ const rekey = /* @__PURE__ */ new Map();
4361
+ const parts = [];
4362
+ for (const { id, key } of embeds) {
4363
+ const target = targets.get(id);
4364
+ if (!target) continue;
4365
+ const name = target.startsWith("/") ? target.slice(1) : posix.normalize(posix.join("word", target));
4366
+ const part = zip.getEntry(name);
4367
+ if (!part) continue;
4368
+ const font = xorFontKey(part.getData(), key);
4369
+ const hex = createHash3("sha256").update(font).digest("hex").slice(0, 32).toUpperCase();
4370
+ const fresh = [
4371
+ hex.slice(0, 8),
4372
+ hex.slice(8, 12),
4373
+ hex.slice(12, 16),
4374
+ hex.slice(16, 20),
4375
+ hex.slice(20)
4376
+ ].join("-");
4377
+ rekey.set(key.toUpperCase(), fresh);
4378
+ parts.push({ name, data: xorFontKey(font, fresh) });
4379
+ }
4380
+ if (rekey.size === 0) return;
4381
+ for (const { name, data } of parts) {
4382
+ const entry = zip.getEntry(name);
4383
+ if (entry) zip.updateFile(entry, data);
4384
+ }
4385
+ const fontTable = zip.getEntry("word/fontTable.xml");
4386
+ if (!fontTable) return;
4387
+ zip.updateFile(
4388
+ fontTable,
4389
+ Buffer.from(
4390
+ xml.replace(
4391
+ /\bw:fontKey="\{([0-9A-Fa-f-]{36})\}"/g,
4392
+ (whole, key) => {
4393
+ const fresh = rekey.get(key.toUpperCase());
4394
+ return fresh ? `w:fontKey="{${fresh}}"` : whole;
4395
+ }
4396
+ ),
4397
+ "utf8"
4398
+ )
4399
+ );
4400
+ }
4335
4401
  function canonicalizeEmbeddedPackages(zip, generatedAt, depth = 1) {
4336
4402
  const names = zip.getEntries().map((entry) => entry.entryName).filter((name) => EMBEDDED_OFFICE_PACKAGE.test(name));
4337
4403
  let changed = false;
@@ -4384,6 +4450,7 @@ function canonicalizeDocxBuffer(buffer, generatedAt = DEFAULT_GENERATION_DATE) {
4384
4450
  normalizeTextCase(zip);
4385
4451
  canonicalizeRelationshipIds(zip);
4386
4452
  canonicalizeEmbeddedPackages(zip, generatedAt);
4453
+ canonicalizeEmbeddedFontKeys(zip);
4387
4454
  const isoTimestamp = generatedAt.toISOString();
4388
4455
  const coreProperties3 = zip.getEntry("docProps/core.xml");
4389
4456
  if (coreProperties3) {
@@ -4400,13 +4467,14 @@ function normalizeDocxCaseBuffer(buffer) {
4400
4467
  const zip = new AdmZip(buffer);
4401
4468
  return normalizeTextCase(zip) ? zip.toBuffer() : buffer;
4402
4469
  }
4403
- var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID, CORE_TIMESTAMP, EMBEDDED_OFFICE_PACKAGE, MAX_EMBEDDED_DEPTH;
4470
+ var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID, FONT_EMBED, CORE_TIMESTAMP, EMBEDDED_OFFICE_PACKAGE, MAX_EMBEDDED_DEPTH;
4404
4471
  var init_packageDocument = __esm({
4405
4472
  "src/utils/packageDocument.ts"() {
4406
4473
  "use strict";
4407
4474
  init_normalizeTextCase();
4408
4475
  DEFAULT_GENERATION_DATE = /* @__PURE__ */ new Date("2000-01-01T00:00:00.000Z");
4409
4476
  STABLE_RELATIONSHIP_ID = /^rId\d+$/;
4477
+ FONT_EMBED = /<w:embed(?:Regular|Bold|Italic|BoldItalic)\b[^>]*>/g;
4410
4478
  CORE_TIMESTAMP = /(<dcterms:(?:created|modified)\b[^>]*>)[^<]*(<\/dcterms:(?:created|modified)>)/g;
4411
4479
  EMBEDDED_OFFICE_PACKAGE = /\.(?:docx|pptx|xlsx|xlsm)$/i;
4412
4480
  MAX_EMBEDDED_DEPTH = 3;
@@ -5007,7 +5075,12 @@ function createDocxJsRenderer() {
5007
5075
  });
5008
5076
  }
5009
5077
  const pageNumbers = needsPageNumbers(ir) ? await createPageNumberEstimator(ir) : void 0;
5010
- const document = buildDocument(ir, resources, pageNumbers);
5078
+ const document = buildDocument(
5079
+ ir,
5080
+ resources,
5081
+ pageNumbers,
5082
+ renderOptions?.embeddedFonts
5083
+ );
5011
5084
  const packed = await Packer.toBuffer(document);
5012
5085
  const fixed = fixFloatingImageIdsInBuffer(packed);
5013
5086
  const repaired = markers ? repairDrawingGroupsInBuffer(fixed, markers) : fixed;
@@ -5025,10 +5098,17 @@ function createDocxJsRenderer() {
5025
5098
  }
5026
5099
  };
5027
5100
  }
5028
- function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers) {
5101
+ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers, embeddedFonts) {
5029
5102
  return new Document({
5030
5103
  ...pageNumbers ? { pageNumbers } : {},
5031
5104
  styles: emitStyles(ir.styles),
5105
+ // word/fonts/font<N>.odttf, their `w:embed*` entries in the font table and
5106
+ // `w:embedTrueTypeFonts` in the settings, all from this one key. Passed
5107
+ // only when a face embeds, so a document in any other mode is built from
5108
+ // exactly the options it always was. `docx/layout` measures the text of an
5109
+ // embedded family from these files, so a contents field's page numbers
5110
+ // need nothing more.
5111
+ ...embeddedFonts && embeddedFonts.length > 0 ? { fonts: embeddedFonts.map(fontOptions) } : {},
5032
5112
  ...ir.theme ? { theme: themeOptions(ir.theme) } : {},
5033
5113
  sections: ir.sections.map((section2) => sectionOptions(section2, resources)),
5034
5114
  ...coreProperties(ir),
@@ -5059,6 +5139,15 @@ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers) {
5059
5139
  } : {}
5060
5140
  });
5061
5141
  }
5142
+ function fontOptions(face) {
5143
+ return {
5144
+ name: face.family,
5145
+ data: face.regular,
5146
+ ...face.bold ? { bold: face.bold } : {},
5147
+ ...face.italic ? { italic: face.italic } : {},
5148
+ ...face.boldItalic ? { boldItalic: face.boldItalic } : {}
5149
+ };
5150
+ }
5062
5151
  function themeOptions(theme) {
5063
5152
  const colors = {};
5064
5153
  for (const [slot, color] of Object.entries(theme.colors)) {
@@ -7054,6 +7143,67 @@ import {
7054
7143
  toRasterizeFontFaces
7055
7144
  } from "@json-to-office/shared/fonts/node";
7056
7145
 
7146
+ // src/core/fontEmbedding.ts
7147
+ import { isSafeFont } from "@json-to-office/shared";
7148
+ import {
7149
+ toEmbeddedFontFaces
7150
+ } from "@json-to-office/shared/fonts/node";
7151
+
7152
+ // src/renderers/types.ts
7153
+ import {
7154
+ DEFAULT_DOCX_RENDERER_ID
7155
+ } from "@json-to-office/shared-docx";
7156
+
7157
+ // src/core/fontEmbedding.ts
7158
+ var EMBEDDING_RENDERER = "docxjs";
7159
+ var warning = (code, message) => ({
7160
+ component: "fontRegistry",
7161
+ message,
7162
+ severity: "warning",
7163
+ context: { code }
7164
+ });
7165
+ function embedsFonts(fonts, renderer, warnings) {
7166
+ if (fonts?.mode !== "embed") return false;
7167
+ const id = renderer ?? DEFAULT_DOCX_RENDERER_ID;
7168
+ if (id === EMBEDDING_RENDERER) return true;
7169
+ warnings.push(
7170
+ warning(
7171
+ "FONT_EMBED_UNSUPPORTED_RENDERER",
7172
+ `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.`
7173
+ )
7174
+ );
7175
+ return false;
7176
+ }
7177
+ function selectEmbeddedFonts(resolved, warnings) {
7178
+ const { faces, report } = toEmbeddedFontFaces(resolved, warnings);
7179
+ const summary = embedSummary(resolved, report);
7180
+ if (summary) warnings.push(warning("FONT_MODE_EMBED", summary));
7181
+ return faces;
7182
+ }
7183
+ function embedSummary(resolved, report) {
7184
+ const byFamily = new Map(report.map((r) => [r.family.toLowerCase(), r]));
7185
+ const seen = /* @__PURE__ */ new Set();
7186
+ const lines = [];
7187
+ for (const font of resolved) {
7188
+ const key = font.family.toLowerCase();
7189
+ if (isSafeFont(font.family.trim()) || seen.has(key)) continue;
7190
+ seen.add(key);
7191
+ const entry = byFamily.get(key);
7192
+ if (!entry) {
7193
+ lines.push(`${font.family}: not embedded (unresolved)`);
7194
+ } else if (entry.slots.length === 0) {
7195
+ lines.push(
7196
+ `${font.family}: not embedded (${entry.reason === "no-regular-face" ? "no regular face" : "no TTF/OTF face it may embed"})`
7197
+ );
7198
+ } else {
7199
+ const nearest = entry.nearest.length ? ` (${entry.nearest.map((n) => `${n.slot} \u2190 ${n.weight}`).join(", ")})` : "";
7200
+ lines.push(`${font.family}: ${entry.slots.join(", ")}${nearest}`);
7201
+ }
7202
+ }
7203
+ if (lines.length === 0) return void 0;
7204
+ 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.`;
7205
+ }
7206
+
7057
7207
  // src/core/prerasterizeVisuals.ts
7058
7208
  import {
7059
7209
  clampVisualDpi,
@@ -7540,7 +7690,7 @@ function assertExportServerAllowed(serverUrl, servicesConfig, warnings) {
7540
7690
  const notice = remoteExportNotice(serverUrl, servicesConfig?.allowRemote);
7541
7691
  if (!notice || !warnings) return;
7542
7692
  const alreadySaid = warnings.some(
7543
- (warning) => warning.context?.code === REMOTE_EXPORT_WARNING && warning.context?.serverUrl === serverUrl
7693
+ (warning2) => warning2.context?.code === REMOTE_EXPORT_WARNING && warning2.context?.serverUrl === serverUrl
7544
7694
  );
7545
7695
  if (alreadySaid) return;
7546
7696
  warnings.push({
@@ -13586,13 +13736,6 @@ function warnOnce(ctx, component, message, code) {
13586
13736
  import {
13587
13737
  RendererRegistry
13588
13738
  } 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
13739
  var registry = new RendererRegistry(
13597
13740
  "docx",
13598
13741
  DEFAULT_DOCX_RENDERER_ID
@@ -16240,13 +16383,15 @@ async function compileDocumentScoped(document, options, warnings) {
16240
16383
  const { context } = prepared.model;
16241
16384
  const hasVisual = collectVisualProps(context.document).length > 0;
16242
16385
  const hasHighcharts = containsComponent(context.document, "highcharts");
16386
+ const embedding = embedsFonts(options.fonts, options.renderer, warnings);
16243
16387
  const resolvedFonts = await resolveDocumentFonts(
16244
16388
  context.document,
16245
16389
  context.theme,
16246
16390
  options.fonts,
16247
16391
  warnings,
16248
- hasVisual || hasHighcharts
16392
+ hasVisual || hasHighcharts || embedding
16249
16393
  );
16394
+ const embeddedFonts = embedding ? selectEmbeddedFonts(resolvedFonts, warnings) : [];
16250
16395
  const visualFonts = hasVisual ? toRasterizeFontFaces(resolvedFonts, warnings) : [];
16251
16396
  const chartFonts = hasHighcharts ? toChartFontFaces(resolvedFonts) : [];
16252
16397
  const desugared = await desugarExternals(context.document, {
@@ -16286,7 +16431,8 @@ async function compileDocumentScoped(document, options, warnings) {
16286
16431
  theme: context.theme,
16287
16432
  warnings: compiled.warnings,
16288
16433
  required: compiled.required,
16289
- unsupported: compiled.unsupported
16434
+ unsupported: compiled.unsupported,
16435
+ embeddedFonts
16290
16436
  };
16291
16437
  }
16292
16438
  async function generateBufferViaIr(document, options = {}) {
@@ -16310,6 +16456,7 @@ async function renderScoped(document, options, warnings) {
16310
16456
  ...options.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
16311
16457
  ...options.generatedAt !== void 0 ? { generatedAt: toDate(options.generatedAt) } : {},
16312
16458
  ...options.svgRasterFallback !== void 0 ? { svgRasterFallback: options.svgRasterFallback } : {},
16459
+ ...compiled.embeddedFonts.length > 0 ? { embeddedFonts: compiled.embeddedFonts } : {},
16313
16460
  warnings: compiled.warnings
16314
16461
  });
16315
16462
  return { buffer: Buffer.from(bytes), warnings: compiled.warnings };
@@ -17815,10 +17962,10 @@ function generateWarningsDocument(warnings) {
17815
17962
  }
17816
17963
  ];
17817
17964
  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);
17965
+ for (const warning2 of warnings) {
17966
+ const existing = warningsByComponent.get(warning2.component) || [];
17967
+ existing.push(warning2);
17968
+ warningsByComponent.set(warning2.component, existing);
17822
17969
  }
17823
17970
  for (const [
17824
17971
  componentName,
@@ -17832,10 +17979,10 @@ function generateWarningsDocument(warnings) {
17832
17979
  }
17833
17980
  });
17834
17981
  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(", ");
17982
+ for (const warning2 of componentWarnings) {
17983
+ let warningText = warning2.message;
17984
+ if (warning2.context && Object.keys(warning2.context).length > 0) {
17985
+ const contextStr = Object.entries(warning2.context).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
17839
17986
  warningText += ` (${contextStr})`;
17840
17987
  }
17841
17988
  warningItems.push(warningText);
@@ -17871,10 +18018,10 @@ function formatWarningsText(warnings) {
17871
18018
  ""
17872
18019
  ];
17873
18020
  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);
18021
+ for (const warning2 of warnings) {
18022
+ const existing = warningsByComponent.get(warning2.component) || [];
18023
+ existing.push(warning2);
18024
+ warningsByComponent.set(warning2.component, existing);
17878
18025
  }
17879
18026
  for (const [
17880
18027
  componentName,
@@ -17883,10 +18030,10 @@ function formatWarningsText(warnings) {
17883
18030
  lines.push(`Component: ${componentName}`);
17884
18031
  lines.push("-".repeat(30));
17885
18032
  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)}`);
18033
+ const warning2 = componentWarnings[i];
18034
+ lines.push(` ${i + 1}. ${warning2.message}`);
18035
+ if (warning2.context && Object.keys(warning2.context).length > 0) {
18036
+ lines.push(` Context: ${JSON.stringify(warning2.context, null, 2)}`);
17890
18037
  }
17891
18038
  }
17892
18039
  lines.push("");