diff --git a/Cargo.lock b/Cargo.lock index 4eb72e1..fb720d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,18 +429,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstyle", "clap_lex", @@ -1143,9 +1143,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", @@ -1736,26 +1736,26 @@ dependencies = [ [[package]] name = "mupdf" version = "0.4.4" -source = "git+https://github.com/itsjunetime/mupdf-rs?branch=june%2Fplug_in_zerocopy#3d41ff71b17138cb2840372a62da2dc94184c90a" +source = "git+https://github.com/itsjunetime/mupdf-rs?branch=remove_debug_print#10f1b1629540e7d62354842198317bcf5e7d619c" dependencies = [ "bitflags 2.8.0", "font-kit", "mupdf-sys", "num_enum", "once_cell", - "zerocopy 0.8.18", + "zerocopy 0.8.19", ] [[package]] name = "mupdf-sys" version = "0.4.4" -source = "git+https://github.com/itsjunetime/mupdf-rs?branch=june%2Fplug_in_zerocopy#3d41ff71b17138cb2840372a62da2dc94184c90a" +source = "git+https://github.com/itsjunetime/mupdf-rs?branch=remove_debug_print#10f1b1629540e7d62354842198317bcf5e7d619c" dependencies = [ "bindgen", "cc", "pkg-config", "regex", - "zerocopy 0.8.18", + "zerocopy 0.8.19", ] [[package]] @@ -2872,15 +2872,16 @@ dependencies = [ "notify", "ratatui", "ratatui-image", + "rayon", "tokio", "xflags", ] [[package]] name = "tempfile" -version = "3.17.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40f762a77d2afa88c2d919489e390a12bdd261ed568e60cfa7e48d4e20f0d33" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", @@ -3254,9 +3255,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -3266,9 +3267,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "unicode-segmentation" @@ -3301,9 +3302,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" dependencies = [ "atomic", "getrandom 0.3.1", @@ -3833,11 +3834,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79386d31a42a4996e3336b0919ddb90f81112af416270cff95b5f5af22b839c2" +checksum = "8207f485579465f62ae51a983e42c906736a17efd2de48b021e64f1bbd8e98c7" dependencies = [ - "zerocopy-derive 0.8.18", + "zerocopy-derive 0.8.19", ] [[package]] @@ -3853,9 +3854,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" +checksum = "7dbe1304a711c6eb4cf1ed333aa0d9b344685e71f6f00c3b176072213bd3783e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index efdcf3f..db5472d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,8 @@ flume = { version = "0.11.0", default-features = false, features = ["async"] } xflags = "0.4.0-pre.2" mimalloc = "0.1.43" nix = { version = "0.29.0", features = ["signal"] } -mupdf = { git = "https://github.com/itsjunetime/mupdf-rs", branch = "june/plug_in_zerocopy", default-features = false, features = ["svg", "system-fonts", "img"] } +mupdf = { git = "https://github.com/itsjunetime/mupdf-rs", branch = "remove_debug_print", default-features = false, features = ["svg", "system-fonts", "img"] } +rayon = { version = "*", default-features = false } # for tracing with tokio-console console-subscriber = { version = "0.4.0", optional = true } diff --git a/src/converter.rs b/src/converter.rs index fc2cd14..ac0727c 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -1,7 +1,9 @@ use flume::{Receiver, SendError, Sender, TryRecvError}; use futures_util::stream::StreamExt; +use image::DynamicImage; use itertools::Itertools; use ratatui_image::{picker::Picker, protocol::Protocol, Resize}; +use rayon::iter::ParallelIterator; use crate::renderer::{fill_default, PageInfo, RenderError}; @@ -53,12 +55,24 @@ pub async fn run_conversion_loop( return Ok(None); }; - let dyn_img = image::load_from_memory_with_format( + let mut dyn_img = image::load_from_memory_with_format( &page_info.img_data.pixels, image::ImageFormat::Pnm ) .map_err(|e| RenderError::Converting(format!("Can't load image: {e}")))?; + match dyn_img { + DynamicImage::ImageRgb8(ref mut img) => + for quad in &*page_info.result_rects { + img.par_enumerate_pixels_mut() + .filter(|(x, y, _)| { + *x > quad.ul_x && *x < quad.lr_x && *y > quad.ul_y && *y < quad.lr_y + }) + .for_each(|(_, _, px)| px.0[2] = px.0[2].saturating_sub(u8::MAX / 2)); + }, + _ => unreachable!() + }; + let img_area = page_info.img_data.cell_area; // We don't actually want to Crop this image, but we've already @@ -79,7 +93,7 @@ pub async fn run_conversion_loop( Ok(Some(ConvertedPage { page: txt_img, num: page_info.page_num, - num_results: page_info.search_results + num_results: page_info.result_rects.len() })) } diff --git a/src/main.rs b/src/main.rs index 1730050..e32245c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -202,7 +202,7 @@ async fn main() -> Result<(), Box> { to_converter.send(ConverterMsg::NumPages(num))?; }, RenderInfo::Page(info) => { - tui.got_num_results_on_page(info.page_num, info.search_results); + tui.got_num_results_on_page(info.page_num, info.result_rects.len()); to_converter.send(ConverterMsg::AddImg(info))?; }, RenderInfo::Reloaded => tui.set_msg(MessageSetting::Some(BottomMessage::Reloaded)), diff --git a/src/renderer.rs b/src/renderer.rs index 54f858f..17a4f76 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -30,7 +30,7 @@ pub enum RenderInfo { pub struct PageInfo { pub img_data: ImageData, pub page_num: usize, - pub search_results: usize + pub result_rects: Vec } #[derive(Clone)] @@ -263,7 +263,7 @@ pub fn start_rendering( // we make a potentially incorrect assumption here that writing the context // to a png won't fail, and mark that it all rendered correctly here before // spawning off the thread to do so and send it. - rendered.contained_term = Some(ctx.num_results > 0); + rendered.contained_term = Some(ctx.result_rects.is_empty()); rendered.successful = true; let cap = (ctx.pixmap.width() @@ -286,7 +286,7 @@ pub fn start_rendering( } }, page_num: num, - search_results: ctx.num_results + result_rects: ctx.result_rects })))?; } // And if we got an error, then obviously we need to propagate that @@ -312,7 +312,7 @@ struct RenderedContext { pixmap: Pixmap, surface_w: f32, surface_h: f32, - num_results: usize + result_rects: Vec } /// SAFETY: I think this is safe because, although the backing struct for `Surface` does contain @@ -333,13 +333,25 @@ fn render_single_page_to_ctx( already_rendered_no_results: bool, (area_w, area_h): (f32, f32) ) -> Result, mupdf::error::Error> { - let result_rects = search_term - .as_ref() - .map(|term| { - page.search(term, u32::MAX) - }) - .transpose()? - .unwrap_or_default(); + let mut max_hits = 10; + let result_rects = loop { + let rects = search_term + .as_ref() + // mupdf allocates a buffer of the size we give it to try to fill it with results. If we + // pass in u32::MAX, it allocates too much memory to function. If we pass too small of a + // number in, we may miss out on some of the results. Ideally, we'd like to make a better + // interface than this, but we're stuck with this kinda ugly looping until we make sure + // that we've found every instance of it on this page. + .map(|term| page.search(term, max_hits)) + .transpose()? + .unwrap_or_default(); + + if rects.len() < (max_hits as usize) { + break rects; + } + + max_hits *= 2; + }; // 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 @@ -384,35 +396,34 @@ fn render_single_page_to_ctx( let new_y = (y_res as f32 * scale_factor) as i32; pixmap.set_resolution(new_x, new_y); - let num_results = result_rects.len(); - - /*if !result_rects.is_empty() { - let mut highlight_color = Color::new(); - highlight_color.set_red((u16::MAX / 5) * 4); - highlight_color.set_green((u16::MAX / 5) * 4); - - let mut old_rect = Rectangle::new(); - for rect in &mut result_rects { - // According to https://gitlab.freedesktop.org/poppler/poppler/-/issues/763, these rects - // need to be corrected since they use different references as the y-coordinate base - rect.set_y1(p_height - rect.y1()); - rect.set_y2(p_height - rect.y2()); - - page.render_selection( - &ctx, - rect, - &mut old_rect, - SelectionStyle::Glyph, - &mut Color::new(), - &mut highlight_color - ); - } - }*/ + let result_rects = result_rects + .into_iter() + .map(|quad| { + let ul_x = (quad.ul.x * scale_factor) as u32; + let ul_y = (quad.ul.y * scale_factor) as u32; + let lr_x = (quad.lr.x * scale_factor) as u32; + let lr_y = (quad.lr.y * scale_factor) as u32; + HighlightRect { + ul_x, + ul_y, + lr_x, + lr_y + } + }) + .collect::>(); Ok(Some(RenderedContext { pixmap, surface_w, surface_h, - num_results + result_rects })) } + +#[derive(Clone)] +pub struct HighlightRect { + pub ul_x: u32, + pub ul_y: u32, + pub lr_x: u32, + pub lr_y: u32 +} diff --git a/src/tui.rs b/src/tui.rs index 02c640b..08ad556 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -569,7 +569,7 @@ impl Tui { pub fn show_error(&mut self, err: RenderError) { self.set_msg(MessageSetting::Some(BottomMessage::Error(match err { RenderError::Notify(e) => format!("Auto-reload failed: {e}"), - RenderError::Doc(e) => format!("Couldn't open document: {e}"), + RenderError::Doc(e) => format!("Couldn't process document: {e}"), RenderError::Converting(e) => format!("Couldn't convert page after rendering: {e}") }))); }