Summary
Upgrading satori from 0.32.0 to 0.33.4 turns Inter Bold WOFF text into solid “redacted” bars.
The SVG no longer contains real glyph outlines. Every character is the same five stacked rectangles, only translated along the x-axis. Adjacent stacks overlap, so the rasterised PNG looks like black bars instead of letters.
0.32.0 (same font, same markup, same rasteriser) renders Inter correctly.
This started in 0.33.0, which added HarfBuzz text shaping (#735, b2127fe, 2026-08-20). 0.33.1–0.33.4 did not fix it.
Satori’s README still lists TTF, OTF, and WOFF as supported (not WOFF2). This file is WOFF, not WOFF2, and not a variable font.
Versions
| Version |
Date |
Changelog |
Result |
| 0.32.0 |
2026-08-20 |
backdrop-filter |
OK — last good release |
| 0.33.0 |
2026-08-20 |
feat: Add HarfBuzz text shaping (#735) |
broken |
| 0.33.1 |
2026-08-21 |
cache / ~12% faster |
still broken at 0.33.4 |
| 0.33.2 |
2026-08-21 |
better font caching |
still broken at 0.33.4 |
| 0.33.3 |
2026-08-21 |
opacity processing |
still broken at 0.33.4 |
| 0.33.4 |
2026-08-24 |
playground Geist tweak |
broken (confirmed) |
0.33.4 still depends on both @shuding/opentype.js@1.4.0-beta.0 and harfbuzzjs@0.10.0.
Expected
- SVG
<path d="…"> is real Inter Bold outlines (curves, counters, distinct shapes per letter).
- Rasterised PNG is readable type.
Actual
- Satori emits one
<path> whose d is only M / L / Z rectangles.
- There are no cubic or quadratic curves.
- Every “glyph” is five ~44×9px bars, shifted ~26px, overlapping.
First glyph (satori 0.33.4):
M49.2 60.8 L5.4 60.8 L5.4 51.4 L49.2 51.4 L49.2 60.8Z
M49.2 47.8 L5.4 47.8 L5.4 38.4 L49.2 38.4 L49.2 47.8Z
M49.2 34.8 L5.4 34.8 L5.4 25.4 L49.2 25.4 L49.2 34.8Z
M49.2 21.8 L5.4 21.8 L5.4 12.4 L49.2 12.4 L49.2 21.8Z
M49.2 8.8 L5.4 8.8 L5.4 -0.6 L49.2 -0.6 L49.2 8.8Z
The next characters are the same five bars, translated (~75.2, 101.2, 127.2, …). That shared outline + overlap is why the PNG looks redacted rather than like Inter.
Minimal reproduction (Node, ESM)
import fs from 'node:fs';
import satori from 'satori';
import { html } from 'satori-html';
const font = fs.readFileSync('./inter-bold.woff'); // Inter Bold, static WOFF (not WOFF2)
const svg = await satori(
html(`<div style="display:flex;flex-direction:column;width:1200px;height:630px;font-family:Inter;font-weight:700;color:#141a26;font-size:52px;line-height:1.15">Hello Inter</div>`),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: font,
weight: 700,
style: 'normal',
},
],
},
);
fs.writeFileSync('out.svg', svg);
console.log('curves', (svg.match(/[CcQq]/g) || []).length);
console.log('rects', (svg.match(/L[\d.]+ [\d.]+L[\d.]+ [\d.]+L[\d.]+ [\d.]+Z/g) || []).length);
0.33.4: out.svg is the 5-bar stacks (curves ≈ 0).
0.32.0: same WOFF produces real Inter outlines.
We also rasterised with @resvg/resvg-js. The bars are already in the SVG, so this is not a Resvg bug.
Font used in production: Inter Bold .woff, passed as a Node Buffer:
fonts: [{ name: 'Inter', data: fs.readFileSync('./inter-bold.woff'), weight: 700, style: 'normal' }]
Environment
- OS: macOS (Darwin)
- Node.js: 22.16.0
- Module system: ESM (
"type": "module")
- Broken:
satori@0.33.4
- Last good:
satori@0.32.0
satori-html@0.3.2 (HTML string → vnode)
- Font: Inter Bold, WOFF, weight 700, style
normal (not WOFF2, not variable)
- Canvas: 1200×630
- Found via
eleventy-plugin-og-image (dependency satori@^0.26.0, overridden). Reproduced with a direct satori() call, so the plugin is not required.
What we already ruled out
These still produced the 5-bar paths on 0.33.4:
- Template / Yoga / extra CSS — a single inline
display:flex div is enough.
line-height — 1.15 and 60px produced the same rectangle paths.
fflate 0.8.x — the parent app overrides fflate to 0.8.3. Pinning satori to 0.32.0 with that override still in place restored glyphs, so this is not a WOFF inflate break from fflate 0.8. (satori itself declares fflate@0.7.3.)
- Resvg / Sharp — the SVG from satori 0.33 already contains the bars.
Workaround
Pin satori to 0.32.0 (last release before #735):
{
"overrides": {
"satori": "0.32.0"
}
}
Debugging notes / questions
These would help isolate the HarfBuzz integration:
- WOFF-only, or TTF/OTF too? We did not convert this face to TTF. A same-weight Inter TTF vs WOFF comparison would tell you if outline export is broken for WOFF blobs specifically.
- Shared outline for every code point. Every letter is the same 5-rect path, only translated. That looks like one broken outline reused for every glyph ID, not per-glyph decode noise.
- Who draws the path? 0.33.4 still depends on both
harfbuzzjs and @shuding/opentype.js. Is HarfBuzz returning glyph IDs that then get the wrong outlines from opentype.js, or is hb_draw / outline export itself wrong for WOFF?
- WOFF vs glyf. 0.32 used opentype.js for both parse and path. 0.33 added HarfBuzz shaping. If HarfBuzz is given raw WOFF bytes (or decompressed glyf that still needs WOFF transforms), outline export could collapse to this rectangle pattern.
Related
I searched existing issues and did not find one about WOFF glyphs becoming stacked bars after 0.33.
Broken result below:

The plugin is being used with 11ty and https://github.com/KiwiKilian/eleventy-plugin-og-image
Summary
Upgrading satori from 0.32.0 to 0.33.4 turns Inter Bold WOFF text into solid “redacted” bars.
The SVG no longer contains real glyph outlines. Every character is the same five stacked rectangles, only translated along the x-axis. Adjacent stacks overlap, so the rasterised PNG looks like black bars instead of letters.
0.32.0 (same font, same markup, same rasteriser) renders Inter correctly.
This started in 0.33.0, which added HarfBuzz text shaping (#735, b2127fe, 2026-08-20). 0.33.1–0.33.4 did not fix it.
Satori’s README still lists TTF, OTF, and WOFF as supported (not WOFF2). This file is WOFF, not WOFF2, and not a variable font.
Versions
0.33.4 still depends on both
@shuding/opentype.js@1.4.0-beta.0andharfbuzzjs@0.10.0.Expected
<path d="…">is real Inter Bold outlines (curves, counters, distinct shapes per letter).Actual
<path>whosedis only M / L / Z rectangles.First glyph (satori 0.33.4):
The next characters are the same five bars, translated (~75.2, 101.2, 127.2, …). That shared outline + overlap is why the PNG looks redacted rather than like Inter.
Minimal reproduction (Node, ESM)
0.33.4:
out.svgis the 5-bar stacks (curves≈ 0).0.32.0: same WOFF produces real Inter outlines.
We also rasterised with
@resvg/resvg-js. The bars are already in the SVG, so this is not a Resvg bug.Font used in production: Inter Bold
.woff, passed as a NodeBuffer:Environment
"type": "module")satori@0.33.4satori@0.32.0satori-html@0.3.2(HTML string → vnode)normal(not WOFF2, not variable)eleventy-plugin-og-image(dependencysatori@^0.26.0, overridden). Reproduced with a directsatori()call, so the plugin is not required.What we already ruled out
These still produced the 5-bar paths on 0.33.4:
display:flexdiv is enough.line-height—1.15and60pxproduced the same rectangle paths.fflate0.8.x — the parent app overridesfflateto0.8.3. Pinning satori to 0.32.0 with that override still in place restored glyphs, so this is not a WOFF inflate break from fflate 0.8. (satori itself declaresfflate@0.7.3.)Workaround
Pin satori to 0.32.0 (last release before #735):
{ "overrides": { "satori": "0.32.0" } }Debugging notes / questions
These would help isolate the HarfBuzz integration:
harfbuzzjsand@shuding/opentype.js. Is HarfBuzz returning glyph IDs that then get the wrong outlines from opentype.js, or ishb_draw/ outline export itself wrong for WOFF?Related
I searched existing issues and did not find one about WOFF glyphs becoming stacked bars after 0.33.
Broken result below:

The plugin is being used with 11ty and https://github.com/KiwiKilian/eleventy-plugin-og-image