C# WinForms自定义PDF Writer类输出异常:仅显示首行排查
我为Windows Forms C#应用编写了一个简易PDF Writer类,应用可成功创建并写入文件,但在PDF阅读器中仅显示首行内容(Hello world!),后续两行内容无法显示。生成的PDF文件内容如下:
%PDF-1.7 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Resources << >> /Contents 4 0 R /MediaBox [0 0 612 792] >> endobj 4 0 obj << /Length 300 >> stream BT /Arial 12 Tf 50 700 Td (Hello world!) Tj 50 680 Td (01/05/2024 08:50:34) Tj 50 660 Td (29 April 2024 - 01 May 2024) Tj ET endstream endobj << /Type /Metadata /Subtype /XML /Length 100 >> stream <?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/"> </x:xmpmeta> <?xpacket end="w"?> endstream endobj << /MarkInfo << /Marked true >> >> endobj << /Type /StructTreeRoot >> endobj xref 0 5 0000000000 65535 f 0000000009 00000 n 0000000055 00000 n 0000000108 00000 n 0000000197 00000 n trailer << /Root 1 0 R /Metadata 5 0 R /MarkInfo 6 0 R /StructTreeRoot 7 0 R /Size 5 >> startxref 254 %%EOF
问题根源分析
问题出在PDF文件的结构错误,和阅读器无关,主要有三点:
1. 交叉引用表(xref)缺失对象条目
你的PDF中定义了5、6、7号对象(Metadata、MarkInfo、StructTreeRoot),但xref表只声明了0 5(即仅包含0-4号对象的引用),且trailer中的/Size值设为5,这导致阅读器无法找到后续三个对象的位置,解析到此处会出现结构错误,中断内容渲染。
2. Stream长度值不匹配
PDF要求每个stream的/Length必须准确对应stream内容的字节数(不包含stream和endstream标记本身)。你设置的对象4的/Length 300、Metadata对象的/Length 100都和实际内容长度不符,阅读器会错误截断或读取无效内容,导致后续文本无法解析。
3. 字体资源未定义
在3号Page对象的/Resources中是空字典,但内容流里使用了/Arial 12 Tf指定字体。PDF规范要求必须在页面资源中定义所使用的字体,否则阅读器无法识别字体信息,即便部分阅读器会尝试 fallback 到默认字体,也可能导致后续文本渲染失败。
修复方案
修正交叉引用表:
- 将xref表的开头改为
0 8(因为对象编号到7,加上0号共8个条目),并添加5、6、7号对象的偏移量。 - 将trailer中的
/Size值改为8。
- 将xref表的开头改为
准确计算Stream长度:
- 计算对象4中
BT到ET之间的字节数,替换/Length 300为实际数值。 - 计算Metadata stream中XML内容的字节数,替换
/Length 100为实际数值。
- 计算对象4中
添加字体资源定义:
最简单的方式是改用PDF内置字体(比如Helvetica),避免自定义字体的复杂定义,修改Page对象的/Resources:/Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>同时将内容流中的
/Arial 12 Tf改为/F1 12 Tf。
内容的提问来源于stack exchange,提问作者Paul Alexander

