From 946fb31eef70cf6bf7fe0b2138e3d263b5bb9944 Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:09:05 +0800 Subject: [PATCH] M3: restore last view settings when reopening documents (#1767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A file opened again shows the page, zoom and page display it was last shown with, as Acrobat's "Restore last view settings when reopening documents" does: remembered by path (the last 100 files) when its tab closes or settings are saved with it open, applied over the file's own initial view, on by default and switched off in Preferences ▸ Documents and view. Co-Authored-By: Claude Fable 5.1 --- ROADMAP.md | 2 + crates/ui-egui/src/i18n/ar.tsv | 1 + crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/kk.tsv | 1 + crates/ui-egui/src/i18n/lt.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sl.tsv | 1 + crates/ui-egui/src/i18n/te.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/js_ui.rs | 3 + crates/ui-egui/src/last_view.rs | 112 ++++++++++++++++++++++++++++ crates/ui-egui/src/lib.rs | 17 +++++ crates/ui-egui/tests/it/ui.rs | 80 ++++++++++++++++++++ parity/acrobat-features.toml | 14 +++- 27 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 crates/ui-egui/src/last_view.rs diff --git a/ROADMAP.md b/ROADMAP.md index 5634d6bf28..0a5fdd4037 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -348,6 +348,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md - **2026-10-15 (community, M14, Polish signature panel):** A Polish signature panel listed its details in English: the catalog lacked every signature wording, the changes after signing they name and 34 UI literals. All 101 are translated; `polish_covers_ui_literals` and `polish_says_what_a_signature_says` keep them so. Estimates unchanged. - **2026-10-14 (M14, Finnish localization):** Added Finnish as another interface and date language, with a 2,674-entry catalog covering every key of the other bundled catalogs and two-form plurals (partitive after numbers other than 1). Calendar dates use partitive month names (`14. lokakuuta 2026`). Coverage tests check commands/tools, scanned UI literals and bundled catalog keys. AI-assisted; native proofreading pending. - **2026-10-14 (M14, Catalan localization):** Added Catalan as another interface and date language, with a 2,592-entry clean-room catalog, one/other plurals and coverage tests for commands, tools, UI literals and every key in the other catalogs. A native Catalan speaker (the contributor) has reviewed the translation. Estimates unchanged. + +- **2026-10-15 (M3, #1767):** Restore last view settings when reopening documents, as Acrobat's preference does: a file opened again shows the page, zoom and page display it was last shown with (remembered for the last 100 files when a tab closes or settings are saved), over the file's own initial view; on by default, Preferences ▸ Documents and view turns it off. - **2026-10-13 (M3, native-opening candidate):** Chosen tools survive preparation, conversion and password retries; startup intake waits for pending preparation. Regression tests cover completion, cancellation and document identity. Estimates unchanged. - **2026-10-13 (community, M3, #531 touchpad momentum):** On Wayland a two-finger swipe keeps gliding after the fingers lift and slows to a stop, measured from the gesture's last 0.1 s and decaying with a 0.35 s time constant through the #759 1:1 path. Any wheel input, click, key, zoom or edge ends it (fingers resting on the touchpad don't yet: the vendored winit binds no Wayland hold gestures); single-page paging, macOS (which sends its own momentum), the web, and Animation effects Off never glide. Unit tests for the physics and two headless view tests. Estimates unchanged. - **2026-10-13 (M3, native-open admission):** Later prepared replies stay queued while a modal or native picker owns the foreground. Picker completion, cancellation and abandonment wake retained opens. Regressions cover recipient ownership, exactly-once admission, source/history preservation and one-shot repaint requests. Estimates unchanged. diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index 765bbba553..603dcd5fb8 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -2193,6 +2193,7 @@ calendar date October أكتوبر calendar date November نوفمبر calendar date December ديسمبر Reopen the files that were open when PdfCraft last closed إعادة فتح الملفات التي كانت مفتوحة عند آخر إغلاق لـ PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again استعادة آخر عرض (الصفحة والتكبير وعرض الصفحات) عند فتح المستند مرة أخرى Clear Recent Files مسح الملفات الأخيرة Remove from Recent إزالة من الأخيرة Remove {name} from Recent إزالة {name} من الأخيرة diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 9bb377a936..da29fa8cdf 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2178,6 +2178,7 @@ signature pad Type Въвеждане action wizard Start Старт Margins (in) Полета (in) Reopen the files that were open when PdfCraft last closed Отваряй отново файловете, които бяха отворени при последното затваряне на PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Възстановяване на последния изглед (страница, мащаб и показване на страниците) при повторно отваряне на документ Clear Recent Files Изчисти последно отваряните файлове Remove from Recent Премахни от последно отваряните Remove {name} from Recent Премахни {name} от последно отваряните diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 66cac29aaf..911f8f53ee 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -139,6 +139,7 @@ Fit one full page Celá jedna stránka 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. Reopen the files that were open when PdfCraft last closed Znovu otevřít soubory, které byly otevřené při posledním zavření aplikace PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Při opětovném otevření dokumentu obnovit poslední zobrazení (stránku, zvětšení a rozložení stránek) Reload documents when their files change on disk Znovu načítat dokumenty při změně jejich souborů na disku Always use my default zoom and page layout Vždy používat mé výchozí zvětšení a rozvržení stránky This file changed on disk. Reload shows the new version and drops your unsaved changes; Save replaces it with this version. Tento soubor se na disku změnil. Znovu načíst zobrazí novou verzi a zahodí neuložené změny; Uložit ji nahradí touto verzí. diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 76366d452f..973b2d83de 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2186,6 +2186,7 @@ signature pad Type Tippen certificate Subject Zertifikatsinhaber action wizard Start Starten Reopen the files that were open when PdfCraft last closed Beim Start die Dateien wieder öffnen, die beim letzten Beenden von PdfCraft geöffnet waren + Restore the last view (page, zoom and page display) when a document is opened again Beim erneuten Öffnen eines Dokuments die letzte Ansicht (Seite, Zoom und Seitenanzeige) wiederherstellen # Labels that reach tl!() through a variable (Fill & Sign buttons, link prompts, notices, history labels, checker and certificate rows) Add {what} {what} hinzufügen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 18464664ac..d7aae3c5be 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1467,6 +1467,7 @@ Couldn't import the signature image: {e} Δεν ήταν δυνατή η εισαγωγή της εικόνας υπογραφής: {e} Go to page… Μετάβαση στη σελίδα… Reopen the files that were open when PdfCraft last closed Εκ νέου άνοιγμα των αρχείων που ήταν ανοιχτά κατά τον τελευταίο τερματισμό του PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Επαναφορά της τελευταίας προβολής (σελίδα, ζουμ και εμφάνιση σελίδων) όταν ένα έγγραφο ανοίγει ξανά The PDF text fonts can't draw “{c}” ({code}). Choose supported text. Οι γραμματοσειρές κειμένου PDF δεν μπορούν να εμφανίσουν τον χαρακτήρα «{c}» ({code}). Επιλέξτε κείμενο που υποστηρίζεται. Open arrow Ανοιχτή αιχμή βέλους Closed arrow Κλειστή αιχμή βέλους diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 73b0ffbf18..51c8922e5a 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2160,6 +2160,7 @@ action wizard Start Iniciar Lines +/− Líneas +/− @{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last} @{login}: {prs} PR, {commits} commits, +{added} / −{deleted} líneas (Δ {delta}), +{bin_added} / −{bin_deleted} recursos binarios, {first} – {last} Reopen the files that were open when PdfCraft last closed Volver a abrir los archivos que estaban abiertos al cerrar PdfCraft por última vez + Restore the last view (page, zoom and page display) when a document is opened again Restaurar la última vista (página, zoom y presentación de páginas) al volver a abrir un documento Add {what} Añadir {what} Use {what} Usar {what} Place saved {what} Colocar {what} desde lo guardado diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index ec6e59e56d..4969ca1bff 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2041,6 +2041,7 @@ Default zoom Zoom par défaut Used when a PDF doesn't ask for a layout or zoom. Continuous scrolling never snaps between pages. Utilisé lorsqu'un PDF ne demande ni présentation ni zoom. Le défilement continu ne saute jamais d'une page à l'autre. Reopen the files that were open when PdfCraft last closed Rouvrir les fichiers qui étaient ouverts lors de la dernière fermeture de PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Rétablir la dernière vue (page, zoom et affichage des pages) à la réouverture d’un document Reload documents when their files change on disk Recharger les documents lorsque leurs fichiers sont modifiés sur le disque Always use my default zoom and page layout Toujours utiliser mon zoom et ma présentation de page par défaut This file changed on disk. Reload shows the new version and drops your unsaved changes; Save replaces it with this version. Ce fichier a été modifié sur le disque. Recharger affiche la nouvelle version et abandonne vos modifications non enregistrées ; Enregistrer la remplace par cette version. diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index f1723629cc..21dd3b3882 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2170,6 +2170,7 @@ comment menu Edit Szerkesztés signature pad Type Gépelés action wizard Start Indítás Reopen the files that were open when PdfCraft last closed A PdfCraft legutóbbi bezárásakor megnyitott fájlok újranyitása + Restore the last view (page, zoom and page display) when a document is opened again A legutóbbi nézet (oldal, nagyítás és oldalmegjelenítés) visszaállítása a dokumentum újbóli megnyitásakor Clear Recent Files Legutóbbi fájlok listájának törlése Remove from Recent Eltávolítás a legutóbbiak közül Remove {name} from Recent {name} eltávolítása a legutóbbiak közül diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 0dc6bb0874..6c3de5aeb0 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2208,6 +2208,7 @@ Remove saved {what} Rimuovi l'elemento salvato: {what} Remove {name} from Recent Rimuovi {name} dai recenti Reopen the files that were open when PdfCraft last closed Riapri i file aperti alla chiusura precedente di PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Ripristina l’ultima vista (pagina, zoom e visualizzazione delle pagine) quando un documento viene riaperto Reset page size Ripristina dimensione pagine Reset to printer defaults Ripristina le impostazioni predefinite della stampante Reverse closed arrow Freccia chiusa inversa diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 963e8d3641..4c4a78389e 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2106,6 +2106,7 @@ Go to page… ページへ移動… For page sizes and margins. ページサイズと余白に使います。 Reopen the files that were open when PdfCraft last closed 前回 PdfCraft を閉じたときに開いていたファイルを再度開く + Restore the last view (page, zoom and page display) when a document is opened again 文書を再び開くときに最後の表示(ページ、ズーム、ページ表示)を復元する The PDF text fonts can't draw “{c}” ({code}). Choose supported text. PDF テキスト用フォントでは「{c}」({code})を表示できません。対応している文字を使用してください。 The standard fonts can't draw “{c}” ({code}). Only Western European characters can be added as text for now. 標準フォントでは「{c}」({code})を表示できません。現在、テキストとして追加できるのは西ヨーロッパの文字のみです。 This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. このフィールドのフォントでは「{c}」({code})を表示できないため、PdfCraft では ? と表示されます。値は保存され、他の PDF ビューアーではこの値からフィールドが再描画されます。 diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 311626f34a..7754cf4d59 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -2113,6 +2113,7 @@ calendar date October Қазан calendar date November Қараша calendar date December Желтоқсан Reopen the files that were open when PdfCraft last closed PdfCraft соңғы рет жабылғанда ашық болған файлдарды қайта ашу + Restore the last view (page, zoom and page display) when a document is opened again Құжат қайта ашылғанда соңғы көріністі (бет, масштаб және бет көрсетілімі) қалпына келтіру Clear Recent Files Соңғы файлдарды тазарту Remove from Recent Соңғылардан алып тастау Remove {name} from Recent {name} дегенді соңғылардан алып тастау diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index e2476f1963..3d9bc52167 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -987,6 +987,7 @@ Remove {n} pages Pašalinti puslapių: {n} Reopen Closed Tab Vėl atverti užvertą kortelę Reopen the files that were open when PdfCraft last closed Vėl atverti failus, kurie buvo atverti paskutinį kartą užveriant „PdfCraft“ + Restore the last view (page, zoom and page display) when a document is opened again Iš naujo atidarant dokumentą atkurti paskutinį rodinį (puslapį, mastelį ir puslapių rodymą) Repair log Taisymo žurnalas Repeat overlay text Kartoti perdangos tekstą Replace Image… Pakeisti paveikslėlį… diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 6836f3e0a3..fe6088a0e9 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2085,6 +2085,7 @@ Couldn't import the signature image: {e} Nie można zaimportować obrazu podpisu: {e} Go to page… Przejdź do strony… Reopen the files that were open when PdfCraft last closed Otwórz ponownie pliki otwarte przy ostatnim zamknięciu PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Przywracaj ostatni widok (stronę, powiększenie i układ stron) przy ponownym otwarciu dokumentu The PDF text fonts can't draw “{c}” ({code}). Choose supported text. Czcionki tekstu PDF nie obsługują znaku „{c}” ({code}). Wybierz obsługiwany tekst. The standard fonts can't draw “{c}” ({code}). Only Western European characters can be added as text for now. Standardowe czcionki nie obsługują znaku „{c}” ({code}). Na razie można dodawać tylko tekst ze znakami zachodnioeuropejskimi. This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. Czcionka tego pola nie obsługuje znaku „{c}” ({code}), więc PdfCraft wyświetla w jego miejscu ?. Wartość została zapisana, a inne przeglądarki PDF odrysują pole na jej podstawie. diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index dcbebae958..6ef9c7e3eb 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -81,6 +81,7 @@ PdfCraft is drawing without a graphics processor, so it may be slow. Updating the graphics driver may help. O PdfCraft está desenhando sem processador gráfico, por isso pode ficar lento. Atualizar o driver de vídeo pode ajudar. Go to page… Ir para a página… Reopen the files that were open when PdfCraft last closed Reabrir os arquivos que estavam abertos quando o PdfCraft foi fechado pela última vez + Restore the last view (page, zoom and page display) when a document is opened again Restaurar a última visualização (página, zoom e exibição de páginas) ao reabrir um documento Margins Margens For page sizes and margins. Para tamanhos de página e margens. Open Abrir diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 0dc40fdba6..9fb91165ed 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2108,6 +2108,7 @@ Default zoom Масштаб по умолчанию Used when a PDF doesn't ask for a layout or zoom. Continuous scrolling never snaps between pages. Используется, когда PDF не запрашивает макет или масштаб. Непрерывная прокрутка никогда не привязывается между страницами. Reopen the files that were open when PdfCraft last closed Повторно открывать файлы, которые были открыты при последнем закрытии PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Восстанавливать последний вид (страницу, масштаб и показ страниц) при повторном открытии документа Reload documents when their files change on disk Перезагружать документы при изменении их файлов на диске Always use my default zoom and page layout Всегда использовать мой масштаб и макет страницы по умолчанию This file changed on disk. Reload shows the new version and drops your unsaved changes; Save replaces it with this version. Этот файл был изменён на диске. «Перезагрузить» покажет новую версию и отбросит несохранённые изменения; «Сохранить» заменит её этой версией. diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index f5e374ae7d..0cb1636bf2 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -1506,6 +1506,7 @@ Rename bookmark Preimenuj zaznamek Rendering: hayro (bootstrap) · UI: egui · Icons: Lucide (ISC) · Fonts: Inter, JetBrains Mono, Dancing Script (OFL) Izris: hayro (bootstrap) · Vmesnik: egui · Ikone: Lucide (ISC) · Pisave: Inter, JetBrains Mono, Dancing Script (OFL) Reopen the files that were open when PdfCraft last closed Ponovno odpri datoteke, ki so bile odprte ob zadnjem zaprtju PdfCrafta + Restore the last view (page, zoom and page display) when a document is opened again Ob ponovnem odprtju dokumenta obnovi zadnji pogled (stran, povečavo in prikaz strani) Repair log Dnevnik popravil Repairs Popravila Repeat overlay text Ponovi besedilo prekrivanja diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index fa3f6cec4f..c53a410fa9 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -2027,6 +2027,7 @@ action wizard Start ప్రారంభించు Default zoom డిఫాల్ట్ జూమ్ Used when a PDF doesn't ask for a layout or zoom. Continuous scrolling never snaps between pages. PDF ఏ లేఅవుట్‌ను గానీ జూమ్‌ను గానీ కోరనప్పుడు ఉపయోగించబడుతుంది. నిరంతర స్క్రోలింగ్ పేజీల మధ్య ఎప్పుడూ స్నాప్ అవ్వదు. Reopen the files that were open when PdfCraft last closed PdfCraftను చివరిసారి మూసివేసినప్పుడు తెరిచి ఉన్న ఫైల్‌లను మళ్లీ తెరువు + Restore the last view (page, zoom and page display) when a document is opened again పత్రాన్ని మళ్లీ తెరిచినప్పుడు చివరి వీక్షణను (పేజీ, జూమ్ మరియు పేజీ ప్రదర్శన) పునరుద్ధరించండి Reload documents when their files change on disk డిస్క్‌లో వాటి ఫైల్‌లు మారినప్పుడు పత్రాలను మళ్లీ లోడ్ చేయి Always use my default zoom and page layout ఎల్లప్పుడూ నా డిఫాల్ట్ జూమ్ మరియు పేజీ లేఅవుట్‌ను ఉపయోగించు This file changed on disk. Reload shows the new version and drops your unsaved changes; Save replaces it with this version. ఈ ఫైల్ డిస్క్‌లో మారింది. రీలోడ్ కొత్త వెర్షన్‌ను చూపించి సేవ్ చేయని మార్పులను వదిలేస్తుంది; సేవ్ దాని స్థానంలో ఈ వెర్షన్‌ను ఉంచుతుంది. diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index c292b88638..4285792d20 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2139,6 +2139,7 @@ signature pad Type Yaz certificate Subject Konu action wizard Start Başlat Reopen the files that were open when PdfCraft last closed PdfCraft en son kapatıldığında açık olan dosyaları yeniden aç + Restore the last view (page, zoom and page display) when a document is opened again Bir belge yeniden açıldığında son görünümü (sayfa, yakınlaştırma ve sayfa görünümü) geri yükle Add {what} {what} ekle Use {what} {what} kullan Place saved {what} Kayıtlı {what} yerleştir diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index cc8f45765c..698e7421b8 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2191,6 +2191,7 @@ calendar date October жовтня calendar date November листопада calendar date December грудня Reopen the files that were open when PdfCraft last closed Знову відкривати файли, які були відкриті під час останнього закриття PdfCraft + Restore the last view (page, zoom and page display) when a document is opened again Відновлювати останній вигляд (сторінку, масштаб і показ сторінок) під час повторного відкриття документа Clear Recent Files Очистити список нещодавніх файлів Remove from Recent Вилучити з нещодавніх Remove {name} from Recent Вилучити {name} з нещодавніх diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 594f68df88..f8758f31a7 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2142,6 +2142,7 @@ Used when opening PDFs. An explicit launch or control mode takes precedence for the session. 用于打开 PDF 时。如果启动参数或控制通道明确指定了模式,则本次会话优先使用该模式。 Used when a PDF doesn't ask for a layout or zoom. Continuous scrolling never snaps between pages. PDF 未指定布局或缩放时使用。连续滚动时页面不会逐页吸附。 Reopen the files that were open when PdfCraft last closed 重新打开上次关闭 PdfCraft 时打开的文件 + Restore the last view (page, zoom and page display) when a document is opened again 再次打开文档时恢复上次的视图(页面、缩放和页面显示) Reload documents when their files change on disk 文件在磁盘上更改时重新加载对应的文档 Always use my default zoom and page layout 始终使用我的默认缩放和页面布局 This file changed on disk. Reload shows the new version and drops your unsaved changes; Save replaces it with this version. 此文件已在磁盘上更改。“重新加载”显示新版本并放弃未保存的更改;“保存”则用此版本替换它。 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index cb578ba26f..abf697998a 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2093,6 +2093,7 @@ Go to page… 轉到頁面… For page sizes and margins. 用於頁面尺寸和邊距。 Reopen the files that were open when PdfCraft last closed 重新開啟上次關閉 PdfCraft 時開啟的檔案 + Restore the last view (page, zoom and page display) when a document is opened again 再次開啟文件時還原上次的檢視(頁面、縮放與頁面顯示) The PDF text fonts can't draw “{c}” ({code}). Choose supported text. PDF 文字字型無法顯示「{c}」({code})。請選擇支援的文字。 The standard fonts can't draw “{c}” ({code}). Only Western European characters can be added as text for now. 標準字型無法顯示「{c}」({code})。目前只能新增西歐字元作為文字。 This field's font can't draw “{c}” ({code}), so PdfCraft shows ? in its place. The value is saved, and other PDF viewers redraw the field from it. 此欄位的字型無法顯示「{c}」({code}),PdfCraft 會在該處顯示 ?。值已儲存,其他 PDF 閱讀器會依據該值重新繪製此欄位。 diff --git a/crates/ui-egui/src/js_ui.rs b/crates/ui-egui/src/js_ui.rs index 7bc3dd0444..94f97ca453 100644 --- a/crates/ui-egui/src/js_ui.rs +++ b/crates/ui-egui/src/js_ui.rs @@ -391,6 +391,9 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok // The web build has no file paths to reopen. #[cfg(not(target_arch = "wasm32"))] ui.add(widgets::checkbox(&mut app.reopen_last_session, tl!("Reopen the files that were open when PdfCraft last closed"))); + // Acrobat's "Restore last view settings when reopening documents" (#1767). + #[cfg(not(target_arch = "wasm32"))] + ui.add(widgets::checkbox(&mut app.restore_last_view, tl!("Restore the last view (page, zoom and page display) when a document is opened again"))); // The web build has no files on disk to watch. #[cfg(not(target_arch = "wasm32"))] ui.add(widgets::checkbox(&mut app.reload_changed_files, tl!("Reload documents when their files change on disk"))); diff --git a/crates/ui-egui/src/last_view.rs b/crates/ui-egui/src/last_view.rs new file mode 100644 index 0000000000..ad40ec25ed --- /dev/null +++ b/crates/ui-egui/src/last_view.rs @@ -0,0 +1,112 @@ +//! Restore last view settings when reopening documents (Acrobat's Preferences ▸ Documents +//! option; #1767): the page, zoom and page display a file was last shown with, remembered by +//! its path when its tab is closed or settings are saved, and applied when the file is opened +//! again. Desktop only: a document without a file has nothing to be found by. + +use serde::Serialize; + +use crate::PdfCraftApp; +use crate::canvas::{self, ViewDefaults}; + +/// How many files' views are remembered (the most recently closed or saved first). +const MAX_REMEMBERED: usize = 100; + +/// How a file was last shown. +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct LastView { + /// Absolute, as `last_session` keys files. + pub path: String, + /// 0-based. + pub page: usize, + /// As settings name it: `fit-width`, `fit-page` or a percentage (`ViewDefaults::zoom_name`). + pub zoom: String, + /// The page display token (`canvas::display_token`). + pub display: String, +} + +/// As `last_session` keys files (and the web has no paths to remember). +#[cfg(not(target_arch = "wasm32"))] +fn absolute(path: &str) -> String { + std::path::absolute(path).map_or_else(|_| path.to_string(), |p| p.to_string_lossy().into_owned()) +} + +#[cfg(target_arch = "wasm32")] +fn absolute(path: &str) -> String { + path.to_string() +} + +impl PdfCraftApp { + /// How view `index` shows its document now; `None` for a document without a file. + fn view_to_remember(&self, index: usize) -> Option { + let view = self.views.get(index)?; + let path = self.session.get(view.id)?.path.as_deref()?; + let zoom = ViewDefaults { fit: view.fit, zoom: view.zoom, ..ViewDefaults::default() }.zoom_name(); + Some(LastView { path: absolute(path), page: view.current, zoom, display: canvas::display_token(view.layout, view.continuous).to_string() }) + } + + /// Remember how view `index` shows its document (when its tab closes). + pub(crate) fn remember_view(&mut self, index: usize) { + if let Some(v) = self.view_to_remember(index) { + self.last_views.retain(|l| l.path != v.path); + self.last_views.insert(0, v); + self.last_views.truncate(MAX_REMEMBERED); + } + } + + /// The remembered views as settings store them: the open views first, as they are now + /// (so quitting remembers them without closing their tabs), then the rest. + pub(crate) fn last_views_to_save(&self) -> Vec { + let mut out: Vec = (0..self.views.len()).filter_map(|i| self.view_to_remember(i)).collect(); + for l in &self.last_views { + if !out.iter().any(|o| o.path == l.path) { + out.push(l.clone()); + } + } + out.truncate(MAX_REMEMBERED); + out + } + + /// Settings are untrusted: an entry without a usable path, zoom or display is dropped, and + /// the list is capped. + pub(crate) fn restore_last_views(&mut self, v: &serde_json::Value) { + let Some(list) = v.as_array() else { return }; + self.last_views = list + .iter() + .take(MAX_REMEMBERED) + .filter_map(|e| { + let path = e["path"].as_str().filter(|p| !p.is_empty())?.to_string(); + let zoom = e["zoom"].as_str().filter(|z| ViewDefaults::default().with_zoom(z).is_some())?.to_string(); + let display = e["display"].as_str().filter(|d| canvas::parse_display_token(d).is_some())?.to_string(); + let page = usize::try_from(e["page"].as_u64().unwrap_or(0)).unwrap_or(0); + Some(LastView { path, page, zoom, display }) + }) + .collect(); + } + + /// Show view `index` as its document's file was last shown, when the preference is on and + /// the file is remembered. Applied after the document's own initial view, which it + /// overrides: where the reader left off is what they asked to come back to. + pub(crate) fn apply_last_view(&mut self, index: usize) { + if !self.restore_last_view { + return; + } + let Some(view) = self.views.get(index) else { return }; + let Some(doc) = self.session.get(view.id) else { return }; + let Some(path) = doc.path.as_deref().map(absolute) else { return }; + let pages = doc.info.pages.len(); + let Some(last) = self.last_views.iter().find(|l| l.path == path).cloned() else { return }; + let Some(view) = self.views.get_mut(index) else { return }; + if let Some((layout, continuous)) = canvas::parse_display_token(&last.display) { + view.set_display(layout, continuous); + } + if let Some(defaults) = ViewDefaults::default().with_zoom(&last.zoom) { + match defaults.fit { + canvas::Fit::None => view.set_zoom(defaults.zoom), + fit => view.set_fit(fit), + } + } + if last.page < pages { + view.go_to_page(last.page); + } + } +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index af9912f456..386a778cd6 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -99,6 +99,7 @@ mod home; mod icon_data; pub mod icons; pub mod last_session; +pub mod last_view; pub mod motion; mod page_selection_ui; mod pageboxes; @@ -584,6 +585,11 @@ pub struct PdfCraftApp { pub recent: Vec, /// Preferences: reopen the files that were open when PdfCraft last closed (#442). pub reopen_last_session: bool, + /// Restore last view settings when reopening documents (Preferences ▸ Documents and view; + /// #1767): the page, zoom and page display a file was last shown with. On by default. + pub restore_last_view: bool, + /// How files were last shown, most recent first (see `last_view`). + pub last_views: Vec, /// The files open when PdfCraft last closed, read from the settings for /// [`PdfCraftApp::reopen_last_files`]. pub last_session: last_session::LastSession, @@ -985,6 +991,8 @@ impl PdfCraftApp { all_tools_expanded: false, recent: Vec::new(), reopen_last_session: false, + restore_last_view: true, + last_views: Vec::new(), last_session: Default::default(), quit_session: None, reload_changed_files: true, @@ -1415,6 +1423,8 @@ impl PdfCraftApp { if activate && initial.full_screen { self.pending_full_screen = Some(id); } + // Where the reader left off wins over the file's own initial view (#1767). + self.apply_last_view(self.views.len() - 1); if activate && self.left_open && self.left == LeftPanel::Tool("organize") @@ -2007,6 +2017,7 @@ impl PdfCraftApp { if index >= self.views.len() { return; } + self.remember_view(index); let id = self.views.remove(index).id; #[cfg(not(target_arch = "wasm32"))] if let Some(path) = self.session.get(id).and_then(|d| d.path.clone()) { @@ -2370,6 +2381,8 @@ impl PdfCraftApp { "docking_right": self.right, "recent": self.recent, "reopen_last_session": self.reopen_last_session, + "restore_last_view": self.restore_last_view, + "last_views": self.last_views_to_save(), // Kept only while the preference is on. "last_session": self.reopen_last_session.then(|| self.session_to_save()), "reload_changed_files": self.reload_changed_files, @@ -2468,6 +2481,10 @@ impl PdfCraftApp { if let Some(on) = v["reopen_last_session"].as_bool() { self.reopen_last_session = on; } + if let Some(on) = v["restore_last_view"].as_bool() { + self.restore_last_view = on; + } + self.restore_last_views(&v["last_views"]); self.last_session = last_session::LastSession::from_json(&v["last_session"]); if let Some(on) = v["system_title_bar"].as_bool() { self.system_title_bar = on; diff --git a/crates/ui-egui/tests/it/ui.rs b/crates/ui-egui/tests/it/ui.rs index 6472b7c66d..604eb8178f 100644 --- a/crates/ui-egui/tests/it/ui.rs +++ b/crates/ui-egui/tests/it/ui.rs @@ -1570,3 +1570,83 @@ fn short_window_rail_buttons_scroll_clear_of_page_navigation() { assert!(tags.bottom() <= page.top(), "Tags {tags:?} scrolls into view above the page field {page:?}"); assert_eq!(h.get(By::new().role(egui::accesskit::Role::TextInput).value("1")).rect(), page, "navigation stays put"); } + +/// A PDF with `n` empty 200×300 pt pages. +fn pages_fixture(n: usize) -> Vec { + let mut objs: Vec> = vec![b"<< /Type /Catalog /Pages 2 0 R >>".to_vec()]; + let kids: Vec = (0..n).map(|i| format!("{} 0 R", 3 + i)).collect(); + objs.push(format!("<< /Type /Pages /Kids [{}] /Count {n} /MediaBox [0 0 200 300] >>", kids.join(" ")).into_bytes()); + for _ in 0..n { + objs.push(b"<< /Type /Page /Parent 2 0 R >>".to_vec()); + } + let mut out = b"%PDF-1.7\n".to_vec(); + let mut offsets = Vec::new(); + for (i, o) in objs.iter().enumerate() { + offsets.push(out.len()); + out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes()); + out.extend_from_slice(o); + out.extend_from_slice(b"\nendobj\n"); + } + let xref = out.len(); + out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes()); + for o in offsets { + out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes()); + } + out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes()); + out +} + +/// A file opened again shows the page, zoom and page display it was last shown with (#1767, +/// Acrobat's "Restore last view settings when reopening documents"): remembered when its tab +/// closes and when settings are saved with it open, applied over the file's own initial view, +/// and not at all with the preference off. Settings are untrusted: bad entries are ignored. +#[test] +fn a_file_opened_again_shows_its_last_view() { + use pdfcraft_ui_egui::canvas::{Fit, PageLayout}; + let path = std::env::temp_dir().join(format!("pdfcraft-last-view-{}.pdf", std::process::id())).to_string_lossy().into_owned(); + let p = path.clone(); + let mut h = harness(move |app| app.open_bytes("doc.pdf", Some(p), pages_fixture(3)).expect("opens")); + let p = path.clone(); + assert!(h.state().restore_last_view, "on by default"); + { + let app = h.state_mut(); + app.set_option("zoom", "50").unwrap(); + let view = &mut app.views[0]; + view.set_display(PageLayout::TwoUp, false); + view.go_to_page(2); + } + h.run_steps(3); + // Saved with the tab still open: quitting remembers it. + let saved_open = h.state().persist(); + h.state_mut().close_tab(0); + let saved_closed = h.state().persist(); + for (label, saved) in [("open", saved_open), ("closed", saved_closed)] { + let (saved, p) = (saved.clone(), p.clone()); + let h = harness(move |app| { + app.restore(&saved); + app.open_bytes("doc.pdf", Some(p), pages_fixture(3)).expect("opens"); + }); + let v = &h.state().views[0]; + assert_eq!((v.current, v.fit, v.zoom, v.layout, v.continuous), (2, Fit::None, 0.5, PageLayout::TwoUp, false), "{label}"); + } + // The preference off: the defaults. + let saved = h.state().persist(); + let p = path.clone(); + let h = harness(move |app| { + app.restore(&saved); + app.restore_last_view = false; + app.open_bytes("doc.pdf", Some(p), pages_fixture(3)).expect("opens"); + }); + let v = &h.state().views[0]; + assert_eq!((v.current, v.fit, v.layout, v.continuous), (0, Fit::Width, PageLayout::Single, true)); + // Garbage in the settings is ignored, a usable entry among it is kept. + let mut app = PdfCraftApp::new(); + app.restore(&format!( + r#"{{"restore_last_view": "yes", "last_views": [5, {{"path": ""}}, {{"path": "{}", "page": -1, "zoom": "huge", "display": "single"}}, {{"path": "{}", "page": 1, "zoom": "fit-page", "display": "two-up"}}]}}"#, + path.replace('\\', "/"), + path.replace('\\', "/") + )); + assert!(app.restore_last_view, "a non-boolean keeps the default"); + assert_eq!(app.last_views.len(), 1); + assert_eq!((app.last_views[0].page, app.last_views[0].zoom.as_str()), (1, "fit-page")); +} diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 4ec39848c5..f00d5f5d0d 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -695,7 +695,19 @@ status = "shipped" commands = [] tools = [] evidence = ["crates/ui-egui/tests/it/editing.rs::the_last_session_reopens_its_files_pages_and_active_tab", "crates/ui-egui/tests/it/editing.rs::quitting_with_unsaved_changes_still_remembers_every_file", "crates/ui-egui/tests/it/editing.rs::the_last_session_is_off_by_default_and_skips_missing_and_duplicate_files", "crates/ui-egui/src/last_session.rs::malformed_sessions_are_skipped_and_capped"] -notes = "Desktop, off by default (Preferences ▸ Documents and view, #442): the files open when PdfCraft last closed reopen at the page each showed, with the same tab in front; files named on the command line open after them, in front; files that are gone, and documents the Recovery dialog offers, are skipped. Quitting with unsaved changes still remembers every tab. Not yet: zoom, layout and scroll position within the page (#418); only the first encrypted file asks for its password (later ones stay closed), and a file opened after it dismisses that prompt; a save that fails partway through a quit can leave the remembered list from that quit; never-saved documents and the web build have no paths to reopen." +notes = "Desktop, off by default (Preferences ▸ Documents and view, #442): the files open when PdfCraft last closed reopen at the page each showed, with the same tab in front; files named on the command line open after them, in front; files that are gone, and documents the Recovery dialog offers, are skipped. Quitting with unsaved changes still remembers every tab. Zoom and page display come back through core.restore-last-view (#1767); not yet the scroll position within the page (#418); only the first encrypted file asks for its password (later ones stay closed), and a file opened after it dismisses that prompt; a save that fails partway through a quit can leave the remembered list from that quit; never-saved documents and the web build have no paths to reopen." + +[[feature]] +id = "core.restore-last-view" +title = "Restore last view settings when reopening documents" +area = "A" +tier = "P2" +milestone = "M3" +status = "shipped" +commands = [] +tools = [] +evidence = ["crates/ui-egui/tests/it/ui.rs::a_file_opened_again_shows_its_last_view"] +notes = "Desktop, on by default (Preferences ▸ Documents and view, #1767): a file opened again shows the page, zoom and page display it was last shown with, remembered by path when its tab closes or when settings are saved with it open (the last 100 files), and applied over the file's own initial view. Not remembered: the view rotation (display only) and the scroll position within the page; Fit height comes back as Fit width (the settings name only two fits)." [[feature]] id = "core.usage-rights-preserve"