mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 13:24:35 +00:00
From CodeRabbit's review of the stack: - Every JBIG2 image read its /JBIG2Globals afresh, within 32 MiB each but with no bound across an export or a redaction. A globals stream of the file, not edited since it was read and with its /DecodeParms direct, now has its filters undone once a document and is kept for the images that share it (in every clone, which share the file), up to 64 MiB in all. An edited one, one added in memory, or one with /DecodeParms by reference (another object, which can change under it) is read each time as before. This saves the filters only: the decoder reads the segments afresh for every image, so a real symbol dictionary still costs its decoding once an image. - view.copy-image dropped "JBIG2 inside other filters" from its missing list on the strength of Export's test; Copy Image now has its own: a JBIG2 picture behind ASCIIHexDecode is found under the pointer and copied as its pixels and as a PNG, and one whose page information is in its globals is read through them. A JBIG2 image's /Width and /Height are read as given directly; one by reference is an error rather than an image of no rows. A /JBIG2Globals reference to no object still reads as no globals: it is a reference to null (ISO 32000-2 §7.3.10), which an entry may hold for being absent (§7.3.7), and the renderer draws the page so. An image that needed those globals then mostly fails to decode; in a probe of twelve pictures that decoded with them, eleven were refused and one decoded to other pixels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
82 lines
3.2 KiB
Rust
82 lines
3.2 KiB
Rust
//! Document: cross-reference data, lazy object loading, and a copy-on-write overlay of edits.
|
|
//!
|
|
//! `Document` is cheap to clone (the original bytes, xref index and parse caches are shared), so
|
|
//! a clone is a snapshot: undo/redo and background jobs hold clones while the UI edits another.
|
|
//! Edits never touch the original bytes; the writer appends them (incremental save) or rewrites
|
|
//! the file (full save).
|
|
|
|
use std::borrow::Cow;
|
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
use std::sync::{Arc, Mutex, Weak};
|
|
|
|
use crate::CosError;
|
|
use crate::object::{Dict, MAX_DECODED, MAX_JBIG2_GLOBALS, ObjRef, Object, Stream};
|
|
use crate::parser::{EndstreamIndex, Lexer, is_whitespace, parse_indirect, parse_indirect_shared};
|
|
|
|
mod objects;
|
|
mod open;
|
|
mod reader;
|
|
mod state;
|
|
mod xref;
|
|
|
|
#[cfg(test)]
|
|
pub(crate) use reader::find;
|
|
#[cfg(test)]
|
|
use reader::rfind;
|
|
pub(crate) use state::ObjectReader;
|
|
use state::{ObjStm, Slot};
|
|
pub use state::{ReadState, SourceIdentity};
|
|
#[cfg(test)]
|
|
use state::{SECTION_READS, objstm_member, owned_bytes};
|
|
use xref::XrefIndex;
|
|
pub use xref::{Revision, XrefEntry};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Document {
|
|
data: Arc<Vec<u8>>,
|
|
/// Where `endstream` occurs in `data`; see [`EndstreamIndex`].
|
|
endstreams: Arc<EndstreamIndex>,
|
|
source_identity: Arc<()>,
|
|
entries: Arc<XrefIndex>,
|
|
trailer: Dict,
|
|
revisions: Arc<Vec<Revision>>,
|
|
/// See [`Self::xref_oddities`].
|
|
xref_oddities: Arc<Vec<(u64, u32)>>,
|
|
/// Where the file's object headers are, as (offset, generation) of the last header of each
|
|
/// number. Filled only when the cross-reference data proved inconsistent; see
|
|
/// `fill_xref_gaps`.
|
|
located: Arc<BTreeMap<u32, (u64, u16)>>,
|
|
repair_log: Arc<Vec<String>>,
|
|
cache: Arc<Mutex<HashMap<u32, Arc<Object>>>>,
|
|
objstms: Arc<Mutex<HashMap<u32, Arc<ObjStm>>>>,
|
|
/// What `/JBIG2Globals` streams of the file decode to, by object number: the images of a scan
|
|
/// share them, and each would otherwise decode them again (see `Document::jbig2_globals`).
|
|
jbig2_globals: Arc<Mutex<HashMap<u32, Arc<Vec<u8>>>>>,
|
|
stream_limit: usize,
|
|
overlay: BTreeMap<u32, Slot>,
|
|
next_num: u32,
|
|
/// Position of the `%PDF-` header (some files have junk before it).
|
|
header_offset: usize,
|
|
version: String,
|
|
/// The authenticated security handler of an encrypted document.
|
|
security: Option<Arc<pdfcraft_crypt::SecurityHandler>>,
|
|
/// Object number of the `/Encrypt` dictionary (never encrypted itself).
|
|
encrypt_num: Option<u32>,
|
|
/// Encryption was added, changed or removed since opening: only a full save can apply it.
|
|
encryption_changed: bool,
|
|
/// Set by edits that must not leave earlier revisions in the file (redaction).
|
|
full_save: bool,
|
|
/// The handler and `/Encrypt` object number that saves use, when encryption changed.
|
|
out_security: Option<Arc<pdfcraft_crypt::SecurityHandler>>,
|
|
out_encrypt_num: Option<u32>,
|
|
}
|
|
|
|
impl std::fmt::Debug for Document {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Document").field("bytes", &self.data.len()).field("xref", &self.entries).field("edits", &self.overlay.len()).finish()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|