mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-01 23:51:46 -04:00
Custom Colors (#70)
* First implementation of custom colors * Remove use-statement Co-authored-by: June <61218022+itsjunetime@users.noreply.github.com> * Cleaned up help-text Co-authored-by: June <61218022+itsjunetime@users.noreply.github.com> * Removed superfluous features from csscolorparser * Fix for clippy * Clarify how to pass in custom colors * Explicitly install clippy and rustfmt in CI * Better error handling when colors can not be parsed Co-authored-by: June <61218022+itsjunetime@users.noreply.github.com> * More elegant type conversion Co-authored-by: June <61218022+itsjunetime@users.noreply.github.com> * Made clippy happy --------- Co-authored-by: June <61218022+itsjunetime@users.noreply.github.com> Co-authored-by: itsjunetime <junewelker@gmail.com>
This commit is contained in:
@@ -28,6 +28,8 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libfontconfig1-dev libgoogle-perftools-dev google-perftools
|
sudo apt-get install -y libfontconfig1-dev libgoogle-perftools-dev google-perftools
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install clippy and fmt
|
||||||
|
run: rustup component add clippy rustfmt
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy -- -D warnings
|
run: cargo clippy -- -D warnings
|
||||||
- name: Check fmt
|
- name: Check fmt
|
||||||
|
|||||||
Generated
+11
-1
@@ -711,6 +711,15 @@ dependencies = [
|
|||||||
"phf",
|
"phf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csscolorparser"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288"
|
||||||
|
dependencies = [
|
||||||
|
"phf",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.11"
|
version = "0.20.11"
|
||||||
@@ -2866,6 +2875,7 @@ dependencies = [
|
|||||||
"cpuprofiler",
|
"cpuprofiler",
|
||||||
"criterion",
|
"criterion",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"csscolorparser 0.7.0",
|
||||||
"flume",
|
"flume",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"image",
|
"image",
|
||||||
@@ -3473,7 +3483,7 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
|
checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csscolorparser",
|
"csscolorparser 0.6.2",
|
||||||
"deltae",
|
"deltae",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"wezterm-dynamic",
|
"wezterm-dynamic",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ rayon = { version = "*", default-features = false }
|
|||||||
|
|
||||||
# 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 }
|
||||||
|
csscolorparser = { version = "0.7.0" }
|
||||||
|
|
||||||
[profile.production]
|
[profile.production]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ async fn main() {
|
|||||||
.nth(1)
|
.nth(1)
|
||||||
.expect("Please enter a file to profile");
|
.expect("Please enter a file to profile");
|
||||||
|
|
||||||
utils::render_doc(file, None).await;
|
utils::render_doc(file, None, 0, 1000).await;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-9
@@ -23,11 +23,14 @@ const FILES: [&str; 3] = [
|
|||||||
"benches/geotopo.pdf"
|
"benches/geotopo.pdf"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const BLACK: i32 = 0;
|
||||||
|
const WHITE: i32 = 1000;
|
||||||
|
|
||||||
fn render_full(c: &mut Criterion) {
|
fn render_full(c: &mut Criterion) {
|
||||||
for file in FILES {
|
for file in FILES {
|
||||||
c.bench_with_input(BenchmarkId::new("render_full", file), &file, |b, &file| {
|
c.bench_with_input(BenchmarkId::new("render_full", file), &file, |b, &file| {
|
||||||
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||||
.iter(|| render_doc(file, None))
|
.iter(|| render_doc(file, None, BLACK, WHITE))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +42,7 @@ fn render_to_first_page(c: &mut Criterion) {
|
|||||||
&file,
|
&file,
|
||||||
|b, &file| {
|
|b, &file| {
|
||||||
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||||
.iter(|| render_first_page(file))
|
.iter(|| render_first_page(file, BLACK, WHITE))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -48,7 +51,7 @@ fn render_to_first_page(c: &mut Criterion) {
|
|||||||
fn only_converting(c: &mut Criterion) {
|
fn only_converting(c: &mut Criterion) {
|
||||||
for file in FILES {
|
for file in FILES {
|
||||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||||
let all_rendered = runtime.block_on(render_all_files(file));
|
let all_rendered = runtime.block_on(render_all_files(file, BLACK, WHITE));
|
||||||
|
|
||||||
c.bench_with_input(
|
c.bench_with_input(
|
||||||
BenchmarkId::new("only_converting", file),
|
BenchmarkId::new("only_converting", file),
|
||||||
@@ -68,7 +71,7 @@ fn search_short_common(c: &mut Criterion) {
|
|||||||
&file,
|
&file,
|
||||||
|b, &file| {
|
|b, &file| {
|
||||||
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||||
.iter(|| render_doc(file, Some("an")))
|
.iter(|| render_doc(file, Some("an"), BLACK, WHITE))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -81,20 +84,20 @@ fn search_long_rare(c: &mut Criterion) {
|
|||||||
&file,
|
&file,
|
||||||
|b, &file| {
|
|b, &file| {
|
||||||
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||||
.iter(|| render_doc(file, Some("this is long and rare")))
|
.iter(|| render_doc(file, Some("this is long and rare"), BLACK, WHITE))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_first_page(path: impl AsRef<Path>) {
|
pub async fn render_first_page(path: impl AsRef<Path>, black: i32, white: i32) {
|
||||||
let RenderState {
|
let RenderState {
|
||||||
mut from_render_rx,
|
mut from_render_rx,
|
||||||
mut from_converter_rx,
|
mut from_converter_rx,
|
||||||
mut pages,
|
mut pages,
|
||||||
mut to_converter_tx,
|
mut to_converter_tx,
|
||||||
to_render_tx
|
to_render_tx
|
||||||
} = start_all_rendering(path);
|
} = start_all_rendering(path, black, white);
|
||||||
|
|
||||||
// we only want to render until the first page is ready to be printed
|
// we only want to render until the first page is ready to be printed
|
||||||
while pages.iter().all(Option::is_none) {
|
while pages.iter().all(Option::is_none) {
|
||||||
@@ -114,8 +117,8 @@ pub async fn render_first_page(path: impl AsRef<Path>) {
|
|||||||
drop(to_render_tx);
|
drop(to_render_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render_all_files(path: &'static str) -> Vec<PageInfo> {
|
async fn render_all_files(path: &'static str, black: i32, white: i32) -> Vec<PageInfo> {
|
||||||
let (mut from_render_rx, to_render_tx) = start_rendering_loop(path);
|
let (mut from_render_rx, to_render_tx) = start_rendering_loop(path, black, white);
|
||||||
let mut pages = Vec::<Option<PageInfo>>::new();
|
let mut pages = Vec::<Option<PageInfo>>::new();
|
||||||
|
|
||||||
while let Some(info) = from_render_rx.next().await {
|
while let Some(info) = from_render_rx.next().await {
|
||||||
|
|||||||
+10
-6
@@ -57,7 +57,9 @@ pub struct RenderState {
|
|||||||
const FONT_SIZE: (u16, u16) = (8, 14);
|
const FONT_SIZE: (u16, u16) = (8, 14);
|
||||||
|
|
||||||
pub fn start_rendering_loop(
|
pub fn start_rendering_loop(
|
||||||
path: impl AsRef<Path>
|
path: impl AsRef<Path>,
|
||||||
|
black: i32,
|
||||||
|
white: i32
|
||||||
) -> (
|
) -> (
|
||||||
RecvStream<'static, Result<RenderInfo, RenderError>>,
|
RecvStream<'static, Result<RenderInfo, RenderError>>,
|
||||||
Sender<RenderNotif>
|
Sender<RenderNotif>
|
||||||
@@ -91,7 +93,9 @@ pub fn start_rendering_loop(
|
|||||||
to_main_tx,
|
to_main_tx,
|
||||||
from_main_rx,
|
from_main_rx,
|
||||||
size,
|
size,
|
||||||
tdf::PrerenderLimit::All
|
tdf::PrerenderLimit::All,
|
||||||
|
black,
|
||||||
|
white
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,8 +126,8 @@ pub fn start_converting_loop(
|
|||||||
(from_converter_rx, to_converter_tx)
|
(from_converter_rx, to_converter_tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_all_rendering(path: impl AsRef<Path>) -> RenderState {
|
pub fn start_all_rendering(path: impl AsRef<Path>, black: i32, white: i32) -> RenderState {
|
||||||
let (from_render_rx, to_render_tx) = start_rendering_loop(path);
|
let (from_render_rx, to_render_tx) = start_rendering_loop(path, black, white);
|
||||||
let (from_converter_rx, to_converter_tx) = start_converting_loop(20);
|
let (from_converter_rx, to_converter_tx) = start_converting_loop(20);
|
||||||
|
|
||||||
let pages: Vec<Option<ConvertedPage>> = Vec::new();
|
let pages: Vec<Option<ConvertedPage>> = Vec::new();
|
||||||
@@ -137,14 +141,14 @@ pub fn start_all_rendering(path: impl AsRef<Path>) -> RenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_doc(path: impl AsRef<Path>, search_term: Option<&str>) {
|
pub async fn render_doc(path: impl AsRef<Path>, search_term: Option<&str>, black: i32, white: i32) {
|
||||||
let RenderState {
|
let RenderState {
|
||||||
mut from_render_rx,
|
mut from_render_rx,
|
||||||
mut from_converter_rx,
|
mut from_converter_rx,
|
||||||
mut pages,
|
mut pages,
|
||||||
mut to_converter_tx,
|
mut to_converter_tx,
|
||||||
to_render_tx
|
to_render_tx
|
||||||
} = start_all_rendering(path);
|
} = start_all_rendering(path, black, white);
|
||||||
|
|
||||||
if let Some(term) = search_term {
|
if let Some(term) = search_term {
|
||||||
to_render_tx
|
to_render_tx
|
||||||
|
|||||||
+29
-1
@@ -51,11 +51,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
/// 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
|
||||||
|
/// Custom white color, specified in css format (e.g. "FFFFFF" or "rgb(255, 255, 255)")
|
||||||
|
optional -w,--white-color white: String
|
||||||
|
/// Custom black color, specified in css format (e.g "000000" or "rgb(0, 0, 0)")
|
||||||
|
optional -b,--black-color black: String
|
||||||
/// PDF file to read
|
/// PDF file to read
|
||||||
required file: PathBuf
|
required file: PathBuf
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = flags.file.canonicalize()?;
|
let path = flags.file.canonicalize()?;
|
||||||
|
let black = parse_color_to_i32(&flags.black_color.unwrap_or("000000".into())).map_err(|e| {
|
||||||
|
BadTermSizeStdin(format!(
|
||||||
|
"Couldn't parse black color: {e} - is it formatted like a CSS color?"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let white = parse_color_to_i32(&flags.white_color.unwrap_or("FFFFFF".into())).map_err(|e| {
|
||||||
|
BadTermSizeStdin(format!(
|
||||||
|
"Couldn't parse while color: {e} - is it formatted like a CSS color?"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
let (watch_to_render_tx, render_rx) = flume::unbounded();
|
let (watch_to_render_tx, render_rx) = flume::unbounded();
|
||||||
let tui_tx = watch_to_render_tx.clone();
|
let tui_tx = watch_to_render_tx.clone();
|
||||||
@@ -141,7 +156,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.and_then(NonZeroUsize::new)
|
.and_then(NonZeroUsize::new)
|
||||||
.map_or(PrerenderLimit::All, PrerenderLimit::Limited);
|
.map_or(PrerenderLimit::All, PrerenderLimit::Limited);
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
renderer::start_rendering(&file_path, render_tx, render_rx, window_size, prerender)
|
renderer::start_rendering(
|
||||||
|
&file_path,
|
||||||
|
render_tx,
|
||||||
|
render_rx,
|
||||||
|
window_size,
|
||||||
|
prerender,
|
||||||
|
black,
|
||||||
|
white
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut ev_stream = crossterm::event::EventStream::new();
|
let mut ev_stream = crossterm::event::EventStream::new();
|
||||||
@@ -286,3 +309,8 @@ fn on_notify_ev(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn parse_color_to_i32(cs: &str) -> Result<i32, csscolorparser::ParseColorError> {
|
||||||
|
let color = csscolorparser::parse(cs)?;
|
||||||
|
let [r, g, b, _] = color.to_rgba8();
|
||||||
|
Ok(i32::from_be_bytes([0, r, g, b]))
|
||||||
|
}
|
||||||
|
|||||||
+10
-2
@@ -76,7 +76,9 @@ pub fn start_rendering(
|
|||||||
sender: Sender<Result<RenderInfo, RenderError>>,
|
sender: Sender<Result<RenderInfo, RenderError>>,
|
||||||
receiver: Receiver<RenderNotif>,
|
receiver: Receiver<RenderNotif>,
|
||||||
size: WindowSize,
|
size: WindowSize,
|
||||||
prerender: PrerenderLimit
|
prerender: PrerenderLimit,
|
||||||
|
black: i32,
|
||||||
|
white: i32
|
||||||
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
|
) -> Result<(), SendError<Result<RenderInfo, RenderError>>> {
|
||||||
// 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
|
||||||
@@ -282,6 +284,8 @@ pub fn start_rendering(
|
|||||||
search_term.as_deref(),
|
search_term.as_deref(),
|
||||||
rendered,
|
rendered,
|
||||||
invert,
|
invert,
|
||||||
|
black,
|
||||||
|
white,
|
||||||
(area_w, area_h)
|
(area_w, area_h)
|
||||||
) {
|
) {
|
||||||
// If that fn returned Some, that means it needed to be re-rendered for some
|
// If that fn returned Some, that means it needed to be re-rendered for some
|
||||||
@@ -421,6 +425,8 @@ fn render_single_page_to_ctx(
|
|||||||
search_term: Option<&str>,
|
search_term: Option<&str>,
|
||||||
prev_render: &PrevRender,
|
prev_render: &PrevRender,
|
||||||
invert: bool,
|
invert: bool,
|
||||||
|
black: i32,
|
||||||
|
white: i32,
|
||||||
(area_w, area_h): (f32, f32)
|
(area_w, area_h): (f32, f32)
|
||||||
) -> Result<RenderedContext, mupdf::error::Error> {
|
) -> Result<RenderedContext, mupdf::error::Error> {
|
||||||
let result_rects = match prev_render.num_search_found {
|
let result_rects = match prev_render.num_search_found {
|
||||||
@@ -461,7 +467,9 @@ fn render_single_page_to_ctx(
|
|||||||
|
|
||||||
let mut pixmap = page.to_pixmap(&matrix, &colorspace, false, false)?;
|
let mut pixmap = page.to_pixmap(&matrix, &colorspace, false, false)?;
|
||||||
if invert {
|
if invert {
|
||||||
pixmap.invert()?;
|
pixmap.tint(white, black)?;
|
||||||
|
} else {
|
||||||
|
pixmap.tint(black, white)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (x_res, y_res) = pixmap.resolution();
|
let (x_res, y_res) = pixmap.resolution();
|
||||||
|
|||||||
Reference in New Issue
Block a user