use std::collections::HashMap;
use anstyle::Style;
use pulldown_cmark::{Alignment, CowStr, LinkType};
#[derive(Debug, PartialEq)]
pub struct PendingLink<'a> {
pub(crate) link_type: LinkType,
pub(crate) dest_url: CowStr<'a>,
pub(crate) title: CowStr<'a>,
}
#[derive(Debug, PartialEq)]
pub struct LinkReferenceDefinition<'a> {
pub(crate) index: u16,
pub(crate) target: CowStr<'a>,
pub(crate) title: CowStr<'a>,
pub(crate) style: Style,
}
#[derive(Debug)]
pub struct CurrentLine {
pub(super) length: u16,
pub(super) trailing_space: Option<String>,
}
impl CurrentLine {
pub(super) fn empty() -> Self {
Self {
length: 0,
trailing_space: None,
}
}
}
#[derive(Debug)]
pub struct TableCell<'a> {
pub(super) fragments: Vec<(Style, CowStr<'a>)>,
}
impl TableCell<'_> {
pub(super) fn empty() -> Self {
Self {
fragments: Vec::new(),
}
}
pub(super) fn text_width(&self) -> usize {
self.fragments
.iter()
.map(|(_, t)| textwrap::core::display_width(t.as_ref()))
.sum()
}
}
#[derive(Debug)]
pub struct TableRow<'a> {
pub(super) cells: Vec<TableCell<'a>>,
pub(super) current_cell: TableCell<'a>,
}
impl TableRow<'_> {
pub(super) fn empty() -> Self {
Self {
cells: Vec::new(),
current_cell: TableCell::empty(),
}
}
}
#[derive(Debug)]
pub struct CurrentTable<'a> {
pub(super) head: Option<TableRow<'a>>,
pub(super) rows: Vec<TableRow<'a>>,
pub(super) current_row: TableRow<'a>,
pub(super) alignments: Vec<Alignment>,
pub(super) is_head: bool,
pub(super) strong_depth: u32,
pub(super) emphasis_depth: u32,
pub(super) strikethrough_depth: u32,
pub(super) span_style: Option<Style>,
}
impl<'a> CurrentTable<'a> {
pub(super) fn empty() -> Self {
Self {
head: None,
rows: Vec::new(),
current_row: TableRow::empty(),
alignments: Vec::new(),
is_head: true,
strong_depth: 0,
emphasis_depth: 0,
strikethrough_depth: 0,
span_style: None,
}
}
fn effective_style(&self, base: Style) -> Style {
let mut style = self.span_style.unwrap_or(base);
if self.strong_depth > 0 || self.is_head {
style = style.bold();
}
if self.emphasis_depth > 0 {
style = style.italic();
}
if self.strikethrough_depth > 0 {
style = style.strikethrough();
}
style
}
pub(super) fn push_text(mut self, text: CowStr<'a>) -> Self {
let style = self.effective_style(Style::new());
self.current_row.current_cell.fragments.push((style, text));
self
}
pub(super) fn push_styled_text(mut self, text: CowStr<'a>, base: Style) -> Self {
let style = self.effective_style(base);
self.current_row.current_cell.fragments.push((style, text));
self
}
pub(super) fn enter_strong(mut self) -> Self {
self.strong_depth += 1;
self
}
pub(super) fn exit_strong(mut self) -> Self {
self.strong_depth = self.strong_depth.saturating_sub(1);
self
}
pub(super) fn enter_emphasis(mut self) -> Self {
self.emphasis_depth += 1;
self
}
pub(super) fn exit_emphasis(mut self) -> Self {
self.emphasis_depth = self.emphasis_depth.saturating_sub(1);
self
}
pub(super) fn enter_strikethrough(mut self) -> Self {
self.strikethrough_depth += 1;
self
}
pub(super) fn exit_strikethrough(mut self) -> Self {
self.strikethrough_depth = self.strikethrough_depth.saturating_sub(1);
self
}
pub(super) fn enter_span(mut self, style: Style) -> Self {
self.span_style = Some(style);
self
}
pub(super) fn exit_span(mut self) -> Self {
self.span_style = None;
self
}
pub(super) fn end_cell(mut self) -> Self {
self.current_row.cells.push(self.current_row.current_cell);
self.current_row.current_cell = TableCell::empty();
self
}
pub(super) fn end_head(mut self) -> Self {
self.head = Some(self.current_row);
self.current_row = TableRow::empty();
self.is_head = false;
self
}
pub(super) fn end_row(mut self) -> Self {
self.rows.push(self.current_row);
self.current_row = TableRow::empty();
self
}
}
#[derive(Debug)]
pub struct StateData<'a> {
pub(super) pending_links: Vec<PendingLink<'a>>,
pub(super) pending_link_definitions: Vec<LinkReferenceDefinition<'a>>,
pub(super) next_link: u16,
pub(super) current_line: CurrentLine,
pub(super) current_table: CurrentTable<'a>,
pub(super) footnote_indices: HashMap<String, u16>,
pub(super) next_footnote: u16,
pub(super) footnote_definitions: Vec<(u16, String)>,
pub(super) current_footnote: Option<(u16, String)>,
}
impl<'a> StateData<'a> {
pub(crate) fn current_line(self, current_line: CurrentLine) -> Self {
Self {
current_line,
..self
}
}
pub(crate) fn push_pending_link(
mut self,
link_type: LinkType,
dest_url: CowStr<'a>,
title: CowStr<'a>,
) -> Self {
self.pending_links.push(PendingLink {
link_type,
dest_url,
title,
});
self
}
pub(crate) fn pop_pending_link(mut self) -> (Self, PendingLink<'a>) {
let link = self.pending_links.pop().unwrap();
(self, link)
}
pub(crate) fn add_link_reference(
mut self,
target: CowStr<'a>,
title: CowStr<'a>,
style: Style,
) -> (Self, u16) {
let index = self.next_link;
self.next_link += 1;
self.pending_link_definitions.push(LinkReferenceDefinition {
index,
target,
title,
style,
});
(self, index)
}
pub(crate) fn take_link_references(self) -> (Self, Vec<LinkReferenceDefinition<'a>>) {
let links = self.pending_link_definitions;
(
StateData {
pending_link_definitions: Vec::new(),
..self
},
links,
)
}
}
impl<'a> StateData<'a> {
pub(super) fn footnote_index(&mut self, label: &str) -> u16 {
if let Some(&idx) = self.footnote_indices.get(label) {
return idx;
}
let idx = self.next_footnote;
self.next_footnote += 1;
self.footnote_indices.insert(label.to_owned(), idx);
idx
}
pub(super) fn start_footnote_definition(&mut self, label: &str) {
let idx = self.footnote_index(label);
self.current_footnote = Some((idx, String::new()));
}
pub(super) fn append_footnote_text(&mut self, text: &str) {
if let Some((_, ref mut body)) = self.current_footnote {
body.push_str(text);
}
}
pub(super) fn end_footnote_definition(&mut self) {
if let Some((idx, body)) = self.current_footnote.take() {
self.footnote_definitions.push((idx, body));
}
}
}
impl Default for StateData<'_> {
fn default() -> Self {
StateData {
pending_links: Vec::new(),
pending_link_definitions: Vec::new(),
next_link: 1,
current_line: CurrentLine::empty(),
current_table: CurrentTable::empty(),
footnote_indices: HashMap::new(),
next_footnote: 1,
footnote_definitions: Vec::new(),
current_footnote: None,
}
}
}