A high-level, safe, zero-allocation TrueType font parser.
Can be used as Rust and as C library.
no_std
/WASM compatible.It's very hard to compare different libraries, so we are using table-based comparison. There are roughly three types of TrueType tables:
head
, OS/2
, etc.).glyf
, CFF
(kinda), hmtx
, etc.).cmap
, kern
, GPOS
, etc.).| Feature/Library | ttf-parser | FreeType | stb_truetype |
| ----------------- | :--------------------: | :-----------------: | :----------------------------: |
| Memory safe | ✓ | | |
| Thread safe | ✓ | | ~ (mostly reentrant) |
| Zero allocation | ✓ | | |
| Variable fonts | ✓ | ✓ | |
| Rendering | | ✓ | ~ (very primitive) |
| avar
table | ✓ | ✓ | |
| bdat
table | | ✓ | |
| bloc
table | | ✓ | |
| CBDT
table | ✓ | ✓ | |
| CBLC
table | ✓ | ✓ | |
| CFF
table | ✓ | ✓ | ~ (no seac
support) |
| CFF2
table | ✓ | ✓ | |
| cmap
table | ~ (no 8; Unicode-only) | ✓ | ~ (no 2,8,10,14; Unicode-only) |
| EBDT
table | | ✓ | |
| EBLC
table | | ✓ | |
| fvar
table | ✓ | ✓ | |
| gasp
table | | ✓ | |
| GDEF
table | ~ | | |
| glyf
table | ~1 | ✓ | ~1 |
| GPOS
table | | | ~ (only 2) |
| GSUB
table | | | |
| gvar
table | ✓ | ✓ | |
| head
table | ✓ | ✓ | ✓ |
| hhea
table | ✓ | ✓ | ✓ |
| hmtx
table | ✓ | ✓ | ✓ |
| HVAR
table | ✓ | ✓ | |
| kern
table | ✓ | ~ (only 0) | ~ (only 0) |
| maxp
table | ✓ | ✓ | ✓ |
| MVAR
table | ✓ | ✓ | |
| name
table | ✓ | ✓ | |
| OS/2
table | ✓ | ✓ | |
| post
table | ✓ | ✓ | |
| sbix
table | ~ (PNG only) | ~ (PNG only) | |
| SVG
table | ✓ | | ✓ |
| vhea
table | ✓ | ✓ | |
| vmtx
table | ✓ | ✓ | |
| VORG
table | ✓ | ✓ | |
| VVAR
table | ✓ | ✓ | |
| Language | Rust + C API | C | C |
| Dynamic lib size | <300KiB2 | ~760KiB3 | ? (header-only) |
| Tested version | 0.7.0 | 2.9.1 | 1.24 |
| License | MIT / Apache-2.0 | FTL / GPLv2 | public domain |
Legend:
Notes:
TrueType fonts designed for fast querying, so most of the methods are very fast. The main exception is glyph outlining. Glyphs can be stored using two different methods: using Glyph Data format and Compact Font Format (pdf). The first one is fairly simple which makes it faster to process. The second one is basically a tiny language with a stack-based VM, which makes it way harder to process.
The benchmark tests how long it takes to outline all glyphs in the font.
| Table/Library | ttf-parser | FreeType | stb_truetype |
| ------------- | -------------: | ---------: | -------------: |
| glyf
| 0.835 ms
| 1.194 ms
| 0.695 ms
|
| gvar
| 3.158 ms
| 3.594 ms
| - |
| CFF
| 1.251 ms
| 5.946 ms
| 2.862 ms
|
| CFF2
| 1.921 ms
| 7.001 ms
| - |
Note: FreeType is surprisingly slow, so I'm worried that I've messed something up.
And here are some methods benchmarks:
text
test outline_glyph_276_from_cff ... bench: 858 ns/iter (+/- 40)
test outline_glyph_276_from_cff2 ... bench: 793 ns/iter (+/- 30)
test from_data_otf_cff ... bench: 746 ns/iter (+/- 10)
test from_data_otf_cff2 ... bench: 709 ns/iter (+/- 75)
test outline_glyph_276_from_glyf ... bench: 606 ns/iter (+/- 10)
test outline_glyph_8_from_cff2 ... bench: 470 ns/iter (+/- 11)
test from_data_ttf ... bench: 351 ns/iter (+/- 5)
test glyph_name_276 ... bench: 299 ns/iter (+/- 4)
test outline_glyph_8_from_cff ... bench: 299 ns/iter (+/- 7)
test outline_glyph_8_from_glyf ... bench: 266 ns/iter (+/- 4)
test family_name ... bench: 198 ns/iter (+/- 3)
test glyph_index_u41 ... bench: 13 ns/iter (+/- 0)
test subscript_metrics ... bench: 2 ns/iter (+/- 0)
test glyph_hor_advance ... bench: 2 ns/iter (+/- 0)
test glyph_hor_side_bearing ... bench: 2 ns/iter (+/- 0)
test glyph_name_8 ... bench: 1 ns/iter (+/- 0)
test ascender ... bench: 1 ns/iter (+/- 0)
test underline_metrics ... bench: 1 ns/iter (+/- 0)
test strikeout_metrics ... bench: 1 ns/iter (+/- 0)
test x_height ... bench: 1 ns/iter (+/- 0)
test units_per_em ... bench: 0.5 ns/iter (+/- 0)
test width ... bench: 0.2 ns/iter (+/- 0)
family_name
is expensive, because it allocates a String
and the original data
is stored as UTF-16 BE.
glyph_name_8
is faster than glyph_name_276
, because for glyph indexes lower than 258
we are using predefined names, so no parsing is involved.
ttf-parser
is designed to parse well-formed fonts, so it does not have an Error
enum.
It doesn't mean that it will crash or panic on malformed fonts, only that the
error handling will boil down to Option::None
. So you will not get a detailed cause of an error.
By doing so we can simplify an API quite a lot since otherwise, we will have to use
Result<Option<T>, Error>
.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.