Compare commits

...

9 Commits

Author SHA1 Message Date
itsjunetime 8abebc99e5 oops lockfile 2026-06-25 16:27:35 -05:00
itsjunetime d6aee778e0 next iteration of mimalloc windows attempts, plus verbose test compilation 2026-06-25 16:23:37 -05:00
itsjunetime 04d4efaf12 try to fix windows dll linking with mimalloc 2026-06-25 15:54:38 -05:00
itsjunetime 5c380a8713 maybe we need to enable debug in mimalloc for windows? 2026-06-22 22:05:51 -05:00
itsjunetime eaeed4730b Box error 2026-06-22 21:47:09 -05:00
itsjunetime 032fad1f9b Update deps again 2026-06-22 21:35:53 -05:00
itsjunetime 91b923f593 fmt 2026-06-06 19:45:34 -05:00
itsjunetime d5abc4bae5 Use upstream versions of ratatui crates 2026-06-06 19:45:27 -05:00
June 06f737b1fe Prevent zoomout in both dimensions using double-pass system (#150) 2026-05-17 14:40:29 -05:00
8 changed files with 519 additions and 279 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
- name: Clippy
run: cargo clippy --locked -- -D warnings
- name: Tests
run: cargo test --locked
run: cargo test --verbose --locked
- name: Check fmt
run: cargo fmt -- --check
- name: Run benchmarks as tests
Generated
+458 -211
View File
File diff suppressed because it is too large Load Diff
+3 -11
View File
@@ -22,21 +22,16 @@ path = "src/main.rs"
name = "tdf"
[dependencies]
# we're using this branch because it has significant performance fixes that I'm waiting on responses from the upstream devs to get upstreamed. See https://github.com/ratatui-org/ratatui/issues/1116
ratatui = { git = "https://github.com/itsjunetime/ratatui.git", rev = "720ac2d0cad1ac6424364fea74856fec9c100cb1", default-features = false, features = [ "crossterm", "layout-cache" ] }
# ratatui = { path = "./ratatui/ratatui/" }
# We're using this to have the vb64 feature (for faster base64 encoding, since that does take up a good bit of time when converting images to the `Protocol`. It also just includes a few more features that I'm waiting on main to upstream
ratatui-image = { git = "https://github.com/itsjunetime/ratatui-image.git", rev = "a276a87cb8e2976442c6cc59db831db81551da89", default-features = false }
# ratatui-image = { path = "./ratatui-image", default-features = false }
ratatui = { version = "^0.30.1", default-features = false, features = [ "crossterm", "layout-cache" ] }
ratatui-image = { version = "11.0", default-features = false }
crossterm = { version = "0.29.0", features = ["event-stream"] }
# crossterm = { path = "../crossterm", features = ["event-stream"] }
image = { version = "0.25.1", features = ["pnm", "rayon", "png"], default-features = false }
notify = { version = "8.0.0", features = ["crossbeam-channel"] }
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] }
futures-util = { version = "0.3.30", default-features = false }
flume = { version = "0.12.0", default-features = false, features = ["async"] }
xflags = "0.4.0-pre.2"
mimalloc = "0.1.43"
mimalloc = { git = "https://github.com/itsjunetime/mimalloc_rust.git", rev = "3b0dda87caa5fbf4df1f60063a32b1462ddb5a85", features = [ "debug_in_debug" ] }
nix = { version = "0.31.0", features = ["signal"] }
mupdf = { git = "https://github.com/messense/mupdf-rs.git", rev = "d7441b9998c92135e329559c0aa71d9dc92cf4de", default-features = false, features = ["svg", "system-fonts", "img"] }
rayon = { version = "1", default-features = false }
@@ -54,9 +49,6 @@ flexi_logger = "0.31"
# for tracing with tokio-console
console-subscriber = { version = "0.5.0", optional = true }
[patch.crates-io]
pathfinder_simd = { git = "https://github.com/itsjunetime/pathfinder.git", rev = "814671e162a1829e074521446317b915311d3d4d" }
[profile.production]
inherits = "release"
lto = "fat"
+10 -4
View File
@@ -4,7 +4,10 @@ use crossterm::terminal::WindowSize;
use flume::{Sender, r#async::RecvStream, unbounded};
use futures_util::stream::StreamExt as _;
use ratatui::layout::Rect;
use ratatui_image::picker::{Picker, ProtocolType};
use ratatui_image::{
FontSize,
picker::{Picker, ProtocolType}
};
use tdf::{
converter::{ConvertedPage, ConverterMsg, run_conversion_loop},
renderer::{RenderError, RenderInfo, RenderNotif, fill_default, start_rendering}
@@ -54,7 +57,10 @@ pub struct RenderState {
pub to_render_tx: Sender<RenderNotif>
}
const FONT_SIZE: (u16, u16) = (8, 14);
const FONT_SIZE: FontSize = FontSize {
width: 8,
height: 14
};
pub fn start_rendering_loop(
path: impl AsRef<Path>,
@@ -74,8 +80,8 @@ pub fn start_rendering_loop(
let size = WindowSize {
columns,
rows,
height: rows * FONT_SIZE.1,
width: columns * FONT_SIZE.0
height: rows * FONT_SIZE.height,
width: columns * FONT_SIZE.width
};
let main_area = Rect {
+8 -10
View File
@@ -8,7 +8,7 @@ use flume::{Receiver, SendError, Sender, TryRecvError};
use futures_util::stream::StreamExt as _;
use image::{DynamicImage, codecs::pnm::PnmDecoder};
use kittage::NumberOrId;
use ratatui::layout::Rect;
use ratatui::prelude::Size;
use ratatui_image::{
Resize,
picker::{Picker, ProtocolType},
@@ -27,7 +27,7 @@ pub enum MaybeTransferred {
Transferred(kittage::ImageId)
}
#[derive(Debug)]
// #[derive(Debug)]
pub enum ConvertedImage {
Generic(Protocol),
Kitty {
@@ -42,8 +42,8 @@ impl ConvertedImage {
pub fn w_h(&self) -> (u16, u16) {
match self {
Self::Generic(prot) => {
let a = prot.area();
(a.width, a.height)
let Size { width, height } = prot.size();
(width, height)
}
Self::Kitty {
img: _,
@@ -72,7 +72,7 @@ pub async fn run_conversion_loop(
picker: Picker,
prerender: usize,
shms_work: bool
) -> Result<(), SendError<Result<ConvertedPage, RenderError>>> {
) -> Result<(), Box<SendError<Result<ConvertedPage, RenderError>>>> {
let mut images = vec![];
let mut page: usize = 0;
let pid = std::process::id();
@@ -134,11 +134,9 @@ pub async fn run_conversion_loop(
.for_each(|(_, _, px)| px.0[2] = px.0[2].saturating_sub(u8::MAX / 2));
}
let img_area = Rect {
let img_size = Size {
width: page_info.img_data.cell_w,
height: page_info.img_data.cell_h,
x: 0,
y: 0
height: page_info.img_data.cell_h
};
let dyn_img = DynamicImage::ImageRgb8(dyn_img);
@@ -174,7 +172,7 @@ pub async fn run_conversion_loop(
}
_ => ConvertedImage::Generic(
picker
.new_protocol(dyn_img, img_area, Resize::None)
.new_protocol(dyn_img, img_size, Resize::Crop(None))
.map_err(|e| {
RenderError::Converting(format!(
"Couldn't convert DynamicImage to ratatui image: {e}"
+2 -2
View File
@@ -260,7 +260,7 @@ async fn inner_main() -> Result<(), WrappedErr> {
// the 'equivalent' that is suggested instead is not the same. We need to keep
// calling this.
#[expect(deprecated)]
Ok(Picker::from_fontsize((cell_width_px, cell_height_px)))
Ok(Picker::from_fontsize(FontSize { width: cell_width_px, height: cell_height_px }))
},
ratatui_image::errors::Errors::NoFontSize => Err(WrappedErr(
"Unable to detect your terminal's font size; this is an issue with your terminal emulator.\nPlease use a different terminal emulator or report this bug to tdf.".into()
@@ -315,7 +315,7 @@ async fn inner_main() -> Result<(), WrappedErr> {
let mut term = Terminal::new(backend).map_err(|e| {
WrappedErr(format!("Couldn't set up crossterm's terminal backend: {e}").into())
})?;
term.skip_diff(true);
// term.skip_diff(true);
enable_raw_mode().map_err(|e| {
WrappedErr(
+3 -12
View File
@@ -1,23 +1,14 @@
use std::num::NonZeroUsize;
use ratatui::widgets::Widget;
use ratatui::{prelude::buffer::CellDiffOption, widgets::Widget};
pub struct Skip {
skip: bool
}
impl Skip {
#[must_use]
pub fn new(skip: bool) -> Self {
Self { skip }
}
}
pub struct Skip;
impl Widget for Skip {
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) {
for x in area.x..(area.x + area.width) {
for y in area.y..(area.y + area.height) {
buf[(x, y)].skip = self.skip;
buf[(x, y)].diff_option = CellDiffOption::Skip;
}
}
}
+34 -28
View File
@@ -262,7 +262,7 @@ impl Tui {
let img_page_w_ratio = img_section_w / initial_page_w;
let img_page_h_ratio = img_section_h / initial_page_h;
let shrink_move_page = |dim: &mut u16, pos: &mut u16, axis_zoom_factor: f32| {
fn shrink_move_page(dim: &mut u16, pos: &mut u16, axis_zoom_factor: f32) {
let old_dim = *dim;
// The axis zoom factor tells us what portion of the axis
// we need to show.
@@ -272,50 +272,56 @@ impl Tui {
.checked_sub(*dim)
.expect("zooming out should shrink the image")
/ 2;
};
}
if img_page_w_ratio < img_page_h_ratio {
fn shrink_move_two_pass(
zoom: &mut Zoom,
dim: &mut u16,
pos: &mut u16,
zoom_factor: f32,
axis_zoom_factor: f32
) {
// To detect if we're already at fit-screen, we perform a first
// pass on `img_area` that emulates the last call to
// `render_zoomed` by subtracting from the `zoom`'s level. This
// holds because the `img_area` that gets passed is always the
// same.
let mut first_pass_area = img_area;
let mut first_pass_dim = *dim;
let mut first_pass_pos = *pos;
let mut first_pass_zoom = *zoom;
if first_pass_zoom.level.is_negative() {
first_pass_zoom.step_in();
}
let first_pass_zoom_factor = first_pass_zoom.factor();
shrink_move_page(
&mut first_pass_area.width,
&mut first_pass_area.x,
first_pass_zoom_factor.max(1.0 / img_page_h_ratio)
&mut first_pass_dim,
&mut first_pass_pos,
first_pass_zoom_factor.max(1.0 / axis_zoom_factor)
);
log::debug!("first_pass_area: {first_pass_area:#?}");
log::debug!("first_pass_dim: {first_pass_dim}, first_pass_pos: {first_pass_pos}");
// vertical scroll / tall image. zooming out means decreasing
// the width of the page area
shrink_move_page(
&mut img_area.width,
&mut img_area.x,
// disallow zooming out past fit-screen
zoom_factor.max(1.0 / img_page_h_ratio)
);
log::debug!("img_area: {img_area:#?}");
// use `max` to disallow zooming out past fit-screen
shrink_move_page(dim, pos, zoom_factor.max(1.0 / axis_zoom_factor));
log::debug!("new dim: {dim}, new pos: {pos}");
// The moment the image area is left unmodified, we've hit
// fit-screen and the zoom level ought be normalized.
if first_pass_area == img_area {
if first_pass_dim == *dim && first_pass_pos == *pos {
zoom.step_in();
}
}
let (dim, pos, axis_zoom_factor) = if img_page_w_ratio < img_page_h_ratio {
(&mut img_area.width, &mut img_area.x, img_page_h_ratio)
} else {
// horizontal scroll / wide image. zooming out means decreasing
// the height of the page area
shrink_move_page(
&mut img_area.height,
&mut img_area.y,
// disallow zooming out past fit-screen
zoom_factor.max(1.0 / img_page_w_ratio)
);
}
(&mut img_area.height, &mut img_area.y, img_page_w_ratio)
};
shrink_move_two_pass(zoom, dim, pos, zoom_factor, axis_zoom_factor);
}
log::debug!("after adjustment, page area is {img_area:#?}");
@@ -343,8 +349,8 @@ impl Tui {
}
}
let width = (img_section_w * f32::from(font_size.0)) as u32;
let height = (img_section_h * f32::from(font_size.1)) as u32;
let width = (img_section_w * f32::from(font_size.width)) as u32;
let height = (img_section_h * f32::from(font_size.height)) as u32;
zoom.cell_pan_from_left = zoom
.cell_pan_from_left
@@ -361,8 +367,8 @@ impl Tui {
y: img_area.y
},
display_loc: DisplayLocation {
x: u32::from(zoom.cell_pan_from_left) * u32::from(font_size.0),
y: u32::from(zoom.cell_pan_from_top) * u32::from(font_size.1),
x: u32::from(zoom.cell_pan_from_left) * u32::from(font_size.width),
y: u32::from(zoom.cell_pan_from_top) * u32::from(font_size.height),
width,
height,
columns: img_area.width,
@@ -403,7 +409,7 @@ impl Tui {
// resize this time), then go through every element in the buffer where any Image would
// be written and set to skip it so that ratatui doesn't spend a lot of time diffing it
// each re-render
frame.render_widget(Skip::new(true), img_area);
frame.render_widget(Skip, img_area);
return KittyDisplay::NoChange;
}