Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/core/src/compiler/htmlBundler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,4 +939,30 @@ describe("bundleToSingleHtml", () => {
expect(bundled).toContain("/* @import url('./old.css'); */");
expect(bundled).not.toContain(".old { display: none; }");
});

// Forces `text-rendering: geometricPrecision` so headless-shell BeginFrame
// renders match full Chrome (which is the snapshot/preview path). See
// `injectTextRenderingRule` in htmlBundler.ts.
it("injects a single text-rendering:geometricPrecision rule into <head>", async () => {
const dir = makeTempProject({
"index.html": `<!doctype html>
<html>
<head><title>t</title></head>
<body>
<div data-composition-id="root" data-width="640" data-height="360">
<h1>Hello</h1>
</div>
</body></html>`,
});

const bundled = await bundleToSingleHtml(dir);
const { document } = parseHTML(bundled);
const styleEls = document.querySelectorAll("style[data-hyperframes-text-rendering]");

expect(styleEls.length).toBe(1);
expect((styleEls[0]?.textContent || "").replace(/\s+/g, "")).toContain(
"html,body,*{text-rendering:geometricPrecision}",
);
expect(styleEls[0]?.parentElement?.tagName.toLowerCase()).toBe("head");
});
});
22 changes: 22 additions & 0 deletions packages/core/src/compiler/htmlBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,27 @@ function coalesceHeadStylesAndBodyScripts(document: Document): void {
}
}

/**
* Force subpixel glyph positioning so headless rendering paths
* (chrome-headless-shell with BeginFrame) lay text out identically to full
* Chrome. `text-rendering: auto` resolves to `optimizeSpeed` (integer glyph
* advances) in headless-shell but `geometricPrecision` in full Chrome, which
* shifts line-wrap points and any animation that reads measured text width.
* Mirrors the producer's `injectTextRenderingRule` so bundled previews and
* compiled renders stay byte-aligned. `*` has zero specificity, so authored
* class/id rules still override.
*/
function injectTextRenderingRule(document: Document): void {
const head = document.head;
if (!head) return;
if (document.querySelector("style[data-hyperframes-text-rendering]")) return;

const styleEl = document.createElement("style");
styleEl.setAttribute("data-hyperframes-text-rendering", "true");
styleEl.textContent = "html,body,*{text-rendering:geometricPrecision}";
head.insertBefore(styleEl, head.firstChild);
}

/**
* Concatenate JS chunks safely. Goals:
* - Each chunk's last statement is terminated, so joining can't introduce ASI
Expand Down Expand Up @@ -842,6 +863,7 @@ export async function bundleToSingleHtml(
enforceCompositionPixelSizing(document);
autoHealMissingCompositionIds(document);
coalesceHeadStylesAndBodyScripts(document);
injectTextRenderingRule(document);

// Inline textual assets
for (const el of [...document.querySelectorAll("[src], [href], [poster], [xlink\\:href]")]) {
Expand Down
47 changes: 47 additions & 0 deletions packages/producer/src/services/htmlCompiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,3 +719,50 @@ describe("template-wrapped sub-composition media offsets", () => {
expect(compiled.html).toContain('var __hfCompId = "scene";');
});
});

// ── injectTextRenderingRule (via compileForRender) ─────────────────────────
//
// Forces `text-rendering: geometricPrecision` so chrome-headless-shell
// (BeginFrame) and full Chrome lay text out identically. See
// `injectTextRenderingRule` in htmlCompiler.ts for full context.

describe("text-rendering rule injection", () => {
it("injects a single geometricPrecision rule into <head> for a full-document composition", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-text-rendering-"));
writeFileSync(
join(projectDir, "index.html"),
`<!DOCTYPE html>
<html>
<head><title>t</title></head>
<body>
<div data-composition-id="root" data-width="640" data-height="360" data-duration="1">
<h1>Hello</h1>
</div>
</body>
</html>`,
);

const compiled = await compileForRender(projectDir, join(projectDir, "index.html"), projectDir);

const { document } = parseHTML(compiled.html);
const styleEls = document.querySelectorAll("style[data-hyperframes-text-rendering]");
expect(styleEls.length).toBe(1);
expect((styleEls[0]?.textContent || "").replace(/\s+/g, "")).toContain(
"html,body,*{text-rendering:geometricPrecision}",
);
expect(styleEls[0]?.parentElement?.tagName.toLowerCase()).toBe("head");
});

it("includes geometricPrecision in the fragment-wrap fallback stylesheet", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-text-rendering-frag-"));
// Fragment (no <html>/<head>/<body>) — exercises ensureFullDocument.
writeFileSync(
join(projectDir, "index.html"),
`<div data-composition-id="root" data-width="640" data-height="360" data-duration="1"><h1>Hi</h1></div>`,
);

const compiled = await compileForRender(projectDir, join(projectDir, "index.html"), projectDir);

expect(compiled.html.replace(/\s+/g, "")).toContain("text-rendering:geometricPrecision");
});
});
31 changes: 29 additions & 2 deletions packages/producer/src/services/htmlCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,32 @@ function ensureFullDocument(html: string): string {
// Wrap fragment with a proper document including margin/padding reset.
// Without this, Chrome applies default body { margin: 8px } which creates
// visible white lines at the edges of rendered video.
return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset="UTF-8">\n <style>*{margin:0;padding:0;box-sizing:border-box}body{overflow:hidden;background:#000}</style>\n</head>\n<body style="margin:0;overflow:hidden">\n${html}\n</body>\n</html>`;
return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset="UTF-8">\n <style>*{margin:0;padding:0;box-sizing:border-box;text-rendering:geometricPrecision}body{overflow:hidden;background:#000}</style>\n</head>\n<body style="margin:0;overflow:hidden">\n${html}\n</body>\n</html>`;
}

/**
* Force subpixel glyph positioning so chrome-headless-shell (BeginFrame) and
* full Chrome (screenshot fallback) lay text out identically. `text-rendering:
* auto` resolves to `optimizeSpeed` (integer advances) in headless-shell but
* `geometricPrecision` in full Chrome — that ~1% advance-width gap shifts
* line-wrap points and any animation that reads `offsetWidth`. The `*`
* selector has zero specificity, so authored class/id rules still override.
*/
function injectTextRenderingRule(html: string): string {
const { document } = parseHTML(html);
const head = document.querySelector("head");
if (!head) return html;

if (document.querySelector("style[data-hyperframes-text-rendering]")) {
return html;
}

const styleEl = document.createElement("style");
styleEl.setAttribute("data-hyperframes-text-rendering", "true");
styleEl.textContent = "html,body,*{text-rendering:geometricPrecision}";
head.insertBefore(styleEl, head.firstChild);

return document.toString();
}

/**
Expand Down Expand Up @@ -894,7 +919,9 @@ export async function compileForRender(
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);

const coalescedHtml = await injectDeterministicFontFaces(
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml)),
injectTextRenderingRule(
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml)),
),
{ failClosedFontFetch: options.failClosedFontFetch === true },
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,23 @@
font-style: normal;
font-weight: 700;
font-display: block;
}</style>
}

@font-face {
font-family: "Space Mono";
src: url("data:font/woff2;base64,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") format("woff2");
font-style: italic;
font-weight: 400;
font-display: block;
}

@font-face {
font-family: "Space Mono";
src: url("data:font/woff2;base64,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") format("woff2");
font-style: italic;
font-weight: 700;
font-display: block;
}</style><style data-hyperframes-text-rendering="true">html,body,*{text-rendering:geometricPrecision}</style>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>png-sequence distributed fixture</title>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading