PDFIUM如何处理宽度数组

文章目录

    • [一、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=0count=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 的处理方式。如果还有任何疑问,请指出具体哪一行或哪一个字节不清楚。

相关推荐
Renhao-Wan2 小时前
Java 中 Integer 对象的缓存机制与包装类特性
java·缓存
孙同学_2 小时前
【Linux篇】应用层自定义协议与序列化
linux·服务器·网络
航Hang*2 小时前
第3章:Linux系统安全管理——第1节:Linux 防火墙部署(firewalld)
linux·服务器·网络·学习·系统安全·vmware
桦02 小时前
【Linux复习】:多线程
linux·运维
云飞云共享云桌面2 小时前
SolidWorks三维设计不用单独买电脑,1台服务器10个设计用
运维·服务器·数据库·3d·电脑
Rabbit_QL2 小时前
从服务器拷文件到本地:scp 与 rsync 实战
服务器
acaad2 小时前
访问信创系统的服务器报错Received fatal alert: handshake_failure
运维·服务器
大树882 小时前
【无标题】
大数据·运维·服务器·人工智能
南境十里·墨染春水2 小时前
linux学习进展 基础命令 vi基础命令
linux·运维·服务器·笔记·学习