Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 15 additions & 16 deletions crates/pdf-document/src/object_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use crate::error::PdfReaderError;
use crate::object_stream::read_object_stream;
use crate::reader::{EncryptionContext, object_id};
use pdf_object::{
cross_reference_table::{CrossReferenceEntry, CrossReferenceEntryType},
error::ObjectError,
cross_reference_table::CrossReferenceEntryType, error::ObjectError,
object_variant::ObjectVariant,
};
use pdf_object_collection::object_collection::ObjectCollection;
Expand All @@ -45,12 +44,12 @@ struct LoadPlan {

impl LoadPlan {
/// Partitions live xref entries into normal objects and compressed-stream batches.
fn from_entries(entries: &BTreeMap<usize, CrossReferenceEntry>) -> Self {
fn from_entries(entries: &BTreeMap<usize, CrossReferenceEntryType>) -> Self {
let mut normal_objects = entries
.values()
.filter_map(|entry| match entry.entry_type {
CrossReferenceEntryType::Normal { byte_offset, .. } if byte_offset != 0 => {
Some(byte_offset)
.filter_map(|entry| match entry {
CrossReferenceEntryType::Normal { byte_offset, .. } if *byte_offset != 0 => {
Some(*byte_offset)
}
_ => None,
})
Expand All @@ -67,14 +66,14 @@ impl LoadPlan {
let CrossReferenceEntryType::Compressed {
object_stream_number,
index_within_stream,
} = entry.entry_type
} = entry
else {
continue;
};
compressed_streams
.entry(object_stream_number)
.entry(*object_stream_number)
.or_default()
.entry(index_within_stream)
.entry(*index_within_stream)
.or_default()
.push(object_number);
}
Expand Down Expand Up @@ -264,7 +263,7 @@ pub(super) struct ObjectLoader<'input, 'loader> {
impl<'input, 'loader> ObjectLoader<'input, 'loader> {
/// Creates an object loader for one parsed cross-reference table.
pub(super) fn new(
entries: &BTreeMap<usize, CrossReferenceEntry>,
entries: &BTreeMap<usize, CrossReferenceEntryType>,
parser: &'loader mut PdfParser<'input>,
encryption: EncryptionContext,
diagnostics: &'loader mut Vec<PdfReadDiagnostic>,
Expand Down Expand Up @@ -476,12 +475,12 @@ mod tests {
#[test]
fn load_plan_partitions_entries_and_groups_compressed_indexes() {
let entries = BTreeMap::from([
(0, CrossReferenceEntry::new_free(0, 65_535)),
(1, CrossReferenceEntry::new_normal(10, 0)),
(2, CrossReferenceEntry::new_compressed(5, 1)),
(3, CrossReferenceEntry::new_compressed(5, 1)),
(4, CrossReferenceEntry::new_normal(0, 0)),
(5, CrossReferenceEntry::new_normal(50, 0)),
(0, CrossReferenceEntryType::new_free(0, 65_535)),
(1, CrossReferenceEntryType::new_normal(10, 0)),
(2, CrossReferenceEntryType::new_compressed(5, 1)),
(3, CrossReferenceEntryType::new_compressed(5, 1)),
(4, CrossReferenceEntryType::new_normal(0, 0)),
(5, CrossReferenceEntryType::new_normal(50, 0)),
]);

let plan = LoadPlan::from_entries(&entries);
Expand Down
6 changes: 3 additions & 3 deletions crates/pdf-document/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use pdf_object::object_id::PdfObjectId;
use pdf_object::object_lookup::ObjectLookupExt;
use pdf_object::object_resolver::{ObjectResolver, PassthroughResolver};
use pdf_object::{
cross_reference_table::{CrossReferenceEntry, CrossReferenceTable},
cross_reference_table::{CrossReferenceEntryType, CrossReferenceTable},
error::ObjectError,
object_variant::ObjectVariant,
trailer::Trailer,
Expand Down Expand Up @@ -94,7 +94,7 @@ impl EncryptionContext {
/// Creates decryption state from the trailer's optional encryption entry.
fn from_trailer(
trailer: &mut Trailer,
entries: &BTreeMap<usize, CrossReferenceEntry>,
entries: &BTreeMap<usize, CrossReferenceEntryType>,
parser: &mut PdfParser,
password: &[u8],
diagnostics: &mut Vec<PdfReadDiagnostic>,
Expand Down Expand Up @@ -171,7 +171,7 @@ fn extract_page_tree(
/// Resolves and parses the trailer's encryption dictionary without decrypting it.
fn load_encrypt_dictionary(
encrypt_reference: ObjectVariant,
entries: &BTreeMap<usize, CrossReferenceEntry>,
entries: &BTreeMap<usize, CrossReferenceEntryType>,
parser: &mut PdfParser,
) -> Result<EncryptDictionary, PdfReaderError> {
let object = match encrypt_reference {
Expand Down
2 changes: 1 addition & 1 deletion crates/pdf-object-collection/src/object_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ impl ObjectCollection {
(
k.to_string(),
json!({
"entry_type": format!("{:?}", v.entry_type)
"entry_type": format!("{v:?}")
}),
)
})
Expand Down
48 changes: 20 additions & 28 deletions crates/pdf-object/src/cross_reference_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ use crate::trailer::Trailer;
#[derive(Debug, PartialEq, Clone)]
pub struct CrossReferenceTable {
/// The map of object numbers to cross-reference entries.
pub entries: BTreeMap<usize, CrossReferenceEntry>,
pub entries: BTreeMap<usize, CrossReferenceEntryType>,
/// The trailer associated with this cross-reference table.
pub trailer: Trailer,
}

impl CrossReferenceTable {
pub fn new(entries: BTreeMap<usize, CrossReferenceEntry>, trailer: Trailer) -> Self {
pub fn new(entries: BTreeMap<usize, CrossReferenceEntryType>, trailer: Trailer) -> Self {
CrossReferenceTable { entries, trailer }
}
}

/// Distinguishes the type of a cross-reference entry.
/// Represents a cross-reference entry.
#[derive(Debug, PartialEq, Clone)]
pub enum CrossReferenceEntryType {
/// Type 1: object at a byte offset in the file (traditional).
Expand All @@ -42,60 +42,52 @@ pub enum CrossReferenceEntryType {
},
}

#[derive(Debug, PartialEq, Clone)]
pub struct CrossReferenceEntry {
pub entry_type: CrossReferenceEntryType,
}

impl CrossReferenceEntry {
impl CrossReferenceEntryType {
/// Creates a normal entry for an uncompressed object at a byte offset.
pub fn new_normal(byte_offset: usize, generation_number: usize) -> Self {
CrossReferenceEntry {
entry_type: CrossReferenceEntryType::Normal {
byte_offset,
generation_number,
},
CrossReferenceEntryType::Normal {
byte_offset,
generation_number,
}
}

/// Creates an entry for an object stored within a compressed object stream.
pub fn new_compressed(object_stream_number: usize, index_within_stream: usize) -> Self {
CrossReferenceEntry {
entry_type: CrossReferenceEntryType::Compressed {
object_stream_number,
index_within_stream,
},
CrossReferenceEntryType::Compressed {
object_stream_number,
index_within_stream,
}
}

/// Creates an entry for a free object.
pub fn new_free(next_free_object: usize, generation_number: usize) -> Self {
CrossReferenceEntry {
entry_type: CrossReferenceEntryType::Free {
next_free_object,
generation_number,
},
CrossReferenceEntryType::Free {
next_free_object,
generation_number,
}
}

/// Returns the byte offset if this is a Normal entry.
pub fn byte_offset(&self) -> Option<usize> {
match &self.entry_type {
match self {
CrossReferenceEntryType::Normal { byte_offset, .. } => Some(*byte_offset),
_ => None,
}
}

/// Returns true if this is a Normal (in-use, uncompressed) entry.
pub fn is_normal(&self) -> bool {
matches!(self.entry_type, CrossReferenceEntryType::Normal { .. })
matches!(self, CrossReferenceEntryType::Normal { .. })
}

/// Returns true if this is a Free entry.
pub fn is_free(&self) -> bool {
matches!(self.entry_type, CrossReferenceEntryType::Free { .. })
matches!(self, CrossReferenceEntryType::Free { .. })
}

/// Returns true if this is a Compressed entry.
pub fn is_compressed(&self) -> bool {
matches!(self.entry_type, CrossReferenceEntryType::Compressed { .. })
matches!(self, CrossReferenceEntryType::Compressed { .. })
}
}

Expand Down
20 changes: 10 additions & 10 deletions crates/pdf-parser/src/cross_reference_stream.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;

use pdf_object::{
cross_reference_table::{CrossReferenceEntry, CrossReferenceTable},
cross_reference_table::{CrossReferenceEntryType, CrossReferenceTable},
object_lookup::ObjectLookupExt,
object_resolver::ObjectResolver,
stream::StreamObject,
Expand Down Expand Up @@ -162,7 +162,7 @@ impl<'data> XrefStreamEntryDecoder<'data> {
}

/// Returns the next complete entry, or `None` when the data is truncated.
fn next_entry(&mut self) -> Option<CrossReferenceEntry> {
fn next_entry(&mut self) -> Option<CrossReferenceEntryType> {
let end = self.position.saturating_add(self.layout.entry_width);
let bytes = self.data.get(self.position..end)?;
let entry = self.decode_entry(bytes)?;
Expand All @@ -171,7 +171,7 @@ impl<'data> XrefStreamEntryDecoder<'data> {
}

/// Decodes one entry according to the `/W` field widths.
fn decode_entry(&self, bytes: &[u8]) -> Option<CrossReferenceEntry> {
fn decode_entry(&self, bytes: &[u8]) -> Option<CrossReferenceEntryType> {
let type_end = self.layout.type_width;
let second_end = type_end.checked_add(self.layout.second_field_width)?;
let third_end = second_end.checked_add(self.layout.third_field_width)?;
Expand All @@ -189,10 +189,10 @@ impl<'data> XrefStreamEntryDecoder<'data> {
let third_value = read_field(third_field);

Some(match entry_type {
0 => CrossReferenceEntry::new_free(second_value, third_value),
1 => CrossReferenceEntry::new_normal(second_value, third_value),
2 => CrossReferenceEntry::new_compressed(second_value, third_value),
_ => CrossReferenceEntry::new_free(0, 0),
0 => CrossReferenceEntryType::new_free(second_value, third_value),
1 => CrossReferenceEntryType::new_normal(second_value, third_value),
2 => CrossReferenceEntryType::new_compressed(second_value, third_value),
_ => CrossReferenceEntryType::new_free(0, 0),
})
}
}
Expand Down Expand Up @@ -284,7 +284,7 @@ mod tests {

let e2 = &table.entries[&2];
assert!(e2.is_compressed());
match &e2.entry_type {
match e2 {
CrossReferenceEntryType::Compressed {
object_stream_number,
index_within_stream,
Expand Down Expand Up @@ -349,7 +349,7 @@ mod tests {

let table = parse_xref_stream(&stream, &PassthroughResolver).unwrap();

assert_eq!(table.entries[&0], CrossReferenceEntry::new_free(0, 0));
assert_eq!(table.entries[&0], CrossReferenceEntryType::new_free(0, 0));
}

/// Builds a compressed xref stream with FlateDecode + PNG Up predictor,
Expand Down Expand Up @@ -469,7 +469,7 @@ mod tests {

let e3 = &table.entries[&3];
assert!(e3.is_compressed());
match &e3.entry_type {
match e3 {
CrossReferenceEntryType::Compressed {
object_stream_number,
index_within_stream,
Expand Down
38 changes: 17 additions & 21 deletions crates/pdf-parser/src/cross_reference_table.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::collections::BTreeMap;

use pdf_object::{
cross_reference_table::{
CrossReferenceEntry, CrossReferenceEntryType, CrossReferenceStatus, CrossReferenceTable,
},
cross_reference_table::{CrossReferenceEntryType, CrossReferenceStatus, CrossReferenceTable},
object_resolver::ObjectResolver,
};
use pdf_tokenizer::PdfToken;
Expand Down Expand Up @@ -37,7 +35,7 @@ impl PdfParser<'_> {
/// Parses the contiguous subsections that follow an `xref` keyword.
pub(crate) fn parse_cross_reference_subsections(
&mut self,
) -> Result<BTreeMap<usize, CrossReferenceEntry>, ParserError> {
) -> Result<BTreeMap<usize, CrossReferenceEntryType>, ParserError> {
let mut entries = BTreeMap::new();

loop {
Expand All @@ -60,7 +58,7 @@ impl PdfParser<'_> {
/// Parses one xref subsection header and its declared number of entries.
fn parse_cross_reference_subsection(
&mut self,
) -> Result<(usize, Vec<CrossReferenceEntry>), ParserError> {
) -> Result<(usize, Vec<CrossReferenceEntryType>), ParserError> {
let start_object_number = self.read_number::<usize>(true)?;
self.skip_whitespace_and_comments();
let entry_count = self.read_number::<usize>(true)?;
Expand All @@ -76,7 +74,7 @@ impl PdfParser<'_> {
/// Parses a traditional xref row into its corresponding entry.
pub(crate) fn parse_cross_reference_entry(
&mut self,
) -> Result<CrossReferenceEntry, ParserError> {
) -> Result<CrossReferenceEntryType, ParserError> {
self.skip_whitespace_and_comments();
let field1 = self.read_number::<usize>(true)?;
self.skip_whitespace_and_comments();
Expand All @@ -93,9 +91,9 @@ impl PdfParser<'_> {
})?;

Ok(match status {
CrossReferenceStatus::Normal => CrossReferenceEntry::new_normal(field1, field2),
CrossReferenceStatus::Normal => CrossReferenceEntryType::new_normal(field1, field2),
CrossReferenceStatus::Free | CrossReferenceStatus::Old => {
CrossReferenceEntry::new_free(field1, field2)
CrossReferenceEntryType::new_free(field1, field2)
}
})
}
Expand All @@ -105,8 +103,8 @@ impl PdfParser<'_> {
/// leading free-object-zero pattern encountered in some PDFs.
fn normalize_xref_subsection_entries(
start_object_number: usize,
entries: Vec<CrossReferenceEntry>,
) -> Vec<(usize, CrossReferenceEntry)> {
entries: Vec<CrossReferenceEntryType>,
) -> Vec<(usize, CrossReferenceEntryType)> {
let has_leading_free_object_zero = start_object_number > 0
&& entries
.first()
Expand All @@ -132,14 +130,12 @@ fn normalize_xref_subsection_entries(

/// Returns whether an entry is the object-zero free entry incorrectly included
/// at the start of a non-zero xref subsection.
fn is_malformed_leading_free_object_zero(entry: &CrossReferenceEntry) -> bool {
fn is_malformed_leading_free_object_zero(entry: &CrossReferenceEntryType) -> bool {
matches!(
entry,
CrossReferenceEntry {
entry_type: CrossReferenceEntryType::Free {
next_free_object: 0,
generation_number: 65_535,
}
CrossReferenceEntryType::Free {
next_free_object: 0,
generation_number: 65_535,
}
)
}
Expand Down Expand Up @@ -271,8 +267,8 @@ mod tests {
#[test]
fn test_normalize_xref_subsection_entries_uses_declared_range() {
let entries = vec![
CrossReferenceEntry::new_normal(17, 0),
CrossReferenceEntry::new_normal(81, 0),
CrossReferenceEntryType::new_normal(17, 0),
CrossReferenceEntryType::new_normal(81, 0),
];

let normalized = normalize_xref_subsection_entries(4, entries);
Expand All @@ -284,9 +280,9 @@ mod tests {
#[test]
fn test_normalize_xref_subsection_entries_handles_leading_free_object_zero() {
let entries = vec![
CrossReferenceEntry::new_free(0, 65_535),
CrossReferenceEntry::new_normal(17, 0),
CrossReferenceEntry::new_normal(81, 0),
CrossReferenceEntryType::new_free(0, 65_535),
CrossReferenceEntryType::new_normal(17, 0),
CrossReferenceEntryType::new_normal(81, 0),
];

let normalized = normalize_xref_subsection_entries(4, entries);
Expand Down
Loading
Loading