### Install tdf from Source using Cargo Source: https://context7.com/itsjunetime/tdf/llms.txt Use Cargo to install the latest release directly from GitHub. Features like EPUB and CBZ support can be enabled using flags. ```bash cargo install --git https://github.com/itsjunetime/tdf.git ``` ```bash # With EPUB support car go install --git https://github.com/itsjunetime/tdf.git --features epub ``` ```bash # With CBZ (comic book archive) support car go install --git https://github.com/itsjunetime/tdf.git --features cbz ``` ```bash # Both formats car go install --git https://github.com/itsjunetime/tdf.git --features epub,cbz ``` ```bash # Build locally (binary at ./target/release/tdf) git clone https://github.com/itsjunetime/tdf.git && cd tdf car go build --release ``` -------------------------------- ### Basic tdf CLI Usage Examples Source: https://context7.com/itsjunetime/tdf/llms.txt Demonstrates common ways to invoke the tdf command-line tool to open PDF files with various options. ```bash # Open a PDF tdf ./paper.pdf ``` ```bash # Open with dark background and light text (inverted colors for dark terminals) tdf --black-color "FAFAFA" --white-color "1E1E1E" ./paper.pdf ``` ```bash # View manga (right-to-left, no more than 2 pages wide, fullscreen) tdf --r-to-l --max-wide 2 --fullscreen ./manga_vol1.pdf ``` ```bash # Compile-time LaTeX preview with fast hot-reload (10ms debounce) tdf --reload-delay 10 ./thesis.pdf ``` ```bash # Pre-render only the 6 pages around the current one (saves RAM on huge PDFs) tdf --prerender 6 ./encyclopedia.pdf ``` ```bash # Print version tdf --version # => 0.5.0 ``` -------------------------------- ### Async Image Conversion Loop with TDF Source: https://context7.com/itsjunetime/tdf/llms.txt Demonstrates setting up and running an asynchronous Tokio task for image conversion. It handles different terminal protocols (Kitty, Generic) and optionally uses shared memory for Kitty. Requires setup of communication channels and a `Picker` to detect terminal capabilities. ```rust use tdf:: converter::{run_conversion_loop, ConverterMsg, ConvertedImage}, renderer::RenderError, }; use ratatui_image::picker::Picker; use flume; use tokio; #[tokio::main] async fn main() { // Create the picker by querying the terminal for its protocol and font size let picker = Picker::from_query_stdio().expect("could not detect terminal protocol"); let is_kitty = picker.protocol_type() == ratatui_image::picker::ProtocolType::Kitty; let prerender_window = 20; // convert up to 20 pages around the current one // channel: main → converter let (to_conv_tx, to_conv_rx) = flume::unbounded::(); // channel: converter → main let (from_conv_tx, from_conv_rx) = flume::unbounded(); // Probe shared-memory support (Kitty-specific optimization) // In real usage this requires an active EventStream from crossterm let shms_work = false; tokio::spawn(run_conversion_loop( from_conv_tx, to_conv_rx, picker, prerender_window, shms_work, )); // Tell the converter how many pages exist to_conv_tx.send(ConverterMsg::NumPages(10)).unwrap(); // Send a rendered page from the renderer (page_num, raw PNM bytes, cell dimensions) // In real usage this comes from the renderer's PageInfo struct // to_conv_tx.send(ConverterMsg::AddImg(page_info)).unwrap(); // Jump the conversion focus to a different page to_conv_tx.send(ConverterMsg::GoToPage(3)).unwrap(); // Receive converted pages while let Ok(result) = from_conv_rx.recv_async().await { match result { Ok(converted) => { let (w, h) = converted.page.w_h(); let protocol = match &converted.page { ConvertedImage::Kitty { .. } => "kitty", ConvertedImage::Generic(_) => "generic", }; println!( "Page {} converted via {} protocol: {}x{} cells, {} search highlights", converted.num, protocol, w, h, converted.num_results ); } Err(RenderError::Converting(e)) => eprintln!("Conversion error: {e}"), Err(e) => eprintln!("Other error: {e:?}"), } } } ``` -------------------------------- ### Start Background PDF Rendering Loop Source: https://context7.com/itsjunetime/tdf/llms.txt Initiates the background rendering process for a PDF document. It requires channels for sending control notifications and receiving rendered page data. Configure rendering parameters like cell size and prerender limit. ```rust use std::{num::NonZeroUsize, path::Path}; use flume; use tdf::{ PrerenderLimit, renderer::{start_rendering, RenderInfo, RenderNotif, RenderError, MUPDF_BLACK, MUPDF_WHITE}, }; fn main() { let path = Path::new("./document.pdf"); // channel: main → renderer (control notifications) let (notif_tx, notif_rx) = flume::unbounded::(); // channel: renderer → main (rendered page data) let (info_tx, info_rx) = flume::unbounded::>(); // Tell the renderer about the available terminal area upfront let cell_h_px: u16 = 20; // pixels per terminal cell (height) let cell_w_px: u10 = 10; // pixels per terminal cell (width) // Inform the renderer of the available area (in terminal columns/rows) notif_tx.send(RenderNotif::Area(ratatui::layout::Rect { x: 0, y: 0, width: 200, height: 50, })).unwrap(); let path_clone = path.to_path_buf(); std::thread::spawn(move || { start_rendering( &path_clone, info_tx, notif_rx, cell_h_px, cell_w_px, PrerenderLimit::All, // pre-render everything MUPDF_BLACK, // default black MUPDF_WHITE, // default white ) }); // Receive the page count first match info_rx.recv().unwrap() { Ok(RenderInfo::NumPages(n)) => println!("PDF has {n} pages"), _ => panic!("Expected NumPages first"), } // Receive rendered pages for msg in info_rx.iter() { match msg { Ok(RenderInfo::Page(page_info)) => { println!( "Page {} rendered: {}x{} terminal cells, {} search hits", page_info.page_num, page_info.img_data.cell_w, page_info.img_data.cell_h, page_info.result_rects.len() ); } Ok(RenderInfo::Reloaded) => println!("Document was reloaded from disk") Ok(RenderInfo::SearchResults { page_num, num_results }) => println!("Page {page_num}: {num_results} search results"), Err(RenderError::Doc(e)) => eprintln!("MuPDF error: {e}"), Err(RenderError::Converting(s)) => eprintln!("Convert error: {s}"), Err(RenderError::Notify(e)) => eprintln!("FS watch error: {e}"), _ => {} } } // Jump to page 5 (0-indexed) notif_tx.send(RenderNotif::JumpToPage(4)).unwrap(); // Start a text search notif_tx.send(RenderNotif::Search("abstract".to_string())).unwrap(); // Reload from disk (e.g. after external edit) notif_tx.send(RenderNotif::Reload).unwrap(); // Invert colors notif_tx.send(RenderNotif::Invert).unwrap(); // Rotate 90° notif_tx.send(RenderNotif::Rotate).unwrap(); } ``` -------------------------------- ### Hot-Reloading PDF Viewer with Debounce Source: https://context7.com/itsjunetime/tdf/llms.txt Demonstrates how to use the `notify` crate to watch a directory for file changes and trigger automatic reloads. Includes configuration for debounce delay to coalesce rapid filesystem events. ```bash # Open a file and watch it reload automatically when it changes on disk tdf ./thesis.pdf # In another terminal, regenerate the PDF (e.g. from LaTeX) pdflatex thesis.tex # tdf detects the change and reloads within 50 ms, displaying "Document was reloaded!" # Use a longer debounce if your build tool is slow to finish writing tdf --reload-delay 500 ./thesis.pdf # Use a shorter debounce for near-instant feedback tdf --reload-delay 5 ./thesis.pdf ``` -------------------------------- ### Tui: Handle Keyboard Events and Manage State Source: https://context7.com/itsjunetime/tdf/llms.txt Demonstrates constructing Tui state, handling keyboard events for navigation and actions, managing bottom messages, and tracking rendering progress. Use this for interactive PDF viewing. ```rust use tdf::tui::{Tui, MessageSetting, BottomMessage, InputAction}; use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers}; fn main() { // Construct the TUI state let mut tui = Tui::new( "example.pdf".to_string(), None, // no max-wide limit false, // left-to-right layout false, // not using Kitty protocol ); // Simulate the renderer sending the page count tui.set_n_pages(42); // --- Keyboard event handling --- let right_key = Event::Key(KeyEvent::new(KeyCode::Right, KeyModifiers::empty())); match tui.handle_event(&right_key) { Some(InputAction::JumpingToPage(p)) => println!("Jumped to page {p}"), Some(InputAction::Redraw) => println!("Needs redraw"), Some(InputAction::QuitApp) => println!("Quit requested"), None => println!("No action"), _ => {} } // Simulate pressing 'i' to invert colors let i_key = Event::Key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::empty())); assert!(matches!(tui.handle_event(&i_key), Some(InputAction::Invert))); // --- Bottom message management --- tui.set_msg(MessageSetting::Some(BottomMessage::SearchResults("rust".into()))); // Later, restore the previous message tui.set_msg(MessageSetting::Pop); // --- Rendering progress tracking --- tui.got_num_results_on_page(3, 7); // page 3 has 7 search hits tui.got_num_results_on_page(9, 0); // page 9 has no hits // Mark page 5 as having failed to display (will be re-queued for rendering) tui.page_failed_display(5); // Error display use tdf::renderer::RenderError; tui.show_error(RenderError::Converting("bad pnm data".into())); } ``` -------------------------------- ### Tui::main_layout: Calculate Responsive Layout Source: https://context7.com/itsjunetime/tdf/llms.txt Calculates the `RenderLayout` based on terminal dimensions for PDF page rendering. Use this to determine the areas for PDF pages, headers, and footers in both fullscreen and normal modes. ```rust use tdf::tui::Tui; // Tui::main_layout is called inside a ratatui draw closure: // // term.draw(|frame| { // let layout = Tui::main_layout(frame, false); // // layout.page_area → Rect for the PDF pages // // layout.top_and_bottom → Some((header_rect, footer_rect)) or None in fullscreen // // // Send the page area to the renderer so it knows the render dimensions: // to_renderer.send(RenderNotif::Area(layout.page_area)).unwrap(); // // // Then render the TUI into the frame: // let kitty_display = tui.render(frame, &layout, font_size); // }) // // Example (non-fullscreen, 120×40 terminal): // frame.area() = Rect { x:0, y:0, w:120, h:40 } // After margins: w:116, h:38 // layout.page_area = Rect { x:2, y:4, w:116, h:32 } (rows 4–35) // top area = Rect { x:2, y:1, w:116, h:3 } (rows 1–3) // bottom area = Rect { x:2, y:36, w:116, h:3 } (rows 36–38) // // Example (fullscreen, 120×40 terminal): // layout.page_area = Rect { x:0, y:0, w:120, h:40 } // top_and_bottom = None ``` -------------------------------- ### tdf Keyboard Bindings for Navigation and Display Source: https://context7.com/itsjunetime/tdf/llms.txt Lists the keyboard shortcuts for navigating through pages, searching text, and controlling display options within the tdf viewer. ```text Navigation: l / Right Next page h / Left Previous page j / Down / PgDn Next screen's worth of pages k / Up / PgUp Previous screen's worth of pages g Jump to specific page (1-indexed) Search: / Enter search mode (type query, then Enter) n Jump to next page with search results N Jump to previous page with search results Esc Clear current search / dismiss current message Display: i Toggle color inversion f Toggle fullscreen (hide top/bottom chrome) r Rotate 90° clockwise (Kitty only) ? Show/hide the help page Kitty-protocol zoom & pan (requires Kitty terminal): z Toggle fill-screen / fit-screen zoom mode o / O Zoom in / zoom out H / J / K / L Pan left / down / up / right gg / G Scroll to top / bottom of page 0 / $ Scroll to left / right edge of page Application: q / Esc Quit Ctrl+Z Suspend to background (Unix only) ``` -------------------------------- ### Aspect-Ratio Scaling Utility Source: https://context7.com/itsjunetime/tdf/llms.txt Calculates the scale factor to fit or fill an image within a given area while preserving aspect ratio. Useful for rendering images at appropriate resolutions for display. ```rust use tdf::{scale_img_for_area, FitOrFill}; fn main() { // --- Fit mode: entire page visible, letterboxed if necessary --- let result = scale_img_for_area( (595.0, 842.0), // A4 page size in MuPDF points (width, height) (1200.0, 900.0), // available terminal area in pixels FitOrFill::Fit, ); // The page is portrait (narrow), so height is the constraining axis. // scale_factor = 900 / 842 ≈ 1.069 println!("Fit → scale={:.3}, w={:.1}, h={:.1}", result.scale_factor, result.width, result.height); // => Fit → scale=1.069, w=635.9, h=900.0 // --- Fill mode: page fills the area, content may be clipped --- let result = scale_img_for_area( (595.0, 842.0), (1200.0, 900.0), FitOrFill::Fill, ); // Width becomes the constraining axis. // scale_factor = 1200 / 595 ≈ 2.017 println!("Fill → scale={:.3}, w={:.1}, h={:.1}", result.scale_factor, result.width, result.height); // => Fill → scale=2.017, w=1200.0, h=1698.3 // --- Wide landscape page fitted into a tall area --- let result = scale_img_for_area( (1189.0, 841.0), // A0 landscape (800.0, 1200.0), // tall portrait terminal FitOrFill::Fit, ); println!("Landscape fit → scale={:.3}, w={:.1}, h={:.1}", result.scale_factor, result.width, result.height); // => Landscape fit → scale=0.673, w=800.0, h=565.9 } ``` -------------------------------- ### Probe Shared Memory Support in Kitty Terminal Source: https://context7.com/itsjunetime/tdf/llms.txt Asynchronously checks if the connected Kitty terminal supports POSIX shared memory for image transfer. It sends a query and returns true if acknowledged. This determines the image transfer strategy. ```rust use tdf::kitty::do_shms_work; use crossterm::event::EventStream; #[tokio::main] async fn main() { // Requires an active raw-mode terminal and EventStream. // Called once at startup before spawning the converter task: // // let mut ev_stream = crossterm::event::EventStream::new(); // let shms_work = do_shms_work(&mut ev_stream).await; // println!("Shared memory transfer: {}", if shms_work { "supported" } else { "not supported" }); // // => "Shared memory transfer: supported" (on Kitty/Ghostty on Linux/macOS) // // => "Shared memory transfer: not supported" (on any non-Kitty terminal) // // tokio::spawn(run_conversion_loop( // sender, receiver, picker, 20, shms_work // )); // // When shms_work is true, the converter uses kittage::image::Image::shm_from() // which maps the image data into a named shared memory object that the terminal // can read directly, bypassing the PTY write path entirely. println!("(do_shms_work requires a live terminal; see usage pattern above)"); } ``` -------------------------------- ### Send Images to Kitty Terminal Source: https://context7.com/itsjunetime/tdf/llms.txt Handles image display logic for Kitty-compatible terminals. Transmits full pixel data on first display and uses image IDs for subsequent displays to avoid redundant transfers. Clears old image placements to prevent accumulation. ```rust // display_kitty_images is called from the main redraw loop after term.draw(): // // let kitty_display = tui.render(frame, &layout, font_size); // // kitty_display is one of: // // KittyDisplay::NoChange — nothing to do // // KittyDisplay::ClearImages — clear all visible kitty images // // KittyDisplay::DisplayImages(..) — transmit/re-display images // // match display_kitty_images(kitty_display, &mut ev_stream, &mut kitty_z_idx).await { // Ok(()) => {}, // Err(DisplayErr { failed_pages, user_facing_err, source }) => { // // TransmitError::Terminal(TerminalError::NoEntity) means kitty evicted // // the image from memory; trigger a re-render silently: // for page_num in failed_pages { // tui.page_failed_display(page_num); // to_renderer.send(RenderNotif::PageNeedsReRender(page_num)).unwrap(); // } // // Other errors surface to the user via the bottom message bar: // tui.set_msg(MessageSetting::Some(BottomMessage::Error( // format!("{user_facing_err}: {source}") // ))); // } // } // // The kitty_z_idx counter is used to layer new frames on top of old ones, // then old placements are deleted by z-index so the terminal doesn't accumulate // invisible stale images. ``` -------------------------------- ### Ratatui Skip Widget for Diff Optimization Source: https://context7.com/itsjunetime/tdf/llms.txt A ratatui Widget that marks cells with a 'skip' flag to prevent redrawing unchanged areas. Used to optimize TUI rendering by skipping diff computations for static content. ```rust use tdf::skip::Skip; // Used inside a ratatui Frame draw closure: // // term.draw(|frame| { // // If size hasn't changed and images are unchanged, mark the image area as skip // if size == last_render_rect { // frame.render_widget(Skip::new(true), img_area); // // ratatui will not diff the cells in img_area this frame // return; // } // // ... otherwise render normally // })?; // // Skip::new(false) can be used to un-skip cells that were previously skipped, // though in tdf this is handled implicitly by the last_render.rect invalidation // mechanism: whenever something changes (resize, new page, zoom change, etc.), // last_render.rect is reset to Rect::default() so the skip branch is not taken. ``` -------------------------------- ### Internal Hot-Reload Logic Source: https://context7.com/itsjunetime/tdf/llms.txt Internal implementation details of the hot-reloading mechanism, including directory watching, event filtering, debouncing, and the reload process within the TUI. ```rust // Internal structure (from main.rs): // // notify watches path.parent() in NonRecursive mode. // Events are filtered to only those whose path.file_name() matches the opened file. // EventKind::Access is ignored; EventKind::Remove triggers a RenderError::Converting. // EventKind::Create / Modify / Any / Other trigger RenderNotif::Reload. // The debouncer coalesces multiple events within the delay window into a single reload. // // On reload, start_rendering() continues from the 'reload loop label: // - The document is re-opened with Document::open() // - RenderInfo::Reloaded is sent to the TUI (shown in the status bar) // - All rendered[] flags are reset and pages are re-rendered // - The existing search_term is preserved across reloads ``` -------------------------------- ### Interleaved Page Rendering Iterator Source: https://context7.com/itsjunetime/tdf/llms.txt Custom iterator that yields page indices in an interleaved pattern radiating outward from a center index. Useful for prioritizing nearby pages during pre-rendering. ```rust use std::num::NonZeroUsize; use tdf::skip::InterleavedAroundWithMax; fn main() { // Render order starting from page 5, in range [2, 21) let order: Vec = InterleavedAroundWithMax::new( 5, // around: currently viewed page 2, // inclusive_min NonZeroUsize::new(21).unwrap(), // exclusive_max ) .take(20) .collect(); println!("{order:?}"); // => [5, 6, 4, 7, 3, 8, 2, 9, 20, 10, 19, 11, 18, 12, 17, 13, 16, 14, 15, 15] // Starting from page 0 (beginning of document), range [0, 5) let from_start: Vec = InterleavedAroundWithMax::new( 0, 0, NonZeroUsize::new(5).unwrap(), ) .take(5) .collect(); println!("{from_start:?}"); // => [0, 1, 4, 2, 3] // Used internally as: // let page_iter = InterleavedAroundWithMax::new(start_point, 0, n_pages) // .take(prerender_limit); // for page_num in page_iter { // // render page_num … // } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.