mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-28 20:18:06 -06:00
119 lines
4.9 KiB
Rust
119 lines
4.9 KiB
Rust
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)>,
|
||
}
|