模拟解析:宽度数组 `[1,2,1]`,10个条目的 XRef 流

张开发
2026/4/8 14:43:09 15 分钟阅读

分享文章

模拟解析:宽度数组 `[1,2,1]`,10个条目的 XRef 流
文章目录模拟解析宽度数组 [1,2,1]10个条目的 XRef 流一、设定场景二、解析代码核心部分与之前相同三、逐条解析模拟 CPU 执行辅助函数 GetVarInt 回顾条目0 (j0, objnum0)条目1 (j1, objnum1)条目2 (j2, objnum2)条目3 (j3, objnum3)条目4 (j4, objnum4)条目5 (j5, objnum5)条目6 (j6, objnum6)条目7 (j7, objnum7)条目8 (j8, objnum8)条目9 (j9, objnum9)四、最终 m_ObjectInfo 映射表五、代码中指针偏移计算的通用公式六、扩展到其他宽度数组的示例七、总结模拟解析宽度数组[1,2,1]10个条目的 XRef 流一、设定场景宽度数组/W [1 2 1]类型字段宽度 1 字节字段2宽度 2 字节适合偏移量 65536 的小文件生成号宽度 1 字节每个条目总字节数 1 2 1 4 字节Index 数组/Index [0 10]对象 0~9 都在流中流数据十六进制共 10 个条目 × 4 字节 40 字节条目0: 01 00 10 00 → 类型1, 偏移0x001016, 生成0 条目1: 01 00 20 00 → 类型1, 偏移32, 生成0 条目2: 02 00 01 00 → 类型2, 对象流编号1, 生成0 条目3: 01 00 30 01 → 类型1, 偏移48, 生成1 条目4: 00 00 00 00 → 类型0 (空闲) 条目5: 01 00 40 00 → 类型1, 偏移64, 生成0 条目6: 02 00 02 00 → 类型2, 对象流编号2, 生成0 条目7: 01 00 50 00 → 类型1, 偏移80, 生成0 条目8: 01 00 60 00 → 类型1, 偏移96, 生成0 条目9: 00 00 00 00 → 类型0 (空闲)流数据内存视图地址 0~39地址: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 数据: 01 00 10 00 01 00 20 00 02 00 01 00 01 00 30 01 00 00 00 00 地址: 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 数据: 01 00 40 00 02 00 02 00 01 00 50 00 01 00 60 00 00 00 00 00二、解析代码核心部分与之前相同std::vectoruint32_tWidthArray{1,2,1};uint32_ttotalWidth4;// 121constuint8_t*pData/* 指向流数据起始地址0 */;uint32_tsegindex0;intstartnum0;uint32_tcount10;constuint8_t*segstartpDatasegindex*totalWidth;// segstart pDatafor(uint32_tj0;jcount;j){constuint8_t*entrystartsegstartj*totalWidth;// 每个条目的起始地址// 读取类型int32_ttype1;if(WidthArray[0]0)// 1 0typeGetVarInt(entrystart,WidthArray[0]);// 读取字段2FX_FILESIZE field20;if(WidthArray[1]0)// 2 0field2GetVarInt(entrystartWidthArray[0],WidthArray[1]);// 读取生成号int32_tgen0;if(WidthArray[2]0)// 1 0genGetVarInt(entrystartWidthArray[0]WidthArray[1],WidthArray[2]);uint32_tobjnumstartnumj;// 存储if(type0){m_ObjectInfo[objnum].pos0;m_ObjectInfo[objnum].type0;}else{m_ObjectInfo[objnum].posfield2;m_ObjectInfo[objnum].gennumgen;if(type1){m_ObjectInfo[objnum].type1;m_SortedOffset.insert(field2);}elseif(type2){m_ObjectInfo[objnum].type2;m_ObjectInfo[field2].type255;// 标记对象流}}}三、逐条解析模拟 CPU 执行辅助函数GetVarInt回顾uint32_tGetVarInt(constuint8_t*p,intn){uint32_tr0;for(inti0;in;i)rr*256p[i];returnr;}条目0 (j0, objnum0)entrystart pData 0*4 地址0读类型GetVarInt(地址0, 1)→ 地址00x01 →type1读字段2GetVarInt(地址01, 2)→ 地址10x00, 地址20x10 → (0*2560)*2560x10 0x0010 16读生成号GetVarInt(地址012, 1)→ 地址30x00 →gen0type1 →m_ObjectInfo[0] {pos16, gennum0, type1}m_SortedOffset插入 16条目1 (j1, objnum1)entrystart 地址4读类型地址40x01 → type1读字段2地址50x00, 地址60x20 → 0x0020 32读生成号地址70x00 → gen0m_ObjectInfo[1] {pos32, gennum0, type1}m_SortedOffset插入 32条目2 (j2, objnum2)entrystart 地址8读类型地址80x02 → type2读字段2地址90x00, 地址100x01 →1读生成号地址110x00 → gen0type2 →m_ObjectInfo[2] {pos1, gennum0, type2}且m_ObjectInfo[1].type 255注意对象流编号为1即对象1被标记为对象流容器重要这里field21表示当前对象对象2是一个压缩对象存储在对象流 #1 中。因此我们需要把对象流 #1 的类型标记为 255以便将来从该流中解压对象。条目3 (j3, objnum3)entrystart 地址12读类型地址120x01 → type1读字段2地址130x00, 地址140x30 → 0x0030 48读生成号地址150x01 → gen1m_ObjectInfo[3] {pos48, gennum1, type1}m_SortedOffset插入 48条目4 (j4, objnum4)entrystart 地址16读类型地址160x00 → type0读字段2地址170x00, 地址180x00 → 0读生成号地址190x00 → 0type0 →m_ObjectInfo[4] {pos0, type0}条目5 (j5, objnum5)entrystart 地址20读类型地址200x01 → type1读字段2地址210x00, 地址220x40 → 0x0040 64读生成号地址230x00 → gen0m_ObjectInfo[5] {pos64, gennum0, type1}m_SortedOffset插入 64条目6 (j6, objnum6)entrystart 地址24读类型地址240x02 → type2读字段2地址250x00, 地址260x02 →2读生成号地址270x00 → gen0m_ObjectInfo[6] {pos2, gennum0, type2}且m_ObjectInfo[2].type 255对象流 #2 被标记条目7 (j7, objnum7)entrystart 地址28读类型地址280x01 → type1读字段2地址290x00, 地址300x50 → 0x0050 80读生成号地址310x00 → gen0m_ObjectInfo[7] {pos80, gennum0, type1}m_SortedOffset插入 80条目8 (j8, objnum8)entrystart 地址32读类型地址320x01 → type1读字段2地址330x00, 地址340x60 → 0x0060 96读生成号地址350x00 → gen0m_ObjectInfo[8] {pos96, gennum0, type1}m_SortedOffset插入 96条目9 (j9, objnum9)entrystart 地址36读类型地址360x00 → type0读字段2地址370x00, 地址380x00 → 0读生成号地址390x00 → 0m_ObjectInfo[9] {pos0, type0}四、最终m_ObjectInfo映射表对象号posgennumtype含义01601未压缩对象位于文件偏移1613201未压缩对象偏移322102压缩对象存储在对象流#1中34811未压缩对象偏移48生成号14000空闲对象56401未压缩对象偏移646202压缩对象存储在对象流#2中78001未压缩对象偏移8089601未压缩对象偏移969000空闲对象另外对象流容器标记m_ObjectInfo[1].type被设置为 255因为条目2中 field21m_ObjectInfo[2].type被设置为 255因为条目6中 field22注意对象1原本是未压缩对象type1现在又被标记为255这在真实PDF中不会发生。这里只是展示解析过程实际中对象1和对象2作为对象流它们自己的XRef条目应该是类型1指向文件中的流对象而不是同时作为压缩对象。为了避免混淆我们应该把对象流编号设为一个不与其他普通对象冲突的值比如100和101。但为了保持示例简单请理解这是演示目的。五、代码中指针偏移计算的通用公式对于任意条目j条目起始地址 pData j * totalWidth类型字段起始 条目起始地址字段2起始 条目起始地址 WidthArray[0]生成号起始 条目起始地址 WidthArray[0] WidthArray[1]无论宽度数组是什么这个公式都适用。因为totalWidth是预计算的总宽度而每个字段的偏移是通过累加前面字段的宽度得到的。为什么这样设计如果硬编码偏移如类型在0字段2在1生成号在5那么当宽度变化时代码就失效了。通过动态累加代码可以适应任何/W组合实现完全通用。六、扩展到其他宽度数组的示例假设/W [0 4 0]只有4字节偏移类型默认1生成号默认0WidthArray[0]0, WidthArray[1]4, WidthArray[2]0totalWidth 4解析时type保持默认1field2 GetVarInt(entrystart 0, 4)读4字节gen保持默认0这样每个条目只存4字节偏移非常紧凑。假设/W [1 8 2]大文件totalWidth 11解析时type读1字节field2读8字节可表示最大 2^64-1 偏移gen读2字节支持超大文件64位偏移和更大生成号。七、总结通过这个包含10个条目的完整示例我们演示了动态读取宽度数组→ 确定每个字段的字节数计算 totalWidth→ 实现 O(1) 跳转到任意条目使用GetVarInt读取大端整数→ 跨平台、任意宽度根据类型分支处理→ 区分未压缩对象、压缩对象、空闲对象标记对象流容器→ 为后续解压做准备整个解析过程完全由/W数组驱动不需要修改代码就能处理任何宽度组合。这正是 PDFium 设计的通用性和健壮性所在。

更多文章