mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 04:22:20 +00:00
fix(ui): list X.PDF in file pickers on Linux and the BSDs (#5481)
rfd 0.17 gives each filter extension to the XDG portal, GTK and Zenity as the glob `*.<ext>`, which those match case-sensitively, so a "PDF" filter of `pdf` hid `Report.PDF`. `AddFilterAnyCase::add_filter_any_case` passes each letter as a bracket class there (`pdf` -> `[pP][dD][fF]`) and the extension unchanged on Windows, macOS and the web, which already ignore case. Every picker filter in ui-egui now goes through it; the imports used only by native pickers are gated off wasm. Fixes #5481 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
This commit is contained in:
1 parent
fa5f9f8c3b
commit
9ca051ac06
16 files changed
+116
-37
No files matched your search
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
|
||||
/// Whether `a` and `b` name the same file: equal, or equal once made absolute against the
|
||||
/// current directory (links are not resolved, as in `last_session`).
|
||||
@@ -607,7 +608,9 @@ impl PdfCraftApp {
|
||||
/// The File ▸ Open picker: PDFs, and images and text that become PDFs.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(super) fn open_picker() -> rfd::AsyncFileDialog {
|
||||
rfd::AsyncFileDialog::new().add_filter("PDF", &["pdf"]).add_filter(tl!("Images and text (converted to PDF)"), &create_ui::CONVERTIBLE)
|
||||
rfd::AsyncFileDialog::new()
|
||||
.add_filter_any_case("PDF", &["pdf"])
|
||||
.add_filter_any_case(tl!("Images and text (converted to PDF)"), &create_ui::CONVERTIBLE)
|
||||
}
|
||||
|
||||
pub fn open_dialog(&mut self) {
|
||||
@@ -615,7 +618,9 @@ impl PdfCraftApp {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.pick(
|
||||
pickers::PickFor::Open,
|
||||
rfd::AsyncFileDialog::new().add_filter("PDF", &["pdf"]).add_filter(tl!("Images and text (converted to PDF)"), &create_ui::CONVERTIBLE),
|
||||
rfd::AsyncFileDialog::new()
|
||||
.add_filter_any_case("PDF", &["pdf"])
|
||||
.add_filter_any_case(tl!("Images and text (converted to PDF)"), &create_ui::CONVERTIBLE),
|
||||
true,
|
||||
);
|
||||
// Browsers pick files asynchronously; the bytes arrive through `inbox`.
|
||||
@@ -625,8 +630,8 @@ impl PdfCraftApp {
|
||||
let ctx = self.ctx.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
if let Some(h) = rfd::AsyncFileDialog::new()
|
||||
.add_filter("PDF", &["pdf"])
|
||||
.add_filter(tl!("Images and text"), &create_ui::CONVERTIBLE)
|
||||
.add_filter_any_case("PDF", &["pdf"])
|
||||
.add_filter_any_case(tl!("Images and text"), &create_ui::CONVERTIBLE)
|
||||
.pick_file()
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! page the range selects, with the marks laid out on it. "Update" opens the same dialog in
|
||||
//! replace mode.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use egui::{Align, Color32, CornerRadius, Layout, Rect, Stroke, pos2, vec2};
|
||||
@@ -777,7 +779,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: M
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if browse {
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.add_filter(tl!("PDF or image"), &["pdf", "png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"]);
|
||||
.add_filter_any_case(tl!("PDF or image"), &["pdf", "png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"]);
|
||||
let epoch = app.dialog_epoch();
|
||||
app.ask_one(crate::pickers::Ask::File(dialog), None, move |app, p| {
|
||||
// Only into the dialog that asked: not one closed meanwhile, or opened again since.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::{PdfCraftApp, StagingName, staging_suffixes};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
|
||||
/// A save held back by [`Dialog::OverwriteChanged`](crate::Dialog::OverwriteChanged): the file
|
||||
/// on disk is another version than the document came from (#2166).
|
||||
@@ -58,7 +60,7 @@ impl PdfCraftApp {
|
||||
(SaveTarget::InPlace, Some(p), _) if !is_mail_temp_copy(&p) => p,
|
||||
_ => {
|
||||
let name = if name.to_ascii_lowercase().ends_with(".pdf") { name } else { format!("{name}.pdf") };
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter("PDF", &["pdf"]).set_file_name(name);
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case("PDF", &["pdf"]).set_file_name(name);
|
||||
let dialog = in_document_folder(dialog, doc_path.as_deref());
|
||||
// The bytes are taken once the user has chosen, so edits made meanwhile are saved too.
|
||||
self.ask_one(crate::pickers::Ask::Save(dialog), None, move |app, dest| {
|
||||
|
||||
@@ -119,6 +119,7 @@ pub use persist::units;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use persist::window_state;
|
||||
use platform::drag_pointer;
|
||||
use platform::file_filter;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use platform::file_watch;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub(super) mod drag_pointer;
|
||||
pub(super) mod file_filter;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod file_watch;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//! Picker filters that list a file whatever the letter case of its extension.
|
||||
//!
|
||||
//! On Linux and the BSDs, rfd gives each extension to the XDG portal, GTK or Zenity as the glob
|
||||
//! `*.<ext>`, and those match it case-sensitively: a "PDF" filter of `pdf` hid `Report.PDF`.
|
||||
//! Windows, macOS and the browser already ignore case, so there the extension passes unchanged.
|
||||
|
||||
/// Whether the platform's pickers match filter globs case-sensitively.
|
||||
const CASE_SENSITIVE_GLOBS: bool = cfg!(all(unix, not(target_os = "macos")));
|
||||
|
||||
/// [`rfd::AsyncFileDialog::add_filter`], with each extension matched in any letter case.
|
||||
pub(crate) trait AddFilterAnyCase {
|
||||
#[must_use]
|
||||
fn add_filter_any_case(self, name: impl Into<String>, extensions: &[impl AsRef<str>]) -> Self;
|
||||
}
|
||||
|
||||
impl AddFilterAnyCase for rfd::AsyncFileDialog {
|
||||
fn add_filter_any_case(self, name: impl Into<String>, extensions: &[impl AsRef<str>]) -> Self {
|
||||
let globs: Vec<String> = extensions.iter().map(|e| any_case(e.as_ref(), CASE_SENSITIVE_GLOBS)).collect();
|
||||
self.add_filter(name, &globs)
|
||||
}
|
||||
}
|
||||
|
||||
/// `ext` as a glob fragment matching any letter case: `pdf` becomes `[pP][dD][fF]`.
|
||||
fn any_case(ext: &str, case_sensitive: bool) -> String {
|
||||
if !case_sensitive {
|
||||
return ext.to_owned();
|
||||
}
|
||||
ext.chars()
|
||||
.map(|c| if c.is_ascii_alphabetic() { format!("[{}{}]", c.to_ascii_lowercase(), c.to_ascii_uppercase()) } else { c.to_string() })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::any_case;
|
||||
|
||||
#[test]
|
||||
fn a_case_sensitive_picker_gets_a_glob_matching_every_case() {
|
||||
assert_eq!(any_case("pdf", true), "[pP][dD][fF]");
|
||||
assert_eq!(any_case("PDF", true), "[pP][dD][fF]");
|
||||
assert_eq!(any_case("jp2", true), "[jJ][pP]2");
|
||||
assert_eq!(any_case("*", true), "*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_case_blind_picker_gets_the_extension_unchanged() {
|
||||
assert_eq!(any_case("pdf", false), "pdf");
|
||||
assert_eq!(any_case("*", false), "*");
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
//! (images keep their proportions), double-click text to retype it, format it from the panel,
|
||||
//! Delete to remove it. Each change is one undoable engine edit.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use egui::{Color32, CornerRadius, Pos2, Rect, Stroke};
|
||||
use pdfcraft_engine::{Added, AddedContent, AddedText, Edit};
|
||||
use pdfcraft_render::DocInfo;
|
||||
@@ -849,7 +851,7 @@ impl crate::PdfCraftApp {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.add_filter(tl!("Images"), &["png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"])
|
||||
.add_filter_any_case(tl!("Images"), &["png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"])
|
||||
.set_title(title);
|
||||
let target = self.active_ids().map(|(_, id)| id);
|
||||
self.ask_one(crate::pickers::Ask::File(dialog), target, read);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! signature. Everything is an annotation (typewriter text, PdfCraft-drawn stamps, ink), so it
|
||||
//! can be moved, deleted and undone like any comment.
|
||||
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use egui::{Color32, CornerRadius, Pos2, Sense, Stroke, pos2, vec2};
|
||||
use pdfcraft_engine::signature_scan::{DEFAULT_SENSITIVITY, PREVIEW_MAX_SIDE, SCAN_MAX_SIDE, ScanError};
|
||||
use pdfcraft_engine::{Edit, FillMark, NewAnnotation, PhotoRect, Shape, SignatureImage, SignaturePhoto, Style, Turn};
|
||||
@@ -780,7 +781,7 @@ impl crate::PdfCraftApp {
|
||||
let epoch = self.dialog_epoch();
|
||||
let initials = self.signature_draft.initials;
|
||||
let scan = self.signature_draft.scan_mode;
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter(tl!(if scan { "Photo" } else { "Image" }), &["png", "jpg", "jpeg"]);
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case(tl!(if scan { "Photo" } else { "Image" }), &["png", "jpg", "jpeg"]);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.ask_one(crate::pickers::Ask::File(dialog), None, move |app, path| {
|
||||
if app.dialog_epoch() != epoch
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//! Measure objects: page-space clicks, live readings and persistent drawing scales.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use crate::{LeftPanel, PdfCraftApp, QuickTool, canvas::DocView, page_cursor::Layer, theme::Tokens, widgets};
|
||||
use egui::{Color32, Pos2, Stroke};
|
||||
use pdfcraft_engine::{
|
||||
@@ -613,7 +615,7 @@ fn export_csv(app: &mut PdfCraftApp) {
|
||||
match app.export_dir_override.as_ref().map(|d| std::path::PathBuf::from(d).join("measurements.csv")) {
|
||||
Some(path) => write(app, path),
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new().set_file_name("measurements.csv").add_filter("CSV", &["csv"]);
|
||||
let dialog = rfd::AsyncFileDialog::new().set_file_name("measurements.csv").add_filter_any_case("CSV", &["csv"]);
|
||||
app.ask_one(crate::pickers::Ask::Save(dialog), None, write);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//! click an empty signature field), the Sign with a Digital ID / Configure New Digital ID /
|
||||
//! Sign as dialogs, the Signatures panel, and the signature message bar.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use egui::{Align, Color32, CornerRadius, Layout, Pos2, Rect, Stroke, vec2};
|
||||
@@ -501,7 +503,7 @@ impl PdfCraftApp {
|
||||
Some(p) => sign_and_save(self, PathBuf::from(p)),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter("PDF", &["pdf"]).set_file_name(format!("{stem}_signed.pdf"));
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case("PDF", &["pdf"]).set_file_name(format!("{stem}_signed.pdf"));
|
||||
let dialog = crate::editing::in_document_folder(dialog, folder.as_deref());
|
||||
// The signature's page, rectangle and field refer to the document as it is now:
|
||||
// the pick is dropped (with a notice) if it is edited or switched away from
|
||||
@@ -533,7 +535,7 @@ impl PdfCraftApp {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn pick_sign_graphic_image(&mut self) {
|
||||
let epoch = self.dialog_epoch();
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter(tl!("Image"), &["png", "jpg", "jpeg"]);
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case(tl!("Image"), &["png", "jpg", "jpeg"]);
|
||||
self.ask_one(crate::pickers::Ask::File(dialog), None, move |app, path| {
|
||||
if app.dialog_epoch() != epoch || app.dialog != Some(crate::Dialog::Sign) {
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,8 @@ use super::{
|
||||
Align, Appearance, AppearanceLabels, Color32, CornerRadius, KEY_ALGORITHMS, Layout, NewIdDraft, PdfCraftApp, Rect, SavedSig, SignGraphic,
|
||||
SignStep, Stroke, Tokens, icons, sign, theme, vec2, widgets,
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
|
||||
/// Draw the signing dialogs; returns `true` when the dialog should close.
|
||||
pub(crate) fn dialog(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
||||
@@ -233,7 +235,7 @@ fn configure(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool {
|
||||
// The picker answers on a later frame, into the draft if the same dialog is still open.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if browse {
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter(tl!("Digital ID"), &["p12", "pfx"]);
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case(tl!("Digital ID"), &["p12", "pfx"]);
|
||||
let epoch = app.dialog_epoch();
|
||||
app.ask_one(crate::pickers::Ask::File(dialog), None, move |app, p| {
|
||||
// Not one closed meanwhile, or opened again since.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Custom stamps (Add a stamp ▸ Custom stamps ▸ Create): a PDF page or an image saved in the
|
||||
//! user's stamp library under a category and a name, then placed like any stamp.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::{Align, Layout};
|
||||
@@ -145,7 +147,7 @@ impl PdfCraftApp {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.add_filter(tl!("PDF or image"), &["pdf", "png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"])
|
||||
.add_filter_any_case(tl!("PDF or image"), &["pdf", "png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"])
|
||||
.set_title(tl!("Select a file for the stamp"));
|
||||
self.ask_one(crate::pickers::Ask::File(dialog), None, read);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Action Wizard: the actions (PdfCraft's and your own), running one over files in the
|
||||
//! background, and creating or editing an action's steps. Your actions are remembered.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use egui::{Align, Layout};
|
||||
@@ -186,7 +188,7 @@ impl PdfCraftApp {
|
||||
match self.action_files_override.clone() {
|
||||
Some(p) => run(self, p.into_iter().map(std::path::PathBuf::from).collect()),
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new().set_title(title).add_filter("PDF", &["pdf"]);
|
||||
let dialog = rfd::AsyncFileDialog::new().set_title(title).add_filter_any_case("PDF", &["pdf"]);
|
||||
self.ask(crate::pickers::Ask::Files(dialog), None, run);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Create a PDF (blank, from images, from text) and Reduce File Size (execution plan M10.2,
|
||||
//! M11.1). Opening an image or a text file converts it to a new, unsaved PDF, as Acrobat does.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::PdfCraftApp;
|
||||
@@ -154,7 +156,7 @@ impl PdfCraftApp {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.add_filter(tl!("Images"), &["png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"])
|
||||
.add_filter_any_case(tl!("Images"), &["png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "jp2", "j2k", "jpx"])
|
||||
.set_title(tl!("Choose images"));
|
||||
self.ask(crate::pickers::Ask::Files(dialog), None, |app, files| {
|
||||
let mut images = Vec::new();
|
||||
@@ -285,7 +287,7 @@ impl PdfCraftApp {
|
||||
match self.save_override.clone() {
|
||||
Some(p) => write(self, p),
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter("PDF", &["pdf"]).set_file_name(&name);
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case("PDF", &["pdf"]).set_file_name(&name);
|
||||
let dialog = crate::editing::in_document_folder(dialog, folder.as_deref());
|
||||
self.ask_one(crate::pickers::Ask::Save(dialog), None, move |app, p| write(app, p.to_string_lossy().into_owned()));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Files are picked asynchronously and used on a later frame: desktop builds through `pickers`
|
||||
//! (a blocking picker crashes the app on macOS), browsers through `requests`.
|
||||
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use std::sync::Arc;
|
||||
|
||||
use pdfcraft_engine::{DocId, Edit, SplitBy};
|
||||
@@ -61,16 +62,16 @@ pub(crate) fn installed_font_files(ctx: &egui::Context) -> Option<Vec<pdfcraft_e
|
||||
fn files_picker(purpose: FilePurpose) -> rfd::AsyncFileDialog {
|
||||
let dialog = rfd::AsyncFileDialog::new();
|
||||
if matches!(purpose, FilePurpose::TextFont { .. }) {
|
||||
return dialog.add_filter("TrueType / OpenType font", &["ttf", "otf"]);
|
||||
return dialog.add_filter_any_case("TrueType / OpenType font", &["ttf", "otf"]);
|
||||
}
|
||||
if purpose == FilePurpose::RedactWords {
|
||||
return dialog.add_filter(tl!("Text"), &["txt"]);
|
||||
return dialog.add_filter_any_case(tl!("Text"), &["txt"]);
|
||||
}
|
||||
if !matches!(purpose, FilePurpose::CreateMultiple | FilePurpose::CreateMultipleSeparate | FilePurpose::InsertPages | FilePurpose::Combine) {
|
||||
return dialog.add_filter("PDF", &["pdf"]);
|
||||
return dialog.add_filter_any_case("PDF", &["pdf"]);
|
||||
}
|
||||
let all: Vec<&str> = std::iter::once("pdf").chain(pdfcraft_engine::CONVERTIBLE).collect();
|
||||
dialog.add_filter(tl!("PDF, images and text"), &all).add_filter("PDF", &["pdf"])
|
||||
dialog.add_filter_any_case(tl!("PDF, images and text"), &all).add_filter_any_case("PDF", &["pdf"])
|
||||
}
|
||||
|
||||
/// The Replace Pages dialog: the chosen file and the ranges (1-based, inclusive).
|
||||
@@ -898,7 +899,7 @@ impl PdfCraftApp {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.add_filter(tl!("Comment and form data").to_string(), &extensions)
|
||||
.add_filter_any_case(tl!("Comment and form data").to_string(), &extensions)
|
||||
.set_title(tl!("Import data").to_string());
|
||||
let target = self.active_ids().map(|(_, id)| id);
|
||||
self.ask_one(crate::pickers::Ask::File(dialog), target, import);
|
||||
@@ -939,7 +940,7 @@ impl PdfCraftApp {
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.set_title(tl!("Export").to_string())
|
||||
.add_filter(ext.to_uppercase(), &[ext])
|
||||
.add_filter_any_case(ext.to_uppercase(), &[ext])
|
||||
.set_file_name(format!("{stem}.{ext}"));
|
||||
let dialog = crate::editing::in_document_folder(dialog, folder.as_deref());
|
||||
self.ask_one(crate::pickers::Ask::Save(dialog), None, write);
|
||||
@@ -959,7 +960,7 @@ impl PdfCraftApp {
|
||||
{
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.set_title(tl!("Select data files to merge").to_string())
|
||||
.add_filter(tl!("Form data and PDF forms").to_string(), &["fdf", "xfdf", "pdf"]);
|
||||
.add_filter_any_case(tl!("Form data and PDF forms").to_string(), &["fdf", "xfdf", "pdf"]);
|
||||
self.ask(crate::pickers::Ask::Files(dialog), None, |app, paths| {
|
||||
let mut files = Vec::new();
|
||||
for p in paths {
|
||||
@@ -1004,7 +1005,7 @@ impl PdfCraftApp {
|
||||
None => {
|
||||
let dialog = rfd::AsyncFileDialog::new()
|
||||
.set_title(tl!("Save the spreadsheet").to_string())
|
||||
.add_filter("CSV", &["csv"])
|
||||
.add_filter_any_case("CSV", &["csv"])
|
||||
.set_file_name("report.csv");
|
||||
self.ask_one(crate::pickers::Ask::Save(dialog), None, write);
|
||||
}
|
||||
@@ -1047,22 +1048,22 @@ impl PdfCraftApp {
|
||||
let title = if comments { tl!("Export comments") } else { tl!("Export form data") };
|
||||
let d = rfd::AsyncFileDialog::new().set_title(title).set_file_name(format!("{stem}.xfdf"));
|
||||
let d = if comments {
|
||||
d.add_filter("XFDF", &["xfdf"]).add_filter("FDF", &["fdf"])
|
||||
d.add_filter_any_case("XFDF", &["xfdf"]).add_filter_any_case("FDF", &["fdf"])
|
||||
} else if xfa {
|
||||
// An XFA form's data, as Acrobat exports it, first.
|
||||
d.set_file_name(format!("{stem}.xml"))
|
||||
.add_filter("XML", &["xml"])
|
||||
.add_filter("XDP", &["xdp"])
|
||||
.add_filter("XFDF", &["xfdf"])
|
||||
.add_filter("FDF", &["fdf"])
|
||||
.add_filter("CSV", &["csv"])
|
||||
.add_filter(tl!("Text").to_string(), &["txt"])
|
||||
.add_filter_any_case("XML", &["xml"])
|
||||
.add_filter_any_case("XDP", &["xdp"])
|
||||
.add_filter_any_case("XFDF", &["xfdf"])
|
||||
.add_filter_any_case("FDF", &["fdf"])
|
||||
.add_filter_any_case("CSV", &["csv"])
|
||||
.add_filter_any_case(tl!("Text").to_string(), &["txt"])
|
||||
} else {
|
||||
d.add_filter("XFDF", &["xfdf"])
|
||||
.add_filter("FDF", &["fdf"])
|
||||
.add_filter("XML", &["xml"])
|
||||
.add_filter("CSV", &["csv"])
|
||||
.add_filter(tl!("Text").to_string(), &["txt"])
|
||||
d.add_filter_any_case("XFDF", &["xfdf"])
|
||||
.add_filter_any_case("FDF", &["fdf"])
|
||||
.add_filter_any_case("XML", &["xml"])
|
||||
.add_filter_any_case("CSV", &["csv"])
|
||||
.add_filter_any_case(tl!("Text").to_string(), &["txt"])
|
||||
};
|
||||
let d = crate::editing::in_document_folder(d, folder.as_deref());
|
||||
self.ask_one(crate::pickers::Ask::Save(d), None, write);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
//! (Size, Poster, Multiple, Booklet); orientation; comments & forms; and a live preview of the
|
||||
//! sheets. Printing sends the print-ready PDF to the system spooler; "Save as PDF" writes it.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::file_filter::AddFilterAnyCase as _;
|
||||
use egui::{Color32, Pos2, Rect, Stroke, pos2, vec2};
|
||||
use pdfcraft_engine::print::{self, Binding, BookletSubset, Content, Layout, Orientation, PAPERS, PageOrder, SizeMode, Subset, spool};
|
||||
|
||||
@@ -551,7 +553,7 @@ impl PdfCraftApp {
|
||||
Some(p) => write(self, p.into()),
|
||||
None => {
|
||||
let stem = name.trim_end_matches(".pdf").trim_end_matches(".PDF");
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter("PDF", &["pdf"]).set_file_name(format!("{stem} (print).pdf"));
|
||||
let dialog = rfd::AsyncFileDialog::new().add_filter_any_case("PDF", &["pdf"]).set_file_name(format!("{stem} (print).pdf"));
|
||||
self.ask_one(crate::pickers::Ask::Save(dialog), None, move |app, path| {
|
||||
write(app, path);
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user