# How ImageKit Handles Font Fallback for International Characters

> ImageKit manages international character font fallback using a prioritized font list including Roboto, Source Han Sans SC, and Noto Sans Thai to ensure glyph display for diverse scripts.

- Repository: [hzbd/imagekit](https://github.com/hzbd/imagekit)
- Tags: internals
- Published: 2026-03-03

---

**ImageKit implements font fallback for international characters by iterating through a prioritized vector of three embedded fonts—Roboto for Latin scripts, Source Han Sans SC for CJK, and Noto Sans Thai for Thai—selecting the first font containing a valid glyph for each Unicode character.**

The open-source image processing library **ImageKit** (hzbd/imagekit) provides robust watermarking capabilities with built-in multilingual support. When rendering watermarks containing international characters, the system automatically resolves appropriate fonts without requiring user configuration. This article examines the technical implementation of font fallback for international characters based on the actual Rust source code.

## Font Asset Initialization in src/lib.rs

ImageKit loads its font assets at startup and stores them in a thread-safe `Arc<Vec<Font<'static>>>` structure. According to the source code in **[`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs)**, the system initializes three specific font files to cover major global scripts:

- **Roboto-Regular.ttf** – Primary font for Latin and most Western scripts
- **SourceHanSansSC-Regular.otf** – Dedicated fallback for CJK (Chinese-Japanese-Korean) characters
- **NotoSansThai-Regular.ttf** – Specialized fallback for Thai script

These fonts are bundled in the `assets/` directory and loaded into memory when the processor initializes. The vector maintains a strict order `[primary, cjk, thai]`, which determines the cascade priority during glyph lookup.

## The Fallback Algorithm in layout_text

The core font fallback logic resides in **[`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs)** within the `layout_text` function. When processing watermark text, the system iterates over each Unicode character and queries the font stack to locate a renderable glyph.

### Glyph Resolution Logic

For every character in the input string, ImageKit executes a `find_map` operation across the loaded fonts:

```rust
let (font_used, glyph) = fonts
    .iter()
    .find_map(|f| {
        let g = f.glyph(ch);
        if g.id() != rusttype::GlyphId(0) { Some((f, g)) } else { None }
    })
    .unwrap_or_else(|| (primary_font, primary_font.glyph('\u{FFFD}')));

```

The algorithm checks if `g.id()` differs from `rusttype::GlyphId(0)`, which represents the "missing glyph" placeholder in the **rusttype** library. The first font returning a valid glyph ID becomes `font_used` for that specific character.

### Replacement Character Safety Net

If no loaded font contains the requested glyph, the system falls back to the Unicode replacement character `U+FFFD` () from the primary Roboto font. This ensures the rendering pipeline never crashes due to missing glyphs, maintaining application stability even when processing unknown scripts.

## Supported Script Coverage

The three-font stack enables ImageKit to render mixed-language watermarks automatically. The fallback order follows this priority:

1. **Roboto** – Covers Latin, Cyrillic, and Greek alphabets
2. **Source Han Sans SC** – Handles Chinese, Japanese, and Korean han characters
3. **Noto Sans Thai** – Renders Thai consonants, vowels, and tone marks

This design allows seamless rendering of composite strings like "Hello 世界 สวัสดี" without manual font selection.

## Implementing Multilingual Watermarks

The font fallback system operates transparently whether using the command-line interface or the Rust API.

### Command-Line Usage

```bash
imagekit \
  --input-dir ./photos \
  --output-dir ./output \
  --watermark-text "Hello 世界 สวัสดี" \
  --font-size 48 \
  --watermark-position center \
  --watermark-color "#FF0000FF"

```

### Programmatic Usage (Rust)

```rust
use imagekit::{run, cli::Cli};

let cli = Cli {
    input_dir: "./photos".into(),
    output_dir: "./output".into(),
    watermark_text: Some("Hello 世界 สวัสดี".to_string()),
    font_size: 48,
    watermark_position: imagekit::cli::WatermarkPosition::Center,
    watermark_color: imagekit::cli::HexColor::from_rgba(255, 0, 0, 255),
    ..Default::default()
};

run(cli).expect("processing failed");

```

After `layout_text` calculates glyph positions and dimensions, the **`add_watermark`** function draws each resolved glyph onto the image buffer at the calculated coordinates.

## Summary

- **ImageKit** stores fonts in an `Arc<Vec<Font<'static>>>` initialized in [`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs) with three specific typefaces for global script coverage.
- The `layout_text` function in [`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs) implements cascade logic using `find_map` to select the first font containing a non-zero glyph ID for each character.
- The system falls back to Unicode replacement character `U+FFFD` when no bundled font supports a specific glyph, preventing rendering failures.
- Font priority follows the order: Roboto (Latin) → Source Han Sans SC (CJK) → Noto Sans Thai (Thai).

## Frequently Asked Questions

### What fonts does ImageKit include for international character support?

ImageKit bundles three open-source fonts: **Roboto-Regular.ttf** for Latin scripts, **SourceHanSansSC-Regular.otf** for CJK characters, and **NotoSansThai-Regular.ttf** for Thai script. These are loaded at startup from the `assets/` directory and stored in a shared vector.

### How does ImageKit handle characters not supported by any bundled font?

When no font in the stack contains a glyph for a specific Unicode character, the system renders the replacement character `U+FFFD` () from the primary Roboto font. This is implemented via `unwrap_or_else` in the `layout_text` function, ensuring the watermarking process never panics due to missing glyphs.

### Can I add custom fonts to ImageKit's fallback chain?

Currently, the font vector is hardcoded in [`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs) with the three specific typefaces. To extend support, you would need to modify the source code to load additional font files into the `Arc<Vec<Font>>` initialization and rebuild the project.

### Does font fallback affect watermark performance?

The per-character font lookup adds minimal overhead because it uses efficient iterator methods (`find_map`) over a small, fixed-size vector (three fonts). The glyph resolution runs once during the `layout_text` phase, and subsequent rendering in `add_watermark` uses the cached font references without repeated lookups.