1extern crate sdl2;
18extern crate tempfile;
19
20use std::fs::{create_dir, remove_dir_all, File};
21use std::io::{Error, ErrorKind, Result as IResult, Write};
22use std::mem::drop;
23use std::path::Path;
24use std::process::{exit, Command};
25use std::sync::Mutex;
26use std::time::Instant;
27
28use image::PngImage;
29use tempfile::tempdir;
30
31const LATEX_PRELUDE: &str = include_str!("latex_prelude.tex");
32const LATEX_POSTLUDE: &str = "\\end{document}";
33
34#[derive(Debug, PartialEq)]
36pub enum LatexError {
37 NotExisting,
42 NotLoaded,
44}
45
46pub struct LatexIdx(usize);
52
53lazy_static! {
54 static ref EQUATIONS: Mutex<Vec<(&'static str, bool, Option<PngImage>)>> =
55 Mutex::new(Vec::new());
56 static ref PRELUDE: Mutex<Vec<&'static str>> = Mutex::new(Vec::new());
57}
58
59pub fn register_equation(equation: &'static str, is_text: bool) -> LatexIdx {
74 if let Ok(ref mut eqs) = EQUATIONS.lock() {
75 let idx = eqs.len();
76 eqs.push((equation, is_text, None));
77 LatexIdx(idx)
78 } else {
79 panic!("Can't eqs");
80 }
81}
82
83pub fn add_prelude(prelude: &'static str) {
94 if let Ok(ref mut preludes) = PRELUDE.lock() {
95 preludes.push(prelude);
96 }
97 }
99
100pub fn read_image(idx: LatexIdx) -> Result<PngImage, LatexError> {
102 let res = if let Ok(ref mut eqs) = EQUATIONS.lock() {
103 if let Some(ref mut x) = eqs.get_mut(idx.0) {
104 if x.2.is_some() {
105 Ok(x.2.take().unwrap())
106 } else {
107 Err(LatexError::NotLoaded)
108 }
109 } else {
110 Err(LatexError::NotExisting)
111 }
112 } else {
113 Err(LatexError::NotLoaded)
114 };
115 drop(idx);
116 res
117}
118
119pub fn render_all_equations() -> IResult<()> {
126 if let Ok(eqs) = EQUATIONS.lock() {
127 if eqs.len() == 0 {
128 return Ok(());
129 }
130 }
131 let fallback = Path::new("/tmp/ytesrev").to_path_buf();
132 let path = tempdir().map(|x| x.into_path()).unwrap_or(fallback);
133
134 eprintln!("Rendering in {}", path.display());
135
136 if path.exists() {
137 remove_dir_all(path.clone())?;
138 }
139 create_dir(path.clone())?;
140
141 let mut tex_path = path.clone();
142 tex_path.push("tmp.tex");
143
144 let mut pdf_path = path.clone();
145 pdf_path.push("tmp.pdf");
146
147 let mut raw_path = path.clone();
148 raw_path.push("tmp-res");
149
150 let start = Instant::now();
151
152 create_tex(&tex_path)?;
153
154 render_tex(&tex_path, &pdf_path, &raw_path)?;
155
156 read_pngs(&path)?;
157
158 let diff = Instant::now() - start;
159 eprintln!("Rendering took {:.2?}", diff);
160
161 Ok(())
162}
163
164fn create_tex(tex_path: &Path) -> IResult<()> {
165 let mut tex_file = File::create(tex_path)?;
166 let mut added_prelude = String::new();
167 if let Ok(prelude) = PRELUDE.lock() {
168 prelude.iter().for_each(|prelude| {
169 added_prelude.push_str(prelude);
170 added_prelude.push('\n');
171 });
172 }
173
174 writeln!(
175 tex_file,
176 "{}",
177 LATEX_PRELUDE.replace("$PRELUDE", &added_prelude)
178 )?;
179
180 if let Ok(eqs) = EQUATIONS.lock() {
181 for equation in eqs.iter() {
182 for col in &["red", "blue"] {
183 writeln!(tex_file, "\\begin{{equation*}}")?;
184 writeln!(tex_file, "\\colorbox{{{}}}{{\\makebox[\\linewidth]{{", col)?;
185 if equation.1 {
186 writeln!(tex_file, "{}", equation.0)?;
187 } else {
188 writeln!(tex_file, "$ {} $", equation.0)?;
189 }
190 writeln!(tex_file, "}} }}")?;
191 writeln!(tex_file, "\\end{{equation*}}")?;
192 }
193 }
194 }
195
196 writeln!(tex_file, "{}", LATEX_POSTLUDE)?;
197
198 Ok(())
199}
200
201fn render_tex(tex_path: &Path, pdf_path: &Path, raw_path: &Path) -> IResult<()> {
202 let out = Command::new("pdflatex")
203 .current_dir(tex_path.parent().unwrap())
204 .arg(tex_path.file_name().unwrap())
205 .output()
206 .expect("Can't make command");
207
208 if !out.status.success() {
209 eprintln!("Latex compile error:");
210 eprintln!("{}", String::from_utf8_lossy(&out.stderr));
211 exit(1);
212 }
213
214 let out = Command::new("pdftoppm")
215 .arg(pdf_path.as_os_str())
216 .arg(raw_path.as_os_str())
217 .arg("-r")
218 .arg("250")
219 .arg("-png")
220 .output()
221 .expect("Can't make command");
222
223 if !out.status.success() {
224 eprintln!("pdftoppm error");
225 eprintln!("{}", String::from_utf8_lossy(&out.stderr));
226 exit(1);
227 }
228
229 Ok(())
230}
231
232fn read_pngs(path: &Path) -> IResult<()> {
233 if let Ok(ref mut eqs) = EQUATIONS.lock() {
234 let digits_max = format!("{}", eqs.len()).len();
235
236 for (i, (_, _, ref mut im)) in eqs.iter_mut().enumerate() {
237 let num_red = zero_pad(format!("{}", 2 * i + 1), digits_max);
238 let num_blue = zero_pad(format!("{}", 2 * i + 2), digits_max);
239
240 let mut img_path_red = path.to_path_buf();
241 img_path_red.push(format!("tmp-res-{}.png", num_red));
242
243 let mut img_path_blue = path.to_path_buf();
244 img_path_blue.push(format!("tmp-res-{}.png", num_blue));
245
246 let mut im_red_res = PngImage::load_from_path(File::open(img_path_red)?)
247 .map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
248
249 let im_blue = PngImage::load_from_path(File::open(img_path_blue)?)
250 .map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
251
252 let mut maxx = 0;
253 let mut maxy = 0;
254 let mut minx = im_red_res.width;
255 let mut miny = im_red_res.height;
256
257 for i in 0..im_red_res.width * im_red_res.height {
258 let x = i % im_red_res.width;
259 let y = i / im_red_res.width;
260
261 let rr = im_red_res.data[4 * i];
262 let rg = im_red_res.data[4 * i];
263 let rb = im_red_res.data[4 * i + 2];
264
265 let br = im_blue.data[4 * i];
266 let bb = im_blue.data[4 * i + 2];
267
268 let rdiff = rr as i16 - br as i16;
269 let bdiff = bb as i16 - rb as i16;
270
271 let alpha = 255 - (rdiff + bdiff) / 2;
272 let alpha = alpha.min(255).max(0) as u8;
273
274 im_red_res.data[4 * i] = br;
275 im_red_res.data[4 * i + 2] = rb;
276 im_red_res.data[4 * i + 3] = alpha;
277
278 if (br < 250 || rg < 250 || rb < 250) && alpha > 250 {
279 maxx = maxx.max(x + 1);
280 maxy = maxy.max(y + 1);
281
282 minx = minx.min(x);
283 miny = miny.min(y);
284 }
285 }
286 maxx = (maxx + 3).min(im_red_res.width - 1);
288 maxy = (maxy + 3).min(im_red_res.height - 1);
289 minx = minx.saturating_sub(3);
290 miny = miny.saturating_sub(3);
291
292 let width = maxx - minx;
293 let height = maxy - miny;
294 let mut resdata = vec![0; 4 * width * height];
295
296 for x in 0..width {
297 for y in 0..height {
298 let i_r = y * width + x;
299 let i_l = (y + miny) * im_red_res.width + x + minx;
300
301 resdata[4 * i_r] = im_red_res.data[4 * i_l];
302 resdata[4 * i_r + 1] = im_red_res.data[4 * i_l + 1];
303 resdata[4 * i_r + 2] = im_red_res.data[4 * i_l + 2];
304 resdata[4 * i_r + 3] = im_red_res.data[4 * i_l + 3];
305 }
306 }
307
308 *im = Some(PngImage {
309 data: resdata,
310 width,
311 height,
312 });
313 }
314 }
315 Ok(())
316}
317
318fn zero_pad(n: String, len: usize) -> String {
319 let needed = len.saturating_sub(n.len());
320 let mut res = (0..needed).map(|_| '0').collect::<String>();
321 res.push_str(&n);
322 res
323}