Skip to main content

docvortex_core/
form_events.rs

1//! 扫描普通内容流的图形状态事件,特殊语法完整交回 pypdf。
2
3/// 事件种类和原始操作数字节区间,避免为无关 PDF 对象分配内存。
4pub type FormEvent = (u8, Vec<(usize, usize)>);
5/// 状态程序持有数值,名称区间指向调用期间的不可变字节。
6pub type FormProgramEvent = (u8, Vec<f64>, Option<(usize, usize)>);
7
8/// PDF 白空白字符,保持与内容流读取器相同的分词边界。
9fn white(b: u8) -> bool {
10    matches!(b, 0 | 9 | 10 | 12 | 13 | 32)
11}
12/// 名称、数字和操作符的终止字符。
13fn delimiter(b: u8) -> bool {
14    white(b) || b"()<>[]{}/%".contains(&b)
15}
16
17/// 容器中的空白和注释不形成对象,结束符留给调用者处理。
18fn skip_space(data: &[u8], cursor: &mut usize) {
19    while *cursor < data.len() {
20        if white(data[*cursor]) {
21            *cursor += 1;
22        } else if data[*cursor] == b'%' {
23            while *cursor < data.len() && !matches!(data[*cursor], 10 | 13) {
24                *cursor += 1;
25            }
26        } else {
27            break;
28        }
29    }
30}
31
32/// 一个完整 PDF 操作数;参考内容流解析器不支持容器间接引用,遇到时整流回退。
33fn object_token(data: &[u8], cursor: &mut usize, depth: usize) -> Option<(u8, usize, usize)> {
34    let value = token(data, cursor, depth)?;
35    if value.0 == 1 && data[value.1..value.2].iter().all(u8::is_ascii_digit) {
36        let mut probe = *cursor;
37        if let Some((1, a, b)) = token(data, &mut probe, depth) {
38            if data[a..b].iter().all(u8::is_ascii_digit) {
39                if let Some((3, start, end)) = token(data, &mut probe, depth) {
40                    if &data[start..end] == b"R" {
41                        return None;
42                    }
43                }
44            }
45        }
46    }
47    Some(value)
48}
49
50/// 返回普通操作数或操作符的字节区间;字符串和数组整体跳过,避免正文伪造 Do。
51fn token(data: &[u8], cursor: &mut usize, depth: usize) -> Option<(u8, usize, usize)> {
52    if depth > 64 {
53        return None;
54    }
55    while *cursor < data.len() {
56        if white(data[*cursor]) {
57            *cursor += 1;
58        } else if data[*cursor] == b'%' {
59            while *cursor < data.len() && !matches!(data[*cursor], 10 | 13) {
60                *cursor += 1;
61            }
62        } else {
63            break;
64        }
65    }
66    let start = *cursor;
67    let first = *data.get(start)?;
68    *cursor += 1;
69    let kind = match first {
70        b'(' => {
71            let mut nesting = 1;
72            while nesting > 0 {
73                let b = *data.get(*cursor)?;
74                *cursor += 1;
75                match b {
76                    b'\\' => {
77                        data.get(*cursor)?;
78                        *cursor += 1;
79                    }
80                    b'(' => nesting += 1,
81                    b')' => nesting -= 1,
82                    _ => {}
83                }
84            }
85            0
86        }
87        b'[' => {
88            loop {
89                let (kind, _, _) = object_token(data, cursor, depth + 1)?;
90                if kind == 4 {
91                    break;
92                }
93                if kind == 3 {
94                    return None;
95                }
96            }
97            0
98        }
99        b']' => 4,
100        b'<' => {
101            if data.get(*cursor) == Some(&b'<') {
102                *cursor += 1;
103                loop {
104                    skip_space(data, cursor);
105                    if data.get(*cursor..*cursor + 2) == Some(b">>") {
106                        *cursor += 2;
107                        break;
108                    }
109                    let (kind, _, _) = token(data, cursor, depth + 1)?;
110                    if kind != 2 {
111                        return None;
112                    }
113                    let (kind, _, _) = object_token(data, cursor, depth + 1)?;
114                    if kind >= 3 {
115                        return None;
116                    }
117                }
118                return Some((0, start, *cursor));
119            }
120            loop {
121                let b = *data.get(*cursor)?;
122                *cursor += 1;
123                if b == b'>' {
124                    break;
125                }
126                if !white(b) && !b.is_ascii_hexdigit() {
127                    return None;
128                }
129            }
130            0
131        }
132        b'/' => {
133            while *cursor < data.len() && !delimiter(data[*cursor]) {
134                *cursor += 1;
135            }
136            // 即便名称属于无关操作,也必须保留参考解析器对损坏转义的整流失败行为。
137            let mut index = start + 1;
138            while index < *cursor {
139                if data[index] == b'#' {
140                    if index + 2 >= *cursor
141                        || !data[index + 1].is_ascii_hexdigit()
142                        || !data[index + 2].is_ascii_hexdigit()
143                    {
144                        return None;
145                    }
146                    index += 3;
147                } else {
148                    index += 1;
149                }
150            }
151            2
152        }
153        b'>' | b')' | b'{' | b'}' => return None,
154        _ => {
155            while *cursor < data.len() && !delimiter(data[*cursor]) {
156                *cursor += 1;
157            }
158            let word = &data[start..*cursor];
159            if [b"true".as_slice(), b"false", b"null"].contains(&word) {
160                return Some((0, start, *cursor));
161            }
162            if first.is_ascii_digit() || matches!(first, b'+' | b'-' | b'.') {
163                // pypdf 分块读取在第四个 16 字节块前触发长度限制,长数字保守交回参考解析器。
164                if word.len() >= 48
165                    || !word
166                        .iter()
167                        .all(|b| b.is_ascii_digit() || matches!(b, b'+' | b'-' | b'.'))
168                    || std::str::from_utf8(word)
169                        .ok()?
170                        .parse::<f64>()
171                        .ok()
172                        .is_none_or(|v| !v.is_finite())
173                {
174                    return None;
175                }
176                1
177            } else if first.is_ascii_alphabetic() || matches!(first, b'\'' | b'"') {
178                3
179            } else {
180                return None;
181            }
182        }
183    };
184    Some((kind, start, *cursor))
185}
186
187/// 只输出 q/Q/cm/Do,操作数仍由 pypdf 在小边界转换以保留原始数值和名称行为。
188pub fn form_state_events(data: &[u8]) -> Option<Vec<FormEvent>> {
189    let mut cursor = 0;
190    let mut operands = Vec::new();
191    let mut events = Vec::new();
192    while cursor < data.len() {
193        // 流尾空白和注释不会形成额外操作数。
194        if white(data[cursor]) {
195            cursor += 1;
196            continue;
197        }
198        if data[cursor] == b'%' {
199            while cursor < data.len() && !matches!(data[cursor], 10 | 13) {
200                cursor += 1;
201            }
202            continue;
203        }
204        let (kind, start, end) = token(data, &mut cursor, 0)?;
205        if kind == 4 {
206            return None;
207        }
208        if kind != 3 {
209            operands.push((kind, start, end));
210            continue;
211        }
212        let op = &data[start..end];
213        if op == b"BI" {
214            return None;
215        }
216        let event = match op {
217            b"q" => Some(0),
218            b"Q" => Some(1),
219            b"cm" if operands.len() == 6 && operands.iter().all(|o| o.0 == 1) => Some(2),
220            b"Do" if operands.len() == 1 && operands[0].0 == 2 => Some(3),
221            b"cm" | b"Do" => return None,
222            _ => None,
223        };
224        if let Some(kind) = event {
225            events.push((
226                kind,
227                if kind < 2 {
228                    vec![]
229                } else {
230                    operands.iter().map(|o| (o.1, o.2)).collect()
231                },
232            ));
233        }
234        operands.clear();
235    }
236    Some(events)
237}
238
239/// 数字操作数直接生成自有浮点值;整数负零按 NumberObject 转换成正零,名称仍由 Python 解码。
240pub fn form_state_program(data: &[u8]) -> Option<Vec<FormProgramEvent>> {
241    form_state_events(data)?
242        .into_iter()
243        .map(|(kind, intervals)| {
244            if kind == 2 {
245                let values = intervals
246                    .into_iter()
247                    .map(|(a, b)| {
248                        let word = &data[a..b];
249                        let value = std::str::from_utf8(word).ok()?.parse::<f64>().ok()?;
250                        Some(if value == 0.0 && !word.contains(&b'.') {
251                            0.0
252                        } else {
253                            value
254                        })
255                    })
256                    .collect::<Option<Vec<_>>>()?;
257                Some((kind, values, None))
258            } else {
259                Some((kind, Vec::new(), intervals.first().copied()))
260            }
261        })
262        .collect()
263}