用pandoc生成大型中文文档的痛点与解决方案
Pandoc是转换文本格式的利器。在用 Pandoc 转换中文文档和生成大型文档时,例如用中文写作毕业论文等时,会遇到一些很麻烦的问题。我在前面的博客里说过我在用 Markdown 写博士毕业论文,这篇博客就我自己的经验讲一下在用 Pandoc 生成大型中文文档的痛点与解决方案。
Pandoc 将 Markdown 生成 pdf 的逻辑
用 Pandoc 将 Markdown 文件生成 pdf,其实是先把 Markdown 格式转换成 LaTeX 格式,再通过 LaTeX 把转换后的 LaTeX 文件生成最终的 pdf 文件。在用 LaTeX 生成 pdf 时,可以指定不同的--pdf-engine
,可选的选项有pdflatex
、xelatex
、lualatex
等。
因为 Pandoc 是通过 LaTeX 来生成 pdf 文件的,所以可以通过修改或添加 LaTeX 源文件来修改最终的 pdf 格式。如果不怕麻烦,可以先用 Pandoc 把 Markdown 文件转换成 LaTeX,再在上面自己改格式。
pandoc --standalone test.md -o test.tex
但是,一般的做法是通过给 Pandoc 传递 LaTeX 命令和 Pandoc 自带的参数来修改 pdf 格式的。当然,也可以直接在 Pandoc 的 pdf template 上进行修改。不过 Pandoc 自带的 template 很复杂,一般用--header-includes
给自带的 template 加上需要的 LaTeX 命令就可以了。
另外,Pandoc 自身也提供来一些可以自定义的参数,这些参数一般是在文件开头用 yaml
写的,Pandoc 将这些参数称作“metadata”。所以在用 Pandoc 生成一个自定义格式的大型 pdf 文档时,除了文本文档外,还需要准备preable.tex
与metadata.yaml
两个文件,分别用来自定义 LaTeX 与 Pandoc 自带的参数。
用 Pandoc 生成中文 pdf
要在 Pandoc 生成的 pdf 里显示中文,跟在 LaTeX 里显示中文的逻辑一样,一般用ctex
包或xeCJK
包就可以了。不过不建议用ctex
包,因为它除了显示中文外还定义了太多其他格式。不如直接用xeCJK
来显示中文,再直接用 LaTeX 提供的其他包来指定其他格式。
用pandoc
来生成中文 pdf 时,需要将默认的--pdf-engine
改成xelatex
:
pandoc --pdf-engine=xelatex test.md -o test.pdf
用pandoc生成大型文档
在用 LaTeX 写作大型文档时,可以用\include{}
来包含单个文件。在用Markdown写大型文档时,用Pandoc
将多个Markdown文件转换成一整个文件时,可以将文件名按顺序放在pandoc
命令中进行转换。必须注意的是,要保证生成的文件是按需要的顺序出现的,需要把Markdown文件按照顺序命名,例如第一章的文件名是1.md
,第二章为2.md
等。
pandoc --pdf-engine=xelatex 1.md 2.md 3.md -o main.pdf
在用这个命令生成大型文档的时候,可能会出现后一个文件变成前一个文件一部分的情况。要避免出现这种情况,必须在每个文档文本后加一个空行。
另外一个问题是,在Markdown中的标题用pandoc
生成默认是不编号的。要给标题编号,需要在yaml
文件中加上以下这行。
numbersections: true
在用单个Markdown文件写作文章时,可以把yaml
参数加在文本上方;但在用多个Markdown文件写作时,最好将yaml
参数单独保存成一个文件,在pandoc
命令中以--metadata-file
来指定该文件就可以了。
pandoc --pdf-engine=xelatex --metadata-file=metadata.yaml 1.md 2.md 3.md -o main.pdf
交叉引用
写作大型文档时,尤其是技术或学术文档时,难免要对图表、公式、章节等进行交叉引用。Pandoc 本身提供了对不同部分进行交叉引用的功能。
Pandoc 本身并没有提供对图表进行交叉引用的功能。要实现这种功能,可以用过滤器--filter
来实现。在MacOS
上,可以用pandoc-crossref来处理图表、公式的编号与交叉引用;在Windows
平台上,对图片、表格、公式的交叉引用可以分别用pandoc-tablenos、pandoc-fignos、pandoc-eqnos来实现。这几个过滤器可以用以下表格中的命令来安装1。
filter | OS | installation |
---|---|---|
pandoc-crossref | MacOS | brew install pandoc-crossref |
pandoc-tablenos | Windows | pip install pandoc-tablenos |
pandoc-fignos | Windows | pip install pandoc-fignos |
pandoc-eqnos | Windows | pip install pandoc-eqnos |
在 Pandoc 中使用过滤器只要用--filter
参数指定所要用的过滤器即可。这几个过滤器的使用逻辑都是一致的,可以设置的参数包括图表标题前缀(tableTitle
或figureTitle
等)、文中引用前缀(tabPrefix
或figPrefix
)等。其他具体参数可以参考各自的说明文档。
至此,Pandoc 命令就变成了以下这样(以 MacOS 为例):
pandoc --pdf-engine=xelatex --filter pandoc-crossref --metadata-file=metadata.yaml 1.md 2.md 3.md -o main.pdf
章节目录
在 LaTeX 中要对特定的标题不编号,可以用\chapter{}
等命令中加*
来实现。Pandoc 也提供了这种功能。对于不想编号的标题,标题后面加上{-}
或者{.unnumbered}
就可以。
要在生成的文件中自动生成目录,只需要将toc
这一参数指定为true
就可以。文档中包含的表格与图片等也可以自动生成目录,分别将lot
和lof
指定为true
就可以了。但是,在 Pandoc 里可以指定目录的标题,例如toc-title: 目录
,但是表格目录与图片目录的标题没法指定。这个问题可以用 LaTeX 来解决,只要在 LaTeX head 里指定表格目录与图片目录标题就行了:
\renewcommand\listtablename{表\ 格\ 目\ 录}
\renewcommand\listfigurename{图\ 片\ 目\ 录}
在用 LaTeX 定义章节等标题格式时,经常会用到titlesec
这一 LaTeX 包。但是,在head
里指定了这个包之后,运行 Pandoc 会出现这样的错误信息:
Error producing PDF.
! Argument of \paragraph has an extra }.
<inserted text>
\par
l.1628 \ttl@extract\paragraph
要解决这个问题,必须在yaml
文件里加上subparagraph: yes
这一行。
yaml
文件参数与含义
前面涉及到了很多yaml
文件里的参数。这里将常见的yaml
参数条列如下,并简单说明各自的作用。
title: 标题 # 指定标题
author: 作者 # 指定作者
numbersections: true # true 表示给文中的部分编号,默认值为 false
subparagraph: yes # 要在 LaTeX head 里用 titlesec 这个包,必须加这一行
fontsize: 12pt # 指定字号大小,默认接受10pt、11pt、12pt
toc: true # 生成目录
toc-title: "目录" # 指定目录标题
lot: true # 生成表格目录
lof: true # 生成图片目录
tableTitle: "表" # 表格标题的前缀,pandoc-crossref 与 pandoc-tablenos 中可用
figureTitle: "图" # 图片标题的前缀,pandoc-crossref 与 pandoc-fignos 中可用
tabPrefix: "表" # 文中对表格引用的前缀,pandoc-crossref 可用
figPrefix: "表" # 文中对图片引用的前缀,pandoc-crossref 可用
header-includes: # 要加进 LaTeX文件的命令,建议放在一个单独的 preamble 文件里
其他 Pandoc 资料
网上有很多其他非常好的 Pandoc 资料,例如:
- Customizing pandoc to generate beautiful pdfs from markdown
- Generating Beautiful PDF from Markdown with Pandoc and Sublime Text
-
在
Windows
里的两个过滤器都是用Python
的包管理器pip
来装的。所以要装这两个过滤器,要先装Python
。除了这里给的命令之外,还可以用python -m pip install packageName
来装python
第三方包。关于pip install
与python -m install
的差别,参见https://snarky.ca/why-you-should-use-python-m-pip/。 ↩︎