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 capture: Option<Vec<u8>>,
76 pub error_line: usize,
78 pub half_error_line: usize,
80 pub tally: usize,
83 pub trick_buf: Vec<u8>,
85 pub trick_count: usize,
87 pub first_count: usize,
89 pub cur_string: Vec<u8>,
91 pub new_line_char: i32,
93 term: Box<dyn Write>,
95 log: Option<Box<dyn Write>>,
97 write_files: Vec<Option<Box<dyn Write>>>,
99}
100
101impl std::fmt::Debug for Printer {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("Printer")
104 .field("selector", &self.selector)
105 .field("term_offset", &self.term_offset)
106 .field("file_offset", &self.file_offset)
107 .finish_non_exhaustive()
108 }
109}
110
111impl Printer {
112 pub fn new(term: Box<dyn Write>) -> Self {
114 Printer {
115 selector: Selector::TermOnly,
116 term_offset: 0,
117 term_just_wrapped: false,
118 file_offset: 0,
119 max_print_line: 79,
120 log_slack: 0,
121 term_quiet: false,
122 capture: None,
123 error_line: 79,
124 half_error_line: 50,
125 tally: 0,
126 trick_buf: Vec::new(),
127 trick_count: 0,
128 first_count: 0,
129 cur_string: Vec::new(),
130 new_line_char: -1,
131 term,
132 log: None,
133 write_files: Vec::new(),
134 }
135 }
136
137 pub fn set_log(&mut self, log: Box<dyn Write>) {
139 self.log = Some(log);
140 }
141
142 pub fn log_opened(&self) -> bool {
144 self.log.is_some()
145 }
146
147 pub fn close_log(&mut self) {
149 if let Some(mut log) = self.log.take() {
150 let _ = log.flush();
151 }
152 }
153
154 pub fn log_raw(&mut self, bytes: &[u8]) {
156 if let Some(log) = self.log.as_mut() {
157 let _ = log.write_all(bytes);
158 }
159 }
160
161 pub fn term_raw(&mut self, bytes: &[u8]) {
163 let _ = self.term.write_all(bytes);
164 }
165
166 pub fn update_terminal(&mut self) {
168 let _ = self.term.flush();
169 }
170
171 pub fn open_write(&mut self, n: u32, w: Box<dyn Write>) {
173 let n = n as usize;
174 if self.write_files.len() <= n {
175 self.write_files.resize_with(n + 1, || None);
176 }
177 self.write_files[n] = Some(w);
178 }
179
180 pub fn open_write_streams(&self) -> Vec<u32> {
182 (0..self.write_files.len() as u32)
183 .filter(|&n| self.write_files[n as usize].is_some())
184 .collect()
185 }
186
187 pub fn close_write(&mut self, n: u32) {
189 if let Some(slot) = self.write_files.get_mut(n as usize)
190 && let Some(mut w) = slot.take()
191 {
192 let _ = w.flush();
193 }
194 }
195
196 pub fn write_open(&self, n: u32) -> bool {
198 matches!(self.write_files.get(n as usize), Some(Some(_)))
199 }
200
201 fn wterm(&mut self, b: &[u8]) {
202 if !self.term_quiet {
203 let _ = self.term.write_all(b);
204 }
205 }
206
207 fn wlog(&mut self, b: &[u8]) {
208 if let Some(log) = self.log.as_mut() {
209 let _ = log.write_all(b);
210 }
211 }
212
213 pub fn print_ln(&mut self) {
215 if let Some(c) = self.capture.as_mut()
216 && matches!(
217 self.selector,
218 Selector::TermAndLog | Selector::TermOnly | Selector::LogOnly
219 )
220 {
221 c.push(b'\n');
222 }
223 match self.selector {
224 Selector::TermAndLog => {
225 self.wterm(b"\n");
226 self.wlog(b"\n");
227 self.term_offset = 0;
228 self.file_offset = 0;
229 }
230 Selector::LogOnly => {
231 self.wlog(b"\n");
232 self.file_offset = 0;
233 }
234 Selector::TermOnly => {
235 self.wterm(b"\n");
236 self.term_offset = 0;
237 }
238 Selector::NoPrint | Selector::Pseudo | Selector::NewString => {}
239 Selector::File(n) => {
240 if let Some(Some(w)) = self.write_files.get_mut(n as usize) {
241 let _ = w.write_all(b"\n");
242 }
243 }
244 }
245 }
246
247 fn put_unit(&mut self, bytes: &[u8], term: bool, log: bool) {
250 let n = bytes.len();
251 if term {
252 if n > 1 && self.term_offset + n > self.max_print_line {
253 self.wterm(b"\n");
254 self.term_offset = 0;
255 }
256 self.wterm(bytes);
257 self.term_offset += n;
258 self.term_just_wrapped = false;
259 if self.term_offset >= self.max_print_line {
260 self.wterm(b"\n");
261 self.term_offset = 0;
262 self.term_just_wrapped = true;
263 }
264 }
265 if log {
266 let max = self.max_print_line + self.log_slack;
267 if n > 1 && self.file_offset + n > max {
268 self.wlog(b"\n");
269 self.file_offset = 0;
270 }
271 self.wlog(bytes);
272 let before = self.file_offset;
273 self.file_offset += n;
274 if self.file_offset >= max && before < max {
277 self.wlog(b"\n");
278 self.file_offset = 0;
279 }
280 }
281 }
282
283 pub fn print_char(&mut self, c: u32) {
286 if c as i32 == self.new_line_char && self.selector.is_output() {
287 self.print_ln();
288 return;
289 }
290 match self.selector {
291 Selector::TermAndLog | Selector::TermOnly | Selector::LogOnly => {
292 if let Some(cap) = self.capture.as_mut() {
293 utf8x::push(cap, c);
294 }
295 let (term, log) = match self.selector {
296 Selector::TermAndLog => (true, true),
297 Selector::TermOnly => (true, false),
298 _ => (false, true),
299 };
300 if c < 32 && c != 9 && c != 10 && c != 13 {
301 for b in [b'^', b'^', (c + 64) as u8] {
302 self.put_unit(&[b], term, log);
303 }
304 } else if c < 128 {
305 self.put_unit(&[c as u8], term, log);
306 } else {
307 let e = utf8x::encode(c);
308 self.put_unit(&e, term, log);
309 }
310 }
311 Selector::NoPrint => {}
312 Selector::Pseudo => {
313 if c < 32 && c != 9 && c != 10 && c != 13 {
314 for b in [b'^', b'^', (c + 64) as u8] {
315 self.pseudo_byte(b);
316 }
317 return;
318 }
319 let e = utf8x::encode(c);
320 for b in e {
321 self.pseudo_byte(b);
322 }
323 return;
324 }
325 Selector::NewString => utf8x::push(&mut self.cur_string, c),
326 Selector::File(n) => {
327 let e = utf8x::encode(c);
328 if let Some(Some(w)) = self.write_files.get_mut(n as usize) {
329 let _ = w.write_all(&e);
330 }
331 }
332 }
333 self.tally += 1;
334 }
335
336 fn pseudo_byte(&mut self, b: u8) {
337 if self.tally < self.trick_count {
338 let i = self.tally % self.error_line;
339 if self.trick_buf.len() <= i {
340 self.trick_buf.resize(i + 1, b' ');
341 }
342 self.trick_buf[i] = b;
343 }
344 self.tally += 1;
345 }
346
347 pub fn print_str(&mut self, s: &str) {
349 let slack = self.log_slack;
352 self.log_slack = 1;
353 for c in s.chars() {
354 self.print_char(c as u32);
355 }
356 self.log_slack = slack;
357 }
358
359 pub fn print_callback_bytes(&mut self, s: &[u8]) {
363 let (term, log) = match self.selector {
364 Selector::TermAndLog => (true, true),
365 Selector::TermOnly => (true, false),
366 Selector::LogOnly => (false, true),
367 _ => {
368 self.print_bytes(s);
369 return;
370 }
371 };
372 let mut i = 0;
373 while i < s.len() {
374 let (c, n) = utf8x::decode(s, i);
375 let unit = &s[i..i + n];
376 if log {
377 self.put_unit(unit, false, true);
378 }
379 if term {
380 if c < 32 {
381 for b in [b'^', b'^', (c + 64) as u8] {
382 self.put_unit(&[b], true, false);
383 }
384 } else {
385 self.put_unit(unit, true, false);
386 }
387 }
388 self.tally += 1;
389 i += n;
390 }
391 }
392
393 pub fn print_plain(&mut self, s: &str) {
396 for c in s.chars() {
397 self.print_char(c as u32);
398 }
399 }
400
401 pub fn print_bytes(&mut self, s: &[u8]) {
404 let mut i = 0;
405 while i < s.len() {
406 let (c, n) = utf8x::decode(s, i);
407 self.print_char(c);
408 i += n;
409 }
410 }
411
412 pub fn print_lua_bytes(&mut self, s: &[u8]) {
415 self.log_slack = 1;
416 self.print_bytes(s);
417 self.log_slack = 0;
418 }
419
420 pub fn luatex_warning(&mut self, category: &str, msg: &str) {
423 self.print_ln();
424 self.print_lua_bytes(format!("warning ({category}): {msg}").as_bytes());
425 self.print_ln();
426 }
427
428 pub fn print_nl(&mut self, s: &str) {
430 self.begin_line();
431 self.print_str(s);
432 }
433
434 pub fn begin_line(&mut self) {
437 let term = matches!(self.selector, Selector::TermOnly | Selector::TermAndLog);
438 let log = matches!(self.selector, Selector::LogOnly | Selector::TermAndLog);
439 if (term && self.term_offset > 0) || (log && self.file_offset > 0) {
440 self.print_ln();
441 }
442 }
443
444 pub fn print_int(&mut self, n: i64) {
446 self.print_plain(&n.to_string());
447 }
448
449 pub fn print_scaled(&mut self, s: i32) {
451 let mut out = String::new();
452 retex_arith::print_scaled(&mut out, s);
453 match out.find('.') {
456 Some(i) => {
457 self.print_plain(&out[..i]);
458 self.print_str(&out[i..]);
459 }
460 None => self.print_plain(&out),
461 }
462 }
463
464 pub fn print_hex(&mut self, n: i64) {
467 self.print_char('"' as u32);
468 self.print_plain(&format!("{n:X}"));
469 }
470
471 pub fn print_roman_int(&mut self, n: i64) {
474 self.print_plain(&roman(n));
475 }
476
477 pub fn take_string(&mut self) -> Vec<u8> {
479 std::mem::take(&mut self.cur_string)
480 }
481
482 pub fn flush(&mut self) {
484 let _ = self.term.flush();
485 if let Some(log) = self.log.as_mut() {
486 let _ = log.flush();
487 }
488 for w in self.write_files.iter_mut().flatten() {
489 let _ = w.flush();
490 }
491 }
492}
493
494pub fn roman(mut n: i64) -> String {
497 let table = b"m2d5c2l5x2v5i";
499 let mut out = String::new();
500 if n <= 0 {
501 return out;
502 }
503 let mut j = 0usize;
504 let mut v: i64 = 1000;
505 loop {
506 while n >= v {
507 out.push(table[j] as char);
508 n -= v;
509 }
510 if n <= 0 {
511 return out;
512 }
513 let mut k = j + 2;
514 let mut u = v / i64::from(table[k - 1] - b'0');
515 if table[k - 1] == b'2' {
516 k += 2;
517 u /= i64::from(table[k - 1] - b'0');
518 }
519 if n + u >= v {
520 out.push(table[k] as char);
521 n += u;
522 } else {
523 j += 2;
524 v /= i64::from(table[j - 1] - b'0');
525 }
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532 use std::cell::RefCell;
533 use std::rc::Rc;
534
535 #[derive(Clone, Default)]
537 struct Shared(Rc<RefCell<Vec<u8>>>);
538 impl Write for Shared {
539 fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
540 self.0.borrow_mut().extend_from_slice(b);
541 Ok(b.len())
542 }
543 fn flush(&mut self) -> std::io::Result<()> {
544 Ok(())
545 }
546 }
547
548 #[test]
549 fn romans() {
550 assert_eq!(roman(1984), "mcmlxxxiv");
551 assert_eq!(roman(4), "iv");
552 assert_eq!(roman(9), "ix");
553 assert_eq!(roman(0), "");
554 assert_eq!(roman(3999), "mmmcmxcix");
555 }
556
557 #[test]
558 fn control_chars_and_utf8() {
559 let buf = Shared::default();
560 let mut p = Printer::new(Box::new(buf.clone()));
561 for c in [1, 9, 0xE9, 127] {
562 p.print_char(c);
563 }
564 assert_eq!(&*buf.0.borrow(), "^^A\té\u{7f}".as_bytes());
565 }
566
567 #[test]
568 fn wraps_without_splitting_utf8() {
569 let buf = Shared::default();
570 let mut p = Printer::new(Box::new(buf.clone()));
571 for _ in 0..40 {
572 p.print_char(0xE9);
573 }
574 let s = String::from_utf8(buf.0.borrow().clone()).unwrap();
575 let lines: Vec<&str> = s.split('\n').collect();
576 assert_eq!(lines[0].chars().count(), 39);
577 assert_eq!(lines[1].chars().count(), 1);
578 }
579}