Files
pdfcraft/crates/edit/src/lib.rs
T
d3384340a6 M7: make page-mark replacement transactional (#5657) (#5704)
* M7: make page-mark replacement transactional (#5657)

* ROADMAP: drop the header line (the log entry stays)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:49:18 -05:00

87 lines
3.9 KiB
Rust

//! pdfcraft-edit — page content editing (L4). Today: page marks, Acrobat's Header & footer,
//! Watermark, Background and Bates numbering (execution plan M7.6).
//!
//! **Atomic page-mark operations.** Adding or replacing headers/footers, watermarks and backgrounds
//! stages the whole call on a document snapshot and commits only after every selected page succeeds.
//! An error preserves existing marks, content, resources, pending edits and allocated objects. The
//! snapshot shares original PDF bytes and unchanged objects; it copies the overlay table and metadata.
//!
//! **How marks are stored.** Each mark is one content stream appended to (or, for content behind
//! the page, inserted after the backgrounds at the start of) the page's `/Contents`, drawn in
//! display space (after `/Rotate`) and wrapped in a marked-content artifact (§14.8.2.2) so
//! readers and screen readers skip it:
//! `/Artifact <</Type /Pagination /Subtype /Header /PCMark /HeaderFooter>> BDC … EMC`. The
//! `/PCMark` entry (also in the stream dictionary) names the kind of mark, which is how Update and
//! Remove find them again without decompressing page content. When
//! anything is drawn on top, the page's original content is wrapped in `q … Q` (two tiny streams)
//! so its graphics state cannot reach the mark. The closing stream also ends known unfinished
//! marked-content sequences before the new mark.
//!
//! Text uses standard Helvetica (WinAnsi) added to the page resources as `/PCHelv`; headers and
//! footers may use standard Times (`/PCTimes`) or Courier (`/PCCour`) instead (#3754).
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
use pdfcraft_cos::{Dict, Document, ObjRef, Object, Stream};
use pdfcraft_fonts::{helvetica_width, literal, win_ansi};
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum EditError {
/// Holds the 0-based index; the message names the 1-based page a user counts (#1550).
#[error("page {} does not exist", .0.saturating_add(1))]
NoSuchPage(usize),
#[error("{0}")]
Invalid(String),
#[error("{0}")]
Cos(#[from] pdfcraft_cos::CosError),
}
pub type Rgb = [f64; 3];
pub mod backdrop;
pub use backdrop::{Backdrop, backdrops_left_off, page_backdrop, page_backdrops};
pub use text::strip_subset_tag;
mod background;
pub use background::page_background;
pub mod objects;
pub use objects::{EditableObject, MAX_MOVE_OBJECTS, ObjectKind, ObjectTarget, editable_objects, move_objects};
mod flatten;
pub use flatten::{flatten, flatten_fill_sign};
pub mod images;
pub use images::{ArrangeOrder, ImageChange, PageImage, change_image, page_images, reading_images, rect_to_rect, turn_about_centre, turn_by_degrees};
pub mod bidi;
pub mod indic;
mod linebreak;
pub mod paragraph_align;
pub mod text;
pub use text::{
BlockKey, BlockStyle, LineEdit, MAX_FORM_DEPTH, MAX_FORM_VISITS, RangeStyle, ReadingBlocks, TextBlock, TextLine, TextSession, block_overlapping,
clear_block, glyph_text_lines, reading_blocks, reading_blocks_checked, replace_block, replace_line, rewrite_block, text_blocks, text_lines,
};
pub mod added;
pub use added::{
Added, AddedImage, AddedText, Align, Content, Family, MarkerScan, add_content, delete_content, first_undrawable, isolate_content, list_added,
scan_markers, update_content,
};
mod apply;
mod marks;
mod place;
pub use apply::{add_background, add_header_footer, add_watermark, marks_present, remove_marks};
#[cfg(test)]
use marks::bates_start;
pub use marks::{Background, Context, HeaderFooter, MarkKind, MarkSource, Watermark, expand};
use marks::{matrix_number, n};
pub(crate) use place::undrawable;
#[cfg(test)]
use place::{ISOLATION_MAX_DEPTH, OLD_WRAP_CLOSE, WRAP_CLOSE, WRAP_OPEN, is_wrap_close, page_fonts, stream_bytes, stream_tag, tagged, wrapper_role};
use place::{check, check_on_top, contents, page_list, place_tagged};
pub use place::{stamp, undrawable_message};
#[cfg(test)]
mod marked_isolation_tests;
#[cfg(test)]
mod tests;