我们开源了 PDF 转 Markdown 引擎,连它还做不好的地方也一起公开了
@mindmax/pdf-markdown 是一个开源的 PDF 转 Markdown 库,Apache-2.0 协议,支持 Node 18+ 和浏览器。 它没有任何运行时依赖,不发任何网络请求,并在 22 份公开文档(共 612 页)上跑回归,120 条断言都由人对照 PDF 逐条核过。它还附带一份「目前仍然做错的案例」清单,我们希望你来一起把它们修掉。
npm i @mindmax/pdf-markdown
PDF 转 Markdown 页面背后跑的、MindMax 各端里用的,就是同一个包, 不是某个私有版本的演示缩水版。
PDF 转 Markdown 为什么总出错
PDF 里没有「段落」,只有放在坐标上的字形。读者看到的一切结构——分栏、标题、表格、阅读顺序—— 都得由转换器反过来猜。大多数糟糕的输出,都是同样几类猜测猜错了。
- 分栏被横着读。 双栏、三栏的论文,输出成左栏半句接右栏半句。
- 页眉页脚混进正文。 每一页,页眉、页脚、页码都插在段落中间。
- 该是表格的不是,不是表格的反成了表格。 真表格塌成一行行空格;行间公式或窄栏却被当成表格。
- 标题靠字号猜。 一个大号符号变成
#标题;折成两行的标题变成两个标题。 - 你的原文被二次解释。 PDF 里的
$、*、_,或者行首的#,转出来的 Markdown 渲染后变了样。
如果你把 PDF 喂给大模型或检索系统,这些就不是排版问题:被切在两栏之间的一句话,没人能检索到; 一个多余的标题,会把一节切成两节。
它怎么处理
引擎拿 pdf.js 已经给出的文字层,从中重建结构。
| 问题 | 引擎的做法 |
|---|---|
| 分栏 | 检测栏缝(含三栏页),按栏阅读;整宽的块把页面切成若干带。 |
| 页眉、页脚、页码 | 有 /Artifact 标记的 PDF 优先用标记;没标记的靠跨页的几何重复识别。 |
| 段落 | 按基线把字形合成行,再按行距和缩进合成段;跨行断开的连字符单词重新接上。 |
| 标题 | 按相对「本页正文」的字号、编号和间距判断;折行的标题合回一个。 |
| 表格 | 按几何画出表格,并拒绝把公式、窄栏当成表格。 |
| 超链接 | 只在 PDF 里真有链接注释时恢复,不凭空捏造。 |
| 特殊字符 | 只转义来自 PDF 的字符,不动引擎自己写的 Markdown 语法,所以你的 $5 还是 $5。 |
另有两个设计选择,比看上去重要。
天生是本地的。 这个包不发任何网络请求;在浏览器里,文件不会离开当前标签页。所以你可以放心让它处理 合同、未发表的论文和内部报告。
输出是可定位的。 库同时导出 buildPdfTextMap,记录每一页、每个文字块落在最终字符串的哪个位置。
阅读器在 PDF 里划一句话,能按偏移量原样取回同一句,而不是去搜文本再祈祷它只出现一次。
如果你在做引文、高亮或带出处的 RAG,这一层事后很难补。
数字:gzip 后 25.1 KB,约 4,100 行 TypeScript,170 条测试,语句覆盖率 97.3%。
它拒绝做什么
与其让你自己撞上边界,不如我们先说清楚。
- 不做 OCR。 扫描页没有文字层。库会报告这一页是扫描件或空页,不会悄悄去调模型。
- 不还原公式。 PDF 存的是定位好的字形,不是公式,
x²到手是x2。库会检测出现这种情况的页并报告页数。 - 不搬图片。 图留在 PDF 里。
- 不上传。 任何内容都不离开本机。
公开测试,缺陷也公开
22 份语料里,16 份是 CC BY,5 份是美国联邦政府作品,1 份是中国政府官方文件,都可以再分发,所以每份文档 转出的 Markdown 原样放在仓库里,任何人都能整套复跑。每份文档有各自的断言清单,120 条全部通过。
同时有 10 份文档里共 17 项已知失败检查。它们被标成「预期失败」,所以 CI 保持绿色,缺陷却一直可见。 完整结果表和缺陷清单由测试运行直接生成,页面上没有一个手写数字。
最可能影响你的几条:
- 两端对齐或拉开字距的中文,字与字之间可能被插进多余的空格。
- 字体缺少 Unicode 映射时,数字会变成
�、全角空格会变成!,而体检并不总能把这一页标出来。 - 杂志式排版:侧栏文字可能被读进正文或列表。
- 表格旁边的正文栏可能被并进表格,表头行也可能和相邻段落合并。
- 标题层级可能倒挂:封面占掉了 1–5 级,编号小节也不一定被认成标题。
以上每一条,在仓库里都对应一份带失败断言的文档。这也正是我们全部的贡献方式。
一起把它做好
如果某份 PDF 在你这里转得不好,你能给我们的最有用的东西是:那份文档(或一份公开的类似文档),加一句话说明 正确的结果应该是什么样。 它会成为一个带失败断言的语料用例:测试变红,代码改到变绿,用例永远留在 仓库里。附带 PDF 的反馈,远比一大段文字描述更有价值。
- 反馈转换效果差:提交语料用例
- **动手修一个:**读 CONTRIBUTING,从上面五条里挑一条,发 Pull Request。目标是把语料扩到 40 份。
- 提问或提议:Discussions
- 先试试看:浏览器里的 playground,不用安装。
我们的承诺
下面几条都写在仓库里,以仓库原文为准,不是我们在这里另说一套:
- 安全问题报告,72 小时内首次响应(SECURITY)。
- 贡献者会被提拔。 合并 3 个 PR 或被采纳的语料用例,就可以申请 triager 权限(GOVERNANCE)。
- 项目不依赖我们。 所有者失联 90 天,维护者可以 fork 继续。
- MindMax 自己依赖同一个包。 App 的修复,会先在这里发版。
有两点要如实说明:仓库还很年轻;发布凭证目前由一位所有者持有,90 天规则就是为这种情况准备的。
接下来
这个引擎是第一块,不是最后一块。支撑我们 Markdown 转 Word、LaTeX 转 Word、 ChatGPT 转 Word 的 Word 导出引擎,是下一个开放候选。开放之前要做完三件事:断开对内部 core 的依赖、 自带测试和往返样例、定下我们愿意长期保持的公开 API。现在先宣布,不给日期。已经开放了什么、边界在哪里,见 开源页。
常见问题
@mindmax/pdf-markdown 可以商用吗?
可以,Apache-2.0。MindMax 应用本身是闭源的,引擎不是。
能处理扫描件吗?
不能,而且它会明说。没有文字层的页会被报告为扫描件或空页,不会送去 OCR 模型。MindMax 桌面端另有一个 由用户手动发起的 OCR 步骤。
会把我的 PDF 发到别处吗?
不会。这个包不发任何网络请求,在浏览器里文件只留在标签页内。
Node 和浏览器都能跑吗?
能,Node 18 及以上,以及浏览器。你传入已打开的 pdf.js 文档,所以它不会再打包第二份 pdf.js。
能用在 RAG 或大模型流程里吗?
这正是主要用途之一:分栏按顺序读、页眉页脚已去掉,buildPdfTextMap 还能给你回到原页的偏移量,用于引文。
怎么反馈某份转得不好的 PDF?
提交一个语料用例, 附上文档(或公开的等价文档),再用一句话描述正确的输出。
比起读这篇文章,你可能更想直接读代码,入口在仓库。 如果清单里的某个缺陷你也被它折磨过,我们很乐意和你一起修掉它。