github.com/OrelSokolov/brotli.cr
0.3.0 / published Oct 16, 2026 / repository
Brotli decoder + encoder (RFC 7932) in pure Crystal, ported from google/brotli (c/dec, c/enc). Decoder — for WOFF2 font decoding; encoder — quality 0..11, one-shot API (Brotli.encode). The reference C implementation is used as a test oracle only.
brotli.cr
Brotli (RFC 7932) decoder and
encoder in pure Crystal, ported file-by-file from
google/brotli (c/dec, c/enc).
No C bindings and no runtime dependencies — the static dictionary, transforms and all tables are embedded.
Features
- Complete decoder: meta-block state machine, static dictionary with word transforms, context modeling, all prefix-code variants. Originally built for freetype.cr WOFF2 font decoding.
- Complete encoder: quality 0–11 (one-pass and two-pass fragment encoders, greedy/lazy hash matchers H2–H54, the q4–q9 metablock pipeline with block splitting and context clustering, q10/q11 Zopfli DP), window sizes lgwin 10–22, input modes generic / text / font.
- One-shot APIs: whole input in memory, whole output returned. Streaming (incremental) APIs are not implemented.
Installation
Add to shard.yml:
dependencies:
brotli:
github: OrelSokolov/brotli.cr
Then shards install and require "brotli".
Built and tested with Crystal 1.21.
Usage
Decode
require "brotli"
compressed = File.read("data.br")
begin
data = Brotli::Decoder.new.decode(compressed.to_slice)
rescue e : Brotli::DecodeError
puts "decode error #{e.code}" # code 2 (NEEDS_MORE_INPUT) = truncated stream
end
Encode
require "brotli"
data = File.read("data.txt").to_slice
# quality 0..11, lgwin 10..22, mode :generic/:text/:font
compressed = Brotli::Enc::Encoder.new(quality: 9).encode(data)
# roundtrip
Brotli::Decoder.new.decode(compressed) == data # => true
CLI
$ crystal bin/brotli_decode input.br [output] # no output arg -> stdout
$ crystal bin/brotli_encode input [output] -q 11 -w 22 -m text
brotli_encode flags: -q N quality (default 11), -w N window bits
(default 22), -m generic|text|font.
Status
Everything is validated by double roundtrips — every produced stream is decoded byte-exactly both by the reference C decoder (used as an oracle) and by this library's own decoder:
- upstream brotli testdata: 24 files × q0–q11
- Silesia corpus: 13 files (~211 MB) × q0–q11
- 173 WOFF2 fonts, an asset corpus (js/css/json/svg/UTF-8 text) × q0–q11
- edge cases: empty, 1 byte, random data, RLE, a 105 MB file (window wraparound at q0/q1), and a full lgwin 10–22 sweep (728/728)
crystal spec: 56 examples; differential decoder tests: 45/45
Compression ratio vs the C brotli CLI at equal quality (Silesia,
size sums): −0.5% (q0) to +4.0% (q11); text-like inputs at q4–q10 are
byte-identical to C. The remaining gap on large binaries comes from
simplified per-window-fragment metablocking.
Throughput on Silesia (280 MB, single-threaded, release builds): q1 2.4×, q5 1.4×, q9 0.5×, q11 0.9× of the C encoder's time (faster than C at q9/q11 — see above).
Project layout
src/brotli/— decoder (decoder.cr,huffman.cr,bit_reader.cr,transform.cr, …) and encoder (src/brotli/enc/)bin/—brotli_decode,brotli_encodeCLI frontendstools/— test drivers:diff_test.sh(decoder differential),encode_test.sh/encode_full_test.sh(double roundtrip),lgwin_test.sh,encode_ratio.sh(size vs C),bench.sh/enc_bench.sh(decoder/encoder benchmarks)upstream/— read-only clone of google/brotli used as the oraclePLAN.md— phase-by-phase porting log (in Russian)
License
MIT — see LICENSE. The port follows the google/brotli sources
(MIT-licensed) closely; the reference clone lives in upstream/brotli.