使用pdf-lib与pdf-dist解析PDF遇文本提取错误及分页偏移问题
PDF解析问题与解决思路
问题描述
- 分页定位偏差:FireFox、Edge等商业PDF阅读器中,某表格两行内容显示在第23页底部,但使用pdf-lib读取时,这两行被定位到第24页顶部;
- 文本内容错误:商业阅读器中某表格单元格显示文本为“5,1”,但使用pdfjs读取对应区域时得到的是“20,1”。
解析流程
- 解析PDF的绘制动作,获取水平和垂直线条;
- 将线条组合成表格,划分页面为逻辑单元格;
- 读取并组合单元格内的文本元素,生成单元格字符串。
该逻辑已验证基本正确,第一个问题因不影响数据组装可接受,但第二个问题无法容忍。
相关代码
加载PDF
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs'; const ... export default async function loadPDF() { const data = new Uint8Array(fs.readFileSync(pdfPath)); const pdf = await pdfjsLib.getDocument({ data, cMapUrl: mapPath, cMapPacked: true }).promise; return pdf; }
获取文本元素
export async function getTextElements(page, debug = false) { let { items } = await page.getTextContent(); let textElements = items .filter(t => !isWatermark(t)) // 通过transform判断水印 .map(({ str, width, height, transform }) => ({ str, width, height, left: transform[4], right: transform[4] + width, bottom: transform[5], top: transform[5] + height })); if (debug) { fs.writeFileSync("debug.json",JSON.stringify(items)); } return removePageNumer(textElements); // 通过位置移除页码 }
排查情况
添加调试代码导出原始文本到debug.json后,发现对应区域的原始内容就是“20,1”,怀疑getTextContent返回了错误的元素。
解决思路
针对文本内容错误的排查方向
检查PDF文本层级覆盖逻辑
PDF通过绘制顺序控制显示优先级,后绘制的内容会覆盖先绘制的内容。如果“5,1”是后绘制的遮罩层覆盖在“20,1”之上,getTextContent会返回所有文本(包括被覆盖的),但阅读器只显示上层内容:- 遍历
getTextContent返回的items,查看元素顺序(对应PDF绘制顺序),判断是否存在后绘制的短文本覆盖长文本的情况; - 检查文本元素的
opacity属性,确认是否有透明或隐藏的文本。
- 遍历
尝试PDF.js的底层文本提取方式
- 使用
page.getOperatorList()获取原始绘制指令,手动解析文本操作(如Tj、TJ指令),直接获取所有原始文本及绘制参数:const opList = await page.getOperatorList(); const textItems = []; let currentText = ''; for (const op of opList.ops) { if (op.fn === pdfjsLib.OPS.showText) { currentText += pdfjsLib.util.decodeText(op.args[0]); } else if (op.fn === pdfjsLib.OPS.showTextWithSpace) { currentText += ' ' + pdfjsLib.util.decodeText(op.args[0]); } else if (op.fn === pdfjsLib.OPS.beginText) { currentText = ''; } else if (op.fn === pdfjsLib.OPS.endText) { if (currentText) textItems.push(currentText); } } - 在
getDocument时添加disableCombineTextItems: true配置,禁止合并相邻文本元素,获取更细粒度的文本片段:const pdf = await pdfjsLib.getDocument({ data, cMapUrl: mapPath, cMapPacked: true, disableCombineTextItems: true }).promise;
- 使用
检查字体与编码映射
即使配置了cMap,特殊字体或自定义编码仍可能导致解析错误:- 在
getTextContent时添加includeMarkedContent: true选项,查看标记内容是否影响文本解析; - 检查对应文本元素的
fontName,确认是否存在字体映射错误导致字符解析偏差。
- 在
针对分页偏差的补充说明
该问题多因PDF的“可见页面边界”与“逻辑页面边界”不一致导致,部分PDF会将超出可见区域的内容放在下一页逻辑区域,但阅读器会自动调整显示。若不影响数据组装,可通过调整单元格区域的分页判断逻辑(如允许跨小范围边界的文本归为同一页)优化。
内容的提问来源于stack exchange,提问作者Willi
相关产品推荐
相关产品推荐

