Skip to main content

خَطّ — Khatt

PyPI CI License: MIT Python Fonts: OFL

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; braille just 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=None turns 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 exactly width columns, 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)

Source distribution for khatt 0.2.0
File Size Uploaded Provenance
khatt-0.2.0.tar.gz 1.0 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for khatt 0.2.0
File Interpreter ABI Platform
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page