mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-02 08:01:47 -04:00
Add caching of rendered pages so we don't need to re-render them after changing search term if we already rendered them without any matches
This commit is contained in:
+70
-24
@@ -35,6 +35,20 @@ pub struct ImageData {
|
|||||||
pub area: Rect
|
pub area: Rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PrevRender {
|
||||||
|
successful: bool,
|
||||||
|
contained_term: Option<bool>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_default<T: Default>(vec: &mut Vec<T>, size: usize) {
|
||||||
|
vec.clear();
|
||||||
|
vec.reserve(size.saturating_sub(vec.len()));
|
||||||
|
for _ in 0..size {
|
||||||
|
vec.push(T::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this function has to be sync (non-async) because the poppler::Document needs to be held during
|
// this function has to be sync (non-async) because the poppler::Document needs to be held during
|
||||||
// most of it, but that's basically just a wrapper around `*c_void` cause it's just a binding to C
|
// most of it, but that's basically just a wrapper around `*c_void` cause it's just a binding to C
|
||||||
// code, so it's !Send and thus can't be held across await points. So we can't call any of the
|
// code, so it's !Send and thus can't be held across await points. So we can't call any of the
|
||||||
@@ -80,7 +94,8 @@ pub fn start_rendering(
|
|||||||
// `split_at_mut` at 0 initially (which bascially makes `right == rendered && left == []`),
|
// `split_at_mut` at 0 initially (which bascially makes `right == rendered && left == []`),
|
||||||
// doing basically nothing, but if we get a notification that something has been jumped to,
|
// doing basically nothing, but if we get a notification that something has been jumped to,
|
||||||
// then we can split at that page and render at both sides of it
|
// then we can split at that page and render at both sides of it
|
||||||
let mut rendered = vec![false; n_pages];
|
let mut rendered = vec![];
|
||||||
|
fill_default::<PrevRender>(&mut rendered, n_pages);
|
||||||
let mut start_point = 0;
|
let mut start_point = 0;
|
||||||
|
|
||||||
// This is kinda a weird way of doing this, but if we get a notification that the area
|
// This is kinda a weird way of doing this, but if we get a notification that the area
|
||||||
@@ -102,7 +117,7 @@ pub fn start_rendering(
|
|||||||
// If the new area is smaller, then the same high-quality-rendered images
|
// If the new area is smaller, then the same high-quality-rendered images
|
||||||
// will still look fine, so it's ok to leave it.
|
// will still look fine, so it's ok to leave it.
|
||||||
if bigger {
|
if bigger {
|
||||||
rendered = vec![false; n_pages];
|
fill_default(&mut rendered, n_pages);
|
||||||
continue 'render_pages;
|
continue 'render_pages;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -111,10 +126,24 @@ pub fn start_rendering(
|
|||||||
continue 'render_pages;
|
continue 'render_pages;
|
||||||
},
|
},
|
||||||
RenderNotif::Search(term) => {
|
RenderNotif::Search(term) => {
|
||||||
rendered = vec![false; n_pages];
|
|
||||||
if term.is_empty() {
|
if term.is_empty() {
|
||||||
|
// If the term is set to nothing, then we don't need to re-render
|
||||||
|
// the pages wherein there were already no search results. So this
|
||||||
|
// is a little optimization to allow that.
|
||||||
|
for page in &mut rendered {
|
||||||
|
if !page.successful || page.contained_term != Some(true) {
|
||||||
|
page.successful = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
search_term = None;
|
search_term = None;
|
||||||
} else {
|
} else {
|
||||||
|
// But if the term is set to something new, we need to reset all of
|
||||||
|
// the 'contained_term' fields so that if they now contain the
|
||||||
|
// term, we can render them with the term, but if they don't, we
|
||||||
|
// don't need to re-render and send it over again.
|
||||||
|
for page in &mut rendered {
|
||||||
|
page.contained_term = None;
|
||||||
|
}
|
||||||
search_term = Some(term);
|
search_term = Some(term);
|
||||||
}
|
}
|
||||||
continue 'render_pages;
|
continue 'render_pages;
|
||||||
@@ -135,8 +164,14 @@ pub fn start_rendering(
|
|||||||
.map(|(idx, p)| (start_point - (idx + 1), p))
|
.map(|(idx, p)| (start_point - (idx + 1), p))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// we go through each page
|
||||||
for (num, rendered) in page_iter {
|
for (num, rendered) in page_iter {
|
||||||
if *rendered {
|
// we only want to continue if one of the following is met:
|
||||||
|
// 1. It failed to render last time (we want to retry)
|
||||||
|
// 2. The `contained_term` is set to None (representing 'Unknown'), meaning that we
|
||||||
|
// need to at least check if it contains the current term to see if it needs a
|
||||||
|
// re-render
|
||||||
|
if rendered.successful && rendered.contained_term.is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,20 +185,26 @@ pub fn start_rendering(
|
|||||||
|
|
||||||
// We know this is in range 'cause we're iterating over it
|
// We know this is in range 'cause we're iterating over it
|
||||||
let Some(page) = doc.page(num as i32) else {
|
let Some(page) = doc.page(num as i32) else {
|
||||||
sender.blocking_send(Err(RenderError::Render(format!("Couldn't get page {num} ({}) of doc??", num as i32))))
|
sender.blocking_send(
|
||||||
|
Err(RenderError::Render(format!("Couldn't get page {num} ({}) of doc?", num as i32)))
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let rendered_with_no_results = rendered.successful && rendered.contained_term == Some(false);
|
||||||
|
|
||||||
// render the page
|
// render the page
|
||||||
let to_send = render_single_page(page, area, num, &search_term)
|
match render_single_page(page, area, num, &search_term, rendered_with_no_results) {
|
||||||
.map(RenderInfo::Page)
|
// If we've already rendered it just fine and we don't need to render it again,
|
||||||
.map_err(RenderError::Render);
|
// just continue. We're all good
|
||||||
|
Ok(None) => (),
|
||||||
// then send it over
|
// If that fn returned Some, that means it needed to be re-rendered for some
|
||||||
sender.blocking_send(to_send).unwrap();
|
// reason or another, so we're sending it here
|
||||||
|
Ok(Some(img)) => sender.blocking_send(Ok(RenderInfo::Page(img))).unwrap(),
|
||||||
*rendered = true;
|
// And if we got an error, then obviously we need to propagate that
|
||||||
|
Err(e) => sender.blocking_send(Err(RenderError::Render(e))).unwrap()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// Then once we've rendered all these pages, wait until we get another notification
|
// Then once we've rendered all these pages, wait until we get another notification
|
||||||
// that this doc needs to be reloaded
|
// that this doc needs to be reloaded
|
||||||
@@ -183,8 +224,20 @@ fn render_single_page(
|
|||||||
page: Page,
|
page: Page,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
page_num: usize,
|
page_num: usize,
|
||||||
search_term: &Option<String>
|
search_term: &Option<String>,
|
||||||
) -> Result<PageInfo, String> {
|
already_rendered_no_results: bool
|
||||||
|
) -> Result<Option<PageInfo>, String> {
|
||||||
|
let mut result_rects = search_term
|
||||||
|
.as_ref()
|
||||||
|
.map(|term| page.find_text_with_options(term, FindFlags::DEFAULT | FindFlags::MULTILINE))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// If there are no search terms on this page, and we've already rendered it with no search
|
||||||
|
// terms, then just return none to avoid this computation
|
||||||
|
if result_rects.is_empty() && already_rendered_no_results {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
// First, get the font size; the number of pixels (width x height) per font character (I
|
// First, get the font size; the number of pixels (width x height) per font character (I
|
||||||
// think; it's at least something like that) on this terminal screen.
|
// think; it's at least something like that) on this terminal screen.
|
||||||
let size = crossterm::terminal::window_size()
|
let size = crossterm::terminal::window_size()
|
||||||
@@ -246,11 +299,6 @@ fn render_single_page(
|
|||||||
ctx.paint().map_err(|e| format!("Couldn't paint Context: {e}"))?;
|
ctx.paint().map_err(|e| format!("Couldn't paint Context: {e}"))?;
|
||||||
page.render(&ctx);
|
page.render(&ctx);
|
||||||
|
|
||||||
let mut result_rects = search_term
|
|
||||||
.as_ref()
|
|
||||||
.map(|term| page.find_text_with_options(term, FindFlags::DEFAULT | FindFlags::MULTILINE))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let num_results = result_rects.len();
|
let num_results = result_rects.len();
|
||||||
|
|
||||||
let mut highlight_color = Color::new();
|
let mut highlight_color = Color::new();
|
||||||
@@ -280,9 +328,7 @@ fn render_single_page(
|
|||||||
ctx.target().write_to_png(&mut img_data)
|
ctx.target().write_to_png(&mut img_data)
|
||||||
.map_err(|e| format!("Couldn't write surface to png: {e}"))?;
|
.map_err(|e| format!("Couldn't write surface to png: {e}"))?;
|
||||||
|
|
||||||
// TODO: Maybe cache which pages had no results with the last search term so we don't have to
|
Ok(Some(PageInfo {
|
||||||
// rerender them when the search term is set to empty and rerenders are requested
|
|
||||||
Ok(PageInfo {
|
|
||||||
img_data: ImageData {
|
img_data: ImageData {
|
||||||
data: img_data,
|
data: img_data,
|
||||||
area: Rect {
|
area: Rect {
|
||||||
@@ -293,5 +339,5 @@ fn render_single_page(
|
|||||||
},
|
},
|
||||||
page: page_num,
|
page: page_num,
|
||||||
search_results: num_results
|
search_results: num_results
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -349,7 +349,14 @@ impl Tui {
|
|||||||
KeyCode::Down | KeyCode::Char('j') => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
KeyCode::Down | KeyCode::Char('j') => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
|
||||||
KeyCode::Left | KeyCode::Char('h') => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
KeyCode::Left | KeyCode::Char('h') => self.change_page(PageChange::Prev, ChangeAmount::Single),
|
||||||
KeyCode::Up | KeyCode::Char('k') => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
KeyCode::Up | KeyCode::Char('k') => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
|
||||||
KeyCode::Esc | KeyCode::Char('q') => Some(InputAction::QuitApp),
|
KeyCode::Esc => match self.bottom_msg {
|
||||||
|
BottomMessage::Input(_) => {
|
||||||
|
self.set_bottom_msg(None);
|
||||||
|
Some(InputAction::Redraw)
|
||||||
|
},
|
||||||
|
_ => Some(InputAction::QuitApp)
|
||||||
|
},
|
||||||
|
KeyCode::Char('q') => Some(InputAction::QuitApp),
|
||||||
KeyCode::Char('g') => {
|
KeyCode::Char('g') => {
|
||||||
self.set_bottom_msg(Some(BottomMessage::Input(InputCommand::GoToPage(0))));
|
self.set_bottom_msg(Some(BottomMessage::Input(InputCommand::GoToPage(0))));
|
||||||
Some(InputAction::Redraw)
|
Some(InputAction::Redraw)
|
||||||
@@ -412,6 +419,11 @@ impl Tui {
|
|||||||
// data to show
|
// data to show
|
||||||
if !term.is_empty() {
|
if !term.is_empty() {
|
||||||
self.set_bottom_msg(Some(BottomMessage::SearchResults(term.clone())));
|
self.set_bottom_msg(Some(BottomMessage::SearchResults(term.clone())));
|
||||||
|
} else {
|
||||||
|
// else, if it's not empty, we just want to reset the bottom
|
||||||
|
// area to show the default data; we don't want it to like show
|
||||||
|
// the data from a previous search
|
||||||
|
self.set_bottom_msg(Some(BottomMessage::Help));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all the search results
|
// Reset all the search results
|
||||||
|
|||||||
Reference in New Issue
Block a user