image_viewer: Preserve PDF state across page changes

- Add ImageItemEvent::PageChanged and emit it from ImageItem::set_page
  instead of Reloaded and MetadataUpdated, since turning a page does not
  change the document bytes
- Handle PageChanged in ImageView by only refreshing layout state,
  keeping the outline, page and thumbnail caches, form edits, undo
  history, and annotations intact
- Jumping via the outline no longer resets the outline panel to
  "No outline"
This commit is contained in:
yenru0 committed 2026-09-25 01:30:39 +09:00
1 parent ad13ed3b7d
commit 1b2685ac9b
2 files changed
+43 -17

No files matched your search

+10
View File
@@ -498,6 +498,15 @@ impl ImageView {
}
cx.notify();
}
ImageItemEvent::PageChanged => {
self.page_bounds.borrow_mut().clear();
self.image_size = self
.image_item
.read(cx)
.image_metadata
.map(|m| (m.width, m.height));
cx.notify();
}
}
}
@@ -5292,6 +5301,7 @@ mod tests {
assert_eq!(cache.current_memory_bytes(), 0);
assert!(cache.is_empty());
}
}
mod persistence {
+33 -17
View File
@@ -21,9 +21,9 @@ use util::{ResultExt, rel_path::RelPath};
use worktree::{LoadedBinaryFile, PathChange, Worktree, WorktreeId};
const EMPTY_1X1_PNG: &[u8] = &[
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1,
8, 6, 0, 0, 0, 31, 21, 196, 137, 0, 0, 0, 13, 73, 68, 65, 84, 120, 156, 99, 96, 96,
96, 96, 0, 0, 0, 5, 0, 1, 165, 246, 69, 64, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0,
0, 0, 31, 21, 196, 137, 0, 0, 0, 13, 73, 68, 65, 84, 120, 156, 99, 96, 96, 96, 96, 0, 0, 0, 5,
0, 1, 165, 246, 69, 64, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
];
#[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]
@@ -53,6 +53,7 @@ pub enum ImageItemEvent {
Reloaded,
FileHandleChanged,
MetadataUpdated,
PageChanged,
}
impl EventEmitter<ImageItemEvent> for ImageItem {}
@@ -180,7 +181,9 @@ impl ImageItem {
if image_bytes.starts_with(b"%PDF-") {
let engine = pdf_viewer::pdfium::PdfiumEngine::new();
let doc = engine.load_document_from_bytes(image_bytes, None)?;
let dim = doc.page_size(0).unwrap_or(pdf_viewer::PageDimensions::new(612.0, 792.0));
let dim = doc
.page_size(0)
.unwrap_or(pdf_viewer::PageDimensions::new(612.0, 792.0));
let (width, height) = dim.to_pixel_size(1.0, 144.0);
return Ok(ImageMetadata {
width,
@@ -278,7 +281,9 @@ impl ImageItem {
if let Some(ref text) = info.full_text {
Some(text.clone())
} else {
pdf_viewer::PdfiumEngine::new().extract_text_from_bytes(&info.pdf_bytes, None).ok()
pdf_viewer::PdfiumEngine::new()
.extract_text_from_bytes(&info.pdf_bytes, None)
.ok()
}
}
@@ -299,8 +304,9 @@ impl ImageItem {
format: image::ImageFormat::Png,
colors: ImageColorInfo::from_color_type(image::ColorType::Rgba8),
});
cx.emit(ImageItemEvent::Reloaded);
cx.emit(ImageItemEvent::MetadataUpdated);
// A page turn keeps the same document bytes, so document-derived state
// (outline, caches, edits) stays valid; only the layout changes.
cx.emit(ImageItemEvent::PageChanged);
cx.notify();
true
} else {
@@ -340,13 +346,15 @@ impl ImageItem {
self.reload_task = Some(cx.spawn(async move |this, cx| {
if let Ok(bytes) = content.await.context("Failed to load image content") {
let is_pdf = bytes.starts_with(b"%PDF-");
let cur_page = this.read_with(cx, |this, _| this.current_page()).unwrap_or(0);
let cur_page = this
.read_with(cx, |this, _| this.current_page())
.unwrap_or(0);
if is_pdf {
let bytes_clone = bytes.clone();
let pdf_res = background.spawn(async move {
create_gpui_images_from_pdf(&bytes_clone)
}).await;
let pdf_res = background
.spawn(async move { create_gpui_images_from_pdf(&bytes_clone) })
.await;
if let Ok((pages, full_text)) = pdf_res {
let total_pages = pages.len();
let target_page = cur_page.min(total_pages.saturating_sub(1));
@@ -403,7 +411,9 @@ pub fn is_image_file(project: &Entity<Project>, path: &ProjectPath, cx: &App) ->
});
match ext {
Some(ext) => (Img::extensions().contains(&ext.as_str()) || ext == "pdf") && !ext.contains("svg"),
Some(ext) => {
(Img::extensions().contains(&ext.as_str()) || ext == "pdf") && !ext.contains("svg")
}
None => false,
}
}
@@ -759,7 +769,10 @@ impl RemoteImageStore {
pages,
full_text,
};
let dummy_img = Arc::new(gpui::Image::from_bytes(gpui::ImageFormat::Png, EMPTY_1X1_PNG.to_vec()));
let dummy_img = Arc::new(gpui::Image::from_bytes(
gpui::ImageFormat::Png,
EMPTY_1X1_PNG.to_vec(),
));
(dummy_img, Some(pdf_info))
} else {
(create_gpui_image(content.clone())?, None)
@@ -829,9 +842,9 @@ impl ImageStoreImpl for Entity<LocalImageStore> {
let is_pdf = content.starts_with(b"%PDF-");
let (image, pdf_info) = if is_pdf {
let content_clone = content.clone();
let pdf_res = background.spawn(async move {
create_gpui_images_from_pdf(&content_clone)
}).await;
let pdf_res = background
.spawn(async move { create_gpui_images_from_pdf(&content_clone) })
.await;
if let Ok((pages, full_text)) = pdf_res {
let total_pages = pages.len();
let pdf_info = PdfPageInfo {
@@ -841,7 +854,10 @@ impl ImageStoreImpl for Entity<LocalImageStore> {
pages,
full_text,
};
let dummy_img = Arc::new(gpui::Image::from_bytes(gpui::ImageFormat::Png, EMPTY_1X1_PNG.to_vec()));
let dummy_img = Arc::new(gpui::Image::from_bytes(
gpui::ImageFormat::Png,
EMPTY_1X1_PNG.to_vec(),
));
(dummy_img, Some(pdf_info))
} else {
(create_gpui_image(content.clone())?, None)