extern crate sdl2;
extern crate tempfile;
use std::fs::{create_dir, remove_dir_all, File};
use std::io::{Error, ErrorKind, Result as IResult, Write};
use std::mem::drop;
use std::path::Path;
use std::process::{exit, Command};
use std::sync::Mutex;
use std::time::Instant;
use image::PngImage;
use tempfile::tempdir;
const LATEX_PRELUDE: &str = include_str!("latex_prelude.tex");
const LATEX_POSTLUDE: &str = "\\end{document}";
#[derive(Debug, PartialEq)]
pub enum LatexError {
NotExisting,
NotLoaded,
}
pub struct LatexIdx(usize);
lazy_static! {
static ref EQUATIONS: Mutex<Vec<(&'static str, bool, Option<PngImage>)>> =
Mutex::new(Vec::new());
static ref PRELUDE: Mutex<Vec<&'static str>> = Mutex::new(Vec::new());
}
pub fn register_equation(equation: &'static str, is_text: bool) -> LatexIdx {
if let Ok(ref mut eqs) = EQUATIONS.lock() {
let idx = eqs.len();
eqs.push((equation, is_text, None));
LatexIdx(idx)
} else {
panic!("Can't eqs");
}
}
pub fn add_prelude(prelude: &'static str) {
if let Ok(ref mut preludes) = PRELUDE.lock() {
preludes.push(prelude);
}
}
pub fn read_image(idx: LatexIdx) -> Result<PngImage, LatexError> {
let res = if let Ok(ref mut eqs) = EQUATIONS.lock() {
if let Some(ref mut x) = eqs.get_mut(idx.0) {
if x.2.is_some() {
Ok(x.2.take().unwrap())
} else {
Err(LatexError::NotLoaded)
}
} else {
Err(LatexError::NotExisting)
}
} else {
Err(LatexError::NotLoaded)
};
drop(idx);
res
}
pub fn render_all_equations() -> IResult<()> {
if let Ok(eqs) = EQUATIONS.lock() {
if eqs.len() == 0 {
return Ok(());
}
}
let fallback = Path::new("/tmp/ytesrev").to_path_buf();
let path = tempdir().map(|x| x.into_path()).unwrap_or(fallback);
eprintln!("Rendering in {}", path.display());
if path.exists() {
remove_dir_all(path.clone())?;
}
create_dir(path.clone())?;
let mut tex_path = path.clone();
tex_path.push("tmp.tex");
let mut pdf_path = path.clone();
pdf_path.push("tmp.pdf");
let mut raw_path = path.clone();
raw_path.push("tmp-res");
let start = Instant::now();
create_tex(&tex_path)?;
render_tex(&tex_path, &pdf_path, &raw_path)?;
read_pngs(&path)?;
let diff = Instant::now() - start;
eprintln!("Rendering took {:.2?}", diff);
Ok(())
}
fn create_tex(tex_path: &Path) -> IResult<()> {
let mut tex_file = File::create(tex_path)?;
let mut added_prelude = String::new();
if let Ok(prelude) = PRELUDE.lock() {
prelude.iter().for_each(|prelude| {
added_prelude.push_str(prelude);
added_prelude.push('\n');
});
}
writeln!(
tex_file,
"{}",
LATEX_PRELUDE.replace("$PRELUDE", &added_prelude)
)?;
if let Ok(eqs) = EQUATIONS.lock() {
for equation in eqs.iter() {
for col in &["red", "blue"] {
writeln!(tex_file, "\\begin{{equation*}}")?;
writeln!(tex_file, "\\colorbox{{{}}}{{\\makebox[\\linewidth]{{", col)?;
if equation.1 {
writeln!(tex_file, "{}", equation.0)?;
} else {
writeln!(tex_file, "$ {} $", equation.0)?;
}
writeln!(tex_file, "}} }}")?;
writeln!(tex_file, "\\end{{equation*}}")?;
}
}
}
writeln!(tex_file, "{}", LATEX_POSTLUDE)?;
Ok(())
}
fn render_tex(tex_path: &Path, pdf_path: &Path, raw_path: &Path) -> IResult<()> {
let out = Command::new("pdflatex")
.current_dir(tex_path.parent().unwrap())
.arg(tex_path.file_name().unwrap())
.output()
.expect("Can't make command");
if !out.status.success() {
eprintln!("Latex compile error:");
eprintln!("{}", String::from_utf8_lossy(&out.stderr));
exit(1);
}
let out = Command::new("pdftoppm")
.arg(pdf_path.as_os_str())
.arg(raw_path.as_os_str())
.arg("-r")
.arg("250")
.arg("-png")
.output()
.expect("Can't make command");
if !out.status.success() {
eprintln!("pdftoppm error");
eprintln!("{}", String::from_utf8_lossy(&out.stderr));
exit(1);
}
Ok(())
}
fn read_pngs(path: &Path) -> IResult<()> {
if let Ok(ref mut eqs) = EQUATIONS.lock() {
let digits_max = format!("{}", eqs.len()).len();
for (i, (_, _, ref mut im)) in eqs.iter_mut().enumerate() {
let num_red = zero_pad(format!("{}", 2 * i + 1), digits_max);
let num_blue = zero_pad(format!("{}", 2 * i + 2), digits_max);
let mut img_path_red = path.to_path_buf();
img_path_red.push(format!("tmp-res-{}.png", num_red));
let mut img_path_blue = path.to_path_buf();
img_path_blue.push(format!("tmp-res-{}.png", num_blue));
let mut im_red_res = PngImage::load_from_path(File::open(img_path_red)?)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
let im_blue = PngImage::load_from_path(File::open(img_path_blue)?)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
let mut maxx = 0;
let mut maxy = 0;
let mut minx = im_red_res.width;
let mut miny = im_red_res.height;
for i in 0..im_red_res.width * im_red_res.height {
let x = i % im_red_res.width;
let y = i / im_red_res.width;
let rr = im_red_res.data[4 * i];
let rg = im_red_res.data[4 * i];
let rb = im_red_res.data[4 * i + 2];
let br = im_blue.data[4 * i];
let bb = im_blue.data[4 * i + 2];
let rdiff = rr as i16 - br as i16;
let bdiff = bb as i16 - rb as i16;
let alpha = 255 - (rdiff + bdiff) / 2;
let alpha = alpha.min(255).max(0) as u8;
im_red_res.data[4 * i] = br;
im_red_res.data[4 * i + 2] = rb;
im_red_res.data[4 * i + 3] = alpha;
if (br < 250 || rg < 250 || rb < 250) && alpha > 250 {
maxx = maxx.max(x + 1);
maxy = maxy.max(y + 1);
minx = minx.min(x);
miny = miny.min(y);
}
}
maxx = (maxx + 3).min(im_red_res.width - 1);
maxy = (maxy + 3).min(im_red_res.height - 1);
minx = minx.saturating_sub(3);
miny = miny.saturating_sub(3);
let width = maxx - minx;
let height = maxy - miny;
let mut resdata = vec![0; 4 * width * height];
for x in 0..width {
for y in 0..height {
let i_r = y * width + x;
let i_l = (y + miny) * im_red_res.width + x + minx;
resdata[4 * i_r] = im_red_res.data[4 * i_l];
resdata[4 * i_r + 1] = im_red_res.data[4 * i_l + 1];
resdata[4 * i_r + 2] = im_red_res.data[4 * i_l + 2];
resdata[4 * i_r + 3] = im_red_res.data[4 * i_l + 3];
}
}
*im = Some(PngImage {
data: resdata,
width,
height,
});
}
}
Ok(())
}
fn zero_pad(n: String, len: usize) -> String {
let needed = len.saturating_sub(n.len());
let mut res = (0..needed).map(|_| '0').collect::<String>();
res.push_str(&n);
res
}