Render visible pages before thumbnails and text extraction (#950)

* Linux: the tab strip carries the window controls, as in Acrobat

On X11 and Wayland the window manager drew its own title bar above the tab
strip, so every PdfCraft window had two bars and the controls the user clicked
were the window manager's, not ours. `ViewportBuilder::with_decorations(false)`
turns the frame off, and `chrome::window_frame` draws minimize, maximize and
close at the top right of the tab strip, plus a hairline border and resizing
from the edges and corners, so a frameless window behaves like a framed one.
Close still goes through the unsaved-changes check, and full screen and the
in-app full-screen page view draw no controls over the page.

Drawing it last keeps the controls reachable from every overlay that covers
the tab strip. `PDFCRAFT_SYSTEM_TITLEBAR=1` puts the window manager's frame
back for anyone who wants it.

* Render visible pages before thumbnails and text extraction

Opening a document queued its visible page pixels together with
Pages-panel thumbnails and background text extraction, so the render
workers spent the first seconds on background work while the visible
page stayed blank. Hold thumbnails and text until no visible main-page
raster is still missing; errored pages never block.

* Render visible pages before thumbnails and text extraction

Opening a document queued its visible page pixels together with
Pages-panel thumbnails and background text extraction, so the render
workers spent the first seconds on background work while the visible
page stayed blank. Hold thumbnails and text until no visible main-page
raster is still missing; errored pages never block.

---------

Co-authored-by: GyuriX <gyurix@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
gyurix 2026-10-11 20:45:36 +02:00 committed by GitHub
parent 8983f05c78
commit 9564ed6885
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 57 additions and 0 deletions

View File

@ -1348,6 +1348,21 @@ impl DocView {
// bounded few under a page whose current tiles are still missing (OLD_TILES).
self.tiles.retain(|key, _| tiles.contains(key) || !tile_tags.contains(&key.1));
queue.extend(grid);
// Opening (or turning to) a page queues its pixels alongside thumbnails and
// text extraction. The workers would otherwise spend the first seconds on
// thumbnails and search text while the visible page stays blank: hold those
// until no visible main-page raster is still missing. Errored pages never
// block: they are never re-requested.
let visible_mains_pending = queue.iter().any(|r| {
r.kind == RequestKind::Pixels
&& r.tag & (GRID_TAG | PRINT_TAG | THUMB_TAG | TEXT_TAG) == 0
&& self.frame_visible.contains(&r.page)
&& !self.errors.contains_key(&r.page)
});
if visible_mains_pending {
// Caches are kept; demand is re-evaluated next frame once pixels arrive.
return queue;
}
let thumbs = self.thumbnail_requests(info, ppp);
let mut thumb_bytes = 0usize;
let mut admitted = HashSet::new();
@ -4192,6 +4207,48 @@ trailer << /Root 1 0 R >>
assert!(v.prepare_render_queue(&info, 1.0).is_empty());
}
#[test]
fn visible_pages_render_before_thumbnails_and_text() {
let ctx = egui::Context::default();
let info = thumbnail_info(4);
let mut v = DocView::new(DocId(1), &info, ViewDefaults::default());
let main = RenderRequest { page: 0, scale: 1.0, tag: 1000, ..Default::default() };
let text = RenderRequest { page: 0, kind: RequestKind::Text, scale: 1.0, tag: TEXT_TAG, ..Default::default() };
// Opening page 0 queues its pixels and text alongside Pages-panel thumbnails.
v.begin_render_frame();
v.frame_visible = [0].into();
v.frame_queue = vec![main, text];
for p in 1..4 {
v.need_thumbnail(p, true);
}
let queue = v.prepare_render_queue(&info, 1.0);
assert_eq!(queue, vec![main], "visible pixels first: no thumbnails or text yet");
// Once the visible raster arrives, background work is admitted.
v.last_queue = queue;
assert!(v.receive_result(&ctx, solid_result(main, 100, 130)) > 0);
v.begin_render_frame();
v.frame_visible = [0].into();
v.frame_queue = vec![text];
for p in 1..4 {
v.need_thumbnail(p, true);
}
let queue = v.prepare_render_queue(&info, 1.0);
assert!(queue.iter().any(|r| r.kind == RequestKind::Text), "text admitted after pixels");
assert!(queue.iter().any(|r| r.tag & THUMB_TAG != 0), "thumbnails admitted after pixels");
assert!(!queue.iter().any(|r| r.kind == RequestKind::Pixels && r.tag & THUMB_TAG == 0), "no main pixels re-requested");
// An errored visible page never holds background work back.
v.errors.insert(0, "gone".to_string());
v.begin_render_frame();
v.frame_visible = [0].into();
v.frame_queue = vec![main, text];
for p in 1..4 {
v.need_thumbnail(p, true);
}
let queue = v.prepare_render_queue(&info, 1.0);
assert!(queue.iter().any(|r| r.kind == RequestKind::Text), "text admitted despite the error");
assert!(queue.iter().any(|r| r.tag & THUMB_TAG != 0), "thumbnails still admitted");
}
#[test]
fn thumbnail_tags_do_not_change_as_a_row_scrolls_in_or_out() {
let info = thumbnail_info(10_000);