
做 BIOS/UEFI 固件开发的同行应该都深有体会——我们天天跟各种芯片规格书打交道:Intel EDS/BS、ARM TRM、各种芯片datasheet,等等。这些文档大都是PDF格式,动辄上千页,包含大量寄存器表格和框图。PDF可读性虽好,但多文档搜索和链接都不方便,速度很慢。更严重的是,PDF 格式对大模型极不友好——文字层叠、换行断裂、表格散架,RAG 的 chunking 和 embedding 质量大打折扣。
最近很火的Obsidian,以它本地md格式存储,双向链接,知识图谱等功能,很受欢迎。我用 Obsidian 搭建了一个本地技术知识库,核心需求是把芯片规格书的内容导入其中,不仅可以自己方便检索和建立跨文档知识图谱,更可以在 Obsidian 中通过 RAG(检索增强生成)将相关段落喂给 Claude Code,来帮助大模型产生更准确的代码。这么做,有一个关键步骤,就是将PDF转换成md格式。
微软官方的开源MarkItDown: github.com/microsoft/markitdown
是 PDF 转 Markdown 的标杆工具,但在芯片文档场景下,它有三个硬伤:
第一,图片完全不提取。 一个 PDF 可能包含几百张框图和架构图,MarkItDown 输出里一张都没有——哪怕你加了 --keep-data-uris 也无济于事。因为 MarkItDown 底层用的是 pdfplumber,而 pdfplumber 的图片提取能力非常有限。
第二,水印混入正文。 很多保密文档中的水印文本会和正文交叠,在输出中产生大量乱码字符。大量的页眉页脚也会混入md文件中,这些都是无效文字。
第三,表格检测不稳定。 ARM TRM 的 Change History 表、指令编码表,Intel EDS 的寄存器位域表——这些都是无边框表格,MarkItDown 有时候检得出,有时候检不出,完全看运气。
我实际的需求,有几个基本诉求:
需求一:表格展现必须保持用户友好。我们生成的md文件,不仅仅要喂给大模型,也要自己读起来方便、准确,这样自己做多文档检索就会非常方便。
需求二:本地图片阅读体验。 芯片文档里有大量框图和架构图——CPU 微架构图、SoC 互联拓扑图、电源时序图。在 Obsidian 中阅读 Markdown 文档时,如果图片以相对路径引用并存放在同级目录,就能无缝预览——点击即放大,不需要切换到 PDF 阅读器。
需求三:版本 diff 与协作。 芯片规格书会持续更新(从 Rev 0.7 到 1.0 到 1.2),固件工程师需要对比不同版本的改动。Markdown 是纯文本,用 git diff 就能看清楚每个寄存器的位域定义是否变化——这在 PDF 上几乎不可能做到。
我用官方的微软官方的 MarkItDown 来转换md格式,效果非常差。有大量乱码,表格一塌糊涂,不是给人阅读的。于是就有了 PDF2MD:
https://github.com/MikeWuPing/pdf2md
PDF2MD 做了什么

PDF2MD 是一个三步流水线,每一步针对一个具体问题:

第一步:水印去除
这是最关键的创新点。很多保密文档的水印不是简单的文本叠加——它是 Form XObject,使用特定字体(AAAAAB+Helvetica-Bold)渲染,通过 Do 命令绘制到页面上。
我们用 pikepdf 深入 PDF 内部结构:递归扫描所有 XObject,找到引用水印字体的 Form XObject,追溯引用链找到页面级的根 XObject,然后在页面内容流中通过正则匹配移除对应的 Do 绘制命令。
关键细节:不同页面上,同一个水印的 XObject 名称可能是 /_0、/_1、/Jn_0、/Jn2200000000_0 等 24 种变体——尤其是带图片的页面,XObject 名完全不一样。所以不能硬编码名称,必须动态扫描引用链。
2
第二步:页眉页脚过滤
PDF 中重复出现的页头(文档标题、章节名)和页脚(页码、版权声明、机密等级)会严重干扰阅读体验。我们采用频率分析法:预扫描全部页面,分别统计顶部 12%、底部 12% 和中间区域的文本块签名(归一化后),跨页出现超过 50% 的即判定为页眉/页脚并过滤。
这个方法的优势在于自适应性——不需要预先知道页眉的具体内容,什么文档都能自动适配。
3
第三步:混合表格检测 + 图片提取
这是整个方案的"引擎",三个工具各司其职:

