diff --git a/packages/producer/src/services/htmlCompiler.test.ts b/packages/producer/src/services/htmlCompiler.test.ts
index 55dcb4751..7253af9ff 100644
--- a/packages/producer/src/services/htmlCompiler.test.ts
+++ b/packages/producer/src/services/htmlCompiler.test.ts
@@ -4,6 +4,7 @@ import { mkdtempSync, writeFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { parseHTML } from "linkedom";
+import { defaultLogger } from "../logger.js";
import {
collectExternalAssets,
compileForRender,
@@ -858,6 +859,40 @@ describe("system-primary font normalization", () => {
});
});
+describe("local font embedding", () => {
+ it("embeds one font file once when sub-compositions use equivalent relative paths", async () => {
+ const projectDir = mkdtempSync(join(tmpdir(), "hf-local-font-dedupe-"));
+ const assetsDir = join(projectDir, "assets");
+ mkdirSync(assetsDir, { recursive: true });
+ writeFileSync(join(assetsDir, "shared.woff2"), "fake-woff2");
+ writeFileSync(
+ join(projectDir, "index.html"),
+ `
+
+ Text
+`,
+ );
+
+ const originalInfo = defaultLogger.info;
+ const embeddedMessages: string[] = [];
+ defaultLogger.info = (message) => {
+ if (message.includes("Embedded local font file")) embeddedMessages.push(message);
+ };
+
+ try {
+ await compileForRender(projectDir, join(projectDir, "index.html"), projectDir);
+ } finally {
+ defaultLogger.info = originalInfo;
+ }
+
+ expect(embeddedMessages).toHaveLength(1);
+ });
+});
+
describe("template-wrapped sub-composition media offsets", () => {
function writeTemplateWrappedProject(
hostAttrs: string,
diff --git a/packages/producer/src/services/htmlCompiler.ts b/packages/producer/src/services/htmlCompiler.ts
index 65d20b145..c9b52dd38 100644
--- a/packages/producer/src/services/htmlCompiler.ts
+++ b/packages/producer/src/services/htmlCompiler.ts
@@ -1605,7 +1605,8 @@ async function embedLocalFontFaces(html: string, projectDir: string): Promise]*>([\s\S]*?)<\/style>/gi;
const fontFaceRe = /@font-face\s*\{([^}]*)\}/gi;
let result = html;
- const embedded = new Set();
+ const embeddedPaths = new Set();
+ const dataUriByAbsolutePath = new Map();
let styleMatch: RegExpExecArray | null;
while ((styleMatch = styleBlockRe.exec(html)) !== null) {
@@ -1618,19 +1619,23 @@ async function embedLocalFontFaces(html: string, projectDir: string): Promise