Render Arabic text as ASCII/Unicode-block art — the Arabic FIGlet that FIGlet can't be.
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⡄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢺⣿⣿⣄
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠟
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣄⡀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⡗
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⠟
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣄⣀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡏⠀⠀⠀⠀⣀⣤⣶⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⣠⣶⣿⣿⣷⣶⣤⣄⣀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠇⠀⣀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣀⣀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣤⣾⣿⠟⠋⠉⠀⠀⠀⠀⠙⢿⣿⣿⡟⠀⠀⠘⠉⠉⠉⠉⠉⠉⠙⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⡶
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⠟⠋⠀⠀⠀⠀⠀⠀⣀⣠⣤⣶⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁
⣀⣀⣀⣀⡀⠀⣀⣀⣀⣴⣿⣿⣥⣤⣤⣤⣴⣶⣶⣿⣿⣿⣿⣿⣿⣋⣀⡀⠀⠀⣀⣀⣀⣀⣤⣤⣶⣾⣿⣿⣿⡿⠿⠛⠋⠉⠁⠀⠀⠀⠀⠀⠀⠈
⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠉
⠀⠀⠙⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠛⠛⠉⠛⠛⠻⠿⠿⠿⠿⠿⠿⠿⠿⠟⠛⠉⠁
(that's «خط» — "khatt", meaning line/script/calligraphy — Amiri font, braille style)
-%+.
:@. =: *+-- =@ *# =@
-@. +: += =@ #% +@
%+ -@. =% =**+***: @# =@ *% -*****%+ -%. +@
@* -@. +@ +@. %* =@ *% -@- @* -@. +@
@+ -@. +@ .@- %* =@ *% +@ @* -@. +@
@+ -@. +@ :@- %# =@ #% :@- @* -@. +@
@* :%****##**********%: +**%#***#% +# -*****%#**+*% =%
.@= :- :- -.
+*%+ -* =+ *:
(«ايه الأخبار» — "What's the news?" — Cairo font, ascii style)
FIGlet fonts map one character to one fixed glyph. Arabic doesn't work that way:
- Every letter has up to four positional forms (isolated / initial / medial / final).
- Some letter pairs form mandatory ligatures — ل + ا must become لا.
- The script is cursive: letters connect, and the connections carry the word's shape.
- Text runs right-to-left, and real text mixes RTL words with LTR numbers and names.
Khatt sidesteps glyph tables entirely with a rasterize-then-map pipeline:
text ──► HarfBuzz shaping ──► FreeType rasterization ──► density mapping ──► art
(positional forms, (real Arabic fonts, (blocks / ascii /
ligatures, bidi) hi-res grayscale) braille ramps)
The result is genuine calligraphic letterforms — from real fonts — approximated in terminal characters.
pip install khatt # or: uv add khattFrom source:
git clone https://github.com/DrEssam97/khatt.git && cd khatt
uv syncimport khatt
print(khatt.render("مرحبا")) # blocks style, 80 columns
print(khatt.render("خط", style="braille", width=60)) # high effective resolution
print(khatt.render("القاهرة", font="cairo", style="ascii", threshold=0.4))
khatt.list_fonts() # ['amiri', 'cairo', 'noto-naskh', 'rubik']
khatt.list_styles() # ['ascii', 'blocks', 'braille']khatt "مرحبا" # render to the terminal
khatt "خط" --style braille --width 60
khatt "القاهرة" -f cairo -s ascii -t 0.4
echo "سلام" | khatt # stdin
khatt "خط" --output art.txt # save the art
khatt "خط" --output khatt.png # save the rendered text as an image
khatt --list-fonts
khatt --list-stylesArabic diacritics (tashkeel) are stripped by default — they're illegible at
character-grid resolution. Pass --keep-tashkeel (API: keep_tashkeel=True) to
render them.
uv sync --extra app
uv run python app.py| Font | Family | Character |
|---|---|---|
amiri (default) |
Amiri | Classical naskh, rich calligraphic detail |
cairo |
Cairo | Geometric, modern, bold strokes |
noto-naskh |
Noto Naskh Arabic | Clean, highly legible naskh |
rubik |
Rubik | Strong Latin + Arabic pairing for mixed text |
All fonts are licensed under the SIL Open Font License 1.1, preserved verbatim in
khatt/fonts/LICENSES/.
| Style | Ramp | Notes |
|---|---|---|
blocks |
░ ▒ ▓ █ |
Default; reads well at any size |
ascii |
. : - = + * # % @ |
Pure ASCII, maximum compatibility |
braille |
⠁ … ⣿ |
2×4 dots per cell — 8× the effective resolution |
app.py follows the Spaces convention (single file at the repo root), so the whole
repository deploys unchanged with one command:
uv run --extra app gradio deploy