mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 22:19:10 -06:00
* M5: Import comments takes another PDF and copies its comments onto the same pages Import comments read only XFDF, FDF, XML, XDP, CSV and text data files. It now also takes a PDF: each comment of that PDF is copied onto the page with the same number, at the same place, with its appearance stream, its pop-up, its replies and its layer. Comments on pages past the document's last page are left out, together with replies to them. Links, form fields and redaction marks are not copied. A comment named like one already on its page replaces it, as an XFDF or FDF import does. A PDF without comments is refused with "nothing in the file matches this document", and a PDF that needs a password is refused. - organize: import_comments copies the comments with the page copier that Insert Pages uses, and registers the layers they use. - engine: Edit::ImportData reads a file that starts with %PDF- as a PDF. Any other file goes to the data importer first, and is read as a PDF when that fails, %PDF- occurs in its first 1,024 bytes and the PDF reader opens it. - ui: Comments > Import comments lists .pdf files; form.import_data lists the same files as before. - automation: the doc_import_data description names the PDF case. - parity: comment.import-from-pdf is shipped. * Import comments: an annotation that is no comment keeps its name A comment imported from a PDF replaced any annotation of the same /NM on its page that was not locked, a redaction mark or a widget, so a link, screen or 3D annotation of that name was overwritten. Only an annotation whose subtype is one of the copied comment subtypes is replaced now; for any other, the copy is added beside it without the name. The subtype is read through an indirect reference. * Import comments: index each page's annotations instead of scanning them per comment * Import comments: a layer that BaseState OFF hides stays hidden * Import comments: a PDF with binary bytes before its header imports on an XFA form, and two comments with one name both land On an XFA form, a decode failure no longer stops the import when a PDF header is in the first 1,024 bytes; the PDF's comments are copied. Two incoming comments on one page with the same /NM no longer replace the same comment: the first replaces it, and the second is added without the name. * Import comments: an inline annotation on the page is replaced by name An annotation written inline in a page's /Annots is made an indirect object before names are matched, so an incoming comment with its /NM replaces it instead of being added beside it. * Import comments: two new comments with one name keep it once When no comment on the page has the name, the first incoming comment keeps its /NM and each later one with the same name is added without it.
70 lines
3.5 KiB
Rust
70 lines
3.5 KiB
Rust
//! Other files to combine with or copy pages from: opening them, and why one can't be used.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use crate::{EditError, SourceProblem};
|
|
|
|
/// A file to combine: its name (the bookmark title), bytes, and page range (`None`: all).
|
|
pub type CombineSource = (String, Arc<Vec<u8>>, Option<String>);
|
|
|
|
/// What a combine produced: the new PDF bytes, and the fields renamed to keep colliding names
|
|
/// separate (old fully-qualified name → new one), in order across the sources.
|
|
#[derive(Debug)]
|
|
pub struct Combined {
|
|
pub bytes: Arc<Vec<u8>>,
|
|
pub renamed_fields: Vec<(String, String)>,
|
|
}
|
|
|
|
/// Parse another PDF to copy pages from.
|
|
pub(crate) fn open_source(name: &str, bytes: &Arc<Vec<u8>>) -> Result<pdfcraft_cos::Document, EditError> {
|
|
open_source_with(name, bytes, None)
|
|
}
|
|
|
|
/// [`open_source`], authenticating with `password` (user or owner) for an encrypted file.
|
|
pub(crate) fn open_source_with(name: &str, bytes: &Arc<Vec<u8>>, password: Option<&str>) -> Result<pdfcraft_cos::Document, EditError> {
|
|
source_document(bytes, password).map_err(|p| EditError::Source(format!("{name}: {p}")))
|
|
}
|
|
|
|
/// Import the comments of `src` onto the pages of `doc` with the same numbers.
|
|
pub(crate) fn import_pdf_comments(doc: &mut pdfcraft_cos::Document, src: &pdfcraft_cos::Document) -> Result<(), EditError> {
|
|
if pdfcraft_organize::import_comments(doc, src)? == 0 {
|
|
return Err(pdfcraft_xfdf::DataError::NothingImported.into());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Parse another PDF to import comments from, whatever its permissions allow.
|
|
pub(crate) fn comment_source(bytes: &Arc<Vec<u8>>) -> Result<pdfcraft_cos::Document, SourceProblem> {
|
|
match std::panic::catch_unwind(|| pdfcraft_cos::Document::open(bytes.clone())) {
|
|
Ok(Ok(d)) => Ok(d),
|
|
Ok(Err(pdfcraft_cos::CosError::NeedsPassword)) => Err(SourceProblem::Password),
|
|
Ok(Err(e)) => Err(SourceProblem::Unreadable(e.to_string())),
|
|
Err(_) => Err(SourceProblem::Unreadable("the file could not be read".into())),
|
|
}
|
|
}
|
|
|
|
fn source_document(bytes: &Arc<Vec<u8>>, password: Option<&str>) -> Result<pdfcraft_cos::Document, SourceProblem> {
|
|
match std::panic::catch_unwind(|| pdfcraft_cos::Document::open_with_password(bytes.clone(), password)) {
|
|
Ok(Ok(d)) if d.permissions().is_some_and(|p| !p.assemble()) => Err(SourceProblem::NotPermitted),
|
|
Ok(Ok(d)) => Ok(d),
|
|
Ok(Err(pdfcraft_cos::CosError::NeedsPassword)) => Err(SourceProblem::Password),
|
|
Ok(Err(pdfcraft_cos::CosError::WrongPassword)) => Err(SourceProblem::WrongPassword),
|
|
Ok(Err(e)) => Err(SourceProblem::Unreadable(e.to_string())),
|
|
Err(_) => Err(SourceProblem::Unreadable("the file could not be read".into())),
|
|
}
|
|
}
|
|
|
|
/// Whether `bytes` can be combined, opened with `password` (user or owner) if given, and if not
|
|
/// why: what Combine would refuse it for, so the Combine files list can say so before Combine
|
|
/// is pressed. The owner (permissions) password lifts the restriction on copying pages.
|
|
pub fn combine_source_check(bytes: &Arc<Vec<u8>>, password: Option<&str>) -> Result<(), SourceProblem> {
|
|
source_document(bytes, password).map(|_| ())
|
|
}
|
|
|
|
/// The number of pages of `bytes`, opened with `password` (user or owner) if given, whatever
|
|
/// its permissions allow; `None` when it can't be read.
|
|
pub fn source_page_count(bytes: &Arc<Vec<u8>>, password: Option<&str>) -> Option<usize> {
|
|
let doc = std::panic::catch_unwind(|| pdfcraft_cos::Document::open_with_password(bytes.clone(), password)).ok()?.ok()?;
|
|
pdfcraft_organize::page_count(&doc).ok()
|
|
}
|