/ src / printer.rs
printer.rs
  1  use crate::str_width;
  2  use crate::table::Table;
  3  use crate::termpix;
  4  use crate::words::Words;
  5  use ansi_term::Style;
  6  use console::AnsiCodeIterator;
  7  use image::{self, GenericImageView as _};
  8  use pulldown_cmark::{Alignment, BlockQuoteKind, CodeBlockKind, Event, HeadingLevel, Tag, TagEnd};
  9  use std::convert::{TryFrom, TryInto};
 10  use std::io::{Read as _, Write as _};
 11  use std::process::{Command, Stdio};
 12  use syncat_stylesheet::{Query, Stylesheet};
 13  
 14  #[derive(Debug, PartialEq)]
 15  enum Scope {
 16      Paper,
 17      Indent,
 18      Italic,
 19      Bold,
 20      Strikethrough,
 21      Link { dest_url: String, title: String },
 22      Caption,
 23      FootnoteDefinition,
 24      FootnoteReference,
 25      FootnoteContent,
 26      List(Option<u64>),
 27      DefinitionList,
 28      Term,
 29      Definition,
 30      ListItem(Option<u64>, bool),
 31      Code,
 32      CodeBlock(String),
 33      BlockQuote(Option<BlockQuoteKind>),
 34      Table(Vec<Alignment>),
 35      TableHead,
 36      TableRow,
 37      TableCell,
 38      Heading(HeadingLevel),
 39  }
 40  
 41  impl Scope {
 42      fn prefix_len(&self) -> usize {
 43          match self {
 44              Scope::Indent => 4,
 45              Scope::FootnoteContent => 4,
 46              Scope::ListItem(..) => 4,
 47              Scope::CodeBlock(..) => 2,
 48              Scope::BlockQuote(..) => 4,
 49              Scope::Heading(HeadingLevel::H2) => 5,
 50              Scope::Heading(..) => 4,
 51              _ => 0,
 52          }
 53      }
 54  
 55      fn prefix(&mut self) -> String {
 56          match self {
 57              Scope::Indent => "    ".to_owned(),
 58              Scope::FootnoteContent => "    ".to_owned(),
 59              Scope::ListItem(Some(index), handled) => {
 60                  if *handled {
 61                      "    ".to_owned()
 62                  } else {
 63                      *handled = true;
 64                      format!("{: <4}", format!("{}.", index))
 65                  }
 66              }
 67              Scope::ListItem(None, handled) => {
 68                  if *handled {
 69                      "    ".to_owned()
 70                  } else {
 71                      *handled = true;
 72                      "•   ".to_owned()
 73                  }
 74              }
 75              Scope::CodeBlock(..) => "  ".to_owned(),
 76              Scope::BlockQuote(..) => "┃   ".to_owned(),
 77              Scope::Heading(HeadingLevel::H2) => "├─── ".to_owned(),
 78              Scope::Heading(..) => "    ".to_owned(),
 79              Scope::Definition => "    ".to_owned(),
 80              _ => String::new(),
 81          }
 82      }
 83  
 84      fn suffix_len(&self) -> usize {
 85          match self {
 86              Scope::CodeBlock(..) => 2,
 87              Scope::Heading(HeadingLevel::H2) => 5,
 88              Scope::Heading(..) => 4,
 89              _ => 0,
 90          }
 91      }
 92  
 93      fn suffix(&mut self) -> String {
 94          match self {
 95              Scope::CodeBlock(..) => "  ".to_owned(),
 96              Scope::Heading(HeadingLevel::H2) => " ───┤".to_owned(),
 97              Scope::Heading(..) => "    ".to_owned(),
 98              _ => String::new(),
 99          }
100      }
101  
102      fn name(&self) -> &'static str {
103          use Scope::*;
104          match self {
105              Paper => "paper",
106              Indent => "indent",
107              Italic => "emphasis",
108              Bold => "strong",
109              Strikethrough => "strikethrough",
110              Link { .. } => "link",
111              Caption => "caption",
112              FootnoteDefinition => "footnote-def",
113              FootnoteReference => "footnote-ref",
114              FootnoteContent => "footnote",
115              List(Some(..)) => "ol",
116              List(None) => "ul",
117              DefinitionList => "dl",
118              Term => "dt",
119              Definition => "dd",
120              ListItem(..) => "li",
121              Code => "code",
122              CodeBlock(..) => "codeblock",
123              BlockQuote(None) => "blockquote",
124              BlockQuote(Some(BlockQuoteKind::Note)) => "note-blockquote",
125              BlockQuote(Some(BlockQuoteKind::Tip)) => "tip-blockquote",
126              BlockQuote(Some(BlockQuoteKind::Important)) => "important-blockquote",
127              BlockQuote(Some(BlockQuoteKind::Warning)) => "warning-blockquote",
128              BlockQuote(Some(BlockQuoteKind::Caution)) => "caution-blockquote",
129              Table(..) => "table",
130              TableHead => "th",
131              TableRow => "tr",
132              TableCell => "td",
133              Heading(HeadingLevel::H1) => "h1",
134              Heading(HeadingLevel::H2) => "h2",
135              Heading(HeadingLevel::H3) => "h3",
136              Heading(HeadingLevel::H4) => "h4",
137              Heading(HeadingLevel::H5) => "h5",
138              Heading(HeadingLevel::H6) => "h6",
139          }
140      }
141  }
142  
143  pub struct Printer<'a> {
144      centering: &'a str,
145      margin: &'a str,
146      stylesheet: &'a Stylesheet,
147      opts: &'a crate::Opts,
148      width: usize,
149      buffer: String,
150      table: (Vec<String>, Vec<Vec<String>>),
151      content: String,
152      scope: Vec<Scope>,
153      empty_queued: bool,
154  }
155  
156  impl<'a> Printer<'a> {
157      pub fn new(
158          centering: &'a str,
159          margin: &'a str,
160          width: usize,
161          stylesheet: &'a Stylesheet,
162          opts: &'a crate::Opts,
163      ) -> Printer<'a> {
164          Printer {
165              centering,
166              margin,
167              width,
168              stylesheet,
169              opts,
170              buffer: String::new(),
171              table: (vec![], vec![]),
172              content: String::new(),
173              scope: vec![Scope::Paper],
174              empty_queued: false,
175          }
176      }
177  
178      fn prefix_len(&self) -> usize {
179          self.scope
180              .iter()
181              .fold(0, |len, scope| len + scope.prefix_len())
182      }
183  
184      fn suffix_len(&self) -> usize {
185          self.scope
186              .iter()
187              .fold(0, |len, scope| len + scope.suffix_len())
188      }
189  
190      fn prefix(&mut self) -> (String, usize) {
191          self.prefix2(None)
192      }
193  
194      fn prefix2(&mut self, extra_scopes: Option<&[&str]>) -> (String, usize) {
195          let stylesheet = self.stylesheet;
196          self.scope
197              .iter_mut()
198              .scan(vec![], |scopes, scope| {
199                  scopes.push(scope.name());
200                  let prefix = scope.prefix();
201                  let mut all_scopes = scopes.clone();
202                  all_scopes.append(&mut extra_scopes.unwrap_or(&[]).to_vec());
203                  let style = Self::resolve_scopes(&stylesheet, &all_scopes, Some("prefix"));
204                  Some((format!("{}", style.paint(&prefix)), str_width(&prefix)))
205              })
206              .fold((String::new(), 0), |(s, c), (s2, c2)| (s + &s2, c + c2))
207      }
208  
209      fn suffix(&mut self) -> (String, usize) {
210          self.suffix2(None)
211      }
212  
213      fn suffix2(&mut self, extra_scopes: Option<&[&str]>) -> (String, usize) {
214          let stylesheet = self.stylesheet;
215          self.scope
216              .iter_mut()
217              .scan(vec![], |scopes, scope| {
218                  scopes.push(scope.name());
219                  let suffix = scope.suffix();
220                  let mut all_scopes = scopes.clone();
221                  all_scopes.append(&mut extra_scopes.unwrap_or(&[]).to_vec());
222                  let style = Self::resolve_scopes(&stylesheet, &all_scopes, Some("suffix"));
223                  Some((format!("{}", style.paint(&suffix)), str_width(&suffix)))
224              })
225              .fold((String::new(), 0), |(s, c), (s2, c2)| (s2 + &s, c + c2))
226      }
227  
228      fn style3(&self, extra_scopes: Option<&[&str]>, token: Option<&str>) -> Style {
229          let mut scope_names: Vec<_> = self.scope.iter().map(Scope::name).collect();
230          if let Some(extras) = extra_scopes {
231              scope_names.append(&mut extras.to_vec());
232          }
233          Self::resolve_scopes(&self.stylesheet, &scope_names, token)
234      }
235  
236      fn resolve_scopes(stylesheet: &Stylesheet, scopes: &[&str], token: Option<&str>) -> Style {
237          if scopes.is_empty() {
238              return Style::default();
239          }
240          let mut query = Query::new(scopes[0], token.unwrap_or(scopes[0]));
241          let mut index = vec![];
242          for scope in &scopes[1..] {
243              query[&index[..]].add_child(Query::new(*scope, token.unwrap_or(scope)));
244              index.push(0);
245          }
246          stylesheet
247              .style(&query)
248              .unwrap_or_default()
249              .try_into()
250              .unwrap_or_default()
251      }
252  
253      fn style2(&self, token: Option<&str>) -> Style {
254          self.style3(None, token)
255      }
256  
257      fn style(&self) -> Style {
258          self.style2(None)
259      }
260  
261      fn shadow(&self) -> String {
262          format!(
263              "{}",
264              Style::try_from(self.stylesheet.style(&"shadow".into()).unwrap_or_default())
265                  .unwrap_or_default()
266                  .paint(" ")
267          )
268      }
269  
270      fn paper_style(&self) -> Style {
271          Style::try_from(self.stylesheet.style(&"paper".into()).unwrap_or_default())
272              .unwrap_or_default()
273      }
274  
275      fn queue_empty(&mut self) {
276          self.empty_queued = true;
277      }
278  
279      fn empty(&mut self) {
280          let (prefix, prefix_len) = self.prefix();
281          let (suffix, suffix_len) = self.suffix();
282          println!(
283              "{}{}{}{}{}{}{}",
284              self.centering,
285              self.margin,
286              prefix,
287              self.paper_style().paint(
288                  " ".repeat(
289                      self.width
290                          .saturating_sub(prefix_len)
291                          .saturating_sub(suffix_len)
292                  )
293              ),
294              suffix,
295              self.margin,
296              self.shadow(),
297          );
298          self.empty_queued = false;
299      }
300  
301      fn print_rule(&mut self) {
302          let (prefix, prefix_len) = self.prefix();
303          let (suffix, suffix_len) = self.suffix();
304          println!(
305              "{}{}{}{}{}{}{}",
306              self.centering,
307              self.margin,
308              prefix,
309              self.style().paint(
310                  "─".repeat(
311                      self.width
312                          .saturating_sub(prefix_len)
313                          .saturating_sub(suffix_len)
314                  )
315              ),
316              suffix,
317              self.margin,
318              self.shadow(),
319          );
320      }
321  
322      fn print_table(&mut self) {
323          let alignments = if let Some(Scope::Table(alignments)) = self.scope.last() {
324              alignments
325          } else {
326              return;
327          };
328          let (heading, rows) = std::mem::replace(&mut self.table, (vec![], vec![]));
329          let available_width = self
330              .width
331              .saturating_sub(self.prefix_len())
332              .saturating_sub(self.suffix_len());
333          let table_str =
334              Table::new(heading, rows, available_width).print(self.paper_style(), alignments);
335          for line in table_str.lines() {
336              let (prefix, _) = self.prefix();
337              let (suffix, _) = self.suffix();
338              println!(
339                  "{}{}{}{}{}{}{}{}",
340                  self.centering,
341                  self.margin,
342                  line,
343                  prefix,
344                  self.paper_style()
345                      .paint(" ".repeat(available_width.saturating_sub(str_width(line)))),
346                  suffix,
347                  self.margin,
348                  self.shadow(),
349              );
350          }
351      }
352  
353      fn flush_buffer(&mut self) {
354          match self.scope.last() {
355              Some(Scope::CodeBlock(lang)) => {
356                  let language_context = if lang.is_empty() || !self.opts.syncat {
357                      String::from("txt")
358                  } else {
359                      lang.to_owned()
360                  };
361                  let style = self.style3(Some(&[&language_context[..]]), None);
362                  let lang = lang.to_owned();
363                  let mut first_prefix = Some(self.prefix2(Some(&[&language_context[..]])));
364                  let mut first_suffix = Some(self.suffix2(Some(&[&language_context[..]])));
365  
366                  let available_width = self
367                      .width
368                      .saturating_sub(first_prefix.as_ref().unwrap().1)
369                      .saturating_sub(first_suffix.as_ref().unwrap().1);
370                  let buffer = std::mem::replace(&mut self.buffer, String::new());
371                  let buffer = if self.opts.syncat {
372                      let syncat = Command::new("syncat")
373                          .args(&["-l", &lang, "-w", &available_width.to_string()])
374                          .stdin(Stdio::piped())
375                          .stdout(Stdio::piped())
376                          .spawn();
377                      match syncat {
378                          Ok(syncat) => {
379                              {
380                                  let mut stdin = syncat.stdin.unwrap();
381                                  write!(stdin, "{}", buffer).unwrap();
382                              }
383                              let mut output = String::new();
384                              syncat.stdout.unwrap().read_to_string(&mut output).unwrap();
385                              output
386                          }
387                          Err(error) => {
388                              eprintln!("{}", error);
389                              buffer.to_owned()
390                          }
391                      }
392                  } else {
393                      buffer
394                          .lines()
395                          .map(|mut line| {
396                              let mut output = String::new();
397                              while str_width(&line) > available_width {
398                                  let not_too_wide = {
399                                      let mut acc = 0;
400                                      move |ch: &char| {
401                                          acc += str_width(&ch.to_string());
402                                          acc < available_width
403                                      }
404                                  };
405                                  let prefix =
406                                      line.chars().take_while(not_too_wide).collect::<String>();
407                                  output = format!("{}{}\n", output, prefix);
408                                  line = &line[prefix.len()..];
409                              }
410                              format!(
411                                  "{}{}{}\n",
412                                  output,
413                                  line,
414                                  " ".repeat(available_width.saturating_sub(str_width(&line)))
415                              )
416                          })
417                          .collect()
418                  };
419  
420                  let (prefix, _) = first_prefix
421                      .take()
422                      .unwrap_or_else(|| self.prefix2(Some(&[&language_context[..]])));
423                  let (suffix, _) = first_suffix
424                      .take()
425                      .unwrap_or_else(|| self.suffix2(Some(&[&language_context[..]])));
426                  println!(
427                      "{}{}{}{}{}{}{}",
428                      self.centering,
429                      self.margin,
430                      prefix,
431                      style.paint(" ".repeat(available_width)),
432                      suffix,
433                      self.margin,
434                      self.shadow(),
435                  );
436  
437                  for line in buffer.lines() {
438                      let width = str_width(line);
439                      let (prefix, _) = self.prefix2(Some(&[&language_context[..]]));
440                      let (suffix, _) = self.suffix2(Some(&[&language_context[..]]));
441                      print!(
442                          "{}{}{}{}",
443                          self.centering,
444                          self.margin,
445                          prefix,
446                          style.prefix(),
447                      );
448                      for (s, is_ansi) in AnsiCodeIterator::new(line) {
449                          if is_ansi {
450                              if s == "\u{1b}[0m" {
451                                  print!("{}{}", s, style.prefix());
452                              } else {
453                                  print!("{}{}", style.prefix(), s);
454                              }
455                          } else {
456                              print!("{}", s);
457                          }
458                      }
459                      println!(
460                          "{}{}{}{}",
461                          style.paint(" ".repeat(available_width.saturating_sub(width))),
462                          suffix,
463                          self.margin,
464                          self.shadow(),
465                      );
466                  }
467  
468                  let (prefix, _) = first_prefix
469                      .take()
470                      .unwrap_or_else(|| self.prefix2(Some(&[&language_context[..]])));
471                  let (suffix, _) = first_suffix
472                      .take()
473                      .unwrap_or_else(|| self.suffix2(Some(&[&language_context[..]])));
474                  println!(
475                      "{}{}{}{}{}{}{}",
476                      self.centering,
477                      self.margin,
478                      prefix,
479                      format!(
480                          "{}{}",
481                          style.paint(" ".repeat(available_width.saturating_sub(str_width(&lang)))),
482                          self.style3(Some(&[&language_context[..]]), Some("lang-tag"))
483                              .paint(lang)
484                      ),
485                      suffix,
486                      self.margin,
487                      self.shadow(),
488                  );
489              }
490              _ => {}
491          }
492      }
493  
494      fn flush(&mut self) {
495          if !self.buffer.is_empty() {
496              return;
497          }
498          if self
499              .scope
500              .iter()
501              .find(|scope| {
502                  if let Scope::Table(..) = scope {
503                      true
504                  } else {
505                      false
506                  }
507              })
508              .is_some()
509          {
510              return;
511          }
512          if self.content.is_empty() {
513              return;
514          }
515          let (prefix, prefix_len) = self.prefix();
516          let (suffix, suffix_len) = self.suffix();
517          println!(
518              "{}{}{}{}{}{}{}{}",
519              self.centering,
520              self.margin,
521              prefix,
522              self.content,
523              suffix,
524              self.paper_style().paint(
525                  " ".repeat(
526                      self.width
527                          .saturating_sub(str_width(&self.content))
528                          .saturating_sub(prefix_len)
529                          .saturating_sub(suffix_len)
530                  )
531              ),
532              self.margin,
533              self.shadow(),
534          );
535          self.content.clear();
536      }
537  
538      fn target(&mut self) -> &mut String {
539          if self
540              .scope
541              .iter()
542              .find(|scope| *scope == &Scope::TableHead)
543              .is_some()
544          {
545              self.table.0.last_mut().unwrap()
546          } else if self
547              .scope
548              .iter()
549              .find(|scope| *scope == &Scope::TableRow)
550              .is_some()
551          {
552              self.table.1.last_mut().unwrap().last_mut().unwrap()
553          } else {
554              &mut self.content
555          }
556      }
557  
558      fn handle_text<S>(&mut self, text: S)
559      where
560          S: AsRef<str>,
561      {
562          let s = text.as_ref();
563          if let Some(Scope::CodeBlock(..)) = self.scope.last() {
564              self.buffer += s;
565              return;
566          }
567          let style = self.style();
568          for word in Words::new(s) {
569              if str_width(&self.content) + word.len() + self.prefix_len() + self.suffix_len()
570                  > self.width
571              {
572                  self.flush();
573              }
574              let mut word = if self.target().is_empty() {
575                  word.trim()
576              } else {
577                  &word
578              };
579              let available_len = self
580                  .width
581                  .saturating_sub(self.prefix_len())
582                  .saturating_sub(self.suffix_len());
583              while str_width(&self.content) + str_width(&word) > available_len {
584                  let part = word.chars().take(available_len).collect::<String>();
585                  self.target().push_str(&format!("{}", style.paint(&part)));
586                  word = &word[part.len()..];
587                  self.flush();
588              }
589              self.target().push_str(&format!("{}", style.paint(word)));
590          }
591      }
592  
593      pub fn handle(&mut self, event: Event) {
594          match event {
595              Event::Start(tag) => {
596                  if self.empty_queued {
597                      // TODO: queue an empty after an item's initial text when there's a block
598                      self.empty();
599                  }
600                  match tag {
601                      Tag::MetadataBlock(..) => self.scope.push(Scope::CodeBlock("".to_owned())),
602                      Tag::HtmlBlock => {}
603                      Tag::Paragraph => {
604                          self.flush();
605                      }
606                      Tag::Heading {
607                          level: HeadingLevel::H1,
608                          ..
609                      } => {
610                          self.flush();
611                          self.print_rule();
612                          self.scope.push(Scope::Heading(HeadingLevel::H1));
613                      }
614                      Tag::Heading { level, .. } => {
615                          self.flush();
616                          self.scope.push(Scope::Heading(level));
617                      }
618                      Tag::BlockQuote(kind) => {
619                          self.flush();
620                          self.scope.push(Scope::BlockQuote(kind));
621                          match kind {
622                              None => {}
623                              Some(BlockQuoteKind::Note) => {
624                                  let style = Self::resolve_scopes(
625                                      &self.stylesheet,
626                                      &["note-blockquote"],
627                                      Some("prefix"),
628                                  );
629                                  self.handle_text(&format!(
630                                      "{} {}",
631                                      style.paint("󰋽"),
632                                      style.paint("Note")
633                                  ));
634                              }
635                              Some(BlockQuoteKind::Tip) => {
636                                  let style = Self::resolve_scopes(
637                                      &self.stylesheet,
638                                      &["tip-blockquote"],
639                                      Some("prefix"),
640                                  );
641                                  self.handle_text(&format!(
642                                      "{} {}",
643                                      style.paint("󰌶"),
644                                      style.paint("Tip")
645                                  ));
646                              }
647                              Some(BlockQuoteKind::Important) => {
648                                  let style = Self::resolve_scopes(
649                                      &self.stylesheet,
650                                      &["important-blockquote"],
651                                      Some("prefix"),
652                                  );
653                                  self.handle_text(&format!(
654                                      "{} {}",
655                                      style.paint("󱋉"),
656                                      style.paint("Important")
657                                  ));
658                              }
659                              Some(BlockQuoteKind::Warning) => {
660                                  let style = Self::resolve_scopes(
661                                      &self.stylesheet,
662                                      &["warning-blockquote"],
663                                      Some("prefix"),
664                                  );
665                                  self.handle_text(&format!(
666                                      "{} {}",
667                                      style.paint("󰀪"),
668                                      style.paint("Warning")
669                                  ));
670                              }
671                              Some(BlockQuoteKind::Caution) => {
672                                  let style = Self::resolve_scopes(
673                                      &self.stylesheet,
674                                      &["caution-blockquote"],
675                                      Some("prefix"),
676                                  );
677                                  self.handle_text(&format!(
678                                      "{} {}",
679                                      style.paint("󰳦"),
680                                      style.paint("Caution")
681                                  ));
682                              }
683                          }
684                      }
685                      Tag::CodeBlock(CodeBlockKind::Indented) => {
686                          self.flush();
687                          self.scope.push(Scope::CodeBlock("".to_owned()));
688                      }
689                      Tag::CodeBlock(CodeBlockKind::Fenced(language)) => {
690                          self.flush();
691                          self.scope.push(Scope::CodeBlock(language.into_string()));
692                      }
693                      Tag::List(start_index) => {
694                          self.flush();
695                          self.scope.push(Scope::List(start_index));
696                      }
697                      Tag::DefinitionList => {
698                          self.flush();
699                          self.scope.push(Scope::DefinitionList);
700                      }
701                      Tag::DefinitionListTitle => {
702                          self.flush();
703                          self.scope.push(Scope::Term);
704                      }
705                      Tag::DefinitionListDefinition => {
706                          self.flush();
707                          self.scope.push(Scope::Definition);
708                      }
709                      Tag::Item => {
710                          self.flush();
711                          if let Some(&Scope::List(index)) = self.scope.last() {
712                              self.scope.push(Scope::ListItem(index, false));
713                          } else {
714                              self.scope.push(Scope::ListItem(None, false));
715                          }
716                      }
717                      Tag::FootnoteDefinition(text) => {
718                          self.flush();
719                          self.scope.push(Scope::FootnoteDefinition);
720                          self.handle_text(&format!("{}:", text));
721                          self.scope.pop();
722                          self.flush();
723                          self.scope.push(Scope::FootnoteContent);
724                      }
725                      Tag::Table(columns) => self.scope.push(Scope::Table(columns)),
726                      Tag::TableHead => {
727                          self.scope.push(Scope::TableHead);
728                      }
729                      Tag::TableRow => {
730                          self.scope.push(Scope::TableRow);
731                          self.table.1.push(vec![]);
732                      }
733                      Tag::TableCell => {
734                          self.scope.push(Scope::TableCell);
735                          if self
736                              .scope
737                              .iter()
738                              .find(|scope| *scope == &Scope::TableHead)
739                              .is_some()
740                          {
741                              self.table.0.push(String::new());
742                          } else {
743                              self.table.1.last_mut().unwrap().push(String::new());
744                          }
745                      }
746                      Tag::Emphasis => {
747                          self.scope.push(Scope::Italic);
748                      }
749                      Tag::Strong => {
750                          self.scope.push(Scope::Bold);
751                      }
752                      Tag::Strikethrough => {
753                          self.scope.push(Scope::Strikethrough);
754                      }
755                      Tag::Link {
756                          dest_url, title, ..
757                      } => {
758                          self.scope.push(Scope::Link {
759                              dest_url: dest_url.into_string(),
760                              title: title.into_string(),
761                          });
762                      }
763                      Tag::Image {
764                          dest_url, title, ..
765                      } => {
766                          self.flush();
767  
768                          if !self.opts.no_images {
769                              let available_width = self
770                                  .width
771                                  .saturating_sub(self.prefix_len())
772                                  .saturating_sub(self.suffix_len());
773                              match image::open(dest_url.as_ref()) {
774                                  Ok(image) => {
775                                      let (mut width, mut height) = image.dimensions();
776                                      if width > available_width as u32 {
777                                          let scale = available_width as f64 / width as f64;
778                                          width = (width as f64 * scale) as u32;
779                                          height = (height as f64 * scale) as u32;
780                                      }
781                                      let mut vec = vec![];
782                                      termpix::print_image(image, true, width, height, &mut vec);
783                                      let string = String::from_utf8(vec).unwrap();
784  
785                                      for line in string.lines() {
786                                          let (prefix, _) = self.prefix();
787                                          let (suffix, _) = self.suffix();
788                                          println!(
789                                              "{}{}{}{}{}{}{}",
790                                              self.centering,
791                                              self.margin,
792                                              prefix,
793                                              line,
794                                              suffix,
795                                              self.margin,
796                                              self.shadow(),
797                                          );
798                                      }
799  
800                                      self.scope.push(Scope::Indent);
801                                      self.scope.push(Scope::Caption);
802                                      self.handle_text(title);
803                                  }
804                                  Err(error) => {
805                                      self.handle_text("Cannot open image ");
806                                      self.scope.push(Scope::Indent);
807                                      self.scope.push(Scope::Link {
808                                          dest_url: "".to_owned(),
809                                          title: "".to_owned(),
810                                      });
811                                      self.handle_text(dest_url);
812                                      self.scope.pop();
813                                      self.handle_text(&format!(": {}", error));
814                                      self.scope.push(Scope::Caption);
815                                      self.flush();
816                                  }
817                              }
818                          } else {
819                              self.scope.push(Scope::Indent);
820                              self.handle_text("[Image");
821                              if !title.is_empty() {
822                                  self.handle_text(": ");
823                                  self.scope.push(Scope::Caption);
824                                  self.handle_text(title);
825                                  self.scope.pop();
826                              }
827                              if !dest_url.is_empty() && !self.opts.hide_urls {
828                                  self.handle_text(" <");
829                                  self.scope.push(Scope::Link {
830                                      dest_url: "".to_owned(),
831                                      title: "".to_owned(),
832                                  });
833                                  self.handle_text(dest_url);
834                                  self.scope.pop();
835                                  self.handle_text(">");
836                              }
837                              self.handle_text("]");
838                              self.scope.push(Scope::Caption);
839                              self.flush();
840                          }
841                      }
842                  }
843              }
844  
845              Event::End(tag) => match tag {
846                  TagEnd::Paragraph => {
847                      self.flush();
848                      self.queue_empty();
849                  }
850                  TagEnd::Heading(HeadingLevel::H1) => {
851                      self.flush();
852                      self.scope.pop();
853                      self.print_rule();
854                      self.queue_empty();
855                  }
856                  TagEnd::Heading(_) => {
857                      self.flush();
858                      self.scope.pop();
859                      self.queue_empty();
860                  }
861                  TagEnd::List(..) => {
862                      self.flush();
863                      self.scope.pop();
864                      self.queue_empty();
865                  }
866                  TagEnd::DefinitionList => {
867                      self.flush();
868                      self.scope.pop();
869                      self.queue_empty();
870                  }
871                  TagEnd::DefinitionListTitle => {
872                      self.flush();
873                      self.scope.pop();
874                  }
875                  TagEnd::DefinitionListDefinition => {
876                      self.flush();
877                      self.scope.pop();
878                      self.queue_empty();
879                  }
880                  TagEnd::Item => {
881                      self.flush();
882                      self.scope.pop();
883                      if let Some(Scope::List(index)) = self.scope.last_mut() {
884                          *index = index.map(|x| x + 1);
885                      }
886                  }
887                  TagEnd::BlockQuote(..) => {
888                      self.flush();
889                      self.scope.pop();
890                      self.queue_empty();
891                  }
892                  TagEnd::Table => {
893                      self.print_table();
894                      self.scope.pop();
895                      self.queue_empty();
896                  }
897                  TagEnd::HtmlBlock => {}
898                  TagEnd::CodeBlock => {
899                      self.flush_buffer();
900                      self.scope.pop();
901                      self.queue_empty();
902                  }
903                  TagEnd::Link => {
904                      let Scope::Link { dest_url, title } = self.scope.pop().unwrap() else {
905                          panic!()
906                      };
907                      if !title.is_empty() && !dest_url.is_empty() && !self.opts.hide_urls {
908                          self.handle_text(format!(" <{}: {}>", title, dest_url));
909                      } else if !dest_url.is_empty() && !self.opts.hide_urls {
910                          self.handle_text(format!(" <{}>", dest_url));
911                      } else if !title.is_empty() {
912                          self.handle_text(format!(" <{}>", title));
913                      }
914                  }
915                  TagEnd::Image => {
916                      self.flush();
917                      self.scope.pop();
918                      self.scope.pop();
919                      self.queue_empty();
920                  }
921                  TagEnd::FootnoteDefinition => {
922                      self.flush();
923                      self.scope.pop();
924                      self.queue_empty();
925                  }
926                  _ => {
927                      self.scope.pop();
928                  }
929              },
930              Event::Rule => {
931                  self.flush();
932                  self.print_rule();
933              }
934              Event::Text(text) => {
935                  self.handle_text(text);
936              }
937              Event::Code(text) => {
938                  self.scope.push(Scope::Code);
939                  self.handle_text(text);
940                  self.scope.pop();
941              }
942              Event::Html(_text) => { /* not rendered */ }
943              Event::InlineHtml(_text) => { /* not rendered */ }
944              Event::InlineMath(text) | Event::DisplayMath(text) => {
945                  self.scope.push(Scope::Code);
946                  self.handle_text(text);
947                  self.scope.pop();
948              }
949              Event::FootnoteReference(text) => {
950                  self.scope.push(Scope::FootnoteReference);
951                  self.handle_text(&format!("[{}]", text));
952                  self.scope.pop();
953              }
954              Event::SoftBreak => {
955                  self.handle_text(" ");
956              }
957              Event::HardBreak => {
958                  self.flush();
959              }
960              Event::TaskListMarker(checked) => {
961                  self.handle_text(if checked { "[✓] " } else { "[ ] " });
962              }
963          }
964      }
965  }