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...