Skip to content

tyql688/pylitehtml

Repository files navigation

pylitehtml

CI

HTML+CSS → PNG/JPEG 图像渲染器。轻量级,无需无头浏览器,线程安全。需要 Python ≥ 3.10。

效果展示

examples/ 下有若干独立可运行的示例,各渲染一张图(无浏览器、纯 pylitehtml,与 tests/ 分开)。

示例 说明 渲染结果
showcase_html.py HTML/CSS 能力参考:排版、颜色/渐变、表格、代码、列表、进度条 + 真实 HTTPS 图片 + data: URI 图标 html
showcase_css.py More CSS:conic/radial 渐变、边框样式、position::beforetext-overflow:ellipsiswhite-space:prevertical-align css
showcase_markdown.py 内置零依赖 Markdown 转换器的端到端输出 markdown
showcase_jinja2.py Jinja2 数据驱动模板(循环 / 条件 / 过滤器 / autoescape,一张订单发票) jinja2
markdown_full_converter.py 用 markdown-it-py 接入完整 GFM(脚注 / 定义列表 / Pygments 语法高亮;需 pip install markdown-it-py mdit-py-plugins pygments gfm
render_markdown_doc.py 渲染一篇真实复杂文档markdown.md:多表格 / 嵌套列表 / 多语言代码 / Mermaid 源码 / 数学 TeX / 行号引用代码块) doc

中英文混排共享同一基线(不会出现中文“飞起来”的错位);文本测量为子像素精度; 模板变量经 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

性能(对比 Playwright)

同一篇文档各渲染 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 30

安装

pip install pylitehtml

快速上手

import 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>")

高级封装:html_to_png / markdown_to_png

render() 是最底层接口(HTML 字符串 → 图片)。在它之上提供了两个高层便捷函数。

html_to_png — 带默认样式的 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 文档字符串

主要参数:widthscale(HiDPI 倍数,默认 1)、wrapNone 自动判断片段/整文档;True/False 强制)、 css / extra_cssfmt"png"/"jpeg")、qualityheightshrink_to_fitlocalefontsimages

markdown_to_png — Markdown → HTML → 图片(可选、默认零依赖)

“用 HTML 渲染 Markdown”:内置一个纯标准库的轻量 Markdown→HTML 转换器,再交给 html_to_png不引入 markdown-it-pypygments 等任何第三方依赖。

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-gradientradial-gradient ⚠️ 仅颜色对)
字体、colortext-alignline-heighttext-shadowtext-transform
text-decoration<mark>/<sub>/<sup>/<kbd>white-space:pre/nowrap
列表(嵌套 / list-style-type / 任务列表)、定义列表、表格(对齐 / :nth-child
floatinline-blockposition:relative/absolutetext-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-shadowtransformopacityletter-spacing
CSS Grid、var()@font-face、动画 / 过渡、filter、JavaScript

图片抓取失败会自动跳过(不报错);官方 wheel(Linux manylinux_2_34 / macOS / Windows)均含 OpenSSL ≥ 3.0,支持 https。


API

# 高层(推荐):自动默认样式、片段/整文档自动判别、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 换算。

render_file 与 Jinja2

按标准网页组织目录,自动解析相对路径的 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-isolation

macOS

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

About

Python wrapper for litehtml: HTML+CSS → PNG/JPEG rendering, multi-platform pip package

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors