M4, #569: Combine files takes images and text files (#1032)

* M10: refuse a PNG whose header claims more than 2 GiB of pixels before allocating

The png crate's limits don't cover the caller's frame buffer, which png_image sized from the
header alone: a few-KB PNG claiming 60000x60000 RGBA made Create allocate and fill 14.4 GB.
Apply the 2 GiB ceiling TIFF pages already have. Reached from Create, Insert pages and (with
#569) Combine files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M4, #569: Combine files takes images and text files

PNG, JPEG, TIFF, GIF, BMP, JPEG 2000 and .txt files become PDFs as they join the Combine files
list (Add files…, drops, folder scans), so each is an ordinary row or card with its pages,
thumbnails and page range. Session::as_pdf does the conversion; PDFs pass through unread
(Combine reads them itself, with their passwords), as does a file named .pdf without a header,
which the reader may still open. doc_combine accepts the same file types, and a converted
file's bookmark drops its extension.

A file that can't be read (a damaged image or PDF, a type that can't be converted) no longer
is refused at the door: it joins the list with why in its Warnings (a red card in the grid).
Combine then asks "Leave out files that can't be read?", naming them; Combine the rest goes on
without them, Cancel changes nothing. Password-protected files still block Combine until
unlocked.

The picker offers "PDF, images and text", and the empty state, folder tooltips and folder
notices say what is accepted; translated in every full catalog (catalog hashes updated).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
mlkonrad 2026-10-11 19:04:10 +03:00 committed by GitHub
parent ea1df6e1cb
commit dd065b39bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 479 additions and 148 deletions

View File

@ -19,7 +19,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "b98e2e4858e635dbc6930f2ff80e7e8a109d27abe996ac29c27b3b13b70e43f2"
sha256 = "737e9f61493dc977faf28df4666365c3f6b1764c416d39c40a6b17c021835dcb"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "b55ec8f2794966e879334121d48b15923cebf38192bd8e1667d4c4e6bf168095"
sha256 = "278c6ba856aba62c5a561c9f1b0cc44b2724c8357d91dcdf2d8a5164789ff7db"
[[asset]]
path = "crates/ui-egui/src/i18n/cs.tsv"
@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "96b0200a9b1f16cb23fb9482a8189b3fd763f31469009c54e59cb0aa1dd5d7b3"
sha256 = "115ec3d852731d72f38ab578f87c4720d3703aa4476333b7b712550e35e8010d"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "81ce4a9a18d10149129ecdf7eb0e0d3179cb4a66cf3f344a32de9a984612dafa"
sha256 = "86c0fe08c6b52b841147a8dffcb3e6c080f649557d979ae0f7a80e1f322ca511"
[[asset]]
path = "crates/ui-egui/src/i18n/fr.tsv"
@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "9b4eb2a8230e856a2fd2abd672fc954eaa02164dae7be6a4299451ecb7e732bd"
sha256 = "af81661d3c705efae149d77b5055980d37d5ee01c28bad41b27ef682b521a098"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "b08667b24b7bb8801e656516064125a29e47f2a88caadda4a81c362d680ed457"
sha256 = "f5e62db505ba7e974eacc64fa7ac2e0ba4955bfb466b4726cc3ba80ef94b1fb6"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "fd2b1b33d940ff9eef4bc067452252c39bf6cf02f3fe22e84ecd782943d2cc26"
sha256 = "361eff32e4e7ef312667c7865adb2af3f2f684ce2bbb481124ba78e8a1715f2f"
[[asset]]
path = "crates/ui-egui/src/i18n/it.tsv"
@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "a574d0ebee8184f917354377257d32312f711f4457e855b9d697d6af15ecc886"
sha256 = "82235760fda93084ce58d3b4da992b584080ccb871f4dc8eb5acbb4dfb2a3df3"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "95e35de200c66936333a95d89561f3c4d32b1d38f9c0a8357bec5c6b0e75fe1e"
sha256 = "5aa16fca81c03f0c7e03760c057a1658a7fdbcc58fb9216b8549aab0e428902a"
[[asset]]
path = "crates/ui-egui/src/i18n/ru.tsv"
@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d4d9a78c19215360e64d1fcf201415546cabc5f8937888f3f5c1616106b94838"
sha256 = "51441023d6960c92b799a80ff9c30acf63128e0061c945995548723802398068"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f5549263f28e22911e2dc8612d8f793112729cecb8d7d8ab6d0faaf3acd4c0b0"
sha256 = "a4e94a2d0deaf79923bf4165c31bc60ac308be5fd120c0132b10abf7fd5bf7c9"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c4c77b4451a0505bfe7aacea5a9ef3701a10e48060dcb98558716b28592d72a1"
sha256 = "13ff66c5924b9db68279f2f936d83802251ac5fc03ca11b5ce97eacfabffc5ae"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "28d98970e3b7c11b1468f2ceec182979160681ca2719ec47246a1655ab452054"
sha256 = "41db30fd76339586c5325036249733f159866ed1628410134249edeb4c3a2182"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "3b732bcfe593c4619909deab926a8aa30826e5effd957866315c7969338eca94"
sha256 = "9ce62e7d757116846bbacbb6977650de79c31603d30fe8568cb08c8a85a98e38"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "263567299cee16b9c2577b6de92e5d0f3bbb82ef334beebbd130c27407db40e2"
sha256 = "cf1d78342ac5a45e03114cf9b7abf65b419a94d977bba94077c3046f0a7d8493"
[[asset]]
path = "assets/icons/accessibility.svg"

View File

