Compare commits

..

11 Commits

Author SHA1 Message Date
itsjunetime 134ba601fa Final adjustments to conform to mupdf changes 2025-02-19 09:39:51 -07:00
itsjunetime 3a264a0ddb perftools in ci 2025-02-19 09:27:12 -07:00
itsjunetime 85857890ed fontconfig in CI 2025-02-19 09:19:28 -07:00
itsjunetime c1c410ebe6 Remove unnecessary CI steps? 2025-02-19 09:15:20 -07:00
itsjunetime 34047ca106 Fix searching hehe 2025-02-19 09:13:54 -07:00
itsjunetime 3452294f59 Update deps 2025-02-16 17:13:01 -07:00
itsjunetime d22aa4596d Switch to git dependency for my fixes 2025-02-14 12:31:15 -07:00
itsjunetime 6e5bb0bdc5 Make features more modular and call search more easily 2025-02-09 15:20:51 -07:00
itsjunetime 7b68fe6b33 Remove some more dead code 2025-02-07 13:03:06 -07:00
itsjunetime a44dba20a7 Change back to no resizing and don't include alpha channel in conversion 2025-02-07 10:24:08 -07:00
itsjunetime d6102de3c6 Initial implementation of attempted mupdf rewrite 2025-02-06 11:14:47 -07:00
9 changed files with 205 additions and 361 deletions
+1 -4
View File
@@ -1,11 +1,8 @@
# v0.3.0 # Unreleased
- Update ratatui(-image) dependencies - Update ratatui(-image) dependencies
- Enable Ctrl+Z/Suspend functionality - Enable Ctrl+Z/Suspend functionality
- Rewrite with mupdf as the backend for much better performance and rendering quality - Rewrite with mupdf as the backend for much better performance and rendering quality
- Support easy inversion of colors via `i` keypress
- Support for filling all available space with `f` keypress
- Change help text at bottom into full help page
# v0.2.0 # v0.2.0
Generated
+41 -71
View File
@@ -52,9 +52,9 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.96" version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
@@ -149,9 +149,9 @@ dependencies = [
[[package]] [[package]]
name = "avif-serialize" name = "avif-serialize"
version = "0.8.3" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
] ]
@@ -349,9 +349,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.16" version = "1.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@@ -429,18 +429,18 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.31" version = "4.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.31" version = "4.5.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@@ -834,9 +834,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.14.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
@@ -889,15 +889,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]] [[package]]
name = "filedescriptor" name = "filedescriptor"
version = "0.8.3" version = "0.8.3"
@@ -935,9 +926,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.0" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@@ -1353,7 +1344,6 @@ dependencies = [
"bytemuck", "bytemuck",
"byteorder-lite", "byteorder-lite",
"num-traits", "num-traits",
"png",
"ravif", "ravif",
"rayon", "rayon",
"zune-core", "zune-core",
@@ -1542,9 +1532,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
@@ -1614,9 +1604,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]] [[package]]
name = "loop9" name = "loop9"
@@ -1724,12 +1714,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
dependencies = [ dependencies = [
"adler2", "adler2",
"simd-adler32",
] ]
[[package]] [[package]]
@@ -1747,26 +1736,26 @@ dependencies = [
[[package]] [[package]]
name = "mupdf" name = "mupdf"
version = "0.4.4" version = "0.4.4"
source = "git+https://github.com/itsjunetime/mupdf-rs?branch=june%2Fmupdf_1_25#9c4f2379d205a78f967bb230f0e72ec18fad23f7" source = "git+https://github.com/itsjunetime/mupdf-rs?branch=remove_debug_print#10f1b1629540e7d62354842198317bcf5e7d619c"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"font-kit", "font-kit",
"mupdf-sys", "mupdf-sys",
"num_enum", "num_enum",
"once_cell", "once_cell",
"zerocopy 0.8.21", "zerocopy 0.8.19",
] ]
[[package]] [[package]]
name = "mupdf-sys" name = "mupdf-sys"
version = "0.4.4" version = "0.4.4"
source = "git+https://github.com/itsjunetime/mupdf-rs?branch=june%2Fmupdf_1_25#9c4f2379d205a78f967bb230f0e72ec18fad23f7" source = "git+https://github.com/itsjunetime/mupdf-rs?branch=remove_debug_print#10f1b1629540e7d62354842198317bcf5e7d619c"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cc", "cc",
"pkg-config", "pkg-config",
"regex", "regex",
"zerocopy 0.8.21", "zerocopy 0.8.19",
] ]
[[package]] [[package]]
@@ -2192,19 +2181,6 @@ dependencies = [
"plotters-backend", "plotters-backend",
] ]
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -2492,9 +2468,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.9" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
] ]
@@ -2647,18 +2623,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.218" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.218" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2667,9 +2643,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.139" version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -2742,12 +2718,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]] [[package]]
name = "simd_helpers" name = "simd_helpers"
version = "0.1.0" version = "0.1.0"
@@ -3332,9 +3302,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.15.1" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
dependencies = [ dependencies = [
"atomic", "atomic",
"getrandom 0.3.1", "getrandom 0.3.1",
@@ -3801,9 +3771,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.3" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -3864,11 +3834,11 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.21" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" checksum = "8207f485579465f62ae51a983e42c906736a17efd2de48b021e64f1bbd8e98c7"
dependencies = [ dependencies = [
"zerocopy-derive 0.8.21", "zerocopy-derive 0.8.19",
] ]
[[package]] [[package]]
@@ -3884,9 +3854,9 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.21" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" checksum = "7dbe1304a711c6eb4cf1ed333aa0d9b344685e71f6f00c3b176072213bd3783e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
+3 -4
View File
@@ -2,7 +2,7 @@
name = "tdf-viewer" name = "tdf-viewer"
version = "0.2.0" version = "0.2.0"
authors = ["June Welker <junewelker@gmail.com>"] authors = ["June Welker <junewelker@gmail.com>"]
edition = "2024" edition = "2021"
description = "A terminal viewer for PDFs" description = "A terminal viewer for PDFs"
readme = "README.md" readme = "README.md"
homepage = "https://github.com/itsjunetime/tdf" homepage = "https://github.com/itsjunetime/tdf"
@@ -11,7 +11,6 @@ license = "AGPL-3.0-only"
keywords = ["pdf", "tui", "cli", "terminal"] keywords = ["pdf", "tui", "cli", "terminal"]
categories = ["command-line-utilities", "text-processing", "visualization"] categories = ["command-line-utilities", "text-processing", "visualization"]
default-run = "tdf" default-run = "tdf"
rust-version = "1.85"
[[bin]] [[bin]]
name = "tdf" name = "tdf"
@@ -29,7 +28,7 @@ ratatui = { git = "https://github.com/itsjunetime/ratatui.git" }
ratatui-image = { git = "https://github.com/itsjunetime/ratatui-image.git", branch = "vb64_on_personal", default-features = false } ratatui-image = { git = "https://github.com/itsjunetime/ratatui-image.git", branch = "vb64_on_personal", default-features = false }
# ratatui-image = { path = "./ratatui-image", features = ["vb64"], default-features = false } # ratatui-image = { path = "./ratatui-image", features = ["vb64"], default-features = false }
crossterm = { version = "0.28.1", features = ["event-stream"] } crossterm = { version = "0.28.1", features = ["event-stream"] }
image = { version = "0.25.1", features = ["pnm", "rayon", "png"], default-features = false } image = { version = "0.25.1", features = ["pnm", "rayon"], default-features = false }
notify = { version = "8.0.0", features = ["crossbeam-channel"] } notify = { version = "8.0.0", features = ["crossbeam-channel"] }
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
futures-util = { version = "0.3.30", default-features = false } futures-util = { version = "0.3.30", default-features = false }
@@ -38,7 +37,7 @@ flume = { version = "0.11.0", default-features = false, features = ["async"] }
xflags = "0.4.0-pre.2" xflags = "0.4.0-pre.2"
mimalloc = "0.1.43" mimalloc = "0.1.43"
nix = { version = "0.29.0", features = ["signal"] } nix = { version = "0.29.0", features = ["signal"] }
mupdf = { git = "https://github.com/itsjunetime/mupdf-rs", branch = "june/mupdf_1_25", 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 } rayon = { version = "*", default-features = false }
# for tracing with tokio-console # for tracing with tokio-console
+4 -4
View File
@@ -6,15 +6,15 @@ use std::{
time::{SystemTime, UNIX_EPOCH} time::{SystemTime, UNIX_EPOCH}
}; };
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main, profiler::Profiler}; use criterion::{criterion_group, criterion_main, profiler::Profiler, BenchmarkId, Criterion};
use futures_util::StreamExt; use futures_util::StreamExt;
use tdf::{ use tdf::{
converter::{ConvertedPage, ConverterMsg}, converter::{ConvertedPage, ConverterMsg},
renderer::{PageInfo, RenderInfo, fill_default} renderer::{fill_default, PageInfo, RenderInfo}
}; };
use utils::{ use utils::{
RenderState, handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering, handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering,
start_converting_loop, start_rendering_loop start_converting_loop, start_rendering_loop, RenderState
}; };
const FILES: [&str; 3] = [ const FILES: [&str; 3] = [
+7 -7
View File
@@ -1,13 +1,13 @@
use std::{hint::black_box, path::Path}; use std::{hint::black_box, path::Path};
use crossterm::terminal::WindowSize; use crossterm::terminal::WindowSize;
use flume::{Sender, r#async::RecvStream, unbounded}; use flume::{r#async::RecvStream, unbounded, Sender};
use futures_util::stream::StreamExt as _; use futures_util::stream::StreamExt as _;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui_image::picker::{Picker, ProtocolType}; use ratatui_image::picker::{Picker, ProtocolType};
use tdf::{ use tdf::{
converter::{ConvertedPage, ConverterMsg, run_conversion_loop}, converter::{run_conversion_loop, ConvertedPage, ConverterMsg},
renderer::{RenderError, RenderInfo, RenderNotif, fill_default, start_rendering} renderer::{fill_default, start_rendering, RenderError, RenderInfo, RenderNotif}
}; };
pub fn handle_renderer_msg( pub fn handle_renderer_msg(
@@ -37,13 +37,13 @@ pub fn handle_converter_msg(
pages[num] = Some(page); pages[num] = Some(page);
let first_none = pages.iter().position(Option::is_none); let num_got = pages.iter().filter(|p| p.is_some()).count();
// we have to tell it to jump to a certain page so that it will actually render it (since // we have to tell it to jump to a certain page so that it will actually render it (since
// it only renders fanning out from the page that we currently have selected) // it only renders fanning out from the page that we currently have selected)
if let Some(first) = first_none { to_converter_tx
to_converter_tx.send(ConverterMsg::GoToPage(first)).unwrap(); .send(ConverterMsg::GoToPage(num_got))
} .unwrap();
} }
pub struct RenderState { pub struct RenderState {
+3 -3
View File
@@ -2,10 +2,10 @@ use flume::{Receiver, SendError, Sender, TryRecvError};
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use image::DynamicImage; use image::DynamicImage;
use itertools::Itertools; use itertools::Itertools;
use ratatui_image::{Resize, picker::Picker, protocol::Protocol}; use ratatui_image::{picker::Picker, protocol::Protocol, Resize};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use crate::renderer::{PageInfo, RenderError, fill_default}; use crate::renderer::{fill_default, PageInfo, RenderError};
pub struct ConvertedPage { pub struct ConvertedPage {
pub page: Protocol, pub page: Protocol,
@@ -49,7 +49,7 @@ pub async fn run_conversion_loop(
let Some((page_info, new_iter)) = (idx_start..page) let Some((page_info, new_iter)) = (idx_start..page)
.interleave(page..idx_end) .interleave(page..idx_end)
.enumerate() .enumerate()
// .skip(*iteration) .skip(*iteration)
.find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx))) .find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx)))
else { else {
return Ok(None); return Ok(None);
+10 -15
View File
@@ -1,6 +1,6 @@
use std::{ use std::{
ffi::OsString, ffi::OsString,
io::{Read, Write, stdout}, io::{stdout, Read, Write},
num::NonZeroUsize, num::NonZeroUsize,
path::PathBuf path::PathBuf
}; };
@@ -8,16 +8,16 @@ use std::{
use crossterm::{ use crossterm::{
execute, execute,
terminal::{ terminal::{
EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, disable_raw_mode, enable_raw_mode, window_size, EndSynchronizedUpdate,
enable_raw_mode, window_size EnterAlternateScreen, LeaveAlternateScreen
} }
}; };
use futures_util::{FutureExt, stream::StreamExt}; use futures_util::{stream::StreamExt, FutureExt};
use notify::{Event, EventKind, RecursiveMode, Watcher}; use notify::{Event, EventKind, RecursiveMode, Watcher};
use ratatui::{Terminal, backend::CrosstermBackend}; use ratatui::{backend::CrosstermBackend, Terminal};
use ratatui_image::picker::Picker; use ratatui_image::picker::Picker;
use tdf::{ use tdf::{
converter::{ConvertedPage, ConverterMsg, run_conversion_loop}, converter::{run_conversion_loop, ConvertedPage, ConverterMsg},
renderer::{self, RenderError, RenderInfo, RenderNotif}, renderer::{self, RenderError, RenderInfo, RenderNotif},
tui::{BottomMessage, InputAction, MessageSetting, Tui} tui::{BottomMessage, InputAction, MessageSetting, Tui}
}; };
@@ -45,8 +45,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
optional -r,--r-to-l r_to_l: bool optional -r,--r-to-l r_to_l: bool
/// The maximum number of pages to display together, horizontally, at a time /// The maximum number of pages to display together, horizontally, at a time
optional -m,--max-wide max_wide: NonZeroUsize optional -m,--max-wide max_wide: NonZeroUsize
/// Fullscreen the pdf (hide document name, page count, etc)
optional -f,--fullscreen fullscreen: bool
/// PDF file to read /// PDF file to read
required file: PathBuf required file: PathBuf
}; };
@@ -169,9 +167,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
)?; )?;
enable_raw_mode()?; enable_raw_mode()?;
let mut fullscreen = flags.fullscreen.unwrap_or_default(); let mut main_area = Tui::main_layout(&term.get_frame());
let mut main_area = Tui::main_layout(&term.get_frame(), fullscreen); tui_tx.send(RenderNotif::Area(main_area[1]))?;
tui_tx.send(RenderNotif::Area(main_area.page_area))?;
let mut tui_rx = tui_rx.into_stream(); let mut tui_rx = tui_rx.into_stream();
let mut from_converter = from_converter.into_stream(); let mut from_converter = from_converter.into_stream();
@@ -194,8 +191,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
to_converter.send(ConverterMsg::GoToPage(page))?; to_converter.send(ConverterMsg::GoToPage(page))?;
}, },
InputAction::Search(term) => tui_tx.send(RenderNotif::Search(term))?, InputAction::Search(term) => tui_tx.send(RenderNotif::Search(term))?,
InputAction::Invert => tui_tx.send(RenderNotif::Invert)?,
InputAction::Fullscreen => fullscreen = !fullscreen,
} }
} }
}, },
@@ -223,10 +218,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}, },
}; };
let new_area = Tui::main_layout(&term.get_frame(), fullscreen); let new_area = Tui::main_layout(&term.get_frame());
if new_area != main_area { if new_area != main_area {
main_area = new_area; main_area = new_area;
tui_tx.send(RenderNotif::Area(main_area.page_area))?; tui_tx.send(RenderNotif::Area(main_area[1]))?;
needs_redraw = true; needs_redraw = true;
} }
+26 -38
View File
@@ -10,8 +10,7 @@ pub enum RenderNotif {
Area(Rect), Area(Rect),
JumpToPage(usize), JumpToPage(usize),
Search(String), Search(String),
Reload, Reload
Invert
} }
#[derive(Debug)] #[derive(Debug)]
@@ -46,10 +45,12 @@ struct PrevRender {
contained_term: Option<bool> contained_term: Option<bool>
} }
#[inline]
pub fn fill_default<T: Default>(vec: &mut Vec<T>, size: usize) { pub fn fill_default<T: Default>(vec: &mut Vec<T>, size: usize) {
vec.clear(); vec.clear();
vec.resize_with(size, T::default); 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 mupdf::Document needs to be held during // this function has to be sync (non-async) because the mupdf::Document needs to be held during
@@ -71,6 +72,14 @@ pub fn start_rendering(
receiver: Receiver<RenderNotif>, receiver: Receiver<RenderNotif>,
size: WindowSize size: WindowSize
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> { ) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
// first, wait 'til we get told what the current starting area is so that we can set it to
// know what to render to
let mut area = loop {
if let RenderNotif::Area(r) = receiver.recv().unwrap() {
break r;
}
};
// We want this outside of 'reload so that if the doc reloads, the search term that somebody // 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 // set will still get highlighted in the reloaded doc
let mut search_term = None; let mut search_term = None;
@@ -81,8 +90,6 @@ pub fn start_rendering(
let col_h = size.height / size.rows; let col_h = size.height / size.rows;
let mut stored_doc = None; let mut stored_doc = None;
let mut invert = false;
let mut preserved_area = None;
'reload: loop { 'reload: loop {
let doc = match Document::open(path) { let doc = match Document::open(path) {
@@ -132,7 +139,7 @@ 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::new(); let mut rendered = vec![];
fill_default::<PrevRender>(&mut rendered, n_pages); fill_default::<PrevRender>(&mut rendered, n_pages);
let mut start_point = 0; let mut start_point = 0;
@@ -141,39 +148,25 @@ pub fn start_rendering(
// document. If there was a mechanism to say 'start this for-loop over' then I would do // document. If there was a mechanism to say 'start this for-loop over' then I would do
// that, but I don't think such a thing exists, so this is our attempt // that, but I don't think such a thing exists, so this is our attempt
'render_pages: loop { 'render_pages: loop {
// next, we gotta wait 'til we get told what the current starting area is so that we can
// set it to know what to render to
let area = match preserved_area {
Some(a) => a,
None => {
let new_area = loop {
if let RenderNotif::Area(r) = receiver.recv().unwrap() {
break r;
}
};
preserved_area = Some(new_area);
new_area
}
};
// what we do with a notif is the same regardless of if we're in the middle of // what we do with a notif is the same regardless of if we're in the middle of
// rendering the list of pages or we're all done // rendering the list of pages or we're all done
macro_rules! handle_notif { macro_rules! handle_notif {
($notif:ident) => { ($notif:ident) => {
match $notif { match $notif {
RenderNotif::Reload => continue 'reload, RenderNotif::Reload => continue 'reload,
RenderNotif::Invert => {
invert = !invert;
for page in &mut rendered {
page.successful = false;
}
continue 'render_pages;
}
RenderNotif::Area(new_area) => { RenderNotif::Area(new_area) => {
preserved_area = Some(new_area); let bigger =
new_area.width > area.width || new_area.height > area.height;
area = new_area;
// we only want to re-render pages if the new area is greater than the old
// one, 'cause then we might need sharper images to make it all look good.
// If the new area is smaller, then the same high-quality-rendered images
// will still look fine, so it's ok to leave it.
if bigger {
fill_default(&mut rendered, n_pages); fill_default(&mut rendered, n_pages);
continue 'render_pages; continue 'render_pages;
} }
}
RenderNotif::JumpToPage(page) => { RenderNotif::JumpToPage(page) => {
start_point = page; start_point = page;
continue 'render_pages; continue 'render_pages;
@@ -259,7 +252,6 @@ pub fn start_rendering(
&page, &page,
search_term.as_deref(), search_term.as_deref(),
rendered_with_no_results, rendered_with_no_results,
invert,
(area_w, area_h) (area_w, area_h)
) { ) {
// If we've already rendered it just fine and we don't need to render it again, // If we've already rendered it just fine and we don't need to render it again,
@@ -274,9 +266,9 @@ pub fn start_rendering(
rendered.contained_term = Some(ctx.result_rects.is_empty()); rendered.contained_term = Some(ctx.result_rects.is_empty());
rendered.successful = true; rendered.successful = true;
let w = ctx.pixmap.width(); let cap = (ctx.pixmap.width()
let h = ctx.pixmap.height(); * ctx.pixmap.height() * u32::from(ctx.pixmap.n()))
let cap = (w * h * u32::from(ctx.pixmap.n())) as usize + 16; as usize;
let mut pixels = Vec::with_capacity(cap); let mut pixels = Vec::with_capacity(cap);
if let Err(e) = ctx.pixmap.write_to(&mut pixels, mupdf::ImageFormat::PNM) { if let Err(e) = ctx.pixmap.write_to(&mut pixels, mupdf::ImageFormat::PNM) {
sender.send(Err(RenderError::Doc(e)))?; sender.send(Err(RenderError::Doc(e)))?;
@@ -327,7 +319,6 @@ fn render_single_page_to_ctx(
page: &Page, page: &Page,
search_term: Option<&str>, search_term: Option<&str>,
already_rendered_no_results: bool, already_rendered_no_results: bool,
invert: bool,
(area_w, area_h): (f32, f32) (area_w, area_h): (f32, f32)
) -> Result<Option<RenderedContext>, mupdf::error::Error> { ) -> Result<Option<RenderedContext>, mupdf::error::Error> {
let mut max_hits = 10; let mut max_hits = 10;
@@ -387,9 +378,6 @@ fn render_single_page_to_ctx(
let matrix = Matrix::new_scale(scale_factor, scale_factor); let matrix = Matrix::new_scale(scale_factor, scale_factor);
let mut pixmap = page.to_pixmap(&matrix, &colorspace, 0.0, false)?; let mut pixmap = page.to_pixmap(&matrix, &colorspace, 0.0, false)?;
if invert {
pixmap.invert()?;
}
let (x_res, y_res) = pixmap.resolution(); let (x_res, y_res) = pixmap.resolution();
let new_x = (x_res as f32 * scale_factor) as i32; let new_x = (x_res as f32 * scale_factor) as i32;
+29 -134
View File
@@ -1,26 +1,25 @@
use std::{borrow::Cow, io::stdout, num::NonZeroUsize}; use std::{borrow::Cow, io::stdout, num::NonZeroUsize, rc::Rc};
use crossterm::{ use crossterm::{
event::{Event, KeyCode, KeyModifiers, MouseEventKind}, event::{Event, KeyCode, KeyModifiers, MouseEventKind},
execute, execute,
terminal::{ terminal::{
BeginSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, disable_raw_mode, enable_raw_mode, BeginSynchronizedUpdate, EnterAlternateScreen,
enable_raw_mode LeaveAlternateScreen
} }
}; };
use nix::{ use nix::{
sys::signal::{Signal::SIGSTOP, kill}, sys::signal::{kill, Signal::SIGSTOP},
unistd::Pid unistd::Pid
}; };
use ratatui::{ use ratatui::{
Frame,
layout::{Constraint, Flex, Layout, Rect}, layout::{Constraint, Flex, Layout, Rect},
style::{Color, Style}, style::{Color, Style},
symbols::border, text::Span,
text::{Span, Text}, widgets::{Block, Borders, Padding},
widgets::{Block, Borders, Clear, Padding} Frame
}; };
use ratatui_image::{Image, protocol::Protocol}; use ratatui_image::{protocol::Protocol, Image};
use crate::{renderer::RenderError, skip::Skip}; use crate::{renderer::RenderError, skip::Skip};
@@ -33,8 +32,7 @@ pub struct Tui {
// jumping to a specific page // jumping to a specific page
prev_msg: Option<BottomMessage>, prev_msg: Option<BottomMessage>,
rendered: Vec<RenderedInfo>, rendered: Vec<RenderedInfo>,
page_constraints: PageConstraints, page_constraints: PageConstraints
showing_help_msg: bool
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@@ -79,12 +77,6 @@ struct RenderedInfo {
num_results: Option<usize> num_results: Option<usize>
} }
#[derive(PartialEq)]
pub struct RenderLayout {
pub page_area: Rect,
pub top_and_bottom: Option<(Rect, Rect)>
}
impl Tui { impl Tui {
pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool) -> Tui { pub fn new(name: String, max_wide: Option<NonZeroUsize>, r_to_l: bool) -> Tui {
Self { Self {
@@ -94,19 +86,12 @@ impl Tui {
bottom_msg: BottomMessage::Help, bottom_msg: BottomMessage::Help,
last_render: LastRender::default(), last_render: LastRender::default(),
rendered: vec![], rendered: vec![],
page_constraints: PageConstraints { max_wide, r_to_l }, page_constraints: PageConstraints { max_wide, r_to_l }
showing_help_msg: false
} }
} }
pub fn main_layout(frame: &Frame<'_>, fullscreened: bool) -> RenderLayout { pub fn main_layout(frame: &Frame<'_>) -> Rc<[Rect]> {
if fullscreened { Layout::default()
RenderLayout {
page_area: frame.area(),
top_and_bottom: None
}
} else {
let layout = Layout::default()
.constraints([ .constraints([
Constraint::Length(3), Constraint::Length(3),
Constraint::Fill(1), Constraint::Fill(1),
@@ -114,23 +99,11 @@ impl Tui {
]) ])
.horizontal_margin(2) .horizontal_margin(2)
.vertical_margin(1) .vertical_margin(1)
.split(frame.area()); .split(frame.area())
RenderLayout {
page_area: layout[1],
top_and_bottom: Some((layout[0], layout[2]))
}
}
} }
// TODO: Make a way to fill the width of the screen with one page and scroll down to view it // TODO: Make a way to fill the width of the screen with one page and scroll down to view it
pub fn render(&mut self, frame: &mut Frame<'_>, full_layout: &RenderLayout) { pub fn render(&mut self, frame: &mut Frame<'_>, main_area: &[Rect]) {
if self.showing_help_msg {
self.render_help_msg(frame);
return;
}
if let Some((top_area, bottom_area)) = full_layout.top_and_bottom {
let top_block = Block::new() let top_block = Block::new()
.padding(Padding { .padding(Padding {
right: 2, right: 2,
@@ -139,7 +112,7 @@ impl Tui {
}) })
.borders(Borders::BOTTOM); .borders(Borders::BOTTOM);
let top_area = top_block.inner(top_area); let top_area = top_block.inner(main_area[0]);
let page_nums_text = format!("{} / {}", self.page + 1, self.rendered.len()); let page_nums_text = format!("{} / {}", self.page + 1, self.rendered.len());
@@ -153,7 +126,7 @@ impl Tui {
let page_nums = Span::styled(&page_nums_text, Style::new().fg(Color::Cyan)); let page_nums = Span::styled(&page_nums_text, Style::new().fg(Color::Cyan));
frame.render_widget(top_block, top_area); frame.render_widget(top_block, main_area[0]);
frame.render_widget(title, top_layout[0]); frame.render_widget(title, top_layout[0]);
frame.render_widget(page_nums, top_layout[1]); frame.render_widget(page_nums, top_layout[1]);
@@ -165,9 +138,9 @@ impl Tui {
bottom: 0 bottom: 0
}) })
.borders(Borders::TOP); .borders(Borders::TOP);
let bottom_inside_block = bottom_block.inner(bottom_area); let bottom_area = bottom_block.inner(main_area[2]);
frame.render_widget(bottom_block, bottom_area); frame.render_widget(bottom_block, main_area[2]);
let rendered_str = if !self.rendered.is_empty() { let rendered_str = if !self.rendered.is_empty() {
format!( format!(
@@ -182,13 +155,16 @@ impl Tui {
Constraint::Fill(1), Constraint::Fill(1),
Constraint::Length(rendered_str.len() as u16) Constraint::Length(rendered_str.len() as u16)
]) ])
.split(bottom_inside_block); .split(bottom_area);
let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan)); let rendered_span = Span::styled(&rendered_str, Style::new().fg(Color::Cyan));
frame.render_widget(rendered_span, bottom_layout[1]); frame.render_widget(rendered_span, bottom_layout[1]);
let (msg_str, color): (Cow<'_, str>, _) = match self.bottom_msg { let (msg_str, color): (Cow<'_, str>, _) = match self.bottom_msg {
BottomMessage::Help => ("?: Show help page".into(), Color::Blue), BottomMessage::Help => (
"/: Search, g: Go To Page, n: Next Search Result, N: Previous Search Result".into(),
Color::Blue
),
BottomMessage::Error(ref e) => (e.as_str().into(), Color::Red), BottomMessage::Error(ref e) => (e.as_str().into(), Color::Red),
BottomMessage::Input(ref input_state) => ( BottomMessage::Input(ref input_state) => (
match input_state { match input_state {
@@ -223,9 +199,8 @@ impl Tui {
let span = Span::styled(msg_str, Style::new().fg(color)); let span = Span::styled(msg_str, Style::new().fg(color));
frame.render_widget(span, bottom_layout[0]); frame.render_widget(span, bottom_layout[0]);
}
let mut img_area = full_layout.page_area; let mut img_area = main_area[1];
let size = frame.area(); let size = frame.area();
if size == self.last_render.rect { if size == self.last_render.rect {
@@ -439,12 +414,6 @@ impl Tui {
))); )));
Some(InputAction::Redraw) Some(InputAction::Redraw)
} }
'i' => Some(InputAction::Invert),
'?' => {
self.showing_help_msg = true;
Some(InputAction::Redraw)
}
'f' => Some(InputAction::Fullscreen),
'n' if self.page < self.rendered.len() - 1 => { 'n' if self.page < self.rendered.len() - 1 => {
// TODO: If we can't find one, then maybe like block until we've verified // TODO: If we can't find one, then maybe like block until we've verified
// all the pages have been checked? // all the pages have been checked?
@@ -516,8 +485,8 @@ impl Tui {
KeyCode::Down => self.change_page(PageChange::Next, ChangeAmount::WholeScreen), KeyCode::Down => self.change_page(PageChange::Next, ChangeAmount::WholeScreen),
KeyCode::Left => self.change_page(PageChange::Prev, ChangeAmount::Single), KeyCode::Left => self.change_page(PageChange::Prev, ChangeAmount::Single),
KeyCode::Up => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen), KeyCode::Up => self.change_page(PageChange::Prev, ChangeAmount::WholeScreen),
KeyCode::Esc => match (self.showing_help_msg, &self.bottom_msg) { KeyCode::Esc => match self.bottom_msg {
(false, BottomMessage::Help) => Some(InputAction::QuitApp), BottomMessage::Help => Some(InputAction::QuitApp),
_ => { _ => {
// When we hit escape, we just want to pop off the current message and // When we hit escape, we just want to pop off the current message and
// show the underlying one. // show the underlying one.
@@ -546,9 +515,7 @@ impl Tui {
Some(InputAction::JumpingToPage(zero_page)) Some(InputAction::JumpingToPage(zero_page))
} else { } else {
self.set_msg(MessageSetting::Some(BottomMessage::Error( self.set_msg(MessageSetting::Some(BottomMessage::Error(
format!( format!("Cannot jump to page {page}; there are only {rendered_len} pages in the document")
"Cannot jump to page {page}; there are only {rendered_len} pages in the document"
)
))); )));
Some(InputAction::Redraw) Some(InputAction::Redraw)
} }
@@ -628,88 +595,16 @@ impl Tui {
self.prev_msg = None; self.prev_msg = None;
self.bottom_msg = BottomMessage::default(); self.bottom_msg = BottomMessage::default();
} }
MessageSetting::Pop => MessageSetting::Pop => self.bottom_msg = self.prev_msg.take().unwrap_or_default()
if self.showing_help_msg {
self.last_render.rect = Rect::default();
self.showing_help_msg = false;
} else {
self.bottom_msg = self.prev_msg.take().unwrap_or_default();
},
} }
} }
pub fn render_help_msg(&self, frame: &mut Frame<'_>) {
let frame_area = frame.area();
frame.render_widget(Clear, frame_area);
let block = Block::new()
.title("Help")
.padding(Padding::proportional(1))
.borders(Borders::ALL)
.border_set(border::ROUNDED)
.border_style(Color::Blue);
let help_span = Text::raw(HELP_PAGE);
let max_w: u16 = HELP_PAGE
.lines()
.map(str::len)
.max()
.unwrap_or_default()
.try_into()
.expect("Every help text line must be shorter than u16::MAX");
let layout = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(max_w + 6),
Constraint::Fill(1)
])
.split(frame_area);
let block_area = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(u16::try_from(HELP_PAGE.lines().count()).unwrap() + 4),
Constraint::Fill(1)
])
.split(layout[1]);
let block_inner = block.inner(block_area[1]);
frame.render_widget(block, block_area[1]);
frame.render_widget(help_span, block_inner);
}
} }
static HELP_PAGE: &str = "\
l, h, left, right:
Go forward/backwards a single page
j, k, down, up:
Go forwards/backwards a screen's worth of pages
q, esc:
Quit
g:
Go to specific page (type numbers after 'g')
/:
Search
n, N:
Next/Previous search result
i:
Invert colors
f:
Remove borders/fullscreen
?:
Show this page
ctrl+z:
Suspend & background tdf \
";
pub enum InputAction { pub enum InputAction {
Redraw, Redraw,
JumpingToPage(usize), JumpingToPage(usize),
Search(String), Search(String),
QuitApp, QuitApp
Invert,
Fullscreen
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]