1use std::io::Write;
45
46use crate::Engine;
47use crate::input::Level;
48use crate::nodes::NodeRef;
49use crate::primitives_gen::assign_int as ip;
50
51pub type Fields = (u32, i32);
53
54enum Sink {
56 Plain(Vec<u8>),
57 Gz(flate2::write::GzEncoder<Vec<u8>>, Vec<u8>),
60}
61
62struct Out {
64 sink: Sink,
65 size: u64,
67 last_bang: u64,
69 count: u64,
71 line: Vec<u8>,
73}
74
75impl Out {
76 fn emit(&mut self, counted: bool) {
77 self.size += self.line.len() as u64;
78 match &mut self.sink {
79 Sink::Plain(v) => v.extend_from_slice(&self.line),
80 Sink::Gz(g, pending) => {
81 pending.extend_from_slice(&self.line);
82 if pending.len() >= 1 << 16 {
83 let _ = g.write_all(pending);
84 pending.clear();
85 }
86 }
87 }
88 self.line.clear();
89 if counted {
90 self.count += 1;
91 }
92 }
93
94 fn bang(&mut self) {
95 let off = self.size - self.last_bang;
96 self.last_bang = self.size;
97 let _ = writeln!(self.line, "!{off}");
98 self.emit(true);
99 }
100}
101
102#[derive(Default)]
104pub struct Synctex {
105 pub option: Option<i32>,
108 pub files: Vec<Vec<u8>>,
110 cwd: Option<Vec<u8>>,
112 out: Option<Out>,
113 pub dir: Option<std::path::PathBuf>,
116 in_page: bool,
119 origin: (i32, i32),
122 ctx: Fields,
124 last: (i32, i32),
126 seen: bool,
128 pending: Option<(Fields, i32)>,
130 run: bool,
132}
133
134impl Synctex {
135 fn input_name(&mut self, path: &std::path::Path) -> Vec<u8> {
137 let name = path.to_string_lossy().into_owned().into_bytes();
138 if path.is_absolute() {
139 return name;
140 }
141 let cwd = self.cwd.get_or_insert_with(|| {
142 std::env::current_dir()
143 .map(|d| d.to_string_lossy().into_owned().into_bytes())
144 .unwrap_or_default()
145 });
146 let mut full = cwd.clone();
147 full.push(b'/');
148 full.extend_from_slice(&name);
149 full
150 }
151
152 fn add_input(&mut self, path: &std::path::Path) -> u32 {
154 let name = self.input_name(path);
155 let tag = self.files.len() as u32 + 1;
156 if let Some(out) = self.out.as_mut() {
157 out.line
158 .extend_from_slice(format!("Input:{tag}:").as_bytes());
159 out.line.extend_from_slice(&name);
160 out.line.push(b'\n');
161 out.emit(false);
162 }
163 self.files.push(name);
164 tag
165 }
166
167 fn open(&mut self, pdf: bool, mag: i32) -> &mut Out {
169 let compressed = self.option.is_none_or(|n| n > 0);
170 let files = &self.files;
171 self.out.get_or_insert_with(|| {
172 let sink = if compressed {
173 Sink::Gz(
174 flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()),
175 Vec::new(),
176 )
177 } else {
178 Sink::Plain(Vec::new())
179 };
180 let mut out = Out {
181 sink,
182 size: 0,
183 last_bang: 0,
184 count: 0,
185 line: Vec::new(),
186 };
187 out.line.extend_from_slice(b"SyncTeX Version:1\n");
188 for (i, f) in files.iter().enumerate() {
189 out.line
190 .extend_from_slice(format!("Input:{}:", i + 1).as_bytes());
191 out.line.extend_from_slice(f);
192 out.line.push(b'\n');
193 }
194 let _ = write!(
195 out.line,
196 "Output:{}\nMagnification:{mag}\nUnit:1\nX Offset:0\nY Offset:0\nContent:\n",
197 if pdf { "pdf" } else { "dvi" }
198 );
199 out.emit(false);
200 out
201 })
202 }
203
204 fn begin_sheet(&mut self, n: u32, origin: (i32, i32), pdf: bool, mag: i32) {
206 let out = self.open(pdf, mag);
207 out.bang();
208 let _ = writeln!(out.line, "{{{n}");
209 out.emit(true);
210 self.origin = origin;
211 self.ctx = (0, 0);
212 self.last = (0, 0);
213 self.seen = false;
214 self.pending = None;
215 self.run = false;
216 }
217
218 fn end_sheet(&mut self, n: u32) {
220 if let Some(out) = self.out.as_mut() {
221 out.bang();
222 let _ = writeln!(out.line, "}}{n}");
223 out.emit(true);
224 }
225 }
226
227 fn out(&mut self) -> &mut Out {
228 self.out.as_mut().expect("a sheet is open")
229 }
230
231 fn record(&mut self, c: u8, f: Fields, h: i32, v: i32, rest: &[i32]) {
233 let (oh, ov) = self.origin;
234 let out = self.out();
235 out.line.push(c);
236 let _ = write!(out.line, "{},{}:{},{}", f.0, f.1, h + oh, v + ov);
237 for (i, x) in rest.iter().enumerate() {
238 out.line.push(if i == 0 { b':' } else { b',' });
239 let _ = write!(out.line, "{x}");
240 }
241 out.line.push(b'\n');
242 out.emit(c != b'x');
243 }
244
245 fn flush_kern(&mut self, next: Option<Fields>) {
248 if let Some((f, w)) = self.pending.take()
249 && Some(f) != next
250 {
251 let (h, v) = self.last;
252 self.record(b'k', f, h, v, &[w]);
253 }
254 }
255
256 #[inline]
258 pub fn end_run(&mut self, h: i32, v: i32) {
259 if self.run {
260 self.run = false;
261 let f = self.ctx;
262 self.record(b'x', f, h, v, &[]);
263 }
264 }
265
266 #[inline]
268 pub fn glyph(&mut self) {
269 self.run = true;
270 }
271
272 pub fn box_open(&mut self, hlist: bool, f: Fields, h: i32, v: i32, size: [i32; 3]) {
275 self.record(if hlist { b'(' } else { b'[' }, f, h, v, &size);
276 self.ctx = f;
277 self.last = (h, v);
278 self.seen = false;
279 self.pending = None;
280 }
281
282 pub fn box_close(&mut self, hlist: bool, f: Fields, h: i32, v: i32) {
286 let out = self.out();
287 out.line.push(if hlist { b')' } else { b']' });
288 out.line.push(b'\n');
289 out.emit(true);
290 self.ctx = f;
291 self.last = (h, v);
292 self.seen = true;
293 self.pending = None;
294 }
295
296 pub fn void_box(&mut self, hlist: bool, f: Fields, h: i32, v: i32, size: [i32; 3]) {
298 self.flush_kern(None);
299 self.record(if hlist { b'h' } else { b'v' }, f, h, v, &size);
300 self.ctx = f;
301 self.last = (h, v);
302 self.seen = true;
303 }
304
305 pub fn glue(&mut self, f: Fields, h: i32, v: i32) {
307 if f.0 == 0 || f.1 == 0 {
308 return;
309 }
310 self.pending = None;
311 self.record(b'g', f, h, v, &[]);
312 self.ctx = f;
313 self.last = (h, v);
314 self.seen = true;
315 }
316
317 pub fn rule(&mut self, f: Fields, h: i32, v: i32, size: [i32; 3]) {
319 if f.0 == 0 || f.1 == 0 {
320 return;
321 }
322 self.pending = None;
323 self.record(b'r', f, h, v, &size);
324 self.ctx = f;
325 self.last = (h, v);
326 self.seen = true;
327 }
328
329 pub fn math(&mut self, f: Fields, h: i32, v: i32) {
331 if f.0 == 0 || f.1 == 0 {
332 return;
333 }
334 self.flush_kern(Some(f));
335 self.record(b'$', f, h, v, &[]);
336 self.ctx = f;
337 self.last = (h, v);
338 self.seen = true;
339 }
340
341 pub fn kern(&mut self, f: Fields, w: i32) {
353 if f.0 == 0 || f.1 == 0 {
354 return;
355 }
356 if self.seen && f != self.ctx {
357 self.flush_kern(Some(f));
358 let (h, v) = self.last;
359 self.record(b'k', f, h, v, &[w]);
360 } else {
361 self.pending = Some((f, w));
362 }
363 self.ctx = f;
364 self.seen = true;
365 }
366
367 fn finish(&mut self) -> Option<(Vec<u8>, bool)> {
369 let mut out = self.out.take()?;
370 out.bang();
371 let _ = write!(out.line, "Postamble:\nCount:{}\n", out.count);
372 out.emit(false);
373 out.bang();
374 out.line.extend_from_slice(b"Post scriptum:\n");
375 out.emit(false);
376 match out.sink {
377 Sink::Plain(v) => Some((v, false)),
378 Sink::Gz(mut g, pending) => {
379 let _ = g.write_all(&pending);
380 g.finish().ok().map(|v| (v, true))
381 }
382 }
383 }
384}
385
386impl Engine {
387 fn synctex_allowed(&self) -> bool {
389 self.synctex.option != Some(0)
390 }
391
392 pub fn synctex_changed(&mut self) {
395 if self.eqtb.int(ip::SYNCTEX) != 0 && self.synctex_allowed() && self.nodes.sync.is_none() {
396 self.nodes.enable_sync();
397 self.sync_input_changed();
398 }
399 }
400
401 #[inline]
404 pub(crate) fn sync_input_changed(&mut self) {
405 if let Some(s) = self.nodes.sync.as_deref_mut() {
406 s.cur = self
407 .input
408 .stack
409 .iter()
410 .rev()
411 .find_map(|l| match l {
412 Level::Text(t) => Some(t.sync),
413 Level::Tokens(_) => None,
414 })
415 .unwrap_or((0, 0));
416 }
417 }
418
419 pub(crate) fn synctex_input_opened(&mut self, path: &std::path::Path) {
422 let tag = self.synctex.add_input(path);
423 if let Some(Level::Text(t)) = self.input.stack.last_mut() {
424 t.sync = (tag, 0);
425 }
426 }
427
428 pub(crate) fn synctex_recording(&self) -> bool {
430 self.nodes.sync.is_some() && self.eqtb.int(ip::SYNCTEX) != 0
431 }
432
433 pub(crate) fn synctex_begin_page(&mut self, origin: (i32, i32)) {
435 let pdf = self.eqtb.int(ip::OUTPUTMODE) > 0;
436 let mag = self.eqtb.int(ip::MAG);
437 self.synctex
438 .begin_sheet(self.total_pages + 1, origin, pdf, mag);
439 self.synctex.in_page = true;
440 }
441
442 pub(crate) fn synctex_end_page(&mut self) {
444 self.synctex.in_page = false;
445 self.synctex.end_sheet(self.total_pages + 1);
446 }
447
448 pub(crate) fn synctex_begin_form(&mut self, total: i32) {
450 let pdf = self.eqtb.int(ip::OUTPUTMODE) > 0;
451 let mag = self.eqtb.int(ip::MAG);
452 let n = self.total_pages + 1 + u32::from(self.synctex.in_page);
453 let page_height = self
454 .eqtb
455 .dimen(crate::primitives_gen::assign_dimen::PAGEHEIGHT);
456 self.synctex
457 .begin_sheet(n, (0, page_height - total), pdf, mag);
458 }
459
460 pub(crate) fn synctex_end_form(&mut self) {
462 let n = self.total_pages + u32::from(self.synctex.in_page);
463 self.synctex.end_sheet(n);
464 }
465
466 #[inline]
468 pub(crate) fn sync_of(&self, p: NodeRef) -> Fields {
469 self.nodes.sync_fields(p)
470 }
471
472 pub(crate) fn finish_synctex(&mut self) {
476 let job =
477 String::from_utf8_lossy(self.job_name.as_deref().unwrap_or(b"texput")).into_owned();
478 let dir = self
479 .synctex
480 .dir
481 .clone()
482 .or_else(|| self.files.output_dir.clone())
483 .unwrap_or_default();
484 let gz = dir.join(format!("{job}.synctex.gz"));
485 let plain = dir.join(format!("{job}.synctex"));
486 let result = if self.total_pages > 0 {
487 self.synctex.finish()
488 } else {
489 None
490 };
491 let Some((data, compressed)) = result else {
492 let _ = std::fs::remove_file(&gz);
493 let _ = std::fs::remove_file(&plain);
494 return;
495 };
496 let (path, other) = if compressed { (gz, plain) } else { (plain, gz) };
497 let _ = std::fs::remove_file(&other);
498 if std::fs::write(&path, &data).is_err() {
499 return;
500 }
501 let name = path
502 .file_name()
503 .map(|n| n.to_string_lossy().into_owned())
504 .unwrap_or_default();
505 let sel = self.printer.selector;
506 self.printer.selector = crate::print::Selector::TermOnly;
507 self.printer.print_nl("SyncTeX written on ");
508 self.printer.print_bytes(name.as_bytes());
509 self.printer.print_char('.' as u32);
510 self.printer.selector = sel;
511 }
512}