15 releases (1 stable)
new 1.0.0 | Mar 13, 2025 |
---|---|
0.1.13 | Nov 6, 2024 |
0.1.12 | Oct 31, 2024 |
0.1.7 | Jan 18, 2024 |
0.1.2 | Feb 28, 2021 |
#50 in GUI
1,282 downloads per month
Used in 3 crates
(2 directly)
130KB
3K
SLoC
rust-fontconfig
Pure-Rust rewrite of the Linux fontconfig library (no system dependencies) - using allsorts as a font parser to support .woff
, .woff2
, .ttc
, .otf
and .ttf
NOTE: Also works on Windows, macOS and WASM - without external dependencies!
Motivation
There are a number of reasons why I want to have a pure-Rust version of fontconfig:
- fontconfig with all dependencies (expat and freetype) is ~190.000 lines of C (extremely bloated for what it does)
- fontconfig, freetype, expat and basically any kind of parsing in C is a common attack vector (via maliciously crafted fonts). The Rust version (allsorts) checks the boundaries before accessing memory, so attacks via font files should be less common.
- it gets rid of the cmake / cc dependencies necessary to build azul on Linux
- fontconfig isn't really a "hard" library to rewrite, it just parses fonts and selects fonts by name
- Rust has existing xml parsers and font parsers, just use those
- It allows fontconfig libraries to be purely statically linked
- Font parsing / loading can be easily multithreaded (parsing font files in parallel)
- It reduces the number of necessary non-Rust dependencies on Linux for azul to 0
- fontconfig (or at least the Rust bindings) do not allow you to store an in-memory cache, only an on-disk cache, requiring disk access on every query (= slow)
no_std
support ("bring your own font files") for WASM
Now for the more practical reasons:
- libfontconfig 0.12.x sometimes hangs and crashes (see issue)
- libfontconfig introduces build issues with cmake / cc (see issue)
- To support font fallback in CSS selectors and text runs based on Unicode ranges, you have to do several calls into C, since fontconfig doesn't handle that
- The rust rewrite uses multithreading and memory mapping, since that is faster than reading each file individually
- The rust rewrite only parses the font tables necessary to select the name, not the entire font
- The rust rewrite uses very few allocations (some are necessary because of UTF-16 / UTF-8 conversions and multithreading lifetime issues)
Usage
Basic Font Query
use rust_fontconfig::{FcFontCache, FcPattern};
fn main() {
// Build the font cache
let cache = FcFontCache::build();
// Query a font by name
let results = cache.query(
&FcPattern {
name: Some(String::from("Arial")),
..Default::default()
},
&mut Vec::new() // Trace messages container
);
if let Some(font_match) = results {
println!("Font match ID: {:?}", font_match.id);
println!("Font unicode ranges: {:?}", font_match.unicode_ranges);
println!("Font fallbacks: {:?}", font_match.fallbacks.len());
} else {
println!("No matching font found");
}
}
Find All Monospace Fonts
use rust_fontconfig::{FcFontCache, FcPattern, PatternMatch};
fn main() {
let cache = FcFontCache::build();
let fonts = cache.query_all(
&FcPattern {
monospace: PatternMatch::True,
..Default::default()
},
&mut Vec::new()
);
println!("Found {} monospace fonts:", fonts.len());
for font in fonts {
println!("Font ID: {:?}", font.id);
}
}
Font Matching for Multilingual Text
use rust_fontconfig::{FcFontCache, FcPattern};
fn main() {
let cache = FcFontCache::build();
let text = "Hello 你好 Здравствуйте";
// Find fonts that can render the mixed-script text
let mut trace = Vec::new();
let matched_fonts = cache.query_for_text(
&FcPattern::default(),
text,
&mut trace
);
println!("Found {} fonts for the multilingual text", matched_fonts.len());
for font in matched_fonts {
println!("Font ID: {:?}", font.id);
}
}
Using from C
Linking with the C API
The rust-fontconfig library provides C-compatible bindings that can be used from C/C++ applications.
Binary Downloads
You can download pre-built binary files from the latest GitHub release:
- Windows:
rust_fontconfig.dll
andrust_fontconfig.lib
- macOS:
librust_fontconfig.dylib
andlibrust_fontconfig.a
- Linux:
librust_fontconfig.so
andlibrust_fontconfig.a
Building from Source
Alternatively, you can build the library from source:
# Clone the repository
git clone https://github.com/maps4print/rust-fontconfig.git
cd rust-fontconfig
# Build with FFI support
cargo build --release --features ffi
# The generated libraries will be in target/release
Including in Your C Project
- Copy the header file from
ffi/rust_fontconfig.h
to your include directory - Link against the static or dynamic library
- Include the header file in your C code:
#include "rust_fontconfig.h"
Minimal C Example
#include <stdio.h>
#include "rust_fontconfig.h"
int main() {
// Build the font cache
FcFontCache cache = fc_cache_build();
if (!cache) {
fprintf(stderr, "Failed to build font cache\n");
return 1;
}
// Create a pattern to search for Arial
FcPattern* pattern = fc_pattern_new();
fc_pattern_set_name(pattern, "Arial");
// Search for the font
FcTraceMsg* trace = NULL;
size_t trace_count = 0;
FcFontMatch* match = fc_cache_query(cache, pattern, &trace, &trace_count);
if (match) {
char id_str[40];
fc_font_id_to_string(&match->id, id_str, sizeof(id_str));
printf("Found font! ID: %s\n", id_str);
// Get the font path
FcFontPath* font_path = fc_cache_get_font_path(cache, &match->id);
if (font_path) {
printf("Font path: %s (index: %zu)\n", font_path->path, font_path->font_index);
fc_font_path_free(font_path);
}
fc_font_match_free(match);
} else {
printf("Font not found\n");
}
// Clean up
fc_pattern_free(pattern);
if (trace) fc_trace_free(trace, trace_count);
fc_cache_free(cache);
return 0;
}
For a more comprehensive example, see the example.c file included in the repository.
Compiling the C Example
On Linux:
gcc -I./include -L. -o font_example example.c -lrust_fontconfig
On macOS:
clang -I./include -L. -o font_example example.c -lrust_fontconfig
On Windows:
cl.exe /I./include /Fe:font_example.exe example.c rust_fontconfig.lib
Performance
- cache building: ~90ms for ~530 fonts
- cache query: ~4µs
Features
- Font matching by name, family, style properties, or Unicode ranges
- Support for font weights (thin, light, normal, bold, etc.)
- Support for font stretches (condensed, normal, expanded, etc.)
- Multilingual text support with automatic font fallback
- In-memory font loading and caching
- Optional
no_std
support - C API for integration with non-Rust languages
License
MIT
Dependencies
~0.6–4.5MB
~91K SLoC