Files
pdfcraft/crates/engine/src/doc_state.rs
T

119 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::{Arc, EditError, Hidden, SignatureInfo, TrustStore, optimize};
/// A change to an existing page image.
#[derive(Clone, Debug, PartialEq)]
pub enum ImageEdit {
/// Move and resize it to this box (user space).
Move([f64; 4]),
/// Quarter turns clockwise about its centre.
Rotate(i32),
/// Any angle in degrees clockwise about its centre (the unit is degrees, not quarter turns).
RotateBy(f64),
Flip {
horizontal: bool,
},
/// Show only this box of it (user space), the pixels staying where they are.
Crop([f64; 4]),
/// Draw this image file (PNG, JPEG, TIFF, GIF, BMP, JPEG 2000) in its place.
Replace {
name: String,
bytes: Arc<Vec<u8>>,
},
Delete,
}
/// Forms ▸ Merge data files into spreadsheet: the field values of each file (FDF, XFDF or a
/// filled-in PDF form), as CSV with one row per file.
pub fn merge_data_files(files: &[(String, Vec<u8>)]) -> Result<String, String> {
let rows =
files.iter().map(|(name, bytes)| pdfcraft_xfdf::data_values(bytes).map_err(|e| format!("{name}: {e}"))).collect::<Result<Vec<_>, _>>()?;
Ok(pdfcraft_xfdf::merge_csv(&rows))
}
/// One file produced by a split: (1-based first page, last page, PDF bytes).
pub type SplitPart = (usize, usize, Arc<Vec<u8>>);
/// Stable identifier of an open document within a session.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DocId(pub u64);
/// The highest resolution a document that allows only low-resolution printing (Table 22, bit 12
/// clear) is printed at.
pub(crate) const LOW_RESOLUTION_PRINT_DPI: f64 = 150.0;
pub(crate) fn render_threads() -> usize {
std::thread::available_parallelism().map(std::num::NonZeroUsize::get).unwrap_or(4).clamp(2, 8) - 1
}
/// A document's working file captured for crash recovery.
#[derive(Clone, Debug)]
pub struct RecoverySnapshot {
pub doc: DocId,
pub name: String,
pub path: Option<String>,
pub bytes: Arc<Vec<u8>>,
/// The snapshot is encrypted (recovering it asks for the password again).
pub encrypted: bool,
}
/// The recovered user password of an R2–R4 file as text for the renderer, which tries a password
/// as its UTF-8 bytes and then in PDFDocEncoding: UTF-8 bytes (as some writers store passwords
/// PDFDocEncoding can't hold, such as "şifre") stay UTF-8, and other bytes are read as
/// PDFDocEncoding, so either way the renderer gets back exactly these bytes.
pub(crate) fn renderer_password(bytes: &[u8]) -> String {
match std::str::from_utf8(bytes) {
Ok(text) => text.to_owned(),
Err(_) => bytes.iter().map(|b| pdfcraft_cos::pdfdoc_char(*b)).collect(),
}
}
/// The document as it may be written to a file (#3926, #3944): a copy without the values of text
/// fields with the Password flag (or XFA `<passwordEdit/>` fields in the datasets), or `cos`
/// itself when it holds none. ISO 32000-2 §12.7.5.3, Table 231 (a note to the Password flag):
/// "it is imperative that PDF processors never store the value of the text field in the PDF file
/// if this flag is set". The open document keeps them in memory.
pub(crate) fn writable(cos: &pdfcraft_cos::Document) -> Result<std::borrow::Cow<'_, pdfcraft_cos::Document>, EditError> {
let has_form_pw = pdfcraft_forms::has_password_values(cos);
let has_xfa_pw = pdfcraft_xfa::has_password_values(cos);
if !has_form_pw && !has_xfa_pw {
return Ok(std::borrow::Cow::Borrowed(cos));
}
let mut copy = cos.clone();
if has_form_pw {
pdfcraft_forms::clear_password_values(&mut copy).map_err(|e| EditError::Write(e.to_string()))?;
}
if has_xfa_pw {
pdfcraft_xfa::clear_password_values(&mut copy).map_err(|e| EditError::Write(e.to_string()))?;
}
Ok(std::borrow::Cow::Owned(copy))
}
/// The last-resort guard (AGENTS.md §4): run `f`, turning a panic that escapes it into an error
/// message, so one bad file or edit can't take the app and its other documents down. It is a
/// safety net for bugs, not a substitute for returning errors.
pub fn guard<T>(f: impl FnOnce() -> T) -> Result<T, String> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_err(|p| {
p.downcast_ref::<&str>().map(|s| (*s).to_string()).or_else(|| p.downcast_ref::<String>().cloned()).unwrap_or_else(|| "unknown error".into())
})
}
/// Validate the signature fields of `cos` (written as `bytes`).
pub(crate) fn signatures_of(
cos: &pdfcraft_cos::Document,
bytes: &[u8],
trust: &TrustStore,
cache: &pdfcraft_sign::DigestCache,
) -> Vec<SignatureInfo> {
pdfcraft_sign::pdf::list_cached(cos, bytes, trust, cache)
}
/// What Optimize PDF did.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OptimizeReport {
pub optimize: optimize::Report,
/// Identical objects merged.
pub merged: usize,
/// Remove Hidden Information categories discarded, with counts.
pub discarded: Vec<(Hidden, usize)>,
}