Merge origin/main into merge/preflight-pdfa-checks

This commit is contained in:
bflatastic
2026-11-09 09:55:52 +07:00
124 changed files with 8603 additions and 626 deletions
+5 -2
View File
@@ -763,8 +763,9 @@ pub(crate) fn same_pages(a: &[pdfcraft_render::PageInfo], b: &[pdfcraft_render::
}
/// Each document-information field of `info` with its value in `cos` (`/Info`, else XMP), read
/// the way the full inspection reads them: NULs and spaces trimmed, empty is none.
pub(crate) fn info_fields<'a>(info: &'a mut DocInfo, cos: &pdfcraft_cos::Document) -> [(&'a mut Option<String>, Option<String>); 6] {
/// the way the full inspection reads them: NULs and spaces trimmed, empty is none, dates shown as
/// [`pdfcraft_render::pretty_date`] shows them.
pub(crate) fn info_fields<'a>(info: &'a mut DocInfo, cos: &pdfcraft_cos::Document) -> [(&'a mut Option<String>, Option<String>); 8] {
let read = |key| pdfcraft_organize::info(cos, key).map(|v| v.trim_matches('\0').trim().to_string()).filter(|v| !v.is_empty());
[
(&mut info.title, read("Title")),
@@ -773,6 +774,8 @@ pub(crate) fn info_fields<'a>(info: &'a mut DocInfo, cos: &pdfcraft_cos::Documen
(&mut info.keywords, read("Keywords")),
(&mut info.creator, read("Creator")),
(&mut info.producer, read("Producer")),
(&mut info.created, read("CreationDate").map(|d| pdfcraft_render::pretty_date(&d))),
(&mut info.modified, read("ModDate").map(|d| pdfcraft_render::pretty_date(&d))),
]
}
+1 -1
View File
@@ -117,7 +117,7 @@ pub use pdfcraft_fonts::{MAX_SIGNATURE_CHARS, ScriptOutline, script_missing, scr
/// Comment geometry helpers (text-box line breaking) for frontends.
pub use pdfcraft_annot::appearance as annot_text;
pub use pdfcraft_annot::links::{Highlight as LinkHighlight, LinkAction, LinkItem, LinkStyle};
pub use pdfcraft_annot::links::{Highlight as LinkHighlight, LinkAction, LinkItem, LinkStyle, LinkZoom};
pub use pdfcraft_annot::{
Activation3d, AppearanceCache, AttachIcon, BorderShape, FillMark, LineEnding, Markup, NOTE_SIZE, NewAnnotation, NoteIcon, OverlayFont,
OverlayLook, Props as CommentProps, ReviewState, Rgb, Shape, StampGroup, StampKind, Style, rect_quad,
+25 -2
View File
@@ -4887,6 +4887,25 @@ fn split_by_size_and_bookmarks_and_page_filters() {
assert_eq!(filter_pages(info, &all, PageParity::Odd, PageOrientation::Portrait), [0, 2, 4]);
}
/// A zoomed link follows its target page, not its number: deleting a page before the target keeps
/// the link on that page at its zoom. Deleting the target page drops the link's destination, as
/// for any link to a deleted page, and undo brings it back with its zoom.
#[test]
fn a_zoomed_link_follows_its_page_and_loses_it_when_the_page_is_deleted() {
let (mut s, id) = session_with(3);
let zoom = LinkZoom::Percent(200.0);
let rect = [10.0, 10.0, 60.0, 30.0];
s.apply(id, Edit::AddLink { page: 0, rect, action: LinkAction::Page { page: 2, zoom }, style: LinkStyle::default() }).unwrap();
let action = |s: &Session| s.get(id).unwrap().links()[0].action.clone();
s.apply(id, Edit::DeletePages { pages: vec![1] }).unwrap();
assert_eq!(action(&s), LinkAction::Page { page: 1, zoom }, "the link follows its page");
s.apply(id, Edit::DeletePages { pages: vec![1] }).unwrap();
// The destination is removed, not left pointing at a page that is gone.
assert_eq!(action(&s), LinkAction::Other("no action".into()), "no destination left");
s.undo(id).unwrap();
assert_eq!(action(&s), LinkAction::Page { page: 1, zoom }, "undo restores the destination and its zoom");
}
#[test]
fn links_from_urls_and_link_edits() {
// One page whose text contains a web address.
@@ -4928,8 +4947,12 @@ fn links_from_urls_and_link_edits() {
assert_eq!(s.get(id).unwrap().links().len(), 1);
// Link Properties and delete.
let l = s.get(id).unwrap().links()[0].clone();
s.apply(id, Edit::SetLink { page: 0, index: l.index, rect: None, action: Some(LinkAction::Page(0)), style: None }).unwrap();
assert_eq!(s.get(id).unwrap().links()[0].action, LinkAction::Page(0));
s.apply(
id,
Edit::SetLink { page: 0, index: l.index, rect: None, action: Some(LinkAction::Page { page: 0, zoom: LinkZoom::FitPage }), style: None },
)
.unwrap();
assert_eq!(s.get(id).unwrap().links()[0].action, LinkAction::Page { page: 0, zoom: LinkZoom::FitPage });
s.apply(id, Edit::DeleteLink { page: 0, index: l.index }).unwrap();
assert!(s.get(id).unwrap().links().is_empty());
assert!(s.apply(id, Edit::RemoveLinks { pages: None }).is_err(), "nothing left to remove");