# ImageKit Multi-Language Text for Watermarks: Font Fallback Implementation

> Add multi-language text watermarks in ImageKit. Our font fallback system supports Latin, CJK, and Thai scripts for dynamic text rendering. Learn how it works.

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

---

**Yes, ImageKit supports multi-language text for watermarks through an automatic font-fallback system that renders Unicode text across Latin, CJK, and Thai scripts by dynamically selecting glyphs from multiple bundled fonts.**

The `hzbd/imagekit` Rust library handles international watermarking natively without requiring users to specify fonts per language. The engine automatically detects which font contains the necessary glyphs for each character in your watermark string, enabling seamless mixed-script text overlays.

## Font Bundle Architecture

ImageKit loads a multi-font asset bundle at initialization to cover major global scripts. In [`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs), the library embeds three primary font files using the RustEmbed asset system:

```rust
// src/lib.rs – loading the bundled fonts
let primary_font_data = Asset::get("Roboto-Regular.ttf")?;
let cjk_font_data    = Asset::get("SourceHanSansSC-Regular.otf")?;
let thai_font_data   = Asset::get("NotoSansThai-Regular.ttf")?;

```

These fonts are stored in an `Arc<Vec<Font>>` and passed to the processing pipeline. The **primary Latin font** (Roboto) handles Western European characters, while **SourceHanSansSC** covers Chinese, Japanese, and Korean glyphs, and **NotoSansThai** handles Thai script. This architecture ensures that common Unicode blocks are covered immediately upon startup without external dependencies.

## Character-Level Font Fallback Logic

When rendering watermarks, the processor examines each character individually to determine which font can render it. The implementation in [`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs) uses a linear search through the loaded font collection, falling back sequentially until it finds a valid glyph:

```rust
// src/processor.rs – per-character font fallback
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}')));

```

This **glyph lookup with fallback** operates at the character level. If the primary font lacks a specific Unicode codepoint—such as a Chinese character in a Latin string—the iterator checks the CJK font, then the Thai font. If no match exists, the system defaults to the Unicode replacement character (U+FFFD) to prevent crashes.

## Rendering Mixed-Language Watermarks

The `add_watermark` function receives the pre-selected fonts and renders the glyphs at the specified position. The engine automatically scales text if it would overflow image boundaries, preserving readability across different scripts.

You can invoke multi-language watermarks via the CLI using the `--watermark-text` flag. The following example applies a Chinese watermark:

```bash

# Run ImageKit on a directory, adding Chinese text as a watermark

imagekit \
    -i ./photos \
    -o ./output \
    --watermark-text "测试水印" \
    --watermark-position se \
    --font-size 48

```

For mixed-script content combining Latin, Thai, and Chinese characters, the engine renders each character from its respective font:

```bash
imagekit \
    -i ./photos \
    -o ./output \
    --watermark-text "Hello สวัสดี 测试" \
    --watermark-position north \
    --font-size 36 \
    --watermark-color "#ff0000ff"

```

Both commands succeed because the font-fallback system dynamically switches between Roboto, NotoSansThai, and SourceHanSansSC for each glyph.

## Extending Language Support

Adding support for additional scripts—such as Arabic, Cyrillic, or Devanagari—requires no code changes. Place a Unicode-compatible font file (e.g., `NotoSansArabic-Regular.ttf`) into the `assets/` directory and rebuild the project. The [`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs) loader will automatically include the new font in the `Arc<Vec<Font>>`, and the fallback logic in [`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs) will immediately utilize it for glyph resolution.

## Summary

- **Font bundling**: ImageKit embeds Roboto, SourceHanSansSC, and NotoSansThai in [`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs) to cover Latin, CJK, and Thai scripts.
- **Automatic fallback**: The processor in [`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs) iterates through all loaded fonts per character, selecting the first font containing the required glyph.
- **Mixed-script support**: Single watermark strings can combine multiple languages, with each character rendered from the appropriate bundled font.
- **Extensible architecture**: Adding fonts to the `assets/` directory extends Unicode coverage without modifying the Rust source code.

## Frequently Asked Questions

### Which languages are supported out of the box?

ImageKit ships with native support for **Latin-based languages** (English, Spanish, French, etc.), **Chinese (Simplified)**, **Japanese**, **Korean**, and **Thai**. These cover the majority of global users through the bundled Roboto, SourceHanSansSC, and NotoSansThai font files loaded in [`src/lib.rs`](https://github.com/hzbd/imagekit/blob/main/src/lib.rs).

### Can I add support for Arabic or Cyrillic scripts?

Yes. Adding Arabic, Cyrillic, Devanagari, or other scripts requires placing a compatible TrueType or OpenType font file into the repository's `assets/` directory. The font-fallback system in [`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs) automatically detects glyphs in new fonts without requiring modifications to the core logic.

### How does the font fallback system handle missing glyphs?

When a character cannot be found in any loaded font, the system defaults to the **Unicode replacement character** (U+FFFD, typically displayed as). This prevents rendering crashes and signals that the current font bundle lacks coverage for that specific Unicode codepoint.

### What happens if my watermark text overflows the image boundaries?

The `add_watermark` function in [`src/processor.rs`](https://github.com/hzbd/imagekit/blob/main/src/processor.rs) automatically scales down the text rendering to fit within the image dimensions while maintaining the aspect ratio. This ensures that long multi-language strings or large font sizes do not get clipped at the image edges.