Skip to main content

Render Arabic text as ASCII/Unicode-block art — the Arabic FIGlet

Project description

خَطّ — Khatt

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, 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

khatt-0.1.0.tar.gz (992.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

khatt-0.1.0-py3-none-any.whl (824.0 kB view details)

Uploaded Python 3

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

Hashes for khatt-0.1.0.tar.gz
Algorithm Hash digest
SHA256 25f4d02158a136c846253beea5c309bf594eaed6f2786653cec9edb6c864abba
MD5 6fe7e7e2c70d79299038aa1cfdd1d07f
BLAKE2b-256 19078215bf55ba0164dc10c7915ff5dca693cdb5afca2427bbdc5ed00096c54f

See more details on using hashes here.

Provenance

The following attestation bundles were made for khatt-0.1.0.tar.gz:

Publisher: release.yml on DrEssam97/khatt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for khatt-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 79eb6d3abaf01eb249f6a45fecd97eb4302703c1040c22bca932e1827ce4d64f
MD5 3bd8f4e44dbd831ec2e4a0dcc88d5466
BLAKE2b-256 6e9fc7276bbc74e4415f8f41efd09934a4a9072a39aa5490cfccec62979b3c81

See more details on using hashes here.

Provenance

The following attestation bundles were made for khatt-0.1.0-py3-none-any.whl:

Publisher: release.yml on DrEssam97/khatt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page