mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-12 08:52:40 +00:00
Conflicts, and how they were resolved: - cos writer.rs and lib.rs: main now numbers a full save in `full_save_order` and exports `reachable_objects`, the object numbers a full save writes, for the pattern guard. This branch had added the same walk as `walk` and `reachable`. Main's is kept, with its alias handling, and this branch's is dropped: `globals::kept` is now `reachable_objects`, so the JBIG2 cleanup and the pattern guard read one walk. `kept` returns the walk's error (a save that can't number its objects), which redaction and Remove Hidden Information pass on. The cos test of what is reachable, and two redaction tests, read `reachable_objects` instead. The exports keep this branch's `DECODE_PARMS_KEYS` and main's `TextChars`, `civil_from_days` and `new_file_id`. - redact lib.rs: main unlinks pruned patterns from `/Pages` nodes after `unlink_doomed`; this branch replaced `restore_shared_jbig2` with `globals::unshare`. Both stay, the unlink first: an image a pruned pattern's cell drew leaves the file with it, and is a user of a dictionary that left too. - redact sanitize.rs: main's `strip_outline_actions` and this branch's `unshare_unmarked` both stay. At the end of `remove_hidden`, `unshare_unmarked` runs after main's `unlink_undrawn`, so an image unlinked for being drawn nowhere takes a shared dictionary with it, and is followed by main's `require_full_save_with_new_id`. - redact image.rs: `MAX_PIXELS` is `pub(crate)` on both sides; the comment names `globals::without_dictionary`, where this branch moved `without_globals`. Tests: `an_undrawn_image_unlinked_takes_a_shared_jbig2_dictionary_too` (Remove Hidden Information, through `unlink_undrawn`) and `an_image_a_pruned_patterns_cell_drew_takes_a_shared_jbig2_dictionary_too` (a redaction, through `unlink_pruned_patterns`) pin the order of the two new routes before `unshare`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
58 lines
2.6 KiB
Rust
58 lines
2.6 KiB
Rust
//! pdfcraft-cos — the PDF object layer (L1, standalone with `pdfcraft-filters`).
|
||
//!
|
||
//! Parses the COS object graph lazily and tolerantly, keeps edits in a copy-on-write overlay
|
||
//! (cheap snapshots for undo), and writes documents back either incrementally (original bytes
|
||
//! untouched) or as a full, garbage-collected rewrite. The PDF object graph *is* PdfCraft's
|
||
//! document model (plan/architecture.md §5, ADR-0010).
|
||
//!
|
||
//! Done: every cross-reference form (tables, streams, hybrid `/XRefStm`), object streams, repair
|
||
//! by scanning, encryption on load and save (R2–R6, crypt filters, `/EFF`), incremental writing,
|
||
//! and full saves that pack objects into compressed object streams with a cross-reference stream.
|
||
//! Not yet: streaming very large files within a memory budget (`ByteSource`,
|
||
//! `core.lazy-loading`), and linearized output (`core.linearization`, M11). See the README.
|
||
|
||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||
|
||
mod budget;
|
||
mod bytes;
|
||
mod document;
|
||
mod object;
|
||
pub mod page_labels;
|
||
mod parser;
|
||
mod security;
|
||
mod writer;
|
||
|
||
pub use budget::{DecodeBudget, ImageBudget, MAX_PAGE_DECODED, MAX_PAGE_FORMS, MAX_REDACTED_IMAGE_PIXELS};
|
||
pub use bytes::Bytes;
|
||
pub use document::{DECODE_PARMS_KEYS, Document, ReadState, Revision, SourceIdentity, XrefEntry};
|
||
pub use object::{Dict, MAX_DECODED, MAX_JBIG2_GLOBALS, Name, ObjRef, Object, PdfString, Stream, TextChars};
|
||
pub use parser::{Lexer, is_whitespace, parse_indirect};
|
||
pub use pdfcraft_crypt::{
|
||
Algorithm, Auth, Method as CryptMethod, NewEncryption, Permissions, SecurityHandler, effective_password, pdfdoc_byte, pdfdoc_char,
|
||
};
|
||
pub use writer::{SaveOptions, civil_from_days, new_file_id, pdf_date, reachable_objects, serialize, write_full, write_incremental};
|
||
|
||
#[derive(Debug, thiserror::Error, Clone, PartialEq)]
|
||
pub enum CosError {
|
||
#[error("not a PDF file (no %PDF- header)")]
|
||
NotPdf,
|
||
#[error("syntax error at byte {offset}: {detail}")]
|
||
Syntax { offset: usize, detail: String },
|
||
#[error("object {0} is missing")]
|
||
MissingObject(u32),
|
||
#[error("object {0} is not a dictionary")]
|
||
NotADictionary(ObjRef),
|
||
#[error("stream data could not be decoded: {0}")]
|
||
Filter(String),
|
||
#[error("the document is protected by a password")]
|
||
NeedsPassword,
|
||
#[error("the password is incorrect")]
|
||
WrongPassword,
|
||
#[error("unsupported security: {0}")]
|
||
Security(String),
|
||
#[error("internal lock poisoned")]
|
||
Poisoned,
|
||
#[error("this document was opened read-only with a decode limit and cannot be saved")]
|
||
ReadOnlyLimit,
|
||
}
|