Skip to content

Releases: fschutt/rust-fontconfig

rust-fontconfig 3.0.0

02 Apr 21:28

Choose a tag to compare

Breaking Changes

  • FcPattern has a new field: render_config: FcFontRenderConfig was added. Code
    using struct literal construction (FcPattern { name: ..., family: ... }) must add
    render_config: FcFontRenderConfig::default() or use ..Default::default().
  • UnicodeRange is 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.
  • ffi feature now implies async-registry: The C bindings include the full
    registry (background thread) API. Previously ffi only implied parsing + 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, tokenization
  • scoring -- Priority queue types and scoring heuristics for background loading
  • multithread -- Scout and builder thread implementations
  • disk_cache -- Disk cache serialization (behind cache feature)
  • utils -- Font file detection, family name normalization

Improvements

  • 14% code reduction (8018 -> 6905 lines) through systematic deduplication
  • FFI safety: Fixed Vec::from_raw_parts capacity mismatch UB in fc_registry_request_fonts
  • Code style: Flattened deeply nested if let patterns with let-else and functional chaining
  • CI: Added cross-compilation checks for WASM, iOS, iOS Simulator, Android targets;
    fixed Windows MSVC example execution; removed continue-on-error that hid failures

C Example

New ffi/example_registry.c with 3 demos showcasing the azul-style fast startup pattern:

  1. Azul-style fast startup: spawn threads -> request only needed fonts -> render first frame
    with 67/806 fonts loaded, remaining parse in background
  2. Incremental loading: request UI fonts (69 loaded) -> monospace for code editor (90) ->
    CJK for pasted Japanese text (116)
  3. 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

25 Nov 23:02

Choose a tag to compare

Breaking Changes

  • resolve_font_chain() signature changed: The text parameter 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: Use cache.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 to FontFallbackChain: 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 as Vec<(char, Option<(FontId, String)>)> for fine-grained control.

  • FontFallbackChain::resolve_char(): Resolve a single character to its font using the font chain.

  • CssFallbackGroup struct: 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

1.0.0

13 Mar 22:07

Choose a tag to compare

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)

  1. Put rust_fontconfig.h in the same directory and librust_fontconfig.so in the same directory
  2. Compile with
    • gcc -I. -L. -lrust_fontconfig example.c -o example
    • clang -I. -L. -lrust_fontconfig example.c -o example
  3. ./example to 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