1use std::io::Write;
16
17use crate::utf8x;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Selector {
22 NoPrint,
24 TermOnly,
26 LogOnly,
28 TermAndLog,
30 Pseudo,
32 NewString,
34 File(u32),
36}
37
38impl Selector {
39 pub fn is_output(self) -> bool {
42 matches!(
43 self,
44 Selector::NoPrint
45 | Selector::TermOnly
46 | Selector::LogOnly
47 | Selector::TermAndLog
48 | Selector::File(_)
49 )
50 }
51}
52
53pub struct Printer {
55 pub selector: Selector,
57 pub term_offset: usize,
59 pub term_just_wrapped: bool,
62 pub file_offset: usize,
64 pub max_print_line: usize,
66 pub log_slack: usize,
70 pub term_quiet: bool,
73 pub quiet_line: Vec<u8>,
76 pub capture: Option<Vec<u8>>,
79 pub error_line: usize,
81 pub half_error_line: usize,
83 pub tally: usize,
86 pub trick_buf: Vec<Vec<u8>>,
89 pub trick_count: usize,
91 pub first_count: usize,
93 pub cur_string: Vec<u8>,
95 pub new_line_char: i32,
97 term: Box<dyn Write>,
99 log: Option<Box<dyn Write>>,
101 write_files: Vec<Option<Box<dyn Write>>>,
103}
104
105impl std::fmt::Debug for Printer {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("Printer")
108 .field("selector", &self.selector)
109 .field("term_offset", &self.term_offset)
110 .field("file_offset", &self.file_offset)
111 .finish_non_exhaustive()
112 }
113}
114
115impl Printer {
116 pub fn new(term: Box<dyn Write>) -> Self {
118 Printer {
119 selector: Selector::TermOnly,
120 term_offset: 0,
121 term_just_wrapped: false,
122 file_offset: 0,
123 max_print_line: 79,
124 log_slack: 0,
125 term_quiet: false,
126 quiet_line: Vec::new(),
127 capture: None,
128 error_line: 79,
129 half_error_line: 50,
130 tally: 0,
131 trick_buf: Vec::new(),
132 trick_count: 0,
133 first_count: 0,
134 cur_string: Vec::new(),
135 new_line_char: -1,
136 term,
137 log: None,
138 write_files: Vec::new(),
139 }
140 }
141
142 pub fn set_log(&mut self, log: Box<dyn Write>) {
144 self.log = Some(log);
145 }
146
147 pub fn log_opened(&self) -> bool {
149 self.log.is_some()
150 }
151
152 pub fn close_log(&mut self) {
154 if let Some(mut log) = self.log.take() {
155 let _ = log.flush();
156 }
157 }
158
159 pub fn log_raw(&mut self, bytes: &[u8]) {
161 if let Some(log) = self.log.as_mut() {
162 let _ = log.write_all(bytes);
163 }
164 }
165
166 pub fn term_raw(&mut self, bytes: &[u8]) {
169 if !self.term_quiet {
170 let _ = self.term.write_all(bytes);
171 }
172 }
173
174 pub fn update_terminal(&mut self) {
176 let _ = self.term.flush();
177 }
178
179 pub fn open_write(&mut self, n: u32, w: Box<dyn Write>) {
181 let n = n as usize;
182 if self.write_files.len() <= n {
183 self.write_files.resize_with(n + 1, || None);
184 }
185 self.write_files[n] = Some(w);
186 }
187
188 pub fn open_write_streams(&self) -> Vec<u32> {
190 (0..self.write_files.len() as u32)
191 .filter(|&n| self.write_files[n as usize].is_some())
192 .collect()
193 }
194
195 pub fn close_write(&mut self, n: u32) {
197 if let Some(slot) = self.write_files.get_mut(n as usize)
198 && let Some(mut w) = slot.take()
199 {
200 let _ = w.flush();
201 }
202 }
203
204 pub fn write_open(&self, n: u32) -> bool {
206 matches!(self.write_files.get(n as usize), Some(Some(_)))
207 }
208
209 fn wterm(&mut self, b: &[u8]) {
210 if !self.term_quiet {
211 let _ = self.term.write_all(b);
212 } else {
213 match b.iter().rposition(|&c| c == b'\n') {
214 Some(i) => self.quiet_line = b[i + 1..].to_vec(),
215 None => self.quiet_line.extend_from_slice(b),
216 }
217 }
218 }
219
220 fn wlog(&mut self, b: &[u8]) {
221 if let Some(log) = self.log.as_mut() {
222 let _ = log.write_all(b);
223 }
224 }
225
226 pub fn print_ln(&mut self) {
228 if let Some(c) = self.capture.as_mut()
229 && matches!(
230 self.selector,
231 Selector::TermAndLog | Selector::TermOnly | Selector::LogOnly
232 )
233 {
234 c.push(b'\n');
235 }
236 match self.selector {
237 Selector::TermAndLog => {
238 self.wterm(b"\n");
239 self.wlog(b"\n");
240 self.term_offset = 0;
241 self.file_offset = 0;
242 }
243 Selector::LogOnly => {
244 self.wlog(b"\n");
245 self.file_offset = 0;
246 }
247 Selector::TermOnly => {
248 self.wterm(b"\n");
249 self.term_offset = 0;
250 }
251 Selector::NoPrint | Selector::Pseudo | Selector::NewString => {}
252 Selector::File(n) => {
253 if let Some(Some(w)) = self.write_files.get_mut(n as usize) {
254 let _ = w.write_all(b"\n");
255 }
256 }
257 }
258 }
259
260 fn put_unit(&mut self, bytes: &[u8], term: bool, log: bool) {
263 let n = bytes.len();
264 if term {
265 if n > 1 && self.term_offset + n > self.max_print_line {
266 self.wterm(b"\n");
267 self.term_offset = 0;
268 }
269 self.wterm(bytes);
270 self.term_offset += n;
271 self.term_just_wrapped = false;
272 if self.term_offset >= self.max_print_line {
273 self.wterm(b"\n");
274 self.term_offset = 0;
275 self.term_just_wrapped = true;
276 }
277 }
278 if log {
279 let max = self.max_print_line + self.log_slack;
280 if n > 1 && self.file_offset + n > max {
281 self.wlog(b"\n");
282 self.file_offset = 0;
283 }
284 self.wlog(bytes);
285 let before = self.file_offset;
286 self.file_offset += n;
287 if self.file_offset >= max && before < max {
290 self.wlog(b"\n");
291 self.file_offset = 0;
292 }
293 }
294 }
295
296 pub fn print_char(&mut self, c: u32) {
299 if c as i32 == self.new_line_char && self.selector.is_output() {
300 self.print_ln();
301 return;
302 }
303 match self.selector {
304 Selector::TermAndLog | Selector::TermOnly | Selector::LogOnly => {
305 if let Some(cap) = self.capture.as_mut() {
306 utf8x::push(cap, c);
307 }
308 let (term, log) = match self.selector {
309 Selector::TermAndLog => (true, true),
310 Selector::TermOnly => (true, false),
311 _ => (false, true),
312 };
313 if c < 32 && c != 9 && c != 10 && c != 13 {
314 for b in [b'^', b'^', (c + 64) as u8] {
315 self.put_unit(&[b], term, log);
316 }
317 } else if c < 128 {
318 self.put_unit(&[c as u8], term, log);
319 } else {
320 let e = utf8x::encode(c);
321 self.put_unit(&e, term, log);
322 }
323 }
324 Selector::NoPrint => {}
325 Selector::Pseudo => {
326 let e = utf8x::encode(c);
330 for b in e {
331 self.pseudo_byte(b);
332 }
333 return;
334 }
335 Selector::NewString => utf8x::push(&mut self.cur_string, c),
336 Selector::File(n) => {
337 let e = utf8x::encode(c);
338 if let Some(Some(w)) = self.write_files.get_mut(n as usize) {
339 let _ = w.write_all(&e);
340 }
341 }
342 }
343 self.tally += 1;
344 }
345
346 pub fn print_raw_byte(&mut self, b: u8) {
348 let (term, log) = match self.selector {
349 Selector::TermAndLog => (true, true),
350 Selector::TermOnly => (true, false),
351 Selector::LogOnly => (false, true),
352 _ => (false, false),
353 };
354 if term || log {
355 if term {
356 self.put_unit(&[b], true, false);
357 }
358 if log {
359 if b >= 0xC0 {
360 self.wlog(&[b]);
363 } else {
364 self.put_unit(&[b], false, true);
365 }
366 }
367 return;
368 }
369 match self.selector {
370 Selector::Pseudo => self.pseudo_unit(&[b]),
371 Selector::NewString => self.cur_string.push(b),
372 _ => {}
373 }
374 }
375
376 pub fn pseudo_unit(&mut self, bytes: &[u8]) {
379 if self.tally < self.trick_count {
380 let i = self.tally % self.error_line;
381 if self.trick_buf.len() <= i {
382 self.trick_buf.resize(i + 1, vec![b' ']);
383 }
384 self.trick_buf[i] = bytes.to_vec();
385 }
386 self.tally += 1;
387 }
388
389 fn pseudo_byte(&mut self, b: u8) {
390 self.pseudo_unit(&[b]);
391 }
392
393 pub fn print_str(&mut self, s: &str) {
395 let slack = self.log_slack;
398 self.log_slack = 1;
399 for c in s.chars() {
400 self.print_char(c as u32);
401 }
402 self.log_slack = slack;
403 }
404
405 pub fn print_callback_bytes(&mut self, s: &[u8]) {
409 let (term, log) = match self.selector {
410 Selector::TermAndLog => (true, true),
411 Selector::TermOnly => (true, false),
412 Selector::LogOnly => (false, true),
413 _ => {
414 self.print_bytes(s);
415 return;
416 }
417 };
418 let mut i = 0;
419 while i < s.len() {
420 let (c, n) = utf8x::decode(s, i);
421 let unit = &s[i..i + n];
422 if log {
423 self.put_unit(unit, false, true);
424 }
425 if term {
426 if c < 32 {
427 for b in [b'^', b'^', (c + 64) as u8] {
428 self.put_unit(&[b], true, false);
429 }
430 } else {
431 self.put_unit(unit, true, false);
432 }
433 }
434 self.tally += 1;
435 i += n;
436 }
437 }
438
439 pub fn print_plain(&mut self, s: &str) {
442 for c in s.chars() {
443 self.print_char(c as u32);
444 }
445 }
446
447 pub fn print_bytes(&mut self, s: &[u8]) {
450 let mut i = 0;
451 while i < s.len() {
452 let (c, n) = utf8x::decode(s, i);
453 self.print_char(c);
454 i += n;
455 }
456 }
457
458 pub fn print_lua_bytes(&mut self, s: &[u8]) {
461 self.log_slack = 1;
462 self.print_bytes(s);
463 self.log_slack = 0;
464 }
465
466 pub fn luatex_warning(&mut self, category: &str, msg: &str) {
469 self.print_ln();
470 self.print_lua_bytes(format!("warning ({category}): {msg}").as_bytes());
471 self.print_ln();
472 }
473
474 pub fn print_nl(&mut self, s: &str) {
476 self.begin_line();
477 self.print_str(s);
478 }
479
480 pub fn begin_line(&mut self) {
483 let term = matches!(self.selector, Selector::TermOnly | Selector::TermAndLog);
484 let log = matches!(self.selector, Selector::LogOnly | Selector::TermAndLog);
485 if (term && self.term_offset > 0) || (log && self.file_offset > 0) {
486 self.print_ln();
487 }
488 }
489
490 pub fn print_int(&mut self, n: i64) {
492 self.print_plain(&n.to_string());
493 }
494
495 pub fn print_scaled(&mut self, s: i32) {
497 let mut out = String::new();
498 retex_arith::print_scaled(&mut out, s);
499 match out.find('.') {
502 Some(i) => {
503 self.print_plain(&out[..i]);
504 self.print_str(&out[i..]);
505 }
506 None => self.print_plain(&out),
507 }
508 }
509
510 pub fn print_hex(&mut self, n: i64) {
513 self.print_char('"' as u32);
514 self.print_plain(&format!("{n:X}"));
515 }
516
517 pub fn print_roman_int(&mut self, n: i64) {
520 self.print_plain(&roman(n));
521 }
522
523 pub fn take_string(&mut self) -> Vec<u8> {
525 std::mem::take(&mut self.cur_string)
526 }
527
528 pub fn flush(&mut self) {
530 let _ = self.term.flush();
531 if let Some(log) = self.log.as_mut() {
532 let _ = log.flush();
533 }
534 for w in self.write_files.iter_mut().flatten() {
535 let _ = w.flush();
536 }
537 }
538}
539
540pub fn roman(mut n: i64) -> String {
543 let table = b"m2d5c2l5x2v5i";
545 let mut out = String::new();
546 if n <= 0 {
547 return out;
548 }
549 let mut j = 0usize;
550 let mut v: i64 = 1000;
551 loop {
552 while n >= v {
553 out.push(table[j] as char);
554 n -= v;
555 }
556 if n <= 0 {
557 return out;
558 }
559 let mut k = j + 2;
560 let mut u = v / i64::from(table[k - 1] - b'0');
561 if table[k - 1] == b'2' {
562 k += 2;
563 u /= i64::from(table[k - 1] - b'0');
564 }
565 if n + u >= v {
566 out.push(table[k] as char);
567 n += u;
568 } else {
569 j += 2;
570 v /= i64::from(table[j - 1] - b'0');
571 }
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578 use std::cell::RefCell;
579 use std::rc::Rc;
580
581 #[derive(Clone, Default)]
583 struct Shared(Rc<RefCell<Vec<u8>>>);
584 impl Write for Shared {
585 fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
586 self.0.borrow_mut().extend_from_slice(b);
587 Ok(b.len())
588 }
589 fn flush(&mut self) -> std::io::Result<()> {
590 Ok(())
591 }
592 }
593
594 #[test]
595 fn romans() {
596 assert_eq!(roman(1984), "mcmlxxxiv");
597 assert_eq!(roman(4), "iv");
598 assert_eq!(roman(9), "ix");
599 assert_eq!(roman(0), "");
600 assert_eq!(roman(3999), "mmmcmxcix");
601 }
602
603 #[test]
604 fn control_chars_and_utf8() {
605 let buf = Shared::default();
606 let mut p = Printer::new(Box::new(buf.clone()));
607 for c in [1, 9, 0xE9, 127] {
608 p.print_char(c);
609 }
610 assert_eq!(&*buf.0.borrow(), "^^A\té\u{7f}".as_bytes());
611 }
612
613 #[test]
614 fn wraps_without_splitting_utf8() {
615 let buf = Shared::default();
616 let mut p = Printer::new(Box::new(buf.clone()));
617 for _ in 0..40 {
618 p.print_char(0xE9);
619 }
620 let s = String::from_utf8(buf.0.borrow().clone()).unwrap();
621 let lines: Vec<&str> = s.split('\n').collect();
622 assert_eq!(lines[0].chars().count(), 39);
623 assert_eq!(lines[1].chars().count(), 1);
624 }
625}