VSCode Markdown PDF 导出排版:CSS、分页与页眉页脚
2026/9/17 7:36:36 网站建设 项目流程

VSCode 的 Markdown PDF 插件导出效果设置,说穿了就是把编辑器里那份 .md 变成一份能直接发出去、能打印、能存档的成品文件。我最早盯上它是为了给客户出接口文档,当时想得很简单——装个插件、右键导出,完事。结果第一版 PDF 出来:中文字体是默认宋体糊成一片、代码块长行被硬生生切掉半截、页脚页码挤到正文上、表格跨页时表头消失。折腾了两三个下午,我才明白 Markdown PDF 的导出效果根本不是"装完就能用",它是一套需要逐项配的参数体系,而九成的人卡在同一个地方——不知道哪些效果由插件参数管,哪些必须靠自定义 CSS,哪些压根不受控。

这篇就把这套东西从头拆一遍:插件的渲染链路是怎么走的、页面骨架参数怎么配、CSS 怎么写才能覆盖到导出结果、代码高亮和图片这类重灾区怎么处理、批量导出怎么省事,以及我这几年踩过的那些坑和排查顺序。如果你只是偶尔导出一两篇随笔,看完前两节就够;如果你要把 PDF 当交付物,建议从头到尾过一遍,尤其是 CSS 那节,收益最大。

1. 导出效果的差异源头:Markdown PDF 插件到底做了什么

1.1 从 VSCode 预览到 PDF 的两套渲染管线

很多人第一次产生困惑,是因为预览和导出"看起来不是一个东西"。你在编辑器里按 Ctrl+Shift+V 看到的预览,是 VSCode 内置 Markdown 预览器渲染的,它吃的是markdown.styles那份 CSS;而 Markdown PDF 插件走的完全是另一条路——它内部把 Markdown 转成 HTML 之后,交给一个无头浏览器去"打印"成 PDF。插件不同版本用的内核不一样,早期版本用的是 PhantomJS,后来换成了 Puppeteer 那一套无头 Chromium。

这个差别决定了三件事。第一,CSS 支持程度不一样:无头 Chromium 支持分页相关的page-break-*break-insidedisplay: table-header-group这些打印专属属性,而编辑器预览里这些属性基本没意义,你在预览里永远调不出分页效果。第二,浏览器默认样式会参与进来,比如h1的默认page-break-before行为、prewhite-space: pre,这些都会影响导出结果。第三,插件会把 VSCode 的编辑器字体、主题完全抛开,一切从头开始,所以你在编辑器里看到的高亮配色和导出的高亮配色是两套独立配置。

理解这一点之后,很多"玄学问题"就不玄学了。比如"为什么我在 markdown.styles 里改了字体,PDF 没变"——因为那份 CSS 压根不在导出的链路上。

1.2 三个最容易被误判为"插件不好用"的问题

第一个是长代码行被截断。插件默认样式里pre保留了white-space: pre,无头浏览器打印时没有横向滚动条的概念,超出页面宽度的部分就直接被裁掉了。这不是 bug,是打印媒体本身的限制,只能靠 CSS 让代码块换行。

第二个是中文排版难看。插件自带的默认样式是按西文排版调的,line-height、段落间距、字重都是给英文准备的。中文行高不够会显得挤,字重不对会显得"发虚",尤其标题用系统默认的 bold 时,笔画容易糊。

第三个是页码、页眉页脚显示不出来。这通常不是没配,而是配了但没给足够的页边距——页眉页脚是画在页边距区域里的,上下边距太窄,它们要么被裁掉,要么直接压在正文第一行上。

1.3 配置文件写在哪:settings.json 的三种作用域

所有参数都写在settings.json里,但你要清楚自己写在哪一层。用户级(User)配置对全局所有项目生效,适合放字体、纸张这类通用偏好;工作区级(Workspace)配置只对当前项目生效,适合放相对路径的 CSS、输出目录这类项目相关的东西;文件夹级(Folder)在单文件夹工作区里和 Workspace 是一回事,多根工作区才会分开。

