fix(producer): force text-rendering:geometricPrecision so headless-shell matches Chrome#985
Merged
Conversation
…ell matches Chrome
Collaborator
Author
This stack of pull requests is managed by Graphite. Learn more about stacking. |
miguel-heygen
approved these changes
May 20, 2026
…o-preview-assets The text-rendering:geometricPrecision rule injected by the previous commit shifts glyph advances by ~1% on chrome-headless-shell (was optimizeSpeed under text-rendering:auto). Two fixtures with strict gates tripped: - distributed/png-sequence: maxFrameFailures=0 byte-identity gate, all 60 frames now differ. The fixture's own meta.json already documents this as the expected response to renderer-pixel changes. - heygen-promo-preview-assets: minPsnr=30, maxFrameFailures=0; one frame dropped to 27.67 dB after the layout shift. Full local regression run (47 fixtures): 45 passed, only these 2 needed regeneration — the text-rendering change passes through the rest without PSNR impact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Inject
html, body, * { text-rendering: geometricPrecision; }into every compiled composition sochrome-headless-shell(the binary BeginFrame requires) lays out text identically to full Chrome.Why
The two Chrome binaries silently disagree on the default value of
text-rendering: auto:text-rendering: autoresolves tochrome-headless-shelloptimizeSpeed(integer-rounded advances)geometricPrecision(subpixel advances)That ~1% advance-width gap cascades:
element.offsetWidthto compute a transform scale (e.g.cardScale = targetWidth / naturalCardWidth) get a different scale per binary, which then shifts every pixel of the card.It's an intrinsic per-binary CSS default — not a Chromium-version drift — so no version pin closes it. Forcing
geometricPrecisionputs both binaries on the same subpixel glyph path that full Chrome was already using.Ported from the experiment-framework GSAP renderer fix in heygen-com/experiment-framework#37369. There it lifted worst-case PSNR from 12.56 → 24.66 dB and cut SSIM standard deviation ~3.2× across 200 production templates.
Same browser dichotomy applies in hyperframes:
packages/engine/src/services/browserManager.ts:43— BeginFrame mode launcheschrome-headless-shell.hyperframes snapshotand friends also drivechrome-headless-shellviabundleToSingleHtml, so the bundler path needs the same rule for studio/snapshot ↔ producer parity.How
Two small head-style injections, mirroring the existing
injectDeterministicFontFacespattern.packages/producer/src/services/htmlCompiler.tsinjectTextRenderingRule(html)— inserts<style data-hyperframes-text-rendering="true">html,body,*{text-rendering:geometricPrecision}</style>at the start of<head>. Idempotent.compileForRenderbetweencoalesceHeadStylesAndBodyScripts(...)andinjectDeterministicFontFaces(...), so the rule lands in the compiled HTML (and the content-addressed planDir hash) before user CSS is merged.ensureFullDocument's fallback<style>block also getstext-rendering:geometricPrecisionappended to the universal selector, so fragment compositions don't bypass the fix.packages/core/src/compiler/htmlBundler.tsbundleToSingleHtmlaftercoalesceHeadStylesAndBodyScripts. Studio runs in full Chrome so this is a visual no-op there, but it keeps the bundled HTML byte-aligned with the producer compile output, which is whatsnapshot/validate/layout(all of which feed bundled HTML into headless renders) need.Specificity is preserved:
*is (0,0,0), so any composition's class/id/element-targetedtext-renderingrule still overrides.Out of scope: residual non-text-layout divergence (SVG filter/shadow/gradient rasterization between the binaries) — same caveat called out in heygen-com/experiment-framework#37369.
Test plan
packages/producer/src/services/htmlCompiler.test.ts(full-doc + fragment paths) andpackages/core/src/compiler/htmlBundler.test.ts.bun test packages/producer/src/services/htmlCompiler.test.ts— 38 pass.bunx vitest run packages/core/src/compiler/htmlBundler.test.ts(frompackages/core) — 31 pass.bunx oxlint+bunx oxfmt --checkon all 4 changed files — clean.bun run --cwd packages/producer build+bun run --cwd packages/core build— green.bun run --cwd packages/producer docker:test:update <test-name>perDockerfile.test(not from host).hyperframes renderto confirm PSNR vs. a full-Chrome render.🤖 Generated with Claude Code