Skip to content

Fonts, i18n & RTL

Bundled font

PdfKmp ships Inter for Latin text — it's PdfFont.Default, used unless you say otherwise. On Desktop, Inter is subset-embedded (only the glyphs you draw), so text stays vector and the file stays small.

System fonts for non-Latin scripts

For CJK / Arabic / Persian on Android and iOS, use the cross-platform PdfFont.System* references. Each is a comma-separated candidate chain that resolves to whichever font the running platform has installed:

text("漢字、ひらがな、カタカナ — 中文 / 日本語 / 한국어") {
    font = PdfFont.SystemCJK         // -> "Noto Sans CJK SC, PingFang SC"
}
text("مرحبًا بكم في PdfKmp") {
    font = PdfFont.SystemArabic      // -> "Noto Sans Arabic, Geeza Pro"
}
text("سلام دنیا") {
    font = PdfFont.SystemPersian     // -> "Noto Naskh Arabic, Tahoma, Geeza Pro"
}

If a platform is missing every candidate the renderer falls back to Inter (and missing glyphs render as tofu boxes).

Desktop & Web caveat

There is no system font-registry lookup on Desktop (JVM) or Web (Wasm), so PdfFont.System* falls back to bundled Inter. On both, supply a PdfFont.Custom with the right script coverage for CJK / Arabic / Persian. The Web backend does embed fonts — a PdfFont.Custom is embedded as a TrueType subset, and non-WinAnsi text with no custom font falls back to a bundled-Inter subset, so Latin + Cyrillic render in the browser out of the box (Inter has no CJK/Arabic glyphs, so those still need a PdfFont.Custom).

Custom fonts (registerFont)

Register a .ttf / .otf to guarantee coverage:

val fontBytes: ByteArray = /* your own loader — assets, resources, network, … */
val notoCjk = PdfFont.Custom(name = "NotoSansCJK", bytes = fontBytes)

pdf {
    registerFont(notoCjk)
    page {
        text("永和九年") { font = notoCjk }
    }
}

Note

Custom fonts referenced anywhere in the document are picked up automatically — registerFont is only needed for a font that no current node references but that should still be embedded.

Right-to-left & shaping

Set direction on the text style; the default Auto detects Hebrew / Arabic from the first strong character and anchors Start / End / Justify to the correct edge:

text("שלום עולם — זהו טקסט בעברית הנצמד לימין") { fontSize = 14.sp }   // Auto detects RTL
text("مرحبا بالعالم — هذا نص عربي") {
    fontSize = 14.sp
    font = PdfFont.SystemArabic
}
text("English forced RTL") { direction = TextDirection.Rtl }
Platform Bidi reorder + Arabic shaping
Android Native
iOS Native
Desktop (JVM) Own bidi reorder + Arabic shaping pass (presentation forms, lam-alef ligatures) — PDFBox does neither
Web (Wasm) Embeds PdfFont.Custom / bundled-Inter subset; Latin + Cyrillic shape, but Arabic needs a PdfFont.Custom with the script's glyphs (see Web)

Kashida justification

For justified Arabic, set kashidaJustify = true on the text style. When the line's resolved direction is right-to-left and TextAlign.Justify is in effect, the line absorbs part of its slack by elongating words at cursive joining points — inserting tatweel (U+0640) between letters that join — before widening the inter-word gaps, the way Arabic typography expects:

text("النص العربي المضبوط بالكشيدة") {
    direction = TextDirection.Rtl
    align = TextAlign.Justify
    kashidaJustify = true
    font = PdfFont.SystemArabic
}

The insertion is a typographic approximation: a small joining table picks candidate positions and at most a couple of tatweels are added per gap; the platform shapers (PdfBox on JVM, native on Android/iOS) then render them into the cursive line. It is ignored for left-to-right text and when not justifying, and is false by default.

Mixed-style RTL in richText

Inside a single richText { } paragraph, RTL spans keep their authored (source) order rather than being reordered as one visual run. Author a whole RTL paragraph as a single text(...) when visual segment order matters.

Non-Latin coverage per platform

Script Android iOS Desktop (JVM) Web (Wasm)
Latin (Inter) ✅ (Helvetica for WinAnsi; Inter subset otherwise)
Cyrillic (Inter) ✅ embedded Inter subset
CJK / Arabic / Persian via System* ✅ if installed ✅ if installed ❌ falls back to Inter ❌ falls back to Inter
Custom .ttf via registerFont ✅ TrueType-outline only (CFF/OTF → Helvetica)

See also

  • Text & typography — justification, RTL alignment, soft hyphens.
  • Web (Kotlin/Wasm) — the Helvetica limitation in detail.
  • Samples.pageChrome(), Samples.newsletter(), Samples.showcase().