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 let c = if c > 0x10_FFFF { c - 0x11_0000 } else { c };
304 if c as i32 == self.new_line_char && self.selector.is_output() {
305 self.print_ln();
306 return;
307 }
308 match self.selector {
309 Selector::TermAndLog | Selector::TermOnly | Selector::LogOnly => {
310 if let Some(cap) = self.capture.as_mut() {
311 utf8x::push(cap, c);
312 }
313 let (term, log) = match self.selector {
314 Selector::TermAndLog => (true, true),
315 Selector::TermOnly => (true, false),
316 _ => (false, true),
317 };
318 if c < 32 && c != 9 && c != 10 && c != 13 {
319 for b in [b'^', b'^', (c + 64) as u8] {
320 self.put_unit(&[b], term, log);
321 }
322 } else if c < 128 {
323 self.put_unit(&[c as u8], term, log);
324 } else {
325 let e = utf8x::encode(c);
326 self.put_unit(&e, term, log);
327 }
328 }
329 Selector::NoPrint => {}
330 Selector::Pseudo => {
331 let e = utf8x::encode(c);
335 for b in e {
336 self.pseudo_byte(b);
337 }
338 return;
339 }
340 Selector::NewString => utf8x::push(&mut self.cur_string, c),
341 Selector::File(n) => {
342 let e = utf8x::encode(c);
343 if let Some(Some(w)) = self.write_files.get_mut(n as usize) {
344 let _ = w.write_all(&e);
345 }
346 }
347 }
348 self.tally += 1;
349 }
350
351 pub fn print_raw_byte(&mut self, b: u8) {
353 let (term, log) = match self.selector {
354 Selector::TermAndLog => (true, true),
355 Selector::TermOnly => (true, false),
356 Selector::LogOnly => (false, true),
357 _ => (false, false),
358 };
359 if term || log {
360 if term {
361 self.put_unit(&[b], true, false);
362 }
363 if log {
364 if b >= 0xC0 {
365 self.wlog(&[b]);
368 } else {
369 self.put_unit(&[b], false, true);
370 }
371 }
372 return;
373 }
374 match self.selector {
375 Selector::Pseudo => self.pseudo_unit(&[b]),
376 Selector::NewString => self.cur_string.push(b),
377 _ => {}
378 }
379 }
380
381 pub fn pseudo_unit(&mut self, bytes: &[u8]) {
384 if self.tally < self.trick_count {
385 let i = self.tally % self.error_line;
386 if self.trick_buf.len() <= i {
387 self.trick_buf.resize(i + 1, vec![b' ']);
388 }
389 self.trick_buf[i] = bytes.to_vec();
390 }
391 self.tally += 1;
392 }
393
394 fn pseudo_byte(&mut self, b: u8) {
395 self.pseudo_unit(&[b]);
396 }
397
398 pub fn print_str(&mut self, s: &str) {
400 let slack = self.log_slack;
403 self.log_slack = 1;
404 for c in s.chars() {
405 self.print_char(c as u32);
406 }
407 self.log_slack = slack;
408 }
409
410 pub fn print_callback_bytes(&mut self, s: &[u8]) {
414 let (term, log) = match self.selector {
415 Selector::TermAndLog => (true, true),
416 Selector::TermOnly => (true, false),
417 Selector::LogOnly => (false, true),
418 _ => {
419 self.print_bytes(s);
420 return;
421 }
422 };
423 let slack = std::mem::replace(&mut self.log_slack, 1);
427 let mut i = 0;
428 while i < s.len() {
429 let (c, n) = utf8x::decode(s, i);
430 let unit = &s[i..i + n];
431 if log {
432 self.put_unit(unit, false, true);
433 }
434 if term {
435 if c < 32 {
436 for b in [b'^', b'^', (c + 64) as u8] {
437 self.put_unit(&[b], true, false);
438 }
439 } else {
440 self.put_unit(unit, true, false);
441 }
442 }
443 self.tally += 1;
444 i += n;
445 }
446 self.log_slack = slack;
447 }
448
449 pub fn print_plain(&mut self, s: &str) {
452 for c in s.chars() {
453 self.print_char(c as u32);
454 }
455 }
456
457 pub fn print_bytes(&mut self, s: &[u8]) {
460 let mut i = 0;
461 while i < s.len() {
462 let (c, n) = utf8x::decode(s, i);
463 self.print_char(c);
464 i += n;
465 }
466 }
467
468 pub fn print_lua_bytes(&mut self, s: &[u8]) {
471 self.log_slack = 1;
472 self.print_bytes(s);
473 self.log_slack = 0;
474 }
475
476 pub fn luatex_warning(&mut self, category: &str, msg: &str) {
479 self.print_ln();
480 self.print_lua_bytes(format!("warning ({category}): {msg}").as_bytes());
481 self.print_ln();
482 }
483
484 pub fn print_nl(&mut self, s: &str) {
486 self.begin_line();
487 self.print_str(s);
488 }
489
490 pub fn begin_line(&mut self) {
493 let term = matches!(self.selector, Selector::TermOnly | Selector::TermAndLog);
494 let log = matches!(self.selector, Selector::LogOnly | Selector::TermAndLog);
495 if (term && self.term_offset > 0) || (log && self.file_offset > 0) {
496 self.print_ln();
497 }
498 }
499
500 pub fn print_int(&mut self, n: i64) {
502 self.print_plain(&n.to_string());
503 }
504
505 pub fn print_scaled(&mut self, s: i32) {
507 let mut out = String::new();
508 retex_arith::print_scaled(&mut out, s);
509 match out.find('.') {
512 Some(i) => {
513 self.print_plain(&out[..i]);
514 self.print_str(&out[i..]);
515 }
516 None => self.print_plain(&out),
517 }
518 }
519
520 pub fn print_hex(&mut self, n: i64) {
523 self.print_char('"' as u32);
524 self.print_plain(&format!("{n:X}"));
525 }
526
527 pub fn print_roman_int(&mut self, n: i64) {
530 self.print_plain(&roman(n));
531 }
532
533 pub fn take_string(&mut self) -> Vec<u8> {
535 std::mem::take(&mut self.cur_string)
536 }
537
538 pub fn flush(&mut self) {
540 let _ = self.term.flush();
541 if let Some(log) = self.log.as_mut() {
542 let _ = log.flush();
543 }
544 for w in self.write_files.iter_mut().flatten() {
545 let _ = w.flush();
546 }
547 }
548}
549
550pub fn roman(mut n: i64) -> String {
553 let table = b"m2d5c2l5x2v5i";
555 let mut out = String::new();
556 if n <= 0 {
557 return out;
558 }
559 let mut j = 0usize;
560 let mut v: i64 = 1000;
561 loop {
562 while n >= v {
563 out.push(table[j] as char);
564 n -= v;
565 }
566 if n <= 0 {
567 return out;
568 }
569 let mut k = j + 2;
570 let mut u = v / i64::from(table[k - 1] - b'0');
571 if table[k - 1] == b'2' {
572 k += 2;
573 u /= i64::from(table[k - 1] - b'0');
574 }
575 if n + u >= v {
576 out.push(table[k] as char);
577 n += u;
578 } else {
579 j += 2;
580 v /= i64::from(table[j - 1] - b'0');
581 }
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588 use std::cell::RefCell;
589 use std::rc::Rc;
590
591 #[derive(Clone, Default)]
593 struct Shared(Rc<RefCell<Vec<u8>>>);
594 impl Write for Shared {
595 fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
596 self.0.borrow_mut().extend_from_slice(b);
597 Ok(b.len())
598 }
599 fn flush(&mut self) -> std::io::Result<()> {
600 Ok(())
601 }
602 }
603
604 #[test]
605 fn romans() {
606 assert_eq!(roman(1984), "mcmlxxxiv");
607 assert_eq!(roman(4), "iv");
608 assert_eq!(roman(9), "ix");
609 assert_eq!(roman(0), "");
610 assert_eq!(roman(3999), "mmmcmxcix");
611 }
612
613 #[test]
614 fn control_chars_and_utf8() {
615 let buf = Shared::default();
616 let mut p = Printer::new(Box::new(buf.clone()));
617 for c in [1, 9, 0xE9, 127] {
618 p.print_char(c);
619 }
620 assert_eq!(&*buf.0.borrow(), "^^A\té\u{7f}".as_bytes());
621 }
622
623 #[test]
624 fn wraps_without_splitting_utf8() {
625 let buf = Shared::default();
626 let mut p = Printer::new(Box::new(buf.clone()));
627 for _ in 0..40 {
628 p.print_char(0xE9);
629 }
630 let s = String::from_utf8(buf.0.borrow().clone()).unwrap();
631 let lines: Vec<&str> = s.split('\n').collect();
632 assert_eq!(lines[0].chars().count(), 39);
633 assert_eq!(lines[1].chars().count(), 1);
634 }
635}