خَطّ — 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, size 10
print(khatt.render("خط", style="braille", size=16)) # bigger, high effective resolution
print(khatt.render("القاهرة", font="cairo", style="ascii", threshold=0.4))
print(khatt.render("السلام عليكم ورحمة الله", width=60)) # wraps at 60 columns
print(khatt.render("مرحبا\nKhatt 2026", font="rubik")) # one art line per text line
art = khatt.render("مرحبا", style="braille")
khatt.art_to_image(art, fg="white", bg="#1e1e2e").save("art.png") # the art as a picture
image = khatt.render_image("مرحبا\nKhatt 2026") # the plain text: Pillow image, black on white
image.save("khatt.png")
khatt.list_fonts() # ['amiri', 'cairo', 'noto-naskh', 'rubik']
khatt.list_styles() # ['ascii', 'blocks', 'braille']
CLI
khatt "مرحبا" # render to the terminal
khatt "خط" --style braille --size 16
khatt "القاهرة" -f cairo -s ascii -t 0.4
echo "سلام" | khatt # stdin
printf 'سطر أول\nسطر ثاني' | khatt # multi-line input
khatt "خط" --output art.txt # save the art
khatt "خط" -s braille -o art.png # save the art as an image
khatt "خط" -s ascii -o art.png --fg "#e5e5e5" --bg "#1e1e2e" # terminal colours
khatt "خط" -o logo.png --bg none # transparent background
khatt "خط" -o khatt.png --png text # the plain high-resolution text instead
khatt --list-fonts
khatt --list-styles
Arabic diacritics (tashkeel) are stripped from text art by default — they're
illegible at character-grid resolution. Pass --keep-tashkeel (API:
render(..., keep_tashkeel=True)) to render them. Plain-text images
(--png text, render_image) keep them by default, since they have the
resolution to show them: pass --strip-tashkeel (API:
render_image(..., keep_tashkeel=False)) to remove them.
In art images, braille dots and shade blocks are drawn as shapes, so they stay
crisp at any size; ascii characters are drawn in a bold sans font. Each
character gets a cell twice as tall as it is wide (12 × 24 px by default;
API: cell_width=), matching the terminal the art was sized for.
Sizing and wrapping
Like FIGlet, letters have a fixed size and long text wraps:
size(--size) is the font size in character rows per em (default 10). It gives the same on-screen height in every style;braillejust packs 4× the dots into it.width(--width) is the maximum width. Lines wrap at spaces; a single word that can't fit on its own shrinks the whole render until it does. The CLI defaults to your terminal's width, the API to 80 columns;width=Noneturns wrapping off.- Line breaks in the input start new lines. Right-to-left paragraphs are right-aligned, left-to-right ones left-aligned.
--fit(API:size=None) is the 0.1.0 behaviour: scale each line to fill exactlywidthcolumns, without wrapping.
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
Metadata
Release files for khatt 0.2.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 | Provenance | |
|---|---|---|---|---|
| khatt-0.2.0.tar.gz | 1.0 MB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| khatt-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / khatt-0.2.0.tar.gz
| Download URL | khatt-0.2.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
589776a866ca859dcd2b266c44a25a7fe8629505892cbd2a894242deb6fb9293
|
|
BLAKE2b-256 checksum How to use checksums |
b66a47e5848b159472c89f45d0e941f8a1efb199a618c6d4920fbc7275cc4875
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 19, 2026.
Transparency logRelease files / khatt-0.2.0-py3-none-any.whl
| Download URL | khatt-0.2.0-py3-none-any.whl |
|---|---|
| Size | 833.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f37337e0dfeb04d1418a8f453ce56f086424f60b96b904774bfa6db04e8bb4ca
|
|
BLAKE2b-256 checksum How to use checksums |
b07839784bead7d23ddd9ec6b74db7d90dac3af6ddf4e12f850c96f1f551b99e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 19, 2026.
Transparency logSecurity for khatt 0.2.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for khatt-0.2.0.tar.gz
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 Oct 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.