基于 WeasyPrint 的 HTML 转 PDF 服务,使用 Docker 容器化部署。
本项目是 4teamwork/weasyprint 的功能超集,在保留原有功能的基础上增加了更多实用特性:
✨ 核心优势:
- JSON 接口 (/convert)(适合服务端 API 调用,无需构造 multipart)
- Markdown 转 PDF(直接传入 Markdown 文本,一步到位)
- API 参数设置页面配置(无需在 HTML 中定义
@pageCSS) - HTML 片段自动补全(自动添加完整 HTML 结构)
- CSS 参数支持(HTML 和 CSS 分离)
- WeasyPrint 高级选项(zoom、元数据、PDF/A 等)
- 内置中文字体(微软雅黑、华文黑体等 6 种字体)
| 特性 | 4teamwork/weasyprint | 本服务 |
|---|---|---|
| 基础 HTML 转 PDF | ✅ | ✅ |
| multipart 文件上传 | ✅ | ✅ |
| JSON 接口 (/convert) | ❌ | ✅ |
| Markdown 转 PDF | ❌ | ✅ |
| API 参数设置页面 | ❌ | ✅ |
| HTML 片段自动补全 | ❌ | ✅ |
| CSS 参数支持 | ❌ | ✅ |
| options 高级选项 | ✅ | ✅ |
| 中文字体预配置 | ❌ | ✅ |
本服务适合转换静态 HTML/CSS 内容为 PDF,例如:
- 📄 Markdown 文档、技术文档、API 文档
- 📊 数据报告、财务报表、统计图表(纯 HTML/CSS 实现)
- 🧾 发票、合同、证书等商务文档
- 📰 文章、博客内容、新闻稿
- 📋 表单、清单、检查表
本服务基于 WeasyPrint,它是一个服务端渲染引擎,不支持 JavaScript 执行。因此以下场景无法正常转换:
- ❌ Canvas 绘图:
<canvas>元素不会被渲染 - ❌ JavaScript 动态内容:所有
<script>标签会被忽略 - ❌ D3.js / ECharts / Chart.js:依赖 JavaScript 的图表库无法渲染
- ❌ React / Vue / Angular:前端框架的动态渲染不会执行
- ❌ Tailwind CSS CDN:使用 JIT 编译的 Tailwind CDN 版本无法工作(需使用预编译的静态 CSS)
- ❌ Web Components:自定义元素的 JavaScript 逻辑不会执行
- ❌ 动画和交互:CSS animations、transitions、
:hover等交互状态不会呈现
解决方案:
方案 1:前端提取渲染后的 DOM 和样式(推荐)
对于 React / Vue / Angular 等框架,可以在前端 JavaScript 执行完成后,直接提取渲染后的 HTML 和 CSS:
// 提取目标 DOM 的 HTML(已经过 JavaScript 渲染)
const targetElement = document.querySelector('#app'); // 或其他容器
const html = targetElement.innerHTML; // 或使用 outerHTML
// 提取所有样式表的 CSS 规则(包括 <style> 和 <link> 引入的)
function extractAllCSS() {
const cssTexts = [];
for (let i = 0; i < document.styleSheets.length; i++) {
const sheet = document.styleSheets[i];
try {
// 遍历样式表中的所有规则
const rules = sheet.cssRules || sheet.rules;
const cssText = Array.from(rules).map(rule => rule.cssText).join('\n');
cssTexts.push(cssText);
} catch (e) {
// 跨域样式表无法访问(CORS 限制)
console.warn('无法访问样式表:', sheet.href, e);
}
}
return cssTexts.join('\n');
}
const allCSS = extractAllCSS();
// 调用转换接口
fetch('http://localhost:3000/convert', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
html: html,
css: allCSS,
page_size: 'A4',
margin: '2cm'
})
})
.then(res => res.blob())
.then(blob => {
// 下载 PDF
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.pdf';
a.click();
});适用于:React、Vue、Angular、ECharts、D3.js 等所有客户端渲染的内容
ECharts 图表转 PDF 示例(✅ 推荐):
// ECharts 使用 SVG 渲染器(关键!)
const chart = echarts.init(document.getElementById('chart'), null, {
renderer: 'svg' // 使用 SVG 渲染,而非默认的 Canvas
});
// 配置图表
chart.setOption({
title: { text: '销售数据' },
xAxis: { data: ['1月', '2月', '3月', '4月', '5月'] },
yAxis: {},
series: [{
type: 'bar',
data: [120, 200, 150, 80, 70]
}]
});
// 等待图表渲染完成
setTimeout(() => {
// 提取包含 SVG 的 HTML
const chartContainer = document.getElementById('chart');
const html = chartContainer.outerHTML;
// 提取 CSS
const css = extractAllCSS();
// 发送到转换接口
fetch('http://localhost:3000/convert', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
html: html,
css: css,
page_size: 'A4',
margin: '2cm'
})
})
.then(res => res.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'chart.pdf';
a.click();
});
}, 500); // 等待图表动画完成为什么 ECharts SVG 模式有效?
- ECharts 默认使用 Canvas 渲染(无法转换)
- 指定
renderer: 'svg'后,ECharts 生成纯 SVG 元素 - SVG 是静态 XML 标记,WeasyPrint 可以直接渲染
- 无需任何图片转换,效果完美!
注意事项:
- 确保在 DOM 渲染完成后(如 React 的
useEffect、Vue 的onMounted)再提取 HTML document.styleSheets是同步 API,性能优于fetch方式- 跨域样式表(CDN 的 CSS)会因 CORS 限制无法访问
cssRules,需确保样式表支持 CORS 或使用同源部署 - ECharts 必须使用 SVG 渲染器,Canvas 渲染器需要转为图片(见下方示例)
- D3.js 默认生成 SVG,可直接使用此方案
Canvas 转图片示例:
// 将 canvas 转为 img 标签
const canvas = document.querySelector('canvas');
const imgDataUrl = canvas.toDataURL('image/png');
const imgTag = `<img src="${imgDataUrl}" style="width: 100%;">`;
// 替换 canvas 后再提取 HTML
canvas.parentElement.innerHTML = imgTag;方案 2:服务端预渲染
对于图表需求,使用服务端图表生成库(如 Python 的 matplotlib、plotly),将图表导出为 SVG 或 PNG 后嵌入 HTML
方案 3:使用预编译静态资源
对于 Tailwind CSS,使用预编译的静态 CSS 文件,而非 CDN 的 JIT 版本
如果你的场景必须执行 JavaScript(如 Canvas 绘图、完整的 React/Vue 应用、复杂交互),可以考虑基于 Headless Chrome 的方案:
方案对比:
| 维度 | WeasyPrint (本项目) | Puppeteer/Playwright |
|---|---|---|
| JavaScript 支持 | ❌ 不支持 | ✅ 完整支持 |
| 适用场景 | 静态 HTML/CSS、Markdown 文档 | SPA 应用、Canvas 图表、动态内容 |
| 渲染引擎 | 自研 CSS 渲染器 | Chromium 浏览器 |
| 资源占用 | 🟢 低(~50MB 内存) | 🔴 高(~300-500MB 内存) |
| 启动速度 | 🟢 快(毫秒级) | 🟡 慢(需启动浏览器,秒级) |
| 并发能力 | 🟢 高(Python 异步) | 🟡 受限(浏览器实例数) |
| CSS 支持 | 🟡 部分 CSS3 | 🟢 完整现代 CSS |
| 字体渲染 | 🟢 精确印刷级 | 🟢 浏览器级 |
| PDF 质量 | 🟢 专业印刷品质 | 🟢 高质量 |
| 容器化体积 | 🟢 小(~200MB) | 🔴 大(~1GB+) |
Puppeteer 示例代码:
const puppeteer = require('puppeteer');
async function htmlToPdf(htmlContent) {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const pdf = await page.pdf({
format: 'A4',
margin: { top: '2cm', right: '2cm', bottom: '2cm', left: '2cm' },
printBackground: true
});
await browser.close();
return pdf;
}为什么本项目选择 WeasyPrint?
- 目标场景明确:主要服务于文档、报告、Markdown 等静态内容转换,不需要 JavaScript 执行
- 性能优先:WeasyPrint 的资源占用和启动速度远优于 Headless Chrome,适合高并发 API 服务
- 容器化友好:镜像体积小(~200MB vs 1GB+),部署成本低
- 印刷品质:WeasyPrint 专为 PDF 生成优化,对 CSS Paged Media 规范支持更好,适合专业文档排版
- 依赖简单:纯 Python 生态,无需管理浏览器进程池
如何选择?
- ✅ 选择 WeasyPrint(本项目):Markdown 文档、技术文档、数据报表、静态网页、发票合同等
- ✅ 选择 Puppeteer/Playwright:需要截图整个 SPA 应用、Canvas 图表、复杂 JavaScript 动态内容
相关开源项目推荐(Puppeteer/Playwright 方案):
- gotenberg - 基于 Chromium 的文档转换服务
- browserless - Headless Chrome 即服务
- pdf-bot - 简单的 Puppeteer PDF 服务
# 使用 Docker 运行
docker run -d -p 3000:3000 --name html2pdf icheerme/html2pdf:latest
# 查看日志
docker logs -f html2pdf服务将在 http://localhost:3000 启动。
# 1. 最简单的用法 - API 参数设置页面
curl -F "[email protected]" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdf
# 2. 单独设置四边边距
curl -F "[email protected]" \
-F "page_size=A4" \
-F "margin_top=3cm" \
-F "margin_right=2cm" \
-F "margin_bottom=2.5cm" \
-F "margin_left=2cm" \
http://localhost:3000/convert -o output.pdf
# 3. 横向页面
curl -F "[email protected]" \
-F "page_size=A4" \
-F "page_orientation=landscape" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdf
# 4. 包含图片资源
curl -F "[email protected]" \
-F "asset.logo.png=@images/logo.png" \
-F "asset.banner.jpg=@images/banner.jpg" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdf统一的 HTML 转 PDF 接口,根据 Content-Type 自动识别请求格式:
multipart/form-data- 文件上传模式(支持资源文件、附件)application/json- JSON 字符串模式(适合服务端 API 调用)
适用场景:需要上传 HTML 文件、图片、CSS 等资源文件。
请求格式:multipart/form-data
请求参数:
| 字段 | 类型 | 必需 | 说明 | 示例 |
|---|---|---|---|---|
html |
File | ✅ | 主 HTML 文件 | @document.html |
| 页面配置 | ||||
page_size |
String | ❌ | 页面尺寸 | A4, A3, A5, letter, legal |
page_orientation |
String | ❌ | 页面方向 | portrait(默认), landscape |
margin |
String | ❌ | 页边距(统一) | 2cm, 1.5cm 2cm, 1cm,1.5cm,2cm,1.5cm |
margin_top |
String | ❌ | 上边距 | 3cm |
margin_right |
String | ❌ | 右边距 | 2cm |
margin_bottom |
String | ❌ | 下边距 | 2.5cm |
margin_left |
String | ❌ | 左边距 | 2cm |
| 像素单位转换 | ||||
base_width_px |
Number | ❌ | 基准页面宽度(像素),启用 px → pt 自动转换 | 1920, 1440, 750 |
| PDF 选项 | ||||
optimize_images |
Boolean | ❌ | 是否优化图像 | true, false |
jpeg_quality |
Integer | ❌ | JPEG 质量(0-95) | 85 |
pdf_variant |
String | ❌ | PDF 变体 | pdf/a-3b, pdf/ua-1 |
options |
String (JSON) | ❌ | WeasyPrint 高级选项(JSON 字符串) | 见高级选项章节 |
| 资源和附件 | ||||
asset.* |
File | ❌ | 资源文件(图片、CSS 等) | [email protected] |
attachment.* |
File | ❌ | PDF 附件 | [email protected] |
使用示例:
# 基础用法
curl -F "[email protected]" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdf
# 包含图片资源
curl -F "[email protected]" \
-F "asset.logo.png=@images/logo.png" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdf适用场景:服务端已经拼合好 HTML 字符串,无需上传文件。
请求格式:application/json
请求参数:
| 字段 | 类型 | 必需 | 说明 | 示例 |
|---|---|---|---|---|
html |
String | HTML 内容字符串(与 markdown 二选一) | "<!DOCTYPE html>..." 或 HTML 片段 |
|
markdown |
String | Markdown 内容字符串(优先级高于 html) | "# Title\n\nContent..." |
|
css |
String | ❌ | 自定义 CSS 样式 | "body { color: red; }" |
page_size |
String | ❌ | 页面尺寸 | "A4", "A3", "letter" |
page_orientation |
String | ❌ | 页面方向 | "portrait", "landscape" |
margin |
String | ❌ | 页边距(统一) | "2cm", "1.5cm 2cm" |
margin_top |
String | ❌ | 上边距 | "3cm" |
margin_right |
String | ❌ | 右边距 | "2cm" |
margin_bottom |
String | ❌ | 下边距 | "2.5cm" |
margin_left |
String | ❌ | 左边距 | "2cm" |
| 像素单位转换 | ||||
base_width_px |
Number | ❌ | 基准页面宽度(像素),启用 px → pt 自动转换 | 1920, 1440, 750 |
optimize_images |
Boolean | ❌ | 是否优化图像 | true, false |
jpeg_quality |
Integer | ❌ | JPEG 质量(0-95) | 85 |
pdf_variant |
String | ❌ | PDF 变体 | "pdf/a-3b" |
options |
Object | ❌ | WeasyPrint 高级选项(对象) | 见高级选项章节 |
特色功能:
-
Markdown 转 PDF:直接传入 Markdown 文本,自动转换为精美的 PDF
- 支持标准 Markdown 语法
- 支持表格、代码块、脚注等扩展语法
- 自动应用专业的排版样式
- 内置中文字体支持
-
HTML 片段自动补全:自动检测并补全 HTML 结构
- 添加
<!DOCTYPE html>声明 - 添加
<html>标签(自动检测中文并设置lang="zh-CN") - 添加
<head>和<body>标签 - 注入默认的中文字体样式
- 添加
-
CSS 参数支持:HTML 和 CSS 完全分离,无需在 HTML 中嵌入
<style>标签
注意事项:
- JSON 模式不支持上传资源文件(
asset.*)和附件(attachment.*) - 如需使用图片,可在 HTML 中使用 base64 编码的 data URLs
- 如需使用外部 CSS/图片/字体文件,请使用 multipart 模式
- 最大请求体大小:50MB
使用示例:
# Markdown 转 PDF
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"markdown": "# 项目文档\n\n## 简介\n\n这是一个 **Markdown** 转 PDF 的示例。\n\n### 支持的功能\n\n- 列表项 1\n- 列表项 2\n\n### 代码示例\n\n```python\nprint(\"Hello World\")\n```\n\n| 列名1 | 列名2 |\n|-------|-------|\n| 数据1 | 数据2 |",
"page_size": "A4",
"margin": "2cm"
}' \
-o document.pdf
# HTML 片段 + CSS 样式
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"html": "<div><h1>标题</h1><p>内容</p></div>",
"css": "h1 { color: #2c3e50; font-size: 24pt; } p { line-height: 1.8; }",
"page_size": "A4",
"margin": "2cm"
}' \
-o output.pdf
# 完整 HTML 文档
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"html": "<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>",
"page_size": "A4",
"margin": "2cm"
}' \
-o output.pdfPython 示例:
import requests
# 示例 1: Markdown 转 PDF
markdown_content = """
# 技术文档
## 概述
这是一个使用 **Markdown** 编写的技术文档。
## 功能特性
- 支持标准 Markdown 语法
- 支持表格、代码块
- 自动应用专业排版
### 代码示例
\`\`\`python
def hello():
print("Hello, World!")
\`\`\`
### 数据表格
| 指标 | 数值 |
|------|------|
| 用户数 | 10,234 |
| 日活跃 | 3,456 |
"""
response = requests.post(
'http://localhost:3000/convert',
json={
'markdown': markdown_content,
'page_size': 'A4',
'margin': '2cm'
}
)
with open('document.pdf', 'wb') as f:
f.write(response.content)
# 示例 2: HTML + CSS
from jinja2 import Template
html_template = Template("""
<div class="report">
<h1>{{ title }}</h1>
<table>
{% for item in items %}
<tr><td>{{ item.name }}</td><td>{{ item.value }}</td></tr>
{% endfor %}
</table>
</div>
""")
css_styles = """
.report { padding: 20px; }
h1 { color: #2c3e50; font-size: 20pt; }
table { width: 100%; border-collapse: collapse; }
td { border: 1px solid #ddd; padding: 8pt; }
"""
html_content = html_template.render(
title="月度报告",
items=[{'name': '用户数', 'value': '10,234'}]
)
response = requests.post(
'http://localhost:3000/convert',
json={
'html': html_content,
'css': css_styles,
'page_size': 'A4',
'margin': '2cm'
}
)
with open('report.pdf', 'wb') as f:
f.write(response.content)Node.js 示例:
const axios = require('axios');
const fs = require('fs');
const htmlContent = `
<article>
<h1>文章标题</h1>
<p>这是内容...</p>
</article>
`;
const cssStyles = `
article { font-family: "Microsoft YaHei", sans-serif; }
h1 { font-size: 22pt; color: #333; }
p { line-height: 1.6; }
`;
axios.post('http://localhost:3000/convert', {
html: htmlContent,
css: cssStyles,
page_size: 'A4',
margin: '2cm'
}, {
responseType: 'arraybuffer'
}).then(response => {
fs.writeFileSync('article.pdf', response.data);
});响应(两种模式相同):
- Content-Type:
application/pdf - Body: PDF 文件二进制数据
健康检查接口。
响应示例:
{
"status": "healthy",
"service": "html2pdf",
"version": "1.0.0"
}| 尺寸 | 说明 | 尺寸(mm) |
|---|---|---|
A4 |
A4 标准 | 210 × 297 |
A3 |
A3 标准 | 297 × 420 |
A5 |
A5 标准 | 148 × 210 |
letter |
美国信纸 | 8.5in × 11in |
legal |
美国法律文件 | 8.5in × 14in |
也可以自定义尺寸(需要在 HTML 的 @page 中定义):
@page { size: 210mm 297mm; }1. 统一边距(四边相同):
-F "margin=2cm"2. 上下、左右边距:
-F "margin=2.5cm 2cm" # 上下 2.5cm,左右 2cm3. 单独设置四边(推荐,最灵活):
-F "margin_top=3cm" \
-F "margin_right=2cm" \
-F "margin_bottom=2.5cm" \
-F "margin_left=2cm"# 纵向(默认)
-F "page_orientation=portrait"
# 横向
-F "page_orientation=landscape"前端开发中通常使用 px(像素) 作为尺寸单位,但 WeasyPrint 在生成 PDF 时按照 96 DPI 标准渲染 px 单位,这会导致:
- 字体过大:
font-size: 18px在 PDF 中显示异常大 - 布局错乱:元素尺寸不符合预期,内容溢出页面
- 排版拥挤:行高、间距等比例失调
通过 base_width_px 参数,服务会自动将 HTML 和 CSS 中的 所有 px 单位转换为 pt 单位,按照目标纸张尺寸进行比例缩放。
缩放比例 = A4 可用宽度(481pt)/ base_width_px
转换后尺寸 = 原始 px 值 × 缩放比例
例如:base_width_px=1920(常见桌面设计稿宽度)
- A4 Portrait 可用宽度 ≈ 481pt(减去默认 2cm 边距)
- 缩放比例 = 481 / 1920 ≈ 0.25
font-size: 18px→font-size: 4.5ptwidth: 800px→width: 200pt
服务会自动转换以下 CSS 属性中的 px 值:
- 尺寸:
width,height,min-width,max-width,min-height,max-height - 字体:
font-size,line-height,letter-spacing,word-spacing - 间距:
margin,padding(及各方向属性) - 边框:
border-width,border-radius(及各方向属性) - 定位:
top,right,bottom,left - 其他:
text-indent,gap,row-gap,column-gap
multipart 接口:
curl -F "[email protected]" \
-F "base_width_px=1920" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdfJSON 接口:
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"html": "<div style=\"font-size: 18px; width: 800px; padding: 20px;\">内容</div>",
"base_width_px": 1920,
"page_size": "A4",
"margin": "2cm"
}' \
-o output.pdfPython 示例(前端动态内容提取 + 像素转换):
import requests
# 前端提取的 HTML(已经过 JavaScript 渲染,使用 px 单位)
html_content = """
<div class="container" style="width: 1200px; padding: 40px;">
<h1 style="font-size: 32px; margin-bottom: 20px;">报告标题</h1>
<p style="font-size: 16px; line-height: 24px;">正文内容...</p>
<div style="width: 800px; height: 400px;">图表区域</div>
</div>
"""
# 提取的 CSS(也使用 px 单位)
css_styles = """
.container { font-family: "Microsoft YaHei"; }
h1 { color: #2c3e50; }
p { color: #555; }
"""
response = requests.post(
'http://localhost:3000/convert',
json={
'html': html_content,
'css': css_styles,
'base_width_px': 1920, # 原始设计宽度
'page_size': 'A4',
'margin': '2cm'
}
)
with open('report.pdf', 'wb') as f:
f.write(response.content)
# 服务会自动转换:
# - font-size: 32px → 8pt
# - font-size: 16px → 4pt
# - width: 800px → 200pt
# 等等...JavaScript 示例(配合前端提取方案):
// 步骤 1:前端提取渲染后的 HTML 和 CSS
const targetElement = document.querySelector('#app');
const html = targetElement.outerHTML;
function extractAllCSS() {
const cssTexts = [];
for (let i = 0; i < document.styleSheets.length; i++) {
const sheet = document.styleSheets[i];
try {
const rules = sheet.cssRules || sheet.rules;
const cssText = Array.from(rules).map(rule => rule.cssText).join('\n');
cssTexts.push(cssText);
} catch (e) {
console.warn('无法访问样式表:', sheet.href, e);
}
}
return cssTexts.join('\n');
}
const allCSS = extractAllCSS();
// 步骤 2:发送到转换接口,启用 px 转换
fetch('http://localhost:3000/convert', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
html: html,
css: allCSS,
base_width_px: 1920, // 指定原始设计宽度
page_size: 'A4',
margin: '2cm'
})
})
.then(res => res.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.pdf';
a.click();
});| 设计场景 | 推荐 base_width_px | 说明 |
|---|---|---|
| 桌面端网页 | 1920 |
标准桌面显示器宽度 |
| 桌面端网页 | 1440 |
MacBook Pro 等高分屏 |
| 移动端网页 | 750 |
iPhone 设计稿常用宽度 |
| 移动端网页 | 375 |
iPhone 逻辑像素宽度 |
| 平板端网页 | 1024 |
iPad 横屏宽度 |
| 自定义 | <容器宽度> |
使用实际容器的 offsetWidth |
-
仅转换内联样式和
<style>标签中的样式- 外部 CSS 文件(
<link>)中的样式也会被转换 - Data URL 内嵌的 CSS 也会被转换
- 外部 CSS 文件(
-
不影响已经使用 pt、cm、mm 等单位的样式
- 只转换明确标注为
px的值 - 已使用印刷单位的样式保持不变
- 只转换明确标注为
-
适用于所有 CSS 属性
- 包括简写属性(如
margin: 10px 20px) - 包括 calc() 表达式中的 px 值
- 包括简写属性(如
-
与 zoom 选项的区别
base_width_px:智能单位转换,保持布局比例zoom:整体缩放,但不改变单位(WeasyPrint 不支持 CSS zoom 属性)
options 参数支持 WeasyPrint 的所有高级特性,让您可以精细控制 PDF 生成过程。
| 选项 | 类型 | 说明 | 示例值 |
|---|---|---|---|
zoom |
Float | 缩放比例 | 1.0 (默认), 1.5, 0.8 |
pdf_version |
String | PDF 版本 | "1.4", "1.7", "2.0" |
pdf_identifier |
Boolean | 生成唯一 PDF 标识符 | true, false |
pdf_variant |
String | PDF 变体/标准 | "pdf/a-3b", "pdf/ua-1" |
pdf_forms |
Boolean | 包含 PDF 表单 | true, false |
uncompressed_pdf |
Boolean | 生成未压缩的 PDF | true, false |
custom_metadata |
Object | 自定义 PDF 元数据 | {"author": "...", "title": "..."} |
使用方式
multipart 接口(JSON 字符串格式):
curl -F "[email protected]" \
-F "page_size=A4" \
-F "margin=2cm" \
-F 'options={"zoom": 1.2, "pdf_version": "1.7", "custom_metadata": {"author": "IDG Capital", "title": "Q3 Report"}}' \
http://localhost:3000/convert -o output.pdfJSON 接口(对象格式):
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"html": "<div><h1>季度报告</h1></div>",
"page_size": "A4",
"margin": "2cm",
"options": {
"zoom": 1.2,
"pdf_version": "1.7",
"custom_metadata": {
"author": "IDG Capital",
"title": "Q3 Financial Report",
"subject": "Quarterly Report",
"keywords": "finance, quarterly, report"
}
}
}' \
-o report.pdf1. 设置 PDF 元数据(文档属性)
import requests
response = requests.post(
'http://localhost:3000/convert',
json={
'html': html_content,
'page_size': 'A4',
'margin': '2cm',
'options': {
'custom_metadata': {
'author': '张三',
'title': '2026年度财务报告',
'subject': '年度报告',
'keywords': '财务,年度,审计',
'creator': 'IDG Capital PDF Service'
}
}
}
)2. 生成 PDF/A 归档格式
// PDF/A-3b 适合长期归档
axios.post('http://localhost:3000/convert', {
html: htmlContent,
page_size: 'A4',
margin: '2cm',
options: {
pdf_variant: 'pdf/a-3b', // 归档标准
pdf_version: '1.7'
}
});3. 缩放 PDF 内容
# 放大 20% 后生成 PDF(适合高分辨率打印)
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"html": "<div>...</div>",
"page_size": "A4",
"options": {"zoom": 1.2}
}' \
-o output.pdf4. 无障碍 PDF (PDF/UA)
# 生成符合无障碍标准的 PDF
response = requests.post(
'http://localhost:3000/convert',
json={
'html': accessible_html,
'options': {'pdf_variant': 'pdf/ua-1'}
}
)5. 调试模式(未压缩 PDF)
# 生成未压缩的 PDF,方便调试和检查内部结构
curl -X POST http://localhost:3000/convert \
-H "Content-Type: application/json" \
-d '{
"html": "<div>...</div>",
"options": {"uncompressed_pdf": true}
}' \
-o debug.pdf项目已预配置 6 种中文字体,内置在 Docker 镜像中。
| 字体文件 | 字体名称(CSS) | 说明 | 大小 |
|---|---|---|---|
msyh.ttc |
Microsoft YaHei |
微软雅黑 | 19MB |
msyhbd.ttc |
Microsoft YaHei Bold |
微软雅黑粗体 | 17MB |
msyhl.ttc |
Microsoft YaHei Light |
微软雅黑细体 | 12MB |
STHeiti Medium.ttc |
STHeiti, STHeiti Medium |
华文黑体中等 | 54MB |
STHeiti Light.ttc |
STHeiti Light |
华文黑体细体 | 54MB |
Menlo.ttc |
Menlo |
等宽编程字体 | 2.1MB |
在 HTML 或 CSS 中指定字体族:
body {
font-family: "Microsoft YaHei", "STHeiti", sans-serif;
}
/* 标题使用粗体 */
h1, h2, h3 {
font-family: "Microsoft YaHei Bold", "STHeiti Medium", sans-serif;
}
/* 代码块使用等宽字体 */
code, pre {
font-family: "Menlo", "Consolas", monospace;
}为了安全考虑,默认情况下服务禁止访问外部 URL 资源。如需允许特定域名的资源,需配置环境变量 WEASYPRINT_ALLOWED_URLS_PATTERN。
当前允许的域名:
https://unpkg.com/*- CDN 资源https://fonts.example.com/*- 字体资源https://logos.example.com/*- Logo 资源
修改配置请编辑 docker-compose.yml 或 docker-compose-dev.yml 中的环境变量:
environment:
- WEASYPRINT_ALLOWED_URLS_PATTERN=^https://unpkg\.com.*|^https://fonts\.googleapis\.com.*<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>报告标题</title>
<style>
/* 只定义内容样式,不定义 @page */
body {
font-family: "Microsoft YaHei", "STHeiti", sans-serif;
font-size: 12pt;
line-height: 1.6;
}
h1 { color: #2c3e50; }
</style>
</head>
<body>
<h1>标题</h1>
<p>内容</p>
</body>
</html>调用方式:
curl -F "[email protected]" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000 -o report.pdf在 HTML 的 <style> 中添加:
/* 在元素前强制分页 */
.chapter {
page-break-before: always;
}
/* 在元素后强制分页 */
.section {
page-break-after: always;
}为了防止表格、图片、代码块等元素在分页时被切断,使用以下 CSS 属性:
/* 避免在元素内部分页(保持元素完整) */
.keep-together {
page-break-inside: avoid;
break-inside: avoid; /* 新标准 */
}
/* 避免标题与后续内容分离 */
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
break-after: avoid;
}
/* 防止表格被切断 */
table {
page-break-inside: avoid;
break-inside: avoid;
}
/* 防止图片被切断 */
img {
page-break-inside: avoid;
break-inside: avoid;
}
/* 防止代码块被切断 */
pre, code {
page-break-inside: avoid;
break-inside: avoid;
}实际应用示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
body {
font-family: "Microsoft YaHei", sans-serif;
font-size: 12pt;
line-height: 1.6;
}
/* 每个章节从新页开始 */
.chapter {
page-break-before: always;
}
/* 卡片内容保持完整,不被分页切断 */
.card {
border: 1px solid #ddd;
padding: 15pt;
margin-bottom: 10pt;
page-break-inside: avoid;
}
/* 表格不被切断 */
table {
width: 100%;
border-collapse: collapse;
page-break-inside: avoid;
}
/* 标题与后续内容保持在同一页 */
h2 {
page-break-after: avoid;
}
</style>
</head>
<body>
<div class="chapter">
<h1>第一章</h1>
<p>章节内容...</p>
</div>
<div class="card">
<h3>重要信息卡片</h3>
<p>这个卡片不会被分页切断</p>
</div>
<table>
<thead>
<tr><th>列1</th><th>列2</th></tr>
</thead>
<tbody>
<tr><td>数据1</td><td>数据2</td></tr>
</tbody>
</table>
</body>
</html>注意事项:
page-break-inside: avoid只对不超过一页高度的元素有效- 如果元素本身超过一页,WeasyPrint 会强制分页
- 同时使用旧标准
page-break-*和新标准break-*可以提高兼容性
不在 HTML 中添加任何 @page 规则,通过 API 参数控制页面:
curl -F "[email protected]" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdf在 HTML 的 <style> 中定义:
@page {
/* 页眉 - 居中显示 */
@top-center {
content: "文档标题";
font-size: 10pt;
color: #666;
}
/* 页脚 - 右侧显示页码 */
@bottom-right {
content: "第 " counter(page) " 页 / 共 " counter(pages) " 页";
font-size: 10pt;
color: #666;
}
}注意:如果在 HTML 中定义了 @page,它会覆盖 API 参数中的页面配置。
推荐方式:使用 asset.* 上传资源
curl -F "[email protected]" \
-F "[email protected]" \
-F "[email protected]" \
-F "page_size=A4" \
-F "margin=2cm" \
http://localhost:3000/convert -o output.pdfHTML 中使用相对路径引用:
<img src="logo.png" alt="Logo">
<link rel="stylesheet" href="style.css">使用预构建的 Docker 镜像快速部署:
# 拉取并运行最新版本
docker run -d \
-p 3000:3000 \
--name html2pdf \
--restart unless-stopped \
icheerme/html2pdf:latest
# 查看日志
docker logs -f html2pdf服务启动后访问:http://localhost:3000
如需自定义配置(如允许外部资源、环境变量等),可创建 docker-compose.yml:
services:
html2pdf:
image: icheerme/html2pdf:latest
container_name: html2pdf
restart: unless-stopped
ports:
- "3000:3000"
environment:
- PORT=3000
# 可选:允许特定外部资源域名
- WEASYPRINT_ALLOWED_URLS_PATTERN=^https://unpkg\.com.*|^https://cdn\.jsdelivr\.net.*启动服务:
docker compose up -d如需从源码构建自定义版本:
# 克隆仓库
git clone https://github.com/icheer/html2pdf.git
cd html2pdf
# 构建镜像
docker build -t html2pdf:custom .
# 运行
docker run -d -p 3000:3000 --name html2pdf html2pdf:custom- WeasyPrint 69.0:HTML/CSS 到 PDF 的渲染引擎
- aiohttp 3.9.5:异步 HTTP 服务器
- Python 3.12:编程语言
- Docker:容器化部署
- GitHub Actions:CI/CD 自动化
确保 HTML 中指定了中文字体:
body { font-family: "Microsoft YaHei", "STHeiti", sans-serif; }检查 URL 是否在 WEASYPRINT_ALLOWED_URLS_PATTERN 白名单中。
启用图像优化:
-F "optimize_images=true" \
-F "jpeg_quality=75"检查 HTML 中是否定义了 @page CSS 规则。如果定义了,它会覆盖 API 参数。建议移除 HTML 中的 @page 规则,完全使用 API 参数控制。
欢迎贡献代码、报告问题或提出建议!
- 🐛 报告 Bug:提交 Issue
- 💡 功能建议:提交 Feature Request
- 🔧 贡献代码:Fork 本仓库并提交 Pull Request
本项目采用 MIT License 开源协议。
如果这个项目对你有帮助,欢迎点个 Star ⭐️!
- Issue:GitHub Issues
- Discussions:GitHub Discussions
感谢使用 HTML2PDF! 🎉