mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
M4: Combine files in its own tab, with a sortable table and unlocking of protected files (#321)
* M4: Combine files in its own tab, with a sortable table and unlocking of protected files Combine files was a modal dialog with a cramped list and up/down buttons on every row. It is now a tab next to Home and the documents, showing a table of the files: File name | Pages | Size | Modified | Warnings. - Columns sort when their heading is clicked (anywhere in the cell; again to reverse; names in natural order), resize from their edges and move by dragging the heading. Order and widths are kept in the settings; a right-click on a heading resets them. - Rows select like a file manager (click, Ctrl/Cmd+click, Shift+click, Shift+arrows, Ctrl/Cmd+A). One toolbar moves, removes and unlocks the selection; Alt+arrows and dragging move it too. Every change to the list undoes and redoes with Cmd/Ctrl+Z (also toolbar buttons). - Add files, Add folder, Add folder and subfolders (a split button), Add open documents, or drop files and folders on the tab. - Problems show before Combine: password-protected files, security settings that withhold page assembly, page ranges (checked as typed, with the pages they take), duplicates. Files that open but are protected say that the combined file won't keep their security; the result is never encrypted. - Unlock... asks for a file's open or permissions password (one password can be tried on several files). Passwords stay in memory, are never printed, and reach the engine through combine_unlocked; doc_combine takes them as `passwords`. The engine decides whether a password works, since the inspector rejects some it accepts (RC4 owner passwords). organize.combine-sort is shipped and organize.combine-options partial (include open files). Strings are translated for es, fr, ja, zh-hans, zh-hant. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Credit Marlon Konrad (mlkonrad) in the About window Adds my own entry to contributors/people.toml, as docs/contributors.md asks. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
24 files changed
+2500
-151
No files matched your search
@@ -631,6 +631,38 @@ fn permissions_password_restricts_others_but_not_this_session() {
|
||||
assert!(s2.get(id3).unwrap().allows_security_change());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combine_source_check_says_why_a_file_cannot_be_combined() {
|
||||
let (mut s, id) = session_with(1);
|
||||
let plain = s.get(id).unwrap().bytes.clone();
|
||||
assert_eq!(crate::combine_source_check(&plain, None), Ok(()));
|
||||
// Page assembly withheld (Changes::None) without a password to open it.
|
||||
let p = Protection { changes: Changes::None, ..protection(None, Some("boss")) };
|
||||
s.apply(id, Edit::Protect(p)).unwrap();
|
||||
let locked = s.save_bytes(id).unwrap();
|
||||
assert_eq!(crate::combine_source_check(&locked, None), Err(crate::SourceProblem::NotPermitted));
|
||||
let (mut s, id) = session_with(1);
|
||||
s.apply(id, Edit::Protect(protection(Some("pw"), Some("owner")))).unwrap();
|
||||
let secret = s.save_bytes(id).unwrap();
|
||||
assert_eq!(crate::combine_source_check(&secret, None), Err(crate::SourceProblem::Password));
|
||||
assert_eq!(crate::combine_source_check(&secret, Some("nope")), Err(crate::SourceProblem::WrongPassword));
|
||||
// Its open password reads it, but its permissions withhold assembling pages: the owner's
|
||||
// password allows it.
|
||||
assert_eq!(crate::combine_source_check(&secret, Some("pw")), Err(crate::SourceProblem::NotPermitted));
|
||||
assert_eq!(crate::combine_source_check(&secret, Some("owner")), Ok(()));
|
||||
// The permissions password lifts the restriction on copying pages.
|
||||
assert_eq!(crate::combine_source_check(&locked, Some("boss")), Ok(()));
|
||||
// Combining with the passwords: the result opens without any.
|
||||
let s = Session::new();
|
||||
let sources = vec![("secret".to_string(), secret, None), ("locked".to_string(), locked, None)];
|
||||
assert!(s.combine_ranges(&sources).is_err(), "no passwords, no combining");
|
||||
let out = s.combine_unlocked(&sources, &[Some("owner"), Some("boss")]).unwrap();
|
||||
let combined = pdfcraft_cos::Document::open(out).unwrap();
|
||||
assert!(combined.permissions().is_none(), "the combined file is not encrypted");
|
||||
assert_eq!(pdfcraft_organize::page_count(&combined).unwrap(), 2);
|
||||
assert!(matches!(crate::combine_source_check(&Arc::new(b"not a pdf".to_vec()), None), Err(crate::SourceProblem::Unreadable(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protection_is_validated_undoable_and_never_logged() {
|
||||
let (mut s, id) = session_with(1);
|
||||
|
||||
Reference in new issue
Block a user