md2png-lite
Pure Python Markdown renderer that outputs PNG images.
Goals
- No browser dependency
- No WeasyPrint dependency
- Cross-platform
pip install - Configurable themes
- Markdown + syntax highlight + basic math support
Rendering approach
- It parses Markdown with
markdown-it-py+dollarmath, maps tokens into a small document model, then lays out headings, paragraphs, tables, quotes, images, and code blocks directly on a Pillow canvas. No browser and no HTML screenshot step. - Font selection is based on glyph coverage instead of one fixed font. It auto-discovers system/custom fonts, scores coverage with
matplotlib.ft2font, preselects body/heading/code fonts, then falls back per text run for CJK and missing glyphs. - Emoji are detected as Unicode sequences and rendered through cached Twemoji PNG assets, so skin-tone modifiers and common ZWJ sequences do not depend on local color-font availability.
- There are now two separate font routes: the default package stays on local system fonts, while the optional
NotoSansextra syncs a curated Noto pack into a user cache directory and prefers that pack at render time. - Math is rendered with
matplotlib.mathtextinto transparent bitmaps, with a light LaTeX sanitizing pass and a readable plain-text fallback for unsupported syntax. - Syntax highlight comes from
Pygmentstoken streams, while wrapping and painting are handled by the renderer itself, so code blocks do not depend on browser CSS/JS.
Install
pip install md2png-lite
# local development
uv sync
System-font route:
pip install md2png-lite
Curated Noto route:
pip install 'md2png-lite[NotoSans]'
The NotoSans extra does not embed fonts into the wheel. It installs the sync helpers and downloads the curated Noto pack into the local cache on first use.
CLI
uv run md2png-lite input.md -o output.png --theme paper
Use the mobile summary preset with theme-driven width / padding / scale defaults:
uv run md2png-lite input.md -o output.png --theme mobile-summary
If you want serialized image content on stdout instead of a file:
uv run md2png-lite input.md --stdout-format base64
uv run md2png-lite input.md --stdout-format json
base64: prints only the PNG base64 payloadjson: prints the full payload withok,renderer,mime_type, andbase64
Choose the font route explicitly at call time:
uv run md2png-lite input.md -o output.png --font-pack system
uv run md2png-lite input.md -o output.png --font-pack noto
Load custom fonts explicitly:
uv run md2png-lite input.md -o output.png \
--font-path ./fonts/NotoSansCJKsc-Regular.otf \
--font-dir ./fonts
Stress Test
python3 scripts/benchmark_examples.py --repeat 3 --keep
This renders the bundled stress samples and prints timing / output size.
Visual Check Samples
Render the smaller inspection-focused samples:
python3 scripts/render_examples.py
Use a custom glob when needed:
python3 scripts/render_examples.py --pattern 'sample_*.md'
Python
from md2png_lite import render_markdown_image
payload = render_markdown_image("# Hello\n\n```python\nprint('hi')\n```")
Choose the route per call:
payload = render_markdown_image(markdown, font_pack="system")
payload = render_markdown_image(markdown, font_pack="noto")
payload = render_markdown_image(markdown, theme="mobile-summary")
Returned payload shape:
{
"ok": True,
"renderer": "md2png-lite",
"mime_type": "image/png",
"base64": "...",
}
Supported syntax
- Headings
- Paragraphs
- Bullet / ordered lists
- Block quotes
- Horizontal rules
- Fenced code blocks
- Custom
summaryfenced blocks rendered as editorial summary cards - Inline code
- Tables
- Links / emphasis / strong / strikethrough
- Inline and block math via
matplotlib.mathtext - Local /
data:/ remote images
Themes
paper: current warm reading style; legacy alias阅读器remains availablegithub-light: GitHub-like daytime themegithub-dark: GitHub-like dark themesolarized-light: classic Solarized light themegraphite: existing dark editorial thememobile-summary: phone-friendly editorial summary theme with large type and neutral#f2f2f2surfaces
Font discovery
- Auto-discovers system fonts on macOS / Windows / Linux
- Supports two separate routes:
systemand syncednoto - Chooses fonts per text run instead of forcing one global font
- Prioritizes CJK-capable fonts for Chinese / Japanese / Korean text
font_pack="system"stays on platform fonts onlyfont_pack="noto"syncs the curated Noto pack into the local cache and prefers it- Supports custom fonts through CLI:
--font-path,--font-dir - Supports route selection through CLI:
--font-pack auto|system|noto - Supports provider config:
md2png_lite.font_paths,md2png_lite.font_dirs,md2png_lite.font_pack - Supports environment variables:
MD2PNG_LITE_FONT_PATHS,MD2PNG_LITE_FONT_DIRS,MD2PNG_LITE_FONT_PACK - Supports emoji cache control through environment variables:
MD2PNG_LITE_EMOJI_CACHE_DIR,MD2PNG_LITE_EMOJI_SOURCE
Synced Noto font license note:
licenses/NOTO_CJK_LICENSE.txt
Boundaries
- Math support follows
matplotlib.mathtext, not full LaTeX - HTML blocks are rendered as plain text
- Deeply nested lists / tables are supported, but layout remains image-oriented rather than browser-perfect
Metadata
Release files for md2png-lite 0.2.4
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 | Actions |
|---|---|---|---|---|
| md2png_lite-0.2.4.tar.gz | 40.4 kB | With provenance | Details for md2png_lite-0.2.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| md2png_lite-0.2.4-py3-none-any.whl | Python 3 | none | any | Details for md2png_lite-0.2.4-py3-none-any.whl |
Total release size: 84.4 kB
Release files / md2png_lite-0.2.4.tar.gz
| Download URL | md2png_lite-0.2.4.tar.gz |
|---|---|
| Size | 40.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dc136a1d4e86773e28360850ea42dd5b996f78e2ef31781ffe3bbef26ebcb562
|
|
BLAKE2b-256 checksum How to use checksums |
5c5cecc0741ca8725d1f7073db96fc54f2393d7f6c5a30e672fd0575129a21e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Apr 9, 2026.
Transparency logRelease files / md2png_lite-0.2.4-py3-none-any.whl
| Download URL | md2png_lite-0.2.4-py3-none-any.whl |
|---|---|
| Size | 44.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2db5648b14cc0939a854be352746a80d6ddd39cde7f68a467472d09b18623ded
|
|
BLAKE2b-256 checksum How to use checksums |
13822fba9cf09363cbbad6c4f121f088a6696774a67178314056e1973fd71ad5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 Apr 9, 2026.
Transparency logSecurity for md2png-lite 0.2.4
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 md2png_lite-0.2.4.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 Apr 9, 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.