Skip to main content

Parmoji

PyPI PyPI - Python Version Runs on Linux | macOS | Windows Arch x86-64 | ARM | AppleSilicon PyPI - Downloads PyPI - License

Build codecov Release Publish (PyPI) Publish (TestPyPI) TestPyPI

Description

Parmoji is a Pillow-based emoji rendering library (unicode + Discord custom emoji) with pluggable image sources (HTTP CDN and local font), LRU in-memory caching, and optional on-disk caching using XDG locations. It’s extracted from the par-term-emu project and packaged as a standalone library for reuse.

Buy Me A Coffee

Technology

  • Python 3.11+
  • Pillow
  • httpx (primary HTTP backend) and requests (selectable alternate backend)

Prerequisites

  • Python 3.11 or higher
  • uv package manager (recommended)

Features

  • Unicode and Discord emoji
  • Multi-line rendering with alignment and anchors
  • Fine control over emoji size/position per draw call
  • Multiple built-in emoji sources (Twemoji, Apple, Google, etc.)
  • LRU in-memory cache and optional disk cache (XDG)
  • PEP 561 typed (py.typed ships; inline types are visible to pyright/mypy)
  • Selectable HTTP backend (httpx default; switch to requests per source via HTTP_BACKEND)

Installation

uv add parmoji

Update

uv add parmoji -U

Quickstart

from parmoji import Parmoji
from parmoji.source import TwitterEmojiSource
from PIL import Image, ImageFont

text = "Hello 👋  from Parmoji 😎"

img = Image.new("RGBA", (480, 120), (255, 255, 255, 255))
font = ImageFont.load_default()

with Parmoji(img, source=TwitterEmojiSource, cache=True) as p:
    p.text((10, 20), text, fill=(0, 0, 0), font=font)

img.save("parmoji_example.png")

Emoji Sources and Caching

  • Default source is Twemoji (Twitter-style). Swap via Parmoji(image, source=AppleEmojiSource).
  • Disk cache: construct sources with disk_cache=True to persist assets.
  • Cache location: $XDG_CACHE_HOME/par-term/parmoji/<SourceClass>/ (or ~/.cache/par-term/parmoji/<SourceClass>/).
  • Clear failed CDN retries: source.clear_failed_cache().

Built-in sources

