use fs::File;
use fs_err as fs;
use std::path::Path;
use std::process::{Command, Stdio};
use std::{io::Write, str, usize};
use sha2::{Digest, Sha256};
use crate::error::{Error, Result};
pub fn hash(input: &str) -> String {
let mut sh = Sha256::new();
sh.update(input.as_bytes());
let mut out = format!("{:x}", sh.finalize());
out.truncate(24);
out
}
fn find_binary(name: &str) -> Result<std::path::PathBuf> {
which::which(name).map_err(|error| Error::BinaryNotFound {
binary: name.to_owned(),
error,
})
}
pub fn generate_svg_from_latex(path: &Path, zoom: f32) -> Result<()> {
let dest_path = path.parent().expect("Parent path must exist. qed");
let file: &Path = path.file_name().unwrap().as_ref();
let dvi_path = path.with_extension("dvi");
if !dvi_path.exists() {
let latex_path = find_binary("latex")?;
let cmd = Command::new(latex_path)
.current_dir(&dest_path)
.arg(&file.with_extension("tex"))
.output()
.expect("Could not spawn latex");
if !cmd.status.success() {
let buf = String::from_utf8_lossy(&cmd.stdout);
if buf.is_empty() {
let buf = String::from_utf8_lossy(&cmd.stderr);
panic!("Latex exited with `{}`", buf);
}
let err = buf
.split("\n")
.filter(|x| {
(x.starts_with("! ") || x.starts_with("l.")) && !x.contains("Emergency stop")
})
.fold(("", "", usize::MAX), |mut err, elm| {
if elm.starts_with("! ") {
err.0 = elm;
} else if elm.starts_with("l.") {
let mut elms = elm[2..].splitn(2, " ").map(|x| x.trim());
if let Some(Ok(val)) = elms.next().map(|x| x.parse::<usize>()) {
err.2 = val;
}
if let Some(val) = elms.next() {
err.1 = val;
}
}
err
});
return Err(Error::InvalidMath(
err.0.to_string(),
err.1.to_string(),
err.2,
));
}
}
let svg_path = path.with_extension("svg");
if !svg_path.exists() && dvi_path.exists() {
let dvisvgm_path = find_binary("dvisvgm")?;
let cmd = Command::new(dvisvgm_path)
.current_dir(&dest_path)
.arg("-b")
.arg("1")
.arg("--font-format=woff")
.arg(&format!("--zoom={}", zoom))
.arg(&dvi_path)
.output()
.expect("Couldn't run svisvgm properly!");
let buf = String::from_utf8_lossy(&cmd.stderr);
if !cmd.status.success() || buf.contains("error:") {
return Err(Error::InvalidDvisvgm(buf.to_string()));
}
}
Ok(())
}
fn generate_latex_from_gnuplot(dest_path: &Path, content: &str, filename: &str) -> Result<()> {
let gnuplot_path = find_binary("gnuplot")?;
let cmd = Command::new(gnuplot_path)
.stdin(Stdio::piped())
.current_dir(dest_path)
.arg("-p")
.spawn()?;
let mut stdin = cmd.stdin.expect("Stdin of gnuplot spawn must exist. qed");
stdin
.write_all(&format!("set output '{}.tex'\n", filename).as_bytes())
.map_err(|err| Error::Io(err))?;
stdin
.write_all("set terminal epslatex color standalone\n".as_bytes())
.map_err(|err| Error::Io(err))?;
stdin
.write_all(content.as_bytes())
.map_err(|err| Error::Io(err))?;
Ok(())
}
pub fn parse_equation(dest_path: &Path, content: &str, zoom: f32) -> Result<String> {
let name = hash(content);
let path = dest_path.join(&name);
if !path.with_extension("tex").exists() {
let mut file = File::create(path.with_extension("tex")).map_err(|err| Error::Io(err))?;
file.write_all(
r####"
\documentclass[20pt, preview]{standalone}
\usepackage{amsmath}
\usepackage{amsfonts}
\begin{document}
$$"####
.as_bytes(),
)?;
file.write_all(content.as_bytes())?;
file.write_all(
r####"$$
\end{document}
"####
.as_bytes(),
)?;
}
generate_svg_from_latex(&path, zoom)?;
Ok(name + ".svg")
}
pub fn parse_latex(dest_path: &Path, content: &str) -> Result<String> {
let name = hash(content);
let path = dest_path.join(&name);
if !path.with_extension("tex").exists() {
let mut file = File::create(path.with_extension("tex"))?;
file.write_all(content.as_bytes())?;
}
generate_svg_from_latex(&path, 1.0)?;
Ok(name + ".svg")
}
pub fn parse_gnuplot(dest_path: &Path, content: &str) -> Result<String> {
let name = hash(content);
let path = dest_path.join(&name);
if !path.with_extension("tex").exists() {
generate_latex_from_gnuplot(dest_path, content, &name)?;
}
if !path.with_extension("svg").exists() {
generate_svg_from_latex(&path, 1.0)?;
}
Ok(name + ".svg")
}
pub fn parse_gnuplot_only(dest_path: &Path, content: &str) -> Result<String> {
let name = hash(content);
let path = dest_path.join(&name);
if !path.with_extension("svg").exists() {
let gnuplot_path = find_binary("gnuplot")?;
let cmd = Command::new(gnuplot_path)
.stdin(Stdio::piped())
.current_dir(dest_path)
.arg("-p")
.spawn()
.unwrap();
let mut stdin = cmd.stdin.unwrap();
stdin
.write_all(&format!("set output '{}.svg'\n", name).as_bytes())
.map_err(|err| Error::Io(err))?;
stdin
.write_all("set terminal svg\n".as_bytes())
.map_err(|err| Error::Io(err))?;
stdin
.write_all("set encoding utf8\n".as_bytes())
.map_err(|err| Error::Io(err))?;
stdin
.write_all(content.as_bytes())
.map_err(|err| Error::Io(err))?;
}
Ok(format!("{}.svg", name))
}
pub fn bib_to_html(source: &str, bib2xhtml: &str) -> Result<String> {
let source = fs::canonicalize(source).unwrap();
let bib2xhtml = Path::new(bib2xhtml);
let cmd = Command::new(bib2xhtml.join("./bib2xhtml.pl"))
.current_dir(bib2xhtml)
.args(&["-s", "alpha", "-u", "-U"])
.arg(source)
.output()
.expect("Could not spawn bib2xhtml");
let buf = String::from_utf8_lossy(&cmd.stdout);
let err_str = String::from_utf8_lossy(&cmd.stderr);
if err_str.contains("error messages)") {
Err(Error::InvalidBibliography(err_str.to_string()))
} else {
let buf = buf
.split("\n")
.skip_while(|x| *x != "<dl class=\"bib2xhtml\">")
.take_while(|x| *x != "</dl>")
.map(|x| x.replace("<a name=\"", "<a id=\""))
.collect();
Ok(buf)
}
}