本文主要写如何使用 Pandoc 从 Markdown 文件生成格式精美的 PDF 文件,也包含了一些遇到的问题的解决办法。

你需要什么

在正式开始文章之前,首先你需要安装下面的这些工具才能在后续正常进行操作:

  • 首先当然是 Pandoc,下载最新的安装包,安装完以后,记得把 Pandoc 安装目录加入系统 PATH 变量。
  • TeX 发行版。请确保你的系统已经安装了 TeX 软件,你可以使用 TeX Live 或者 MiKTeX,安装完成之后可能需要设置 PATH,推荐安装 TeX Live。
  • 一个强大的文本编辑器,当然首推 Sublime Text. 当然了,Visual Studio CodeGitHub Atom 也是不错的选择。

使用 Pandoc 从 Markdown 生成 PDF 文件

背景介绍

Pandoc 可以很方便地对不同 Markup 语言的文件进行格式转换,因此被誉为格式转换的「瑞士军刀」。使用 Pandoc 把 Markdown 文件转为 PDF 文件,实际上包含两个步骤:

  • 第一步, Markdown 文件被转为 LaTeX 源文件。
  • 第二步,调用系统的 pdflatex, xelatex 或者其他 TeX 命令,将 .tex 文件转换为最终的 PDF 文件 (见上图)。

由于我的文档是中文,并且包含引用,表格等比较复杂的格式,在文件转换过程中遇到了一些问题,下面介绍具体解决办法。

如何处理中文

Pandoc 默认使用的 pdflatex 命令无法处理 Unicode 字符,如果要把包含中文的 Markdown 文件转为 PDF,在生成 PDF 的过程中会报错。我们需要使用 xelatex 来处理中文,并且需要使用 CJKmainfont 选项指定支持中文的字体。 在 Windows 系统中,对于 Pandoc 2.0 版本以上,可以使用以下的命令生成 PDF 文件:

pandoc --pdf-engine=xelatex -V CJKmainfont="KaiTi" test.md -o test1.pdf

CJKmainfont 后面跟的是支持中文的字体名称。那么如何找到支持中文的字体呢? 首先,你需要知道你所使用的语言的 language code, 例如,中文(即 Chinese)的 language code 是 zh。 然后使用 fc-list 命令查看支持中文的字体(对于 Windows 系统,fc-list 命令在安装 TeX Live 完整版以后可以使用, Unix 系统一般会预装这个程序):

fc-list :lang=zh # zh 是中文的 「language code」

系统输出如下图所示:

字体的名称就是字体文件位置后面的字符串,由于字体名称可能会包含空格,因此在引用的时候需要加上引号,例如 -V CJKmainfont="Source Han Serif CN"

在 Pandoc 2.0 版本中, --pdf-engine 命令取代了原有的 --latex-engine 命令。对于 Linux 系统,Pandoc 版本可能比较老,上述生成 PDF 的命令可能并不适合,正确的命令如下 (在 Pandoc 1.12.3.1 验证):

pandoc --latex-engine=xelatex -V mainfont='WenQuanYi Micro Hei' test.md -o test.pdf

在 Linux 系统上,找出支持中文的字体的方式与 Windows 系统是一样的。

使用问题以及技巧

block quote, table 以及 list 未能正确渲染

原因是在 Pandoc 中 block quote, list 以及 table 前需要空一行。 另外 block quote 中每一行渲染成 PDF 未能正确换行,所有行的文字都跑到了一行,可以通过强制在原 block quote 的每一行后面加上空格来解决。

给 block code 加上 highlight

Pandoc 支持给 block code 里面的代码加上背景高亮,并提供了不同的高亮主题,支持非常多的语言。要列出 Pandoc 提供的高亮方案,使用下面命令,

pandoc --list-highlight-styles

要列出所有支持的语言,使用下面命令,

pandoc --list-highlight-languages

要使用语法高亮,Markdown 文件中的 block code 必须指定语言,同时在命令行使用 --highligh-style 选项,例如:

pandoc --pdf-engine=xelatex --highlight-style zenburn test.md -o test.pdf

以上命令,使用了 zenburn 主题, 另外,也推荐使用 tango, zenburn 或者 breezedark 高亮主题。

给 section 加上编号以及给文章加上目录

默认情况下,生成的 PDF 不含目录,同时各级标题不含编号,仅仅字体大小有变化,要给各个 section 加上编号,可以用 -N 选项;加上目录,可以使用 --toc 选项。 一个完整例子如下:

pandoc --pdf-engine=xelatex --toc -N -o test.pdf test.md

给链接加上颜色

根据 Pandoc user guide 的说明,我们可以通过 colorlinks 选项给各种链接加上颜色,便于和普通文本区分开来:

