Render Arabic text as ASCII/Unicode-block art — the Arabic FIGlet
Project description
خَطّ — 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/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file khatt-0.1.0.tar.gz.
File metadata
- Download URL: khatt-0.1.0.tar.gz
- Upload date:
- Size: 992.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25f4d02158a136c846253beea5c309bf594eaed6f2786653cec9edb6c864abba
|
|
| MD5 |
6fe7e7e2c70d79299038aa1cfdd1d07f
|
|
| BLAKE2b-256 |
19078215bf55ba0164dc10c7915ff5dca693cdb5afca2427bbdc5ed00096c54f
|
Provenance
The following attestation bundles were made for khatt-0.1.0.tar.gz:
Publisher:
release.yml on DrEssam97/khatt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
khatt-0.1.0.tar.gz -
Subject digest:
25f4d02158a136c846253beea5c309bf594eaed6f2786653cec9edb6c864abba - Sigstore transparency entry: 2147910528
- Sigstore integration time:
-
Permalink:
DrEssam97/khatt@077fa64865def03bea33ab563f404800ea70a3a3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/DrEssam97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@077fa64865def03bea33ab563f404800ea70a3a3 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file khatt-0.1.0-py3-none-any.whl.
File metadata
- Download URL: khatt-0.1.0-py3-none-any.whl
- Upload date:
- Size: 824.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79eb6d3abaf01eb249f6a45fecd97eb4302703c1040c22bca932e1827ce4d64f
|
|
| MD5 |
3bd8f4e44dbd831ec2e4a0dcc88d5466
|
|
| BLAKE2b-256 |
6e9fc7276bbc74e4415f8f41efd09934a4a9072a39aa5490cfccec62979b3c81
|
Provenance
The following attestation bundles were made for khatt-0.1.0-py3-none-any.whl:
Publisher:
release.yml on DrEssam97/khatt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
khatt-0.1.0-py3-none-any.whl -
Subject digest:
79eb6d3abaf01eb249f6a45fecd97eb4302703c1040c22bca932e1827ce4d64f - Sigstore transparency entry: 2147910601
- Sigstore integration time:
-
Permalink:
DrEssam97/khatt@077fa64865def03bea33ab563f404800ea70a3a3 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/DrEssam97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@077fa64865def03bea33ab563f404800ea70a3a3 -
Trigger Event:
workflow_dispatch
-
Statement type: