@json-to-office/core-docx 7.5.0 → 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.
- package/dist/core/fontEmbedding.d.ts +39 -0
- package/dist/core/fontEmbedding.d.ts.map +1 -0
- package/dist/core/fontResolution.d.ts +13 -9
- package/dist/core/fontResolution.d.ts.map +1 -1
- package/dist/core/generateFromIr.d.ts +8 -1
- package/dist/core/generateFromIr.d.ts.map +1 -1
- package/dist/core/generationContext.d.ts.map +1 -1
- package/dist/index.js +176 -29
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +164 -17
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/renderers/docxjs/index.d.ts +3 -1
- package/dist/renderers/docxjs/index.d.ts.map +1 -1
- package/dist/renderers/types.d.ts +11 -1
- package/dist/renderers/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/packageDocument.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -4237,6 +4237,8 @@ var init_normalizeTextCase = __esm({
|
|
|
4237
4237
|
|
|
4238
4238
|
// src/utils/packageDocument.ts
|
|
4239
4239
|
import AdmZip from "adm-zip";
|
|
4240
|
+
import { createHash as createHash3 } from "crypto";
|
|
4241
|
+
import { posix } from "path";
|
|
4240
4242
|
function resolveGenerationDate(options) {
|
|
4241
4243
|
if (options?.generatedAt !== void 0) {
|
|
4242
4244
|
const date = new Date(options.generatedAt);
|
|
@@ -4304,6 +4306,70 @@ function canonicalizeRelationshipIds(zip) {
|
|
|
4304
4306
|
if (entry) zip.updateFile(entry, Buffer.from(update.xml, "utf8"));
|
|
4305
4307
|
}
|
|
4306
4308
|
}
|
|
4309
|
+
function xorFontKey(bytes, guid) {
|
|
4310
|
+
const key = Buffer.from(guid.replace(/-/g, ""), "hex").reverse();
|
|
4311
|
+
const out = Buffer.from(bytes);
|
|
4312
|
+
for (let i = 0; i < Math.min(32, out.length); i++) out[i] ^= key[i % 16];
|
|
4313
|
+
return out;
|
|
4314
|
+
}
|
|
4315
|
+
function canonicalizeEmbeddedFontKeys(zip) {
|
|
4316
|
+
const table2 = zip.getEntry("word/fontTable.xml");
|
|
4317
|
+
if (!table2) return;
|
|
4318
|
+
const xml = table2.getData().toString("utf8");
|
|
4319
|
+
const embeds = [...xml.matchAll(FONT_EMBED)].flatMap(([element]) => {
|
|
4320
|
+
const id = /\br:id="([^"]+)"/.exec(element)?.[1];
|
|
4321
|
+
const key = /\bw:fontKey="\{([0-9A-Fa-f-]{36})\}"/.exec(element)?.[1];
|
|
4322
|
+
return id && key ? [{ id, key }] : [];
|
|
4323
|
+
});
|
|
4324
|
+
if (embeds.length === 0) return;
|
|
4325
|
+
const rels = zip.getEntry("word/_rels/fontTable.xml.rels")?.getData().toString("utf8") ?? "";
|
|
4326
|
+
const targets = /* @__PURE__ */ new Map();
|
|
4327
|
+
for (const [relationship] of rels.matchAll(/<Relationship\b[^>]*>/g)) {
|
|
4328
|
+
const id = /\bId="([^"]+)"/.exec(relationship)?.[1];
|
|
4329
|
+
const target = /\bTarget="([^"]+)"/.exec(relationship)?.[1];
|
|
4330
|
+
if (id && target) targets.set(id, target);
|
|
4331
|
+
}
|
|
4332
|
+
const rekey = /* @__PURE__ */ new Map();
|
|
4333
|
+
const parts = [];
|
|
4334
|
+
for (const { id, key } of embeds) {
|
|
4335
|
+
const target = targets.get(id);
|
|
4336
|
+
if (!target) continue;
|
|
4337
|
+
const name = target.startsWith("/") ? target.slice(1) : posix.normalize(posix.join("word", target));
|
|
4338
|
+
const part = zip.getEntry(name);
|
|
4339
|
+
if (!part) continue;
|
|
4340
|
+
const font = xorFontKey(part.getData(), key);
|
|
4341
|
+
const hex = createHash3("sha256").update(font).digest("hex").slice(0, 32).toUpperCase();
|
|
4342
|
+
const fresh = [
|
|
4343
|
+
hex.slice(0, 8),
|
|
4344
|
+
hex.slice(8, 12),
|
|
4345
|
+
hex.slice(12, 16),
|
|
4346
|
+
hex.slice(16, 20),
|
|
4347
|
+
hex.slice(20)
|
|
4348
|
+
].join("-");
|
|
4349
|
+
rekey.set(key.toUpperCase(), fresh);
|
|
4350
|
+
parts.push({ name, data: xorFontKey(font, fresh) });
|
|
4351
|
+
}
|
|
4352
|
+
if (rekey.size === 0) return;
|
|
4353
|
+
for (const { name, data } of parts) {
|
|
4354
|
+
const entry = zip.getEntry(name);
|
|
4355
|
+
if (entry) zip.updateFile(entry, data);
|
|
4356
|
+
}
|
|
4357
|
+
const fontTable = zip.getEntry("word/fontTable.xml");
|
|
4358
|
+
if (!fontTable) return;
|
|
4359
|
+
zip.updateFile(
|
|
4360
|
+
fontTable,
|
|
4361
|
+
Buffer.from(
|
|
4362
|
+
xml.replace(
|
|
4363
|
+
/\bw:fontKey="\{([0-9A-Fa-f-]{36})\}"/g,
|
|
4364
|
+
(whole, key) => {
|
|
4365
|
+
const fresh = rekey.get(key.toUpperCase());
|
|
4366
|
+
return fresh ? `w:fontKey="{${fresh}}"` : whole;
|
|
4367
|
+
}
|
|
4368
|
+
),
|
|
4369
|
+
"utf8"
|
|
4370
|
+
)
|
|
4371
|
+
);
|
|
4372
|
+
}
|
|
4307
4373
|
function canonicalizeEmbeddedPackages(zip, generatedAt, depth = 1) {
|
|
4308
4374
|
const names = zip.getEntries().map((entry) => entry.entryName).filter((name) => EMBEDDED_OFFICE_PACKAGE.test(name));
|
|
4309
4375
|
let changed = false;
|
|
@@ -4356,6 +4422,7 @@ function canonicalizeDocxBuffer(buffer, generatedAt = DEFAULT_GENERATION_DATE) {
|
|
|
4356
4422
|
normalizeTextCase(zip);
|
|
4357
4423
|
canonicalizeRelationshipIds(zip);
|
|
4358
4424
|
canonicalizeEmbeddedPackages(zip, generatedAt);
|
|
4425
|
+
canonicalizeEmbeddedFontKeys(zip);
|
|
4359
4426
|
const isoTimestamp = generatedAt.toISOString();
|
|
4360
4427
|
const coreProperties3 = zip.getEntry("docProps/core.xml");
|
|
4361
4428
|
if (coreProperties3) {
|
|
@@ -4372,13 +4439,14 @@ function normalizeDocxCaseBuffer(buffer) {
|
|
|
4372
4439
|
const zip = new AdmZip(buffer);
|
|
4373
4440
|
return normalizeTextCase(zip) ? zip.toBuffer() : buffer;
|
|
4374
4441
|
}
|
|
4375
|
-
var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID, CORE_TIMESTAMP, EMBEDDED_OFFICE_PACKAGE, MAX_EMBEDDED_DEPTH;
|
|
4442
|
+
var DEFAULT_GENERATION_DATE, STABLE_RELATIONSHIP_ID, FONT_EMBED, CORE_TIMESTAMP, EMBEDDED_OFFICE_PACKAGE, MAX_EMBEDDED_DEPTH;
|
|
4376
4443
|
var init_packageDocument = __esm({
|
|
4377
4444
|
"src/utils/packageDocument.ts"() {
|
|
4378
4445
|
"use strict";
|
|
4379
4446
|
init_normalizeTextCase();
|
|
4380
4447
|
DEFAULT_GENERATION_DATE = /* @__PURE__ */ new Date("2000-01-01T00:00:00.000Z");
|
|
4381
4448
|
STABLE_RELATIONSHIP_ID = /^rId\d+$/;
|
|
4449
|
+
FONT_EMBED = /<w:embed(?:Regular|Bold|Italic|BoldItalic)\b[^>]*>/g;
|
|
4382
4450
|
CORE_TIMESTAMP = /(<dcterms:(?:created|modified)\b[^>]*>)[^<]*(<\/dcterms:(?:created|modified)>)/g;
|
|
4383
4451
|
EMBEDDED_OFFICE_PACKAGE = /\.(?:docx|pptx|xlsx|xlsm)$/i;
|
|
4384
4452
|
MAX_EMBEDDED_DEPTH = 3;
|
|
@@ -4979,7 +5047,12 @@ function createDocxJsRenderer() {
|
|
|
4979
5047
|
});
|
|
4980
5048
|
}
|
|
4981
5049
|
const pageNumbers = needsPageNumbers(ir) ? await createPageNumberEstimator(ir) : void 0;
|
|
4982
|
-
const document = buildDocument(
|
|
5050
|
+
const document = buildDocument(
|
|
5051
|
+
ir,
|
|
5052
|
+
resources,
|
|
5053
|
+
pageNumbers,
|
|
5054
|
+
renderOptions?.embeddedFonts
|
|
5055
|
+
);
|
|
4983
5056
|
const packed = await Packer.toBuffer(document);
|
|
4984
5057
|
const fixed = fixFloatingImageIdsInBuffer(packed);
|
|
4985
5058
|
const repaired = markers ? repairDrawingGroupsInBuffer(fixed, markers) : fixed;
|
|
@@ -4997,10 +5070,17 @@ function createDocxJsRenderer() {
|
|
|
4997
5070
|
}
|
|
4998
5071
|
};
|
|
4999
5072
|
}
|
|
5000
|
-
function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers) {
|
|
5073
|
+
function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers, embeddedFonts) {
|
|
5001
5074
|
return new Document({
|
|
5002
5075
|
...pageNumbers ? { pageNumbers } : {},
|
|
5003
5076
|
styles: emitStyles(ir.styles),
|
|
5077
|
+
// word/fonts/font<N>.odttf, their `w:embed*` entries in the font table and
|
|
5078
|
+
// `w:embedTrueTypeFonts` in the settings, all from this one key. Passed
|
|
5079
|
+
// only when a face embeds, so a document in any other mode is built from
|
|
5080
|
+
// exactly the options it always was. `docx/layout` measures the text of an
|
|
5081
|
+
// embedded family from these files, so a contents field's page numbers
|
|
5082
|
+
// need nothing more.
|
|
5083
|
+
...embeddedFonts && embeddedFonts.length > 0 ? { fonts: embeddedFonts.map(fontOptions) } : {},
|
|
5004
5084
|
...ir.theme ? { theme: themeOptions(ir.theme) } : {},
|
|
5005
5085
|
sections: ir.sections.map((section2) => sectionOptions(section2, resources)),
|
|
5006
5086
|
...coreProperties(ir),
|
|
@@ -5031,6 +5111,15 @@ function buildDocument(ir, resources = /* @__PURE__ */ new Map(), pageNumbers) {
|
|
|
5031
5111
|
} : {}
|
|
5032
5112
|
});
|
|
5033
5113
|
}
|
|
5114
|
+
function fontOptions(face) {
|
|
5115
|
+
return {
|
|
5116
|
+
name: face.family,
|
|
5117
|
+
data: face.regular,
|
|
5118
|
+
...face.bold ? { bold: face.bold } : {},
|
|
5119
|
+
...face.italic ? { italic: face.italic } : {},
|
|
5120
|
+
...face.boldItalic ? { boldItalic: face.boldItalic } : {}
|
|
5121
|
+
};
|
|
5122
|
+
}
|
|
5034
5123
|
function themeOptions(theme) {
|
|
5035
5124
|
const colors = {};
|
|
5036
5125
|
for (const [slot, color] of Object.entries(theme.colors)) {
|
|
@@ -7326,6 +7415,67 @@ import {
|
|
|
7326
7415
|
toRasterizeFontFaces
|
|
7327
7416
|
} from "@json-to-office/shared/fonts/node";
|
|
7328
7417
|
|
|
7418
|
+
// src/core/fontEmbedding.ts
|
|
7419
|
+
import { isSafeFont } from "@json-to-office/shared";
|
|
7420
|
+
import {
|
|
7421
|
+
toEmbeddedFontFaces
|
|
7422
|
+
} from "@json-to-office/shared/fonts/node";
|
|
7423
|
+
|
|
7424
|
+
// src/renderers/types.ts
|
|
7425
|
+
import {
|
|
7426
|
+
DEFAULT_DOCX_RENDERER_ID
|
|
7427
|
+
} from "@json-to-office/shared-docx";
|
|
7428
|
+
|
|
7429
|
+
// src/core/fontEmbedding.ts
|
|
7430
|
+
var EMBEDDING_RENDERER = "docxjs";
|
|
7431
|
+
var warning = (code, message) => ({
|
|
7432
|
+
component: "fontRegistry",
|
|
7433
|
+
message,
|
|
7434
|
+
severity: "warning",
|
|
7435
|
+
context: { code }
|
|
7436
|
+
});
|
|
7437
|
+
function embedsFonts(fonts, renderer, warnings) {
|
|
7438
|
+
if (fonts?.mode !== "embed") return false;
|
|
7439
|
+
const id = renderer ?? DEFAULT_DOCX_RENDERER_ID;
|
|
7440
|
+
if (id === EMBEDDING_RENDERER) return true;
|
|
7441
|
+
warnings.push(
|
|
7442
|
+
warning(
|
|
7443
|
+
"FONT_EMBED_UNSUPPORTED_RENDERER",
|
|
7444
|
+
`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.`
|
|
7445
|
+
)
|
|
7446
|
+
);
|
|
7447
|
+
return false;
|
|
7448
|
+
}
|
|
7449
|
+
function selectEmbeddedFonts(resolved, warnings) {
|
|
7450
|
+
const { faces, report } = toEmbeddedFontFaces(resolved, warnings);
|
|
7451
|
+
const summary = embedSummary(resolved, report);
|
|
7452
|
+
if (summary) warnings.push(warning("FONT_MODE_EMBED", summary));
|
|
7453
|
+
return faces;
|
|
7454
|
+
}
|
|
7455
|
+
function embedSummary(resolved, report) {
|
|
7456
|
+
const byFamily = new Map(report.map((r) => [r.family.toLowerCase(), r]));
|
|
7457
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7458
|
+
const lines = [];
|
|
7459
|
+
for (const font of resolved) {
|
|
7460
|
+
const key = font.family.toLowerCase();
|
|
7461
|
+
if (isSafeFont(font.family.trim()) || seen.has(key)) continue;
|
|
7462
|
+
seen.add(key);
|
|
7463
|
+
const entry = byFamily.get(key);
|
|
7464
|
+
if (!entry) {
|
|
7465
|
+
lines.push(`${font.family}: not embedded (unresolved)`);
|
|
7466
|
+
} else if (entry.slots.length === 0) {
|
|
7467
|
+
lines.push(
|
|
7468
|
+
`${font.family}: not embedded (${entry.reason === "no-regular-face" ? "no regular face" : "no TTF/OTF face it may embed"})`
|
|
7469
|
+
);
|
|
7470
|
+
} else {
|
|
7471
|
+
const nearest = entry.nearest.length ? ` (${entry.nearest.map((n) => `${n.slot} \u2190 ${n.weight}`).join(", ")})` : "";
|
|
7472
|
+
lines.push(`${font.family}: ${entry.slots.join(", ")}${nearest}`);
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
if (lines.length === 0) return void 0;
|
|
7476
|
+
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.`;
|
|
7477
|
+
}
|
|
7478
|
+
|
|
7329
7479
|
// src/core/prerasterizeVisuals.ts
|
|
7330
7480
|
import {
|
|
7331
7481
|
clampVisualDpi,
|
|
@@ -7797,7 +7947,7 @@ function assertExportServerAllowed(serverUrl, servicesConfig, warnings) {
|
|
|
7797
7947
|
const notice = remoteExportNotice(serverUrl, servicesConfig?.allowRemote);
|
|
7798
7948
|
if (!notice || !warnings) return;
|
|
7799
7949
|
const alreadySaid = warnings.some(
|
|
7800
|
-
(
|
|
7950
|
+
(warning2) => warning2.context?.code === REMOTE_EXPORT_WARNING && warning2.context?.serverUrl === serverUrl
|
|
7801
7951
|
);
|
|
7802
7952
|
if (alreadySaid) return;
|
|
7803
7953
|
warnings.push({
|
|
@@ -13843,13 +13993,6 @@ function warnOnce(ctx, component, message, code) {
|
|
|
13843
13993
|
import {
|
|
13844
13994
|
RendererRegistry
|
|
13845
13995
|
} from "@json-to-office/shared/rendering";
|
|
13846
|
-
|
|
13847
|
-
// src/renderers/types.ts
|
|
13848
|
-
import {
|
|
13849
|
-
DEFAULT_DOCX_RENDERER_ID
|
|
13850
|
-
} from "@json-to-office/shared-docx";
|
|
13851
|
-
|
|
13852
|
-
// src/renderers/registry.ts
|
|
13853
13996
|
var registry = new RendererRegistry(
|
|
13854
13997
|
"docx",
|
|
13855
13998
|
DEFAULT_DOCX_RENDERER_ID
|
|
@@ -16302,13 +16445,15 @@ async function compileDocumentScoped(document, options, warnings) {
|
|
|
16302
16445
|
const { context } = prepared.model;
|
|
16303
16446
|
const hasVisual = collectVisualProps(context.document).length > 0;
|
|
16304
16447
|
const hasHighcharts = containsComponent(context.document, "highcharts");
|
|
16448
|
+
const embedding = embedsFonts(options.fonts, options.renderer, warnings);
|
|
16305
16449
|
const resolvedFonts = await resolveDocumentFonts(
|
|
16306
16450
|
context.document,
|
|
16307
16451
|
context.theme,
|
|
16308
16452
|
options.fonts,
|
|
16309
16453
|
warnings,
|
|
16310
|
-
hasVisual || hasHighcharts
|
|
16454
|
+
hasVisual || hasHighcharts || embedding
|
|
16311
16455
|
);
|
|
16456
|
+
const embeddedFonts = embedding ? selectEmbeddedFonts(resolvedFonts, warnings) : [];
|
|
16312
16457
|
const visualFonts = hasVisual ? toRasterizeFontFaces(resolvedFonts, warnings) : [];
|
|
16313
16458
|
const chartFonts = hasHighcharts ? toChartFontFaces(resolvedFonts) : [];
|
|
16314
16459
|
const desugared = await desugarExternals(context.document, {
|
|
@@ -16348,7 +16493,8 @@ async function compileDocumentScoped(document, options, warnings) {
|
|
|
16348
16493
|
theme: context.theme,
|
|
16349
16494
|
warnings: compiled.warnings,
|
|
16350
16495
|
required: compiled.required,
|
|
16351
|
-
unsupported: compiled.unsupported
|
|
16496
|
+
unsupported: compiled.unsupported,
|
|
16497
|
+
embeddedFonts
|
|
16352
16498
|
};
|
|
16353
16499
|
}
|
|
16354
16500
|
async function generateBufferViaIr(document, options = {}) {
|
|
@@ -16372,6 +16518,7 @@ async function renderScoped(document, options, warnings) {
|
|
|
16372
16518
|
...options.deterministic !== void 0 ? { deterministic: options.deterministic } : {},
|
|
16373
16519
|
...options.generatedAt !== void 0 ? { generatedAt: toDate(options.generatedAt) } : {},
|
|
16374
16520
|
...options.svgRasterFallback !== void 0 ? { svgRasterFallback: options.svgRasterFallback } : {},
|
|
16521
|
+
...compiled.embeddedFonts.length > 0 ? { embeddedFonts: compiled.embeddedFonts } : {},
|
|
16375
16522
|
warnings: compiled.warnings
|
|
16376
16523
|
});
|
|
16377
16524
|
return { buffer: Buffer.from(bytes), warnings: compiled.warnings };
|
|
@@ -17368,10 +17515,10 @@ async function runPluginDemo() {
|
|
|
17368
17515
|
if (result.warnings && result.warnings.length > 0) {
|
|
17369
17516
|
console.log(`
|
|
17370
17517
|
\u26A0\uFE0F ${result.warnings.length} warning(s) generated:`);
|
|
17371
|
-
result.warnings.forEach((
|
|
17372
|
-
console.log(` - [${
|
|
17373
|
-
if (
|
|
17374
|
-
console.log(` Context: ${JSON.stringify(
|
|
17518
|
+
result.warnings.forEach((warning2) => {
|
|
17519
|
+
console.log(` - [${warning2.component}] ${warning2.message}`);
|
|
17520
|
+
if (warning2.context) {
|
|
17521
|
+
console.log(` Context: ${JSON.stringify(warning2.context)}`);
|
|
17375
17522
|
}
|
|
17376
17523
|
});
|
|
17377
17524
|
}
|