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

文章目录

模拟解析:宽度数组 [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, 偏移=0x0010=16, 生成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

二、解析代码核心部分(与之前相同)

cpp 复制代码
std::vector<uint32_t> WidthArray = {1, 2, 1};
uint32_t totalWidth = 4;  // 1+2+1

const uint8_t* pData = /* 指向流数据起始地址0 */;
uint32_t segindex = 0;
int startnum = 0;
uint32_t count = 10;

const uint8_t* segstart = pData + segindex * totalWidth; // segstart = pData

for (uint32_t j = 0; j < count; ++j) {
    const uint8_t* entrystart = segstart + j * totalWidth; // 每个条目的起始地址
    
    // 读取类型
    int32_t type = 1;
    if (WidthArray[0] > 0)   // 1 > 0
        type = GetVarInt(entrystart, WidthArray[0]);
    
    // 读取字段2
    FX_FILESIZE field2 = 0;
    if (WidthArray[1] > 0)   // 2 > 0
        field2 = GetVarInt(entrystart + WidthArray[0], WidthArray[1]);
    
    // 读取生成号
    int32_t gen = 0;
    if (WidthArray[2] > 0)   // 1 > 0
        gen = GetVarInt(entrystart + WidthArray[0] + WidthArray[1], WidthArray[2]);
    
    uint32_t objnum = startnum + j;
    
    // 存储
    if (type == 0) {
        m_ObjectInfo[objnum].pos = 0;
        m_ObjectInfo[objnum].type = 0;
    } else {
        m_ObjectInfo[objnum].pos = field2;
        m_ObjectInfo[objnum].gennum = gen;
        if (type == 1) {
            m_ObjectInfo[objnum].type = 1;
            m_SortedOffset.insert(field2);
        } else if (type == 2) {
            m_ObjectInfo[objnum].type = 2;
            m_ObjectInfo[field2].type = 255;   // 标记对象流
        }
    }
}

三、逐条解析(模拟 CPU 执行)

辅助函数 GetVarInt 回顾
cpp 复制代码
uint32_t GetVarInt(const uint8_t* p, int n) {
    uint32_t r = 0;
    for (int i = 0; i < n; ++i) r = r * 256 + p[i];
    return r;
}

条目0 (j=0, objnum=0)
  • entrystart = pData + 0*4 = 地址0
  • 读类型:GetVarInt(地址0, 1) → 地址0=0x01 → type=1
  • 读字段2:GetVarInt(地址0+1, 2) → 地址1=0x00, 地址2=0x10 → (0*256+0)*256+0x10 = 0x0010 = 16
  • 读生成号:GetVarInt(地址0+1+2, 1) → 地址3=0x00 → gen=0
  • type=1 → m_ObjectInfo[0] = {pos=16, gennum=0, type=1}m_SortedOffset 插入 16

条目1 (j=1, objnum=1)
  • entrystart = 地址4
  • 读类型:地址4=0x01 → type=1
  • 读字段2:地址5=0x00, 地址6=0x20 → 0x0020 = 32
  • 读生成号:地址7=0x00 → gen=0
  • m_ObjectInfo[1] = {pos=32, gennum=0, type=1}m_SortedOffset 插入 32

条目2 (j=2, objnum=2)
  • entrystart = 地址8
  • 读类型:地址8=0x02 → type=2
  • 读字段2:地址9=0x00, 地址10=0x01 → 1
  • 读生成号:地址11=0x00 → gen=0
  • type=2 → m_ObjectInfo[2] = {pos=1, gennum=0, type=2},且 m_ObjectInfo[1].type = 255(注意:对象流编号为1,即对象1被标记为对象流容器)

重要 :这里 field2=1 表示当前对象(对象2)是一个压缩对象,存储在对象流 #1 中。因此我们需要把对象流 #1 的类型标记为 255,以便将来从该流中解压对象。


条目3 (j=3, objnum=3)
  • entrystart = 地址12
  • 读类型:地址12=0x01 → type=1
  • 读字段2:地址13=0x00, 地址14=0x30 → 0x0030 = 48
  • 读生成号:地址15=0x01 → gen=1
  • m_ObjectInfo[3] = {pos=48, gennum=1, type=1}m_SortedOffset 插入 48

条目4 (j=4, objnum=4)
  • entrystart = 地址16
  • 读类型:地址16=0x00 → type=0
  • 读字段2:地址17=0x00, 地址18=0x00 → 0
  • 读生成号:地址19=0x00 → 0
  • type=0 → m_ObjectInfo[4] = {pos=0, type=0}

条目5 (j=5, objnum=5)
  • entrystart = 地址20
  • 读类型:地址20=0x01 → type=1
  • 读字段2:地址21=0x00, 地址22=0x40 → 0x0040 = 64
  • 读生成号:地址23=0x00 → gen=0
  • m_ObjectInfo[5] = {pos=64, gennum=0, type=1}m_SortedOffset 插入 64

条目6 (j=6, objnum=6)
  • entrystart = 地址24
  • 读类型:地址24=0x02 → type=2
  • 读字段2:地址25=0x00, 地址26=0x02 → 2
  • 读生成号:地址27=0x00 → gen=0
  • m_ObjectInfo[6] = {pos=2, gennum=0, type=2},且 m_ObjectInfo[2].type = 255(对象流 #2 被标记)

条目7 (j=7, objnum=7)
  • entrystart = 地址28
  • 读类型:地址28=0x01 → type=1
  • 读字段2:地址29=0x00, 地址30=0x50 → 0x0050 = 80
  • 读生成号:地址31=0x00 → gen=0
  • m_ObjectInfo[7] = {pos=80, gennum=0, type=1}m_SortedOffset 插入 80

条目8 (j=8, objnum=8)
  • entrystart = 地址32
  • 读类型:地址32=0x01 → type=1
  • 读字段2:地址33=0x00, 地址34=0x60 → 0x0060 = 96
  • 读生成号:地址35=0x00 → gen=0
  • m_ObjectInfo[8] = {pos=96, gennum=0, type=1}m_SortedOffset 插入 96

条目9 (j=9, objnum=9)
  • entrystart = 地址36
  • 读类型:地址36=0x00 → type=0
  • 读字段2:地址37=0x00, 地址38=0x00 → 0
  • 读生成号:地址39=0x00 → 0
  • m_ObjectInfo[9] = {pos=0, type=0}

四、最终 m_ObjectInfo 映射表

对象号 pos gennum type 含义
0 16 0 1 未压缩对象,位于文件偏移16
1 32 0 1 未压缩对象,偏移32
2 1 0 2 压缩对象,存储在对象流#1中
3 48 1 1 未压缩对象,偏移48,生成号1
4 0 0 0 空闲对象
5 64 0 1 未压缩对象,偏移64
6 2 0 2 压缩对象,存储在对象流#2中
7 80 0 1 未压缩对象,偏移80
8 96 0 1 未压缩对象,偏移96
9 0 0 0 空闲对象

另外,对象流容器标记:

  • m_ObjectInfo[1].type 被设置为 255(因为条目2中 field2=1)
  • m_ObjectInfo[2].type 被设置为 255(因为条目6中 field2=2)

注意:对象1原本是未压缩对象(type=1),现在又被标记为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,生成号默认0):

  • WidthArray[0]=0, WidthArray[1]=4, WidthArray[2]=0
  • totalWidth = 4
  • 解析时:
    • type 保持默认1
    • field2 = 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个条目的完整示例,我们演示了:

  1. 动态读取宽度数组 → 确定每个字段的字节数
  2. 计算 totalWidth → 实现 O(1) 跳转到任意条目
  3. 使用 GetVarInt 读取大端整数 → 跨平台、任意宽度
  4. 根据类型分支处理 → 区分未压缩对象、压缩对象、空闲对象
  5. 标记对象流容器 → 为后续解压做准备

整个解析过程完全由 /W 数组驱动,不需要修改代码就能处理任何宽度组合。这正是 PDFium 设计的通用性和健壮性所在。

相关推荐
lixinnnn.2 小时前
多源BFS:矩阵距离
算法·宽度优先
禅思院2 小时前
探索Vite深入 Rollup 分块插件:从零实现一个智能分包工具
前端·前端框架·vite
MyY_DO2 小时前
布隆过滤器todo
java
CHANG_THE_WORLD2 小时前
PDFium 处理通用 `W` 数组的方式
数据结构·算法
砍材农夫2 小时前
spring-ai 第五模型介绍
java·人工智能·spring
mu_guang_2 小时前
计算机体系结构2-内存一致性
java·后端·spring·计算机体系结构
小旭95272 小时前
SpringBoot + 七牛云 + Quartz:图片存储与定时清理
java·spring boot·后端·mybatis
咕噜签名-铁蛋2 小时前
腾讯云ICP备案:变更主体&备案准备
前端·云计算·腾讯云
小码哥_常2 小时前
解锁Android黑科技:动态加载Activity,让你的App秒变变形金刚
前端