mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-13 01:41:18 +00:00
The engine caches a page's text blocks for opening a paragraph and applying its edit, but only while the document is unmodified: it was keyed by the source identity, which edits keep. After the first edit the cache never hit again, and the page was grouped again on every call (the inline editor and the panel each call it after an apply). pdfcraft_cos::Document::read_state() returns what the document reads as: its source identity, its trailer and every changed object, compared as allocations that the state keeps alive. A clone or a snapshot restored by undo has the same state; any set, add, free or trailer change makes a different one. The cache is keyed by it, so it hits after edits and never returns another state's blocks. On a page of 120 rows, text_blocks after an edit took 0.215 ms per call and now 0.006 ms (release build, best of three; a throwaway timing test). Tests: a read state matches a clone and not a document changed by set, free or a trailer change, nor another document; after an edit the engine groups the page once and undo and redo get their own blocks. The engine test fails without this change. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
56 lines
2.4 KiB
Rust
56 lines
2.4 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 bytes;
|
||
mod document;
|
||
mod object;
|
||
pub mod page_labels;
|
||
mod parser;
|
||
mod security;
|
||
mod writer;
|
||
|
||
pub use bytes::Bytes;
|
||
pub use document::{Document, ReadState, Revision, SourceIdentity, XrefEntry};
|
||
pub use object::{Dict, MAX_DECODED, Name, ObjRef, Object, PdfString, Stream};
|
||
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, pdf_date, 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,
|
||
}
|