HTML+CSS → PNG/JPEG 图像渲染器。轻量级,无需无头浏览器,线程安全。需要 Python ≥ 3.10。
examples/ 下有若干独立可运行的示例,各渲染一张图(无浏览器、纯 pylitehtml,与 tests/ 分开)。
| 示例 | 说明 | 渲染结果 |
|---|---|---|
showcase_html.py |
HTML/CSS 能力参考:排版、颜色/渐变、表格、代码、列表、进度条 + 真实 HTTPS 图片 + data: URI 图标 |
![]() |
showcase_css.py |
More CSS:conic/radial 渐变、边框样式、position、::before、text-overflow:ellipsis、white-space:pre、vertical-align |
![]() |
showcase_markdown.py |
内置零依赖 Markdown 转换器的端到端输出 | ![]() |
showcase_jinja2.py |
Jinja2 数据驱动模板(循环 / 条件 / 过滤器 / autoescape,一张订单发票) | ![]() |
markdown_full_converter.py |
用 markdown-it-py 接入完整 GFM(脚注 / 定义列表 / Pygments 语法高亮;需 pip install markdown-it-py mdit-py-plugins pygments) |
![]() |
render_markdown_doc.py |
渲染一篇真实复杂文档(markdown.md:多表格 / 嵌套列表 / 多语言代码 / Mermaid 源码 / 数学 TeX / 行号引用代码块) |
![]() |
中英文混排共享同一基线(不会出现中文“飞起来”的错位);文本测量为子像素精度; 模板变量经 autoescape 安全转义;抓取失败的远程图片自动跳过,不影响整图渲染。
python examples/showcase_html.py # 生成 assets/showcase_html.png(需联网加载真实图片)
python examples/showcase_css.py
python examples/showcase_markdown.py
python examples/showcase_jinja2.py同一篇文档各渲染 30 次(macOS arm64,width=720;基准脚本
bench/bench_vs_playwright.py):
| 指标 | pylitehtml | Playwright(无头 Chromium) |
|---|---|---|
| 单次渲染(warm,均值) | ≈ 15.5 ms | ≈ 21 ms |
| 首屏 / 冷启动 | ≈ 0.2 s | ≈ 0.25 s(OS 已缓存)/ 首次可达 ~3.4 s |
| 额外依赖 | 无(仅系统 Cairo/Pango 等) | 需下载 Chromium ~190 MB+,每次渲染有浏览器进程内存开销 |
| JavaScript | ❌ 不执行 | ✅ 可执行(KaTeX/Mermaid 等) |
要点:稳态吞吐 pylitehtml 约快 1.4×;真正的差距在部署体积与冷启动 —— 无需 ~190MB 的 Chromium、无浏览器进程,特别适合容器 / Serverless / 边缩缩容场景。 需要执行 JS(数学排版、Mermaid 绘图)时仍应选 Playwright。
pip install playwright && playwright install chromium # 或: uv pip install --group bench
python bench/bench_vs_playwright.py 30pip install pylitehtmlimport pylitehtml
# 最简单:HTML 片段 → PNG(自动套用一套干净的默认样式)
png = pylitehtml.html_to_png("<h1>Hello</h1><p>世界</p>")
open("out.png", "wb").write(png)
# Markdown → PNG(零依赖内置转换器)
from pylitehtml.markdown import markdown_to_png
png = markdown_to_png("# 标题\n\n- 列表\n- [x] 任务")
# 需要精细控制时用底层接口:render / Renderer(不注入默认样式)
png = pylitehtml.render("<h1>Hello</h1>", width=800)
r = pylitehtml.Renderer(width=800) # 复用,字体只加载一次
jpg = r.render("<h1>Hello</h1>", fmt="jpeg", quality=90)
# 本地 HTML 文件(自动解析相对路径的 CSS/图片)+ Jinja2 模板
png = r.render_file("project/index.html")
png = r.render_file("project/order.html", title="订单", items=[...])
# 异步(不阻塞事件循环)
import asyncio
png = await asyncio.to_thread(r.render, "<h1>Hello</h1>")render() 是最底层接口(HTML 字符串 → 图片)。在它之上提供了两个高层便捷函数。
from pylitehtml import html_to_png, html_to_image, wrap_html, DEFAULT_CSS
# 传入“片段”时,自动套用一套干净的 GitHub 风默认样式
png = html_to_png("<h1>标题</h1><p>正文 <code>code</code></p>", width=720)
# 传入完整文档(含 <html>/<body>)时,按原样渲染,不再注入默认样式
png = html_to_png("<html><body style='background:#000'>…</body></html>")
# HiDPI:scale 同时放大画布宽度与(默认样式的)根字号,em 布局等比放大
png = html_to_png(fragment, width=720, scale=2)
# 追加/覆盖样式;或拿原始 RGBA 像素
png = html_to_png(fragment, extra_css="body{background:#0d1117;color:#e6edf3}")
raw = html_to_image(fragment, fmt="raw") # → RawResult(.data/.width/.height)
doc = wrap_html("<p>x</p>", css=DEFAULT_CSS) # 仅生成完整 HTML 文档字符串主要参数:width、scale(HiDPI 倍数,默认 1)、wrap(None 自动判断片段/整文档;True/False 强制)、
css / extra_css、fmt("png"/"jpeg")、quality、height、shrink_to_fit、locale、fonts、images。
“用 HTML 渲染 Markdown”:内置一个纯标准库的轻量 Markdown→HTML 转换器,再交给 html_to_png。
不引入 markdown-it-py、pygments 等任何第三方依赖。
from pylitehtml.markdown import markdown_to_png, markdown_to_html
png = markdown_to_png("# 标题\n\n- 列表\n- [x] 任务\n\n> 引用", width=720) # scale 默认 2
html = markdown_to_html("**bold**") # → '<p><strong>bold</strong></p>'内置转换器覆盖:标题、粗/斜/***粗斜***/删除线/行内代码、反斜杠转义、链接/图片/裸 URL、
行内 raw HTML、硬换行、围栏代码块(不高亮)、引用、有序(含 start)/无序/嵌套/任务列表、
带对齐的 GFM 表格、分隔线。不支持:setext 标题、引用式链接、脚注、定义列表、缩进代码块。
需要完整 CommonMark/GFM(脚注、定义列表、语法高亮)时传入 converter= 接入 markdown-it-py,
见 examples/markdown_full_converter.py:
from markdown_it import MarkdownIt
from mdit_py_plugins.footnote import footnote_plugin
from mdit_py_plugins.deflist import deflist_plugin
from pylitehtml.markdown import markdown_to_png
md = MarkdownIt("gfm-like").use(footnote_plugin).use(deflist_plugin)
png = markdown_to_png(text, converter=md.render)不执行 JavaScript:数学公式(KaTeX)与 Mermaid 无法排版/绘制,需调用前自行预渲染或接入完整引擎。
litehtml = CSS 2.1 + 部分 CSS3。下表为实测结论:
| 能力 | 状态 |
|---|---|
| PNG / JPEG / RAW 输出 | ✅ |
盒模型、边框(solid/dashed/dotted/double)、border-radius、背景色 |
✅ |
linear-gradient / conic-gradient(radial-gradient |
✅ |
字体、color、text-align、line-height、text-shadow、text-transform |
✅ |
text-decoration、<mark>/<sub>/<sup>/<kbd>、white-space:pre/nowrap |
✅ |
列表(嵌套 / list-style-type / 任务列表)、定义列表、表格(对齐 / :nth-child) |
✅ |
float、inline-block、position:relative/absolute、text-overflow:ellipsis、::before/::after |
✅ |
图片 <img>:data:(PNG/JPEG/WebP/SVG) / 本地 / HTTP·HTTPS、@import CSS |
✅ |
| 中文 / 多语言(内置 Noto Sans + SC,中英共享基线、子像素测量) | ✅ |
多线程并发、asyncio.to_thread |
✅ |
| Flexbox(简单行 / 列) | |
background-image:url()、box-shadow、transform、opacity、letter-spacing |
❌ |
CSS Grid、var()、@font-face、动画 / 过渡、filter、JavaScript |
❌ |
图片抓取失败会自动跳过(不报错);官方 wheel(Linux manylinux_2_34 / macOS / Windows)均含 OpenSSL ≥ 3.0,支持 https。
# 高层(推荐):自动默认样式、片段/整文档自动判别、HiDPI scale
html_to_png(html, *, width=720, scale=1, wrap=None, css=DEFAULT_CSS, extra_css="",
fmt="png", quality=85, height=0, shrink_to_fit=True,
locale="en-US", fonts=None, images=None) -> bytes
html_to_image(...) # 同参数,fmt 可取 "raw" → RawResult(.data/.width/.height,RGBA 行主序)
# 底层:不注入默认样式
Renderer(width, *, locale="en-US", dpi=96.0, device_height=600, fonts=None, images=None)
Renderer.render(html, *, fmt="png", quality=85, height=0, shrink_to_fit=True)
Renderer.render_file(path, *, fmt="png", quality=85, height=0, shrink_to_fit=True, **template_data)
render(html, width, ...) / render_file(path, width, ...) # 一次性便捷函数
FontConfig(default="Noto Sans", size=16, extra=[]) # 已内置 Noto Sans/SC + DejaVu Sans
ImageConfig(cache_mb=64.0, timeout_ms=5000, max_mb=10.0, allow_http=True)fmt="raw"返回 RGBA:Image.frombytes("RGBA",(raw.width,raw.height),raw.data)或np.frombuffer(...)。scale等比放大画布与(默认样式的)根字号;shrink_to_fit把画布收窄到内容宽度。- 内置字体使
sans-serif/serif/monospace与中文在无系统字体时也能渲染;dpi影响pt换算。
按标准网页组织目录,自动解析相对路径的 CSS/图片;传入关键字参数即作为
Jinja2 模板渲染(变量 / 循环 / 条件 / 过滤器 / include / extends):
png = r.render_file("project/index.html") # 纯文件
png = r.render_file("order.html", title="订单", items=[{"name": "苹果", "price": 5}]) # 模板autoescape 默认开启:
.html模板里的{{ 变量 }}会转义</&/",不可信数据也不会注入。 本地文件安全:file://与根路径资源仅在render_file()下加载;直接render()字符串时忽略。
先在单线程构造 Renderer,之后可多线程并发 render()(渲染期间释放 GIL);
异步用 await asyncio.to_thread(r.render, html)。带 extra_fonts 的构造会改写进程级字体配置,
勿在渲染进行中于其它线程构造新 Renderer。
Ubuntu / Debian
sudo apt-get install -y libcairo2-dev libpango1.0-dev libfontconfig1-dev \
libwebp-dev libjpeg-turbo8-dev libssl-dev cmake ninja-build pkg-config
pip install -e ".[dev]" --no-build-isolationmacOS
brew install cairo pango fontconfig webp jpeg-turbo openssl@3 cmake ninja
CC=/usr/bin/clang CXX=/usr/bin/clang++ pip install -e ".[dev]" --no-build-isolation运行测试 / 代码检查
pytest tests/ -v
ruff check . # lint
ruff format . # 格式化(CI 用 `ruff format --check` 校验)litehtml · Cairo · Pango · FontConfig · pybind11
MIT License





