文章目录
-
- [一、W=[1,3,1] 时的 PDF 文件内二进制布局](#一、W=[1,3,1] 时的 PDF 文件内二进制布局)
-
- [示例:一个包含 3 个条目的 XRef 流数据(十六进制)](#示例:一个包含 3 个条目的 XRef 流数据(十六进制))
-
- [条目0 (字节 0~4):](#条目0 (字节 0~4):)
- [条目1 (字节 5~9):](#条目1 (字节 5~9):)
- [条目2 (字节 10~14):](#条目2 (字节 10~14):)
- 图示:条目0的二进制布局(带字节索引)
- [二、PDFium 代码如何解析这个 W=[1,3,1] 的流](#二、PDFium 代码如何解析这个 W=[1,3,1] 的流)
-
- [2.1 读取 W 数组](#2.1 读取 W 数组)
- [2.2 加载流数据](#2.2 加载流数据)
- [2.3 遍历条目并解析](#2.3 遍历条目并解析)
- [2.4 具体执行过程(针对条目0)](#2.4 具体执行过程(针对条目0))
- [三、代码与 PDF 结构的完整映射图](#三、代码与 PDF 结构的完整映射图)
- [四、对比不同 W 数组的差异](#四、对比不同 W 数组的差异)
- 五、完整解析流程图(结合代码与数据)
您说得对,我需要把 PDF 二进制结构、代码、以及一个具体例子(W=[1,3,1])三者紧密结合起来,让您看到每一个字节是如何被代码处理的。
一、W=[1,3,1] 时的 PDF 文件内二进制布局
假设一个 XRef 流的 /W 数组为 [1 3 1],这意味着:
| 字段 | 宽度(字节) | 含义 |
|---|---|---|
| 类型(Type) | 1 | 0=空闲,1=未压缩对象,2=压缩对象 |
| 字段2 | 3 | type=1时是文件偏移,type=2时是对象流编号 |
| 生成号(Gen) | 1 | 对象的生成号(通常为0) |
每个条目总宽度 = 1 + 3 + 1 = 5 字节。
示例:一个包含 3 个条目的 XRef 流数据(十六进制)
假设流中连续存放 3 个条目,原始字节如下(以十六进制表示):
地址 字节内容 说明
0: 01 00 01 00 00 ← 条目0 (对象0)
5: 02 00 00 01 00 ← 条目1 (对象1)
10: 00 00 00 00 00 ← 条目2 (对象2)
解析每个条目:
条目0 (字节 0~4):
01 00 01 00 00
│ └──┬──┘ │
类型=1 字段2=0x000100=256 生成号=0
含义:未压缩对象,位于文件偏移 256 字节处,生成号 0。
条目1 (字节 5~9):
02 00 00 01 00
│ └──┬──┘ │
类型=2 字段2=0x000001=1 生成号=0
含义:压缩对象,位于对象流 #1 中,生成号 0。
条目2 (字节 10~14):
00 00 00 00 00
│ └──┬──┘ │
类型=0 字段2=0 生成号=0
含义:空闲对象(已删除或未使用)。
图示:条目0的二进制布局(带字节索引)
条目0 (5字节):
+--------+------------------------+--------+
| 字节0 | 字节1 | 字节2 | 字节3 | 字节4 |
| 类型 | 字段2 (大端) | 生成号 |
| 0x01 | 0x00 | 0x01 | 0x00 | 0x00 |
+--------+------------------------+--------+
二、PDFium 代码如何解析这个 W=[1,3,1] 的流
2.1 读取 W 数组
cpp
CPDF_Array* pArray = pDict->GetArrayFor("W");
std::vector<uint32_t> WidthArray;
for (size_t i = 0; i < pArray->GetCount(); ++i)
WidthArray.push_back(pArray->GetIntegerAt(i));
// 此时 WidthArray = [1, 3, 1]
uint32_t totalWidth = WidthArray[0] + WidthArray[1] + WidthArray[2];
// totalWidth = 1 + 3 + 1 = 5
2.2 加载流数据
cpp
auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
pAcc->LoadAllData();
const uint8_t* pData = pAcc->GetData(); // 指向流数据的第一个字节
uint32_t dwTotalSize = pAcc->GetSize(); // 假设 >= 15 (3个条目*5字节)
2.3 遍历条目并解析
假设 Index 数组指定了 [0 3](即对象0,1,2都在此流中),那么 segindex=0,count=3。
cpp
const uint8_t* segstart = pData + segindex * totalWidth; // segstart = pData
for (uint32_t j = 0; j < count; j++) {
const uint8_t* entrystart = segstart + j * totalWidth; // 每个条目起始
// 读取类型 (宽度=1)
int32_t type = 1; // 默认值
if (WidthArray[0]) // 1 > 0
type = GetVarInt(entrystart, WidthArray[0]); // 读1字节
// 读取字段2 (宽度=3)
FX_FILESIZE field2 = 0;
if (WidthArray[1]) // 3 > 0
field2 = GetVarInt(entrystart + WidthArray[0], WidthArray[1]); // 偏移1字节,读3字节
// 读取生成号 (宽度=1)
int32_t gen = 0;
if (WidthArray[2]) // 1 > 0
gen = GetVarInt(entrystart + WidthArray[0] + WidthArray[1], WidthArray[2]); // 偏移4字节,读1字节
// 根据 type 处理
m_ObjectInfo[startnum + j].type = type;
if (type == 0) {
m_ObjectInfo[startnum + j].pos = 0;
} else {
m_ObjectInfo[startnum + j].pos = field2;
if (type == 1) {
m_SortedOffset.insert(field2);
} else if (type == 2) {
// 标记对象流
m_ObjectInfo[field2].type = 255;
}
}
}
2.4 具体执行过程(针对条目0)
j = 0:
entrystart = pData + 0*5 = pData (指向 0x01)
WidthArray[0]=1 → type = GetVarInt(entrystart, 1) = 0x01 = 1
field2 = GetVarInt(entrystart+1, 3) = 读取字节1,2,3: 0x00,0x01,0x00 → 大端 = 0x000100 = 256
gen = GetVarInt(entrystart+4, 1) = 0x00 = 0
type=1 → m_ObjectInfo[0].pos=256, type=1, gennum=0; 插入 m_SortedOffset(256)
条目1:
entrystart = pData + 5
type = 0x02 = 2
field2 = 读取 0x00,0x00,0x01 = 1
gen = 0x00 = 0
type=2 → m_ObjectInfo[1].pos=1, type=2; 且 m_ObjectInfo[1].type=255 (标记对象流1)
条目2:
entrystart = pData + 10
type = 0x00 = 0
field2 = 0
gen = 0
type=0 → m_ObjectInfo[2].pos=0, type=0
三、代码与 PDF 结构的完整映射图
PDF 文件中的 XRef 流 (W=[1,3,1]) 二进制数据:
偏移(相对流起始) 字节内容(十六进制) 字段解析
0: 01 → type=1
1-3: 00 01 00 → field2=256 (大端)
4: 00 → gen=0
---------------------------------------------
5: 02 → type=2
6-8: 00 00 01 → field2=1
9: 00 → gen=0
---------------------------------------------
10: 00 → type=0
11-13: 00 00 00 → field2=0
14: 00 → gen=0
PDFium 代码如何读取:
uint8_t* p = pData; // 指向偏移0
// 条目0
int type = GetVarInt(p, 1); // p[0]=0x01 → 1
int64_t field2 = GetVarInt(p+1, 3); // p[1..3]=0x000100 → 256
int gen = GetVarInt(p+4, 1); // p[4]=0x00 → 0
p += 5; // 移动到下一个条目
// 条目1
type = GetVarInt(p, 1); // 0x02 → 2
field2 = GetVarInt(p+1, 3); // 0x000001 → 1
gen = GetVarInt(p+4, 1); // 0x00 → 0
p += 5;
// 条目2
...
四、对比不同 W 数组的差异
| W 数组 | 每个条目字节数 | 类型字段 | 字段2宽度 | 生成号宽度 | 示例用途 |
|---|---|---|---|---|---|
| [1,4,1] | 6 | 1字节 | 4字节 | 1字节 | 常规文件(偏移<4GB) |
| [1,3,1] | 5 | 1字节 | 3字节 | 1字节 | 小文件(偏移<16MB) |
| [0,4,1] | 5 | 默认1 | 4字节 | 1字节 | 所有对象都是未压缩的 |
| [1,8,2] | 11 | 1字节 | 8字节 | 2字节 | 超大文件(64位偏移) |
| [0,8,0] | 8 | 默认1 | 8字节 | 默认0 | 极致压缩,只有偏移 |
代码的通用性:无论哪种组合,上述三段解析代码都能工作,因为:
- 宽度值决定读取字节数
- 宽度为0时跳过读取并使用默认值
- 指针偏移通过累加宽度精确计算
五、完整解析流程图(结合代码与数据)
PDF 文件
│
▼
解析 XRef 流对象
│
├── 读取 /W → [1,3,1] → totalWidth=5
│
├── 读取 /Index → [0,3] → 对象0,1,2
│
├── 读取流数据 (15字节)
│
└── 循环每个对象 (j=0..2)
│
├── entrystart = pData + j*5
│
├── type = (WidthArray[0]?GetVarInt(entrystart,1):1)
├── field2 = (WidthArray[1]?GetVarInt(entrystart+1,3):0)
├── gen = (WidthArray[2]?GetVarInt(entrystart+4,1):0)
│
└── 存储到 m_ObjectInfo[objnum]
希望这个结合了具体 W=[1,3,1] 例子、二进制字节布局、代码逐行解析的答案能让您彻底理解 PDFium 的处理方式。如果还有任何疑问,请指出具体哪一行或哪一个字节不清楚。