Show HN: Encode arbitrary data into a WOFF2 color font, decode via canvas

etherdream1 pts0 comments

GitHub - EtherDream/brpack: Encode arbitrary data into a WOFF2 color font (Brotli-compressed), decode it via canvas rendering at runtime. · GitHub

/" data-turbo-transient="true" />

Skip to content

Search or jump to...

Search code, repositories, users, issues, pull requests...

-->

Search

Clear

Search syntax tips

Provide feedback

--><br>We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Cancel

Submit feedback

Saved searches

Use saved searches to filter your results more quickly

-->

Name

Query

To see all available qualifiers, see our documentation.

Cancel

Create saved search

Sign in

/;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up

Appearance settings

Resetting focus

You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

{{ message }}

EtherDream

brpack

Public

Notifications<br>You must be signed in to change notification settings

Fork

Star

main

BranchesTags

Go to file

CodeOpen more actions menu

Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit

History<br>1 Commit<br>1 Commit

.github/workflows

.github/workflows

demo

demo

.gitignore

.gitignore

README.md

README.md

brpack.py

brpack.py

template.ttx

template.ttx

View all files

Repository files navigation

brpack

Encode arbitrary data into a font with color glyphs and package it as WOFF2, which uses Brotli internally.

At runtime, recover the original data by rendering the font onto a canvas.

Use Case

For larger assets served through CDNs without Brotli support, where the size savings justify the extra deployment complexity.

Demo

https://etherdream.github.io/brpack

Firefox 147+ and Safari 18.4+ support Brotli decompression via the DecompressionStream API, but Chrome does not yet support it. Use Chrome to see the WOFF2 path.

In the demo, a 412,226-byte text file compresses to 146,749 bytes with Brotli and 148,944 bytes as WOFF2 — about 1.5% larger than the raw Brotli payload.

As shown, characters 1, 2, and 3 are rendered as images.

How It Works

Raw data is encoded as RGB pixels, stored as PNG images, and embedded in the font’s CBDT table, much like emoji glyphs. The resulting font is then packaged as WOFF2, so it can be handled by the browser’s native Brotli-based WOFF2 pipeline.

PNG uses zlib internally, so its compression level is set to 0 (store only). This preserves the payload’s original redundancy, adding only a small amount of PNG and zlib container overhead.

To decode it, the page loads the WOFF2 font, renders the glyphs to a canvas, reads back the pixel data, and reconstructs the original byte stream. Since WOFF2 decoding is performed natively by the browser, the JavaScript glue code is only a few hundred bytes minified.

Why RGB Instead of RGBA?

Canvas 2D uses premultiplied alpha, which can alter transparent pixel values. To avoid this, only RGB channels are used.

PNG supports RGB natively, and the JavaScript decoder simply skips the alpha channel when reading back from the canvas.

The payload is padded to a multiple of 3 bytes for RGB packing. The current encoder uses spaces as padding, which tends to be benign for text-heavy inputs such as JS and CSS.

Installation

Clone this repository and run:

python -m venv .venv<br>source .venv/bin/activate

pip install fonttools brotli

Usage

Write a WOFF2 file:

./brpack.py demo/test.txt \<br>--woff2 demo/test.txt.woff2

To also write a plain Brotli file:

./brpack.py demo/test.txt \<br>--woff2 demo/test.txt.woff2 \<br>--br demo/test.txt.brotli

For debugging, use --ttf, --ttx, or --png-dir to write intermediate files:

./brpack.py demo/test.txt \<br>--woff2 demo/test.txt.woff2 \<br>--br demo/test.txt.brotli \<br>--ttf test.ttf \<br>--ttx test.ttx \<br>--png-dir .

Character Mapping

Each embedded image is limited to 255 × 255 pixels, so a single glyph can store at most 195,075 bytes. Larger payloads must be split across multiple glyph images and characters.

Use --chars to specify the characters mapped to those glyphs. The default is 1234567890 (~1.9 MB max). Add more unique characters for larger payloads.

The JavaScript side must also know which characters were used. For example, "123" means the payload spans three glyph images.

Size Optimization

Glyphs are rendered vertically on the canvas rather than horizontally so the byte stream stays sequential across images and compresses better.

To squeeze out every byte, nonessential font fields are zeroed out where possible, including creation and modification timestamps. This removes unnecessary variation and can slightly improve compressibility. See template.ttx for details.

License

MIT

About

Encode arbitrary data into a WOFF2 color font (Brotli-compressed), decode it via canvas rendering at...

woff2 brotli demo test font brpack

Related Articles