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 }