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
8 changes: 2 additions & 6 deletions crates/pdf-annotation-core/src/source_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::pdf_data::{
};
use pdf_graphics::{DashPattern, color::Color, rect::Rect};
use pdf_object_reader::{
DictionaryContext, FromPdfObject, ObjectAccess, ObjectContext, ObjectReadError, ReadResult,
DictionaryContext, FromPdfObject, ObjectAccess, ObjectContext, ReadResult,
object_lookup::ObjectLookupExt,
object_resolver::{DanglingAsNullResolver, ObjectResolver},
object_variant::ObjectVariant,
Expand Down Expand Up @@ -128,11 +128,7 @@ impl SourceAnnotation {
};
// `/Annots` that resolves to anything but an array (e.g. a reference whose object
// number was reused by a later update) holds no annotations.
let resolved = match context.resolve(entry) {
Ok(resolved) => resolved,
Err(ObjectReadError::MissingObject { .. }) => return Ok(None),
Err(error) => return Err(error),
};
let resolved = context.resolve(entry)?;
let ObjectVariant::Array(annots) = resolved.value() else {
return Ok(None);
};
Expand Down
11 changes: 1 addition & 10 deletions crates/pdf-document/src/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,7 @@ impl FromPdfObject for PdfPage {
fn from_pdf_object(context: ObjectContext<'_, impl ObjectAccess + ?Sized>) -> ReadResult<Self> {
let mut context = context.dictionary()?;
let dictionary = context.dictionary();
// A dangling `/Contents` reference is null (ISO 32000 搂7.3.10), leaving the page empty.
let contents_absent = match dictionary.get(b"Contents") {
Some(object) => context.is_absent(object)?,
None => true,
};
let contents = if contents_absent {
None
} else {
context.optional::<ContentStream>(b"Contents")?
};
let contents = context.optional::<ContentStream>(b"Contents")?;
let media_box = dictionary.optional_media_box(context.source())?;
let crop_box = dictionary.optional_rect(b"CropBox", context.source())?;
let rotation = dictionary.optional_number::<i32>(b"Rotate", context.source())?;
Expand Down
33 changes: 32 additions & 1 deletion crates/pdf-object-collection/src/object_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl ObjectCollection {
// component, precision, colour, and opacity metadata that a
// flat byte buffer would lose, so the image path decodes it.
if !stream.filters_applied() {
let reader = ObjectReader::new(&*self);
let reader = ObjectReader::new(LoadedObjects(&*self));
let mut session = reader.session();
if !carries_jpx_codestream(stream, &mut session)
&& let Ok(data) =
Expand Down Expand Up @@ -524,6 +524,37 @@ mod tests {
}
}

/// Reads the objects inserted so far, failing on a reference to one not yet loaded.
///
/// Insertion decodes stream filters before later objects arrive, so an object missing
/// here may still be defined. Failing the read keeps such a stream encoded for a later
/// decode, where [`ObjectCollection`] resolves an object that is still missing to null.
struct LoadedObjects<'a>(&'a ObjectCollection);

impl ObjectResolver for LoadedObjects<'_> {
fn resolve_object<'a>(
&'a self,
obj: &'a ObjectVariant,
) -> Result<&'a ObjectVariant, ObjectError> {
self.0.resolve_object(obj)
}
}

impl pdf_object_reader::ObjectSource for LoadedObjects<'_> {
type Error = ObjectError;
fn read_object(
&self,
id: pdf_object_reader::object_id::ObjectId,
) -> Result<Option<pdf_object_reader::pdf_object::PdfObject>, Self::Error> {
self.0
.read_object(id)?
.map(Some)
.ok_or_else(|| ObjectError::FailedResolveObjectReference {
obj_num: id.number(),
})
}
}

impl pdf_object_reader::ObjectSource for ObjectCollection {
type Error = ObjectError;
fn read_object(
Expand Down
12 changes: 4 additions & 8 deletions crates/pdf-object-reader/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,16 +302,12 @@ macro_rules! context_access {
self.access.resolve(object)
}

/// Returns whether an object is null or references a nonexistent object.
/// Returns whether an object resolves to null.
///
/// Per PDF spec 搂7.3.10 such a reference is treated as null, and per 搂7.3.7 a
/// null dictionary value is equivalent to an absent entry.
/// A reference to a nonexistent object resolves to null (PDF spec 搂7.3.10), and
/// per 搂7.3.7 a null dictionary value is equivalent to an absent entry.
pub fn is_absent(&mut self, object: &ObjectVariant) -> ReadResult<bool> {
match self.access.resolve(object) {
Ok(resolved) => Ok(resolved.kind() == ObjectKind::Null),
Err(ObjectReadError::MissingObject { .. }) => Ok(true),
Err(error) => Err(error),
}
Ok(self.access.resolve(object)?.kind() == ObjectKind::Null)
}

/// Reborrows the active traversal for decoders that take an access directly.
Expand Down
6 changes: 0 additions & 6 deletions crates/pdf-object-reader/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@ pub enum ObjectReadError {
#[source]
source: BoxedError,
},
/// The source had no value for a referenced indirect object.
#[error("indirect object {object_id:?} was not found")]
MissingObject {
/// The unresolved object identifier.
object_id: ObjectId,
},
/// Reference traversal revisited an object active in the current read.
#[error("cyclic reference encountered at {repeated:?}; active path: {path:?}")]
CyclicReference {
Expand Down
3 changes: 2 additions & 1 deletion crates/pdf-object-reader/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ where
object_id: id,
source: Box::new(source),
})?
.ok_or(ObjectReadError::MissingObject { object_id: id })?;
// A reference to a nonexistent object is null (ISO 32000 搂7.3.10).
.unwrap_or_else(|| PdfObject::new(ObjectVariant::Null));
self.reader
.raw_cache
.insert_if_absent(id, object)
Expand Down
4 changes: 3 additions & 1 deletion crates/pdf-object-reader/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ pub trait ObjectSource: crate::object_resolver::ObjectResolver + Send + Sync {

/// Reads one indirect object without recursively resolving its references.
///
/// `Ok(None)` indicates that the identifier is absent. Returned objects may
/// `Ok(None)` indicates that the identifier is absent, which the reader resolves
/// to null (ISO 32000 搂7.3.10). Return an error instead where absence must fail
/// the read, such as an object that has not been loaded yet. Returned objects may
/// themselves be references and may contain references at any depth.
fn read_object(&self, object_id: ObjectId) -> Result<Option<PdfObject>, Self::Error>;
}
Expand Down
Loading