@ -137,6 +137,7 @@ Newest first: the date, what moved, and the new overall estimate.
- **2026-10-11 (M7, watermark placement):** A watermark can be moved from the page centre by a horizontal and a vertical offset in points: Add/Update Watermark has an "Offset from centre" row (the preview follows it, for text and for pictures) and `doc_watermark` takes `offset_x` and `offset_y`. A non-finite offset is refused instead of written into the page. `edit.watermark-appearance` stays partial: no offset from the page edge or in page units, no print/screen-only variants, no repeat. Image and PDF watermarks, already shipped, no longer appear as missing. The overall estimate is unchanged.
- **2026-10-11 (M12, XFA script time limits):** An XFA script's time limit (1 s for scripts that run unasked, on open and on every change) counted the engine's start-up too: the script's thread, a new context and, for the first script in a process, the engine's own set-up (≈ 60–170 ms when idle). On a busy machine a trivial initialize script could pass the limit, be abandoned and turn the form's scripts off; that made `xfa_buttons_run_their_scripts_in_the_app` and `messages_from_scripts_run_on_open_show_right_away` flaky (under full CPU load 25 of 40 runs of the test binary failed, with "ran longer than 1.0 s and was abandoned" in the console). The limit now runs from when the engine is ready; start-up has its own 10 s cap, so nothing waits forever, and a trivial warm-up script takes the first-in-process set-up. Under the same load none of 40 runs fails, and runaway scripts are abandoned as before. The two UI tests print the script console when they fail. Overall unchanged.
- **2026-10-11 (M4, #569):** Combine files takes images and text files as well as PDFs. PNG, JPEG, TIFF, GIF, BMP, JPEG 2000 and .txt files become PDFs as they are added (Add files…, drops, folder scans), so each is an ordinary row with its pages, thumbnails and page range; a file that can't be read (a damaged image or PDF, a type that can't be converted) stays in the list with why in its Warnings, and Combine asks before leaving such files out (Cancel changes nothing); password-protected files still have to be unlocked first. `doc_combine` accepts the same types, and a converted file's bookmark drops its extension. A PNG whose header claims more than 2 GiB of pixels is now refused before its buffer is allocated (the png crate's limits don't cover it), as TIFF already was. Office documents and HTML are still not converted. Overall estimate unchanged.
- **2026-10-10 (M14, interface size):** Preferences ▸ Interface size scales the whole interface (50–250 %, setting `ui_scale`, control option `ui-scale`); 50 % brings an interface the system draws at 200 % back to 100 % (#456). Auto, the default, follows the desktop's text scaling: on Linux and the BSDs the desktop app reads GNOME's `text-scaling-factor` once at launch, so with Large Text (1.25) on a 1× display the interface no longer draws 13 px labels that show their pixels next to the system's text. The size is egui's zoom factor, so `pixels_per_point` grows with it and page rasters stay one texel per screen pixel; egui's own ⌘+/⌘− interface zoom is turned off so those keys stay page zoom. Still missing: KDE's font DPI and Windows' "Make text bigger" aren't read (both scale only text there), and egui's text is still greyscale-antialiased without the system's gamma, so small text looks rougher than GTK's at 100 %.
- **2026-10-10 (M14, #496):** Shortcut displays share a UI formatter across toolbar search, registry menus, command palette, Help and tooltips. Modifier and key names use a dedicated catalog context in every bundled translation; missing entries keep the English key name rather than a command translation. German shows Strg/Umschalt/Entf, macOS retains its symbols, and runtime language changes refresh the display. Regression coverage checks localized layout widths, English/German switching, platform conventions and unchanged command execution. Milestone and effort estimates unchanged.

View File

@ -1354,7 +1354,10 @@ impl Automation {
if let Some(g) = group {
firsts.push((g, path.clone(), sources.len()));
}
Arc::new(std::fs::read(&path).map_err(|e| failed(format!("{}: {e}", path.display())))?)
let bytes = Arc::new(std::fs::read(&path).map_err(|e| failed(format!("{}: {e}", path.display())))?);
// Images and text files are made into PDFs first (#569), as in the Combine files tab.
let file_name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
self.session.as_pdf(&file_name, &bytes).map_err(failed)?
}
};
let name = path.file_stem().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();

View File

@ -205,7 +205,7 @@ pub fn tools() -> Vec<ToolDef> {
json!({ "doc": doc(), "pages": pages("to extract"), "out": save_out.clone(), "open": open.clone(), "separate": { "type": "boolean" }, "out_dir": { "type": "string" }, "delete": { "type": "boolean" } }),
&["doc", "pages"],
)),
t("doc_combine", "Combine files", "Combine PDFs, in order, into one (bookmarks are kept under one entry per file). pages optionally chooses each file's pages, in step with paths: a range such as \"1-3, 6\" or null for all pages. passwords, also in step with paths, opens encrypted files (the open password, or the permissions password where a file's security doesn't allow copying pages; null for none). The result is not encrypted. Passwords are never echoed back. groups, also in step with paths, splits a file around others: entries with the same number (they must name the same path) are one file, copied once, so it keeps one bookmark at its first page, its links and its form fields; null for a file of its own. A group uses its first entry's password.")
t("doc_combine", "Combine files", "Combine files, in order, into one PDF (bookmarks are kept under one entry per file). Images (PNG, JPEG, TIFF, GIF, BMP, JPEG 2000) and .txt files are made into PDFs first. pages optionally chooses each file's pages, in step with paths: a range such as \"1-3, 6\" or null for all pages. passwords, also in step with paths, opens encrypted files (the open password, or the permissions password where a file's security doesn't allow copying pages; null for none). The result is not encrypted. Passwords are never echoed back. groups, also in step with paths, splits a file around others: entries with the same number (they must name the same path) are one file, copied once, so it keeps one bookmark at its first page, its links and its form fields; null for a file of its own. A group uses its first entry's password.")
.cmd("page.combine")
.with(schema(
json!({

View File

@ -167,6 +167,36 @@ fn edit_undo_redo_and_save_round_trip() {
assert_eq!(after.windows(5).filter(|w| w == b"%%EOF").count(), 2);
}
/// #569: images and text files combine with PDFs, each made into a PDF first.
#[test]
fn combine_takes_images_and_text_files() {
let dir = workdir("combine-images");
let mut png = Vec::new();
{
let mut encoder = png::Encoder::new(&mut png, 4, 2);
encoder.set_color(png::ColorType::Rgb);
encoder.set_depth(png::BitDepth::Eight);
encoder.write_header().unwrap().write_image_data(&[90; 24]).unwrap();
}
std::fs::write(dir.join("scan.png"), png).unwrap();
std::fs::write(dir.join("notes.txt"), "Some notes").unwrap();
std::fs::write(dir.join("broken.png"), b"\x89PNG\r\n\x1a\nbroken").unwrap();
std::fs::write(dir.join("report.docx"), b"PK\x03\x04").unwrap();
let mut a = auto(&dir);
let combined = ok(&mut a, "doc_combine", json!({ "paths": ["a.pdf", "scan.png", "notes.txt"], "pages": [null, null, "1"], "open": true }));
let doc = combined["document"]["doc"].as_u64().unwrap();
assert_eq!(combined["document"]["pages"], 5);
assert_eq!(page_text(&mut a, doc), ["Page 1", "Page 2", "Page 3", "", "Some notes"]);
let marks = ok(&mut a, "bookmark_list", json!({ "doc": doc }))["bookmarks"].clone();
let titles: Vec<&str> = marks.as_array().unwrap().iter().map(|b| b["title"].as_str().unwrap()).collect();
assert_eq!(titles, ["a", "scan", "notes"]);
// A file that can't be made into a PDF stops the combine, and says which.
for bad in ["broken.png", "report.docx"] {
let refused = a.call("doc_combine", &json!({ "paths": ["a.pdf", bad] }));
assert!(matches!(&refused, Err(ToolError::Failed(e)) if e.contains(bad)), "{bad}: {refused:?}");
}
}
#[test]
fn combine_extract_and_split() {
let dir = workdir("organize");

View File

@ -314,7 +314,10 @@ fn png_image(name: &str, bytes: &[u8]) -> Result<Embedded, CreateError> {
_ => (72.0, 72.0),
};
let icc = reader.info().icc_profile.as_ref().map(|p| p.to_vec());
let mut buf = vec![0; reader.output_buffer_size().ok_or_else(|| bad("image too large".into()))?];
// The png crate's limits don't cover the frame buffer, sized by the header alone: a small
// file claiming enormous dimensions must not make it allocate (and fill) gigabytes.
let size = reader.output_buffer_size().filter(|n| *n <= MAX_DECODED_BYTES).ok_or_else(|| bad("image too large".into()))?;
let mut buf = vec![0; size];
let frame = reader.next_frame(&mut buf).map_err(|e| bad(e.to_string()))?;
let (w, h) = (frame.width, frame.height);
let data = &buf[..frame.buffer_size()];
@ -385,8 +388,9 @@ fn decoded(name: &str, bytes: &[u8], format: image::ImageFormat) -> Result<Embed
Ok(rgba_image(rgba.as_raw(), rgba.dimensions(), (72.0, 72.0)))
}
/// Most decoded bytes one TIFF page may take: room for the 768 MiB CMYK poster in #665, twice over.
const MAX_TIFF_DECODED_BYTES: usize = 2 << 30;
/// Most decoded bytes one TIFF page or PNG may take: room for the 768 MiB CMYK poster in #665,
/// twice over.
const MAX_DECODED_BYTES: usize = 2 << 30;
/// Every page of a TIFF (multi-page scans become multi-page PDFs).
fn tiff_pages(name: &str, bytes: &[u8]) -> Result<Vec<Embedded>, CreateError> {
@ -398,7 +402,7 @@ fn tiff_pages(name: &str, bytes: &[u8]) -> Result<Vec<Embedded>, CreateError> {
// 12000x16000 CMYK is 768 MiB. Raise it, but keep a ceiling so a tiny file claiming
// enormous dimensions can't ask for unbounded memory.
let mut limits = Limits::default();
limits.decoding_buffer_size = MAX_TIFF_DECODED_BYTES;
limits.decoding_buffer_size = MAX_DECODED_BYTES;
let mut dec = Decoder::new(std::io::Cursor::new(bytes)).map_err(|e| bad(e.to_string()))?.with_limits(limits);
let mut out = Vec::new();
loop {

View File

@ -198,6 +198,25 @@ fn a_tiff_claiming_enormous_dimensions_is_refused() {
assert!(matches!(from_images(&[("huge.tif".into(), tif)]), Err(CreateError::Image(..))));
}
/// Likewise a PNG: the png crate's limits don't cover the frame buffer, which its header sizes
/// (60000x60000 RGBA is 14.4 GB). Refused before anything is allocated, not after.
#[test]
fn a_png_claiming_enormous_dimensions_is_refused() {
let mut png = Vec::new();
{
let mut enc = png::Encoder::new(&mut png, 60_000, 60_000);
enc.set_color(png::ColorType::Rgba);
enc.set_depth(png::BitDepth::Eight);
let mut w = enc.write_header().unwrap();
// A zlib stream holding one zero byte: far too little data, but a well-formed start.
w.write_chunk(png::chunk::IDAT, &[0x78, 0x01, 0x01, 0x01, 0x00, 0xFE, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x01]).unwrap();
}
match from_images(&[("huge.png".into(), png)]) {
Err(CreateError::Image(_, m)) => assert!(m.contains("too large"), "{m}"),
other => panic!("expected a refusal, got {:?}", other.map(|_| ())),
}
}
#[test]
fn images_export_as_jpeg_unchanged_and_others_as_png() {
let doc = reopen(

View File

@ -2803,6 +2803,18 @@ impl Session {
Ok((kind, created?))
}
/// A file for Combine as PDF bytes: an image or text file is converted; a PDF is returned as
/// it is, unchecked (Combine reads it itself, with its password), as is a file named .pdf
/// without a `%PDF-` header, which the reader may still open.
pub fn as_pdf(&self, name: &str, bytes: &Arc<Vec<u8>>) -> Result<Arc<Vec<u8>>, EditError> {
let pdf = match source_kind(name, bytes) {
Some(SourceKind::Pdf) => true,
Some(SourceKind::Image | SourceKind::Text) => false,
None => name.to_ascii_lowercase().ends_with(".pdf"),
};
if pdf { Ok(bytes.clone()) } else { self.convert_to_pdf(name, bytes).map(|(_, pdf)| pdf) }
}
/// Reduce File Size: Acrobat's defaults (images above 225 ppi to 150 ppi, JPEG medium
/// quality; thumbnails dropped), identical resources merged, unused objects dropped, objects
/// packed into compressed object streams. Returns the bytes and how many objects were

View File

@ -578,6 +578,21 @@ fn files_that_cannot_be_converted_are_refused_clearly() {
assert_eq!(err, EditError::Source("locked.pdf: it is password-protected".into()));
}
/// Combine files (#569): images and text become PDFs; PDFs pass through unread, even protected
/// ones (Combine asks for their password) and ones named .pdf without a header.
#[test]
fn files_for_combine_are_made_into_pdfs_and_pdfs_pass_through() {
let s = Session::new();
let locked = protected("pw", "o", -1);
assert!(Arc::ptr_eq(&s.as_pdf("locked.pdf", &locked).unwrap(), &locked));
let headless = Arc::new(b"1 0 obj << >> endobj".to_vec());
assert!(Arc::ptr_eq(&s.as_pdf("old.PDF", &headless).unwrap(), &headless));
assert!(s.as_pdf("scan.jpg", &Arc::new(jpeg_bytes())).unwrap().starts_with(b"%PDF-"));
assert!(s.as_pdf("notes.txt", &Arc::new(b"hello".to_vec())).unwrap().starts_with(b"%PDF-"));
let err = s.as_pdf("report.docx", &Arc::new(b"PK\x03\x04".to_vec())).unwrap_err();
assert!(matches!(err, EditError::Source(m) if m.starts_with("report.docx: this file type can't be converted")));
}
fn protected(user: &str, owner: &str, permissions: i32) -> Arc<Vec<u8>> {
let mut doc = pdfcraft_cos::Document::open(Arc::new(fixture(2))).unwrap();
doc.set_encryption(&pdfcraft_cos::NewEncryption {

View File

@ -213,6 +213,9 @@ pub struct CombineTab {
pub(crate) selected_pages: BTreeSet<(u64, usize)>,
/// The page a Shift-click selects from.
pub(crate) page_anchor: Option<(u64, usize)>,
/// "Leave out files that can't be read?", asked when Combine is clicked with such files in the
/// list.
pub(crate) confirm_skip: bool,
}
/// The pages a range takes, and the range and page count they were worked out for.
@ -395,6 +398,13 @@ impl CombineFile {
self.checked = None;
}
/// Whether the file can't be read at all (damaged, or a type that can't be made into a PDF):
/// it stays in the list, with why, and Combine leaves it out once the user agrees. A
/// password-protected file can be unlocked instead, so it isn't one.
pub(crate) fn unreadable(&self) -> bool {
self.problem.is_some() && self.lock.is_none()
}
/// The password the file was unlocked with, if any.
pub(crate) fn password(&self) -> Option<&str> {
self.password.as_ref().map(|p| p.0.as_str())
@ -583,7 +593,7 @@ pub(crate) fn page(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
// The keyboard is the list's unless a text field, the password box or the size slider has it.
let slider_focused = grid && app.combine_tab.zoom_slider.is_some_and(|id| ui.memory(|m| m.has_focus(id)));
// (Nor while a file is shown large: its window may only open or close this frame.)
let covered = app.combine_tab.unlock.is_some() || app.combine_tab.preview.is_some();
let covered = app.combine_tab.unlock.is_some() || app.combine_tab.preview.is_some() || app.combine_tab.confirm_skip;
let focus = ui.memory(|m| m.focused());
let space = focus.is_none() || focus == app.combine_tab.card_focus;
let any_picked = count > 0 || !app.combine_tab.selected_pages.is_empty();
@ -793,7 +803,67 @@ pub(crate) fn page(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
app.combine_apply(action);
}
if combine && blocker.is_none() {
app.combine_staged();
if app.combine_draft.iter().any(CombineFile::unreadable) {
app.combine_tab.confirm_skip = true;
} else {
app.combine_staged();
}
}
skip_prompt(app, ui.ctx());
}
/// Most files the prompt names; the rest are counted.
const SKIP_PROMPT_NAMES: usize = 8;
/// "Leave out files that can't be read?": names them and why, then combines the others, or not.
fn skip_prompt(app: &mut PdfCraftApp, ctx: &egui::Context) {
if !app.combine_tab.confirm_skip {
return;
}
let t = Tokens::get(ctx);
let mut left_out: Vec<(&str, &str)> = Vec::new();
let mut seen = BTreeSet::new();
for f in app.combine_draft.iter().filter(|f| f.unreadable() && seen.insert(f.group)) {
left_out.push((f.name.as_str(), f.problem.as_deref().unwrap_or_default()));
}
let mut choice: Option<bool> = None;
let modal = egui::Modal::new(egui::Id::new("combine-skip")).show(ctx, |ui| {
ui.set_width(460.0);
ui.horizontal(|ui| {
ui.add(icons::image("triangle-alert", 22.0, ERROR));
ui.add(egui::Label::new(egui::RichText::new(tl!("Leave out files that can't be read?")).font(theme::semibold(16.0))).wrap());
});
ui.add_space(6.0);
ui.label(egui::RichText::new(tl!("These files can't be read, so they won't be in the combined PDF:")).color(t.text_muted));
ui.add_space(6.0);
for (name, why) in left_out.iter().take(SKIP_PROMPT_NAMES) {
ui.horizontal_wrapped(|ui| {
ui.label(egui::RichText::new(format!("• {}", crate::bidi::visual(name))).strong());
ui.label(egui::RichText::new(*why).color(t.text_muted));
});
}
if left_out.len() > SKIP_PROMPT_NAMES {
let more = (left_out.len() - SKIP_PROMPT_NAMES).to_string();
ui.label(egui::RichText::new(crate::i18n::fmt(tl!("and {n} more"), &[("n", &more)])).color(t.text_muted));
}
ui.add_space(14.0);
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
if widgets::pill_button(ui, tl!("Combine the rest"), true).clicked() {
choice = Some(true);
}
if widgets::pill_button(ui, tl!("Cancel"), false).clicked() {
choice = Some(false);
}
});
});
if choice.is_none() && modal.should_close() {
choice = Some(false);
}
if let Some(go) = choice {
app.combine_tab.confirm_skip = false;
if go {
app.combine_staged();
}
}
}
@ -907,11 +977,20 @@ fn expandable(f: &mut CombineFile) -> bool {
/// Why Combine can't run yet, if so.
fn blocker(files: &[CombineFile], checks: &[Result<usize, String>]) -> Option<String> {
// (A file split into parts is still one file.)
if files.iter().map(|f| f.group).collect::<BTreeSet<u64>>().len() < 2 {
return Some(tl!("Add at least two files").to_string());
// (A file split into parts is still one file.) Files that can't be read are left out, so
// they don't count.
if files.iter().filter(|f| !f.unreadable()).map(|f| f.group).collect::<BTreeSet<u64>>().len() < 2 {
return Some(match files.iter().find(|f| f.unreadable()) {
Some(f) => {
crate::i18n::fmt(tl!("{name} can't be combined: {why}"), &[("name", &f.name), ("why", f.problem.as_deref().unwrap_or_default())])
}
None => tl!("Add at least two files").to_string(),
});
}
for (f, check) in files.iter().zip(checks) {
if f.unreadable() {
continue;
}
if let Some(p) = &f.problem {
return Some(crate::i18n::fmt(tl!("{name} can't be combined: {why}"), &[("name", &f.name), ("why", p)]));
}
@ -1041,9 +1120,9 @@ fn empty_state(app: &mut PdfCraftApp, ui: &mut egui::Ui, t: &Tokens, files_hover
ui.add_space((ui.available_height() / 2.0 - 70.0).max(16.0));
ui.add(icons::image("files", 40.0, if files_hovering { t.accent } else { t.text_faint }));
ui.add_space(10.0);
ui.label(egui::RichText::new(tl!("Add the PDFs to combine")).font(theme::medium(15.0)));
ui.label(egui::RichText::new(tl!("Add the files to combine")).font(theme::medium(15.0)));
ui.add_space(4.0);
ui.label(egui::RichText::new(tl!("Drop PDFs or folders here, or choose them.")).color(t.text_faint));
ui.label(egui::RichText::new(tl!("Drop PDFs, images, text files or folders here, or choose them.")).color(t.text_faint));
ui.add_space(12.0);
let folders = cfg!(not(target_arch = "wasm32"));
// Centre the buttons: measure them first.
@ -1084,12 +1163,12 @@ fn add_menu(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
app.combine_dialog();
ui.close();
}
if item(ui, "folder", tl!("Add folder…")).on_hover_text(tl!("Every PDF in the folder")).clicked() {
if item(ui, "folder", tl!("Add folder…")).on_hover_text(tl!("Every PDF, image and text file in the folder")).clicked() {
app.combine_add_folder(false);
ui.close();
}
if item(ui, "folder-open", tl!("Add folder and subfolders…"))
.on_hover_text(tl!("Every PDF in the folder and the folders inside it"))
.on_hover_text(tl!("Every PDF, image and text file in the folder and the folders inside it"))
.clicked()
{
app.combine_add_folder(true);
@ -1406,10 +1485,10 @@ fn table_body(
});
}
/// The PDFs in `dir` (and, if `recursive`, the folders inside it, not following links), in
/// natural order, at most [`MAX_FOLDER_FILES`]; and whether there were more.
/// The PDFs, images and text files in `dir` (and, if `recursive`, the folders inside it, not
/// following links), in natural order, at most [`MAX_FOLDER_FILES`]; and whether there were more.
#[cfg(not(target_arch = "wasm32"))]
fn pdfs_in(dir: &std::path::Path, recursive: bool) -> (Vec<std::path::PathBuf>, bool) {
fn combinable_in(dir: &std::path::Path, recursive: bool) -> (Vec<std::path::PathBuf>, bool) {
let mut found = Vec::new();
let mut more = false;
let mut folders = vec![(dir.to_path_buf(), 0usize)];
@ -1423,7 +1502,7 @@ fn pdfs_in(dir: &std::path::Path, recursive: bool) -> (Vec<std::path::PathBuf>,
if recursive && !hidden && depth < MAX_FOLDER_DEPTH {
folders.push((path, depth + 1));
}
} else if path.extension().is_some_and(|e| e.eq_ignore_ascii_case("pdf")) && path.is_file() {
} else if path.extension().is_some_and(|e| combinable(&e.to_string_lossy())) && path.is_file() {
if found.len() >= MAX_FOLDER_FILES {
more = true;
break 'walk;
@ -1436,6 +1515,21 @@ fn pdfs_in(dir: &std::path::Path, recursive: bool) -> (Vec<std::path::PathBuf>,
(found, more)
}
/// Whether a file with extension `ext` can join the list: a PDF, or an image or text file that
/// becomes one.
fn combinable(ext: &str) -> bool {
std::iter::once("pdf").chain(pdfcraft_engine::CONVERTIBLE).any(|c| ext.eq_ignore_ascii_case(c))
}
/// A file's bookmark in the combined PDF: its name without ".pdf", or without the extension of
/// the image or text file it was made from.
fn title_of(name: &str) -> &str {
match name.rsplit_once('.') {
Some((stem, ext)) if !stem.is_empty() && combinable(ext) => stem,
_ => name,
}
}
/// File names in the order people expect: case-insensitive, with numbers by value
/// ("scan2" before "scan10").
fn natural(a: &str, b: &str) -> Ordering {
@ -2191,12 +2285,35 @@ impl PdfCraftApp {
pub(crate) fn stage_combine_with(&mut self, files: Vec<Incoming>) {
let mut added = Vec::new();
for Incoming { name, bytes, modified, note } in files {
let assessed = match assess(&bytes, None) {
Ok(a) => a,
Err(AssessError::Unreadable(e)) => {
self.notify_fmt("Couldn't add {name}: {e}", &[("name", &name), ("e", &e)]);
continue;
// Images and text files become PDFs as they join the list (#569): from then on they
// are ordinary files, with pages, thumbnails and page ranges.
// A file that can't be read joins the list all the same, with why (the Warnings
// column, its card); Combine asks before leaving it out.
let unreadable = |e: &str| Assessed {
pages: 0,
sizes: Arc::from(Vec::new()),
problem: Some(crate::i18n::fmt(tl!("Can't be read: {e}"), &[("e", e)])),
lock: None,
notes: Vec::new(),
};
let (bytes, assessed) = match self.session.as_pdf(&name, &bytes) {
Ok(pdf) => {
let assessed = assess(&pdf, None);
(pdf, assessed)
}
Err(e) => {
// (The engine's message names the file, which the row already shows.)
let e = match e {
pdfcraft_engine::EditError::Source(m) => m,
other => other.to_string(),
};
let why = e.strip_prefix(&format!("{name}: ")).unwrap_or(&e).to_string();
(bytes, Ok(unreadable(&why)))
}
};
let assessed = match assessed {
Ok(a) => a,
Err(AssessError::Unreadable(e)) => unreadable(&e),
Err(AssessError::WrongPassword) => {
self.notify_fmt("Couldn't add {name}: {e}", &[("name", &name), ("e", "the password is wrong")]);
continue;
@ -2286,7 +2403,8 @@ impl PdfCraftApp {
self.combine_unlock(&ids, password)
}
/// Add every PDF in a folder the user picks (and its subfolders, if `recursive`).
/// Add every PDF, image and text file in a folder the user picks (and its subfolders, if
/// `recursive`).
pub fn combine_add_folder(&mut self, recursive: bool) {
#[cfg(not(target_arch = "wasm32"))]
{
@ -2298,19 +2416,19 @@ impl PdfCraftApp {
let _ = recursive;
}
/// Add the PDFs in `dir` to the Combine files list, as one undo step.
/// Add the PDFs, images and text files in `dir` to the Combine files list, as one undo step.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn add_folder_to_combine(&mut self, dir: &std::path::Path, recursive: bool) {
let (paths, more) = pdfs_in(dir, recursive);
let (paths, more) = combinable_in(dir, recursive);
let folder = dir.file_name().map_or_else(|| dir.to_string_lossy().into_owned(), |n| n.to_string_lossy().into_owned());
if paths.is_empty() {
self.notify_fmt("No PDFs found in {folder}", &[("folder", &folder)]);
self.notify_fmt("No PDFs, images or text files found in {folder}", &[("folder", &folder)]);
self.open_combine_tab();
return;
}
self.use_paths(crate::files::FilePurpose::Combine, &paths);
if more {
self.notify_fmt("Added the first {n} PDFs in {folder}", &[("n", &MAX_FOLDER_FILES.to_string()), ("folder", &folder)]);
self.notify_fmt("Added the first {n} files in {folder}", &[("n", &MAX_FOLDER_FILES.to_string()), ("folder", &folder)]);
}
}
@ -2331,18 +2449,17 @@ impl PdfCraftApp {
}
/// Combine the listed files into a new, unsaved document; the tab makes way for it.
/// Files that can't be read are left out (the user was asked).
pub fn combine_staged(&mut self) {
let files = &self.combine_draft;
let files: Vec<&CombineFile> = self.combine_draft.iter().filter(|f| !f.unreadable()).collect();
if files.is_empty() {
return;
}
// Each file once, however many parts it is in.
let count = files.iter().map(|f| f.group).collect::<BTreeSet<u64>>().len();
let groups: Vec<u64> = files.iter().map(|f| f.group).collect();
let sources: Vec<(String, Arc<Vec<u8>>, Option<String>)> = files
.iter()
.map(|f| (crate::files::strip_pdf(&f.name).to_string(), f.bytes.clone(), Some(f.range.clone()).filter(|r| !r.trim().is_empty())))
.collect();
let sources: Vec<(String, Arc<Vec<u8>>, Option<String>)> =
files.iter().map(|f| (title_of(&f.name).to_string(), f.bytes.clone(), Some(f.range.clone()).filter(|r| !r.trim().is_empty()))).collect();
let passwords: Vec<Option<&str>> = files.iter().map(|f| f.password.as_ref().map(|p| p.0.as_str())).collect();
match self.session.combine_grouped(&sources, &groups, &passwords) {
Ok(bytes) => {
@ -2423,6 +2540,16 @@ mod tests {
}
}
#[test]
fn bookmarks_are_named_without_the_extension_of_a_converted_file() {
assert_eq!(title_of("report.pdf"), "report");
assert_eq!(title_of("REPORT.PDF"), "REPORT");
assert_eq!(title_of("scan.2024.JPG"), "scan.2024");
assert_eq!(title_of("notes.txt"), "notes");
assert_eq!(title_of("v1.2"), "v1.2", "not a known extension");
assert_eq!(title_of(".png"), ".png", "nothing would be left");
}
#[test]
fn page_lists_become_the_shortest_range() {
assert_eq!(range_of(&[0, 1, 2], 3), "", "every page in order: no range");

View File

@ -29,7 +29,7 @@ fn files_picker(purpose: FilePurpose) -> rfd::AsyncFileDialog {
if purpose == FilePurpose::RedactWords {
return dialog.add_filter(tl!("Text"), &["txt"]);
}
if !matches!(purpose, FilePurpose::CreateMultiple | FilePurpose::InsertPages) {
if !matches!(purpose, FilePurpose::CreateMultiple | FilePurpose::InsertPages | FilePurpose::Combine) {
return dialog.add_filter("PDF", &["pdf"]);
}
let all: Vec<&str> = std::iter::once("pdf").chain(pdfcraft_engine::CONVERTIBLE).collect();

View File

@ -1586,16 +1586,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. فُتح، لكن إعدادات أمانه لا تسمح بالدمج. أدخل كلمة مرور الأذونات.
Unlocked: the combined file won't be password-protected أُلغي القفل: الملف المدموج لن يكون محميًا بكلمة مرور
Unlocked {n} of {m} files أُلغي قفل {n} من {m} ملفات
Drop PDFs or folders here, or choose them. أفلت ملفات PDF أو مجلدات هنا، أو اخترها.
Drop PDFs, images, text files or folders here, or choose them. أفلت ملفات PDF أو الصور أو الملفات النصية أو المجلدات هنا، أو اخترها.
Add folder… إضافة مجلد…
Add folder and subfolders… إضافة مجلد ومجلداته الفرعية…
More ways to add files طرق أخرى لإضافة الملفات
Every PDF in the folder كل ملفات PDF في المجلد
Every PDF in the folder and the folders inside it كل ملفات PDF في المجلد والمجلدات التي بداخله
Every PDF, image and text file in the folder كل ملفات PDF والصور والملفات النصية في المجلد
Every PDF, image and text file in the folder and the folders inside it كل ملفات PDF والصور والملفات النصية في المجلد والمجلدات التي بداخله
Add folder إضافة مجلد
Add folder and subfolders إضافة مجلد ومجلداته الفرعية
No PDFs found in {folder} لم يُعثر على ملفات PDF في {folder}
Added the first {n} PDFs in {folder} أُضيفت أول {n} ملفات PDF في {folder}
No PDFs, images or text files found in {folder} لم يُعثر على ملفات PDF أو صور أو ملفات نصية في {folder}
Added the first {n} files in {folder} أُضيفت أول {n} ملفات في {folder}
Click to sort, drag to move the column. Drag its edge to resize it. انقر للفرز، واسحب لنقل العمود. اسحب حافته لتغيير حجمه.
Reset columns إعادة ضبط الأعمدة
Remove {n} files إزالة {n} ملفات
@ -1613,7 +1613,7 @@
{n} files need attention {n} ملفات تحتاج إلى انتباه
{name} can't be combined: {why} لا يمكن دمج {name}: {why}
Check the pages of {name}: {why} تحقق من صفحات {name}: {why}
Add the PDFs to combine أضف ملفات PDF المراد دمجها
Add the files to combine أضف الملفات المراد دمجها
File name اسم الملف
Warnings التحذيرات
Drag to reorder اسحب لإعادة الترتيب
@ -1626,6 +1626,10 @@
Password-protected محمي بكلمة مرور
Its security settings don't allow copying pages إعدادات أمانه لا تسمح بنسخ الصفحات
Can't be read: {e} تتعذر قراءته: {e}
Leave out files that can't be read? هل تريد استبعاد الملفات التي تتعذر قراءتها؟
These files can't be read, so they won't be in the combined PDF: تتعذر قراءة هذه الملفات، لذا لن تكون في ملف PDF المدمج:
Combine the rest دمج الباقي
and {n} more و{n} أخرى
Includes unsaved changes يتضمن تغييرات غير محفوظة
Pages of {name} to combine, e.g. 1-3, 6 صفحات {name} المراد دمجها، مثلًا 1-3, 6
Remove {name} إزالة {name}

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -1547,16 +1547,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Файлът е отворен, но настройките му за защита не позволяват обединяване. Въведете паролата за разрешения.
Unlocked: the combined file won't be password-protected Отключен: обединеният файл няма да бъде защитен с парола
Unlocked {n} of {m} files Отключени са {n} от {m} файла
Drop PDFs or folders here, or choose them. Пуснете тук PDF файлове или папки или ги изберете.
Drop PDFs, images, text files or folders here, or choose them. Пуснете тук PDF файлове, изображения, текстови файлове или папки или ги изберете.
Add folder… Добави папка…
Add folder and subfolders… Добави папка и подпапки…
More ways to add files Още начини за добавяне на файлове
Every PDF in the folder Всички PDF файлове в папката
Every PDF in the folder and the folders inside it Всички PDF файлове в папката и в папките в нея
Every PDF, image and text file in the folder Всички PDF файлове, изображения и текстови файлове в папката
Every PDF, image and text file in the folder and the folders inside it Всички PDF файлове, изображения и текстови файлове в папката и в папките в нея
Add folder Добави папка
Add folder and subfolders Добави папка и подпапки
No PDFs found in {folder} Не са открити PDF файлове в {folder}
Added the first {n} PDFs in {folder} Добавени са първите {n} PDF файла от {folder}
No PDFs, images or text files found in {folder} Не са открити PDF файлове, изображения или текстови файлове в {folder}
Added the first {n} files in {folder} Добавени са първите {n} файла от {folder}
Click to sort, drag to move the column. Drag its edge to resize it. Щракнете за сортиране, плъзнете, за да преместите колоната. Плъзнете ръба на колоната, за да промените размера.
Reset columns Нулирай колоните
Remove {n} files Премахни {n} файла
@ -1574,7 +1574,7 @@
{n} files need attention {n} файла изискват внимание
{name} can't be combined: {why} {name} не може да бъде обединен: {why}
Check the pages of {name}: {why} Проверете страниците на {name}: {why}
Add the PDFs to combine Добавете PDF файловете за обединяване
Add the files to combine Добавете файловете за обединяване
File name Име на файла
Warnings Предупреждения
Drag to reorder Плъзнете, за да пренаредите
@ -1587,6 +1587,10 @@
Password-protected Защитен с парола
Its security settings don't allow copying pages Настройките му за защита не позволяват копиране на страници
Can't be read: {e} Не може да бъде прочетен: {e}
Leave out files that can't be read? Да се пропуснат ли файловете, които не могат да бъдат прочетени?
These files can't be read, so they won't be in the combined PDF: Тези файлове не могат да бъдат прочетени, затова няма да бъдат в обединения PDF:
Combine the rest Обедини останалите
and {n} more и още {n}
Includes unsaved changes Съдържа незаписани промени
Pages of {name} to combine, e.g. 1-3, 6 Страници от {name} за обединяване, напр. 1-3, 6
Remove {name} Премахни {name}

Can't render this file because it contains an unexpected character in line 8 and column 16.

View File

@ -1990,18 +1990,22 @@
Add folder and subfolders… Ordner mit Unterordnern hinzufügen…
Add folder… Ordner hinzufügen…
Add open documents Geöffnete Dokumente hinzufügen
Add the PDFs to combine Füge die zu kombinierenden PDFs hinzu
Add the files to combine Füge die zu kombinierenden Dateien hinzu
Added more than once Mehrfach hinzugefügt
All open documents Alle geöffneten Dokumente
Can't be read: {e} Kann nicht gelesen werden: {e}
Leave out files that can't be read? Dateien auslassen, die nicht gelesen werden können?
These files can't be read, so they won't be in the combined PDF: Diese Dateien können nicht gelesen werden und sind deshalb nicht im kombinierten PDF:
Combine the rest Die übrigen kombinieren
and {n} more und {n} weitere
Check the pages of {name}: {why} Prüfe die Seiten von {name}: {why}
Click to sort, drag to move the column. Drag its edge to resize it. Klicke zum Sortieren, ziehe zum Verschieben der Spalte. Ziehe ihren Rand, um die Breite zu ändern.
Drag to reorder Ziehen zum Umsortieren
Drop PDFs or folders here, or choose them. Lege PDFs oder Ordner hier ab oder wähle sie aus.
Drop PDFs, images, text files or folders here, or choose them. Lege PDFs, Bilder, Textdateien oder Ordner hier ab oder wähle sie aus.
Enter the password that opens it Gib das Passwort zum Öffnen ein
Enter the permissions password to allow combining Gib das Berechtigungspasswort ein, um das Kombinieren zu erlauben
Every PDF in the folder Jedes PDF im Ordner
Every PDF in the folder and the folders inside it Jedes PDF im Ordner und in seinen Unterordnern
Every PDF, image and text file in the folder Jedes PDF, jedes Bild und jede Textdatei im Ordner
Every PDF, image and text file in the folder and the folders inside it Jedes PDF, jedes Bild und jede Textdatei im Ordner und in seinen Unterordnern
File name Dateiname
Files are combined from top to bottom. Leave Pages empty to take every page. Dateien werden von oben nach unten kombiniert. Lass „Seiten“ leer, um alle Seiten zu übernehmen.
Hide password Passwort ausblenden
@ -2065,8 +2069,8 @@
Only continue if you trust this document. An address can carry information from the document, such as what you typed into its form. Fahre nur fort, wenn du diesem Dokument vertraust. Eine Adresse kann Informationen aus dem Dokument übermitteln, etwa was du in sein Formular eingegeben hast.
Print single-sided. Keep the sheets in order, cut at the marks, then stack the piles left to right, top to bottom. Einseitig drucken. Halte die Blätter in Reihenfolge, schneide an den Marken und lege die Stapel dann von links nach rechts, von oben nach unten aufeinander.
Unlocked {n} of {m} files {n} von {m} Dateien entsperrt
No PDFs found in {folder} Keine PDFs in {folder} gefunden
Added the first {n} PDFs in {folder} Die ersten {n} PDFs aus {folder} hinzugefügt
No PDFs, images or text files found in {folder} Keine PDFs, Bilder oder Textdateien in {folder} gefunden
Added the first {n} files in {folder} Die ersten {n} Dateien aus {folder} hinzugefügt
comment menu Edit Bearbeiten
Undo {action} {action} rückgängig machen
Redo {action} {action} wiederholen

Can't render this file because it contains an unexpected character in line 6 and column 51.

View File

@ -1588,16 +1588,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Se abrió, pero su configuración de seguridad no permite combinarlo. Introduce la contraseña de permisos.
Unlocked: the combined file won't be password-protected Desbloqueado: el archivo combinado no estará protegido con contraseña
Unlocked {n} of {m} files Se desbloquearon {n} de {m} archivos
Drop PDFs or folders here, or choose them. Suelta aquí PDF o carpetas, o elígelos.
Drop PDFs, images, text files or folders here, or choose them. Suelta aquí PDF, imágenes, archivos de texto o carpetas, o elígelos.
Add folder… Añadir carpeta…
Add folder and subfolders… Añadir carpeta y subcarpetas…
More ways to add files Más formas de añadir archivos
Every PDF in the folder Todos los PDF de la carpeta
Every PDF in the folder and the folders inside it Todos los PDF de la carpeta y de las carpetas que contiene
Every PDF, image and text file in the folder Todos los PDF, imágenes y archivos de texto de la carpeta
Every PDF, image and text file in the folder and the folders inside it Todos los PDF, imágenes y archivos de texto de la carpeta y de las carpetas que contiene
Add folder Añadir carpeta
Add folder and subfolders Añadir carpeta y subcarpetas
No PDFs found in {folder} No se encontraron PDF en {folder}
Added the first {n} PDFs in {folder} Se añadieron los primeros {n} PDF de {folder}
No PDFs, images or text files found in {folder} No se encontraron PDF, imágenes ni archivos de texto en {folder}
Added the first {n} files in {folder} Se añadieron los primeros {n} archivos de {folder}
Click to sort, drag to move the column. Drag its edge to resize it. Haz clic para ordenar; arrastra para mover la columna. Arrastra su borde para cambiar el ancho.
Reset columns Restablecer columnas
Remove {n} files Quitar {n} archivos
@ -1615,7 +1615,7 @@
{n} files need attention {n} archivos necesitan atención
{name} can't be combined: {why} No se puede combinar {name}: {why}
Check the pages of {name}: {why} Revisa las páginas de {name}: {why}
Add the PDFs to combine Añade los PDF que quieres combinar
Add the files to combine Añade los archivos que quieres combinar
File name Nombre de archivo
Warnings Advertencias
Drag to reorder Arrastra para reordenar
@ -1628,6 +1628,10 @@
Password-protected Protegido con contraseña
Its security settings don't allow copying pages Su configuración de seguridad no permite copiar páginas
Can't be read: {e} No se puede leer: {e}
Leave out files that can't be read? ¿Omitir los archivos que no se pueden leer?
These files can't be read, so they won't be in the combined PDF: Estos archivos no se pueden leer, así que no estarán en el PDF combinado:
Combine the rest Combinar el resto
and {n} more y {n} más
Includes unsaved changes Incluye cambios sin guardar
Pages of {name} to combine, e.g. 1-3, 6 Páginas de {name} que combinar, p. ej., 1-3, 6
Remove {name} Quitar {name}

Can't render this file because it contains an unexpected character in line 6 and column 60.

View File

@ -1992,18 +1992,22 @@
Add folder and subfolders… Ajouter un dossier et ses sous-dossiers…
Add folder… Ajouter un dossier…
Add open documents Ajouter des documents ouverts
Add the PDFs to combine Ajoutez les PDF à combiner
Add the files to combine Ajoutez les fichiers à combiner
Added more than once Ajouté plusieurs fois
All open documents Tous les documents ouverts
Can't be read: {e} Illisible : {e}
Leave out files that can't be read? Laisser de côté les fichiers illisibles ?
These files can't be read, so they won't be in the combined PDF: Ces fichiers sont illisibles ; ils ne figureront pas dans le PDF combiné :
Combine the rest Combiner les autres
and {n} more et {n} de plus
Check the pages of {name}: {why} Vérifiez les pages de {name} : {why}
Click to sort, drag to move the column. Drag its edge to resize it. Cliquer pour trier, faire glisser pour déplacer la colonne. Faire glisser son bord pour la redimensionner.
Drag to reorder Faire glisser pour réordonner
Drop PDFs or folders here, or choose them. Déposez ici des PDF ou des dossiers, ou choisissez-les.
Drop PDFs, images, text files or folders here, or choose them. Déposez ici des PDF, des images, des fichiers texte ou des dossiers, ou choisissez-les.
Enter the password that opens it Saisissez le mot de passe qui l'ouvre
Enter the permissions password to allow combining Saisissez le mot de passe des autorisations pour permettre de le combiner
Every PDF in the folder Tous les PDF du dossier
Every PDF in the folder and the folders inside it Tous les PDF du dossier et des dossiers qu'il contient
Every PDF, image and text file in the folder Tous les PDF, images et fichiers texte du dossier
Every PDF, image and text file in the folder and the folders inside it Tous les PDF, images et fichiers texte du dossier et des dossiers qu'il contient
File name Nom du fichier
Files are combined from top to bottom. Leave Pages empty to take every page. Les fichiers sont combinés de haut en bas. Laissez Pages vide pour prendre toutes les pages.
Hide password Masquer le mot de passe
@ -2086,7 +2090,7 @@
A script Un script
About À propos
Add {what} Ajouter {what}
Added the first {n} PDFs in {folder} Les {n} premiers PDF de {folder} ont été ajoutés
Added the first {n} files in {folder} Les {n} premiers fichiers de {folder} ont été ajoutés
Align fields Aligner les champs
Another file dialog is still open. Finish with it first. Une autre boîte de dialogue de fichier est encore ouverte. Terminez d'abord avec celle-ci.
Basic constraints Contraintes de base
@ -2102,7 +2106,7 @@
Mark pages for redaction Marquer des pages pour biffure
Match field sizes Uniformiser la taille des champs
Models Modèles
No PDFs found in {folder} Aucun PDF trouvé dans {folder}
No PDFs, images or text files found in {folder} Aucun PDF, image ou fichier texte trouvé dans {folder}
Only continue if you trust this document. An address can carry information from the document, such as what you typed into its form. Ne continuez que si vous faites confiance à ce document. Une adresse peut transmettre des informations du document, par exemple ce que vous avez saisi dans son formulaire.
Place saved {what} Placer {what} (version enregistrée)
Print single-sided. Keep the sheets in order, cut at the marks, then stack the piles left to right, top to bottom. Imprimez en recto seul. Gardez les feuilles dans l'ordre, découpez aux repères, puis empilez les piles de gauche à droite et de haut en bas.

Can't render this file because it contains an unexpected character in line 6 and column 39.

View File

@ -1979,18 +1979,22 @@
Add folder and subfolders… Mappa és almappák hozzáadása…
Add folder… Mappa hozzáadása…
Add open documents Megnyitott dokumentumok hozzáadása
Add the PDFs to combine Add hozzá az egyesítendő PDF-eket
Add the files to combine Add hozzá az egyesítendő fájlokat
Added more than once Többször hozzáadva
All open documents Minden megnyitott dokumentum
Can't be read: {e} Nem olvasható: {e}
Leave out files that can't be read? Kihagyod az olvashatatlan fájlokat?
These files can't be read, so they won't be in the combined PDF: Ezek a fájlok nem olvashatók, ezért nem kerülnek bele az egyesített PDF-be:
Combine the rest A többi egyesítése
and {n} more és még {n}
Check the pages of {name}: {why} Ellenőrizd {name} oldalait: {why}
Click to sort, drag to move the column. Drag its edge to resize it. Kattints a rendezéshez, húzd az oszlop áthelyezéséhez. A szélét húzva méretezd.
Drag to reorder Húzás az átrendezéshez
Drop PDFs or folders here, or choose them. Húzz ide PDF-eket vagy mappákat, vagy válaszd ki őket.
Drop PDFs, images, text files or folders here, or choose them. Húzz ide PDF-eket, képeket, szövegfájlokat vagy mappákat, vagy válaszd ki őket.
Enter the password that opens it Add meg a megnyitó jelszót
Enter the permissions password to allow combining Add meg a jogosultsági jelszót az egyesítés engedélyezéséhez
Every PDF in the folder A mappa minden PDF-je
Every PDF in the folder and the folders inside it A mappa és az almappái minden PDF-je
Every PDF, image and text file in the folder A mappa minden PDF-je, képe és szövegfájlja
Every PDF, image and text file in the folder and the folders inside it A mappa és az almappái minden PDF-je, képe és szövegfájlja
File name Fájlnév
Files are combined from top to bottom. Leave Pages empty to take every page. A fájlok fentről lefelé egyesülnek. Hagyd üresen az Oldalak mezőt, ha minden oldalt kérsz.
Hide password Jelszó elrejtése
@ -2054,8 +2058,8 @@
Only continue if you trust this document. An address can carry information from the document, such as what you typed into its form. Csak akkor folytasd, ha megbízol ebben a dokumentumban. Egy cím adatokat vihet a dokumentumból, például azt, amit az űrlapjába írtál.
Print single-sided. Keep the sheets in order, cut at the marks, then stack the piles left to right, top to bottom. Nyomtass egyoldalasan. Tartsd a lapokat sorrendben, vágd el a jeleknél, majd rakd a kötegeket balról jobbra, fentről lefelé egymásra.
Unlocked {n} of {m} files {n} / {m} fájl feloldva
No PDFs found in {folder} Nem található PDF itt: {folder}
Added the first {n} PDFs in {folder} {folder} első {n} PDF-je hozzáadva
No PDFs, images or text files found in {folder} Nem található PDF, kép vagy szövegfájl itt: {folder}
Added the first {n} files in {folder} {folder} első {n} fájlja hozzáadva
comment menu Edit Szerkesztés
Undo {action} {action} visszavonása
Redo {action} {action} megismétlése

Can't render this file because it contains an unexpected character in line 7 and column 44.

View File

@ -1590,16 +1590,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Aperto, ma le sue impostazioni di sicurezza non consentono la combinazione. Inserisci la password delle autorizzazioni.
Unlocked: the combined file won't be password-protected Sbloccato: il file combinato non sarà protetto da password
Unlocked {n} of {m} files Sbloccati {n} file su {m}
Drop PDFs or folders here, or choose them. Trascina qui PDF o cartelle, oppure sceglili.
Drop PDFs, images, text files or folders here, or choose them. Trascina qui PDF, immagini, file di testo o cartelle, oppure sceglili.
Add folder… Aggiungi cartella…
Add folder and subfolders… Aggiungi cartella e sottocartelle…
More ways to add files Altri modi per aggiungere file
Every PDF in the folder Tutti i PDF nella cartella
Every PDF in the folder and the folders inside it Tutti i PDF nella cartella e nelle sue sottocartelle
Every PDF, image and text file in the folder Tutti i PDF, le immagini e i file di testo nella cartella
Every PDF, image and text file in the folder and the folders inside it Tutti i PDF, le immagini e i file di testo nella cartella e nelle sue sottocartelle
Add folder Aggiungi cartella
Add folder and subfolders Aggiungi cartella e sottocartelle
No PDFs found in {folder} Nessun PDF trovato in {folder}
Added the first {n} PDFs in {folder} Aggiunti i primi {n} PDF di {folder}
No PDFs, images or text files found in {folder} Nessun PDF, immagine o file di testo trovato in {folder}
Added the first {n} files in {folder} Aggiunti i primi {n} file di {folder}
Click to sort, drag to move the column. Drag its edge to resize it. Fai clic per ordinare, trascina per spostare la colonna. Trascinane il bordo per ridimensionarla.
Reset columns Reimposta colonne
Remove {n} files Rimuovi {n} file
@ -1617,7 +1617,7 @@
{n} files need attention {n} file richiedono attenzione
{name} can't be combined: {why} Impossibile combinare {name}: {why}
Check the pages of {name}: {why} Controlla le pagine di {name}: {why}
Add the PDFs to combine Aggiungi i PDF da combinare
Add the files to combine Aggiungi i file da combinare
File name Nome file
Warnings Avvisi
Drag to reorder Trascina per riordinare
@ -1630,6 +1630,10 @@
Password-protected Protetto da password
Its security settings don't allow copying pages Le sue impostazioni di sicurezza non consentono di copiare le pagine
Can't be read: {e} Impossibile leggerlo: {e}
Leave out files that can't be read? Escludere i file che non si possono leggere?
These files can't be read, so they won't be in the combined PDF: Questi file non si possono leggere, quindi non saranno nel PDF combinato:
Combine the rest Combina gli altri
and {n} more e altri {n}
Includes unsaved changes Include modifiche non salvate
Pages of {name} to combine, e.g. 1-3, 6 Pagine di {name} da combinare, es. 1-3, 6
Remove {name} Rimuovi {name}

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -1595,16 +1595,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. 開けましたが、セキュリティ設定により結合が許可されていません。権限パスワードを入力してください。
Unlocked: the combined file won't be password-protected ロック解除済み: 結合後のファイルはパスワードで保護されません
Unlocked {n} of {m} files {m} 個中 {n} 個のファイルのロックを解除しました
Drop PDFs or folders here, or choose them. ここに PDF またはフォルダーをドロップするか、選択してください。
Drop PDFs, images, text files or folders here, or choose them. ここに PDF、画像、テキストファイル、またはフォルダーをドロップするか、選択してください。
Add folder… フォルダーを追加…
Add folder and subfolders… フォルダーとサブフォルダーを追加…
More ways to add files ファイルを追加するその他の方法
Every PDF in the folder フォルダー内のすべての PDF
Every PDF in the folder and the folders inside it フォルダーとその中のフォルダーにあるすべての PDF
Every PDF, image and text file in the folder フォルダー内のすべての PDF、画像、テキストファイル
Every PDF, image and text file in the folder and the folders inside it フォルダーとその中のフォルダーにあるすべての PDF、画像、テキストファイル
Add folder フォルダーを追加
Add folder and subfolders フォルダーとサブフォルダーを追加
No PDFs found in {folder} {folder} に PDF が見つかりません
Added the first {n} PDFs in {folder} {folder} の最初の {n} 個の PDF を追加しました
No PDFs, images or text files found in {folder} {folder} に PDF、画像、テキストファイルが見つかりません
Added the first {n} files in {folder} {folder} の最初の {n} 個のファイルを追加しました
Click to sort, drag to move the column. Drag its edge to resize it. クリックで並べ替え、ドラッグで列を移動します。列の端をドラッグすると幅を変更できます。
Reset columns 列をリセット
Remove {n} files {n} 個のファイルを削除
@ -1622,7 +1622,7 @@
{n} files need attention {n} 個のファイルに問題があります
{name} can't be combined: {why} {name} は結合できません: {why}
Check the pages of {name}: {why} {name} のページ指定を確認してください: {why}
Add the PDFs to combine 結合する PDF を追加
Add the files to combine 結合するファイルを追加
File name ファイル名
Warnings 警告
Drag to reorder ドラッグして並べ替え
@ -1635,6 +1635,10 @@
Password-protected パスワードで保護されています
Its security settings don't allow copying pages セキュリティ設定によりページのコピーが許可されていません
Can't be read: {e} 読み込めません: {e}
Leave out files that can't be read? 読み込めないファイルを除外しますか?
These files can't be read, so they won't be in the combined PDF: 次のファイルは読み込めないため、結合した PDF には含まれません:
Combine the rest 残りを結合
and {n} more ほか {n} 件
Includes unsaved changes 未保存の変更を含みます
Pages of {name} to combine, e.g. 1-3, 6 結合する {name} のページ(例: 1-3, 6)
Remove {name} {name} を削除

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -1580,16 +1580,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Otwarto, ale ustawienia zabezpieczeń nie zezwalają na łączenie. Wpisz hasło uprawnień.
Unlocked: the combined file won't be password-protected Odblokowano: połączony plik nie będzie chroniony hasłem
Unlocked {n} of {m} files Odblokowano {n} z {m} plików
Drop PDFs or folders here, or choose them. Upuść tutaj pliki PDF lub foldery albo wybierz je.
Drop PDFs, images, text files or folders here, or choose them. Upuść tutaj pliki PDF, obrazy, pliki tekstowe lub foldery albo wybierz je.
Add folder… Dodaj folder…
Add folder and subfolders… Dodaj folder i podfoldery…
More ways to add files Więcej sposobów dodawania plików
Every PDF in the folder Każdy PDF w folderze
Every PDF in the folder and the folders inside it Każdy PDF w folderze i jego podfolderach
Every PDF, image and text file in the folder Każdy PDF, obraz i plik tekstowy w folderze
Every PDF, image and text file in the folder and the folders inside it Każdy PDF, obraz i plik tekstowy w folderze i jego podfolderach
Add folder Dodaj folder
Add folder and subfolders Dodaj folder i podfoldery
No PDFs found in {folder} Nie znaleziono plików PDF w {folder}
Added the first {n} PDFs in {folder} Dodano pierwsze {n} plików PDF z {folder}
No PDFs, images or text files found in {folder} Nie znaleziono plików PDF, obrazów ani plików tekstowych w {folder}
Added the first {n} files in {folder} Dodano pierwsze {n} plików z {folder}
Click to sort, drag to move the column. Drag its edge to resize it. Kliknij, aby sortować; przeciągnij, aby przenieść kolumnę. Przeciągnij jej krawędź, aby zmienić szerokość.
Reset columns Zresetuj kolumny
Remove {n} files Usuń pliki w liczbie {n}
@ -1607,7 +1607,7 @@
{n} files need attention Liczba plików wymagających uwagi: {n}
{name} can't be combined: {why} Nie można połączyć {name}: {why}
Check the pages of {name}: {why} Sprawdź strony {name}: {why}
Add the PDFs to combine Dodaj pliki PDF do połączenia
Add the files to combine Dodaj pliki do połączenia
File name Nazwa pliku
Warnings Ostrzeżenia
Drag to reorder Przeciągnij, aby zmienić kolejność
@ -1620,6 +1620,10 @@
Password-protected Chroniony hasłem
Its security settings don't allow copying pages Ustawienia zabezpieczeń nie zezwalają na kopiowanie stron
Can't be read: {e} Nie można odczytać: {e}
Leave out files that can't be read? Pominąć pliki, których nie można odczytać?
These files can't be read, so they won't be in the combined PDF: Tych plików nie można odczytać, więc nie znajdą się w połączonym pliku PDF:
Combine the rest Połącz pozostałe
and {n} more i jeszcze {n}
Includes unsaved changes Zawiera niezapisane zmiany
Pages of {name} to combine, e.g. 1-3, 6 Strony z {name} do połączenia, np. 1-3, 6
Remove {name} Usuń {name}

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -1942,14 +1942,14 @@
Add open documents Adicionar documentos abertos
All open documents Todos os documentos abertos
No documents are open Nenhum documento está aberto
Add the PDFs to combine Adicionar os PDFs para combinar
Drop PDFs or folders here, or choose them. Solte PDFs ou pastas aqui, ou escolha-os.
Add the files to combine Adicionar os arquivos para combinar
Drop PDFs, images, text files or folders here, or choose them. Solte PDFs, imagens, arquivos de texto ou pastas aqui, ou escolha-os.
Add folder… Adicionar pasta…
More ways to add files Mais formas de adicionar arquivos
Add folder Adicionar pasta
Every PDF in the folder Todo PDF da pasta
Every PDF, image and text file in the folder Todo PDF, imagem e arquivo de texto da pasta
Add folder and subfolders… Adicionar pasta e subpastas…
Every PDF in the folder and the folders inside it Todo PDF da pasta e das pastas dentro dela
Every PDF, image and text file in the folder and the folders inside it Todo PDF, imagem e arquivo de texto da pasta e das pastas dentro dela
Add folder and subfolders Adicionar pasta e subpastas
PDF, images and text PDF, imagens e texto
Files are combined from top to bottom. Leave Pages empty to take every page. Os arquivos são combinados de cima para baixo. Deixe Páginas em branco para incluir todas as páginas.
@ -1968,6 +1968,10 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Aberto, mas as configurações de segurança dele não permitem combinar. Digite a senha de permissões.
Its security settings don't allow copying pages As configurações de segurança dele não permitem copiar páginas
Can't be read: {e} Não é possível ler: {e}
Leave out files that can't be read? Deixar de fora os arquivos que não podem ser lidos?
These files can't be read, so they won't be in the combined PDF: Estes arquivos não podem ser lidos, então não estarão no PDF combinado:
Combine the rest Combinar os demais
and {n} more e mais {n}
Unlocked: the combined file won't be password-protected Desbloqueado: o arquivo combinado não será protegido por senha
Protected: the combined file won't keep its security settings Protegido: o arquivo combinado não manterá as configurações de segurança
Unlock {name} Desbloquear {name}
@ -2041,7 +2045,7 @@ action wizard Start Iniciar
A script Um script
About Sobre
Add {what} Adicionar {what}
Added the first {n} PDFs in {folder} Adicionados os primeiros {n} PDFs de {folder}
Added the first {n} files in {folder} Adicionados os primeiros {n} arquivos de {folder}
Align fields Alinhar campos
Another file dialog is still open. Finish with it first. Outra janela de arquivos ainda está aberta. Conclua-a primeiro.
Basic constraints Restrições básicas
@ -2082,7 +2086,7 @@ action wizard Start Iniciar
Model Modelo
Models Modelos
Name (A–Z) Nome (A–Z)
No PDFs found in {folder} Nenhum PDF encontrado em {folder}
No PDFs, images or text files found in {folder} Nenhum PDF, imagem ou arquivo de texto encontrado em {folder}
None of those files can be made into a PDF; use PDFs, images or .txt files Nenhum desses arquivos pode virar PDF; use PDFs, imagens ou arquivos .txt
Opens the printer driver's preferences. They are saved for every print from this computer; the copies, two-sided, grayscale and paper chosen here still apply. Abre as preferências do driver da impressora. Elas valem para toda impressão deste computador; as cópias, frente e verso, escala de cinza e papel escolhidos aqui continuam valendo.
Optimization cancelled Otimização cancelada

Can't render this file because it contains an unexpected character in line 9 and column 16.

View File

@ -1587,16 +1587,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Открыто, но настройки безопасности не разрешают объединение. Введите пароль разрешений.
Unlocked: the combined file won't be password-protected Разблокировано: объединённый файл не будет защищён паролем
Unlocked {n} of {m} files Разблокировано {n} из {m} файлов
Drop PDFs or folders here, or choose them. Перетащите PDF или папки сюда или выберите их.
Drop PDFs, images, text files or folders here, or choose them. Перетащите PDF, изображения, текстовые файлы или папки сюда или выберите их.
Add folder… Добавить папку…
Add folder and subfolders… Добавить папку и вложенные папки…
More ways to add files Больше способов добавить файлы
Every PDF in the folder Каждый PDF в папке
Every PDF in the folder and the folders inside it Каждый PDF в папке и вложенных папках
Every PDF, image and text file in the folder Каждый PDF, изображение и текстовый файл в папке
Every PDF, image and text file in the folder and the folders inside it Каждый PDF, изображение и текстовый файл в папке и вложенных папках
Add folder Добавить папку
Add folder and subfolders Добавить папку и вложенные папки
No PDFs found in {folder} В {folder} не найдено PDF
Added the first {n} PDFs in {folder} Добавлены первые {n} PDF из {folder}
No PDFs, images or text files found in {folder} В {folder} не найдено PDF, изображений или текстовых файлов
Added the first {n} files in {folder} Добавлены первые {n} файлов из {folder}
Click to sort, drag to move the column. Drag its edge to resize it. Щёлкните для сортировки, перетащите, чтобы переместить столбец. Перетащите край, чтобы изменить размер.
Reset columns Сбросить столбцы
Remove {n} files Удалить {n} файлов
@ -1614,7 +1614,7 @@
{n} files need attention {n} файлов требуют внимания
{name} can't be combined: {why} {name} нельзя объединить: {why}
Check the pages of {name}: {why} Проверьте страницы {name}: {why}
Add the PDFs to combine Добавить PDF для объединения
Add the files to combine Добавить файлы для объединения
File name Имя файла
Warnings Предупреждения
Drag to reorder Перетащить для изменения порядка
@ -1627,6 +1627,10 @@
Password-protected Защищён паролем
Its security settings don't allow copying pages Его настройки безопасности не разрешают копировать страницы
Can't be read: {e} Нельзя прочитать: {e}
Leave out files that can't be read? Пропустить файлы, которые не удаётся прочитать?
These files can't be read, so they won't be in the combined PDF: Эти файлы не удаётся прочитать, поэтому их не будет в объединённом PDF:
Combine the rest Объединить остальные
and {n} more и ещё {n}
Includes unsaved changes Включает несохранённые изменения
Pages of {name} to combine, e.g. 1-3, 6 Страницы {name} для объединения, например 1-3, 6
Remove {name} Удалить {name}

Can't render this file because it contains an unexpected character in line 9 and column 16.

View File

@ -1987,16 +1987,16 @@ action wizard Start ప్రారంభించు
Opened, but its security settings don't allow combining. Enter the permissions password. తెరవబడింది, కానీ దీని భద్రతా సెట్టింగ్‌లు కలపడాన్ని అనుమతించవు. అనుమతుల పాస్‌వర్డ్‌ను నమోదు చేయండి.
Unlocked: the combined file won't be password-protected అన్‌లాక్ చేయబడింది: కలిపిన ఫైల్ పాస్‌వర్డ్‌తో రక్షించబడదు
Unlocked {n} of {m} files {m} ఫైల్‌లలో అన్‌లాక్ చేయబడినవి: {n}
Drop PDFs or folders here, or choose them. PDFలను లేదా ఫోల్డర్‌లను ఇక్కడ లాగి వదలండి, లేదా వాటిని ఎంచుకోండి.
Drop PDFs, images, text files or folders here, or choose them. PDFలను, చిత్రాలను, టెక్స్ట్ ఫైల్‌లను లేదా ఫోల్డర్‌లను ఇక్కడ లాగి వదలండి, లేదా వాటిని ఎంచుకోండి.
Add folder… ఫోల్డర్‌ను జోడించు…
Add folder and subfolders… ఫోల్డర్‌ను, దాని ఉప ఫోల్డర్‌లను జోడించు…
More ways to add files ఫైల్‌లను జోడించడానికి మరిన్ని మార్గాలు
Every PDF in the folder ఫోల్డర్‌లోని ప్రతి PDF
Every PDF in the folder and the folders inside it ఫోల్డర్‌లోని, దాని లోపలి ఫోల్డర్‌లలోని ప్రతి PDF
Every PDF, image and text file in the folder ఫోల్డర్‌లోని ప్రతి PDF, చిత్రం, టెక్స్ట్ ఫైల్
Every PDF, image and text file in the folder and the folders inside it ఫోల్డర్‌లోని, దాని లోపలి ఫోల్డర్‌లలోని ప్రతి PDF, చిత్రం, టెక్స్ట్ ఫైల్
Add folder ఫోల్డర్‌ను జోడించు
Add folder and subfolders ఫోల్డర్‌ను, దాని ఉప ఫోల్డర్‌లను జోడించు
No PDFs found in {folder} {folder}లో PDFలు ఏవీ కనుగొనబడలేదు
Added the first {n} PDFs in {folder} {folder}లోని మొదటి {n} PDFలు జోడించబడ్డాయి
No PDFs, images or text files found in {folder} {folder}లో PDFలు, చిత్రాలు లేదా టెక్స్ట్ ఫైల్‌లు ఏవీ కనుగొనబడలేదు
Added the first {n} files in {folder} {folder}లోని మొదటి {n} ఫైల్‌లు జోడించబడ్డాయి
Click to sort, drag to move the column. Drag its edge to resize it. క్రమపరచడానికి క్లిక్ చేయండి, నిలువు వరుసను తరలించడానికి లాగండి. పరిమాణం మార్చడానికి దాని అంచును లాగండి.
Reset columns నిలువు వరుసలను రీసెట్ చేయి
Remove {n} files {n} ఫైల్‌లను తీసివేయి
@ -2014,7 +2014,7 @@ action wizard Start ప్రారంభించు
{n} files need attention {n} ఫైల్‌లలో సమస్యలు ఉన్నాయి
{name} can't be combined: {why} {name}ను కలపడం సాధ్యం కాదు: {why}
Check the pages of {name}: {why} {name} పేజీలను తనిఖీ చేయండి: {why}
Add the PDFs to combine కలపాల్సిన PDFలను జోడించండి
Add the files to combine కలపాల్సిన ఫైల్‌లను జోడించండి
File name ఫైల్ పేరు
Warnings హెచ్చరికలు
Drag to reorder క్రమం మార్చడానికి లాగండి
@ -2027,6 +2027,10 @@ action wizard Start ప్రారంభించు
Password-protected పాస్‌వర్డ్‌తో రక్షించబడింది
Its security settings don't allow copying pages దీని భద్రతా సెట్టింగ్‌లు పేజీలను కాపీ చేయడాన్ని అనుమతించవు
Can't be read: {e} చదవడం సాధ్యం కాదు: {e}
Leave out files that can't be read? చదవలేని ఫైల్‌లను వదిలేయాలా?
These files can't be read, so they won't be in the combined PDF: ఈ ఫైల్‌లను చదవలేము, కాబట్టి అవి కలిపిన PDFలో ఉండవు:
Combine the rest మిగిలినవి కలుపు
and {n} more మరియు మరో {n}
Includes unsaved changes సేవ్ చేయని మార్పులు ఉన్నాయి
This web address mixes letters from different alphabets, a common way to imitate another site's name. ఈ వెబ్ చిరునామాలో వేర్వేరు లిపుల అక్షరాలు కలిసి ఉన్నాయి. వేరే సైట్ పేరును అనుకరించడానికి ఇది తరచూ వాడే పద్ధతి.
This web address uses letters from another alphabet, which can look like familiar ones. ఈ వెబ్ చిరునామాలో వేరే లిపి అక్షరాలు ఉన్నాయి. అవి మీకు తెలిసిన అక్షరాల్లాగే కనిపించవచ్చు.

Can't render this file because it contains an unexpected character in line 13 and column 16.

View File

@ -1584,16 +1584,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. Відкрито, але налаштування захисту не дозволяють об’єднання. Введіть пароль дозволів.
Unlocked: the combined file won't be password-protected Розблоковано: об’єднаний файл не буде захищено паролем
Unlocked {n} of {m} files Розблоковано {n} із {m} файлів
Drop PDFs or folders here, or choose them. Перетягніть сюди PDF або теки чи виберіть їх.
Drop PDFs, images, text files or folders here, or choose them. Перетягніть сюди PDF, зображення, текстові файли або теки чи виберіть їх.
Add folder… Додати теку…
Add folder and subfolders… Додати теку й підтеки…
More ways to add files Інші способи додати файли
Every PDF in the folder Усі PDF у теці
Every PDF in the folder and the folders inside it Усі PDF у теці та її підтеках
Every PDF, image and text file in the folder Усі PDF, зображення й текстові файли в теці
Every PDF, image and text file in the folder and the folders inside it Усі PDF, зображення й текстові файли в теці та її підтеках
Add folder Додати теку
Add folder and subfolders Додати теку й підтеки
No PDFs found in {folder} У {folder} не знайдено PDF
Added the first {n} PDFs in {folder} Додано перші {n} PDF із {folder}
No PDFs, images or text files found in {folder} У {folder} не знайдено PDF, зображень або текстових файлів
Added the first {n} files in {folder} Додано перші {n} файлів із {folder}
Click to sort, drag to move the column. Drag its edge to resize it. Клацніть для сортування, перетягніть для переміщення стовпця. Тягніть за його край для зміни розміру.
Reset columns Скинути стовпці
Remove {n} files Видалити файли: {n}
@ -1611,7 +1611,7 @@
{n} files need attention Файли потребують уваги: {n}
{name} can't be combined: {why} {name} неможливо об’єднати: {why}
Check the pages of {name}: {why} Перевірте сторінки {name}: {why}
Add the PDFs to combine Додайте PDF для об’єднання
Add the files to combine Додайте файли для об’єднання
File name Назва файлу
Warnings Попередження
Drag to reorder Перетягніть для зміни порядку
@ -1624,6 +1624,10 @@
Password-protected Захищено паролем
Its security settings don't allow copying pages Налаштування захисту не дозволяють копіювати сторінки
Can't be read: {e} Не вдалося прочитати: {e}
Leave out files that can't be read? Пропустити файли, які не вдається прочитати?
These files can't be read, so they won't be in the combined PDF: Ці файли не вдається прочитати, тож їх не буде в об’єднаному PDF:
Combine the rest Об’єднати решту
and {n} more і ще {n}
Includes unsaved changes Містить незбережені зміни
Pages of {name} to combine, e.g. 1-3, 6 Сторінки з {name} для об’єднання, наприклад 1-3, 6
Remove {name} Видалити {name}

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -1554,16 +1554,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. 已打开,但其安全设置不允许合并。请输入权限密码。
Unlocked: the combined file won't be password-protected 已解锁:合并后的文件将不受密码保护
Unlocked {n} of {m} files 已解锁 {m} 个文件中的 {n} 个
Drop PDFs or folders here, or choose them. 将 PDF 或文件夹拖放到此处,或选择它们。
Drop PDFs, images, text files or folders here, or choose them. 将 PDF、图像、文本文件或文件夹拖放到此处,或选择它们。
Add folder… 添加文件夹…
Add folder and subfolders… 添加文件夹及子文件夹…
More ways to add files 更多添加文件的方式
Every PDF in the folder 文件夹中的所有 PDF
Every PDF in the folder and the folders inside it 文件夹及其子文件夹中的所有 PDF
Every PDF, image and text file in the folder 文件夹中的所有 PDF、图像和文本文件
Every PDF, image and text file in the folder and the folders inside it 文件夹及其子文件夹中的所有 PDF、图像和文本文件
Add folder 添加文件夹
Add folder and subfolders 添加文件夹及子文件夹
No PDFs found in {folder} 在 {folder} 中未找到 PDF
Added the first {n} PDFs in {folder} 已添加 {folder} 中的前 {n} 个 PDF
No PDFs, images or text files found in {folder} 在 {folder} 中未找到 PDF、图像或文本文件
Added the first {n} files in {folder} 已添加 {folder} 中的前 {n} 个文件
Click to sort, drag to move the column. Drag its edge to resize it. 单击以排序,拖动以移动列。拖动列边缘可调整宽度。
Reset columns 重置列
Remove {n} files 删除 {n} 个文件
@ -1581,7 +1581,7 @@
{n} files need attention {n} 个文件需要处理
{name} can't be combined: {why} 无法合并 {name}:{why}
Check the pages of {name}: {why} 请检查 {name} 的页码:{why}
Add the PDFs to combine 添加要合并的 PDF
Add the files to combine 添加要合并的文件
File name 文件名
Warnings 警告
Drag to reorder 拖动以重新排序
@ -1594,6 +1594,10 @@
Password-protected 受密码保护
Its security settings don't allow copying pages 其安全设置不允许复制页面
Can't be read: {e} 无法读取:{e}
Leave out files that can't be read? 要排除无法读取的文件吗?
These files can't be read, so they won't be in the combined PDF: 这些文件无法读取,因此不会包含在合并后的 PDF 中:
Combine the rest 合并其余文件
and {n} more 以及另外 {n} 个
Includes unsaved changes 包含未保存的更改
Pages of {name} to combine, e.g. 1-3, 6 要合并的{name}的页面,例如 1-3、6
Remove {name} 删除{name}

Can't render this file because it contains an unexpected character in line 9 and column 16.

View File

@ -1502,16 +1502,16 @@
Opened, but its security settings don't allow combining. Enter the permissions password. 已開啟,但其安全性設定不允許合併。請輸入權限密碼。
Unlocked: the combined file won't be password-protected 已解除鎖定:合併後的檔案將不受密碼保護
Unlocked {n} of {m} files 已解除鎖定 {m} 個檔案中的 {n} 個
Drop PDFs or folders here, or choose them. 將 PDF 或資料夾拖放到此處,或選取它們。
Drop PDFs, images, text files or folders here, or choose them. 將 PDF、圖片、文字檔案或資料夾拖放到此處,或選取它們。
Add folder… 新增資料夾…
Add folder and subfolders… 新增資料夾及子資料夾…
More ways to add files 更多新增檔案的方式
Every PDF in the folder 資料夾中的所有 PDF
Every PDF in the folder and the folders inside it 資料夾及其子資料夾中的所有 PDF
Every PDF, image and text file in the folder 資料夾中的所有 PDF、圖片和文字檔案
Every PDF, image and text file in the folder and the folders inside it 資料夾及其子資料夾中的所有 PDF、圖片和文字檔案
Add folder 新增資料夾
Add folder and subfolders 新增資料夾及子資料夾
No PDFs found in {folder} 在 {folder} 中找不到 PDF
Added the first {n} PDFs in {folder} 已新增 {folder} 中的前 {n} 個 PDF
No PDFs, images or text files found in {folder} 在 {folder} 中找不到 PDF、圖片或文字檔案
Added the first {n} files in {folder} 已新增 {folder} 中的前 {n} 個檔案
Click to sort, drag to move the column. Drag its edge to resize it. 按一下以排序,拖曳以移動欄。拖曳欄邊緣可調整寬度。
Reset columns 重設欄
Remove {n} files 刪除 {n} 個檔案
@ -1529,7 +1529,7 @@
{n} files need attention {n} 個檔案需要處理
{name} can't be combined: {why} 無法合併 {name}:{why}
Check the pages of {name}: {why} 請檢查 {name} 的頁碼:{why}
Add the PDFs to combine 新增要合併的 PDF
Add the files to combine 新增要合併的檔案
File name 檔案名稱
Warnings 警告
Drag to reorder 拖曳以重新排序
@ -1542,6 +1542,10 @@
Password-protected 受密碼保護
Its security settings don't allow copying pages 其安全性設定不允許複製頁面
Can't be read: {e} 無法讀取:{e}
Leave out files that can't be read? 要排除無法讀取的檔案嗎?
These files can't be read, so they won't be in the combined PDF: 這些檔案無法讀取,因此不會包含在合併後的 PDF 中:
Combine the rest 合併其餘檔案
and {n} more 以及另外 {n} 個
Includes unsaved changes 包含未儲存的變更
Pages of {name} to combine, e.g. 1-3, 6 要合併的{name}的頁面,例如 1-3、6
Remove {name} 刪除{name}

Can't render this file because it contains an unexpected character in line 9 and column 16.

View File

@ -735,7 +735,7 @@ fn combine_files_opens_in_its_own_tab() {
h.state_mut().execute("page.combine");
h.run_steps(3);
assert!(h.state().combine_showing() && h.state().active.is_none());
h.get_by_label("Add the PDFs to combine");
h.get_by_label("Add the files to combine");
// Back to the document, then to Combine through its tab.
h.get_by_label("doc.pdf").click();
h.run_steps(3);
@ -991,33 +991,93 @@ fn columns_resize_and_the_layout_is_kept_in_the_settings() {
}
#[test]
fn a_folder_adds_its_pdfs_in_order_and_optionally_its_subfolders() {
fn a_folder_adds_its_pdfs_images_and_text_in_order_and_optionally_its_subfolders() {
let dir = std::env::temp_dir().join(format!("pdfcraft-combine-folder-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("sub")).unwrap();
for (name, pages) in [("page10.pdf", 1), ("page2.PDF", 2), ("sub/inner.pdf", 3)] {
std::fs::write(dir.join(name), fixture(pages)).unwrap();
}
std::fs::write(dir.join("notes.txt"), b"not a pdf").unwrap();
// #569: images and text files join too; other files are left out.
std::fs::write(dir.join("notes.txt"), b"Some notes").unwrap();
std::fs::write(dir.join("readme.md"), b"# not combinable").unwrap();
let mut h = harness(1, |_| {});
h.state_mut().execute("page.combine");
h.run_steps(2);
h.state_mut().pick_override = Some(vec![dir.to_string_lossy().into_owned()]);
h.get_by_label("Add folder…").click();
h.run_steps(4);
assert_eq!(combine_names(&h), ["page2.PDF", "page10.pdf"], "PDFs only, numbers by value");
assert_eq!(combine_names(&h), ["notes.txt", "page2.PDF", "page10.pdf"], "numbers by value");
// The toolbar's menu: the folder and its subfolders, as one undo step.
h.get_by_label("More ways to add files").click();
h.run_steps(2);
h.get_by_label("Add folder and subfolders…").click();
h.run_steps(4);
assert_eq!(combine_names(&h), ["page2.PDF", "page10.pdf", "page2.PDF", "page10.pdf", "inner.pdf"]);
assert_eq!(combine_names(&h), ["notes.txt", "page2.PDF", "page10.pdf", "notes.txt", "page2.PDF", "page10.pdf", "inner.pdf"]);
h.key_press_modifiers(Modifiers::COMMAND, Key::Z);
h.run_steps(2);
assert_eq!(combine_names(&h).len(), 2);
assert_eq!(combine_names(&h).len(), 3);
let _ = std::fs::remove_dir_all(&dir);
}
/// #569: an image or a text file added to Combine files becomes a PDF as it joins the list, an
/// ordinary row with its pages.
#[test]
fn images_and_text_files_join_the_combine_list_as_pdfs() {
let mut png = std::io::Cursor::new(Vec::new());
image::RgbImage::from_pixel(40, 20, image::Rgb([90, 120, 200])).write_to(&mut png, image::ImageFormat::Png).unwrap();
let mut h = combine_of(&[("one.pdf", 2)]);
h.state_mut()
.use_files(pdfcraft_ui_egui::FilePurpose::Combine, vec![("scan.png".into(), png.into_inner()), ("notes.txt".into(), b"Some notes".to_vec())]);
h.run_steps(3);
assert_eq!(combine_names(&h), ["one.pdf", "scan.png", "notes.txt"]);
let draft = &h.state().combine_draft;
assert!(draft.iter().all(|f| f.bytes.starts_with(b"%PDF-") && f.problem.is_none()), "made into PDFs");
assert_eq!(draft.iter().map(|f| f.pages).collect::<Vec<_>>(), [2, 1, 1]);
h.get_by_label("Combine").click();
h.run_steps(3);
assert_eq!(texts_of(h.state(), 1), ["Page 1", "Page 2", "", "Some notes"]);
}
/// A file that can't be read (a damaged image, a type that can't be made into a PDF) joins the
/// list all the same, with why; Combine asks before leaving it out, and Cancel changes nothing.
#[test]
fn files_that_cannot_be_read_are_listed_and_left_out_once_the_user_agrees() {
let mut h = combine_of(&[("one.pdf", 2)]);
let bad = vec![("broken.jpg".to_string(), b"\xff\xd8\xff\xe0 not a JPEG".to_vec()), ("report.docx".to_string(), b"PK\x03\x04".to_vec())];
h.state_mut().use_files(pdfcraft_ui_egui::FilePurpose::Combine, bad);
h.run_steps(3);
assert_eq!(combine_names(&h), ["one.pdf", "broken.jpg", "report.docx"]);
for f in &h.state().combine_draft[1..] {
let why = f.problem.as_deref().expect("why it can't be combined");
assert!(why.starts_with("Can't be read: ") && !why.contains(&f.name), "{why}");
assert!(f.lock.is_none() && f.pages == 0);
}
h.get_by_label_contains("this file type can't be converted");
// One readable file is not enough to combine.
assert!(h.get_by_label("Combine").accesskit_node().is_disabled());
h.state_mut().use_files(pdfcraft_ui_egui::FilePurpose::Combine, vec![("two.pdf".into(), fixture(1))]);
h.run_steps(3);
assert!(!h.get_by_label("Combine").accesskit_node().is_disabled());
// Combine asks first, naming the files left out; Cancel leaves everything as it was.
h.get_by_label("Combine").click();
h.run_steps(3);
h.get_by_label("Leave out files that can't be read?");
h.get_by_label("• broken.jpg");
h.get_by_label("• report.docx");
h.get_by_label("Cancel").click();
h.run_steps(3);
assert!(h.query_by_label("Leave out files that can't be read?").is_none());
assert_eq!(h.state().views.len(), 1, "nothing combined");
assert_eq!(combine_names(&h).len(), 4);
// Asked again: Combine the rest.
h.get_by_label("Combine").click();
h.run_steps(3);
h.get_by_label("Combine the rest").click();
h.run_steps(3);
assert_eq!(texts_of(h.state(), 1), ["Page 1", "Page 2", "Page 1"]);
}
fn type_password(h: &mut Harness<'static, PdfCraftApp>, password: &str) {
let field = h.get_by_role(Role::PasswordInput);
field.focus();

View File

@ -2914,8 +2914,8 @@ milestone = "M4"
status = "shipped"
commands = ["page.combine"]
tools = ["doc_combine"]
evidence = ["crates/organize/src/tests.rs::combine_concatenates_with_a_bookmark_per_file", "crates/ui-egui/tests/editing.rs::combining_files_opens_a_new_unsaved_tab", "crates/ui-egui/tests/editing.rs::combine_files_opens_in_its_own_tab", "crates/ui-egui/tests/editing.rs::combine_lists_size_and_warns_before_combining", "crates/ui-egui/tests/editing.rs::a_password_protected_file_is_unlocked_with_its_password_and_combined", "crates/ui-egui/tests/editing.rs::a_folder_adds_its_pdfs_in_order_and_optionally_its_subfolders", "crates/ui-egui/tests/ui.rs::pdfs_dropped_on_the_combine_tab_join_its_list", "crates/automation/tests/automation.rs::combine_extract_and_split", "crates/automation/tests/automation.rs::combine_opens_protected_files_with_their_passwords", "crates/organize/src/tests.rs::combine_keeps_pdfx_when_every_source_agrees", "crates/organize/src/tests.rs::combine_claims_no_pdfx_when_the_sources_disagree"]
notes = "Combine files is its own tab: a table of the files (name, pages, size, modified, warnings) with column sorting, resizing and moving; files, folders (with subfolders) and open documents can be added, or dropped on the tab. Problems show before Combine (password, security settings that withhold page assembly, bad page ranges, duplicates); protected files are unlocked with their open or permissions password (also doc_combine passwords). The result is never encrypted, and is not yet offered for protection in the same step. A PDF/X result (output intent, GTS_PDFXVersion, Trapped, XMP pdfxid) only when every source declares the same version and output intent (#263). When they differ the result claims none, and nothing reports that yet; Acrobat behaviour not observed."
evidence = ["crates/organize/src/tests.rs::combine_concatenates_with_a_bookmark_per_file", "crates/ui-egui/tests/editing.rs::combining_files_opens_a_new_unsaved_tab", "crates/ui-egui/tests/editing.rs::combine_files_opens_in_its_own_tab", "crates/ui-egui/tests/editing.rs::combine_lists_size_and_warns_before_combining", "crates/ui-egui/tests/editing.rs::a_password_protected_file_is_unlocked_with_its_password_and_combined", "crates/ui-egui/tests/editing.rs::a_folder_adds_its_pdfs_images_and_text_in_order_and_optionally_its_subfolders", "crates/ui-egui/tests/editing.rs::images_and_text_files_join_the_combine_list_as_pdfs", "crates/ui-egui/tests/editing.rs::files_that_cannot_be_read_are_listed_and_left_out_once_the_user_agrees", "crates/ui-egui/tests/ui.rs::pdfs_dropped_on_the_combine_tab_join_its_list", "crates/automation/tests/automation.rs::combine_extract_and_split", "crates/automation/tests/automation.rs::combine_opens_protected_files_with_their_passwords", "crates/automation/tests/automation.rs::combine_takes_images_and_text_files", "crates/engine/src/tests.rs::files_for_combine_are_made_into_pdfs_and_pdfs_pass_through", "crates/organize/src/tests.rs::combine_keeps_pdfx_when_every_source_agrees", "crates/organize/src/tests.rs::combine_claims_no_pdfx_when_the_sources_disagree"]
notes = "Combine files is its own tab: a table of the files (name, pages, size, modified, warnings) with column sorting, resizing and moving; files, folders (with subfolders) and open documents can be added, or dropped on the tab. Images (PNG, JPEG, TIFF, GIF, BMP, JPEG 2000) and .txt files become PDFs as they are added (#569; also in doc_combine). A file that can't be read stays in the list with why, and Combine asks before leaving it out; Office documents, HTML and other formats are not converted yet. Problems show before Combine (password, security settings that withhold page assembly, bad page ranges, duplicates); protected files are unlocked with their open or permissions password (also doc_combine passwords). The result is never encrypted, and is not yet offered for protection in the same step. A PDF/X result (output intent, GTS_PDFXVersion, Trapped, XMP pdfxid) only when every source declares the same version and output intent (#263). When they differ the result claims none, and nothing reports that yet; Acrobat behaviour not observed."
[[feature]]
id = "organize.combine-bookmarks"