mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-02 08:01:47 -04:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d7a913a27 | |||
| cb4d956f2d | |||
| 5a492599da | |||
| 7551c4dba3 | |||
| e61eb9b846 | |||
| 6b37976357 | |||
| 3628d21c74 | |||
| f4f3b4f539 | |||
| a79c534e97 | |||
| 971393892a |
@@ -1,6 +1,17 @@
|
|||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- Switched simd base64 crate for one that works on stable (from `vb64` to `base64_simd`)
|
||||||
|
- Allow boolean arguments to function as flags, without a `true` or `false` argument following the flag itself
|
||||||
|
- Fix cropping issues when zooming out too much while using kitty protocol
|
||||||
|
- Added `gg` and `G` keybindings for scrolling to the top and bottom of a page, respectively, when filling the width of the screen with kitty
|
||||||
|
- Updated help page to only show kitty keybindings when you're actually using kitty
|
||||||
|
|
||||||
|
# v0.4.3
|
||||||
|
|
||||||
|
- Fix issue with some terminals hanging on startup
|
||||||
|
- Fix issues with some iterm2-backend terminals not displaying anything
|
||||||
- Allow using ctrl+scroll to zoom in/out while zoomed using kitty backend
|
- Allow using ctrl+scroll to zoom in/out while zoomed using kitty backend
|
||||||
|
- (Internal) run CI with `--locked` flag to ensure lockfile is always in-sync
|
||||||
|
|
||||||
# v0.4.2
|
# v0.4.2
|
||||||
|
|
||||||
|
|||||||
Generated
+422
-542
File diff suppressed because it is too large
Load Diff
+4
-5
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tdf-viewer"
|
name = "tdf-viewer"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
authors = ["June Welker <junewelker@gmail.com>"]
|
authors = ["June Welker <junewelker@gmail.com>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "A terminal viewer for PDFs"
|
description = "A terminal viewer for PDFs"
|
||||||
@@ -49,7 +49,7 @@ log = "0.4.27"
|
|||||||
flexi_logger = "0.31"
|
flexi_logger = "0.31"
|
||||||
|
|
||||||
# for tracing with tokio-console
|
# for tracing with tokio-console
|
||||||
console-subscriber = { version = "0.4.0", optional = true }
|
console-subscriber = { version = "0.5.0", optional = true }
|
||||||
csscolorparser = { version = "0.7.0" }
|
csscolorparser = { version = "0.7.0" }
|
||||||
|
|
||||||
[profile.production]
|
[profile.production]
|
||||||
@@ -57,8 +57,7 @@ inherits = "release"
|
|||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["nightly"]
|
default = []
|
||||||
nightly = ["ratatui-image/vb64"]
|
|
||||||
tracing = ["tokio/tracing", "dep:console-subscriber"]
|
tracing = ["tokio/tracing", "dep:console-subscriber"]
|
||||||
epub = ["mupdf/epub"]
|
epub = ["mupdf/epub"]
|
||||||
cbz = ["mupdf/cbz"]
|
cbz = ["mupdf/cbz"]
|
||||||
@@ -93,7 +92,7 @@ checked_conversions = "warn"
|
|||||||
copy_iterator = "warn"
|
copy_iterator = "warn"
|
||||||
default_trait_access = "warn"
|
default_trait_access = "warn"
|
||||||
doc_link_with_quotes = "warn"
|
doc_link_with_quotes = "warn"
|
||||||
empty_enum = "warn"
|
empty_enums = "warn"
|
||||||
explicit_into_iter_loop = "warn"
|
explicit_into_iter_loop = "warn"
|
||||||
explicit_iter_loop = "warn"
|
explicit_iter_loop = "warn"
|
||||||
filter_map_next = "warn"
|
filter_map_next = "warn"
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ Designed to be performant, very responsive, and work well with even very large P
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Get the rust toolchain from [rustup.rs](https://rustup.rs)
|
1. Get the rust toolchain from [rustup.rs](https://rustup.rs)
|
||||||
2. Run `rustup install nightly && cargo +nightly install --git https://github.com/itsjunetime/tdf.git`
|
2. Run `cargo install --git https://github.com/itsjunetime/tdf.git`
|
||||||
|
|
||||||
|
If you want to use this with `epub`s or `cbz`s, add `--features epub` or `--features cbz` to the command line (or `--features cbz,epub` for both)
|
||||||
|
|
||||||
## To Build
|
## To Build
|
||||||
First, you need to install the system dependencies. This will generally only include `libfontconfig` and `clang`. If you're on linux, these will probably show up in your package manager as something like `libfontconfig1-devel` or `libfontconfig-dev` and just `clang`.
|
First, you need to install the system dependencies. This will generally only include `libfontconfig` and `clang`. If you're on linux, these will probably show up in your package manager as something like `libfontconfig1-devel` or `libfontconfig-dev` and just `clang`.
|
||||||
@@ -25,7 +27,7 @@ If it turns out that you're missing one of these, it will fail to compile and te
|
|||||||
|
|
||||||
1. Get the rust toolchain from [rustup.rs](https://rustup.rs)
|
1. Get the rust toolchain from [rustup.rs](https://rustup.rs)
|
||||||
2. Clone the repo and `cd` into it
|
2. Clone the repo and `cd` into it
|
||||||
3. Run `cargo +nightly build --release`
|
3. Run `cargo build --release`
|
||||||
|
|
||||||
The binary should then be found at `./target/release/tdf`.
|
The binary should then be found at `./target/release/tdf`.
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule ratatui updated: 47c200fb7f...6a0b8ddf76
+1
-1
Submodule ratatui-image updated: fe3d0b992b...9564ae6466
+4
-9
@@ -83,11 +83,11 @@ async fn inner_main() -> Result<(), WrappedErr> {
|
|||||||
let flags = xflags::parse_or_exit! {
|
let flags = xflags::parse_or_exit! {
|
||||||
/// Display the pdf with the pages starting at the right hand size and moving left and
|
/// Display the pdf with the pages starting at the right hand size and moving left and
|
||||||
/// adjust input keys to match
|
/// adjust input keys to match
|
||||||
optional -r,--r-to-l r_to_l: bool
|
optional -r,--r-to-l
|
||||||
/// 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)
|
/// Fullscreen the pdf (hide document name, page count, etc)
|
||||||
optional -f,--fullscreen fullscreen: bool
|
optional -f,--fullscreen
|
||||||
/// The number of pages to prerender surrounding the currently-shown page; 0 means no
|
/// The number of pages to prerender surrounding the currently-shown page; 0 means no
|
||||||
/// limit. By default, there is no limit.
|
/// limit. By default, there is no limit.
|
||||||
optional -p,--prerender prerender: usize
|
optional -p,--prerender prerender: usize
|
||||||
@@ -267,12 +267,7 @@ async fn inner_main() -> Result<(), WrappedErr> {
|
|||||||
|| "Unknown file".into(),
|
|| "Unknown file".into(),
|
||||||
|n| n.to_string_lossy().to_string()
|
|n| n.to_string_lossy().to_string()
|
||||||
);
|
);
|
||||||
let tui = Tui::new(
|
let tui = Tui::new(file_name, flags.max_wide, flags.r_to_l, is_kitty);
|
||||||
file_name,
|
|
||||||
flags.max_wide,
|
|
||||||
flags.r_to_l.unwrap_or_default(),
|
|
||||||
is_kitty
|
|
||||||
);
|
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(std::io::stdout());
|
let backend = CrosstermBackend::new(std::io::stdout());
|
||||||
let mut term = Terminal::new(backend).map_err(|e| {
|
let mut term = Terminal::new(backend).map_err(|e| {
|
||||||
@@ -300,7 +295,7 @@ async fn inner_main() -> Result<(), WrappedErr> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fullscreen = flags.fullscreen.unwrap_or_default();
|
let fullscreen = flags.fullscreen;
|
||||||
let main_area = Tui::main_layout(&term.get_frame(), fullscreen);
|
let main_area = Tui::main_layout(&term.get_frame(), fullscreen);
|
||||||
to_renderer
|
to_renderer
|
||||||
.send(RenderNotif::Area(main_area.page_area))
|
.send(RenderNotif::Area(main_area.page_area))
|
||||||
|
|||||||
+70
-28
@@ -16,6 +16,7 @@ use nix::{
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
layout::{Constraint, Flex, Layout, Position, Rect},
|
layout::{Constraint, Flex, Layout, Position, Rect},
|
||||||
|
prelude::{Line, Text},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
symbols::border,
|
symbols::border,
|
||||||
text::Span,
|
text::Span,
|
||||||
@@ -205,23 +206,28 @@ impl Tui {
|
|||||||
|
|
||||||
log::debug!("zoom is now {zoom:#?}");
|
log::debug!("zoom is now {zoom:#?}");
|
||||||
log::debug!("img_area is {img_area:#?}");
|
log::debug!("img_area is {img_area:#?}");
|
||||||
|
log::debug!("img dimensions are {cell_w}x{cell_h}");
|
||||||
|
|
||||||
if zoom.level < 0 {
|
|
||||||
img_area = Rect {
|
|
||||||
width: img_area
|
|
||||||
.width
|
|
||||||
.saturating_sub((zoom.level * 2).unsigned_abs())
|
|
||||||
.max(1),
|
|
||||||
x: img_area.x + (zoom.level.unsigned_abs().min(img_area.width / 2)),
|
|
||||||
..img_area
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("after adjustment, img_area is {img_area:#?}");
|
|
||||||
|
|
||||||
// Ugh I don't like this logic. I wish we could simplify it.
|
|
||||||
let img_width = f32::from(cell_w);
|
let img_width = f32::from(cell_w);
|
||||||
let img_height = f32::from(cell_h);
|
let img_height = f32::from(cell_h);
|
||||||
|
|
||||||
|
let img_aspect_ratio = img_width / img_height;
|
||||||
|
|
||||||
|
if zoom.level < 0 {
|
||||||
|
let old_width = img_area.width;
|
||||||
|
img_area.width = img_area
|
||||||
|
.width
|
||||||
|
.saturating_sub((zoom.level * 2).unsigned_abs())
|
||||||
|
.max((f32::from(img_area.height) * img_aspect_ratio) as u16);
|
||||||
|
img_area.x += (old_width - img_area.width) / 2;
|
||||||
|
|
||||||
|
log::debug!("after adjustment, img_area is {img_area:#?}");
|
||||||
|
|
||||||
|
// TODO: Find a way to detect when we've hit the maximum zoom-out and stop
|
||||||
|
// more zooming out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ugh I don't like this logic. I wish we could simplify it.
|
||||||
let img_area_width = f32::from(img_area.width);
|
let img_area_width = f32::from(img_area.width);
|
||||||
let img_area_height = f32::from(img_area.height);
|
let img_area_height = f32::from(img_area.height);
|
||||||
let available_to_real_width_ratio = img_area_width / img_width;
|
let available_to_real_width_ratio = img_area_width / img_width;
|
||||||
@@ -560,6 +566,12 @@ impl Tui {
|
|||||||
if let BottomMessage::Input(InputCommand::GoToPage(ref mut page)) =
|
if let BottomMessage::Input(InputCommand::GoToPage(ref mut page)) =
|
||||||
self.bottom_msg
|
self.bottom_msg
|
||||||
{
|
{
|
||||||
|
if c == 'g' && self.is_kitty {
|
||||||
|
self.update_zoom(|z| z.cell_pan_from_top = 0);
|
||||||
|
self.set_msg(MessageSetting::Pop);
|
||||||
|
return Some(InputAction::Redraw);
|
||||||
|
}
|
||||||
|
|
||||||
return c.to_digit(10).map(|input_num| {
|
return c.to_digit(10).map(|input_num| {
|
||||||
*page = (*page * 10) + input_num as usize;
|
*page = (*page * 10) + input_num as usize;
|
||||||
InputAction::Redraw
|
InputAction::Redraw
|
||||||
@@ -673,6 +685,8 @@ impl Tui {
|
|||||||
'K' if self.is_kitty => self.update_zoom(|z| {
|
'K' if self.is_kitty => self.update_zoom(|z| {
|
||||||
z.cell_pan_from_top = z.cell_pan_from_top.saturating_sub(1)
|
z.cell_pan_from_top = z.cell_pan_from_top.saturating_sub(1)
|
||||||
}),
|
}),
|
||||||
|
'G' if self.is_kitty =>
|
||||||
|
self.update_zoom(|z| z.cell_pan_from_top = u16::MAX),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -787,7 +801,7 @@ impl Tui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// I want this to always return 0 'cause I just use it to return from `Self::handle_event`]
|
// I want this to always return an option 'cause I just use it to return from `Self::handle_event`
|
||||||
#[expect(clippy::unnecessary_wraps)]
|
#[expect(clippy::unnecessary_wraps)]
|
||||||
fn update_zoom(&mut self, f: impl FnOnce(&mut Zoom)) -> Option<InputAction> {
|
fn update_zoom(&mut self, f: impl FnOnce(&mut Zoom)) -> Option<InputAction> {
|
||||||
if let Some(z) = &mut self.zoom {
|
if let Some(z) = &mut self.zoom {
|
||||||
@@ -847,11 +861,24 @@ impl Tui {
|
|||||||
.border_set(border::ROUNDED)
|
.border_set(border::ROUNDED)
|
||||||
.border_style(Color::Blue);
|
.border_style(Color::Blue);
|
||||||
|
|
||||||
let help_span = Paragraph::new(HELP_PAGE).wrap(Wrap { trim: false });
|
let help_sections = [
|
||||||
|
Text::from(HELP_PAGE),
|
||||||
|
// just some spacing
|
||||||
|
Text::from(""),
|
||||||
|
if self.is_kitty {
|
||||||
|
Text::from(KITTY_HELP)
|
||||||
|
} else {
|
||||||
|
Text::from("Not using kitty, kitty-specific keybindings hidden")
|
||||||
|
.style(Color::DarkGray)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
let max_w: u16 = HELP_PAGE
|
let max_w: u16 = help_sections
|
||||||
.lines()
|
.iter()
|
||||||
.map(str::len)
|
.flat_map(|section| section.lines.as_slice())
|
||||||
|
// We don't really need full unicode-width since we're using all ascii for the help
|
||||||
|
// pages, but this is the function they give us.
|
||||||
|
.map(Line::width)
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -866,15 +893,24 @@ impl Tui {
|
|||||||
|
|
||||||
let block_area = Layout::vertical([
|
let block_area = Layout::vertical([
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(u16::try_from(HELP_PAGE.lines().count()).unwrap() + 4),
|
Constraint::Length(
|
||||||
|
u16::try_from(help_sections.iter().map(|s| s.lines.len()).sum::<usize>()).unwrap()
|
||||||
|
+ 4
|
||||||
|
),
|
||||||
Constraint::Fill(1)
|
Constraint::Fill(1)
|
||||||
])
|
])
|
||||||
.split(layout[1]);
|
.split(layout[1]);
|
||||||
|
|
||||||
let block_inner = block.inner(block_area[1]);
|
let mut block_inner = block.inner(block_area[1]);
|
||||||
|
|
||||||
frame.render_widget(block, block_area[1]);
|
frame.render_widget(block, block_area[1]);
|
||||||
frame.render_widget(help_span, block_inner);
|
|
||||||
|
for section in help_sections {
|
||||||
|
let section_lines = section.lines.len();
|
||||||
|
let span = Paragraph::new(section).wrap(Wrap { trim: false });
|
||||||
|
frame.render_widget(span, block_inner);
|
||||||
|
block_inner.y += u16::try_from(section_lines).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,18 +931,24 @@ i:
|
|||||||
Invert colors
|
Invert colors
|
||||||
f:
|
f:
|
||||||
Remove borders/fullscreen
|
Remove borders/fullscreen
|
||||||
z (when using kitty protocol):
|
|
||||||
Toggle between fill-screen and fit-screen
|
|
||||||
o/O (when on fill-screen):
|
|
||||||
zoom in and out, respectively
|
|
||||||
H, J, K, L (when zoomed in):
|
|
||||||
pan direction around page
|
|
||||||
?:
|
?:
|
||||||
Show this page
|
Show this page
|
||||||
ctrl+z:
|
ctrl+z:
|
||||||
Suspend & background tdf \
|
Suspend & background tdf \
|
||||||
";
|
";
|
||||||
|
|
||||||
|
static KITTY_HELP: &str = "\
|
||||||
|
When using Kitty Protocol:
|
||||||
|
z:
|
||||||
|
Toggle between fill-screen and fit-screen
|
||||||
|
o/O (when on fill-screen):
|
||||||
|
Zoom in and out, respectively
|
||||||
|
gg/G (when on fill-screen):
|
||||||
|
Scroll to top/bottom of page
|
||||||
|
H, J, K, L (when zoomed in):
|
||||||
|
Pan direction around page
|
||||||
|
";
|
||||||
|
|
||||||
pub enum InputAction {
|
pub enum InputAction {
|
||||||
Redraw,
|
Redraw,
|
||||||
JumpingToPage(usize),
|
JumpingToPage(usize),
|
||||||
|
|||||||
Reference in New Issue
Block a user