PDF 图书导出器
概述
PDF Book Exporter 是一个专门为 Hugo 网站设计的 PDF 导出工具,位于 tools/pdf-book-exporter/
目录。该工具能够将 Hugo 书籍内容转换为高质量的 PDF 文档,支持中文、emoji、代码高亮、表格优化等高级功能。
工具架构
核心组件
- cli.py - 主要的 Python 脚本
- template.tex - LaTeX 模板文件
- filters/ - Lua 过滤器目录
- emoji_diagnostics.py - emoji 支持诊断工具
- install_pdf_dependencies.sh - 依赖安装脚本
主要功能模块
1. 目录树构建器 (Tree Builder)
- 扫描 Hugo 目录结构
- 解析 front matter 元数据
- 构建层次化的章节树
- 支持 weight 排序和内容过滤
2. 配置加载器 (Config Loader)
- 解析书籍根目录的
_index.md
- 提取书籍元数据(标题、作者、日期等)
- 支持多语言配置
3. 图片处理器 (Image Processor)
- 智能图片缓存系统
- 支持 SVG、WebP 格式转换
- 远程图片下载和缓存
- 自动格式优化
4. Lua 过滤器系统
- 模块化的内容处理管道
- 支持 emoji、代码高亮、表格优化
- 可扩展的过滤器架构
工作流程
时序图
sequenceDiagram
participant User as 用户
participant Script as cli.py
participant Tree as 目录树构建器
participant Config as 配置加载器
participant Image as 图片处理器
participant Cache as 缓存系统
participant Pandoc as Pandoc引擎
participant Filters as Lua过滤器
participant LaTeX as LaTeX引擎
participant Output as PDF输出
User->>Script: 执行导出命令
Script->>Script: 解析命令行参数
alt 诊断模式
Script->>Script: 运行系统诊断
Script-->>User: 返回诊断报告
else 正常导出模式
Script->>Tree: 构建章节树
Tree->>Tree: 扫描目录结构
Tree->>Tree: 解析front matter
Tree->>Tree: 过滤草稿和未发布内容
Tree-->>Script: 返回章节树
Script->>Config: 加载书籍配置
Config->>Config: 解析_index.md
Config-->>Script: 返回元数据
Script->>Image: 处理图片资源
Image->>Cache: 检查图片缓存
alt 缓存命中
Cache-->>Image: 返回缓存图片
else 缓存未命中
Image->>Image: 转换图片格式
Image->>Cache: 保存到缓存
end
Image-->>Script: 返回处理后的图片
Script->>Script: 生成临时Markdown文件
Script->>Script: 配置Pandoc命令
Script->>Pandoc: 执行PDF转换
Pandoc->>Filters: 应用Lua过滤器链
Filters->>Filters: ansi-cleanup.lua (清理ANSI码)
Filters->>Filters: fix-lstinline.lua (修复内联代码)
Filters->>Filters: emoji-passthrough.lua (处理emoji)
Filters->>Filters: minted-filter.lua (代码高亮)
Filters->>Filters: cleanup-filter.lua (清理格式)
Filters->>Filters: symbol-fallback-filter.lua (符号回退)
Filters->>Filters: table-wrap.lua (表格包装)
Filters-->>Pandoc: 返回处理后的内容
Pandoc->>LaTeX: 生成LaTeX代码
LaTeX->>LaTeX: 编译PDF
LaTeX-->>Output: 生成最终PDF
Output-->>User: 返回PDF文件
end
主要处理流程
- 命令行解析 - 解析用户输入的参数和选项
- 系统验证 - 检查依赖和系统环境
- 内容扫描 - 递归扫描 Hugo 目录结构
- 树构建 - 根据 weight 和发布状态构建章节树
- 图片处理 - 处理和缓存图片资源
- 内容合并 - 将所有章节合并为单一 Markdown 文件
- Pandoc 转换 - 使用 Lua 过滤器链处理内容
- LaTeX 编译 - 生成最终的 PDF 文件
Lua 过滤器详解
过滤器执行顺序
过滤器按照特定顺序执行,确保内容处理的正确性:
- ansi-cleanup.lua - 清理 ANSI 转义码
- fix-lstinline.lua - 修复内联代码样式
- emoji-passthrough.lua - 处理 emoji 字符
- minted-filter.lua - 代码语法高亮
- cleanup-filter.lua - 清理格式问题
- symbol-fallback-filter.lua - 特殊符号回退
- table-wrap.lua - 表格包装优化
各过滤器功能详解
1. ansi-cleanup.lua
作用: 清理 ANSI 转义码,防止 LaTeX 编译错误
处理内容:
- 移除标准 ANSI 颜色码 (
\033[...m
) - 处理 shell 变量赋值中的 ANSI 码
- 转义问题字符序列
- 支持多种 ANSI 码格式(八进制、十六进制等)
关键函数:
function strip_ansi_codes(text)
-- 移除各种形式的 ANSI 转义序列
text = text:gsub("\27%[[%d;]*m", "") -- 字面 ESC 字符
text = text:gsub("\\033%[[%d;]*m", "") -- 八进制转义
text = text:gsub("\\x1[bB]%[[%d;]*m", "") -- 十六进制转义
return text
end
2. fix-lstinline.lua
作用: 修复 Pandoc 生成的内联代码样式问题
处理内容:
- 检测
\passthrough{\lstinline!...!}
模式 - 转换为表格友好的
\texttt{}
命令 - 转义 LaTeX 特殊字符
关键函数:
function RawInline(elem)
local content = elem.text
if content:match("^\\passthrough{\\lstinline!.*!}$") then
local inline_content = content:match("\\passthrough{\\lstinline!(.-)}$")
if inline_content then
inline_content = inline_content:gsub("!$", "")
return pandoc.RawInline('latex', '\\texttt{' .. inline_content .. '}')
end
end
return elem
end
3. emoji-passthrough.lua
作用: 处理 emoji 字符,确保正确的字体渲染
处理内容:
- 检测 Unicode emoji 范围
- 为 emoji 字符添加字体包装
- 处理变体选择器
- 在代码中转换 emoji 为文本表示
关键特性:
- 精确的 emoji 范围检测
- 支持复合 emoji 序列
- 字体回退机制
- 代码块中的 emoji 处理
4. minted-filter.lua
作用: 将代码块转换为 minted 环境,提供高级语法高亮
处理内容:
- 语言别名映射
- 支持的语言检测
- 生成 minted + mdframed 环境
- 回退到 text 语言
语言映射:
local lang_map = {
["sh"] = "bash",
["shell"] = "bash",
["js"] = "javascript",
["ts"] = "typescript",
["py"] = "python",
["yml"] = "yaml"
}
5. cleanup-filter.lua
作用: 清理各种格式问题,确保 LaTeX 编译成功
处理内容:
- 移除代码块中的尾随空行
- 清理空段落
- 修复双反斜杠问题
- 清理表格单元格
6. symbol-fallback-filter.lua
作用: 为特殊符号提供 LaTeX 命令回退
符号映射示例:
local symbol_map = {
["₹"] = "\\rupee{}", -- 印度卢比
["₽"] = "\\ruble{}", -- 俄罗斯卢布
["ℹ"] = "\\infoSymbol{}", -- 信息符号
["✊"] = "\\raisedFist{}", -- 举拳符号
}
7. table-wrap.lua
作用: 优化表格格式,支持跨页和内容包装
处理内容:
- 转换为 longtable 环境
- 自动计算列宽
- 处理表格中的内联代码
- 支持表格跨页
使用方法
基本用法
# 导出基本 PDF
python cli.py content/zh/book/example -o output.pdf
# 启用 emoji 支持
python cli.py content/zh/book/example -o output.pdf --emoji
# 生成目录摘要
python cli.py content/zh/book/example --generate-summary
# 包含草稿内容
python cli.py content/zh/book/example -o output.pdf --include-drafts
高级选项
# 使用自定义模板
python cli.py content/zh/book/example -o output.pdf --template custom.tex
# 添加附录
python cli.py content/zh/book/example -o output.pdf --appendix appendix.md
# 运行系统诊断
python cli.py --diagnostics content/zh/book/example
# 缓存管理
python cli.py --cache-info content/zh/book/example
python cli.py --clean-cache all content/zh/book/example
配置说明
书籍配置
在书籍根目录的 _index.md
中配置书籍信息:
---
title: "书籍标题"
book:
title: "完整书籍标题"
author: "作者姓名"
date: "2024-01-01"
description: "书籍描述"
language: "zh-hans"
cover: "cover.png"
website: "https://example.com"
appendix: true
subject: "技术文档"
keywords: "Hugo, PDF, 导出"
creator: "LaTeX with hyperref"
producer: "XeLaTeX"
---
章节配置
在每个章节的 _index.md
中配置:
---
title: "章节标题"
weight: 10
draft: false
publish: true
export_pdf: true
---
控制选项
draft: true
- 标记为草稿,默认不导出publish: false
- 不发布,不导出export_pdf: false
- 不包含在 PDF 导出中
故障排除
常见问题
1. LaTeX 编译错误
症状: Pandoc 执行失败,LaTeX 错误信息 解决方案:
- 检查 LaTeX 包安装:
tlmgr list --installed
- 安装缺失包:
sudo tlmgr install package-name
- 运行诊断:
python cli.py --diagnostics
2. Emoji 显示问题
症状: PDF 中 emoji 显示为方框或缺失 解决方案:
- 检查 emoji 字体:
fc-list | grep -i emoji
- 安装 emoji 字体(macOS: Apple Color Emoji,Linux: Noto Color Emoji)
- 使用 LuaLaTeX 引擎:
--emoji
选项
3. 图片处理失败
症状: 图片无法显示或转换失败 解决方案:
- 检查 ImageMagick 安装:
magick --version
- 清理图片缓存:
--clean-cache images
- 检查图片路径和权限
4. 中文字体问题
症状: 中文字符显示异常 解决方案:
- 安装中文字体(推荐:Source Han Sans SC)
- 检查字体配置:
fc-list :lang=zh
- 使用正确的 LaTeX 引擎
5. 表格内联代码背景渲染问题
症状: 表格中内联代码的背景色覆盖整个单元格宽度 解决方案:
- 问题已在最新版本中修复
\flexcode{}
命令现在只为实际代码内容添加背景色- 如遇到相关问题,检查
codecolor
颜色是否正确定义
诊断工具
系统诊断
python cli.py --diagnostics content/zh/book/example
诊断内容包括:
- LaTeX 引擎可用性
- 必需包安装状态
- 字体系统检查
- Lua 过滤器验证
- 系统环境信息
缓存信息
python cli.py --cache-info content/zh/book/example
显示:
- 缓存目录位置
- 缓存文件统计
- 缓存大小信息
- 缓存有效性
性能优化
图片缓存
- 自动缓存转换后的图片
- 基于文件哈希的缓存验证
- 支持增量更新
编译优化
- 并行图片处理
- 智能重试机制
- 错误恢复策略
内存管理
- 临时文件自动清理
- 大文件流式处理
- 内存使用监控
扩展开发
添加新的 Lua 过滤器
- 在
filters/
目录创建新的.lua
文件 - 实现必要的处理函数
- 在主脚本中添加过滤器引用
- 测试过滤器功能
自定义 LaTeX 模板
- 复制
template.tex
为新模板 - 修改样式和布局
- 使用
--template
选项指定模板
扩展图片处理
- 在
cli.py
中添加新的转换函数 - 更新
process_images_in_content
函数 - 添加新的格式支持
依赖要求
必需依赖
- Python 3.7+
- Pandoc 2.0+
- LaTeX 发行版(TeX Live 或 MacTeX)
- ImageMagick(图片转换)
LaTeX 包
- xeCJK(中文支持)
- fontspec(字体配置)
- minted(代码高亮)
- longtable(表格支持)
- graphicx(图片支持)
- hyperref(链接和书签)
可选依赖
- Pygments(语法高亮)
- emoji 字体包
- 额外的 LaTeX 包
代码架构详解
主要类和函数
Node 类
@dataclass
class Node:
"""表示书籍中的章节或部分"""
title: str # 章节标题
path: str # 文件路径
weight: int # 排序权重
children: List["Node"] = field(default_factory=list) # 子章节
核心函数解析
parse_front_matter(path)
功能: 解析 Markdown 文件的 front matter
返回: (title, weight, draft, publish, export_pdf)
关键逻辑:
- 解析 YAML front matter
- 提取标题、权重、发布状态
- 支持多种布尔值格式
build_tree(directory, include_drafts)
功能: 递归构建章节树 参数:
directory
: 扫描的目录路径include_drafts
: 是否包含草稿内容 返回: Node 对象或 None
处理流程:
- 查找
_index.md
或index.md
- 解析 front matter
- 检查发布状态和导出设置
- 递归处理子目录
- 按 weight 排序子节点
process_images_in_content(content, book_dir, temp_dir, temp_pngs, current_file_path, cache_dir)
功能: 处理内容中的图片引用 参数:
content
: Markdown 内容book_dir
: 书籍根目录temp_dir
: 临时目录temp_pngs
: 临时 PNG 文件列表current_file_path
: 当前文件路径cache_dir
: 缓存目录
处理步骤:
- 移除 mermaid 代码块
- 查找图片引用

- 处理远程图片下载
- 转换图片格式(SVG→PNG, WebP→PNG)
- 更新图片引用路径
- 生成 LaTeX figure 环境
图片缓存系统
缓存机制
- 哈希验证: 使用 SHA256 检测文件变更
- 元数据管理: JSON 格式存储缓存信息
- 自动清理: 无效缓存自动删除
- 增量更新: 只处理变更的图片
缓存结构
{
"image_hash_12chars.png": {
"source_path": "/path/to/original/image.svg",
"source_hash": "sha256_hash",
"cached_at": 1640995200.0,
"cache_path": "/cache/dir/image_hash_12chars.png"
}
}
缓存函数
get_cached_image(source_path, cache_dir, target_extension)
功能: 检查并返回有效的缓存图片 返回: 缓存路径或 None
save_to_cache(source_path, converted_path, cache_dir)
功能: 将转换后的图片保存到缓存 返回: 缓存路径或 None
字体系统
字体检测
def detect_emoji_fonts():
"""检测系统可用的 emoji 字体"""
emoji_font_priorities = [
'Apple Color Emoji', # macOS
'Noto Color Emoji', # Linux
'Segoe UI Emoji', # Windows
'Arial Unicode MS', # 回退
]
# 返回检测结果字典
字体配置生成
def generate_emoji_font_config(emoji_fonts_info):
"""生成 LaTeX 字体配置命令"""
# 生成嵌套的 \IfFontExistsTF 命令
# 实现优先级回退机制
错误处理机制
多层重试策略
- Pandoc 执行重试: 最多 2 次重试
- 错误分析: 分析具体错误类型
- 自动修复: 应用针对性修复策略
- 优雅降级: emoji 支持失败时回退到 XeLaTeX
错误分析函数
def _analyze_pandoc_error(error, emoji, pdf_engine, validation):
"""分析 Pandoc 错误并提供修复建议"""
return {
'retry_recommended': bool,
'retry_reason': str,
'suggested_fixes': list
}
诊断系统
EmojiDiagnostics 类
功能: 全面的系统诊断 检查项目:
- LaTeX 引擎可用性
- 必需包安装状态
- 字体系统检查
- Lua 过滤器验证
- Python 依赖检查
诊断报告格式
@dataclass
class DiagnosticResult:
name: str # 诊断项名称
status: str # pass/warning/fail
message: str # 详细信息
suggestion: str # 修复建议
高级配置
LaTeX 模板自定义
模板变量
模板支持以下变量:
$title$
- 书籍标题$author$
- 作者$date$
- 日期$emoji$
- emoji 支持标志$primary_emoji_font$
- 主要 emoji 字体$cover-image$
- 封面图片路径$backcover-image$
- 封底图片路径
字体配置
% LuaLaTeX 多语言字体配置
\ifluatex
\usepackage{fontspec}
\defaultfontfeatures{Renderer=HarfBuzz}
% 配置主字体
\IfFontExistsTF{Source Han Sans SC}{
\setmainfont{Source Han Sans SC}[
Scale=1.0,
BoldFont=Source Han Sans SC Bold,
Renderer=HarfBuzz
]
}{
% 字体回退逻辑
}
\fi
代码块样式
% minted 代码块样式
\definecolor{codebg}{RGB}{248,248,248}
\mdfdefinestyle{codeblockstyle}{
backgroundcolor=codebg,
linecolor=gray,
linewidth=1pt,
leftmargin=0pt,
rightmargin=0pt,
innerleftmargin=8pt,
innerrightmargin=8pt,
innertopmargin=8pt,
innerbottommargin=8pt
}
Pandoc 命令行选项
基本选项
pandoc input.md -o output.pdf \
--pdf-engine=lualatex \
--template=template.tex \
--toc \
--toc-depth=4 \
--number-sections \
--top-level-division=chapter
高级选项
# 启用 shell-escape(minted 需要)
--pdf-engine-opt=-shell-escape
# 设置交互模式
--pdf-engine-opt=--interaction=nonstopmode
# 资源路径
--resource-path=/path/to/images
# 列宽设置
--columns=120
# 保持换行
--wrap=preserve
过滤器开发指南
基本结构
-- 过滤器名称和描述
-- 功能说明
-- 全局变量和配置
local config = {
option1 = "value1",
option2 = "value2"
}
-- 辅助函数
local function helper_function(input)
-- 处理逻辑
return output
end
-- 主要处理函数
function ElementType(elem)
-- 检查输出格式
if not FORMAT:match 'latex' then
return elem
end
-- 处理逻辑
local processed = helper_function(elem.content)
-- 返回处理结果
if processed ~= elem.content then
return pandoc.RawInline('latex', processed)
end
return elem
end
常用 Pandoc 元素类型
Str
- 字符串文本Code
- 内联代码CodeBlock
- 代码块RawInline
- 原始内联内容RawBlock
- 原始块内容Table
- 表格Image
- 图片Link
- 链接
调试技巧
-- 输出调试信息
function debug_print(message)
io.stderr:write("[DEBUG] " .. message .. "\n")
end
-- 检查元素类型
function inspect_element(elem)
debug_print("Element type: " .. elem.t)
if elem.text then
debug_print("Text content: " .. elem.text)
end
end
最佳实践
内容组织
- 合理的目录结构: 使用清晰的层次结构
- 权重设置: 使用 weight 控制章节顺序
- front matter 规范: 统一的元数据格式
- 图片管理: 集中存放图片资源
性能优化
- 图片优化: 使用适当的图片格式和大小
- 缓存利用: 充分利用图片缓存机制
- 增量构建: 只重新处理变更的内容
- 并行处理: 利用多核处理能力
质量控制
- 内容验证: 检查链接和图片引用
- 格式一致性: 统一的 Markdown 格式
- 测试流程: 定期测试 PDF 导出
- 版本控制: 跟踪配置和模板变更
版本历史
v2.1.1 (2024-07-27)
- 🐛 修复内联代码溢出问题
- ⚖️ 平衡背景色覆盖范围与换行能力
- 🔧 优化
\flexcode{}
命令使用 80% 行宽约束 - 📝 更新修复文档说明
v2.1.0 (2024-07-27)
- 🐛 修复表格内联代码背景渲染问题
- ✨ 添加
codecolor
颜色定义 - 🔧 重新设计
\flexcode{}
命令实现 - 📚 完善故障排除文档
v2.0.0
- 重构 Lua 过滤器系统
- 添加 emoji 支持
- 改进图片缓存机制
- 增强错误处理
v1.5.0
- 添加诊断工具
- 优化表格处理
- 改进中文字体支持
v1.0.0
- 初始版本
- 基本 PDF 导出功能
- Hugo 目录树支持