diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 09044ea..61ccf7b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,6 +28,8 @@ jobs: sudo apt-get update sudo apt-get install -y libfontconfig1-dev libgoogle-perftools-dev google-perftools - uses: actions/checkout@v4 + - name: Install clippy and fmt + run: rustup component add clippy rustfmt - name: Clippy run: cargo clippy -- -D warnings - name: Check fmt diff --git a/Cargo.lock b/Cargo.lock index b5fa1ac..4cf2431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,15 @@ dependencies = [ "phf", ] +[[package]] +name = "csscolorparser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288" +dependencies = [ + "phf", +] + [[package]] name = "darling" version = "0.20.11" @@ -2866,6 +2875,7 @@ dependencies = [ "cpuprofiler", "criterion", "crossterm", + "csscolorparser 0.7.0", "flume", "futures-util", "image", @@ -3473,7 +3483,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" dependencies = [ - "csscolorparser", + "csscolorparser 0.6.2", "deltae", "lazy_static", "wezterm-dynamic", diff --git a/Cargo.toml b/Cargo.toml index 8d18d8e..878b45f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ rayon = { version = "*", default-features = false } # for tracing with tokio-console console-subscriber = { version = "0.4.0", optional = true } +csscolorparser = { version = "0.7.0" } [profile.production] inherits = "release" diff --git a/benches/for_profiling.rs b/benches/for_profiling.rs index f03a017..6800929 100644 --- a/benches/for_profiling.rs +++ b/benches/for_profiling.rs @@ -9,5 +9,5 @@ async fn main() { .nth(1) .expect("Please enter a file to profile"); - utils::render_doc(file, None).await; + utils::render_doc(file, None, 0, 1000).await; } diff --git a/benches/rendering.rs b/benches/rendering.rs index b7a3457..78ca4f6 100644 --- a/benches/rendering.rs +++ b/benches/rendering.rs @@ -23,11 +23,14 @@ const FILES: [&str; 3] = [ "benches/geotopo.pdf" ]; +const BLACK: i32 = 0; +const WHITE: i32 = 1000; + fn render_full(c: &mut Criterion) { for file in FILES { c.bench_with_input(BenchmarkId::new("render_full", file), &file, |b, &file| { b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter(|| render_doc(file, None)) + .iter(|| render_doc(file, None, BLACK, WHITE)) }); } } @@ -39,7 +42,7 @@ fn render_to_first_page(c: &mut Criterion) { &file, |b, &file| { b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter(|| render_first_page(file)) + .iter(|| render_first_page(file, BLACK, WHITE)) } ); } @@ -48,7 +51,7 @@ fn render_to_first_page(c: &mut Criterion) { fn only_converting(c: &mut Criterion) { for file in FILES { let runtime = tokio::runtime::Runtime::new().unwrap(); - let all_rendered = runtime.block_on(render_all_files(file)); + let all_rendered = runtime.block_on(render_all_files(file, BLACK, WHITE)); c.bench_with_input( BenchmarkId::new("only_converting", file), @@ -68,7 +71,7 @@ fn search_short_common(c: &mut Criterion) { &file, |b, &file| { b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter(|| render_doc(file, Some("an"))) + .iter(|| render_doc(file, Some("an"), BLACK, WHITE)) } ); } @@ -81,20 +84,20 @@ fn search_long_rare(c: &mut Criterion) { &file, |b, &file| { b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter(|| render_doc(file, Some("this is long and rare"))) + .iter(|| render_doc(file, Some("this is long and rare"), BLACK, WHITE)) } ); } } -pub async fn render_first_page(path: impl AsRef) { +pub async fn render_first_page(path: impl AsRef, black: i32, white: i32) { let RenderState { mut from_render_rx, mut from_converter_rx, mut pages, mut to_converter_tx, to_render_tx - } = start_all_rendering(path); + } = start_all_rendering(path, black, white); // we only want to render until the first page is ready to be printed while pages.iter().all(Option::is_none) { @@ -114,8 +117,8 @@ pub async fn render_first_page(path: impl AsRef) { drop(to_render_tx); } -async fn render_all_files(path: &'static str) -> Vec { - let (mut from_render_rx, to_render_tx) = start_rendering_loop(path); +async fn render_all_files(path: &'static str, black: i32, white: i32) -> Vec { + let (mut from_render_rx, to_render_tx) = start_rendering_loop(path, black, white); let mut pages = Vec::>::new(); while let Some(info) = from_render_rx.next().await { diff --git a/benches/utils.rs b/benches/utils.rs index 86230ed..28d847e 100644 --- a/benches/utils.rs +++ b/benches/utils.rs @@ -57,7 +57,9 @@ pub struct RenderState { const FONT_SIZE: (u16, u16) = (8, 14); pub fn start_rendering_loop( - path: impl AsRef + path: impl AsRef, + black: i32, + white: i32 ) -> ( RecvStream<'static, Result>, Sender @@ -91,7 +93,9 @@ pub fn start_rendering_loop( to_main_tx, from_main_rx, size, - tdf::PrerenderLimit::All + tdf::PrerenderLimit::All, + black, + white ) }); @@ -122,8 +126,8 @@ pub fn start_converting_loop( (from_converter_rx, to_converter_tx) } -pub fn start_all_rendering(path: impl AsRef) -> RenderState { - let (from_render_rx, to_render_tx) = start_rendering_loop(path); +pub fn start_all_rendering(path: impl AsRef, black: i32, white: i32) -> RenderState { + let (from_render_rx, to_render_tx) = start_rendering_loop(path, black, white); let (from_converter_rx, to_converter_tx) = start_converting_loop(20); let pages: Vec> = Vec::new(); @@ -137,14 +141,14 @@ pub fn start_all_rendering(path: impl AsRef) -> RenderState { } } -pub async fn render_doc(path: impl AsRef, search_term: Option<&str>) { +pub async fn render_doc(path: impl AsRef, search_term: Option<&str>, black: i32, white: i32) { let RenderState { mut from_render_rx, mut from_converter_rx, mut pages, mut to_converter_tx, to_render_tx - } = start_all_rendering(path); + } = start_all_rendering(path, black, white); if let Some(term) = search_term { to_render_tx diff --git a/src/main.rs b/src/main.rs index c692ba4..f6497ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,11 +51,26 @@ async fn main() -> Result<(), Box> { /// The number of pages to prerender surrounding the currently-shown page; 0 means no /// limit. By default, there is no limit. optional -p,--prerender prerender: usize + /// Custom white color, specified in css format (e.g. "FFFFFF" or "rgb(255, 255, 255)") + optional -w,--white-color white: String + /// Custom black color, specified in css format (e.g "000000" or "rgb(0, 0, 0)") + optional -b,--black-color black: String /// PDF file to read required file: PathBuf }; let path = flags.file.canonicalize()?; + let black = parse_color_to_i32(&flags.black_color.unwrap_or("000000".into())).map_err(|e| { + BadTermSizeStdin(format!( + "Couldn't parse black color: {e} - is it formatted like a CSS color?" + )) + })?; + + let white = parse_color_to_i32(&flags.white_color.unwrap_or("FFFFFF".into())).map_err(|e| { + BadTermSizeStdin(format!( + "Couldn't parse while color: {e} - is it formatted like a CSS color?" + )) + })?; let (watch_to_render_tx, render_rx) = flume::unbounded(); let tui_tx = watch_to_render_tx.clone(); @@ -141,7 +156,15 @@ async fn main() -> Result<(), Box> { .and_then(NonZeroUsize::new) .map_or(PrerenderLimit::All, PrerenderLimit::Limited); std::thread::spawn(move || { - renderer::start_rendering(&file_path, render_tx, render_rx, window_size, prerender) + renderer::start_rendering( + &file_path, + render_tx, + render_rx, + window_size, + prerender, + black, + white + ) }); let mut ev_stream = crossterm::event::EventStream::new(); @@ -286,3 +309,8 @@ fn on_notify_ev( } } } +fn parse_color_to_i32(cs: &str) -> Result { + let color = csscolorparser::parse(cs)?; + let [r, g, b, _] = color.to_rgba8(); + Ok(i32::from_be_bytes([0, r, g, b])) +} diff --git a/src/renderer.rs b/src/renderer.rs index 169c4fa..16bccaf 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -76,7 +76,9 @@ pub fn start_rendering( sender: Sender>, receiver: Receiver, size: WindowSize, - prerender: PrerenderLimit + prerender: PrerenderLimit, + black: i32, + white: i32 ) -> Result<(), SendError>> { // We want this outside of 'reload so that if the doc reloads, the search term that somebody // set will still get highlighted in the reloaded doc @@ -282,6 +284,8 @@ pub fn start_rendering( search_term.as_deref(), rendered, invert, + black, + white, (area_w, area_h) ) { // If that fn returned Some, that means it needed to be re-rendered for some @@ -421,6 +425,8 @@ fn render_single_page_to_ctx( search_term: Option<&str>, prev_render: &PrevRender, invert: bool, + black: i32, + white: i32, (area_w, area_h): (f32, f32) ) -> Result { let result_rects = match prev_render.num_search_found { @@ -461,7 +467,9 @@ fn render_single_page_to_ctx( let mut pixmap = page.to_pixmap(&matrix, &colorspace, false, false)?; if invert { - pixmap.invert()?; + pixmap.tint(white, black)?; + } else { + pixmap.tint(black, white)?; } let (x_res, y_res) = pixmap.resolution();