我自己的习惯是:字体栈、highlightStyle、scale 这类"我个人审美"的东西放用户级;markdown-pdf.styles里指向项目内 CSS 的路径、outputDirectory放工作区级,跟项目一起进版本库。这样换台机器拉下代码,导出效果和同事完全一致——这一点在做团队交付文档时特别重要,不然你导出的 PDF 和同事导出的能差出两个风格。

另外提醒一句,路径尽量用绝对路径,尤其是用户级配置里的 CSS。相对路径的解析基准在不同版本里表现不完全一致,有时候是工作区根,有时候是文件所在目录,懒得研究就写绝对路径,稳。

2. 页面骨架参数:纸张、边距、缩放与页眉页脚

2.1 pageSize 与 orientation 的选择依据

markdown-pdf.pageSize控制纸张尺寸,常见可选值有 A3、A4、A5、A6、Letter、Legal、Tabloid、Ledger 以及 A0 到 A6 这一系列。国内文档交付基本无脑 A4,markdown-pdf.orientation控制横竖,默认竖版(portrait),需要横版就写 landscape。

什么时候该用横版?两种情况:一种是表格列数特别多,竖版 A4 减去左右边距大概只有 17cm 可用宽度,五六列带说明文字的表格必然挤成一团;另一种是代码片段特别长,横版能少很多折行。但横版读起来累,我的做法是正文一律竖版,只给"附录"这类章节的表格单独考虑——不过插件没有按章节切换纸张的能力,所以实际操作里往往是拆成两个文件分别导出,再合并。

纸张尺寸其实也可以在 CSS 里通过@page规则干预,但和插件参数一起用时优先级容易打架,我建议统一由插件参数控制,CSS 里不要碰@page

2.2 margin 的单位陷阱与实用取值

markdown-pdf.margin.top / bottom / left / right这四个值必须是带单位的字符串,比如"1.5cm""20mm""0.8in""60px"。只写数字会直接导致导出报错或参数被忽略,这是新手最容易犯的错。

取值上有个换算关系需要心里有数:1 英寸约等于 2.54 厘米,CSS 里的 1px 在打印时按 96dpi 换算,约等于 0.026 厘米。所以"0.8in""2cm"大致相当,"60px"大概 1.6cm。

我的常用组合是这样:上下边距给"1.8cm",左右给"1.6cm"。上下留得多一点的原因是给页眉页脚腾地方,同时视觉上不压抑;左右不用太宽,因为 A4 本来就不宽,留太多正文会显得窄条。如果这份文档要双面打印装订,左边距加到"2.2cm"留装订位。

有个细节值得说:scale参数会缩放整个页面内容,但它和边距是叠加作用的。你把 scale 调到 0.9 觉得"内容变小了",其实是整个渲染视口按比例缩了,边距也跟着视觉变小。想要"字小一点但版心不变",应该改 CSS 里的font-size,而不是动 scale。

2.3 headerTemplate 与 footerTemplate 的变量与写法

页眉页脚靠markdown-pdf.headerTemplatemarkdown-pdf.footerTemplate两个字符串参数,内容是 HTML 片段。可用的内置 class 有五个:date(导出日期)、title(文档标题)、url(文件路径)、pageNumber(当前页码)、totalPages(总页数)。

一个我用了很久的配置长这样:

{ "markdown-pdf.displayHeaderFooter": true, "markdown-pdf.headerTemplate": "<div style=\"font-size:9px;color:#666;width:100%;margin:0 1.6cm;display:flex;justify-content:space-between;\"><span class='title'></span><span class='date'></span></div>", "markdown-pdf.footerTemplate": "<div style=\"font-size:9px;color:#666;width:100%;margin:0 1.6cm;display:flex;justify-content:space-between;\"><span class='url'></span><span><span class='pageNumber'></span> / <span class='totalPages'></span></span></div>" }

这里有几个实测出来的经验。第一,模板里的样式必须内联写,外部 CSS 影响不到页眉页脚区域,因为它是浏览器打印引擎单独渲染的一层。第二,字号别低于 8px,我用 9px 一直比较稳,太小的字号在某些环境下会被渲染成异常大小或者直接消失。第三,模板里的margin要和你配的页边距对上,不然页眉会贴着纸边或者跑到版心上方去。第四,displayHeaderFooter打开之后,上下边距至少要留 1.2cm 以上,我习惯给 1.8cm,留足呼吸空间。