colorlinks
    add color to link text; automatically enabled if any of linkcolor, filecolor, citecolor, urlcolor, or toccolor are set

同时,为了精确控制不同类型链接颜色,Pandoc 还提供了对不同链接颜色的个性化设置选项:

linkcolor, filecolor, citecolor, urlcolor, toccolor
    color for internal links, external links, citation links, linked URLs, and links in table of contents, respectively: uses options allowed by xcolor, including the dvipsnames, svgnames, and x11names lists

例如,如果我们想给 URL 链接加上颜色,并且 urlcolor 要设为 NavyBlue, 可以使用下面的命令:

pandoc --pdf-engine=xelatex -V colorlinks -V urlcolor=NavyBlue test.md -o test.pdf

其他链接的颜色可以按照上述方式设置。

值得注意的是,urlcolor 选项并不能给文中直接展示的 raw URL link 加上颜色。 要给直接展示的 URL link 加上颜色,可以用 <> 包围要展示的链接,例如<www.google.com>

另外,也可以使用选项 -f gfm,参考这里。完整命令如下,

pandoc --pdf-engine=xelatex -f gfm -Vurlcolor=cyan -o test.pdf test.md

使用 -f gfm 的一个缺点是 gfm 不支持公式,因此如果在 Markdown 中包含公式,将不能正确渲染。解决办法是去掉 -f gfm flag,或者使用 Pandoc 自带的 markdown 格式。

更改 PDF 的 margin

使用默认设置生成的 PDF margin 太大,根据 Pandoc 官方 FAQ,可以使用下面的选项更改 margin:

-V geometry:"top=2cm, bottom=1.5cm, left=2cm, right=2cm"

完整命令为:

pandoc --pdf-engine=xelatex -V geometry:"top=2cm, bottom=1.5cm, left=2cm, right=2cm" -o test.pdf test.md

Markdown 文件中使用斜杠 backslash 会出错

原始的 Markdown 格式,支持在文件中使用 backslash,但是 Pandoc 把 backslash 以及后面的内容理解成 LaTeX 命令,因此在编译包含 backslash 的文件时,可能会遇到 undefined command 错误或者更加奇怪的错误。 参考这里以及这里,解决办法是让 Pandoc 把 Markdown 文件当成正常的 Markdown,不要解读 LaTeX 命令,使用下面的 flag:

pandoc -f markdown-raw_tex

或者,也可以用两个 backslash 表示字面意义的 backslash,例如 \\columnwidth 或者如果 backslash 及后面文字原本就是一个命令, 用 inline code block 包含起来。

给 inline code 加上背景色

将 Markdown 转为 PDF 时,由于 Pandoc 使用 LaTeX 中的 \textt 命令来表示 inline code,所以 inline code 是没有背景色的。为了增加 inline code 的可读性,我们可以修改 \texttt 命令,给文本添加背景色。首先建立 head.tex 文件,在其中加入以下命令:

% change background color for inline code in
% markdown files. The following code does not work well for
% long text as the text will exceed the page boundary
\definecolor{bgcolor}{HTML}{E0E0E0}
\let\oldtexttt\texttt

\renewcommand{\texttt}[1]{
  \colorbox{bgcolor}{\oldtexttt{#1}}
}

在使用 Pandoc 转换 Markdown 文件时,加上 -H 选项来引用 head.tex 文件,例如

pandoc --pdf-engine=xelatex -H head.tex test.md -o test.pdf

这样生成的 PDF 中,inline code 就会有灰色的背景色,背景颜色可以根据自己喜好修改。

将设置放入 head.tex 文件中

由于将 Markdown 转为 PDF 需要诸多选项与设置,将这些设置都写在命令行上,既浪费时间,也不利于小修小改,所以可以将常用的一些命令都放到 head.tex 文件中,然后在转换 Markdown 文件的时候引用该文件即可。

例如,可以将设置页面宽度的命令,给 inline code 着色的命令,以及设置链接颜色的命令统一放入 head.tex 中:

\usepackage{fancyvrb,newverbs}
\usepackage[top=2cm, bottom=1.5cm, left=2cm, right=2cm]{geometry}

% change background color for inline code in
% markdown files. The following code does not work well for
% long text as the text will exceed the page boundary
\definecolor{bgcolor}{HTML}{E0E0E0}
\let\oldtexttt\texttt

\renewcommand{\texttt}[1]{
  \colorbox{bgcolor}{\oldtexttt{#1}}
}

%% color and other settings for hyperref package
\hypersetup{
    bookmarksopen=true,
    linkcolor=blue,
    filecolor=magenta,
    urlcolor=RoyalBlue,
}

下次使用的时候,直接用 -H 选项引用以上文件即可。

List 嵌套层次太多的问题

读者Karl Liu反应,如果使用 Markdown List 嵌套的层次太多(具体是超过六层),使用 Pandoc 生成的 PDF 文件的时候,会出现错误

! LaTeX Error: Too deeply nested.

具体讨论见这个 issue,作者提出的解决方法是,在 head.tex 文件中加上如下设置:

\usepackage{enumitem}
\setlistdepth{9}

\setlist[itemize,1]{label=$\bullet$}
\setlist[itemize,2]{label=$\bullet$}
\setlist[itemize,3]{label=$\bullet$}
\setlist[itemize,4]{label=$\bullet$}
\setlist[itemize,5]{label=$\bullet$}
\setlist[itemize,6]{label=$\bullet$}
\setlist[itemize,7]{label=$\bullet$}
\setlist[itemize,8]{label=$\bullet$}
\setlist[itemize,9]{label=$\bullet$}
\renewlist{itemize}{itemize}{9}

\setlist[enumerate,1]{label=$\arabic*.$}
\setlist[enumerate,2]{label=$\alph*.$}
\setlist[enumerate,3]{label=$\roman*.$}
\setlist[enumerate,4]{label=$\arabic*.$}
\setlist[enumerate,5]{label=$\alpha*$}
\setlist[enumerate,6]{label=$\roman*.$}
\setlist[enumerate,7]{label=$\arabic*.$}
\setlist[enumerate,8]{label=$\alph*.$}
\setlist[enumerate,9]{label=$\roman*.$}
\renewlist{enumerate}{enumerate}{9}

在编译 PDF 的时候,加上 -H head.tex 选项。

使用 Sublime Text build system 生成 PDF

写完 Markdown,再切换到 terminal 使用 Pandoc 生成 PDF 以及预览,颇为麻烦,因此我使用 Sublime Text 的 build system 来简化编译生成 PDF 以及预览的整个过程。PDF 预览使用了轻量级的 Sumatra PDF reader

一个示例的 build system 如下,

{
    "shell_cmd": "pandoc --pdf-engine=xelatex --highlight-style=zenburn -V colorlinks -V CJKmainfont=KaiTi \"${file}\" -o \"${file_path}/${file_base_name}.pdf\" ",
    "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
    "working_dir": "${file_path}",
    "selector": "text.html.markdown",

    "variants":
    [
        {
            "name": "Convert to PDF and Preview",
            "shell_cmd": "pandoc --pdf-engine=xelatex --highlight-style=zenburn -V colorlinks  -V CJKmainfont=KaiTi \"${file}\" -o \"${file_path}/${file_base_name}.pdf\"  &&SumatraPDF \"${file_path}/${file_base_name}.pdf\" ",
            // "shell_cmd":   "start \"$file_base_name\" call $file_base_name"
        }
    ]
}

在命令中使用到了引号,必须使用 backslash escape。 可以从 这里 下载这个 build system。

系统无法识别 Pandoc 的问题

不知什么原因,上述把 Markdown 文件转为 PDF 的 build system 在编译 Markdown 文件时,突然给出如下错误提示:

‘pandoc’ is not recognized as an internal or external command, operable program or batch file.

通过查询 sublime 文档,发现我们可以在 build system 里面加上 path 变量。因此,我对上述的 build system 进行了调整,新的 build system 如下:

{
    "shell_cmd": "pandoc --pdf-engine=xelatex --highlight-style=zenburn -V colorlinks -V CJKmainfont=\"Source Han Serif SC\" \"${file}\" -o \"${file_path}/${file_base_name}.pdf\" ",
    "path": "C:/Users/east/AppData/Local/Pandoc/;%PATH%",
    "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
    "working_dir": "${file_path}",
    "selector": "text.html.markdown",

    "variants":
    [
        {
            "name": "Convert to PDF and Preview",
            "shell_cmd": "pandoc --pdf-engine=xelatex --highlight-style=zenburn -V colorlinks  -V CJKmainfont=\"Source Han Serif SC\" \"${file}\" -o \"${file_path}/${file_base_name}.pdf\"  &&SumatraPDF \"${file_path}/${file_base_name}.pdf\" ",
            "path": "C:/Users/east/AppData/Local/Pandoc/;%PATH%",
            // "shell_cmd":   "start \"$file_base_name\" call $file_base_name"
        }
    ]
}

使用新的 build system 以后,一切恢复正常。

笔记云同步

云同步可以选择坚果云。同步速度快,并且支持多个文件夹同步,每个月有 1G 免费上传流量,对于文档同步来说,足够使用了。当然也可以使用自己的网盘或者云存储,都是可以的,看自己喜好。


参考