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().