mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
1 parent
3cd619e937
commit
d258764abc
5 files changed
+62
-3
No files matched your search
@@ -1328,13 +1328,21 @@ impl Automation {
|
||||
let dir = self.resolve(a.str("out_dir")?, true)?;
|
||||
std::fs::create_dir_all(&dir).map_err(|e| failed(format!("{}: {e}", dir.display())))?;
|
||||
let mut files = Vec::new();
|
||||
let mut used = std::collections::HashSet::new();
|
||||
for (i, (first, last, bytes)) in parts.iter().enumerate() {
|
||||
let safe = |t: &str| t.chars().map(|c| if c.is_alphanumeric() || " -_.,()".contains(c) { c } else { '_' }).collect::<String>();
|
||||
let file = match titles.iter().find(|t| t.0 + 1 == *first) {
|
||||
Some((_, t)) => format!("{stem}-{}.pdf", safe(t)),
|
||||
None => format!("{stem}-part{}.pdf", i + 1),
|
||||
};
|
||||
let path = child(&dir, &file);
|
||||
// Equal titles (or titles that sanitize alike) get -2, -3, ... so no part overwrites another; compared case-insensitively.
|
||||
let mut unique = file.clone();
|
||||
let mut n = 1;
|
||||
while !used.insert(unique.to_lowercase()) {
|
||||
n += 1;
|
||||
unique = format!("{}-{n}.pdf", file.trim_end_matches(".pdf"));
|
||||
}
|
||||
let path = child(&dir, &unique);
|
||||
write_atomic(&path, bytes)?;
|
||||
files.push(json!({ "path": path.to_string_lossy(), "first_page": first, "last_page": last }));
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
}),
|
||||
&["paths"],
|
||||
)),
|
||||
t("doc_split", "Split a document", "Split into several files written to out_dir: every N pages, before given pages, at top-level bookmarks (bookmarks: true; files named after them), or by file size (max_mb). Files are <name>-partK.pdf.")
|
||||
t("doc_split", "Split a document", "Split into several files written to out_dir: every N pages, before given pages, at top-level bookmarks (bookmarks: true; files named after them, repeated titles get -2, -3), or by file size (max_mb). Files are <name>-partK.pdf.")
|
||||
.cmd("page.split")
|
||||
.with(schema(
|
||||
json!({ "doc": doc(), "every": { "type": "integer", "minimum": 1 }, "before": pages("that start a new part"), "bookmarks": { "type": "boolean" }, "max_mb": { "type": "number", "exclusiveMinimum": 0 }, "out_dir": { "type": "string" } }),
|
||||
|
||||
@@ -2334,6 +2334,27 @@ fn stamps_through_tools() {
|
||||
assert!(matches!(a.call("stamp_custom", &json!({ "doc": doc, "page": 1, "path": "nope.png", "at": [1, 1] })), Err(ToolError::Failed(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bookmark_split_with_equal_titles_keeps_every_part() {
|
||||
let dir = workdir("organize_dup_titles");
|
||||
let mut a = auto(&dir);
|
||||
let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap();
|
||||
ok(&mut a, "bookmark_add", json!({ "doc": doc, "title": "Same", "page": 1 }));
|
||||
ok(&mut a, "bookmark_add", json!({ "doc": doc, "title": "Same", "page": 2 }));
|
||||
ok(&mut a, "bookmark_add", json!({ "doc": doc, "title": "same", "page": 3 }));
|
||||
let s = ok(&mut a, "doc_split", json!({ "doc": doc, "bookmarks": true, "out_dir": "parts" }));
|
||||
let files: Vec<String> = s["files"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|f| std::path::Path::new(f["path"].as_str().unwrap()).file_name().unwrap().to_string_lossy().into_owned())
|
||||
.collect();
|
||||
assert_eq!(files, ["a-Same.pdf", "a-Same-2.pdf", "a-same-3.pdf"]);
|
||||
for f in &files {
|
||||
assert!(dir.join("parts").join(f).exists(), "{f} missing");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn organizing_with_filters_bookmark_splits_and_extract_options() {
|
||||
let dir = workdir("organize2");
|
||||
|
||||
@@ -597,6 +597,7 @@ impl PdfCraftApp {
|
||||
}
|
||||
};
|
||||
let safe = |t: &str| t.chars().map(|c| if c.is_alphanumeric() || " -_.,()".contains(c) { c } else { '_' }).collect::<String>();
|
||||
let mut used = std::collections::HashSet::new();
|
||||
let named: Vec<(String, Arc<Vec<u8>>)> = parts
|
||||
.into_iter()
|
||||
.map(|(a, b, bytes)| {
|
||||
@@ -607,7 +608,14 @@ impl PdfCraftApp {
|
||||
None if a == b => format!("{stem} (page {a}).pdf"),
|
||||
None => format!("{stem} (pages {a}-{b}).pdf"),
|
||||
};
|
||||
(name, bytes)
|
||||
// Equal titles (or titles that sanitize alike) get (2), (3), ... so no part overwrites another; compared case-insensitively.
|
||||
let mut unique = name.clone();
|
||||
let mut n = 1;
|
||||
while !used.insert(unique.to_lowercase()) {
|
||||
n += 1;
|
||||
unique = format!("{} ({n}).pdf", name.trim_end_matches(".pdf"));
|
||||
}
|
||||
(unique, bytes)
|
||||
})
|
||||
.collect();
|
||||
self.write_files(&named, "Choose a folder for the split files");
|
||||
|
||||
@@ -1380,6 +1380,28 @@ fn split_dialog_writes_one_file_per_part() {
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_at_bookmarks_with_equal_titles_keeps_every_part() {
|
||||
let dir = temp_path("split-dup-titles");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut h = organize(3);
|
||||
h.state_mut().export_dir_override = Some(dir.to_string_lossy().into_owned());
|
||||
for (index, title) in [(0, "Same"), (1, "Same"), (2, "same")] {
|
||||
h.state_mut().apply_edit(pdfcraft_engine::Edit::AddBookmark { parent: vec![], index, title: title.into(), page: index });
|
||||
}
|
||||
h.state_mut().split_draft.mode = pdfcraft_ui_egui::SplitMode::Bookmarks;
|
||||
h.state_mut().run_command("page.split");
|
||||
h.run_steps(3);
|
||||
h.get_by_label_contains("Creates 3 files from 3 pages");
|
||||
h.get_by_label("Split").click();
|
||||
h.run_steps(3);
|
||||
let mut names: Vec<String> = std::fs::read_dir(&dir).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().into_owned()).collect();
|
||||
names.sort();
|
||||
assert_eq!(names, ["doc - Same (2).pdf", "doc - Same.pdf", "doc - same (3).pdf"]);
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_before_selected_pages() {
|
||||
let dir = temp_path("split-sel");
|
||||
|
||||
Reference in new issue
Block a user