1mod appinfo;
13mod compose;
14mod inflate;
15mod jpeg;
16mod native;
17mod native_glyphs;
18mod native_page;
19pub use native_glyphs::{MAX_NATIVE_FONT_GLYPHS, NativeFontGlyphAlias};
20mod outline;
21mod placement;
22mod structure;
23mod text;
24mod type3_image;
25
26pub use appinfo::{
27 ApplicationInfo, ApplicationInfoDefect, ApplicationInfoReport, ApplicationInfoStatus,
28 MAX_APPLICATION_INFO_BYTES, MAX_APPLICATION_INFO_FIELD_BYTES,
29};
30pub use compose::{
31 C8FontSource, C8FontSources, ComposeOptions, ComposePage, ComposeReport, ComposeVisitor,
32 ComposedImage, convert_document_pdf, convert_source_pages_pdf, uses_native_text,
33};
34pub use jpeg::{JpegColor, JpegInfo, read_type2_jpeg_info};
35pub(crate) use native::{
36 NativeRecord, NativeRecordVisitor, decode_native_character, decode_native_character_for_mode,
37 decode_native_image_coordinate,
38};
39#[cfg(test)]
40pub(crate) use native_page::labelled_font;
41pub(crate) use native_page::write_c8_native_page;
42pub use native_page::{
43 C8_DEFAULT_DECORATION_ALIAS, C8_MAX_FONTS, C8PageFonts, NativeDecorationGlyph,
44 NativeSymbolGlyph, SymbolFontIdentity, is_mode_zero_symbol,
45};
46pub use outline::{MAX_RECORDED_OUTLINE_DEFECTS, OutlineDefect, OutlineRepair, OutlineReport};
47pub(crate) use placement::{
48 C8GlyphClass, EMPIRICAL_COORDINATE_POINTS_PER_UNIT, EmpiricalPageGeometry,
49 empirical_c8_horizontal_decoration, empirical_c8_segment, empirical_image_transform,
50 empirical_page_from_pixels,
51};
52pub use structure::{ApplicationInfoTail, TextFraming, TextStructure};
53pub use text::RawTextCoordinate;
54pub(crate) use text::TEXT_DECODER_RESERVATION_BYTES;
55
56use crate::jbig1::Type0Span;
57use crate::{
58 Cancellation, Context, Error, ErrorKind, Hnc8Stage, Limits, RangedSource, Result, read_exact_at,
59};
60
61const PAGE_ROW_BYTES: u64 = 20;
62const IMAGE_RECORD_BYTES: u64 = 12;
63const OUTLINE_RECORD_BYTES: u64 = 308;
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub enum Variant {
68 C8,
69 HnA,
70 HnB,
71}
72
73impl Variant {
74 pub const fn as_str(self) -> &'static str {
75 match self {
76 Self::C8 => "C8",
77 Self::HnA => "HN-A",
78 Self::HnB => "HN-B",
79 }
80 }
81}
82
83#[derive(Clone, Copy)]
86struct At {
87 variant: Option<Variant>,
88 page: Option<u32>,
89 image: Option<u32>,
90 offset: Option<u64>,
91}
92
93impl At {
94 const NONE: Self = Self {
95 variant: None,
96 page: None,
97 image: None,
98 offset: None,
99 };
100
101 fn with_offset(self, offset: u64) -> Self {
102 Self {
103 offset: Some(offset),
104 ..self
105 }
106 }
107
108 fn locate(self, stage: Hnc8Stage, error: Error) -> Error {
112 let context = match error.context {
113 Context::None => Context::Hnc8 {
114 variant: self.variant,
115 page: self.page,
116 image: self.image,
117 segment: None,
118 stage: Some(stage),
119 },
120 Context::Jbig2 { segment } => Context::Hnc8 {
121 variant: self.variant,
122 page: self.page,
123 image: self.image,
124 segment,
125 stage: Some(stage),
126 },
127 Context::Hnc8 {
128 variant,
129 page,
130 image,
131 segment,
132 stage: own,
133 } => Context::Hnc8 {
134 variant: variant.or(self.variant),
135 page: page.or(self.page),
136 image: image.or(self.image),
137 segment,
138 stage: own.or(Some(stage)),
139 },
140 _ => return error,
141 };
142 Error {
143 offset: error.offset.or(self.offset),
144 context,
145 ..error
146 }
147 }
148
149 fn error(self, stage: Hnc8Stage, error: Error) -> Error {
151 self.locate(stage, error)
152 }
153
154 fn locator(self, stage: Hnc8Stage) -> impl FnOnce(Error) -> Error {
156 move |error| self.locate(stage, error)
157 }
158}
159
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub struct Span {
163 pub offset: u64,
164 pub length: u64,
165}
166
167impl Span {
168 pub fn checked_end(self) -> Option<u64> {
169 self.offset.checked_add(self.length)
170 }
171}
172
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub struct Header {
175 pub variant: Variant,
176 pub native_mode: Option<u32>,
180 pub native_origin: Option<[u16; 2]>,
184 pub page_size: Option<[u16; 2]>,
186 pub page_count: u32,
187 pub page_index: Span,
188}
189
190#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub struct PageRecord {
192 pub page_number: u32,
193 pub row_offset: u64,
194 pub text: Span,
195 pub image_count: u32,
196 pub unknown: [u8; 10],
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
203pub struct ImageRecord {
204 pub page_number: u32,
205 pub image_number: u32,
206 pub descriptor_offset: u64,
207 pub record_type: u32,
209 pub payload: Span,
210}
211
212impl ImageRecord {
213 pub fn type0_span(self) -> Option<Type0Span> {
216 (self.record_type == 0).then_some(Type0Span {
217 record_type: self.record_type,
218 offset: self.payload.offset,
219 length: self.payload.length,
220 })
221 }
222}
223
224#[derive(Clone, Copy)]
225struct Location {
226 variant: Option<Variant>,
227 offset: u64,
228 page: Option<u32>,
229 image: Option<u32>,
230}
231
232impl Location {
233 fn at(self, offset: u64) -> Self {
234 Self { offset, ..self }
235 }
236
237 fn context(self) -> Context {
238 Context::Hnc8 {
239 variant: self.variant,
240 page: self.page,
241 image: self.image,
242 segment: None,
243 stage: None,
244 }
245 }
246
247 fn locate(self, error: Error) -> Error {
249 error.or_at(self.offset, self.context())
250 }
251
252 fn malformed(self, reason: &'static str) -> Error {
253 self.locate(Error::invalid(reason))
254 }
255
256 fn unsupported(self, reason: &'static str) -> Error {
257 self.locate(Error::unsupported(self.offset, reason))
258 }
259
260 fn truncated(self, field: &'static str, expected: u64, available: u64) -> Error {
261 self.locate(Error::truncated(self.offset, expected, available).because(field))
262 }
263
264 fn limit(self, resource: &'static str, limit: u64, attempted: u64) -> Error {
265 self.locate(Error::limit(resource, limit, attempted))
266 }
267
268 fn cancelled(self) -> Error {
269 self.locate(Error::cancelled())
270 }
271}
272
273fn checked_span(
279 source_size: u64,
280 offset: u64,
281 length: u64,
282 loc: Location,
283 field: &'static str,
284) -> Result<Span> {
285 debug_assert!(offset < 1 << 42 && length < 1 << 42);
286 let end = offset + length;
287 if offset > source_size || end > source_size {
288 return Err(loc.truncated(field, length, source_size.saturating_sub(offset)));
289 }
290 Ok(Span { offset, length })
291}
292
293fn signed32(bytes: &[u8]) -> i32 {
294 i32::from_le_bytes(bytes.try_into().expect("fixed field width"))
295}
296
297fn signed16(bytes: &[u8]) -> i16 {
298 i16::from_le_bytes(bytes.try_into().expect("fixed field width"))
299}
300
301fn nonnegative32(value: i32, loc: Location, reason: &'static str) -> Result<u64> {
303 u64::try_from(value).map_err(|_| loc.malformed(reason))
304}
305
306fn read_fixed<S: RangedSource, C: Cancellation>(
307 source: &mut S,
308 limits: &Limits,
309 cancellation: &C,
310 offset: u64,
311 bytes: &mut [u8],
312 loc: Location,
313 field: &'static str,
314) -> Result<()> {
315 checked_span(source.size(), offset, bytes.len() as u64, loc, field)?;
316 let mut done = 0;
317 while done < bytes.len() {
318 let count = (bytes.len() - done).min(limits.io_chunk_bytes);
319 let current = offset + done as u64;
320 read_exact_at(
321 source,
322 current,
323 &mut bytes[done..done + count],
324 limits,
325 cancellation,
326 )
327 .map_err(|error| match error.kind {
328 ErrorKind::Truncated { .. } => loc.at(current).locate(error.because(field)),
329 _ => loc.at(current).locate(error),
330 })?;
331 done += count;
332 }
333 Ok(())
334}
335
336fn overlaps_protected(span: Span, protected_end: u64) -> bool {
337 span.length > 0 && span.offset < protected_end
338}
339
340#[derive(Clone, Copy)]
341struct CurrentPage {
342 page: PageRecord,
343 next_image: u32,
344 next_descriptor: u64,
345}
346
347pub struct Hnc8Reader<'a, S: RangedSource, C: Cancellation> {
350 source: &'a mut S,
351 limits: &'a Limits,
352 cancellation: &'a C,
353 header: Header,
354 page_row_bytes: u64,
355 next_page: u32,
356 current: Option<CurrentPage>,
357}
358
359impl<'a, S: RangedSource, C: Cancellation> Hnc8Reader<'a, S, C> {
360 pub fn open(source: &'a mut S, limits: &'a Limits, cancellation: &'a C) -> Result<Self> {
363 Self::open_starting_at(source, limits, cancellation, 1)
364 }
365
366 pub fn probe_at_page(
372 source: &'a mut S,
373 limits: &'a Limits,
374 cancellation: &'a C,
375 start_page: u32,
376 ) -> Result<Self> {
377 Self::open_starting_at(source, limits, cancellation, start_page)
378 }
379
380 fn open_starting_at(
381 source: &'a mut S,
382 limits: &'a Limits,
383 cancellation: &'a C,
384 start_page: u32,
385 ) -> Result<Self> {
386 let base = Location {
387 variant: None,
388 offset: 0,
389 page: None,
390 image: None,
391 };
392 limits.validate().map_err(|source| base.locate(source))?;
393 if cancellation.is_cancelled() {
394 return Err(base.cancelled());
395 }
396 if source.size() > limits.max_input_bytes {
397 return Err(base.limit("source bytes", limits.max_input_bytes, source.size()));
398 }
399 let mut magic = [0; 4];
400 read_fixed(
401 source,
402 limits,
403 cancellation,
404 0,
405 &mut magic,
406 base,
407 "signature",
408 )?;
409 let (variant, count_offset, index_start): (Variant, u64, u64) = match magic {
410 [0xc8, 0, 0, 0] => (Variant::C8, 0x08, 0x50),
411 [b'H', b'N', 0, 0] => {
412 let mut marker = [0; 4];
413 read_fixed(
414 source,
415 limits,
416 cancellation,
417 4,
418 &mut marker,
419 base.at(4),
420 "HN marker",
421 )?;
422 match marker {
423 [0x90, 0x01, 0, 0] => (Variant::HnA, 0x90, 0x15c),
424 [0xc8, 0, 0, 0] => (Variant::HnB, 0x90, 0xd8),
425 _ => {
426 return Err(base.at(4).unsupported("HN marker"));
427 }
428 }
429 }
430 _ => {
431 return Err(base.unsupported("signature"));
432 }
433 };
434 let loc = Location {
435 variant: Some(variant),
436 ..base
437 };
438 let mut count = [0; 4];
439 read_fixed(
440 source,
441 limits,
442 cancellation,
443 count_offset,
444 &mut count,
445 loc.at(count_offset),
446 "page count",
447 )?;
448 let signed_count = signed32(&count);
449 if signed_count <= 0 {
450 return Err(loc
451 .at(count_offset)
452 .malformed("page count: must be positive"));
453 }
454 let page_count = signed_count as u32;
455 if page_count > limits.max_pages {
456 return Err(loc.at(count_offset).limit(
457 "pages",
458 u64::from(limits.max_pages),
459 u64::from(page_count),
460 ));
461 }
462 let native_mode = if variant != Variant::HnA {
463 let offset = count_offset + 4;
464 let mut mode = [0; 4];
465 read_fixed(
466 source,
467 limits,
468 cancellation,
469 offset,
470 &mut mode,
471 loc.at(offset),
472 "native mode",
473 )?;
474 Some(u32::from_le_bytes(mode))
475 } else {
476 None
477 };
478 let native_origin = if variant != Variant::HnA {
479 let offset = count_offset + 20;
480 let mut origin = [0; 4];
481 read_fixed(
482 source,
483 limits,
484 cancellation,
485 offset,
486 &mut origin,
487 loc.at(offset),
488 "native coordinate origin",
489 )?;
490 Some([
491 u16::from_le_bytes([origin[0], origin[1]]),
492 u16::from_le_bytes([origin[2], origin[3]]),
493 ])
494 } else {
495 None
496 };
497 let page_size = {
498 let offset = count_offset + 24;
499 let mut size = [0; 4];
500 read_fixed(
501 source,
502 limits,
503 cancellation,
504 offset,
505 &mut size,
506 loc.at(offset),
507 "page dimensions",
508 )?;
509 Some([
510 u16::from_le_bytes([size[0], size[1]]),
511 u16::from_le_bytes([size[2], size[3]]),
512 ])
513 };
514 let index_start = if variant == Variant::HnA {
515 let mut outline = [0; 4];
516 read_fixed(
517 source,
518 limits,
519 cancellation,
520 0x158,
521 &mut outline,
522 loc.at(0x158),
523 "outline count",
524 )?;
525 let outline_count = nonnegative32(
526 signed32(&outline),
527 loc.at(0x158),
528 "outline count: negative signed value",
529 )?;
530 let outline_bytes = outline_count * OUTLINE_RECORD_BYTES;
532 index_start + outline_bytes
533 } else {
534 index_start
535 };
536 let page_row_bytes = if variant == Variant::HnB {
537 let mut marker = [0; 4];
538 read_fixed(
539 source,
540 limits,
541 cancellation,
542 0x88,
543 &mut marker,
544 loc.at(0x88),
545 "HN-B page-index layout",
546 )?;
547 match u32::from_le_bytes(marker) {
548 0 => 12,
549 0xc8 => PAGE_ROW_BYTES,
550 _ => {
551 return Err(loc.at(0x88).unsupported("HN-B page-index layout"));
552 }
553 }
554 } else {
555 PAGE_ROW_BYTES
556 };
557 let index_length = u64::from(page_count) * page_row_bytes;
558 let page_index = checked_span(
559 source.size(),
560 index_start,
561 index_length,
562 loc.at(index_start),
563 "page index",
564 )?;
565 if start_page == 0 || start_page > page_count {
566 return Err(loc
567 .at(index_start)
568 .malformed("page number: outside declared page index"));
569 }
570 Ok(Self {
571 source,
572 limits,
573 cancellation,
574 header: Header {
575 variant,
576 native_mode,
577 native_origin,
578 page_size,
579 page_count,
580 page_index,
581 },
582 page_row_bytes,
583 next_page: start_page,
584 current: None,
585 })
586 }
587
588 pub fn header(&self) -> Header {
589 self.header
590 }
591
592 pub fn source_mut(&mut self) -> &mut S {
593 self.source
594 }
595
596 pub fn next_page(&mut self) -> Result<Option<PageRecord>> {
597 let loc = Location {
598 variant: Some(self.header.variant),
599 offset: self.header.page_index.offset,
600 page: Some(self.next_page),
601 image: None,
602 };
603 if let Some(current) = self
604 .current
605 .filter(|page| page.next_image <= page.page.image_count)
606 {
607 return Err(Location {
608 page: Some(current.page.page_number),
609 image: Some(current.next_image),
610 offset: current.next_descriptor,
611 ..loc
612 }
613 .malformed("page has unread image records"));
614 }
615 if self.next_page > self.header.page_count {
616 return Ok(None);
617 }
618 if self.cancellation.is_cancelled() {
619 return Err(loc.cancelled());
620 }
621 let page_number = self.next_page;
622 let row_offset =
623 self.header.page_index.offset + u64::from(page_number - 1) * self.page_row_bytes;
624 let loc = loc.at(row_offset);
625 let mut row = [0; PAGE_ROW_BYTES as usize];
626 read_fixed(
627 self.source,
628 self.limits,
629 self.cancellation,
630 row_offset,
631 &mut row[..self.page_row_bytes as usize],
632 loc,
633 "page row",
634 )?;
635 let text_offset = nonnegative32(
636 signed32(&row[..4]),
637 loc,
638 "text offset: negative signed value",
639 )?;
640 let text_length = nonnegative32(
641 signed32(&row[4..8]),
642 loc.at(row_offset + 4),
643 "text length: negative signed value",
644 )?;
645 let text = checked_span(
646 self.source.size(),
647 text_offset,
648 text_length,
649 loc.at(row_offset),
650 "text span",
651 )?;
652 if self.page_row_bytes == 12 {
653 if text.offset < self.header.page_index.checked_end().expect("checked index") {
654 return Err(loc.malformed("text span: overlaps protected container index"));
655 }
656 let value = u32::from_le_bytes(row[8..12].try_into().expect("four bytes"));
657 if value != 0 {
658 return Err(loc
659 .at(row_offset + 8)
660 .unsupported("compact HN-B third word"));
661 }
662 }
663 let signed_images = signed16(&row[8..10]);
664 if signed_images < 0 {
665 return Err(loc
666 .at(row_offset + 8)
667 .malformed("image count: negative signed value"));
668 }
669 let image_count = signed_images as u32;
670 let mut unknown = [0; 10];
671 unknown.copy_from_slice(&row[10..]);
672 let page = PageRecord {
673 page_number,
674 row_offset,
675 text,
676 image_count,
677 unknown,
678 };
679 self.current = Some(CurrentPage {
680 page,
681 next_image: 1,
682 next_descriptor: text.checked_end().expect("checked text span"),
683 });
684 self.next_page += 1;
685 Ok(Some(page))
686 }
687
688 pub fn next_image(&mut self) -> Result<Option<ImageRecord>> {
689 let loc = Location {
690 variant: Some(self.header.variant),
691 offset: self.header.page_index.offset,
692 page: self.current.map(|current| current.page.page_number),
693 image: self.current.map(|current| current.next_image),
694 };
695 let current = self
696 .current
697 .ok_or_else(|| loc.malformed("no current page"))?;
698 if current.next_image > current.page.image_count {
699 return Ok(None);
700 }
701 if self.cancellation.is_cancelled() {
702 return Err(loc.cancelled());
703 }
704 let descriptor_offset = current.next_descriptor;
705 let loc = loc.at(descriptor_offset);
706 let descriptor = checked_span(
707 self.source.size(),
708 descriptor_offset,
709 IMAGE_RECORD_BYTES,
710 loc,
711 "image descriptor",
712 )?;
713 if overlaps_protected(
714 descriptor,
715 self.header
716 .page_index
717 .checked_end()
718 .expect("checked page index"),
719 ) {
720 return Err(loc.malformed("image descriptor: overlaps header or page index"));
721 }
722 let mut bytes = [0; IMAGE_RECORD_BYTES as usize];
723 read_fixed(
724 self.source,
725 self.limits,
726 self.cancellation,
727 descriptor_offset,
728 &mut bytes,
729 loc,
730 "image descriptor",
731 )?;
732 let signed_type = signed32(&bytes[..4]);
733 if signed_type < 0 {
734 return Err(loc.malformed("image type: negative signed value"));
735 }
736 if signed_type > 3 {
737 return Err(loc.unsupported("image type"));
738 }
739 let payload_offset = nonnegative32(
740 signed32(&bytes[4..8]),
741 loc.at(descriptor_offset + 4),
742 "image offset: negative signed value",
743 )?;
744 let payload_length = nonnegative32(
745 signed32(&bytes[8..12]),
746 loc.at(descriptor_offset + 8),
747 "image length: negative signed value",
748 )?;
749 if payload_length == 0 {
750 return Err(loc
751 .at(descriptor_offset + 8)
752 .malformed("image length: zero-length payload"));
753 }
754 let payload = checked_span(
755 self.source.size(),
756 payload_offset,
757 payload_length,
758 loc.at(descriptor_offset + 4),
759 "image payload",
760 )?;
761 if payload_length > self.limits.max_allocation_bytes {
762 return Err(loc.at(descriptor_offset + 8).limit(
763 "image span bytes",
764 self.limits.max_allocation_bytes,
765 payload_length,
766 ));
767 }
768 if payload.offset < descriptor.checked_end().expect("checked descriptor span") {
769 return Err(loc
770 .at(descriptor_offset + 4)
771 .malformed("image offset: payload overlaps descriptor or chain regresses"));
772 }
773 debug_assert!(!overlaps_protected(
777 payload,
778 self.header
779 .page_index
780 .checked_end()
781 .expect("checked page index"),
782 ));
783 let record_type = signed_type as u32;
784 let record = ImageRecord {
785 page_number: current.page.page_number,
786 image_number: current.next_image,
787 descriptor_offset,
788 record_type,
789 payload,
790 };
791 self.current = Some(CurrentPage {
792 next_image: current.next_image + 1,
793 next_descriptor: payload.checked_end().expect("checked image span"),
794 ..current
795 });
796 Ok(Some(record))
797 }
798}