pdfrum-edit 0.1.0

PDF serializer: full/incremental save, page import, subsetting
//! Turning a page-object graph back into content-stream operators
//! (ISO 32000-1 §8, §9).
//!
//! A page whose objects were modified is rewritten from the graph rather than
//! patched, so these bytes *are* the round trip: any divergence here shows up
//! as pixels.
//!
//! # This is lossy, and the losses are limits rather than a requirement
//!
//! Each thing the emitter drops is something it cannot yet express. Nothing
//! requires it to drop them: no comparison anywhere scores a regenerated page
//! against another producer's regenerated page, so emitting *more* than some
//! other writer does costs nothing.
//!
//! The losses, in full:
//!
//! - **Colour.** Only `rg` and `RG` are ever written, but **every colour space
//!   converts to them** — see `emit::expressible_rgb`, which carries the
//!   `[oracle-bug]` citation. Only a **pattern** emits nothing and inherits the
//!   black the per-stream prologue set, because a pattern paints through a
//!   resource no `rg` can name.
//! - **Shadings.** A shading page object emits nothing at all.
//! - **Text.** Only `Tm`, `Tf`, `Tr` and `TJ`. All positioning collapses into
//!   `Tm`; `Td`, `TD`, `T*`, `Tj`, `'`, `"`, `Tc`, `Tw`, `Tz`, `TL` and `Ts`
//!   are never emitted, so character and word spacing are lost. A Type 3 font
//!   drops its whole text object.
//! - **Graphics state.** Only `ca`, `CA` and `BM` reach an `/ExtGState`. The
//!   miter limit and soft masks do not.
//! - **Clips.** Path clips only: text clips, clip-path soft masks and shading
//!   clips are never written.
//!
//! # No state diffing, ever
//!
//! Each object is wrapped in its own `q`/`Q` and states everything it needs
//! from scratch, comparing each value against the *hardcoded PDF default* —
//! never against the object emitted before it. That makes the output longer
//! than a hand-written stream and makes every object independently
//! relocatable, which is what lets objects from several source streams
//! interleave into new ones without a fixup pass.
//!
//! # An unsupported object contributes nothing
//!
//! Two C++ paths write a prefix and then bail, leaving a `q ` — and for text
//! a `BT ` — unclosed. We build each object's bytes into a scratch buffer and
//! commit them only on success, so an object we cannot express contributes
//! *nothing* rather than an unbalanced fragment. The rendered result is the
//! same for well-formed input, because the stream-level `Q` closes the C++'s
//! stray `q` anyway; the difference is that our output stays parseable.

// `pdfium_test` has no save flag, so there is no such thing as the oracle's
// regenerated page (`conformance/src/saveroundtrip.rs:6-8`). Tier-B compares
// our render against a golden of the *original*
// (`conformance/src/run.rs:339-361`). The one sweep that does regenerate
// compares two renders of the **same** regenerated bytes, so a content loss
// dropped on both sides cannot score either way
// (`conformance/src/mutation.rs:12-17`, `:41-44`) — and it is a separate
// command, not on the board.

mod apply;
mod emit;
mod marks;
pub(crate) mod num;
mod path;
mod regen;
mod resource;
mod text;

// What the crate root re-exports. Everything else this module holds is
// `pub(crate)`: the emitter's internals are the writer's, not a caller's.
pub use apply::{ShareCounts, apply_rewrite, shared_objects};
pub use num::{write_float, write_matrix, write_point, write_rect};
pub use regen::{ContentsShape, PageRewrite, Regenerated, regenerate};
pub use resource::ResourceTable;