启发式规则会对 pdfplumber 漏掉的文本块做两遍扫描:
ARM 风格:每行是一个文本块,列间以 \n 分隔(如 "December 2002\nA\nNon-Confidential\nFirst Release for r0p0")。取列数众数对齐,溢出列以 <br> 合并——这样多行详情(如 "First release for r1p1.\nSystem control coprocessor...")就不会撑破表格。
Intel 风格:列间以 \s{2,} 分隔的多块聚类。要求 ≥3 行且每字段 <100 字符才判为表格,避免正文段落被误判。
图片方面,PyMuPDF 提取出的图片按页数+序号命名存入 <文件名>_images/ 子目录,只有 >3KB 的图片(框图、架构图)才会在 Markdown 中生成引用链接,过滤掉小图标和装饰元素。
实用特性

除了三步核心流水线,还有两个在工程实践中特别实用的设计:
批量处理 + 目录结构镜像。 实际工作中很少只需要转换一个 PDF——通常是一个目录下按厂商/芯片族/文档类型组织的十几个甚至几十个 PDF(反正我是这么规范我的spec目录的)。PDF2MD 支持递归扫描 input/ 下的所有 PDF,在 output_hybrid/ 下重建完全一致的目录结构,每个 .pdf 对应一个 .md。
每个 .md 旁边还有对应的 *_images/ 目录,图片路径使用相对引用,所以整个 output_hybrid/ 目录可以原封不动搬进 Obsidian vault。
OpenClaw / Claude Code / Cursor 等 AI 工具友好。 Markdown 是这些工具的原生格式——不像 PDF 需要插件或外部解析器才能读取。把芯片规格书转成 Markdown 后,可以直接放在项目目录下,让 Claude Code 或 Cursor 在写驱动代码时引用寄存器定义、时序参数、接口协议。PDF2MD 本身也是作为 Claude Code 的 skill 开发的,在 .claude/skills/pdf2md/ 下有完整的 skill 定义,可以直接用 /pdf2md 命令触发转换。它本身也可以用在OpenClaw里面。
实际效果

我用 12 个 PDF(共计 3983 页)做了完整测试,涵盖 Intel CNL/PCH/PTL 的 EDS、BIOS Spec、FAS、Platform Deck 以及 ARM1136 TRM:

局限与展望

这个方案也有它的局限性,需要说明一下:
字符级交织的水印:一种特别难处理的情况——水印文字和正文在 PDF 渲染时被逐字符交叠在一起(如斜向贯穿全页的 overlay),这种在 PDF 源码层面就分不开了。好在大部分水印不是这种方式。后续计划引入 OCR 辅助层来识别并覆盖这类顽固水印。
跨页表格:大型寄存器表跨越页面时,续页会重复表头。目前无法自动合并——这是个更难的问题,需要跨页文本匹配。计划通过相邻页面的表格结构比对(列头一致 + 内容连续)来实现自动拼接。
加密 PDF:部分PDF有加密,pikepdf 无法处理。实际测试中这类文件水印本身也不严重,直接跳过不影响最终效果。后续可考虑集成 qpdf 等解密工具做前置处理。
矢量图:方框图、流水线图是矢量绘制而非嵌入位图,无法提取为图片。这是 PDF 本身的限制,我也没发现任何工具可以做到这点。不过可以考虑以后用 PyMuPDF 将矢量区域渲染为高分辨率 PNG 来间接解决。
另外,水印去除模块(remove_watermark.py)重复利用了我以前做的一个Skill开源一个PDF去水印的Skill,适用于Claude Code和OpenClaw:
https://github.com/MikeWuPing/pdf-watermark-removal
它不依赖 pdfplumber 和 PyMuPDF,只用了 pikepdf,安装更轻量、执行更快,适合集成到自动化流水线中做批量水印清理。如果仅仅去水印,可以直接用它
开源

项目已开源在 GitHub,MIT 协议:
https://github.com/MikeWuPing/pdf2md
在Claude Code里面使用非常简单:
批量将xx目录下的PDF文件转换成md文件
应该就会触发这个Skill,当然,显式的告诉CC用这个Skill会更加保险。
参考资料



夜雨聆风