说起来,“Markdown 转 PDF”这个需求,我最早是写技术文档的时候被逼出来的。当时团队要求周五前交付一份带封面、目录、页眉页脚的产品说明书,我手里只有一堆 Markdown 源码。第一反应是复制到 Word 里再排版,折腾到周四晚上,发现自己光调整图表位置就花了两个多小时。后来认真把常见的转换路线捋了一遍,才摸清楚这里面不是“能不能转”的问题,而是“用什么方式转、转完能不能满足交付标准”的问题。
这篇内容不聊抽象理论,就讲三套我实测过、并且在项目里真正用出效果的方案:编辑器插件路线、Pandoc 命令行路线、浏览器打印路线。每一套我都给到完整的配置、命令以及背后为什么这么做的逻辑,最后再结合不同的使用场景给出推荐。不管你是刚接触 Markdown 的新人,还是被 PDF 格式折磨过的老手,都可以直接照着操作。
1. 选型前的三个核心问题
在动手之前,先想清楚一件事:你转换出的 PDF 到底是给谁看的,对排版的要求到了哪个程度?同样是 Markdown 转 PDF,给自己看、给同事协同、给客户交付,这三者的标准完全不同。我给学员讲的时候,通常先让大家回答三个问题:要不要页眉页脚和封面?文档里有多少代码块和表格?之后是不是要反复修改、频繁重新导出?
这三个问题的答案,基本就框定了方案选择的范围。如果想要效果接近出版物级别,编辑器自带的导出功能大概率撑不住,得靠 Pandoc 配合 LaTeX 引擎去控制排版;如果只是内部看个效果、打印出来讨论,浏览器打印加适当配置就够用;如果是程序员日常写 README、接口文档,VS Code 插件一条龙是最顺手的。
这里有一个常见的误区:很多人以为 Markdown 转 PDF 就是把文本内容“换个壳子”,导出来能读就行。但实际上,Markdown 是极简排版语言,PDF 是固定版式的交付格式,中间隔着的正是排版引擎、字体渲染、分页控制这一整套链路。不同工具做的,其实是不同层面的渲染工作,这也是同样的 Markdown 文件,在不同方式下导出的 PDF 效果天差地别的根本原因。
下面把三套方案的整体情况摆在一起做个对比,方便你快速建立认知框架。
| 方案 | 工具链 | 上手成本 | 排版控制力 | 中文支持 | 典型场景 |
|---|---|---|---|---|---|
| 编辑器插件 | VS Code + Markdown PDF | 低 | 中等 | 依赖系统字体 | 技术文档、README、日常笔记 |
| 命令行 | Pandoc + xelatex | 中高 | 极强 | 需要配置字体 | 论文、报告、交付级文档 |
| 浏览器打印 | Typora / VSCode 预览 + 浏览器 | 极低 | 较弱 | 通常正常 | 快速分享、内部审阅 |
1.1 为什么同一需求会有三条路线
这个问题的本质,是对 Markdown 的“渲染责任”由谁承担的分歧。编辑器插件方案,是编辑器帮你完成 Markdown 到 HTML 的渲染,再用内置浏览器内核打印成 PDF;Pandoc 方案,是 Pandoc 负责把 Markdown 解析成文档对象模型,再交给 LaTeX 排版引擎生成 PDF;浏览器打印方案,是在你调整好预览效果之后,借助浏览器的打印功能完成“所见即所得”的输出。
这三条路线没有绝对的优劣,只有适不适合。我个人的经验是:日常笔记和开源项目文档,用 VS Code 插件足够;硕士论文、产品白皮书、带复杂表格的行业报告,老老实实上 Pandoc;三五分钟就要拿去打印的会议纪要,打开 Typora 直接导出可能更省事。
1.2 通用前提:字体与编码准备
不管用哪条路线,中文环境下都会遇到一个绕不开的问题:字体。Markdown 源文件本质是 UTF-8 编码的纯文本,PDF 是矢量排版文件,中文字符必须由字体文件提供轮廓信息。有些工具默认引用的字体里没有中文字形,就出现了最常见的“方块字”现象。
提前做两件事能少踩很多坑:第一,确认系统里有完整的中文字体,比如 Windows 下的微软雅黑、思源黑体,macOS 下的苹方,Linux 下的 Noto CJK 系列;第二,确认 Markdown 文件保存为 UTF-8 无 BOM 格式,尤其是从 Windows 记事本里复制过来的内容,编码问题很容易导致乱码。这是所有方案的基础保障,别嫌啰嗦,后面每个方案的踩坑记录里几乎都绕不开这两个因素。
2. 编辑器插件路线:VS Code 三分钟导出
VS Code 是绝大多数开发者接触 Markdown 的入口,它自带预览功能,配合插件就能把 Markdown 直接导出为 PDF。这条路线的核心优势是“顺”字,写完即转,不需要离开编辑器,也不需要记住命令行参数。缺点是排版控制力有一定上限,精密控制分页、页眉页脚比较吃力。
2.1 插件安装与选型对比
VS Code 市场里 Markdown 转 PDF 相关的插件主要有两个:Markdown PDF 和 Markdown Preview Enhanced,也就是常说的 MPE。这两个我都认真用过,说下实际体验的差异。
Markdown PDF 是纯粹为导出而生的插件,安装后右键 Markdown 文件就能看到“Markdown PDF: Export (pdf)”选项。它底层调用 Electron 相关的渲染进程,导出结果与 VS Code 预览效果高度一致,配置项直观,适合不想折腾、只想快速拿到一个版式干净 PDF 的人。
MPE 的功能要重得多,它不只是一个导出工具,还支持流程图、公式、自定义容器、幻灯片模式等。如果你正在写一个包含大量图表的 Markdown 文档,MPE 会更顺手,但它导出的 PDF 依赖当前主题的 CSS 样式,深色主题下导出内容可能是深底白字,需要额外适配。
如果你只是日常转换,我建议直接用 Markdown PDF,路径最短。
2.2 插件导出配置与实操步骤
以 Markdown PDF 为例,装好插件后,在 VS Code 设置里搜索markdown-pdf,可以看到大量配置项。我平时固定使用这样一组配置:
{ "markdown-pdf.header": "<div style='text-align: right; font-size: 10px; color: gray;'>文档标题</div><hr/>", "markdown-pdf.footer": "<div style='text-align: center; font-size: 10px; color: gray;'>第 {{pageNumber}} 页 / 共 {{totalPages}} 页</div>", "markdown-pdf.margin": { "top": "1.8cm", "right": "2cm", "bottom": "2cm", "left": "2cm" }, "markdown-pdf.format": "A4", "markdown-pdf.displayHeaderFooter": true, "markdown-pdf.styles": ["D:/config/custom.css"] }需要特别说明displayHeaderFooter这个开关,很多人在页眉页脚不显示时才发现是这里没打开。页眉页脚里的变量语法用的是 Handlebars 模板,{{pageNumber}}表示当前页,{{totalPages}}表示总页数,别手写成 Vue 的{{ }}语法以外的形式。
设置完成后,在打开的 Markdown 文件编辑区域右键,选择 “Markdown PDF: Export (pdf)”。插件会先启动一次内置浏览器渲染页面,再执行打印流程。首次运行如果发现进度条卡住或者输出目录里没出现 PDF,多数是因为 Electron 内核正在下载,稍微等一下,或者确认机器能正常访问相关资源。
2.3 自定义样式的经验与技巧
Markdown PDF 默认样式比较素,适合大多数场景,但如果你对代码块的配色、标题字号有要求,可以通过markdown-pdf.styles指向一个本地 CSS 文件。这里有一点经验之谈:CSS 只影响预览和导出效果,不影响 Markdown 源文件,所以你可以放心大胆地调整,随时一键导回。
举一个实际例子。我希望代码块在 PDF 里带浅灰底和细边框,就在自定义 CSS 里加了:
pre { background-color: #f6f8fa; border: 1px solid #d0d7de; border-radius: 6px; padding: 12px; } code { font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; font-size: 13px; }这个文件路径用绝对路径更稳妥,避免 VS Code 工作区变化后找不到样式文件的情况。另外,在表格较多、内容较宽的文档里,建议同时把table { display: block; overflow: auto; }写进样式里,防止表格被页边距截断。
3. 命令行路线:Pandoc 打造专业排版
如果说 VS Code 插件是快餐,那 Pandoc 就是正经的后厨。它的能力边界远超“Markdown 转 PDF”这一项,本质上是一个万能文档格式转换器,支持 Markdown、LaTeX、HTML、DOCX、PDF 之间的任意组合转换。能让你实现对 PDF 排版细节的极致控制,比如页边距、字号、目录深度、页眉页脚,甚至自定义封面。
3.1 Pandoc 与 LaTeX 引擎的协同原理
Pandoc 本身不直接生成 PDF,它先把 Markdown 解析成一个抽象的文档结构,再转换成 LaTeX 源码,最后调用 LaTeX 引擎(如 xelatex)编译成 PDF。所以,你机器上必须装有一个可用的 LaTeX 发行版。Windows 推荐 MiKTeX,macOS 和 Linux 推荐 TeX Live。
为什么特别强调用 xelatex 而不是默认的 pdflatex?原因在于中文字体支持。pdflatex 对 UTF-8 中文的支持很繁琐,需要用 CTeX 宏包等额外配置;而 xelatex 原生支持系统字体,可以直接指定中文字体名称,这在中文化文档转换中是决定性的优势。
3.2 安装 Pandoc 与配置中文字体
Pandoc 的安装没有太多可说的地方,Windows 直接下载安装包,macOS 可以brew install pandoc,Linux 可以用各自的包管理器。关键是 LaTeX 发行版装好后,记得更新宏包索引,否则后续编译时遇到缺少宏包的报错会非常折磨。
字体准备上,推荐安装 “Noto Sans CJK SC”或“思源黑体”,这类开源中文字体覆盖全、清晰度高。安装确认后,在命令行执行以下命令查看字体是否被系统识别:
fc-list :lang=zh如果这个命令输出的字体列表里有你需要的字体,那万事俱备,可以进入下一步了。
3.3 核心转换命令与参数解析
先看一个我常用的基础命令,这段命令足够应对 70% 的文档转换需求:
pandoc input.md -o output.pdf \ --pdf-engine=xelatex \ -V mainfont="Noto Serif CJK SC" \ -V sansfont="Noto Sans CJK SC" \ -V monofont="Noto Sans Mono CJK SC" \ -V CJKmainfont="Noto Serif CJK SC" \ -V geometry:margin=2.5cm \ -V colorlinks=true逐个解释这些参数的作用,你就明白 Pandoc 为什么能做出专业排版了。
--pdf-engine=xelatex指定引擎,解决中文和字体问题;-V是设置变量,告诉 LaTeX 模板使用什么样的字体和布局;mainfont指定正文拉丁字体,sansfont指定无衬线字体,monofont指定等宽字体(代码块字体),CJKmainfont指定中文正文;geometry:margin=2.5cm统一设置页边距;colorlinks=true让超链接在 PDF 里显示为可点击的彩色链接,而不是大段难看的蓝色方框。
实际操作时,如果文档有封面信息,还可以加一行:
-V title="文档标题" \ -V author="你的名字" \ -V date="2025-01-01"加了这几个变量后,Pandoc 会利用默认 LaTeX 模板生成一个简单的标题块,效果等于自动加了封面首页。
3.4 进阶技巧:模板、目录与页眉页脚
Pandoc 真正强的地方在于可以使用自定义 LaTeX 模板。先用下面命令把默认模板导出到本地:
pandoc -D latex > my-template.latex打开这个文件,搜索header-includes或者\begin{document}前后位置,可以插入自定义的页眉页脚定义。比如加入 fancyhdr 宏包并设置页脚中央显示页码:
\usepackage{fancyhdr} \pagestyle{fancy} \fancyhf{} \fancyfoot[C]{\small 第 \thepage\ 页}然后转换时指定--template=my-template.latex。这里要提醒一点,模板是 LaTeX 代码,语法错误会在编译阶段暴露,报错信息往往不直观。我的习惯是每次只改一小块,确认编译通过后再改下一处,别一次性堆大量自定义内容,排错能少花一半时间。
目录方面,Pandoc 支持--toc参数自动生成目录,配合--toc-depth=2可以控制目录深度只显示到二级标题:
pandoc input.md -o output.pdf --pdf-engine=xelatex --toc --toc-depth=2这个功能在长文档中特别实用,免去了手动核对页码的烦恼。
4. 零门槛路线:浏览器打印与 Typora 导出
有些情况下,我们并不需要精细排版,只要快速拿到一个版式说得过去的 PDF。这时候浏览器打印路线是最好的选择。它的原理很简单:Markdown 只是一种标记语言,在编辑器里预览时会被渲染成 HTML,而任何现代浏览器都支持把 HTML 页面“打印”为 PDF。
4.1 从 VS Code / Typora 预览到打印成 PDF
以 Typora 为例,这是很多人公认“最好用的 Markdown 编辑器”之一。打开 Markdown 文件,点击“文件 -> 导出 -> PDF”,Typora 会直接调用内置的渲染引擎生成一个带主题样式的 PDF。
不过,Typora 导出的本质其实上也相当于“套用了当前主题的网页打印”。如果你对导出版式不满意,又不打算折腾主题文件,还有一个更灵活的替代做法:在 Typora 里先导出为 HTML,再用浏览器打开这个 HTML,通过浏览器的打印功能输出 PDF。这一步能让你在打印设置里调整纸张大小、页边距、背景图形开关等选项。
对比两者,Typora 直接导出速度快、样式统一,但可调参数少;浏览器打印虽然多了一步,胜在“打印前还能手动干预”,对极少数排版有特殊要求的文档更友好。
4.2 浏览器打印对话框的关键设置
无论你是从 Typora 导出的 HTML,还是从 VS Code 预览页复制出来的 HTML,最终都要经过浏览器打印对话框这一关。很多人在这里吃亏是因为忽略了三个选项。
第一是“背景图形”,默认是关闭的,代码块浅灰底色和醒目的引用块都会变成白底,整体层次感大打折扣。需要手动在“更多设置”里勾选“背景图形”。第二是“边距”,选默认值往往上下留白过大,我习惯在打印预览里直接选“自定义”,设置上下 1.5cm、左右 2cm。第三是“缩放”,如果文档内容略宽,适当缩小到 90% 或 80%,避免表格或代码被横向裁剪。
还有一个容易忽略的问题:打印对话框选择“另存为 PDF”而不是真正的打印机。现代浏览器的“另存为 PDF”本质是一套虚拟打印驱动,支持所有打印选项,输出质量也很稳定,完全不需要额外安装软件。
4.3 这套方案的适用边界
浏览器打印方案最大的优点是快,最大缺点是分页不可控。Markdown 渲染成 HTML 后,浏览器按内容流分页,如果恰好一行为标题、下一行是正文开头,很可能出现“标题孤悬页末”的情况。我处理这类问题的方法简单粗暴:要么在 Markdown 中适当调整段落顺序,要么在关键位置手工插入分页符。
这里补充一个实用技巧:Typora 支持在 Markdown 中直接使用 HTML 标签,因此可以这样插入分页符:
<div style="page-break-after: always;"></div>这一行在渲染后的 PDF 中会强制分页,适合每一章结束后统一翻页。浏览器打印路线虽然控制力弱,但借助这类小技巧,能满足 80% 的日常交付需求。
5. 高频问题避坑清单:5 个让人头疼的场景
写到这里,前面那些场景里的坑也已经陆续提到了。这节把我在实际转换中反复遇到的 5 个高频问题集中整理成清单,每个都包含现象、原因和解决办法,方便你直接按图索骥。
5.1 中文乱码与方块字
现象:导出的 PDF 中所有中文变成“口口口”,英文字符正常。
原因分两类:一类是 LaTeX 引擎选错,比如用了 pdflatex,此时中文字形无法加载;另一类是字体变量没设置,系统里没有找到可用的中文字体。解决办法是固定使用--pdf-engine=xelatex,并显式指定-V CJKmainfont,比如填入“Noto Serif CJK SC”。如果你在用 VS Code 插件方案,确认系统内有可用的中文字体,并在自定义 CSS 里给body设置font-family,比如"Microsoft YaHei", "PingFang SC", sans-serif。
5.2 代码块换行与背景色丢失
现象:代码太长被挤出页面边界,或者代码底色在 PDF 里消失了。
代码块溢出通常是等宽字体宽度问题。针对 Pandoc,可以在 Markdown 中用反引号包裹代码,并在转换时设置较小的monofont字号,例如:
-V monofontsize=10pt针对浏览器打印方案,记得勾选“背景图形”,否则代码底色就没了。如果想精细控制,可以在自定义 CSS 里给pre加white-space: pre-wrap; word-break: break-all;,这会让超长代码自动换行,虽然对可读性略有影响,至少不会出现内容被裁掉的尴尬。
5.3 图片路径与相对位置
现象:PDF 里看不到图片,或者图片位置和 Markdown 预览里不一致。
图片问题要分两层看。第一层是路径问题:Pandoc 默认相对当前工作目录解析图片路径,如果你的图片散落在子目录里,可以用--resource-path=images指定资源根目录。第二层是位置问题:LaTeX 排版引擎会按照浮动体策略决定图片位置,这和 HTML 预览里的“文档流”不一样。想强制图片固定在当前位置,可以在 Markdown 里把图片包裹成:
<center><img src="images/demo.png" width="400"></center>或者使用 Pandoc 的-V float-placement=H。日常场景中,我优先用 HTML 标签控制图片位置,因为更直观,而且对后续修改更友好。
5.4 表格溢出页面边界
现象:表格列数较多时,右侧内容被切断,或者表格缩小后字小到看不清。
Markdown 表格转 PDF 是最考验工具链的地方。Pandoc 默认处理宽表的能力一般,对复杂表建议直接用 LaTeX 的longtable或tabularx环境。更稳妥的方案是所有表格都手写成 HTML 格式,然后在 Pandoc 转换时通过-H参数加载一个自定义 LaTeX 宏包文件,把longtable设置得更好看。考虑到多数人并不想深入了解 LaTeX,我的建议是:如果表格确实复杂,就不要执着于纯 Markdown 转换,而是把最终排版交给 HTML 打印方案,在打印预览里观察表格宽度,必要时用缩放兜底。
5.5 页边距与页眉页脚设置失效
现象:设置完页边距、页眉页脚,导出后毫无变化。
这个问题的根源通常是“优先级”。Pandoc 的默认模板会读取很多变量,如果你定义了geometry:margin=2.5cm,但又同时加载了另一个模板或别处有旧配置,旧配置可能会覆盖新变量。VS Code 插件方案里,页眉页脚还受displayHeaderFooter总开关控制,忘记开启就会出现“内容忘了放”的错觉。遇到这一类问题,我建议先用最小化配置测试,每加一个变量就重新导出验证,用二分法定位到底是哪一层配置冲突了。
6. 场景推荐与最终方案选择
讲了这么多工具和可调项,最后落到选择的层面。不同场景对 PDF 的“交付标准”不一样,选错了方案不是不能用,而是时间和效果上的性价比不够。
| 使用场景 | 推荐方案 | 选择理由 |
|---|---|---|
| 写 README、项目文档、内部接口说明 | VS Code + Markdown PDF 插件 | 写完就导,步骤少,样式通用 |
| 学位论文、行业报告、正式交付文档 | Pandoc + xelatex 自定模板 | 排版控制力强,可定制封面目录页眉 |
| 会议记录、临时打印、快速分享 | Typora 导出或浏览器打印 | 零成本,所见即所得,输出速度最快 |
| 含大量图表、公式、复杂布局的文档 | MPE 导出 / Pandoc + LaTeX | 对复杂元素支持完整,能保持排版一致性 |
| 非技术同事协助编辑的场景 | 导出 HTML 后交给同事打印 | 不依赖特定软件,任何浏览器都能渲染 |
这套选择逻辑并不复杂:工具服从场景,效果取决需求。给自己看的东西,不要为了“专业感”去安装重型的 LaTeX 环境;给客户交付的东西,也不要为了图省事在打印预览里草草了事。
最后分享一个我这些年实际用出来的习惯:文件命名和版本管理对这类转换任务同样重要。每次导出 PDF 时,我会同时保留对应的 Markdown 源文件和转换日志(用了哪个方案、哪些参数),下次遇到类似需求直接复制命令或配置,省去重新摸索的时间。比如我所在团队的文档仓库里,现在固定维护了一个build_pdf.sh脚本,里面就是一段 Pandoc 命令加上若干统一参数,所有人提交 PDF 都用同一套标准。
另一个可复用的技巧是把每次的配置沉淀成一个工作区级别的.vscode/settings.json,这样 VS Code 插件的页边距、页眉设置会跟着项目走,新成员拉下代码后导出的 PDF 风格完全一致,不用再逐个调整。整个过程下来你会发现,Markdown 转 PDF 真正考验的不是“工具会不会用”,而是你对交付结果的定义是否清晰,所有技术细节最终都是为这个定义服务的。