Issue #157: the Windows portable zip keeps settings, logs and recovery beside pdfcraft.exe (#325)

* Issue #157: the Windows portable zip keeps settings, logs and recovery beside pdfcraft.exe

The portable zip had no portable.txt, and the app had no portable mode: it always wrote %APPDATA%\PdfCraft and %LOCALAPPDATA%\PdfCraft. Now portable.txt (or PdfCraft.portable) beside the executable puts the settings, log files, crash recovery and new digital IDs in PdfCraftData next to it, as PhotoCraft does, and the zip ships portable.txt. An unwritable PdfCraftData falls back to the per-user folders and says so.

* Issue #157: update ATTRIBUTION.toml SHA-256 for the translation catalogs this PR changes

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
MarekSpodBiedry
2026-10-08 17:29:59 -04:00
committed by GitHub
co-authored by Brandon Thomas
parent acb8e96aff
commit 2f42c743b9
18 changed files with 239 additions and 18 deletions
+7 -7
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 = "8792b00c7e9a12a247cbf6d4758d8583972f861010f4e4ed52949978e5a380e7"
sha256 = "e14c411ac69fd96279de51aaaa9589b8810e2970c3c212585bf47104f7c2cbd7"
[[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 = "9429421177790a0492b1513102823a9aa5dbd34c7a850a737792602d4d19b8d9"
sha256 = "27715eb6c29938938a39880f5ab6f3b0aa60ba6d0940609af1bfb96f1fd124de"
[[asset]]
path = "crates/ui-egui/src/i18n/cs.tsv"
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "9a655daa584e3a5d049dbaaa30229ae7a931984b0f1c7f0177fde2ccdd6804ca"
sha256 = "3bc4e3c1d1a50c50d6be907f62e1fa2fd85ea719f1b65af21eeadcbbc7db5e44"
[[asset]]
path = "crates/ui-egui/src/i18n/pt-br.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 = "777a7f441950714a61d41e5a44405209aa85bfdd4b46e27090544eb9122d715f"
sha256 = "3a46800299b34558642676d0d67ff31619d8e9bbeb2ba83e713a108f544f2d82"
[[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 = "cb3759b57445a3948a707aaf9423338053c7e7ad68f19f52583527dae3dfabee"
sha256 = "dad2013f25795cafc93d89c9ff4c0cc510ba844628b27469fe9b38c2fce5e0ea"
[[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 = "b131025cb6c14135440968f08ab6334d70dd3b20271539471878d3825ecc9629"
sha256 = "c41079a024a360951e0f935b14b6d18a16c8478daac21bc7a1c57eb73b6b3590"
[[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 = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c9879375eed52003bb16790e687808fe0d50dd9405390be383eab66cd513291c"
sha256 = "f42c77a513e83f4d48e1792bcba8dfe60b7453daf1b240d0a7ffb6d276ce98e3"
[[asset]]
path = "assets/icons/accessibility.svg"
+4
View File
@@ -420,6 +420,10 @@ The honest assessment by area, what's lacking and where we're going are in **[RO
Installers and executables are code-signed.
The portable zip runs from any folder, a USB stick included. Its `portable.txt` keeps the settings,
logs and crash recovery in a `PdfCraftData` folder next to `pdfcraft.exe`, so nothing is written to
`%APPDATA%`; delete that file to use the normal per-user folders.
The MSI installs for all users and requires administrator privileges. For unattended deployment
without a desktop shortcut, run from an elevated terminal:
+18 -3
View File
@@ -39,16 +39,24 @@ const APP_ICON_PNG: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256
/// The app was called PrintCraft before; settings saved then are under this key.
const LEGACY_STORAGE_KEY: &str = "printcraft";
/// The settings folder: `app.ron` and the `logs` folder (docs/development.md). eframe would
/// otherwise derive it from the app id; keep it under "PdfCraft".
/// The settings folder: `app.ron` and the `logs` folder (docs/development.md). In portable mode
/// it is `PdfCraftData` beside the executable (#157). eframe would otherwise derive it from the
/// app id; keep it under "PdfCraft".
fn settings_dir() -> Option<std::path::PathBuf> {
if let Some(dir) = pdfcraft_ui_egui::portable::data_dir() {
return Some(dir.to_path_buf());
}
eframe::storage_dir("PdfCraft")
}
/// Move the settings and crash-recovery folders of the app's former name, PrintCraft, to the new
/// name once, so an upgrade keeps recent files, preferences and unsaved work. Best effort: a
/// folder is left alone when the new one already exists or the move fails.
/// folder is left alone when the new one already exists or the move fails. A portable copy leaves
/// the per-user folders alone.
fn migrate_legacy_folders() {
if pdfcraft_ui_egui::portable::data_dir().is_some() {
return;
}
let mut moves = vec![(eframe::storage_dir("PrintCraft"), settings_dir())];
// Recovery lives in the settings folder except on Windows, where it is under %LOCALAPPDATA%.
if cfg!(windows) {
@@ -173,6 +181,13 @@ fn main() -> eframe::Result {
if let Some(dir) = pdfcraft_ui_egui::RecoveryStore::default_dir() {
app.enable_recovery(pdfcraft_ui_egui::RecoveryStore::new(dir));
}
// A portable marker whose data folder can't be written (#157): say where settings went.
if let Some(w) = &pdfcraft_ui_egui::portable::current().unwritable {
app.notify_fmt(
"Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead.",
&[("folder", &w.folder.display().to_string()), ("error", &w.error)],
);
}
if create_images {
if let Err(e) = app.begin_image_import_paths(&files) {
app.notify(e);
+1
View File
@@ -108,3 +108,4 @@
Used when a PDF doesn't ask for a layout or zoom. Continuous scrolling never snaps between pages. Používá se, když PDF nepožaduje rozložení ani zvětšení. Plynulé posouvání mezi stránkami neskáče.
Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Vyberte obrázek PNG nebo JPEG (nejvýše 4 MiB). Průhlednost bude zachována.
Couldn't import the signature image: {e} Obrázek podpisu nelze importovat: {e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. Přenosný režim je vypnutý: do složky {folder} nelze zapisovat ({error}). Nastavení se místo toho ukládá do vaší uživatelské složky.
Can't render this file because it contains an unexpected character in line 7 and column 81.
+1
View File
@@ -1619,6 +1619,7 @@
Created a comment summary Se ha creado un resumen de comentarios
Couldn't open {name}: {e} No se pudo abrir {name}: {e}
Couldn't read {name}: {e} No se pudo leer {name}: {e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. El modo portátil está desactivado: no se puede escribir en {folder} ({error}). La configuración se guarda en tu carpeta de usuario.
Couldn't save: {e} No se pudo guardar: {e}
Downloading attachments on the web arrives with M3.10 ({n} bytes ready) La descarga de archivos adjuntos en la versión web llegará con M3.10 ({n} bytes listos)
`{command}` {when} `{command}` {when}
Can't render this file because it contains an unexpected character in line 6 and column 60.
+1
View File
@@ -1560,6 +1560,7 @@
Created a comment summary Un résumé des commentaires a été créé
Couldn't open {name}: {e} Impossible d'ouvrir {name} : {e}
Couldn't read {name}: {e} Impossible de lire {name} : {e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. Le mode portable est désactivé : impossible d'écrire dans {folder} ({error}). Les réglages sont conservés dans votre dossier utilisateur.
Couldn't save: {e} Impossible d'enregistrer : {e}
Downloading attachments on the web arrives with M3.10 ({n} bytes ready) Le téléchargement des pièces jointes sur le web arrivera avec M3.10 ({n} octets prêts)
`{command}` {when} `{command}` {when}
Can't render this file because it contains an unexpected character in line 6 and column 39.
+1
View File
@@ -1625,6 +1625,7 @@
Created a comment summary 注釈の一覧を作成しました
Couldn't open {name}: {e} {name} を開けませんでした: {e}
Couldn't read {name}: {e} {name} を読み取れませんでした: {e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. ポータブルモードは無効です: {folder} に書き込めません ({error})。設定は代わりにユーザーフォルダーに保存されます。
Couldn't save: {e} 保存できませんでした: {e}
Downloading attachments on the web arrives with M3.10 ({n} bytes ready) Web 版の添付ファイルのダウンロードは M3.10 で対応予定です({n} バイト準備済み)
`{command}` {when} `{command}`: {when}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -62,3 +62,4 @@
Auto Automático
Choose a PNG or JPEG image (up to 4 MiB). Transparency is preserved. Escolha uma imagem PNG ou JPEG (até 4 MiB). A transparência é preservada.
Couldn't import the signature image: {e} Não foi possível importar a imagem da assinatura: {e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. O modo portátil está desativado: não é possível gravar em {folder} ({error}). As configurações ficam na sua pasta de usuário.
Can't render this file because it contains an unexpected character in line 9 and column 16.
+1
View File
@@ -1576,6 +1576,7 @@
Created a comment summary 已创建注释摘要
Couldn't open {name}: {e} 无法打开{name}:{e}
Couldn't read {name}: {e} 无法读取{name}:{e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. 便携模式已关闭:无法写入{folder}({error})。设置改为保存在你的用户文件夹中。
Couldn't save: {e} 无法保存:{e}
Downloading attachments on the web arrives with M3.10 ({n} bytes ready) Web 端附件下载将于 M3.10 到来({n} 字节已就绪)
`{command}` {when} `{command}` {when}
Can't render this file because it contains an unexpected character in line 9 and column 16.
+1
View File
@@ -1534,6 +1534,7 @@
Created a comment summary 已建立註解摘要
Couldn't open {name}: {e} 無法開啟{name}:{e}
Couldn't read {name}: {e} 無法讀取{name}:{e}
Portable mode is off: {folder} can't be written ({error}). Settings are kept in your user folder instead. 可攜模式已關閉:無法寫入{folder}({error})。設定改為儲存在你的使用者資料夾中。
Couldn't save: {e} 無法儲存:{e}
Downloading attachments on the web arrives with M3.10 ({n} bytes ready) Web 端附件下載將於 M3.10 到來({n} 位元組已就緒)
`{command}` {when} `{command}` {when}
Can't render this file because it contains an unexpected character in line 9 and column 16.
+1
View File
@@ -84,6 +84,7 @@ pub mod i18n;
/// The longest author name kept (Preferences ▸ Identity, restored settings).
pub(crate) const MAX_AUTHOR_CHARS: usize = 200;
pub mod portable;
mod protect;
mod recovery;
#[cfg(not(target_arch = "wasm32"))]
+171
View File
@@ -0,0 +1,171 @@
//! Portable mode (#157): a marker file beside the executable ([`MARKERS`]) keeps the settings,
//! log files, crash-recovery autosaves and new digital IDs in `<exe dir>/PdfCraftData`, so a
//! portable copy (on a USB stick, say) writes nothing to `%APPDATA%` or `%LOCALAPPDATA%`. The
//! Windows portable zip ships with `portable.txt`; the MSI doesn't. PhotoCraft uses the same
//! scheme (`photocraft/apps/photocraft/src/app_dirs.rs`).
//!
//! If that folder can't be written (read-only medium, Program Files), portable mode stays off, the
//! per-user folders are used and the app says why.
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
/// Files beside the executable that switch on portable mode (either one; contents are ignored).
pub const MARKERS: [&str; 2] = ["portable.txt", "PdfCraft.portable"];
/// The data folder created beside the executable in portable mode.
pub const DATA_DIR: &str = "PdfCraftData";
/// The outcome of looking for a portable marker.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Portable {
/// `<exe dir>/PdfCraftData` when portable mode is on, `None` otherwise.
pub dir: Option<PathBuf>,
/// Set when a marker was found but its data folder can't be written.
pub unwritable: Option<Unwritable>,
}
/// A marker was found, but the data folder beside it can't be created or written.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Unwritable {
pub marker: PathBuf,
pub folder: PathBuf,
/// The operating system's error text.
pub error: String,
}
impl std::fmt::Display for Unwritable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"portable mode is off: {} was found, but {} can't be written ({}); settings are kept in the user folder instead",
self.marker.display(),
self.folder.display(),
self.error
)
}
}
/// Portable mode for this process, resolved once (the check probes the disk).
pub fn current() -> &'static Portable {
static CURRENT: OnceLock<Portable> = OnceLock::new();
CURRENT.get_or_init(|| {
let exe = std::env::current_exe().ok();
let portable = resolve(exe.as_deref().and_then(Path::parent));
if let Some(dir) = &portable.dir {
log::info!("portable mode: settings are kept in {}", dir.display());
}
if let Some(unwritable) = &portable.unwritable {
log::warn!("{unwritable}");
}
portable
})
}
/// The portable data folder, when portable mode is on.
pub fn data_dir() -> Option<&'static Path> {
current().dir.as_deref()
}
/// Look for a marker in `exe_dir` and, when one is there, create its data folder beside it.
pub fn resolve(exe_dir: Option<&Path>) -> Portable {
let Some((exe_dir, marker)) = exe_dir.and_then(|d| find_marker(d).map(|m| (d, m))) else {
return Portable::default();
};
let folder = exe_dir.join(DATA_DIR);
match ensure_writable(&folder) {
Ok(()) => Portable { dir: Some(folder), unwritable: None },
Err(e) => Portable { dir: None, unwritable: Some(Unwritable { marker, folder, error: e.to_string() }) },
}
}
/// The first marker file in `exe_dir`, if any. A folder with a marker's name is not a marker.
pub fn find_marker(exe_dir: &Path) -> Option<PathBuf> {
MARKERS.iter().map(|m| exe_dir.join(m)).find(|p| p.is_file())
}
/// Create `dir` and prove a file can be written in it (a folder can exist yet be read-only).
fn ensure_writable(dir: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dir)?;
let probe = dir.join(".write-test");
std::fs::write(&probe, b"")?;
// A leftover probe is harmless; don't fail portable mode over it.
let _ = std::fs::remove_file(&probe);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
fn temp(tag: &str) -> PathBuf {
static N: AtomicUsize = AtomicUsize::new(0);
let d = std::env::temp_dir().join(format!("pdfcraft-portable-{tag}-{}-{}", std::process::id(), N.fetch_add(1, Ordering::Relaxed)));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
#[test]
fn no_marker_leaves_portable_mode_off() {
let exe = temp("off");
assert_eq!(resolve(Some(&exe)), Portable::default());
assert!(!exe.join(DATA_DIR).exists(), "no data folder without a marker");
let _ = std::fs::remove_dir_all(&exe);
}
#[test]
fn either_marker_keeps_the_data_beside_the_exe() {
for marker in MARKERS {
let exe = temp("on");
std::fs::write(exe.join(marker), b"").unwrap();
let p = resolve(Some(&exe));
assert_eq!(p.unwritable, None, "{marker}");
let dir = p.dir.unwrap();
assert_eq!(dir, exe.join(DATA_DIR));
assert!(dir.is_dir(), "the data folder is created");
assert_eq!(std::fs::read_dir(&dir).unwrap().count(), 0, "the write probe is removed");
let _ = std::fs::remove_dir_all(&exe);
}
}
#[test]
fn an_existing_data_folder_is_reused() {
let exe = temp("reuse");
std::fs::write(exe.join("portable.txt"), b"PdfCraft portable mode").unwrap();
std::fs::create_dir_all(exe.join(DATA_DIR)).unwrap();
std::fs::write(exe.join(DATA_DIR).join("app.ron"), b"()").unwrap();
assert_eq!(resolve(Some(&exe)).dir, Some(exe.join(DATA_DIR)));
assert_eq!(std::fs::read(exe.join(DATA_DIR).join("app.ron")).unwrap(), b"()", "existing settings are kept");
let _ = std::fs::remove_dir_all(&exe);
}
#[test]
fn a_marker_folder_is_not_a_marker() {
let exe = temp("dir-marker");
std::fs::create_dir_all(exe.join("portable.txt")).unwrap();
assert_eq!(resolve(Some(&exe)), Portable::default());
let _ = std::fs::remove_dir_all(&exe);
}
#[test]
fn unwritable_data_folder_turns_portable_mode_off_and_says_why() {
let exe = temp("unwritable");
std::fs::write(exe.join("portable.txt"), b"").unwrap();
// A file where the data folder should go: it can't be created, on every platform.
std::fs::write(exe.join(DATA_DIR), b"in the way").unwrap();
let p = resolve(Some(&exe));
assert_eq!(p.dir, None);
let unwritable = p.unwritable.unwrap();
assert_eq!(unwritable.marker, exe.join("portable.txt"));
assert_eq!(unwritable.folder, exe.join(DATA_DIR));
let message = unwritable.to_string();
assert!(message.contains("portable mode is off") && message.contains("can't be written"), "{message}");
let _ = std::fs::remove_dir_all(&exe);
}
#[test]
fn no_exe_dir_leaves_portable_mode_off() {
assert_eq!(resolve(None), Portable::default());
}
}
+5 -1
View File
@@ -40,10 +40,14 @@ impl RecoveryStore {
Self { dir: dir.into() }
}
/// The platform's per-user data folder: `~/Library/Application Support/PdfCraft/Recovery`
/// `Recovery` in the portable data folder in portable mode ([`crate::portable`]), otherwise
/// the platform's per-user data folder: `~/Library/Application Support/PdfCraft/Recovery`
/// (macOS), `%LOCALAPPDATA%\PdfCraft\Recovery` (Windows), or
/// `$XDG_DATA_HOME/pdfcraft/recovery` / `~/.local/share/pdfcraft/recovery` (others).
pub fn default_dir() -> Option<PathBuf> {
if let Some(dir) = crate::portable::data_dir() {
return Some(dir.join("Recovery"));
}
let env = |k: &str| std::env::var_os(k).filter(|v| !v.is_empty()).map(PathBuf::from);
if cfg!(target_os = "macos") {
env("HOME").map(|h| h.join("Library/Application Support/PdfCraft/Recovery"))
+2 -1
View File
@@ -157,7 +157,8 @@ pub(crate) fn page_input(ui: &egui::Ui, resp: &egui::Response, xf: &PageXform, p
}
}
/// Where new digital IDs are saved: next to the recovery folder (`…/PdfCraft/Digital IDs`).
/// Where new digital IDs are saved: next to the recovery folder (`…/PdfCraft/Digital IDs`, or
/// `PdfCraftData/Digital IDs` in portable mode).
fn id_dir() -> Option<PathBuf> {
crate::recovery::RecoveryStore::default_dir().and_then(|d| d.parent().map(|p| p.join("Digital IDs")))
}
+6 -3
View File
@@ -8,9 +8,12 @@ collects what the desktop app reads from its environment and where it writes its
The desktop app writes its `log` records to standard error and to `logs/pdfcraft.log` in its
settings folder, next to `app.ron`: Linux and FreeBSD `~/.local/share/pdfcraft/logs/` (or
`$XDG_DATA_HOME/pdfcraft/logs/`), macOS `~/Library/Application Support/PdfCraft/logs/`, Windows
`%APPDATA%\PdfCraft\data\logs\`. A start from a desktop menu, Finder or the Start menu has no
terminal, so this file is what to attach to a bug report: a failed autosave, a page that would not
render, a render worker that could not start and the report of an internal error all land there.
`%APPDATA%\PdfCraft\data\logs\`. In portable mode (a `portable.txt` file next to the executable, as
the Windows portable zip ships) the settings folder is `PdfCraftData\` beside the executable, and
crash recovery and new digital IDs go there too. A start from a desktop menu, Finder or the Start
menu has no terminal, so this file is what to attach to a bug report: a failed autosave, a page that
would not render, a render worker that could not start and the report of an internal error all land
there.
Each launch moves the previous log to `pdfcraft.1.log` (and that one to `pdfcraft.2.log`), so the
log of a run that crashed survives the next start. The file stops growing at 16 MiB. `--version`
writes no file.
+4 -2
View File
@@ -113,8 +113,10 @@ neither the MSI nor the portable zip needs the Visual C++ redistributable.
child process before signing, so the MSI isn't held open when signtool runs. The ARM64 install
smoke test checks that both all-users shortcuts point at the installed `pdfcraft.exe` and are
removed on uninstall, and that `INSTALLDESKTOPSHORTCUT=0` skips the desktop one.
- The portable zip holds both executables, the README, the licences, and the OFL licence of each
embedded craft-fonts family.
- The portable zip holds both executables, the README, the licences, the OFL licence of each
embedded craft-fonts family, and `portable.txt`. That marker beside `pdfcraft.exe` keeps the
settings, logs, crash recovery and new digital IDs in `PdfCraftData\` next to the exe instead of
`%APPDATA%` and `%LOCALAPPDATA%` (`crates/ui-egui/src/portable.rs`).
- **Signing:** `packaging/windows/sign.ps1` signs both executables and the MSI with `signtool`
(SHA-256, RFC 3161 timestamp), from a `.pfx` (`WINDOWS_CERTIFICATE`) or Azure Trusted Signing
(`AZURE_*`), whichever is configured. It is the one place to change when signing changes.
+5 -1
View File
@@ -5,7 +5,7 @@
.DESCRIPTION
Produces, in $env:DIST (default: dist/release):
pdfcraft-<version>-windows-<arch>.msi per-machine installer (WiX v5)
pdfcraft-<version>-windows-<arch>-portable.zip pdfcraft.exe + pdfcraft-cli.exe
pdfcraft-<version>-windows-<arch>-portable.zip pdfcraft.exe + pdfcraft-cli.exe + portable.txt
The binaries link the C runtime statically (+crt-static), so neither the MSI nor the portable
zip needs the Visual C++ redistributable. Signing is delegated to sign.ps1 (skipped with a
@@ -122,6 +122,10 @@ if ($env:CRAFT_FONTS_DIR) {
if (Test-Path $ofl) { Copy-Item $ofl (Join-Path $Portable "OFL-$($_.Name).txt") }
}
}
# portable.txt beside pdfcraft.exe switches on portable mode: settings, logs, recovery files and new
# digital IDs go to PdfCraftData\ next to the exe instead of %APPDATA% (#157; see
# crates/ui-egui/src/portable.rs).
Copy-Item (Join-Path $PSScriptRoot 'portable.txt') $Portable
$Zip = Join-Path $Dist "pdfcraft-$Version-windows-$Arch-portable.zip"
Remove-Item -Force $Zip -ErrorAction SilentlyContinue
Compress-Archive -Path $Portable -DestinationPath $Zip
+9
View File
@@ -0,0 +1,9 @@
PdfCraft portable mode
While this file sits next to pdfcraft.exe, PdfCraft keeps its settings, log files, crash-recovery
autosaves and new digital IDs in the PdfCraftData folder beside it, and writes nothing to
%APPDATA% or %LOCALAPPDATA%. Copy the whole folder to move PdfCraft with its settings.
Delete this file to use the normal per-user folders (%APPDATA%\PdfCraft and
%LOCALAPPDATA%\PdfCraft) instead. If PdfCraftData can't be written (for example on a read-only
drive), PdfCraft says so and uses the per-user folders instead.