mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-02 08:01:47 -04:00
Initial implementation of attempted mupdf rewrite
This commit is contained in:
Generated
+538
-318
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -21,8 +21,8 @@ path = "src/main.rs"
|
|||||||
name = "tdf"
|
name = "tdf"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
poppler-rs = { version = "0.24.1", default-features = false, features = ["v23_7"] }
|
# poppler-rs = { version = "0.24.1", default-features = false, features = ["v23_7"] }
|
||||||
cairo-rs = { version = "0.20.0", default-features = false, features = ["png"] }
|
# cairo-rs = { version = "0.20.0", default-features = false, features = ["png"] }
|
||||||
# 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
|
# 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" }
|
ratatui = { git = "https://github.com/itsjunetime/ratatui.git" }
|
||||||
# ratatui = { path = "./ratatui/ratatui" }
|
# ratatui = { path = "./ratatui/ratatui" }
|
||||||
@@ -30,16 +30,17 @@ 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 = ["png", "rayon"], default-features = false }
|
image = { version = "0.25.1", features = ["pnm", "bmp", "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 }
|
||||||
glib = "0.20.0"
|
# glib = "0.20.0"
|
||||||
itertools = "*"
|
itertools = "*"
|
||||||
flume = { version = "0.11.0", default-features = false, features = ["async"] }
|
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 = "0.4.4"
|
||||||
|
|
||||||
# for tracing with tokio-console
|
# for tracing with tokio-console
|
||||||
console-subscriber = { version = "0.4.0", optional = true }
|
console-subscriber = { version = "0.4.0", optional = true }
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ async fn render_all_files(path: &'static str) -> Vec<PageInfo> {
|
|||||||
RenderInfo::Reloaded => (),
|
RenderInfo::Reloaded => (),
|
||||||
RenderInfo::NumPages(num) => fill_default(&mut pages, num),
|
RenderInfo::NumPages(num) => fill_default(&mut pages, num),
|
||||||
RenderInfo::Page(page) => {
|
RenderInfo::Page(page) => {
|
||||||
let num = page.page;
|
let num = page.page_num;
|
||||||
pages[num] = Some(page);
|
pages[num] = Some(page);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ pub fn start_rendering_loop(
|
|||||||
Sender<RenderNotif>
|
Sender<RenderNotif>
|
||||||
) {
|
) {
|
||||||
let pathbuf = path.as_ref().canonicalize().unwrap();
|
let pathbuf = path.as_ref().canonicalize().unwrap();
|
||||||
let str_path = format!("file://{}", pathbuf.into_os_string().to_string_lossy());
|
let str_path = pathbuf.into_os_string().to_string_lossy().to_string();
|
||||||
|
|
||||||
let (to_render_tx, from_main_rx) = unbounded();
|
let (to_render_tx, from_main_rx) = unbounded();
|
||||||
let (to_main_tx, from_render_rx) = unbounded();
|
let (to_main_tx, from_render_rx) = unbounded();
|
||||||
|
|||||||
+10
-11
@@ -1,6 +1,5 @@
|
|||||||
use flume::{Receiver, SendError, Sender, TryRecvError};
|
use flume::{Receiver, SendError, Sender, TryRecvError};
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use image::ImageFormat;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui_image::{picker::Picker, protocol::Protocol, Resize};
|
use ratatui_image::{picker::Picker, protocol::Protocol, Resize};
|
||||||
|
|
||||||
@@ -54,22 +53,22 @@ pub async fn run_conversion_loop(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let img_area = page_info.img_data.area;
|
let dyn_img = image::load_from_memory_with_format(
|
||||||
|
&page_info.img_data.pixels,
|
||||||
|
image::ImageFormat::Pnm
|
||||||
|
)
|
||||||
|
.map_err(|e| RenderError::Converting(format!("Can't load image: {e}")))?;
|
||||||
|
|
||||||
let dyn_img =
|
let img_area = page_info.img_data.cell_area;
|
||||||
image::load_from_memory_with_format(&page_info.img_data.data, ImageFormat::Png)
|
|
||||||
.map_err(|e| {
|
|
||||||
RenderError::Render(format!("Couldn't convert Vec<u8> to DynamicImage: {e}"))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// We don't actually want to Crop this image, but we've already
|
// We don't actually want to Crop this image, but we've already
|
||||||
// verified (with the ImageSurface stuff) that the image is the correct
|
// verified (with the ImageSurface stuff) that the image is the correct
|
||||||
// size for the area given, so to save ratatui the work of having to
|
// size for the area given, so to save ratatui the work of having to
|
||||||
// resize it, we tell them to crop it to fit.
|
// resize it, we tell them to crop it to fit.
|
||||||
let txt_img = picker
|
let txt_img = picker
|
||||||
.new_protocol(dyn_img, img_area, Resize::None)
|
.new_protocol(dyn_img, img_area, Resize::Scale(None))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
RenderError::Render(format!(
|
RenderError::Converting(format!(
|
||||||
"Couldn't convert DynamicImage to ratatui image: {e}"
|
"Couldn't convert DynamicImage to ratatui image: {e}"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -79,7 +78,7 @@ pub async fn run_conversion_loop(
|
|||||||
|
|
||||||
Ok(Some(ConvertedPage {
|
Ok(Some(ConvertedPage {
|
||||||
page: txt_img,
|
page: txt_img,
|
||||||
num: page_info.page,
|
num: page_info.page_num,
|
||||||
num_results: page_info.search_results
|
num_results: page_info.search_results
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -87,7 +86,7 @@ pub async fn run_conversion_loop(
|
|||||||
fn handle_notif(msg: ConverterMsg, images: &mut Vec<Option<PageInfo>>, page: &mut usize) {
|
fn handle_notif(msg: ConverterMsg, images: &mut Vec<Option<PageInfo>>, page: &mut usize) {
|
||||||
match msg {
|
match msg {
|
||||||
ConverterMsg::AddImg(img) => {
|
ConverterMsg::AddImg(img) => {
|
||||||
let page_num = img.page;
|
let page_num = img.page_num;
|
||||||
images[page_num] = Some(img);
|
images[page_num] = Some(img);
|
||||||
}
|
}
|
||||||
ConverterMsg::NumPages(n_pages) => {
|
ConverterMsg::NumPages(n_pages) => {
|
||||||
|
|||||||
+7
-7
@@ -13,7 +13,7 @@ use crossterm::{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
use futures_util::{stream::StreamExt, FutureExt};
|
use futures_util::{stream::StreamExt, FutureExt};
|
||||||
use glib::{LogField, LogLevel, LogWriterOutput};
|
// use glib::{LogField, LogLevel, LogWriterOutput};
|
||||||
use notify::{Event, EventKind, RecursiveMode, Watcher};
|
use notify::{Event, EventKind, RecursiveMode, Watcher};
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use ratatui_image::picker::Picker;
|
use ratatui_image::picker::Picker;
|
||||||
@@ -81,7 +81,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// TODO: Handle non-utf8 file names? Maybe by constructing a CString and passing that in to the
|
// TODO: Handle non-utf8 file names? Maybe by constructing a CString and passing that in to the
|
||||||
// poppler stuff instead of a rust string?
|
// poppler stuff instead of a rust string?
|
||||||
let file_path = format!("file://{}", path.clone().into_os_string().to_string_lossy());
|
let file_path = path.clone().into_os_string().to_string_lossy().to_string();
|
||||||
|
|
||||||
let mut window_size = window_size()?;
|
let mut window_size = window_size()?;
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// poppler has some annoying logging (e.g. if you request a page index out-of-bounds of a
|
// poppler has some annoying logging (e.g. if you request a page index out-of-bounds of a
|
||||||
// document's pages, then it will return `None`, but still log to stderr with CRITICAL level),
|
// document's pages, then it will return `None`, but still log to stderr with CRITICAL level),
|
||||||
// so we want to just ignore all logging since this is a tui app.
|
// so we want to just ignore all logging since this is a tui app.
|
||||||
glib::log_set_writer_func(noop);
|
// glib::log_set_writer_func(noop);
|
||||||
|
|
||||||
execute!(
|
execute!(
|
||||||
term.backend_mut(),
|
term.backend_mut(),
|
||||||
@@ -208,7 +208,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
to_converter.send(ConverterMsg::NumPages(num))?;
|
to_converter.send(ConverterMsg::NumPages(num))?;
|
||||||
},
|
},
|
||||||
RenderInfo::Page(info) => {
|
RenderInfo::Page(info) => {
|
||||||
tui.got_num_results_on_page(info.page, info.search_results);
|
tui.got_num_results_on_page(info.page_num, info.search_results);
|
||||||
to_converter.send(ConverterMsg::AddImg(info))?;
|
to_converter.send(ConverterMsg::AddImg(info))?;
|
||||||
},
|
},
|
||||||
RenderInfo::Reloaded => tui.set_msg(MessageSetting::Some(BottomMessage::Reloaded)),
|
RenderInfo::Reloaded => tui.set_msg(MessageSetting::Some(BottomMessage::Reloaded)),
|
||||||
@@ -275,7 +275,7 @@ fn on_notify_ev(
|
|||||||
match ev.kind {
|
match ev.kind {
|
||||||
EventKind::Access(_) => (),
|
EventKind::Access(_) => (),
|
||||||
EventKind::Remove(_) => to_tui_tx
|
EventKind::Remove(_) => to_tui_tx
|
||||||
.send(Err(RenderError::Render("File was deleted".into())))
|
.send(Err(RenderError::Converting("File was deleted".into())))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
// This shouldn't fail to send unless the receiver gets disconnected. If that's
|
// This shouldn't fail to send unless the receiver gets disconnected. If that's
|
||||||
// happened, then like the main thread has panicked or something, so it doesn't matter
|
// happened, then like the main thread has panicked or something, so it doesn't matter
|
||||||
@@ -287,6 +287,6 @@ fn on_notify_ev(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn noop(_: LogLevel, _: &[LogField<'_>]) -> LogWriterOutput {
|
/*fn noop(_: LogLevel, _: &[LogField<'_>]) -> LogWriterOutput {
|
||||||
LogWriterOutput::Handled
|
LogWriterOutput::Handled
|
||||||
}
|
}*/
|
||||||
|
|||||||
+83
-105
@@ -1,10 +1,9 @@
|
|||||||
use std::thread;
|
use std::{thread::sleep, time::Duration};
|
||||||
|
|
||||||
use cairo::{Antialias, Context, Format, Surface};
|
|
||||||
use crossterm::terminal::WindowSize;
|
use crossterm::terminal::WindowSize;
|
||||||
use flume::{Receiver, SendError, Sender, TryRecvError};
|
use flume::{Receiver, SendError, Sender, TryRecvError};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use poppler::{Color, Document, FindFlags, Page, Rectangle, SelectionStyle};
|
use mupdf::{Colorspace, Document, Matrix, Page, Pixmap, TextPageOptions};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
|
||||||
pub enum RenderNotif {
|
pub enum RenderNotif {
|
||||||
@@ -17,10 +16,8 @@ pub enum RenderNotif {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RenderError {
|
pub enum RenderError {
|
||||||
Notify(notify::Error),
|
Notify(notify::Error),
|
||||||
Doc(glib::Error),
|
Doc(mupdf::error::Error),
|
||||||
// Don't like storing an error as a string but it needs to be Send to send to the main thread,
|
Converting(String)
|
||||||
// and it's just going to be shown to the user, so whatever
|
|
||||||
Render(String)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum RenderInfo {
|
pub enum RenderInfo {
|
||||||
@@ -32,14 +29,14 @@ pub enum RenderInfo {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PageInfo {
|
pub struct PageInfo {
|
||||||
pub img_data: ImageData,
|
pub img_data: ImageData,
|
||||||
pub page: usize,
|
pub page_num: usize,
|
||||||
pub search_results: usize
|
pub search_results: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ImageData {
|
pub struct ImageData {
|
||||||
pub data: Vec<u8>,
|
pub pixels: Vec<u8>,
|
||||||
pub area: Rect
|
pub cell_area: Rect
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -71,7 +68,7 @@ pub fn fill_default<T: Default>(vec: &mut Vec<T>, size: usize) {
|
|||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn start_rendering(
|
pub fn start_rendering(
|
||||||
path: &str,
|
path: &str,
|
||||||
mut sender: Sender<Result<RenderInfo, RenderError>>,
|
sender: Sender<Result<RenderInfo, RenderError>>,
|
||||||
receiver: Receiver<RenderNotif>,
|
receiver: Receiver<RenderNotif>,
|
||||||
size: WindowSize
|
size: WindowSize
|
||||||
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
|
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
|
||||||
@@ -95,7 +92,7 @@ pub fn start_rendering(
|
|||||||
let mut stored_doc = None;
|
let mut stored_doc = None;
|
||||||
|
|
||||||
'reload: loop {
|
'reload: loop {
|
||||||
let doc = match Document::from_file(path, None) {
|
let doc = match Document::open(path) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// if there's an error, tell the main loop
|
// if there's an error, tell the main loop
|
||||||
sender.send(Err(RenderError::Doc(e)))?;
|
sender.send(Err(RenderError::Doc(e)))?;
|
||||||
@@ -125,7 +122,17 @@ pub fn start_rendering(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let n_pages = doc.n_pages() as usize;
|
//let n_pages = doc.page_count() as usize;
|
||||||
|
let n_pages = match doc.page_count() {
|
||||||
|
Ok(n) => n as usize,
|
||||||
|
Err(e) => {
|
||||||
|
sender.send(Err(RenderError::Doc(e)))?;
|
||||||
|
// just basic backoff i think
|
||||||
|
sleep(Duration::from_secs(1));
|
||||||
|
continue 'reload;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
sender.send(Ok(RenderInfo::NumPages(n_pages)))?;
|
sender.send(Ok(RenderInfo::NumPages(n_pages)))?;
|
||||||
|
|
||||||
// We're using this vec of bools to indicate which page numbers have already been rendered,
|
// We're using this vec of bools to indicate which page numbers have already been rendered,
|
||||||
@@ -205,8 +212,8 @@ pub fn start_rendering(
|
|||||||
.map(|(idx, p)| (start_point - (idx + 1), p))
|
.map(|(idx, p)| (start_point - (idx + 1), p))
|
||||||
);
|
);
|
||||||
|
|
||||||
let area_w = f64::from(area.width) * f64::from(col_w);
|
let area_w = f32::from(area.width) * f32::from(col_w);
|
||||||
let area_h = f64::from(area.height) * f64::from(col_h);
|
let area_h = f32::from(area.height) * f32::from(col_h);
|
||||||
|
|
||||||
// we go through each page
|
// we go through each page
|
||||||
for (num, rendered) in page_iter {
|
for (num, rendered) in page_iter {
|
||||||
@@ -230,12 +237,12 @@ pub fn start_rendering(
|
|||||||
|
|
||||||
// We know this is in range 'cause we're iterating over it but we still just want
|
// We know this is in range 'cause we're iterating over it but we still just want
|
||||||
// to be safe
|
// to be safe
|
||||||
let Some(page) = doc.page(num as i32) else {
|
let page = match doc.load_page(num as i32) {
|
||||||
sender.send(Err(RenderError::Render(format!(
|
Err(e) => {
|
||||||
"Couldn't get page {num} ({}) of doc?",
|
sender.send(Err(RenderError::Doc(e)))?;
|
||||||
num as i32
|
continue;
|
||||||
))))?;
|
}
|
||||||
continue;
|
Ok(p) => p
|
||||||
};
|
};
|
||||||
|
|
||||||
let rendered_with_no_results =
|
let rendered_with_no_results =
|
||||||
@@ -260,23 +267,31 @@ pub fn start_rendering(
|
|||||||
rendered.contained_term = Some(ctx.num_results > 0);
|
rendered.contained_term = Some(ctx.num_results > 0);
|
||||||
rendered.successful = true;
|
rendered.successful = true;
|
||||||
|
|
||||||
// if this is the page that the user is currently trying to look at, don't
|
let cap = (ctx.pixmap.width()
|
||||||
// bother spawning off a thread to render it to a png - it'll only slow
|
* ctx.pixmap.height() * u32::from(ctx.pixmap.n()))
|
||||||
// down the time til the user can see it (due to the overhead of creating a
|
as usize;
|
||||||
// thread), but we still want to spawn threads to render the other pages
|
let mut pixels = Vec::with_capacity(cap);
|
||||||
// since the effects of parallelizing that will be noticeable if the user
|
if let Err(e) = ctx.pixmap.write_to(&mut pixels, mupdf::ImageFormat::PAM) {
|
||||||
// tries to move through pages more quickly
|
sender.send(Err(RenderError::Doc(e)))?;
|
||||||
if num == start_point {
|
continue;
|
||||||
render_ctx_to_png(&ctx, &mut sender, (col_w, col_h), num)?;
|
};
|
||||||
} else {
|
|
||||||
let mut sender = sender.clone();
|
sender.send(Ok(RenderInfo::Page(PageInfo {
|
||||||
thread::spawn(move || {
|
img_data: ImageData {
|
||||||
render_ctx_to_png(&ctx, &mut sender, (col_w, col_h), num)
|
pixels,
|
||||||
});
|
cell_area: Rect {
|
||||||
}
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: (ctx.surface_w / f32::from(col_w)) as u16,
|
||||||
|
height: (ctx.surface_h / f32::from(col_h)) as u16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
page_num: num,
|
||||||
|
search_results: ctx.num_results
|
||||||
|
})))?;
|
||||||
}
|
}
|
||||||
// And if we got an error, then obviously we need to propagate that
|
// And if we got an error, then obviously we need to propagate that
|
||||||
Err(e) => sender.send(Err(RenderError::Render(e)))?
|
Err(e) => sender.send(Err(RenderError::Doc(e)))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,10 +310,10 @@ pub fn start_rendering(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RenderedContext {
|
struct RenderedContext {
|
||||||
surface: Surface,
|
pixmap: Pixmap,
|
||||||
num_results: usize,
|
surface_w: f32,
|
||||||
surface_width: f64,
|
surface_h: f32,
|
||||||
surface_height: f64
|
num_results: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SAFETY: I think this is safe because, although the backing struct for `Surface` does contain
|
/// SAFETY: I think this is safe because, although the backing struct for `Surface` does contain
|
||||||
@@ -317,11 +332,15 @@ 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,
|
||||||
(area_w, area_h): (f64, f64)
|
(area_w, area_h): (f32, f32)
|
||||||
) -> Result<Option<RenderedContext>, String> {
|
) -> Result<Option<RenderedContext>, mupdf::error::Error> {
|
||||||
let mut result_rects = search_term
|
let result_rects = search_term
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|term| page.find_text_with_options(term, FindFlags::DEFAULT | FindFlags::MULTILINE))
|
.map(|term| {
|
||||||
|
page.to_text_page(TextPageOptions::all())?
|
||||||
|
.search(term, u32::MAX)
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// If there are no search terms on this page, and we've already rendered it with no search
|
// If there are no search terms on this page, and we've already rendered it with no search
|
||||||
@@ -331,7 +350,11 @@ fn render_single_page_to_ctx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// then, get the size of the page
|
// then, get the size of the page
|
||||||
let (p_width, p_height) = page.size();
|
let bounds = page.bounds()?;
|
||||||
|
let (p_width, p_height) = (
|
||||||
|
f32::from(bounds.x1 - bounds.x0),
|
||||||
|
f32::from(bounds.y1 - bounds.y0)
|
||||||
|
);
|
||||||
|
|
||||||
// and get its aspect ratio
|
// and get its aspect ratio
|
||||||
let p_aspect_ratio = p_width / p_height;
|
let p_aspect_ratio = p_width / p_height;
|
||||||
@@ -353,39 +376,22 @@ fn render_single_page_to_ctx(
|
|||||||
area_h / p_height
|
area_h / p_height
|
||||||
};
|
};
|
||||||
|
|
||||||
let surface_width = p_width * scale_factor;
|
let surface_w = p_width * scale_factor;
|
||||||
let surface_height = p_height * scale_factor;
|
let surface_h = p_height * scale_factor;
|
||||||
|
|
||||||
let surface = cairo::ImageSurface::create(
|
let colorspace = Colorspace::device_rgb();
|
||||||
Format::Rgb16_565,
|
let matrix = Matrix::new_scale(scale_factor, scale_factor);
|
||||||
// No matter how big you make these arguments, the image will be drawn at the same
|
|
||||||
// size. So if you make them really big, the image will be drawn on a quarter of it. If
|
|
||||||
// you make them really small, the image will cover more than all of the surface.
|
|
||||||
//
|
|
||||||
// However, that only stands as long as you don't scale the context that you place this
|
|
||||||
// surface into. If you scale the dimensions of this image by n, then scale the context
|
|
||||||
// by that same amount, then it'll still fit perfectly into the context, but be
|
|
||||||
// rendered at higher quality.
|
|
||||||
surface_width as i32,
|
|
||||||
surface_height as i32
|
|
||||||
)
|
|
||||||
.map_err(|e| format!("Couldn't create ImageSurface: {e}"))?;
|
|
||||||
surface.set_device_scale(scale_factor, scale_factor);
|
|
||||||
|
|
||||||
let ctx = Context::new(surface).map_err(|e| format!("Couldn't create Context: {e}"))?;
|
let mut pixmap = page.to_pixmap(&matrix, &colorspace, 0.0, false)?;
|
||||||
|
|
||||||
// The default background color of PDFs (at least, I think) is white, so we need to set
|
let (x_res, y_res) = pixmap.resolution();
|
||||||
// that as the background color, then paint, then render.
|
let new_x = (x_res as f32 * scale_factor) as i32;
|
||||||
ctx.set_source_rgba(1.0, 1.0, 1.0, 1.0);
|
let new_y = (y_res as f32 * scale_factor) as i32;
|
||||||
|
pixmap.set_resolution(new_x, new_y);
|
||||||
ctx.set_antialias(Antialias::None);
|
|
||||||
ctx.paint()
|
|
||||||
.map_err(|e| format!("Couldn't paint Context: {e}"))?;
|
|
||||||
page.render(&ctx);
|
|
||||||
|
|
||||||
let num_results = result_rects.len();
|
let num_results = result_rects.len();
|
||||||
|
|
||||||
if !result_rects.is_empty() {
|
/*if !result_rects.is_empty() {
|
||||||
let mut highlight_color = Color::new();
|
let mut highlight_color = Color::new();
|
||||||
highlight_color.set_red((u16::MAX / 5) * 4);
|
highlight_color.set_red((u16::MAX / 5) * 4);
|
||||||
highlight_color.set_green((u16::MAX / 5) * 4);
|
highlight_color.set_green((u16::MAX / 5) * 4);
|
||||||
@@ -406,40 +412,12 @@ fn render_single_page_to_ctx(
|
|||||||
&mut highlight_color
|
&mut highlight_color
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
Ok(Some(RenderedContext {
|
Ok(Some(RenderedContext {
|
||||||
surface: ctx.target(),
|
pixmap,
|
||||||
num_results,
|
surface_w,
|
||||||
surface_width,
|
surface_h,
|
||||||
surface_height
|
num_results
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_ctx_to_png(
|
|
||||||
ctx: &RenderedContext,
|
|
||||||
sender: &mut Sender<Result<RenderInfo, RenderError>>,
|
|
||||||
(col_w, col_h): (u16, u16),
|
|
||||||
page: usize
|
|
||||||
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
|
|
||||||
let mut img_data = Vec::with_capacity((ctx.surface_height * ctx.surface_width) as usize);
|
|
||||||
|
|
||||||
match ctx.surface.write_to_png(&mut img_data) {
|
|
||||||
Err(e) => sender.send(Err(RenderError::Render(format!(
|
|
||||||
"Couldn't write surface to png: {e}"
|
|
||||||
)))),
|
|
||||||
Ok(()) => sender.send(Ok(RenderInfo::Page(PageInfo {
|
|
||||||
img_data: ImageData {
|
|
||||||
data: img_data,
|
|
||||||
area: Rect {
|
|
||||||
width: ctx.surface_width as u16 / col_w,
|
|
||||||
height: ctx.surface_height as u16 / col_h,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
page,
|
|
||||||
search_results: ctx.num_results
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -570,7 +570,7 @@ impl Tui {
|
|||||||
self.set_msg(MessageSetting::Some(BottomMessage::Error(match err {
|
self.set_msg(MessageSetting::Some(BottomMessage::Error(match err {
|
||||||
RenderError::Notify(e) => format!("Auto-reload failed: {e}"),
|
RenderError::Notify(e) => format!("Auto-reload failed: {e}"),
|
||||||
RenderError::Doc(e) => format!("Couldn't open document: {e}"),
|
RenderError::Doc(e) => format!("Couldn't open document: {e}"),
|
||||||
RenderError::Render(e) => format!("Couldn't render page: {e}")
|
RenderError::Converting(e) => format!("Couldn't convert page after rendering: {e}")
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user