خَطّ — Khatt
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)
Why FIGlet can't do this
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.
Install
pip install khatt # or: uv add khatt
From source:
git clone https://github.com/DrEssam97/khatt.git && cd khatt
uv sync
Quickstart
Python API
import 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']
CLI
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-styles
Arabic diacritics (tashkeel) are stripped by default — they're illegible at
character-grid resolution. Pass --keep-tashkeel (API: keep_tashkeel=True) to
render them.
Gradio app
uv sync --extra app
uv run python app.py
Bundled fonts and styles
| 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 |
Deploy the app to a Hugging Face Space
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
This prompts for a Space name on first run (it uses your huggingface-cli login
credentials) and pushes the repo as a Gradio-SDK Space. Alternatively, create a Gradio
Space on huggingface.co and git push this repository to it — app.py is picked up
automatically and dependencies install via requirements.txt (which just points at
pyproject.toml).
Development
uv sync --all-extras
uv run pre-commit install
The gate that must be green before every merge:
uv run ruff check . && uv run ruff format --check . && uv run mypy khatt && uv run pytest
Rendering changed intentionally? Regenerate the golden snapshots and commit the diff:
uv run pytest --update-golden
See CONTRIBUTING.md, PLAN.md and ROADMAP.md.
License
MIT for the code — see LICENSE. Bundled fonts are under the SIL Open Font
License 1.1 — see khatt/fonts/LICENSES/.
Metadata
Release files for khatt 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| khatt-0.1.0.tar.gz | 992.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| khatt-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / khatt-0.1.0.tar.gz
| Download URL | khatt-0.1.0.tar.gz |
|---|---|
| Size | 992.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
25f4d02158a136c846253beea5c309bf594eaed6f2786653cec9edb6c864abba
|
|
BLAKE2b-256 checksum How to use checksums |
19078215bf55ba0164dc10c7915ff5dca693cdb5afca2427bbdc5ed00096c54f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / khatt-0.1.0-py3-none-any.whl
| Download URL | khatt-0.1.0-py3-none-any.whl |
|---|---|
| Size | 824.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
79eb6d3abaf01eb249f6a45fecd97eb4302703c1040c22bca932e1827ce4d64f
|
|
BLAKE2b-256 checksum How to use checksums |
6e9fc7276bbc74e4415f8f41efd09934a4a9072a39aa5490cfccec62979b3c81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency log