For English version of this post, click here.

更新时间:2019-11-18

本文主要写如何使用 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 必须指定语言,同时在命令行使用 --highlight-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 中的 extt 命令来表示 inline code,所以 inline code 是没有背景色的。为了增加 inline code 的可读性,我们可以 修改 exttt 命令,给文本添加背景色。首先建立 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}
letoldtexttt	exttt

enewcommand{ exttt}[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 就会有灰色的背景色,背景颜色可以根据自己喜好修改。

更改引用 (block quote) 的风格

默认情况下,当我们把 Markdown 文件转为 PDF 时,Pandoc 使用 LaTeX 中的 quote 环境来表示 block quote。生成的 PDF 中,引用文字只是被缩进了,看起来很不明显。

我们可以自己定义一个 quotation 环境,给环境加上背景颜色和缩进。将下面的设置写入 到 head.tex 中:

usepackage{framed}
usepackage{quoting}

definecolor{bgcolor}{HTML}{DADADA}
colorlet{shadecolor}{bgcolor}
% define a new environment shadedquotation. You can change leftmargin and
% rightmargin as you wish.
ewenvironment{shadedquotation}
{egin{shaded}
quoting[leftmargin=1em, rightmargin=0pt, vskip=0pt, font=itshape]
}
{endquoting
end{shaded
}
}

%
defquote{shadedquotation}
defendquote{endshadedquotation}

当你想把 Markdown 转为 PDF 时,可以使用下面的命令:

pandoc -H head.tex test.md -o test.pdf

生成的 PDF 中引用的效果如下:

参考

将设置放入 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}
letoldtexttt exttt

enewcommand{ exttt}[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=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,2]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,3]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,4]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,5]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,6]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,7]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,8]{label=(</span><span class="nv">ullet</span><span class="s">)}
setlist[itemize,9]{label=(</span><span class="nv">ullet</span><span class="s">)}
enewlist{itemize}{itemize}{9}

setlist[enumerate,1]{label=(</span><span class="nv">arabic</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,2]{label=(</span><span class="nv">alph</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,3]{label=(</span><span class="nv"> oman</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,4]{label=(</span><span class="nv">arabic</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,5]{label=(</span><span class="nv">alpha</span><span class="o">*</span><span class="s">)}
setlist[enumerate,6]{label=(</span><span class="nv"> oman</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,7]{label=(</span><span class="nv">arabic</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,8]{label=(</span><span class="nv">alph</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
setlist[enumerate,9]{label=(</span><span class="nv"> oman</span><span class="o">*</span><span class="nb">.</span><span class="s">)}
enewlist{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 免费上传流量,对于文档同步来说,足够使用了。当然也可以使 用自己的网盘或者云存储,都是可以的,看自己喜好。

注:以后有更新的话,会优先更新文章开头提到的英文版本,由于精力有限,此版本更新 将不会很频繁,以修复错误为主。

参考资料