1use super::{FieldCursor, FieldFault, SegmentHeader, SegmentSpan, unsupported};
11use crate::{Cancellation, Context, Error, Limits, RangedSource, Result};
12
13pub const IMMEDIATE_TEXT_REGION: u8 = 6;
15const INTERMEDIATE_TEXT_REGION: u8 = 4;
16const IMMEDIATE_LOSSLESS_TEXT_REGION: u8 = 7;
17const REGION_INFO_BYTES: usize = 17;
18const FLAGS_BYTES: usize = 2;
19const PREFIX_BYTES: usize = REGION_INFO_BYTES + FLAGS_BYTES;
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum RegionCombination {
25 Or,
26 And,
27 Xor,
28 Xnor,
29 Replace,
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct RegionInfo {
35 pub width: u32,
36 pub height: u32,
37 pub x: u32,
38 pub y: u32,
39 pub combination: RegionCombination,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub enum ReferenceCorner {
45 BottomLeft,
46 TopLeft,
47 BottomRight,
48 TopRight,
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub enum SymbolCombination {
54 Or,
55 And,
56 Xor,
57 Xnor,
58}
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub struct TextRegionFlags {
63 pub raw: u16,
64 pub huffman: bool,
66 pub refine: bool,
68 pub log_strips: u8,
70 pub reference_corner: ReferenceCorner,
71 pub transposed: bool,
72 pub combination: SymbolCombination,
73 pub default_pixel: bool,
75 pub ds_offset: i8,
77 pub refinement_template: u8,
79}
80
81impl TextRegionFlags {
82 pub fn strips(self) -> u8 {
84 1 << self.log_strips
85 }
86}
87
88#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
91pub enum TextHeaderPolicy {
92 #[default]
93 Strict,
94 HnC8UnusedRefinementTemplate,
99}
100
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum TextHeaderAnomaly {
104 UnusedRefinementTemplate,
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub struct TextRegionHeader {
110 pub segment: u32,
112 pub page_association: u32,
113 pub dictionary_segment: u32,
114 pub region: RegionInfo,
115 pub flags: TextRegionFlags,
116 pub anomaly: Option<TextHeaderAnomaly>,
118 pub huffman_flags: Option<u16>,
120 pub refinement_at: Option<[(i8, i8); 2]>,
123 pub instances: u32,
125 pub header_bytes: u64,
126 pub body: SegmentSpan,
127}
128
129impl TextRegionHeader {
130 pub fn unsupported_feature(&self) -> Option<(&'static str, u64)> {
133 if let Some(flags) = self.huffman_flags {
134 Some(("Huffman text region", u64::from(flags)))
135 } else if self.refinement_at.is_some() {
136 Some(("refinement template 0 with adaptive pixels", 0))
137 } else {
138 None
139 }
140 }
141}
142
143struct Cursor<'a, S, C> {
144 source: &'a mut S,
145 cancellation: &'a C,
146 segment: u32,
147 fields: FieldCursor,
148}
149
150impl<S: RangedSource, C: Cancellation> Cursor<'_, S, C> {
151 fn error_at(&self, offset: u64, error: Error) -> Error {
152 error.or_at(
153 offset,
154 Context::Jbig2 {
155 segment: Some(self.segment),
156 },
157 )
158 }
159
160 fn error(&self, error: Error) -> Error {
161 self.error_at(self.fields.at, error)
162 }
163
164 fn read<const N: usize>(&mut self, field: &'static str) -> Result<[u8; N]> {
166 let mut bytes = [0u8; N];
167 let result = self.fields.fill(self.source, self.cancellation, &mut bytes);
168 result.map_err(|fault| self.fault(fault, field))?;
169 Ok(bytes)
170 }
171
172 fn fault(&self, fault: FieldFault, field: &'static str) -> Error {
174 let at = self.fields.at;
175 self.error(match fault {
176 FieldFault::Overflow { expected } => {
177 Error::truncated(at, expected, self.fields.end.saturating_sub(at)).because(field)
178 }
179 fault => fault.error(at, field),
180 })
181 }
182}
183
184fn be32(bytes: &[u8]) -> u32 {
185 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
186}
187
188pub fn read_text_region_header<S: RangedSource, C: Cancellation>(
196 source: &mut S,
197 header: &SegmentHeader,
198 dictionary: &SegmentHeader,
199 limits: &Limits,
200 cancellation: &C,
201) -> Result<TextRegionHeader> {
202 read_text_region_header_with_policy(
203 source,
204 header,
205 dictionary,
206 limits,
207 cancellation,
208 TextHeaderPolicy::Strict,
209 )
210}
211
212pub fn read_text_region_header_with_policy<S: RangedSource, C: Cancellation>(
216 source: &mut S,
217 header: &SegmentHeader,
218 dictionary: &SegmentHeader,
219 limits: &Limits,
220 cancellation: &C,
221 policy: TextHeaderPolicy,
222) -> Result<TextRegionHeader> {
223 let fail = |error: Error| {
224 error.or_at(
225 header.data.offset,
226 Context::Jbig2 {
227 segment: Some(header.number),
228 },
229 )
230 };
231 if cancellation.is_cancelled() {
232 return Err(fail(Error::cancelled()));
233 }
234 match header.segment_type {
235 IMMEDIATE_TEXT_REGION => {}
236 INTERMEDIATE_TEXT_REGION | IMMEDIATE_LOSSLESS_TEXT_REGION => {
237 return Err(fail(unsupported("text region segment type")));
238 }
239 _ => {
240 return Err(fail(Error::invalid("segment type is not a text region")));
241 }
242 }
243 if header.page_association == 0 {
244 return Err(fail(Error::invalid("immediate region without a page")));
245 }
246 if header.referred_to.len() != 1 {
247 return Err(fail(unsupported("text region reference count")));
248 }
249 if header.referred_to[0] != dictionary.number {
250 return Err(fail(Error::invalid(
251 "reference differs from the supplied dictionary",
252 )));
253 }
254 if dictionary.segment_type != 0 {
255 return Err(fail(Error::invalid(
256 "referred segment is not a symbol dictionary",
257 )));
258 }
259 if dictionary.number >= header.number {
260 return Err(fail(Error::invalid("dictionary does not precede region")));
261 }
262 if dictionary.page_association != 0 && dictionary.page_association != header.page_association {
263 return Err(fail(Error::invalid("dictionary page association differs")));
264 }
265 if header.data.length > limits.max_input_bytes {
266 return Err(fail(Error::limit(
267 "text region data bytes",
268 limits.max_input_bytes,
269 header.data.length,
270 )));
271 }
272 if header.header_length > header.data.offset {
273 return Err(fail(Error::invalid("segment header start underflow")));
274 }
275 let end = header
276 .data
277 .offset
278 .checked_add(header.data.length)
279 .ok_or_else(|| fail(Error::invalid("data end overflow")))?;
280 if end > source.size() {
281 return Err(fail(Error::invalid("data outside source")));
282 }
283
284 let mut cursor = Cursor {
285 source,
286 cancellation,
287 segment: header.number,
288 fields: FieldCursor {
289 start: header.data.offset,
290 at: header.data.offset,
291 end,
292 fetched: 0,
293 request_bytes: limits.io_chunk_bytes.max(1),
294 },
295 };
296 let start = header.data.offset;
297 let prefix: [u8; PREFIX_BYTES] = cursor.read("text region header")?;
298 let region = parse_region(&prefix, &cursor, limits)?;
299 let (flags, anomaly) = parse_flags(
300 u16::from_be_bytes([prefix[REGION_INFO_BYTES], prefix[REGION_INFO_BYTES + 1]]),
301 policy,
302 )
303 .map_err(|error| cursor.error_at(start + REGION_INFO_BYTES as u64, error))?;
304
305 let huffman_flags = if flags.huffman {
306 let offset = cursor.fields.at;
307 let raw = u16::from_be_bytes(cursor.read("text region Huffman flags")?);
308 check_huffman_flags(raw, flags.refine).map_err(|error| cursor.error_at(offset, error))?;
309 Some(raw)
310 } else {
311 None
312 };
313 let refinement_at = if flags.refine && flags.refinement_template == 0 {
314 let [x1, y1, x2, y2] = cursor.read("text region refinement AT")?;
315 Some([(x1 as i8, y1 as i8), (x2 as i8, y2 as i8)])
316 } else {
317 None
318 };
319 let instances_offset = cursor.fields.at;
320 let instances = u32::from_be_bytes(cursor.read("SBNUMINSTANCES")?);
321 if u64::from(instances) > limits.max_image_pixels {
322 return Err(cursor.error_at(
323 instances_offset,
324 Error::limit(
325 "text region symbol instances",
326 limits.max_image_pixels,
327 u64::from(instances),
328 ),
329 ));
330 }
331 let body_length = end - cursor.fields.at;
332 if !flags.huffman && body_length < 2 {
333 return Err(cursor.error(
334 Error::truncated(cursor.fields.at, 2, body_length).because("MQ body terminal pair"),
335 ));
336 }
337 Ok(TextRegionHeader {
338 segment: header.number,
339 page_association: header.page_association,
340 dictionary_segment: header.referred_to[0],
341 region,
342 flags,
343 anomaly,
344 huffman_flags,
345 refinement_at,
346 instances,
347 header_bytes: cursor.fields.at - start,
348 body: SegmentSpan {
349 offset: cursor.fields.at,
350 length: body_length,
351 },
352 })
353}
354
355fn parse_region<S: RangedSource, C: Cancellation>(
356 bytes: &[u8; PREFIX_BYTES],
357 cursor: &Cursor<'_, S, C>,
358 limits: &Limits,
359) -> Result<RegionInfo> {
360 let start = cursor.fields.start;
361 let (width, height) = (be32(&bytes[0..4]), be32(&bytes[4..8]));
362 let (x, y) = (be32(&bytes[8..12]), be32(&bytes[12..16]));
363 let flags_offset = start + 16;
364 if bytes[16] & 0xf8 != 0 {
365 return Err(cursor.error_at(
366 flags_offset,
367 Error::invalid("reserved region segment flags"),
368 ));
369 }
370 let combination = match bytes[16] & 7 {
371 0 => RegionCombination::Or,
372 1 => RegionCombination::And,
373 2 => RegionCombination::Xor,
374 3 => RegionCombination::Xnor,
375 4 => RegionCombination::Replace,
376 _ => {
377 return Err(
378 cursor.error_at(flags_offset, Error::invalid("region combination operator"))
379 );
380 }
381 };
382 for (attempted, offset) in [(width, start), (height, start + 4)] {
383 if attempted == 0 {
384 return Err(cursor.error_at(offset, unsupported("empty text region")));
385 }
386 }
387 let pixels = u64::from(width) * u64::from(height);
388 if pixels > limits.max_image_pixels {
389 return Err(cursor.error_at(
390 start,
391 Error::limit("text region pixels", limits.max_image_pixels, pixels),
392 ));
393 }
394 Ok(RegionInfo {
395 width,
396 height,
397 x,
398 y,
399 combination,
400 })
401}
402
403fn parse_flags(
404 raw: u16,
405 policy: TextHeaderPolicy,
406) -> Result<(TextRegionFlags, Option<TextHeaderAnomaly>)> {
407 let refine = raw & 2 != 0;
408 let refinement_template = (raw >> 15) as u8;
409 let anomaly = if !refine && refinement_template != 0 {
410 if policy == TextHeaderPolicy::HnC8UnusedRefinementTemplate && raw & !0x7c00 == 0x800c {
413 Some(TextHeaderAnomaly::UnusedRefinementTemplate)
414 } else {
415 return Err(Error::invalid("SBRTEMPLATE without SBREFINE"));
416 }
417 } else {
418 None
419 };
420 let ds_offset = (((raw >> 10) & 0x1f) as i8) << 3 >> 3;
422 Ok((
423 TextRegionFlags {
424 raw,
425 huffman: raw & 1 != 0,
426 refine,
427 log_strips: ((raw >> 2) & 3) as u8,
428 reference_corner: match (raw >> 4) & 3 {
429 0 => ReferenceCorner::BottomLeft,
430 1 => ReferenceCorner::TopLeft,
431 2 => ReferenceCorner::BottomRight,
432 _ => ReferenceCorner::TopRight,
433 },
434 transposed: raw & 0x40 != 0,
435 combination: match (raw >> 7) & 3 {
436 0 => SymbolCombination::Or,
437 1 => SymbolCombination::And,
438 2 => SymbolCombination::Xor,
439 _ => SymbolCombination::Xnor,
440 },
441 default_pixel: raw & 0x200 != 0,
442 ds_offset,
443 refinement_template,
444 },
445 anomaly,
446 ))
447}
448
449fn check_huffman_flags(raw: u16, refine: bool) -> Result<()> {
452 let malformed = |field| Err(Error::invalid(field));
453 if raw & 0x8000 != 0 {
454 return malformed("reserved Huffman flag");
455 }
456 if [0, 6, 8, 10, 12]
457 .iter()
458 .any(|shift| (raw >> shift) & 3 == 2)
459 {
460 return malformed("reserved Huffman table selector");
461 }
462 if !refine && raw & 0x7fc0 != 0 {
463 return malformed("refinement Huffman tables without SBREFINE");
464 }
465 Ok(())
466}