title这个变量取的是文档里的第一个 H1 或者文件名,具体取哪个和版本有关,如果你对页眉内容有严格要求,干脆写成固定文本更省心。

2.4 printBackground 与 emoji 开关的两个细节

markdown-pdf.printBackground控制是否打印背景色和背景图。默认不开的话,你 CSS 里写的引用块底色、代码块灰底、表头浅灰全都会消失,导出的 PDF 一片惨白。做交付文档我一般把它打开。

但它有个副作用:底色块会在跨页处被切断,看起来像"半截色块"。这个没法完全避免,只能通过 CSS 的break-inside: avoid让整个块尽量不跨页。

markdown-pdf.emoji控制是否把:smile:这类短代码渲染成图标。如果你文档里根本没有这类写法,开着也无所谓;但如果文档里有正常的冒号加单词组合被误判,就关掉它。

3. 用一份自定义 CSS 掌控排版细节

3.1 CSS 怎么被加载:styles 与 includeDefaultStyles 的关系

插件通过markdown-pdf.styles加载自定义 CSS,这是一个数组,可以放多个文件路径,按顺序加载。另外还有一个markdown-pdf.includeDefaultStyles,默认是开的,意思是"先加载插件自带的默认样式,再加载你指定的样式"。CSS 的层叠规则决定了后面的覆盖前面的,所以你写的规则只要选择器权重够,就能盖掉默认样式。

这里有个关键决策:是把includeDefaultStyles关掉从零写,还是留着只做覆盖?我的建议是留着。原因很实际——默认样式里包含了不少基础规则(代码块背景、表格边框、引用块样式),从零写等于要自己重建一套完整的排版体系,工作量翻倍还容易漏。留着它,只针对你不满意的地方写覆盖规则,效率高得多。

还有个高频困惑:markdown.stylesmarkdown-pdf.styles的关系。编辑器预览读的是前者,导出读的是后者,两个是独立的。想让预览和导出一致,最省事的做法是把同一份 CSS 文件路径同时登记到这两个配置里,改一次两边都变。

3.2 中文字体栈、行高、段距的组合写法

中文排版的核心三件套是字体栈、行高、段间距。我用了两年的那份基础样式是这样:

body { font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Noto Sans CJK SC", sans-serif; font-size: 14px; line-height: 1.75; color: #24292e; } p { margin: 0 0 0.85em 0; text-align: justify; } h1, h2, h3, h4 { font-weight: 600; page-break-after: avoid; color: #111; } h2 { border-bottom: 1px solid #e1e4e8; padding-bottom: 0.35em; margin-top: 1.6em; }

字体栈的顺序讲究的是"从最想要到最保底"。Windows 上首选微软雅黑,macOS 上首选苹方,Linux 或者容器环境里前面两个都没有,就会落到思源黑体或 Noto Sans CJK。这个栈的好处是跨平台不会掉到"Times New Roman 里塞中文字符"这种灾难场景去。

行高 1.75 是我反复试出来的甜点值。1.5 太挤,中文笔画密度大,行间距不够会显得整页发闷;2.0 又太散,一页装不下多少内容,打印出来页数虚高。1.7 到 1.8 之间都算合理区间。

text-align: justify在两端的处理上能让中文段落左右对齐,视觉更整齐。但要注意它对纯英文段落可能导致单词间距被拉得很难看,如果你的文档中英混排严重,可以只给p用,别给li用。

3.3 表格、引用块、代码块的样式模板

表格是我见过最容易导崩的元素。默认样式下表格没有边框收敛,跨页时表头还会消失。这套是我现在的标准配置:

table { border-collapse: collapse; width: 100%; font-size: 12.5px; margin: 1em 0; } th, td { border: 1px solid #d0d7de; padding: 6px 10px; text-align: left; vertical-align: top; } thead { background: #f6f8fa; display: table-header-group; } tr { page-break-inside: avoid; }

display: table-header-group是解决"表格跨页表头丢失"的关键,它告诉浏览器把 thead 当成重复表头,每一页顶部都重画一次。tr { page-break-inside: avoid }保证单行不被拦腰截断。

代码块要解决的核心问题是长行截断:

pre { background: #f6f8fa; border-radius: 6px; padding: 12px 14px; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-all; overflow-x: visible; } code { font-family: Consolas, "Courier New", "Microsoft YaHei", monospace; }

white-space: pre-wrapword-break: break-all是组合拳:前者让长行换行而不是横向溢出,后者保证超长的连续字符串(比如一长串 URL、base64)也能断在任意位置,不至于顶破版心。overflow-x: visible是为了防止默认的overflow: auto在打印媒体下裁掉内容。

引用块相对简单,给左侧竖线和浅底就够了:

blockquote { margin: 1em 0; padding: 0.5em 1em; border-left: 4px solid #d0d7de; color: #57606a; background: #fafbfc; }

3.4 分页三件套:break-before、break-inside、orphans

分页控制有几种做法。第一种是插件自带的开关markdown-pdf.breaks,打开之后 Markdown 里的水平分割线(三个短横线单独一行)会被转成强制分页。这是最省事的章节分页方式,我在每章之间都加一条分割线。

第二种是 CSS 强制分页。给某个 class 加page-break-before: always(新一点的写法是break-before: page),配合 Markdown 里的行内 HTML 使用。比如你写<div class="page-break"></div>,然后在 CSS 里定义.page-break { page-break-before: always; },就能在任意位置插分页。注意行内 HTML 在 Markdown 里必须前后各留一个空行才能被正确识别。

第三种是避免分页。标题后面紧跟正文、图片单独成段、表格单行,这几处都需要page-break-after: avoidpage-break-inside: avoid来保护。我见过最常见的问题就是 H3 标题孤零零留在上一页末尾,正文全跑到下一页去了,加个page-break-after: avoid就能解决。

还有一对冷门但好用的属性是orphanswidows,分别控制页面底部和顶部最少保留几行文字。设成orphans: 3; widows: 3;能避免孤行,对中文长文档的阅读体验提升挺明显。这两个属性在老版本内核里可能不生效,但现在的版本用起来没问题。

4. 代码高亮、图片与图表这三块"重灾区"

4.1 highlight 与 highlightStyle 的搭配

代码高亮由两个参数控制:markdown-pdf.highlight打开高亮功能,markdown-pdf.highlightStyle指定配色主题,主题名对应的是常见高亮库的那套 CSS 文件名,比如github.cssatom-one-light.cssatom-one-dark.cssmonokai.cssvs2015.css等。

选择上有两条实用原则。第一,如果文档要打印,一定选浅色主题。深色主题在屏幕上好看,打印出来就是一大片黑,费墨不说,还容易糊得看不清字。我长期用github.css,就是因为它浅、对比度够、彩色饱和度不高,打印友好。第二,如果你同时在用自定义 CSS 覆盖pre的背景色,要注意主题 CSS 也会设置背景色,权重相同的情况下加载顺序决定胜负。稳妥的做法是在自己的 CSS 里显式写precode的背景色,用!important兜底也行——虽然不优雅,但在这种多层样式叠加的场景里确实省事。

还有一个细节:自定义 CSS 里给code设的字体栈只影响行内代码和代码块里的字体,不影响高亮分配的颜色,那部分完全由 highlightStyle 决定。所以字体和配色要分两处调,别在一处死磕。

4.2 图片路径、尺寸与体积控制

图片导出翻车基本集中在三个点:路径、宽度、体积。

路径方面,Markdown 里的相对路径是相对于 .md 文件本身的,插件渲染时会按源文件位置解析,正常情况下能正确加载。但如果你的图片在另一个盘符或者网络位置上,建议改成绝对路径,避免解析失败。还有一种情况是图片本身是 URL,导出时会实时下载,网络不通就会变成一个破裂图标,重要文档建议把图先落到本地。

宽度方面,默认样式没有限制图片最大宽度,一张 3000px 宽的截图在 A4 版心里会直接溢出,被裁掉右边一半。必须加这条:

img { max-width: 100%; height: auto; display: block; margin: 0.8em auto; }

体积方面,PDF 里嵌入的图片是按原分辨率走的。一份几十张截图的文档,导出后轻松上百兆。我的做法是导出前用图片压缩工具把截图统一压到宽度 1600px 以内、质量 80 左右,视觉上看不出差别,体积能降七成。另外 PNG 截图换成 JPEG 也能省不少,但如果截图里有细线条或者文字,JPEG 会有压缩噪点,这种情况还是保留 PNG。

4.3 Mermaid、流程图与公式的现实情况

这是很多人最关心也最容易失望的部分。Markdown PDF 插件本身对 Mermaid 代码块没有原生渲染能力——它把文本转成 HTML 之后就交给浏览器打印,而浏览器不认识 Mermaid 语法。所以你会看到导出的 PDF 里躺着一整段原始代码文本。

可行的绕过方式是预渲染:把 Mermaid 图先导出成 SVG 或 PNG,再以图片形式插进 Markdown。本地方案里,Mermaid 官方那套命令行工具、或者各种在线编辑器都能导出图片,SVG 插入 PDF 后清晰度最好,但要注意 SVG 里的字体是否被正确嵌入,否则可能显示成方框。稳妥起见导出 PNG,宽度给到 1600px 以上,打印时也够清晰。

至于数学公式,不同版本表现不完全一致。我的建议是先导出成 HTML 检查一遍——如果 HTML 里公式正常显示,转 PDF 一般也没问题;如果 HTML 里公式就是一堆原始符号,那就别指望 PDF 能好,改成图片插入更省时间。这个"先导 HTML 验证"的做法我后面还会提,它是调试导出效果最有效的手段,没有之一。

5. 批量转换、输出命名与自动化

5.1 type 的四个取值与适用场景

markdown-pdf.type支持 pdf、html、png、jpeg 四种输出。pdf 是主用途,不用多说。html 是我的调试利器:它生成一个自带样式的独立 HTML 文件,用浏览器打开就能看到导出前的真实渲染状态,CSS 有没有生效、表格边框对不对、代码块有没有溢出,一眼就能看出来,改完再导 PDF,比反复导 PDF 看结果快得多。

png 和 jpeg 是把每一页渲染成图片,一份多页文档会输出成多个带序号的文件。这个适合做预览图或者往聊天工具里贴的场景,但不适合正式交付,因为文字变成像素后没法搜索、没法复制、放大会糊。jpeg 比 png 体积小,但文字边缘会有轻微噪点。

5.2 outputDirectory 与文件名规则

markdown-pdf.outputDirectory指定输出目录,留空就是跟源文件放一起。配合markdown-pdf.outputDirectoryRelativePathFile使用:这个参数设为 true 时,输出目录相对于源文件所在目录解析;设为 false 时相对于工作区根目录解析。

我一般设成工作区根目录下的dist或者export目录,并且把这个目录加进忽略文件,免得导出的成品被提交进版本库。文件名默认跟随源文件名,插件没有提供文件名模板,所以想改命名规则只能导出后手动改,或者用脚本处理。

5.3 convertOnSave 与任务流的组合

markdown-pdf.convertOnSave打开后,保存 .md 文件时会自动导出。还有markdown-pdf.convertOnSaveExtensions用来指定触发导出的扩展名列表,默认覆盖 md、markdown、mdown、mkdn 等常见写法。

保存即导出听着美好,实际用起来要谨慎。一个大文档导出要几秒到十几秒,你每按一次 Ctrl+S 就卡一下,写东西的节奏全被打断。而且导出过程中频繁保存容易触发并发问题。我的用法是平时关掉,等文档定稿了再打开跑一轮,或者干脆不打开,用命令面板手动触发导出。

命令面板里搜"Markdown PDF"能看到几个命令,分别是导出 PDF、导出 HTML、导出 PNG、导出 JPEG,建成快捷键会比右键菜单快不少。

5.4 逆向需求:从 PDF 回到 Markdown 的可行路径

顺手说一下反方向的需求——有时候拿到的是别人给的 PDF,想转成 Markdown 再编辑。这事比正向难得多,因为 PDF 本质是"打印指令",不含语义结构。我的经验是分三类处理:文本层完整的 PDF,用命令行工具抽文字最快,但得到的是纯文本,标题层级、表格结构全丢,适合只需要内容不需要格式的场景;版式规整的文档,用带版面分析能力的工具还原效果会好一些,表格和标题能大致保住,但依然需要人工校对;扫描件或者图片型 PDF,必须先做文字识别,识别质量直接决定最终效果,表格和公式是重灾区,基本靠手动重建。

我的实际做法是:先用工具跑一遍拿到初稿,然后拿它和我导出的那份 PDF 对照着修,重点修表格、列表缩进和代码块。纯手工从头敲反而更快的情况也不少,取决于原文的复杂度。

6. 我在导出异常上踩过的坑与排查顺序

6.1 自定义 CSS 完全不生效

这是最高频的问题。排查路径按顺序走:先确认markdown-pdf.styles里的路径到底对不对,相对路径先换成绝对路径试一次;再确认文件本身有没有被保存,我确实干过"改了 CSS 但没保存就导出"这种蠢事;然后确认你的选择器权重够不够,插件默认样式里有些规则写得比较具体,比如body h1这种,你写个h1就盖不住,改成body h1或者提高优先级;最后用导出 HTML 的方式验证,HTML 里样式生效但 PDF 里不生效,才说明是打印媒体的特殊行为,比如分页属性在屏幕上本来就看不出来。

6.2 中文变方框、字重不对

中文显示成方框或者乱码,九成是字体问题。核心原因是你的字体栈里那些字体在这台机器上不存在,而且系统没有可用的中文回退字体。解决办法是把字体栈写全,末尾一定留一个通用兜底sans-serif,并且确认机器上确实装了至少一个中文字体。

字重不对是另一个表现:标题用 bold 之后笔画糊成一团,或者某些字看起来比周围粗一截。这是字体本身在不同字重下的设计差异,微软雅黑的粗体在低分辨率下确实容易糊。可以在 CSS 里给标题改成font-weight: 600,用半粗替代全粗,或者干脆换成思源黑体这类字重设计更完整的字体。

6.3 导出空白页、卡住与超时

空白页常见于强制分页之后紧接着又有一个强制分页,或者分页元素本身高度为 0 但同时带着分页属性,就会凭空多出一页。检查方法是在导出 HTML 里看元素结构,找连续的分页标记。

导出卡住或者报超时,通常和图片有关:某张图路径不对导致浏览器一直等,或者图片体积过大渲染缓慢。我的排查方式是先把所有图片注释掉导一次,能通就说明是图的问题,再逐张加回来定位。另一个可能是无头浏览器内核下载失败或者路径不对,这种情况下可以检查markdown-pdf.executablePath,手动指定本机已装浏览器的可执行文件路径,很多时候能直接解决问题。

6.4 页眉页脚被裁掉或压住正文

这个问题的成因非常单一:上下边距不够。页眉页脚画在页边距区域内,边距小于页眉自身高度时,要么被纸张边缘裁掉,要么向下侵入版心压住第一行正文。把上下边距加到 1.5cm 以上再试,一般立刻好转。如果加了还是不对,检查模板里的margin数值是不是超过了页边距宽度——模板内部也是有内外边距的,两边叠起来的总和不能超过你设置的页边距。

6.5 一套我常用的排查顺序

踩了这么多次之后我固定成了一套流程,每次遇到异常直接照着走,基本五分钟内能定位:

  1. 先导出 HTML,用浏览器打开,确认渲染层的问题还是打印层的问题。
  2. 打印层的问题优先查 CSS 里的分页属性、white-spaceoverflow这三类。
  3. 渲染层的问题优先查字体、图片路径、第三方语法(Mermaid、公式)。
  4. 参数类问题回settings.json逐个核对,重点看单位有没有漏、布尔值有没有写反。
  5. 环境类问题查无头浏览器可执行文件、图片网络依赖、磁盘权限。

最后分享一个我最近才用顺的小技巧:把常用配置分成两套。一套是"屏幕阅读版",字号大、行高宽、浅色主题,适合发给同事在电脑上看;另一套是"打印版",字号小一档、边距窄一点、去掉背景色,适合真要打印的场合。操作上就是准备两个settings.json片段,需要哪套切哪套,或者用多根工作区配不同配置。听起来有点麻烦,但比你每次导出前手动改十几个参数省事太多,尤其是文档要反复出好几版的时候,这个习惯省下的时间很可观。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询