Releases: fschutt/rust-fontconfig
rust-fontconfig 3.0.0
Breaking Changes
FcPatternhas a new field:render_config: FcFontRenderConfigwas added. Code
using struct literal construction (FcPattern { name: ..., family: ... }) must add
render_config: FcFontRenderConfig::default()or use..Default::default().UnicodeRangeis now#[repr(C)]: Layout is guaranteed to match C{ uint32_t start; uint32_t end; }.
This was already the case in practice but is now an explicit contract.ffifeature now impliesasync-registry: The C bindings include the full
registry (background thread) API. Previouslyffionly impliedparsing+std.
New Features
Per-font rendering config from fonts.conf (#16)
On Linux, fonts.conf <match target="font"> rules are now parsed and exposed via
FcFontRenderConfig on each FcPattern. Supported properties:
| Field | Type | Description |
|---|---|---|
antialias |
Option<bool> |
Enable/disable antialiasing |
hinting |
Option<bool> |
Enable/disable hinting |
hintstyle |
Option<FcHintStyle> |
None, Slight, Medium, Full |
autohint |
Option<bool> |
Use autohinter |
rgba |
Option<FcRgba> |
Subpixel order (Rgb, Bgr, Vrgb, Vbgr) |
lcdfilter |
Option<FcLcdFilter> |
LCD filter mode |
embeddedbitmap |
Option<bool> |
Use embedded bitmaps |
embolden |
Option<bool> |
Synthetic bold |
dpi |
Option<f64> |
Per-font DPI override |
scale |
Option<f64> |
Scale factor |
minspace |
Option<bool> |
Minimum spacing |
All fields are None on macOS/Windows (use system defaults). New enums:
FcHintStyle, FcRgba, FcLcdFilter.
Async font registry C API
12 new C FFI functions expose the background-thread font loading API:
// Lifecycle
FcFontRegistry fc_registry_new(void);
void fc_registry_spawn(FcFontRegistry registry);
void fc_registry_shutdown(FcFontRegistry registry);
void fc_registry_free(FcFontRegistry registry);
// Priority-based font loading (blocks only for requested fonts)
FcFontChain* fc_registry_request_fonts(registry, stacks, counts, num, &out);
void fc_registry_chains_free(FcFontChain* chains, size_t count);
// Status
bool fc_registry_is_scan_complete(FcFontRegistry registry);
bool fc_registry_is_build_complete(FcFontRegistry registry);
// Query
FcFontMatch* fc_registry_query(FcFontRegistry registry, const FcPattern* pattern);
FcFontInfo* fc_registry_list_fonts(FcFontRegistry registry, size_t* count);
FcFontChain fc_registry_resolve_font_chain(registry, families, count, weight, italic, oblique);
FcFontPath* fc_registry_get_font_path(FcFontRegistry registry, const FcFontId* id);
FcFontMetadata* fc_registry_get_metadata(FcFontRegistry registry, const FcFontId* id);
FcFontRenderConfig fc_registry_get_render_config(FcFontRegistry registry, const FcFontId* id);
FcFontCache fc_registry_snapshot(FcFontRegistry registry);Unicode script detection helpers
New public functions for checking Unicode block coverage:
pub fn has_cjk_ranges(ranges: &[UnicodeRange]) -> bool;
pub fn has_arabic_ranges(ranges: &[UnicodeRange]) -> bool;
pub fn has_cyrillic_ranges(ranges: &[UnicodeRange]) -> bool;
pub fn has_hebrew_ranges(ranges: &[UnicodeRange]) -> bool;
pub fn has_thai_ranges(ranges: &[UnicodeRange]) -> bool;New public modules
Internal code has been restructured into public modules for better organization:
config-- OS-specific font directories, generic family expansion, tokenizationscoring-- Priority queue types and scoring heuristics for background loadingmultithread-- Scout and builder thread implementationsdisk_cache-- Disk cache serialization (behindcachefeature)utils-- Font file detection, family name normalization
Improvements
- 14% code reduction (8018 -> 6905 lines) through systematic deduplication
- FFI safety: Fixed
Vec::from_raw_partscapacity mismatch UB infc_registry_request_fonts - Code style: Flattened deeply nested
if letpatterns withlet-elseand functional chaining - CI: Added cross-compilation checks for WASM, iOS, iOS Simulator, Android targets;
fixed Windows MSVC example execution; removedcontinue-on-errorthat hid failures
C Example
New ffi/example_registry.c with 3 demos showcasing the azul-style fast startup pattern:
- Azul-style fast startup: spawn threads -> request only needed fonts -> render first frame
with 67/806 fonts loaded, remaining parse in background - Incremental loading: request UI fonts (69 loaded) -> monospace for code editor (90) ->
CJK for pasted Japanese text (116) - Old vs new API comparison: blocking
fc_cache_build()(806 fonts) vs async
fc_registry_request_fonts()(67 fonts, rest in background)
Migration from 2.0.0
// Before (2.0.0)
let pattern = FcPattern {
name: Some("Arial".into()),
family: Some("Arial".into()),
..Default::default()
};
// After (3.0.0) -- add render_config or use ..Default::default()
let pattern = FcPattern {
name: Some("Arial".into()),
family: Some("Arial".into()),
..Default::default() // render_config defaults to all-None
};If you were constructing FcPattern with all fields explicitly, add:
render_config: FcFontRenderConfig::default(),1.2.0
Breaking Changes
-
resolve_font_chain()signature changed: Thetextparameter has been removed. Font chains are now resolved based on CSS properties only (font-family, weight, italic, oblique), not text content.- cache.resolve_font_chain(&families, text, weight, italic, oblique, &mut trace); + cache.resolve_font_chain(&families, weight, italic, oblique, &mut trace);
-
query_all()method removed: Usecache.list()with filtering instead.- let fonts = cache.query_all(&pattern, &mut trace); + let fonts: Vec<_> = cache.list().into_iter() + .filter(|(pattern, _id)| /* your filter */) + .collect();
-
query_for_text()moved toFontFallbackChain: Text-to-font resolution now requires a font chain first.- let fonts = cache.query_for_text(&pattern, text, &mut trace); + let chain = cache.resolve_font_chain(&families, weight, italic, oblique, &mut trace); + let font_runs = chain.query_for_text(&cache, text);
Added
-
FontFallbackChain::resolve_text(): Returns per-character font assignments asVec<(char, Option<(FontId, String)>)>for fine-grained control. -
FontFallbackChain::resolve_char(): Resolve a single character to its font using the font chain. -
CssFallbackGroupstruct: Groups fonts by their CSS source name, making it clear which CSS font-family each font came from. -
Font chain caching: Identical CSS font-family stacks now share cached font chains, improving performance when the same fonts are used with different text content.
Changed
-
Architecture: The new two-step workflow (chain resolution → text querying) better matches CSS/browser font handling semantics and enables better caching.
-
Performance: Font chains are now cached by CSS properties, avoiding redundant font resolution for the same font-family declarations.
Links
- Documentation: https://docs.rs/rust-fontconfig
- Crate: https://crates.io/crates/rust-fontconfig
- Repository: https://github.com/fschutt/rust-fontconfig
1.0.0
rust-fontconfig v1.0.0 Release Notes
Overview
rust-fontconfig is a pure-Rust alternative to the Linux fontconfig library with no system dependencies. It supports .woff, .woff2, .ttc, .otf, and .ttf formats and works on Windows, macOS, and WASM environments.
Key Features
- Zero external dependencies - uses Rust's native libraries only
- Cross-platform support (Linux, Windows, macOS, and WASM)
- Memory-safe font parsing via allsorts (reduces risk of font-based attacks)
- Multithreaded font loading and parsing
- In-memory font caching for improved performance
- Flexible font matching by name, family, style, and Unicode ranges
- Automatic fallback selection for multilingual text
- C API for integration with non-Rust languages
Improvements Over C Implementation
- Smaller codebase than original fontconfig (~190,000 lines of C)
- Multithreaded parsing for faster initialization
- Memory-mapping for efficient file access
- Selective table parsing (only reads tables needed for matching)
- Lower memory consumption due to fewer allocations
- In-memory caching (vs disk-only in original implementation)
Usage (Rust)
use rust_fontconfig::{FcFontCache, FcPattern};
fn main() {
let cache = FcFontCache::build();
// Simple query
let results = cache.query(
&FcPattern {
name: Some(String::from("Arial")),
..Default::default()
},
&mut Vec::new()
);
if let Some(font_match) = results {
println!("Font match ID: {:?}", font_match.id);
}
// find all monospace fonts
let fonts = cache.query_all(
&FcPattern {
monospace: PatternMatch::True,
..Default::default()
},
&mut Vec::new()
);
println!("Found {} monospace fonts", fonts.len());
// Multilingual text support with fallback fonts
let matched_fonts = cache.query_for_text(
&FcPattern::default(),
"Hello 你好 Здравствуйте",
&mut Vec::new()
);
println!("You need {} fonts to render this text", matched_fonts.len());
}Usage (C)
- Put
rust_fontconfig.hin the same directory andlibrust_fontconfig.soin the same directory - Compile with
gcc -I. -L. -lrust_fontconfig example.c -o exampleclang -I. -L. -lrust_fontconfig example.c -o example
./exampleto run
#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;
}Links
- Documentation: https://docs.rs/rust-fontconfig
- Crate: https://crates.io/crates/rust-fontconfig
- Repository: https://github.com/fschutt/rust-fontconfig