diff --git a/README.md b/README.md index af49129eca..7d3069258f 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,8 @@ Right-click a PDF tab to close it, reopen the last closed file-backed tab, or re Bookmarks, page thumbnails and the document's own page labels (i, ii, 1, 2…) keep you oriented in long documents. Search bookmark titles in the Bookmarks panel to find nested entries even when their parents are collapsed. Matches keep their ancestors for context; Clear restores the unfiltered tree without changing its expansion state. +In **Inspector > Pages**, use **Thumbnail size** and the smaller/larger buttons to show more pages or inspect larger previews (50–150%). This changes only the thumbnails, keeping multi-page selection and the current page in view. The size belongs to the open document view and survives reloading its file; it is not remembered after restarting. Agents can set it with `ui.set {"key":"thumbnail-zoom","value":"75"}` and read `active.thumbnail_zoom` from `ui.state`. + diff --git a/ROADMAP.md b/ROADMAP.md index 96f6ede4b9..28122d6530 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,7 @@ # PdfCraft roadmap +> **Last reviewed:** 2026-10-21 · **Last updated:** 2026-10-21 · **Change:** minor (M3, #3808: the Pages inspector has an independent 50–150% thumbnail size slider and buttons; selection and current-page following are retained, with headless ui.set coverage. Estimates unchanged.) + **Stage: alpha** · next: beta, ~25% (ready for real work 50% → 75%) and ~400–700 h away > **Last reviewed:** 2026-10-18 · **Last updated:** 2026-10-18 · **Change:** minor (M5, PR #1971: bounded nested FDF export excludes source page/page-tree dictionaries; refreshed against upstream 4db6ac4a; estimates unchanged) @@ -172,6 +174,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md ## Progress log +- **2026-10-21 (M3, #3808):** Page thumbnails in the Inspector can be resized independently from the document and Organize, with a slider, buttons and `thumbnail-zoom` control option. Selection, page following and reload are covered by regressions. Estimates unchanged. - **2026-10-20 (M6, calculation order page):** Prepare a form ▸ Calculation order, a page of its own, lists the calculated fields in the order they calculate with their formulas and values, moved with Up/Down arrows (as Acrobat's Set Field Calculation Order), Alt+↑/↓ or by dragging. A card says what is wrong with the order and holds Fix order; a loop has a card of its own that opens one of its formulas. On the page, each calculated field shows its step, and arrows show what the field in focus reads and what reads it. Estimates unchanged. - **2026-10-20 (M6, calculation order):** The order a form's fields calculate in (`/CO`) is read, checked, fixed and set headlessly with `form_calculation_order`: it says which fields read one calculated later (they show stale values), which read each other in a loop, and which the PDF's order leaves out; Fix order moves only what must move, in one undo step, and the form recalculates. Editing a formula no longer moves its field to the end of the order, and a field the order lists twice calculates once. Estimates unchanged. - **2026-10-19 (M6, renames keep formulas):** Renaming a field renames it in every calculation that reads it, in the same undo step: `AFSimple_Calculate` field lists, simplified field notation (the comment and the JavaScript after it) and `getField` calls naming it in one string literal; the rest of each script is kept as written. A sum used to drop the renamed field, notation read it as 0 and a script kept a stale value. Field Properties says how many formulas changed and `form_set_props` lists them as `formulas_updated`. Names built at run time and other events' scripts are not rewritten. Estimates unchanged. diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 3d78f952d6..98369bd59c 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -565,6 +565,8 @@ pub struct DocView { /// The current page the Pages panel last followed: it scrolls to the current page's /// thumbnail only when the page changes. pub(crate) panel_followed: Option, + /// Scale of the thumbnails in the Pages inspector, independent of document and grid zoom. + thumbnail_zoom: f32, /// How large the page grid draws its pages (1.0 = the usual thumbnails). grid_zoom: f32, /// A zoom asked for this frame (toolbar, pinch, keys); the grid applies it once it has laid @@ -603,6 +605,8 @@ pub struct DocView { /// The page grid's zoom range and the step of its buttons and keys. pub const GRID_ZOOM_RANGE: std::ops::RangeInclusive = 0.5..=3.0; +/// Thumbnail size in the Pages inspector, relative to its usual size. +pub const THUMBNAIL_ZOOM_RANGE: std::ops::RangeInclusive = 0.5..=1.5; const GRID_ZOOM_STEP: f32 = 1.25; /// How far a sharp grid render may be from the size drawn before it is redone: a pinch changes /// the size on every frame, and a slightly soft or slightly large image is fine meanwhile. @@ -785,6 +789,7 @@ impl DocView { pages_panel: None, panel_gap: None, panel_followed: None, + thumbnail_zoom: 1.0, grid_zoom: 1.0, grid_zoom_request: None, grid_anchor: None, @@ -862,6 +867,7 @@ impl DocView { fresh.current = self.current; fresh.organize = self.organize; fresh.grid_zoom = self.grid_zoom; + fresh.thumbnail_zoom = self.thumbnail_zoom; // Same document id, so the scroll offset egui keeps for it is still this document's. fresh.shown = self.shown; fresh.highlight_fields = self.highlight_fields; @@ -1498,6 +1504,23 @@ impl DocView { self.grid_zoom } + /// Scale of the thumbnails in the Pages inspector (1.0 is the usual size). + pub fn thumbnail_zoom(&self) -> f32 { + self.thumbnail_zoom + } + + /// Resize the inspector thumbnails, keeping the current page in view after layout. + /// Out-of-range values are clamped; non-finite values are ignored. + pub fn set_thumbnail_zoom(&mut self, zoom: f32) { + if zoom.is_finite() { + let zoom = zoom.clamp(*THUMBNAIL_ZOOM_RANGE.start(), *THUMBNAIL_ZOOM_RANGE.end()); + if zoom != self.thumbnail_zoom { + self.thumbnail_zoom = zoom; + self.panel_followed = None; + } + } + } + /// Zoom the page grid; out-of-range values are clamped and nonsense is ignored. pub fn set_grid_zoom(&mut self, zoom: f32) { if zoom.is_finite() { diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index a4ab35d969..bca9a7784a 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -680,6 +680,7 @@ impl Host for crate::PdfCraftApp { "layout": format!("{:?}", v.layout), "organize": v.organize, "grid_zoom": v.grid_zoom(), + "thumbnail_zoom": v.thumbnail_zoom(), // Pages picked in the organize grid or the Pages panel (empty: the current page). "selected_pages": v.selected.iter().map(|p| p + 1).collect::>(), "auto_scrolling": v.auto_scrolling(), diff --git a/crates/ui-egui/src/i18n/ar.tsv b/crates/ui-egui/src/i18n/ar.tsv index f6b3d093d5..66852656bc 100644 --- a/crates/ui-egui/src/i18n/ar.tsv +++ b/crates/ui-egui/src/i18n/ar.tsv @@ -2757,3 +2757,6 @@ certificate Version الإصدار Paragraph spacing تباعد الفقرات Less أقل Other options خيارات أخرى + Thumbnail size حجم الصور المصغرة + Smaller thumbnails تصغير الصور المصغرة + Larger thumbnails تكبير الصور المصغرة diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 90e152c27b..6e35360155 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2444,3 +2444,6 @@ animation effects Off Изключени Paragraph spacing Разстояние между абзаците Less По-малко Other options Други опции + Thumbnail size Размер на миниатюрите + Smaller thumbnails По-малки миниатюри + Larger thumbnails По-големи миниатюри diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 1366cb2546..ac20dcd70d 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2555,3 +2555,6 @@ animation effects Off Vypnuto Paragraph spacing Mezera mezi odstavci Less Méně Other options Další možnosti + Thumbnail size Velikost miniatur + Smaller thumbnails Menší miniatury + Larger thumbnails Větší miniatury diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index ac136cd91a..d6f38a0308 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2482,3 +2482,6 @@ animation effects Off Aus Paragraph spacing Absatzabstand Less Weniger Other options Weitere Optionen + Thumbnail size Miniaturgröße + Smaller thumbnails Kleinere Miniaturen + Larger thumbnails Größere Miniaturen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 133e8ca8e6..18c84d6098 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2487,3 +2487,6 @@ animation effects Off Ανενεργά Paragraph spacing Απόσταση παραγράφων Less Λιγότερα Other options Άλλες επιλογές + Thumbnail size Μέγεθος μικρογραφιών + Smaller thumbnails Μικρότερες μικρογραφίες + Larger thumbnails Μεγαλύτερες μικρογραφίες diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 7c7b7c8d71..27ba9b364b 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2621,3 +2621,6 @@ animation effects Off Desactivados # Historical snapshot notice bar (View signed version / View revision). You are viewing the signed version of “{from}” (revision {rev}). It is a snapshot: changes here won't affect the original. Estás viendo la versión firmada de «{from}» (revisión {rev}). Es una instantánea: los cambios aquí no afectarán al original. You are viewing revision {rev} of “{from}”. It is a snapshot: changes here won't affect the original. Estás viendo la revisión {rev} de «{from}». Es una instantánea: los cambios aquí no afectarán al original. + Thumbnail size Tamaño de las miniaturas + Smaller thumbnails Miniaturas más pequeñas + Larger thumbnails Miniaturas más grandes diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index aa83f81970..00046dc1a9 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2481,3 +2481,6 @@ animation effects Off Désactivés Paragraph spacing Espacement des paragraphes Less Moins Other options Autres options + Thumbnail size Taille des vignettes + Smaller thumbnails Réduire les vignettes + Larger thumbnails Agrandir les vignettes diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 123be343f3..09242cd0d7 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2480,3 +2480,6 @@ animation effects Off Ki Paragraph spacing Bekezdéstávolság Less Kevesebb Other options Egyéb beállítások + Thumbnail size Bélyegképek mérete + Smaller thumbnails Kisebb bélyegképek + Larger thumbnails Nagyobb bélyegképek diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 71715dc5d7..5abc16b6c0 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2693,3 +2693,6 @@ certificate SHA-256 digest Impronta SHA-256 Paragraph spacing Spaziatura paragrafi Less Meno Other options Altre opzioni + Thumbnail size Dimensione miniature + Smaller thumbnails Miniature più piccole + Larger thumbnails Miniature più grandi diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index f73e753def..47a5668822 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2481,3 +2481,6 @@ animation effects Off オフ Paragraph spacing 段落間隔 Less 折りたたむ Other options その他のオプション + Thumbnail size サムネイルのサイズ + Smaller thumbnails サムネイルを小さく + Larger thumbnails サムネイルを大きく diff --git a/crates/ui-egui/src/i18n/kk.tsv b/crates/ui-egui/src/i18n/kk.tsv index 7f113d4bfc..861890857f 100644 --- a/crates/ui-egui/src/i18n/kk.tsv +++ b/crates/ui-egui/src/i18n/kk.tsv @@ -2479,3 +2479,6 @@ animation effects Off Өшірулі Paragraph spacing Абзац аралығы Less Азырақ Other options Басқа параметрлер + Thumbnail size Нобай өлшемі + Smaller thumbnails Кішірек нобайлар + Larger thumbnails Үлкенірек нобайлар diff --git a/crates/ui-egui/src/i18n/lt.tsv b/crates/ui-egui/src/i18n/lt.tsv index 6448c6949e..f31541bc32 100644 --- a/crates/ui-egui/src/i18n/lt.tsv +++ b/crates/ui-egui/src/i18n/lt.tsv @@ -2731,3 +2731,6 @@ theme Appearance Išvaizda Paragraph spacing Tarpas tarp pastraipų Less Mažiau Other options Kitos parinktys + Thumbnail size Miniatiūrų dydis + Smaller thumbnails Mažesnės miniatiūros + Larger thumbnails Didesnės miniatiūros diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 578b512bb7..833719e831 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2457,3 +2457,6 @@ animation effects Off Wyłączone Paragraph spacing Odstęp między akapitami Less Mniej Other options Inne opcje + Thumbnail size Rozmiar miniatur + Smaller thumbnails Mniejsze miniatury + Larger thumbnails Większe miniatury diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index ee12fe7ab2..134ce06775 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2495,3 +2495,6 @@ animation effects Off Desativados Paragraph spacing Espaçamento entre parágrafos Less Menos Other options Outras opções + Thumbnail size Tamanho das miniaturas + Smaller thumbnails Miniaturas menores + Larger thumbnails Miniaturas maiores diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 3baec45600..49eae5feef 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2394,3 +2394,6 @@ animation effects Off Выкл Paragraph spacing Интервал между абзацами Less Меньше Other options Другие параметры + Thumbnail size Размер миниатюр + Smaller thumbnails Уменьшить миниатюры + Larger thumbnails Увеличить миниатюры diff --git a/crates/ui-egui/src/i18n/sl.tsv b/crates/ui-egui/src/i18n/sl.tsv index 5ba9dc90c3..10c7aa2699 100644 --- a/crates/ui-egui/src/i18n/sl.tsv +++ b/crates/ui-egui/src/i18n/sl.tsv @@ -2486,3 +2486,6 @@ animation effects Off Izklopljeni Paragraph spacing Razmik med odstavki Less Manj Other options Druge možnosti + Thumbnail size Velikost sličic + Smaller thumbnails Manjše sličice + Larger thumbnails Večje sličice diff --git a/crates/ui-egui/src/i18n/te.tsv b/crates/ui-egui/src/i18n/te.tsv index e0d6a2b34c..445be83198 100644 --- a/crates/ui-egui/src/i18n/te.tsv +++ b/crates/ui-egui/src/i18n/te.tsv @@ -2390,3 +2390,6 @@ animation effects Off ఆఫ్ Paragraph spacing పేరా అంతరం Less తక్కువ Other options ఇతర ఎంపికలు + Thumbnail size థంబ్‌నెయిల్ పరిమాణం + Smaller thumbnails చిన్న థంబ్‌నెయిల్‌లు + Larger thumbnails పెద్ద థంబ్‌నెయిల్‌లు diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 48d34a30da..4d9b88f77b 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2477,3 +2477,6 @@ animation effects Off Kapalı Paragraph spacing Paragraf aralığı Less Daha az Other options Diğer seçenekler + Thumbnail size Küçük resim boyutu + Smaller thumbnails Küçük resimleri küçült + Larger thumbnails Küçük resimleri büyüt diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 246a4a2c56..1137db7f69 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2503,3 +2503,6 @@ animation effects Off Вимкнено Paragraph spacing Інтервал між абзацами Less Менше Other options Інші параметри + Thumbnail size Розмір мініатюр + Smaller thumbnails Зменшити мініатюри + Larger thumbnails Збільшити мініатюри diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 56caf38859..a7ae163844 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2733,3 +2733,6 @@ key Space Space # Historical snapshot notice bar (View signed version / View revision). You are viewing the signed version of “{from}” (revision {rev}). It is a snapshot: changes here won't affect the original. Bạn đang xem phiên bản đã ký của “{from}” (bản sửa đổi {rev}). Đây là ảnh nhanh: các thay đổi ở đây không được áp dụng đến bản gốc. You are viewing revision {rev} of “{from}”. It is a snapshot: changes here won't affect the original. Bạn đang xem bản sửa đổi {rev} của “{from}”. Đây là ảnh nhanh: các thay đổi ở đây không được áp dụng đến bản gốc. + Thumbnail size Kích thước hình thu nhỏ + Smaller thumbnails Giảm kích thước hình thu nhỏ + Larger thumbnails Tăng kích thước hình thu nhỏ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 645c0ab301..2ef3d54e96 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2445,3 +2445,6 @@ animation effects Off 关 Paragraph spacing 段落间距 Less 收起 Other options 其他选项 + Thumbnail size 缩略图大小 + Smaller thumbnails 缩小缩略图 + Larger thumbnails 放大缩略图 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 712f0666a5..fcaeea1616 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2431,3 +2431,6 @@ animation effects Off 關 Paragraph spacing 段落間距 Less 收起 Other options 其他選項 + Thumbnail size 縮圖大小 + Smaller thumbnails 縮小縮圖 + Larger thumbnails 放大縮圖 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index c3b882779c..651ad9c335 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -2649,6 +2649,14 @@ impl PdfCraftApp { v.open_organize(); } } + // Inspector thumbnail size, in percent, independent of document and grid zoom. + ("thumbnail-zoom", Some(v)) => { + let percent = value.trim_end_matches('%').parse::().map_err(|e| e.to_string())?; + if !percent.is_finite() || !canvas::THUMBNAIL_ZOOM_RANGE.contains(&(percent / 100.0)) { + return Err("thumbnail-zoom must be between 50 and 150".into()); + } + v.set_thumbnail_zoom(percent / 100.0); + } // `--grid-zoom 150`: the size of the pages in the organize grid, in percent. ("grid-zoom", Some(v)) => { let percent = value.trim_end_matches('%').parse::().map_err(|e| e.to_string())?; @@ -2790,6 +2798,7 @@ impl PdfCraftApp { "cover", "organize", "grid-zoom", + "thumbnail-zoom", "fields", "find", "rotate", diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 38d5ce557e..56f418167f 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -786,6 +786,10 @@ pub fn right_panel(app: &mut PdfCraftApp, ui: &mut egui::Ui) { ancestor.pop(); } } + if panel == RightPanel::Pages { + let enabled = !modal && view.panel_drag.is_none(); + thumbnail_size(ui, &t, view, enabled); + } egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| match panel { RightPanel::Comments => { if let Some(e) = crate::comments_panel::show(ui, &t, info, view, prefs, comment_allowed, &mut nav) { @@ -1187,6 +1191,57 @@ fn outline_item(ui: &mut egui::Ui, t: &Tokens, info: &DocInfo, item: &OutlineIte } } +/// Size controls stay above the scroll area, so they remain reachable on long documents. +fn thumbnail_size(ui: &mut egui::Ui, t: &Tokens, view: &mut crate::DocView, enabled: bool) { + let before = view.thumbnail_zoom(); + let mut zoom = before; + let label = ui + .horizontal(|ui| { + let label = ui.label(egui::RichText::new(tl!("Thumbnail size")).small().color(t.text_muted)); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + ui.label(egui::RichText::new(format!("{:.0}%", before * 100.0)).small().color(t.text_muted)); + }); + label + }) + .inner; + ui.add_enabled_ui(enabled, |ui| { + ui.spacing_mut().slider_width = (ui.available_width() - 52.0 - 2.0 * ui.spacing().item_spacing.x).max(1.0); + ui.horizontal(|ui| { + if ui + .add_enabled_ui(zoom > *crate::canvas::THUMBNAIL_ZOOM_RANGE.start(), |ui| { + icons::button(ui, "zoom-out", 26.0, false, tl!("Smaller thumbnails")) + }) + .inner + .clicked() + { + zoom -= 0.25; + } + let mut percent = zoom * 100.0; + let range = (*crate::canvas::THUMBNAIL_ZOOM_RANGE.start() * 100.0)..=(*crate::canvas::THUMBNAIL_ZOOM_RANGE.end() * 100.0); + ui.scope(|ui| { + // The regular field fill blends into this panel; keep the slider's full rail visible. + ui.visuals_mut().widgets.inactive.bg_fill = t.border; + let slider = ui.add(egui::Slider::new(&mut percent, range).step_by(1.0).show_value(false).trailing_fill(true)).labelled_by(label.id); + slider.widget_info(|| egui::WidgetInfo::slider(ui.is_enabled(), f64::from(percent), tl!("Thumbnail size"))); + }); + zoom = percent / 100.0; + if ui + .add_enabled_ui(zoom < *crate::canvas::THUMBNAIL_ZOOM_RANGE.end(), |ui| { + icons::button(ui, "zoom-in", 26.0, false, tl!("Larger thumbnails")) + }) + .inner + .clicked() + { + zoom += 0.25; + } + }); + }); + if zoom != before { + view.set_thumbnail_zoom(zoom); + } + ui.add_space(6.0); +} + /// The page thumbnails. A click goes to the page; ⌘/Ctrl-click and ⇧-click pick several pages /// (the selection page commands and Print act on) without moving the document. Right-click /// offers Extract, Cut, Copy and Paste on the selection, or on the page clicked; dragging @@ -1215,7 +1270,9 @@ fn pages(ui: &mut egui::Ui, t: &Tokens, info: &DocInfo, view: &mut crate::DocVie // scroll bar dragged by the user already wins over a scroll to a place in egui. let follow = view.panel_followed != Some(view.current) && view.panel_drag.is_none(); view.panel_followed = Some(view.current); - let w = (ui.available_width() - 40.0).min(150.0); + let available = (ui.available_width() - 16.0).max(1.0); + let usual = (ui.available_width() - 40.0).clamp(1.0, 150.0); + let w = (usual * view.thumbnail_zoom()).min(available); let count = info.pages.len(); let mut rows: Vec = Vec::with_capacity(info.pages.len()); let mut dropped = false; diff --git a/crates/ui-egui/tests/control.rs b/crates/ui-egui/tests/control.rs index 7de5c59997..013f96abd7 100644 --- a/crates/ui-egui/tests/control.rs +++ b/crates/ui-egui/tests/control.rs @@ -86,6 +86,31 @@ fn ok(h: &mut Harness<'static, PdfCraftApp>, c: &ControlClient, method: &str, pa call(h, c, method, params).unwrap_or_else(|e| panic!("{method}: {e}")) } +#[test] +fn thumbnail_size_is_settable_and_inspectable_through_the_control_channel() { + let (mut h, c) = harness(); + ok(&mut h, &c, "ui.set", json!({"key": "panel", "value": "pages"})); + ok(&mut h, &c, "ui.set", json!({"key": "thumbnail-zoom", "value": "50"})); + let state = ok(&mut h, &c, "ui.state", json!({})); + assert_eq!(state["active"]["thumbnail_zoom"], 0.5); + assert_eq!(state["active"]["grid_zoom"], 1.0); + assert_eq!(state["documents"][0]["dirty"], false); + let controls = ok(&mut h, &c, "ui.inspect", json!({"query": "thumbnails"})); + let widgets = controls["widgets"].as_array().unwrap(); + assert!(widgets.iter().any(|w| w["label"] == "Larger thumbnails")); + assert!(widgets.iter().any(|w| w["label"] == "Smaller thumbnails" && w["enabled"] == false)); + assert!(call(&mut h, &c, "ui.set", json!({"key": "thumbnail-zoom", "value": "NaN"})).is_err()); + ok(&mut h, &c, "ui.click", json!({"label": "Larger thumbnails"})); + h.run_steps(3); + assert_eq!(ok(&mut h, &c, "ui.state", json!({}))["active"]["thumbnail_zoom"], 0.75); + let sliders = ok(&mut h, &c, "ui.inspect", json!({"query": "Thumbnail size", "role": "Slider"})); + let slider = sliders["widgets"].as_array().unwrap().first().expect("the thumbnail size slider is labelled"); + ok(&mut h, &c, "ui.focus", json!({"id": slider["id"]})); + ok(&mut h, &c, "ui.key", json!({"key": "ArrowRight"})); + h.run_steps(3); + assert!(ok(&mut h, &c, "ui.state", json!({}))["active"]["thumbnail_zoom"].as_f64().unwrap() > 0.75); +} + #[test] fn control_resize_reports_observed_dimensions_and_rejects_invalid_sizes() { let (mut h, c) = harness(); diff --git a/crates/ui-egui/tests/editing.rs b/crates/ui-egui/tests/editing.rs index 6885a2bb6d..9f71772ad9 100644 --- a/crates/ui-egui/tests/editing.rs +++ b/crates/ui-egui/tests/editing.rs @@ -3854,6 +3854,28 @@ fn dragging_pages_panel_thumbnails_reorders_pages() { assert!(view.panel_drag.is_none()); } +#[test] +fn resized_pages_panel_thumbnails_reorder_multiple_pages_and_undo() { + let mut h = harness(4, |app| { + app.right = Some(pdfcraft_ui_egui::RightPanel::Pages); + app.set_option("thumbnail-zoom", "50").unwrap(); + }); + let before = page_texts(h.state()); + h.state_mut().views[0].select_pages(&[1, 2]); + h.run_steps(2); + let from = panel_thumb_rect(&h, "Page 2").center(); + let to = panel_thumb_rect(&h, "Page 1").center_top() + egui::vec2(0.0, 4.0); + drag(&mut h, from, to); + h.run_steps(4); + assert_eq!(page_texts(h.state()), vec![before[1].clone(), before[2].clone(), before[0].clone(), before[3].clone()]); + assert_eq!(h.state().views[0].target_pages(), vec![0, 1]); + assert_eq!(h.state().views[0].thumbnail_zoom(), 0.5); + assert!(h.state_mut().execute("edit.undo")); + h.run_steps(4); + assert_eq!(page_texts(h.state()), before); + assert_eq!(h.state().views[0].thumbnail_zoom(), 0.5); +} + #[test] fn a_thumbnail_drag_that_ends_off_the_panel_or_outlives_the_pages_is_dropped() { let mut h = harness(4, |app| app.right = Some(pdfcraft_ui_egui::RightPanel::Pages)); diff --git a/crates/ui-egui/tests/viewer.rs b/crates/ui-egui/tests/viewer.rs index b154694854..9fa287bd0d 100644 --- a/crates/ui-egui/tests/viewer.rs +++ b/crates/ui-egui/tests/viewer.rs @@ -1408,6 +1408,86 @@ fn in_panel_view(h: &Harness<'static, PdfCraftApp>, r: egui::Rect) -> bool { h.state().views[0].pages_panel.expect("the Pages panel is shown").contains_rect(r) } +#[test] +fn pages_panel_thumbnail_size_controls_show_more_pages_and_keep_the_selection() { + let mut h = pages_panel_harness(&[(200, 300); 30]); + h.state_mut().views[0].select_pages(&[0, 1]); + h.run_steps(2); + let usual = thumbnail_rect(&h, 1).width(); + let zoom = h.state().views[0].zoom; + let visible = |h: &Harness<'static, PdfCraftApp>| (1..=30).filter(|n| in_panel_view(h, thumbnail_rect(h, *n))).count(); + let before = visible(&h); + h.get_by_label("Smaller thumbnails").click(); + h.run_steps(4); + assert!(thumbnail_rect(&h, 1).width() < usual); + assert!(visible(&h) > before, "smaller thumbnails show more complete pages"); + assert_eq!(h.state().views[0].target_pages(), vec![0, 1]); + assert_eq!(h.state().views[0].current, 0); + assert_eq!(h.state().views[0].zoom, zoom, "only the thumbnails are resized"); + assert!(!h.state().session.get(h.state().views[0].id).unwrap().dirty); + if let Ok(dir) = std::env::var("PDFCRAFT_THUMBNAIL_SHOTS") { + h.render().unwrap().save(format!("{dir}/small.png")).unwrap(); + } + h.get_by_label("Larger thumbnails").click(); + h.run_steps(4); + assert!((thumbnail_rect(&h, 1).width() - usual).abs() < 0.1); + h.get_by_label("Larger thumbnails").click(); + h.run_steps(4); + assert!(thumbnail_rect(&h, 1).width() > usual); + assert_eq!(h.state().views[0].target_pages(), vec![0, 1]); + if let Ok(dir) = std::env::var("PDFCRAFT_THUMBNAIL_SHOTS") { + h.render().unwrap().save(format!("{dir}/large.png")).unwrap(); + h.state_mut().set_option("theme", "dark").unwrap(); + h.run_steps(4); + h.render().unwrap().save(format!("{dir}/large-dark.png")).unwrap(); + } +} + +#[test] +fn pages_panel_thumbnail_zoom_is_bounded_independent_and_survives_reload() { + let mut h = pages_panel_harness(&[(200, 300); 6]); + let usual = thumbnail_rect(&h, 1).width(); + let zoom = h.state().views[0].zoom; + h.state_mut().set_option("grid-zoom", "200").unwrap(); + h.state_mut().set_option("thumbnail-zoom", "50%").unwrap(); + h.run_steps(4); + let small = thumbnail_rect(&h, 1).width(); + assert!(small < usual); + for invalid in ["0", "49", "151", "NaN", "inf", "nope"] { + assert!(h.state_mut().set_option("thumbnail-zoom", invalid).is_err(), "reject {invalid}"); + } + h.run_steps(3); + assert_eq!(thumbnail_rect(&h, 1).width(), small); + assert_eq!(h.state().views[0].grid_zoom(), 2.0); + assert_eq!(h.state().views[0].zoom, zoom); + let info = h.state().session.get(h.state().views[0].id).unwrap().info.clone(); + h.state_mut().views[0].document_replaced(&info); + h.run_steps(4); + assert_eq!(thumbnail_rect(&h, 1).width(), small, "file reload keeps the chosen thumbnail size"); + h.state_mut().views[0].set_thumbnail_zoom(f32::NAN); + assert_eq!(h.state().views[0].thumbnail_zoom(), 0.5); + h.state_mut().views[0].set_thumbnail_zoom(f32::MAX); + assert_eq!(h.state().views[0].thumbnail_zoom(), 1.5); + assert!(!h.state().session.get(h.state().views[0].id).unwrap().dirty); + let mut empty = PdfCraftApp::new(); + assert!(empty.set_option("thumbnail-zoom", "100").is_err()); +} + +#[test] +fn pages_panel_thumbnail_zoom_keeps_a_far_current_page_visible() { + let mut h = pages_panel_harness(&[(200, 300); 30]); + h.state_mut().views[0].go_to_page(24); + h.run_steps(4); + for size in ["50", "150", "80", "100"] { + h.state_mut().set_option("thumbnail-zoom", size).unwrap(); + h.run_steps(4); + assert!(in_panel_view(&h, thumbnail_rect(&h, 25)), "page 25 stays visible at {size}%"); + assert_eq!(h.state().views[0].current, 24); + h.get_by_label("Smaller thumbnails"); + h.get_by_label("Larger thumbnails"); + } +} + /// #2781: as in Acrobat, going to a page whose thumbnail is out of sight scrolls the Pages /// panel to show it. #[test] diff --git a/docs/roadmap.md b/docs/roadmap.md index 201cc1d6c3..7f7ce15959 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,5 +1,7 @@ # PdfCraft roadmap: milestones and what's next +> **Last reviewed:** 2026-10-21 · **Last updated:** 2026-10-21 · **Change:** minor (M3, #3808: the Pages inspector has an independent 50–150% thumbnail size slider and buttons; selection and current-page following are retained, with headless ui.set coverage. Estimates unchanged.) + > **Last reviewed:** 2026-10-18 · **Last updated:** 2026-10-18 · **Change:** minor (M5, PR #1971: bounded nested FDF export excludes source page/page-tree dictionaries; refreshed against upstream 4db6ac4a; estimates unchanged) > **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-15 · **Change:** minor (M14/#2383 GPU-limit candidate; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) > **Last reviewed:** 2026-10-15 · **Last updated:** 2026-10-15 · **Change:** minor (M7: the Edit text caret is as tall as the row it is on; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) @@ -116,7 +118,7 @@ total matches the parity estimate. | M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings. Packed TIFF-predictor RGB rows retain later rows in decoding and rendering. Page marks, added content and print output retain small page-unit transform coefficients when serialized. | | M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings. Packed TIFF-predictor RGB rows retain later rows in decoding and rendering. The renderer keeps its one-point crop-size floor in physical points after UserUnit scaling, with regression coverage for crop origins, rotations and f32 reciprocal rounding. Existing unit policy and nearly-zero fallback are retained. | | M2 | Model, render, text | 200–350 | 15% | 130–230 | hayro bootstrap renderer (vendored patches); inspector predictor scratch is bounded by available input, and JPX sample rescaling handles 1–32 bits safely. Text extraction reaches word-F1 0.98 against pdftotext. Short same-line redraws split before placement rather than interleaving. Missing: model crate, fonts, DisplayList, renderer independent of hayro. Inspector page-label traversal and output are resource-bounded, with physical-number fallback warnings | -| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel
Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). Selected-text Copy in Edit and the canvas context menu is covered by focused regressions and native clipboard checks (#1225); document copy restrictions apply to the floating action too. #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. Page-label navigation displays logical labels, retains the current page on unchanged submission and refreshes focused same-document replacements; clean file-watcher reload preserves the chosen scrolling mode, preventing a Single Page reader from jumping to page 1 during replacement. Synthetic control regressions cover these behaviors. #3149: page-list changes discard render demand based on old geometry; actual raster dimensions are checked against the GPU limit before upload. Synthetic crash and continued-rendering regressions cover the replacement path. Save-prompt shortcuts yield to dialogs above them; stacked link-consent UI regression covers Escape and Return. | +| M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). #3808: the Pages inspector offers a 50–150% thumbnail slider and buttons, also available through `ui.set`. Missing: 60 fps test on a 500-page document, snapshot tests of every panel
Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). Selected-text Copy in Edit and the canvas context menu is covered by focused regressions and native clipboard checks (#1225); document copy restrictions apply to the floating action too. #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. Page-label navigation displays logical labels, retains the current page on unchanged submission and refreshes focused same-document replacements; clean file-watcher reload preserves the chosen scrolling mode, preventing a Single Page reader from jumping to page 1 during replacement. Synthetic control regressions cover these behaviors. #3149: page-list changes discard render demand based on old geometry; actual raster dimensions are checked against the GPU limit before upload. Synthetic crash and continued-rendering regressions cover the replacement path. Save-prompt shortcuts yield to dialogs above them; stacked link-consent UI regression covers Escape and Return. | | M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel
Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). Selected-text Copy in Edit and the canvas context menu is covered by focused regressions and native clipboard checks (#1225); document copy restrictions apply to the floating action too. #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. Page-label navigation displays logical labels, retains the current page on unchanged submission and refreshes focused same-document replacements; clean file-watcher reload preserves the chosen scrolling mode, preventing a Single Page reader from jumping to page 1 during replacement. Synthetic control regressions cover these behaviors. Wrapped revision buttons stay above Binding in Document Properties (#2739); a synthetic UI test checks layout and saved revision content. | | M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel
Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. Later native-open replies stay queued while a modal or picker owns the foreground; regressions cover recipient ownership, completion/cancellation and one-shot wakes. | | M3 | Viewer app (native + web) | 80–150 | 90% | 10–20 | Acrobat-style shell, find, select, panels, tiles, web build, UI control channel for agents (opt-in). Missing: 60 fps test on a 500-page document, snapshot tests of every panel
Done since: fit visible, document title in the window; Linux middle-button scrolling; shared three-state theme preferences in the toolbar, View menu and control channel; numeric Linux portal events and idle wakeups completing #881, with native appearance inheritance retained (#446); late browser startup downloads preserve the current document and Save target (#171). #1364 candidate: absolute local GoToR links ask before target access; headless callers explicitly opt in. Native-opening candidate: chosen tools survive preparation and password retries; startup intake waits for pending preparation. #1980: exact quit modifiers preserve Ctrl+Alt+Q email input; Ctrl+Q/Cmd+Q asks before discarding unsaved documents. | @@ -202,6 +204,7 @@ M2 (renderer), M7 (content editing), M10 (OCR and Office), M11 (Preflight and st | Date | Change | Summary | |---|---|---| +| 2026-10-21 | minor | M3, #3808: the Pages inspector has an independent 50–150% thumbnail size slider and buttons; selection and current-page following are retained, with headless ui.set coverage. Estimates unchanged. | | 2026-10-20 | minor | M6: Prepare a form ▸ Calculation order: formulas and values, what is wrong and Fix order, Up/Down, Alt+↑/↓ and drag, links on the page. Estimates unchanged. | | 2026-10-20 | minor | M6: `form_calculation_order` reads, checks, fixes and sets the calculation order; an edited formula keeps its place in `/CO`. Estimates unchanged. | | 2026-10-19 | minor | M6: renaming a field renames it in the calculations that read it; `form_set_props` returns `formulas_updated` and Field Properties says how many changed. Estimates unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index a78409fade..e8edbae406 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,5 +1,7 @@ # UI and interaction parity +> **Last reviewed:** 2026-10-21 · **Last updated:** 2026-10-21 · **Change:** minor (M3, #3808: the Pages inspector has an independent 50–150% thumbnail size slider and buttons; selection and current-page following are retained, with headless ui.set coverage. Estimates unchanged.) + > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (column select #740, multi-object move #844, Shift-⌘-G #810 fixed) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS) How PdfCraft's tools, panels, shortcuts and feel compare with Acrobat Pro's current interface (the @@ -33,7 +35,7 @@ No dump of Acrobat's menu tree has been diffed against the registry yet (gap 24) | Split window, spreadsheet split | yes | no | planned | | Read mode, full screen | yes | yes | shipped; presentation options planned | | Light, dark, system theme | yes | yes | shipped | -| Side panels: thumbnails, bookmarks, attachments, layers, comments, fields, signatures | yes | yes; panels keep their width (#704) | shipped | +| Side panels: thumbnails, bookmarks, attachments, layers, comments, fields, signatures | yes | yes; panels keep their width (#704); the Pages inspector has a 50–150% thumbnail size slider and buttons (#3808) | shipped | | Destinations, articles, model tree, Tags, Order, Content panels | yes | no | planned | | Message bars (restricted, signed, PDF/A, forms) | yes | restricted and signed; no PDF/A view mode | partial | @@ -81,5 +83,6 @@ No dump of Acrobat's menu tree has been diffed against the registry yet (gap 24) | Date | Change | Summary | |---|---|---| +| 2026-10-21 | minor | Pages inspector thumbnail sizing (#3808), separate from page and Organize zoom; view setting retained on reload and exposed through UI control. Not persisted between sessions. | | 2026-10-11 | minor | Column select shipped (#740, PR #765); several objects move together on one page (#844, PR #907); Shift-⌘-G goes back (#810, PR #835) | | 2026-10-10 | major | Created from the command registry, automation tools, the View area of the checklist and open UI issues | diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index ad00b0115d..f56bd6bec2 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -1262,8 +1262,8 @@ milestone = "M3" status = "shipped" commands = [] tools = [] -evidence = ["crates/ui-egui/tests/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/viewer.rs::going_to_a_far_page_scrolls_its_thumbnail_into_view", "crates/ui-egui/tests/viewer.rs::going_to_a_page_in_view_leaves_the_pages_panel_still", "crates/ui-egui/tests/viewer.rs::a_page_change_while_thumbnails_are_dragged_does_not_scroll_the_pages_panel"] -notes = "The panel follows the current page (#2781): a page whose thumbnail is out of sight scrolls it into view, as little as it takes; a thumbnail already in view, or clicked, leaves the panel still, and a page change while the user scrolls the panel or drags thumbnails does not move it." +evidence = ["crates/ui-egui/tests/editing.rs::resized_pages_panel_thumbnails_reorder_multiple_pages_and_undo", "crates/ui-egui/tests/viewer.rs::pages_panel_thumbnail_zoom_is_bounded_independent_and_survives_reload", "crates/ui-egui/tests/viewer.rs::pages_panel_thumbnail_size_controls_show_more_pages_and_keep_the_selection", "crates/ui-egui/tests/viewer.rs::pages_panel_thumbnail_zoom_keeps_a_far_current_page_visible", "crates/ui-egui/tests/control.rs::thumbnail_size_is_settable_and_inspectable_through_the_control_channel", "crates/ui-egui/tests/ui.rs::theme_and_view_options_apply", "crates/ui-egui/tests/viewer.rs::going_to_a_far_page_scrolls_its_thumbnail_into_view", "crates/ui-egui/tests/viewer.rs::going_to_a_page_in_view_leaves_the_pages_panel_still", "crates/ui-egui/tests/viewer.rs::a_page_change_while_thumbnails_are_dragged_does_not_scroll_the_pages_panel"] +notes = "Thumbnail size: 50–150% with a slider and buttons above the scroll area (#3808), independent of document and Organize zoom. Accessible through ui.set thumbnail-zoom and ui.state active.thumbnail_zoom; retained on file reload, not persisted between sessions. The panel follows the current page (#2781): a page whose thumbnail is out of sight scrolls it into view, as little as it takes; a thumbnail already in view, or clicked, leaves the panel still, and a page change while the user scrolls the panel or drags thumbnails does not move it." [[feature]] id = "view.thumbnail-context-ops" @@ -2885,7 +2885,7 @@ status = "shipped" commands = ["page.organize"] tools = [] evidence = ["crates/ui-egui/tests/editing.rs::page_grid_zooms_with_its_buttons_keys_and_pinch", "crates/ui-egui/tests/editing.rs::a_zoomed_page_grid_still_inserts_and_moves_at_the_right_gap", "crates/ui-egui/tests/editing.rs::zoomed_pages_in_view_are_rendered_at_the_size_drawn", "crates/ui-egui/tests/editing.rs::page_grid_size_is_remembered_for_every_document_and_restart"] -notes = "Organize page grid: 50–300% with toolbar buttons (and a percentage that resets to 100%), trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0; pages drawn larger than their thumbnails are rendered at the size drawn while they are in view (the thumbnail stands in until then, and after an edit), so text stays sharp however long the document is. As in Acrobat, the size last chosen is one for every document and kept in the settings across restarts (also set by --grid-zoom). Missing: a slider as in Acrobat; the Pages side panel has no size control." +notes = "Organize page grid: 50–300% with toolbar buttons (and a percentage that resets to 100%), trackpad pinch or Ctrl/⌘ + wheel about the page under the pointer, and ⌘+ / ⌘− / ⌘0; pages drawn larger than their thumbnails are rendered at the size drawn while they are in view (the thumbnail stands in until then, and after an edit), so text stays sharp however long the document is. As in Acrobat, the size last chosen is one for every document and kept in the settings across restarts (also set by --grid-zoom). Missing in Organize: a slider as in Acrobat. The Pages inspector has its own 50–150% slider and buttons (#3808)." [[feature]] id = "organize.split-every-n"
The find bar showing match 10 of 16 for the word 'type', highlighted in the Expressive Type chapter heading