Parmoji ships twelve HTTP CDN styles (all routed through https://emojicdn.elk.sh/) plus one offline source. CDN sources are imported from parmoji.source; the offline source from parmoji.local_source.

Class STYLE
TwitterEmojiSource twitter
AppleEmojiSource apple
GoogleEmojiSource google
MicrosoftEmojiSource microsoft
SamsungEmojiSource samsung
WhatsAppEmojiSource whatsapp
FacebookEmojiSource facebook
MessengerEmojiSource messenger
JoyPixelsEmojiSource joypixels
OpenmojiEmojiSource openmoji
EmojidexEmojiSource emojidex
MozillaEmojiSource mozilla
LocalFontSource (renders from system fonts; no network)

Aliases: Twemoji and TwemojiEmojiSource are TwitterEmojiSource; Openmoji is OpenmojiEmojiSource; FacebookMessengerEmojiSource is MessengerEmojiSource; DiscordEmojiSource is the preferred public name for DiscordEmojiSourceMixin (every CDN style above already subclasses it, so Discord custom-emoji support is built in). TwitterEmojiSource is also the emoji set Discord uses.

Local font source (offline rendering)

For no-network or system-font rendering, use LocalFontSource. It renders emoji from locally installed fonts (Apple Color Emoji on macOS, Segoe UI Emoji on Windows, Noto Color Emoji on Linux by default) and needs no HTTP backend. It supports the same disk_cache and get_emoji(..., tight=, margin=) API as the CDN sources.

from parmoji.local_source import LocalFontSource

src = LocalFontSource(disk_cache=True)  # renders using a detected system emoji font
  • get_emoji always crops to the glyph's alpha bounding box (there is no CDN safe-zone to remove); tight/margin drive the crop padding and are honored, with results cached under a derived key.
  • Discord custom emoji are not supported by LocalFontSource: get_discord_emoji(...) always returns None. Use a CDN source if you need Discord emoji.
  • Disk-cache I/O is shared with the CDN sources via BaseSource._cache_get/_cache_put, so cache behavior is consistent across source kinds.

Tight Cropping (remove Twemoji safe-zone)

Some emoji sets (notably Twemoji) include transparent padding around glyphs. To have the visible emoji fill the cell area (useful for multi-cell flags), request a tightly cropped asset directly from the source:

from parmoji.source import TwitterEmojiSource

src = TwitterEmojiSource(disk_cache=True)
stream = src.get_emoji("🇺🇸", tight=True, margin=1)  # crop to alpha bbox + 1px margin
  • Cropped variants are cached on disk with a derived key, so subsequent calls don’t repeat work.
  • You can enable tight cropping by default via environment:
    • PARMOJI_TIGHT=1 to enable
    • PARMOJI_TIGHT_MARGIN=2 to set a default margin

Tuning HTTP behavior

httpx is the default backend for every CDN source; requests is an alternate, selected per subclass via the HTTP_BACKEND ClassVar. Both are hard dependencies (no urllib fallback).

from parmoji.source import TwitterEmojiSource

class RequestsTwemoji(TwitterEmojiSource):
    HTTP_BACKEND = "requests"
    TIMEOUT = 5.0           # per-request timeout (seconds)
    MAX_RETRIES = 5         # attempt count
    RETRY_BACKOFF = 0.5     # base for exponential backoff

The full set of public override knobs lives on HTTPBasedSource: REQUEST_KWARGS, TIMEOUT, MAX_RETRIES, RETRY_BACKOFF, HTTP_BACKEND, MAX_RESPONSE_BYTES, ALLOWED_SCHEMES, ALLOWED_HOSTS, and TRUST_ENV. The last three are security guards — ALLOWED_HOSTS is re-checked against the post-redirect URL, and any mirror/failover host you point a subclass at must be added there.

Architecture

For a high-level system design, components, rendering flow, and caching details, see the Architecture overview:

Development

make setup          # uv lock + uv sync
make checkall       # lint + format + typecheck + test
make test           # run tests
make package-all    # build wheel + sdist
  • Pre-commit: pre-commit install (then pre-commit run --all-files)
  • Type checking: uv run pyright
  • Lint/format: uv run ruff check --fix src/ tests and uv run ruff format src/ tests

CI / Releases

  • Build & test on push: .github/workflows/build.yml
  • Publish to TestPyPI (manual): .github/workflows/publish-dev.yml
  • Publish to PyPI (manual): .github/workflows/publish.yml (trusted publishing)
  • GitHub Release (manual): .github/workflows/release.yml

Limitations / Known Issues

  • Discord custom emoji require network access. Discord emoji are fetched from cdn.discordapp.com via an HTTP source; LocalFontSource.get_discord_emoji(...) always returns None.
  • Tight cropping exists to remove CDN safe-zone padding. Some sets (notably Twemoji) ship glyphs with transparent padding; get_emoji(..., tight=True, margin=N) trims to the alpha bounding box so the visible glyph fills its cell (useful for multi-cell flags). LocalFontSource already renders tightly cropped and uses tight/margin only for crop padding.
  • Thread safety. A single Parmoji instance is not safe for concurrent text() calls across threads. Create one instance per thread, or serialize access.
  • Single upstream CDN, no automatic failover. All twelve built-in styles route through one host (emojicdn.elk.sh); a host outage takes down every style. Subclass EmojiCDNSource with a custom BASE_EMOJI_CDN_URL (and add the host to HTTPBasedSource.ALLOWED_HOSTS) to point at a mirror.

What's New

For the full versioned changelog, see docs/RELEASE_NOTES.md.

  • 2.1.0 — Audit remediation: PEP 561 py.typed shipped; animated Discord emoji (.gif) and Parmoji.open() httpx-restore fixes; HTTP-backend hardening (decompression-bomb guards, host allowlist); bounded, thread-safe bytes caches; configurable HTTP_BACKEND; stroke_width-aware getsize; DiscordEmojiSource alias.
  • 2.0.8 — Updated dependencies and ensure Python 3.13 compatibility
  • 2.0.7 — Tight-cropping support for CDN sources (Twemoji, etc.):
    • get_emoji(..., tight=True, margin=1) trims Twemoji's transparent safe‑zone.
    • Cropped variants are cached with a derived key.
    • Env toggles: PARMOJI_TIGHT=1, PARMOJI_TIGHT_MARGIN=2.

License

MIT — see LICENSE.

Acknowledgements

Originally based on the Pilmoji project by jay3332; heavily refactored and optimized.

Download files

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

Source Distribution

parmoji-2.1.0.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

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

parmoji-2.1.0-py3-none-any.whl (37.1 kB view details)

Uploaded Python 3

File details

Details for the file parmoji-2.1.0.tar.gz.

File metadata

  • Download URL: parmoji-2.1.0.tar.gz
  • Upload date:
  • Size: 34.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for parmoji-2.1.0.tar.gz
Algorithm Hash digest
SHA256 f90416c5eafa39548dafebf24f118d2fbf2d4561939d97af55f5e5a0eea48dc8
MD5 de90290e2aed02ac670e79c764299bb3
BLAKE2b-256 cbc7e7f7c5551a394fea1f47bc30832f6e986b6fa9c3adf6bf488938e8c2cc50

See more details on using hashes here.

Provenance

The following attestation bundles were made for parmoji-2.1.0.tar.gz:

Publisher: publish.yml on paulrobello/parmoji

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

File details

Details for the file parmoji-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: parmoji-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 37.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for parmoji-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 74a8bc81cb913d336d4c11dd65046bb5645ad97e7ece953df142d218dcfcd804
MD5 da2096e182262d50b3be536509cac368
BLAKE2b-256 ad4298f55ee53f50b7aa3ed991f4a43a4949099865abc6150890f473f50f8cb4

See more details on using hashes here.

Provenance

The following attestation bundles were made for parmoji-2.1.0-py3-none-any.whl:

Publisher: publish.yml on paulrobello/parmoji

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

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 files

2.0.7

2 files

2.0.6

2 files

Supported by

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