use crate::Colour;
use crate::WhiteSpace;
use crate::WhitespaceExt as _;
use crate::config::ImageRenderMode;
use super::Renderer;
use super::Result;
use super::TooNarrow;
use std::collections::VecDeque;
use std::mem;
use std::ops::Deref;
use std::ops::DerefMut;
use std::vec;
use std::{collections::LinkedList, fmt::Debug};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
#[derive(Debug)]
pub(crate) struct TextRenderer<D: TextDecorator> {
subrender: Vec<SubRenderer<D>>,
links: Vec<String>,
}
impl<D: TextDecorator> Deref for TextRenderer<D> {
type Target = SubRenderer<D>;
fn deref(&self) -> &Self::Target {
self.subrender.last().expect("Underflow in renderer stack")
}
}
impl<D: TextDecorator> DerefMut for TextRenderer<D> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.subrender
.last_mut()
.expect("Underflow in renderer stack")
}
}
impl<D: TextDecorator> TextRenderer<D> {
pub fn new(subrenderer: SubRenderer<D>) -> TextRenderer<D> {
TextRenderer {
subrender: vec![subrenderer],
links: Vec::new(),
}
}
pub fn start_link(&mut self, target: &str) -> Result<()> {
self.links.push(target.to_string());
self.subrender.last_mut().unwrap().start_link(target)?;
Ok(())
}
pub fn end_link(&mut self) -> Result<()> {
self.subrender.last_mut().unwrap().end_link()?;
if self.options.include_link_footnotes {
let footnote_num = self.links.len();
self.add_inline_text(&format!("[{}]", footnote_num))?;
}
Ok(())
}
pub fn push(&mut self, builder: SubRenderer<D>) {
self.subrender.push(builder);
}
pub fn pop(&mut self) -> SubRenderer<D> {
self.subrender
.pop()
.expect("Attempt to pop a subrender from empty stack")
}
pub fn into_inner(mut self) -> (SubRenderer<D>, Vec<String>) {
assert_eq!(self.subrender.len(), 1);
(
self.subrender
.pop()
.expect("Attempt to pop a subrenderer from an empty stack"),
self.links,
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TaggedString<T> {
pub s: String,
pub tag: T,
}
impl<T> TaggedString<T> {
pub fn width(&self) -> usize {
self.s.width()
}
}
impl<T> TaggedString<T> {
pub fn map_tag<U>(self, f: &impl Fn(T) -> U) -> TaggedString<U> {
TaggedString {
s: self.s,
tag: f(self.tag),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TaggedLineElement<T> {
Str(TaggedString<T>),
FragmentStart(String),
}
impl<T> TaggedLineElement<T> {
fn has_content(&self) -> bool {
match self {
TaggedLineElement::Str(_) => true,
TaggedLineElement::FragmentStart(_) => false,
}
}
pub fn map_tag<U>(self, f: &impl Fn(T) -> U) -> TaggedLineElement<U> {
match self {
TaggedLineElement::Str(ts) => TaggedLineElement::Str(ts.map_tag(f)),
TaggedLineElement::FragmentStart(s) => TaggedLineElement::FragmentStart(s.clone()),
}
}
pub fn width(&self) -> usize {
match self {
TaggedLineElement::Str(tagged_string) => tagged_string.width(),
TaggedLineElement::FragmentStart(_) => 0usize,
}
}
pub fn as_str(&self) -> &str {
match self {
TaggedLineElement::Str(tagged_string) => &tagged_string.s,
TaggedLineElement::FragmentStart(_) => "",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TaggedLine<T> {
v: Vec<TaggedLineElement<T>>,
len: usize,
}
impl<T: Debug + Eq + PartialEq + Clone + Default> Default for TaggedLine<T> {
fn default() -> Self {
TaggedLine::new()
}
}
impl<T: Debug + Eq + PartialEq + Clone + Default> TaggedLine<T> {
pub fn new() -> TaggedLine<T> {
TaggedLine {
v: Vec::new(),
len: 0,
}
}
pub fn from_string(s: String, tag: &T) -> TaggedLine<T> {
let len = UnicodeWidthStr::width(s.as_str());
TaggedLine {
v: vec![TaggedLineElement::Str(TaggedString {
s,
tag: tag.clone(),
})],
len,
}
}
#[allow(clippy::inherent_to_string)]
fn to_string(&self) -> String {
let mut s = String::new();
for tle in &self.v {
if let TaggedLineElement::Str(ts) = tle {
s.push_str(&ts.s);
}
}
s
}
fn is_empty(&self) -> bool {
for elt in &self.v {
if elt.has_content() {
return false;
}
}
true
}
fn push_str(&mut self, ts: TaggedString<T>) {
use self::TaggedLineElement::Str;
if !ts.s.is_empty() {
self.len += UnicodeWidthStr::width(ts.s.as_str());
if let Some(Str(ts_prev)) = self.v.last_mut() {
if ts_prev.tag == ts.tag {
ts_prev.s.push_str(&ts.s);
return;
}
}
self.v.push(Str(ts));
}
}
fn push(&mut self, tle: TaggedLineElement<T>) {
use self::TaggedLineElement::Str;
if let Str(ts) = tle {
self.push_str(ts);
} else {
self.v.push(tle);
}
}
fn push_ws(&mut self, len: usize, tag: &T) {
use self::TaggedLineElement::Str;
self.push(Str(TaggedString {
s: " ".repeat(len),
tag: tag.clone(),
}));
}
fn insert_front(&mut self, ts: TaggedString<T>) {
use self::TaggedLineElement::Str;
self.len += UnicodeWidthStr::width(ts.s.as_str());
if let Some(Str(ts1)) = self.v.get_mut(0) {
if ts1.tag == ts.tag {
ts1.s.insert_str(0, &ts.s);
return;
}
}
self.v.insert(0, Str(ts));
}
fn push_char(&mut self, c: char, tag: &T) {
use self::TaggedLineElement::Str;
self.len += UnicodeWidthChar::width(c).unwrap_or(0);
if let Some(Str(ts_prev)) = self.v.last_mut() {
if ts_prev.tag == *tag {
ts_prev.s.push(c);
return;
}
}
let mut s = String::new();
s.push(c);
self.v.push(Str(TaggedString {
s,
tag: tag.clone(),
}));
}
fn consume(&mut self, tl: &mut TaggedLine<T>) {
for ts in tl.v.drain(..) {
self.push(ts);
}
}
pub fn map_tag<U>(self, f: &impl Fn(T) -> U) -> TaggedLine<U> {
TaggedLine {
v: self.v.into_iter().map(|e| e.map_tag(f)).collect(),
len: self.len,
}
}
fn remove_items(&mut self) -> impl Iterator<Item = TaggedLineElement<T>> + use<T> {
self.len = 0;
std::mem::take(&mut self.v).into_iter()
}
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
use self::TaggedLineElement::Str;
self.v.iter().flat_map(|tle| {
if let Str(ts) = tle {
ts.s.chars()
} else {
"".chars()
}
})
}
pub fn iter(&self) -> impl Iterator<Item = &TaggedLineElement<T>> + '_ {
self.v.iter()
}
pub fn tagged_strings(&self) -> impl Iterator<Item = &TaggedString<T>> {
self.v.iter().filter_map(|tle| match tle {
TaggedLineElement::Str(ts) => Some(ts),
_ => None,
})
}
fn into_tagged_strings(self) -> impl Iterator<Item = TaggedString<T>> {
self.v.into_iter().filter_map(|tle| match tle {
TaggedLineElement::Str(ts) => Some(ts),
_ => None,
})
}
fn width(&self) -> usize {
let result = self.tagged_strings().map(TaggedString::width).sum();
debug_assert_eq!(self.len, result);
result
}
fn pad_to(&mut self, width: usize, tag: &T) {
let my_width = self.width();
if width > my_width {
self.push_ws(width - my_width, tag);
}
}
fn remove_leading_whitespace(&mut self) {
let mut pieces_to_remove = 0;
let mut width_removed = 0;
for element in &mut self.v {
if let TaggedLineElement::Str(piece) = element {
let trimmed = piece.s.trim_start();
let tlen = trimmed.len();
let toffset = piece.s.len() - tlen;
if toffset == 0 {
break;
}
if tlen == 0 {
pieces_to_remove += 1;
width_removed += piece.width();
} else {
let orig_width = piece.width();
piece.s.replace_range(0..toffset, "");
let new_width = piece.width();
width_removed += orig_width - new_width;
break;
}
} else {
break;
}
}
if pieces_to_remove > 0 {
self.v = self.v.split_off(pieces_to_remove);
}
self.len -= width_removed;
}
fn remove_trailing_spaces(&mut self) {
while let Some(TaggedLineElement::Str(piece)) = self.v.last_mut() {
let trimmed = piece.s.trim_end_matches(' ');
let tlen = trimmed.len();
if tlen == 0 {
self.len -= piece.width();
self.v.pop();
} else if tlen == piece.s.len() {
break;
} else {
self.len -= piece.width() - trimmed.width();
piece.s.replace_range(tlen.., "");
break;
}
}
}
}
impl<T> IntoIterator for TaggedLine<T> {
type Item = TaggedLineElement<T>;
type IntoIter = <Vec<TaggedLineElement<T>> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.v.into_iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct WithWhiteSpace<T>(T, WhiteSpace);
#[derive(Debug, Clone)]
struct WrappedBlock<T> {
width: usize,
text: Vec<TaggedLine<T>>,
line: TaggedLine<WithWhiteSpace<T>>,
spacetag: Option<WithWhiteSpace<T>>, word: TaggedLine<WithWhiteSpace<T>>, wordlen: usize,
wslen: usize,
pre_wrapped: bool, pad_blocks: bool,
allow_overflow: bool,
default_tag: T,
}
impl<T: Clone + Eq + Debug + Default> WrappedBlock<T> {
pub fn new(
width: usize,
pad_blocks: bool,
allow_overflow: bool,
default_tag: T,
) -> WrappedBlock<T> {
WrappedBlock {
width,
text: Vec::new(),
line: TaggedLine::new(),
spacetag: None,
word: TaggedLine::new(),
wordlen: 0,
wslen: 0,
pre_wrapped: false,
pad_blocks,
allow_overflow,
default_tag,
}
}
fn flush_word(&mut self) -> Result<()> {
use self::TaggedLineElement::Str;
html_trace_quiet!(
"flush_word: word={:?}, linelen={}",
self.word,
self.line.len
);
if !self.word.is_empty() {
let ws_mode = self
.word
.v
.iter()
.find_map(|e| match e {
TaggedLineElement::Str(ts) => Some(ts.tag.1),
_ => None,
})
.unwrap_or(WhiteSpace::Normal);
self.pre_wrapped = false;
let space_in_line = self.width - self.line.len;
let space_needed = self.wslen + self.wordlen;
if space_needed <= space_in_line {
html_trace!("Got enough space");
if self.wslen > 0 {
self.line.push(Str(TaggedString {
s: " ".repeat(self.wslen),
tag: self.spacetag.take().unwrap(),
}));
self.wslen = 0;
}
self.line.consume(&mut self.word);
html_trace!("linelen increased by wordlen to {}", self.line.len);
} else {
html_trace!("Not enough space");
match self.spacetag {
Some(WithWhiteSpace(_, WhiteSpace::Pre)) => {
if self.wslen >= space_in_line {
self.wslen -= space_in_line;
} else if self.wslen > 0 {
self.line
.push_ws(self.wslen, &self.spacetag.take().unwrap());
self.wslen = 0;
}
}
Some(WithWhiteSpace(_, WhiteSpace::Normal)) => {
self.spacetag = None;
self.wslen = 0;
}
Some(WithWhiteSpace(_, WhiteSpace::PreWrap)) => {
self.spacetag = None;
self.wslen = 0;
self.word.remove_leading_whitespace();
self.line.remove_trailing_spaces();
}
None => (),
}
self.flush_line();
if ws_mode == WhiteSpace::Pre {
self.pre_wrapped = true;
}
while self.wslen > 0 {
let to_copy = self.wslen.min(self.width);
self.line.push_ws(to_copy, self.spacetag.as_ref().unwrap());
if to_copy == self.width {
self.flush_line();
}
self.wslen -= to_copy;
}
self.spacetag = None;
self.flush_word_hard_wrap()?;
}
}
self.wordlen = 0;
Ok(())
}
fn flush_word_hard_wrap(&mut self) -> Result<()> {
use self::TaggedLineElement::Str;
let mut lineleft = self.width - self.line.len;
for element in self.word.remove_items() {
if let Str(piece) = element {
let w = piece.width();
let mut wpos = 0; let mut bpos = 0; while w - wpos > lineleft {
let mut split_idx = 0;
for (idx, c) in piece.s[bpos..].char_indices() {
let c_w = UnicodeWidthChar::width(c).unwrap();
if c_w <= lineleft {
lineleft -= c_w;
wpos += c_w;
} else {
if idx == 0 && self.line.width() == 0 {
if self.allow_overflow {
split_idx = c.len_utf8();
wpos += c_w;
break;
} else {
return Err(TooNarrow);
}
}
split_idx = idx;
break;
}
}
self.line.push(Str(TaggedString {
s: piece.s[bpos..bpos + split_idx].into(),
tag: piece.tag.clone(),
}));
bpos += split_idx;
self.force_flush_line();
lineleft = self.width;
}
if bpos == 0 {
self.line.push(Str(piece));
lineleft -= w;
} else if bpos < piece.s.len() {
self.line.push(Str(TaggedString {
s: piece.s[bpos..].into(),
tag: piece.tag,
}));
lineleft -= w.saturating_sub(wpos);
}
}
}
Ok(())
}
fn flush_line(&mut self) {
if !self.line.is_empty() {
self.force_flush_line();
}
}
fn force_flush_line(&mut self) {
let mut tmp_line = TaggedLine::new();
mem::swap(&mut tmp_line, &mut self.line);
if self.pad_blocks {
tmp_line.pad_to(
self.width,
&WithWhiteSpace(self.default_tag.clone(), WhiteSpace::Normal),
);
}
self.text
.push(tmp_line.map_tag(&|ww: WithWhiteSpace<T>| ww.0));
}
fn flush(&mut self) -> Result<()> {
self.flush_word()?;
self.flush_line();
Ok(())
}
pub fn take_trailing_fragments(&mut self) -> Vec<TaggedLineElement<T>> {
if self.word.is_empty() {
std::mem::take(&mut self.word)
.v
.into_iter()
.map(|e| e.map_tag(&|ww: WithWhiteSpace<T>| ww.0))
.collect()
} else {
Default::default()
}
}
pub fn into_lines(mut self) -> Result<Vec<TaggedLine<T>>> {
self.flush()?;
Ok(self.text)
}
fn add_text(
&mut self,
text: &str,
ws_mode: WhiteSpace,
main_tag: &T,
wrap_tag: &T,
) -> Result<()> {
html_trace!("WrappedBlock::add_text({}), {:?}", text, main_tag);
let mut tag = if self.pre_wrapped { wrap_tag } else { main_tag };
for c in text.chars() {
html_trace!(
"c = {:?} word={:?} linelen={} wslen={} line={:?}",
c,
self.word,
self.line.len,
self.wslen,
self.line
);
if c.is_wordbreak_point() && self.wordlen > 0 && ws_mode != WhiteSpace::Pre {
self.flush_word()?;
}
if c == '\u{200b}' {
continue;
} else if !c.always_takes_space() {
if ws_mode.preserve_whitespace() {
match c {
'\n' => {
self.flush_word()?;
self.force_flush_line();
self.wslen = 0;
self.spacetag = None;
self.pre_wrapped = false;
tag = main_tag;
}
'\t' => {
self.flush_word()?;
let tab_stop = 8;
let mut pos = self.line.len + self.wordlen + self.wslen;
let mut at_least_one_space = false;
while pos % tab_stop != 0 || !at_least_one_space {
if pos >= self.width {
self.flush_line();
pos = 0;
} else {
self.line
.push_char(' ', &WithWhiteSpace(tag.clone(), ws_mode));
pos += 1;
at_least_one_space = true;
}
}
}
_ => {
if let Some(cwidth) = UnicodeWidthChar::width(c) {
if self.word.is_empty() && char::is_whitespace(c) {
self.wslen += cwidth;
self.spacetag = Some(WithWhiteSpace(tag.clone(), ws_mode));
} else {
self.word
.push_char(c, &WithWhiteSpace(tag.clone(), ws_mode));
self.wordlen += cwidth;
}
}
}
}
} else {
if self.line.len > 0 && self.wslen == 0 {
self.spacetag = Some(WithWhiteSpace(tag.clone(), ws_mode));
self.wslen = 1;
}
}
} else {
if let Some(cwidth) = UnicodeWidthChar::width(c) {
self.wordlen += cwidth;
if ws_mode == WhiteSpace::Pre
&& (self.line.len + self.wslen + self.wordlen > self.width)
{
self.pre_wrapped = true;
tag = wrap_tag;
}
self.word
.push_char(c, &WithWhiteSpace(tag.clone(), ws_mode));
}
}
}
Ok(())
}
fn add_element(&mut self, elt: TaggedLineElement<T>) {
self.word
.push(elt.map_tag(&|t| WithWhiteSpace(t, WhiteSpace::Normal))); }
fn text_len(&self) -> usize {
self.text.len() + self.line.len + self.wordlen
}
fn is_empty(&self) -> bool {
self.text_len() == 0
}
}
pub trait TextDecorator {
type Annotation: Eq + PartialEq + Debug + Clone + Default;
fn decorate_link_start(&mut self, url: &str) -> (String, Self::Annotation);
fn decorate_link_end(&mut self) -> String;
fn decorate_em_start(&self) -> (String, Self::Annotation);
fn decorate_em_end(&self) -> String;
fn decorate_strong_start(&self) -> (String, Self::Annotation);
fn decorate_strong_end(&self) -> String;
fn decorate_strikeout_start(&self) -> (String, Self::Annotation);
fn decorate_strikeout_end(&self) -> String;
fn decorate_code_start(&self) -> (String, Self::Annotation);
fn decorate_code_end(&self) -> String;
fn decorate_preformat_first(&self) -> Self::Annotation;
fn decorate_preformat_cont(&self) -> Self::Annotation;
fn decorate_image(&mut self, src: &str, title: &str) -> (String, Self::Annotation);
fn header_prefix(&self, level: usize) -> String;
fn quote_prefix(&self) -> String;
fn unordered_item_prefix(&self) -> String;
fn ordered_item_prefix(&self, i: i64) -> String;
fn make_subblock_decorator(&self) -> Self;
fn push_colour(&mut self, _: Colour) -> Option<Self::Annotation> {
None
}
fn pop_colour(&mut self) -> bool {
false
}
fn push_bgcolour(&mut self, _: Colour) -> Option<Self::Annotation> {
None
}
fn pop_bgcolour(&mut self) -> bool {
false
}
fn decorate_superscript_start(&self) -> (String, Self::Annotation) {
("^{".into(), Default::default())
}
fn decorate_superscript_end(&self) -> String {
"}".into()
}
fn finalise(&mut self, urls: Vec<String>) -> Vec<TaggedLine<Self::Annotation>> {
urls.into_iter()
.enumerate()
.map(|(idx, s)| {
TaggedLine::from_string(format!("[{}]: {}", idx + 1, s), &Default::default())
})
.collect()
}
}
#[derive(Copy, Clone, Debug)]
enum BorderSegHoriz {
Horiz,
JoinAbove,
JoinBelow,
JoinCross,
Vert,
HorizVert,
JoinLeft,
JoinRight,
CornerTL,
CornerTR,
CornerBL,
CornerBR,
}
impl BorderSegHoriz {
pub fn chop_left(&mut self) {
use BorderSegHoriz::*;
match *self {
Horiz | HorizVert => {}
JoinBelow => {
*self = CornerTL;
}
JoinAbove => {
*self = CornerBL;
}
JoinCross => {
*self = JoinRight;
}
Vert => {}
JoinLeft => {
*self = Vert;
}
JoinRight => {}
CornerTL => {}
CornerTR => {
*self = Vert;
}
CornerBL => {}
CornerBR => {
*self = Vert;
}
}
}
pub fn chop_right(&mut self) {
use BorderSegHoriz::*;
match *self {
Horiz | HorizVert => {}
JoinBelow => {
*self = CornerTR;
}
JoinAbove => {
*self = CornerBR;
}
JoinCross => {
*self = JoinLeft;
}
Vert => {}
JoinLeft => {}
JoinRight => {
*self = Vert;
}
CornerTL => {
*self = Vert;
}
CornerTR => {}
CornerBL => {
*self = Vert;
}
CornerBR => {}
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct BorderHoriz<T> {
segments: Vec<BorderSegHoriz>,
tag: T,
holes: Vec<(usize, TaggedLineElement<T>)>,
}
impl<T: Clone> BorderHoriz<T> {
pub fn new(width: usize, tag: T) -> Self {
BorderHoriz {
segments: vec![BorderSegHoriz::Horiz; width],
tag,
holes: Default::default(),
}
}
fn new_type(width: usize, linetype: BorderSegHoriz, tag: T) -> Self {
BorderHoriz {
segments: vec![linetype; width],
tag,
holes: Default::default(),
}
}
fn stretch_to(&mut self, width: usize) {
use self::BorderSegHoriz::*;
while width > self.segments.len() {
self.segments.push(Horiz);
}
}
fn join_above(&mut self, x: usize) {
use self::BorderSegHoriz::*;
self.stretch_to(x + 1);
let prev = self.segments[x];
self.segments[x] = match prev {
Horiz | JoinAbove => JoinAbove,
JoinBelow | JoinCross => JoinCross,
Vert => Vert,
JoinLeft => JoinLeft,
JoinRight => JoinRight,
CornerTL => JoinRight,
CornerTR => JoinLeft,
CornerBL => CornerBL,
CornerBR => CornerBR,
HorizVert => HorizVert,
}
}
fn join_below(&mut self, x: usize) {
use self::BorderSegHoriz::*;
self.stretch_to(x + 1);
let prev = self.segments[x];
self.segments[x] = match prev {
Horiz | JoinBelow => JoinBelow,
JoinAbove | JoinCross => JoinCross,
Vert => Vert,
JoinLeft => JoinLeft,
JoinRight => JoinRight,
CornerTL => CornerTL,
CornerTR => CornerTR,
CornerBL => JoinRight,
CornerBR => JoinLeft,
HorizVert => HorizVert,
}
}
fn merge_from_below(&mut self, other: &BorderHoriz<T>, pos: usize) {
use self::BorderSegHoriz::*;
for (idx, seg) in other.segments.iter().enumerate() {
match *seg {
Horiz | Vert | JoinLeft | JoinRight | CornerTL | CornerTR | HorizVert => (),
JoinAbove | JoinBelow | JoinCross | CornerBL | CornerBR => {
self.join_below(idx + pos);
}
}
}
}
fn merge_from_above(&mut self, other: &BorderHoriz<T>, pos: usize) {
use self::BorderSegHoriz::*;
for (idx, seg) in other.segments.iter().enumerate() {
match *seg {
Horiz | Vert | JoinLeft | JoinRight | CornerBL | CornerBR | HorizVert => (),
JoinAbove | JoinBelow | JoinCross | CornerTL | CornerTR => {
self.join_above(idx + pos);
}
}
}
}
fn to_vertical_lines_above(&self) -> String {
use self::BorderSegHoriz::*;
self.segments
.iter()
.map(|seg| match *seg {
Horiz | JoinBelow | Vert | JoinLeft | JoinRight | CornerTL | CornerTR
| HorizVert => ' ',
JoinAbove | JoinCross | CornerBL | CornerBR => '│',
})
.collect()
}
fn add_text_span(&mut self, pos: usize, t: TaggedLineElement<T>)
where
T: Debug,
{
if pos > 0 {
if let Some(seg) = self.segments.get_mut(pos - 1) {
seg.chop_right()
}
}
let rpos = pos + t.width();
if let Some(seg) = self.segments.get_mut(rpos) {
seg.chop_left()
}
self.holes.push((pos, t));
}
fn seg_to_char(seg: BorderSegHoriz) -> char {
match seg {
BorderSegHoriz::Horiz => '─',
BorderSegHoriz::Vert => '│',
BorderSegHoriz::HorizVert => '/',
BorderSegHoriz::JoinAbove => '┴',
BorderSegHoriz::JoinBelow => '┬',
BorderSegHoriz::JoinCross => '┼',
BorderSegHoriz::JoinLeft => '┤',
BorderSegHoriz::JoinRight => '├',
BorderSegHoriz::CornerTL => '┌',
BorderSegHoriz::CornerTR => '┐',
BorderSegHoriz::CornerBL => '└',
BorderSegHoriz::CornerBR => '┘',
}
}
#[allow(clippy::inherent_to_string)]
fn to_string(&self) -> String {
let mut result = String::new();
let mut pos = 0usize;
for (holepos, hole) in &self.holes {
for seg in &self.segments[pos..*holepos] {
result.push(Self::seg_to_char(*seg));
}
pos = *holepos;
result.push_str(hole.as_str());
pos += hole.width();
}
if pos < self.segments.len() {
for seg in &self.segments[pos..] {
result.push(Self::seg_to_char(*seg));
}
}
result
}
fn extend_to(&mut self, len: usize) {
while self.segments.len() < len {
self.segments.push(BorderSegHoriz::Horiz);
}
}
}
impl<T: Clone + Debug + Eq + Default> BorderHoriz<T> {
fn into_tagged_line(self) -> TaggedLine<T> {
use self::TaggedLineElement::Str;
let mut pos = 0usize;
let mut result = TaggedLine::new();
let tag = self.tag.clone();
for (holepos, hole) in self.holes {
if holepos > pos {
let mut s = String::new();
for seg in &self.segments[pos..holepos] {
s.push(Self::seg_to_char(*seg));
}
result.push(Str(TaggedString {
s,
tag: tag.clone(),
}));
pos = holepos;
}
pos += hole.width();
result.push(hole);
}
if pos < self.segments.len() {
let mut s = String::new();
for seg in &self.segments[pos..] {
s.push(Self::seg_to_char(*seg));
}
result.push(Str(TaggedString {
s,
tag: tag.clone(),
}));
}
result
}
}
#[derive(Clone, Debug)]
pub(crate) enum RenderLine<T> {
Text(TaggedLine<T>),
Line(BorderHoriz<T>),
}
impl<T: PartialEq + Eq + Clone + Debug + Default> RenderLine<T> {
#[allow(clippy::inherent_to_string)]
fn to_string(&self) -> String {
match self {
RenderLine::Text(tagged) => tagged.to_string(),
RenderLine::Line(border) => border.to_string(),
}
}
pub fn into_tagged_line(self) -> TaggedLine<T> {
match self {
RenderLine::Text(tagged) => tagged,
RenderLine::Line(border) => border.into_tagged_line(),
}
}
fn has_content(&self) -> bool {
match self {
RenderLine::Text(line) => !line.is_empty(),
RenderLine::Line(bord) => bord.holes.is_empty(),
}
}
}
pub(crate) struct SubRenderer<D: TextDecorator> {
width: usize,
pub options: RenderOptions,
lines: LinkedList<RenderLine<Vec<D::Annotation>>>,
pending_frags: Vec<TaggedLineElement<Vec<D::Annotation>>>,
at_block_end: bool,
wrapping: Option<WrappedBlock<Vec<D::Annotation>>>,
decorator: D,
ann_stack: Vec<D::Annotation>,
text_filter_stack: Vec<fn(&str) -> Option<String>>,
pre_depth: usize,
ws_stack: Vec<WhiteSpace>,
overhang_cells: Vec<LineSet<D::Annotation>>,
}
impl<D: TextDecorator> std::fmt::Debug for SubRenderer<D> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("SubRenderer")
.field("width", &self.width)
.field("lines", &self.lines)
.field("ann_stack", &self.ann_stack)
.field("ws_stack", &self.ws_stack)
.field("wrapping", &self.wrapping)
.finish()
}
}
impl<D: TextDecorator> std::fmt::Display for SubRenderer<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "SubRenderer(width={})", self.width)?;
writeln!(f, " Lines: {}", self.lines.len())?;
for line in &self.lines {
match line {
RenderLine::Text(tagged_line) => {
writeln!(f, " {}", tagged_line.to_string())?;
}
RenderLine::Line(_) => {
writeln!(f, " <<<border>>>")?;
}
}
}
if let Some(wrapping) = &self.wrapping {
writeln!(f, " WrappedBlock text:")?;
for line in &wrapping.text {
writeln!(f, " {}", line.to_string())?;
}
writeln!(f, " WrappedBlock cur line:")?;
writeln!(f, " {}", wrapping.line.to_string())?;
writeln!(f, " WrappedBlock Word: [[{}]]", wrapping.word.to_string())?;
}
writeln!(f)
}
}
#[derive(Clone)]
#[non_exhaustive]
pub(crate) struct RenderOptions {
pub wrap_width: Option<usize>,
pub allow_width_overflow: bool,
pub pad_block_width: bool,
pub raw: bool,
pub draw_borders: bool,
pub wrap_links: bool,
pub include_link_footnotes: bool,
pub use_unicode_strikeout: bool,
pub img_mode: ImageRenderMode,
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
wrap_width: Default::default(),
allow_width_overflow: Default::default(),
pad_block_width: Default::default(),
raw: false,
draw_borders: true,
wrap_links: true,
include_link_footnotes: false,
use_unicode_strikeout: true,
img_mode: Default::default(),
}
}
}
fn get_wrapping_or_insert<'w, D: TextDecorator>(
wrapping: &'w mut Option<WrappedBlock<Vec<D::Annotation>>>,
options: &RenderOptions,
width: usize,
default_tag: &[D::Annotation],
) -> &'w mut WrappedBlock<Vec<D::Annotation>> {
wrapping.get_or_insert_with(|| {
let wwidth = match options.wrap_width {
Some(ww) => ww.min(width),
None => width,
};
WrappedBlock::new(
wwidth,
options.pad_block_width,
options.allow_width_overflow,
default_tag.to_owned(),
)
})
}
impl<D: TextDecorator> SubRenderer<D> {
pub fn finalise(&mut self, links: Vec<String>) -> Vec<TaggedLine<D::Annotation>> {
if self.options.include_link_footnotes {
self.decorator.finalise(links)
} else {
self.decorator.finalise(Vec::new())
}
}
pub fn new(width: usize, options: RenderOptions, decorator: D) -> SubRenderer<D> {
html_trace!("new({})", width);
SubRenderer {
width,
options,
lines: LinkedList::new(),
at_block_end: false,
wrapping: None,
decorator,
ann_stack: Vec::new(),
ws_stack: Vec::new(),
pre_depth: 0,
text_filter_stack: Vec::new(),
pending_frags: Default::default(),
overhang_cells: Default::default(),
}
}
fn add_line(&mut self, mut line: RenderLine<Vec<D::Annotation>>) {
if !self.pending_frags.is_empty() {
match line {
RenderLine::Text(tagged_line) => {
let mut tl = TaggedLine::new();
for frag in std::mem::take(&mut self.pending_frags) {
tl.push(frag);
}
for part in tagged_line.into_iter() {
tl.push(part);
}
if self.options.pad_block_width {
tl.pad_to(self.width, &self.ann_stack);
}
line = RenderLine::Text(tl);
}
RenderLine::Line(..) => (),
}
}
if self.options.pad_block_width {
match &mut line {
RenderLine::Text(tl) => {
tl.pad_to(self.width, &self.ann_stack);
}
RenderLine::Line(..) => (),
}
}
self.lines.push_back(line);
}
fn extend_lines(&mut self, lines: impl IntoIterator<Item = RenderLine<Vec<D::Annotation>>>) {
for line in lines {
self.add_line(line);
}
}
fn flush_wrapping(&mut self) -> Result<()> {
if let Some(mut w) = self.wrapping.take() {
let frags = w.take_trailing_fragments();
self.extend_lines(w.into_lines()?.into_iter().map(RenderLine::Text));
self.pending_frags.extend(frags);
}
Ok(())
}
fn flush_all(&mut self) -> Result<()> {
self.flush_wrapping()?;
Ok(())
}
pub fn into_string(mut self) -> Result<String> {
let mut result = String::new();
self.flush_wrapping()?;
for line in &self.lines {
result.push_str(&line.to_string());
result.push('\n');
}
html_trace!("into_string({}, {:?})", self.width, result);
Ok(result)
}
#[cfg(feature = "html_trace")]
#[allow(clippy::inherent_to_string_shadow_display)]
fn to_string(&self) -> String {
let mut result = String::new();
for line in &self.lines {
result += &line.to_string();
result.push('\n');
}
result
}
pub fn fmt_links(&mut self, mut links: Vec<TaggedLine<D::Annotation>>) {
for line in links.drain(..) {
let mut pos = 0;
let mut wrapped_line = TaggedLine::new();
for ts in line.into_tagged_strings() {
let s = ts.s.replace('\n', " ");
let tag = vec![ts.tag];
let width = s.width();
if self.options.wrap_links && pos + width > self.width {
let mut buf = String::new();
for c in s.chars() {
let c_width = UnicodeWidthChar::width(c).unwrap_or(0);
if pos + c_width > self.width {
if !buf.is_empty() {
wrapped_line.push_str(TaggedString {
s: buf,
tag: tag.clone(),
});
buf = String::new();
}
self.add_line(RenderLine::Text(wrapped_line));
wrapped_line = TaggedLine::new();
pos = 0;
}
pos += c_width;
buf.push(c);
}
wrapped_line.push_str(TaggedString { s: buf, tag });
} else {
wrapped_line.push_str(TaggedString {
s: s.to_owned(),
tag,
});
pos += width;
}
}
self.add_line(RenderLine::Text(wrapped_line));
}
}
pub fn into_lines(mut self) -> Result<LinkedList<RenderLine<Vec<D::Annotation>>>> {
self.flush_wrapping()?;
Ok(self.lines)
}
fn add_horizontal_line(&mut self, line: BorderHoriz<Vec<D::Annotation>>) -> Result<()> {
self.flush_wrapping()?;
self.add_line(RenderLine::Line(line));
Ok(())
}
pub fn width_minus(&self, prefix_len: usize, min_width: usize) -> Result<usize> {
let new_width = self.width.saturating_sub(prefix_len);
if new_width < min_width && !self.options.allow_width_overflow {
return Err(TooNarrow);
}
Ok(new_width.max(min_width))
}
fn ws_mode(&self) -> WhiteSpace {
self.ws_stack.last().cloned().unwrap_or(WhiteSpace::Normal)
}
}
fn filter_text_strikeout(s: &str) -> Option<String> {
let mut result = String::new();
for c in s.chars() {
result.push(c);
if UnicodeWidthChar::width(c).unwrap_or(0) > 0 {
result.push('\u{336}');
}
}
Some(result)
}
#[derive(Default, Debug)]
struct LineSet<A> {
pos: usize,
width: usize,
rowspan: usize,
lines: VecDeque<RenderLine<Vec<A>>>,
}
impl<A: PartialEq + Eq + Clone + Debug + Default> std::fmt::Display for LineSet<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let lines = self.lines.iter().map(|l| l.to_string()).collect::<Vec<_>>();
f.debug_struct("LineSet")
.field("pos", &self.pos)
.field("width", &self.width)
.field("rowspan", &self.rowspan)
.field("lines", &lines)
.finish()
}
}
impl<A> LineSet<A> {
fn cell_height(&self) -> usize {
let tot_lines = self.lines.len();
if self.rowspan == 1 {
tot_lines
} else {
tot_lines / self.rowspan
}
}
}
impl<D: TextDecorator> Renderer for SubRenderer<D> {
fn add_empty_line(&mut self) -> Result<()> {
html_trace!("add_empty_line()");
self.flush_all()?;
self.add_line(RenderLine::Text(TaggedLine::new()));
html_trace_quiet!("add_empty_line: at_block_end <- false");
self.at_block_end = false;
html_trace_quiet!("add_empty_line: new lines: {:?}", self.lines);
Ok(())
}
fn new_sub_renderer(&self, width: usize) -> Result<Self> {
let mut result = SubRenderer::new(
width,
self.options.clone(),
self.decorator.make_subblock_decorator(),
);
result.ann_stack = self.ann_stack.clone();
Ok(result)
}
fn start_block(&mut self) -> Result<()> {
html_trace!("start_block({})", self.width);
self.flush_all()?;
if self.lines.iter().any(|l| l.has_content()) {
self.add_empty_line()?;
}
html_trace_quiet!("start_block; at_block_end <- false");
self.at_block_end = false;
Ok(())
}
fn start_table(&mut self) -> Result<()> {
self.start_block()
}
fn new_line(&mut self) -> Result<()> {
self.flush_all()
}
fn new_line_hard(&mut self) -> Result<()> {
match &self.wrapping {
None => self.add_empty_line(),
Some(wrapping) => {
if wrapping.wordlen == 0 && wrapping.line.len == 0 {
self.add_empty_line()
} else {
self.flush_all()
}
}
}
}
fn add_horizontal_border(&mut self) -> Result<()> {
self.flush_wrapping()?;
self.add_line(RenderLine::Line(BorderHoriz::new(
self.width,
self.ann_stack.clone(),
)));
Ok(())
}
fn add_horizontal_border_width(&mut self, width: usize) -> Result<()> {
self.flush_wrapping()?;
self.add_line(RenderLine::Line(BorderHoriz::new(
width,
self.ann_stack.clone(),
)));
Ok(())
}
fn push_ws(&mut self, ws: WhiteSpace) {
self.ws_stack.push(ws);
}
fn pop_ws(&mut self) {
self.ws_stack.pop();
}
fn push_preformat(&mut self) {
self.pre_depth += 1;
}
fn pop_preformat(&mut self) {
debug_assert!(self.pre_depth > 0);
self.pre_depth -= 1;
}
fn end_block(&mut self) {
self.at_block_end = true;
}
fn add_inline_text(&mut self, text: &str) -> Result<()> {
html_trace!("add_inline_text({}, {})", self.width, text);
if !self.ws_mode().preserve_whitespace()
&& self.at_block_end
&& text.chars().all(char::is_whitespace)
{
return Ok(());
}
if self.at_block_end {
self.start_block()?;
}
let mut s = None;
for filter in &self.text_filter_stack {
let srctext = s.as_deref().unwrap_or(text);
if let Some(filtered) = filter(srctext) {
s = Some(filtered);
}
}
let filtered_text = s.as_deref().unwrap_or(text);
let ws_mode = self.ws_mode();
let wrapping = get_wrapping_or_insert::<D>(
&mut self.wrapping,
&self.options,
self.width,
&self.ann_stack,
);
let mut pre_tag_start;
let mut pre_tag_cont;
let main_tag;
let cont_tag;
if self.pre_depth > 0 {
pre_tag_start = self.ann_stack.clone();
pre_tag_cont = self.ann_stack.clone();
pre_tag_start.push(self.decorator.decorate_preformat_first());
pre_tag_cont.push(self.decorator.decorate_preformat_cont());
main_tag = &pre_tag_start;
cont_tag = &pre_tag_cont;
} else {
main_tag = &self.ann_stack;
cont_tag = &self.ann_stack;
}
wrapping.add_text(filtered_text, ws_mode, main_tag, cont_tag)?;
Ok(())
}
fn width(&self) -> usize {
self.width
}
fn append_subrender<'a, I>(&mut self, other: Self, prefixes: I) -> Result<()>
where
I: Iterator<Item = &'a str>,
{
use self::TaggedLineElement::Str;
self.flush_wrapping()?;
let tag = self.ann_stack.clone();
self.extend_lines(
other
.into_lines()?
.into_iter()
.zip(prefixes)
.map(|(line, prefix)| match line {
RenderLine::Text(mut tline) => {
if !prefix.is_empty() {
tline.insert_front(TaggedString {
s: prefix.to_string(),
tag: tag.clone(),
});
}
RenderLine::Text(tline)
}
RenderLine::Line(l) => {
let mut tline = TaggedLine::new();
tline.push(Str(TaggedString {
s: prefix.to_string(),
tag: tag.clone(),
}));
tline.push(Str(TaggedString {
s: l.to_string(),
tag: tag.clone(),
}));
RenderLine::Text(tline)
}
}),
);
Ok(())
}
fn append_columns_with_borders<I>(&mut self, cols: I, collapse: bool) -> Result<()>
where
I: IntoIterator<Item = (Self, usize)>,
Self: Sized,
{
use self::TaggedLineElement::Str;
html_trace!("append_columns_with_borders(collapse={})", collapse);
html_trace!("self=<<<\n{}>>>", self.to_string());
self.flush_wrapping()?;
let mut tot_width = 0;
let mut line_sets = cols
.into_iter()
.map(|(sub_r, rowspan)| {
let width = sub_r.width;
let pos = tot_width;
tot_width += width + 1;
html_trace!("Adding column:\n{}", sub_r.to_string());
Ok(LineSet {
pos,
width,
rowspan,
lines: sub_r
.into_lines()?
.into_iter()
.map(|mut line| {
match line {
RenderLine::Text(ref mut tline) => {
tline.pad_to(width, &self.ann_stack);
}
RenderLine::Line(ref mut border) => {
border.stretch_to(width);
}
}
line
})
.collect(),
})
})
.collect::<Result<Vec<LineSet<_>>>>()?;
{
let mut lidx = 0;
let mut lnextpos = 0;
for ls in std::mem::take(&mut self.overhang_cells) {
while lidx < line_sets.len() && line_sets[lidx].pos < ls.pos {
let lpos = line_sets[lidx].pos;
lnextpos = lpos + line_sets[lidx].width + 1;
lidx += 1;
}
if lidx >= line_sets.len() {
if lnextpos < ls.pos {
line_sets.push(LineSet {
pos: lnextpos,
width: ls.pos.saturating_sub(lnextpos + 1),
rowspan: 1,
lines: Default::default(),
});
}
if ls.pos + ls.width > tot_width {
tot_width = ls.pos + ls.width + 1;
}
line_sets.push(ls);
} else {
line_sets[lidx] = ls;
}
}
}
tot_width = tot_width.saturating_sub(1);
let mut next_border = BorderHoriz::new(tot_width, self.ann_stack.clone());
if let Some(RenderLine::Line(prev_border)) = self.lines.back_mut() {
let mut pos = 0;
html_trace!("Merging with last line:\n{}", prev_border.to_string());
for ls in &line_sets[..line_sets.len().saturating_sub(1)] {
let w = ls.width;
html_trace!("pos={}, w={}", pos, w);
prev_border.join_below(pos + w);
next_border.join_above(pos + w);
pos += w + 1;
}
if let Some(ls) = line_sets.last() {
prev_border.extend_to(pos + ls.width);
}
}
let mut column_padding = vec![None; line_sets.len()];
if collapse {
html_trace!("Collapsing borders.");
let mut pos = 0;
for ls in &mut line_sets {
let w = ls.width;
let sublines = &mut ls.lines;
let starts_border = matches!(sublines.front(), Some(RenderLine::Line(_)));
if starts_border {
html_trace!("Starts border");
if let &mut RenderLine::Line(ref mut prev_border) =
self.lines.back_mut().expect("No previous line")
{
if let Some(RenderLine::Line(line)) = sublines.pop_front() {
html_trace!(
"prev border:\n{}\n, pos={}, line:\n{}",
prev_border.to_string(),
pos,
line.to_string()
);
prev_border.merge_from_below(&line, pos);
}
} else {
unreachable!();
}
}
pos += w + 1;
}
let mut pos = 0;
for (col_no, ls) in line_sets.iter_mut().enumerate() {
let w = ls.width;
let sublines = &mut ls.lines;
if let Some(RenderLine::Line(line)) = sublines.back() {
html_trace!("Ends border");
next_border.merge_from_above(line, pos);
column_padding[col_no] = Some(line.to_vertical_lines_above());
sublines.pop_back();
}
pos += w + 1;
}
}
let cell_height = line_sets
.iter()
.map(|ls| ls.cell_height())
.max()
.unwrap_or(0);
let spaces: String = (0..tot_width).map(|_| ' ').collect();
let last_cellno = line_sets.len().saturating_sub(1);
for i in 0..cell_height {
let mut line = TaggedLine::new();
for (cellno, ls) in line_sets.iter_mut().enumerate() {
match ls.lines.get_mut(i) {
Some(RenderLine::Text(tline)) => line.consume(tline),
Some(RenderLine::Line(bord)) => line.push(Str(TaggedString {
s: bord.to_string(),
tag: self.ann_stack.clone(),
})),
None => line.push(Str(TaggedString {
s: column_padding[cellno]
.clone()
.unwrap_or_else(|| spaces[0..ls.width].to_string()),
tag: self.ann_stack.clone(),
})),
}
if cellno != last_cellno {
line.push_char(
if self.options.draw_borders {
'│'
} else {
' '
},
&self.ann_stack,
);
}
}
self.add_line(RenderLine::Text(line));
}
{
let mut pos = 0;
for mut ls in line_sets.into_iter() {
if ls.rowspan > 1 {
if let Some(l) = ls.lines.get(cell_height) {
let mut tmppos = pos;
for ts in l.clone().into_tagged_line() {
let w = ts.width();
next_border.add_text_span(tmppos, ts);
tmppos += w;
}
} else {
next_border.add_text_span(
pos,
TaggedLineElement::Str(TaggedString {
s: " ".repeat(ls.width),
tag: next_border.tag.clone(),
}),
);
}
{
let new_len = ls.lines.len().saturating_sub(cell_height + 1);
while ls.lines.len() > new_len {
ls.lines.pop_front();
}
}
self.overhang_cells.push(LineSet {
pos: ls.pos,
width: ls.width,
rowspan: ls.rowspan - 1,
lines: ls.lines,
});
}
pos += ls.width + 1;
}
}
if self.options.draw_borders {
self.add_line(RenderLine::Line(next_border));
}
Ok(())
}
fn append_vert_row<I>(&mut self, cols: I) -> Result<()>
where
I: IntoIterator<Item = Self>,
Self: Sized,
{
html_trace!("append_vert_row()");
html_trace!("self=\n{}", self.to_string());
self.flush_wrapping()?;
let width = self.width();
let mut first = true;
for col in cols {
if first {
first = false;
} else if self.options.draw_borders {
let border =
BorderHoriz::new_type(width, BorderSegHoriz::HorizVert, self.ann_stack.clone());
self.add_horizontal_line(border)?;
}
self.append_subrender(col, std::iter::repeat(""))?;
}
if self.options.draw_borders {
self.add_horizontal_border()?;
}
Ok(())
}
fn empty(&self) -> bool {
self.lines.is_empty()
&& if let Some(wrapping) = &self.wrapping {
wrapping.is_empty()
} else {
true
}
}
fn start_link(&mut self, target: &str) -> Result<()> {
let (s, annotation) = self.decorator.decorate_link_start(target);
self.ann_stack.push(annotation);
self.add_inline_text(&s)
}
fn end_link(&mut self) -> Result<()> {
let s = self.decorator.decorate_link_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_emphasis(&mut self) -> Result<()> {
let (s, annotation) = self.decorator.decorate_em_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)
}
fn end_emphasis(&mut self) -> Result<()> {
let s = self.decorator.decorate_em_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_strong(&mut self) -> Result<()> {
let (s, annotation) = self.decorator.decorate_strong_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)
}
fn end_strong(&mut self) -> Result<()> {
let s = self.decorator.decorate_strong_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_strikeout(&mut self) -> Result<()> {
let (s, annotation) = self.decorator.decorate_strikeout_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)?;
if self.options.use_unicode_strikeout {
self.text_filter_stack.push(filter_text_strikeout);
}
Ok(())
}
fn end_strikeout(&mut self) -> Result<()> {
if self.options.use_unicode_strikeout {
self.text_filter_stack
.pop()
.expect("end_strikeout() called without a corresponding start_strokeout()");
}
let s = self.decorator.decorate_strikeout_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_code(&mut self) -> Result<()> {
let (s, annotation) = self.decorator.decorate_code_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)?;
Ok(())
}
fn end_code(&mut self) -> Result<()> {
let s = self.decorator.decorate_code_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn add_image(&mut self, src: &str, title: &str) -> Result<()> {
let (s, tag) = match (title, self.options.img_mode) {
("", ImageRenderMode::IgnoreEmpty) => {
return Ok(());
}
("", ImageRenderMode::Filename) => {
let slash = src.rfind('/');
let sub_title = match slash {
Some(pos) => &src[pos + 1..],
None => src,
};
self.decorator.decorate_image(src, sub_title)
}
("", ImageRenderMode::ShowAlways) => self.decorator.decorate_image(src, title),
("", ImageRenderMode::Replace(s)) => self.decorator.decorate_image(src, s),
(title, _) => self.decorator.decorate_image(src, title),
};
self.ann_stack.push(tag);
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn header_prefix(&mut self, level: usize) -> String {
self.decorator.header_prefix(level)
}
fn quote_prefix(&mut self) -> String {
self.decorator.quote_prefix()
}
fn unordered_item_prefix(&mut self) -> String {
self.decorator.unordered_item_prefix()
}
fn ordered_item_prefix(&mut self, i: i64) -> String {
self.decorator.ordered_item_prefix(i)
}
fn record_frag_start(&mut self, fragname: &str) {
use self::TaggedLineElement::FragmentStart;
get_wrapping_or_insert::<D>(
&mut self.wrapping,
&self.options,
self.width,
&self.ann_stack,
)
.add_element(FragmentStart(fragname.to_string()));
}
#[allow(unused)]
fn push_colour(&mut self, colour: Colour) {
if let Some(ann) = self.decorator.push_colour(colour) {
self.ann_stack.push(ann);
}
}
#[allow(unused)]
fn pop_colour(&mut self) {
if self.decorator.pop_colour() {
self.ann_stack.pop();
}
}
fn push_bgcolour(&mut self, colour: Colour) {
if let Some(ann) = self.decorator.push_bgcolour(colour) {
self.ann_stack.push(ann);
}
}
fn pop_bgcolour(&mut self) {
if self.decorator.pop_bgcolour() {
self.ann_stack.pop();
}
}
fn start_superscript(&mut self) -> Result<()> {
let (s, annotation) = self.decorator.decorate_superscript_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)?;
Ok(())
}
fn end_superscript(&mut self) -> Result<()> {
let s = self.decorator.decorate_superscript_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct PlainDecorator {}
impl PlainDecorator {
#[allow(clippy::new_without_default)]
pub fn new() -> PlainDecorator {
PlainDecorator {}
}
}
impl TextDecorator for PlainDecorator {
type Annotation = ();
fn decorate_link_start(&mut self, _url: &str) -> (String, Self::Annotation) {
("[".to_string(), ())
}
fn decorate_link_end(&mut self) -> String {
"]".to_string()
}
fn decorate_em_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_em_end(&self) -> String {
"".to_string()
}
fn decorate_strong_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strong_end(&self) -> String {
"".to_string()
}
fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strikeout_end(&self) -> String {
"".to_string()
}
fn decorate_code_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_code_end(&self) -> String {
"".to_string()
}
fn decorate_preformat_first(&self) -> Self::Annotation {}
fn decorate_preformat_cont(&self) -> Self::Annotation {}
fn decorate_image(&mut self, _src: &str, title: &str) -> (String, Self::Annotation) {
(format!("[{}]", title), ())
}
fn header_prefix(&self, level: usize) -> String {
"#".repeat(level) + " "
}
fn quote_prefix(&self) -> String {
"> ".to_string()
}
fn unordered_item_prefix(&self) -> String {
"* ".to_string()
}
fn ordered_item_prefix(&self, i: i64) -> String {
format!("{}. ", i)
}
fn make_subblock_decorator(&self) -> Self {
self.clone()
}
}
#[derive(Clone, Debug)]
pub struct TrivialDecorator {}
impl TrivialDecorator {
#[allow(clippy::new_without_default)]
pub fn new() -> TrivialDecorator {
TrivialDecorator {}
}
}
impl TextDecorator for TrivialDecorator {
type Annotation = ();
fn decorate_link_start(&mut self, _url: &str) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_link_end(&mut self) -> String {
"".to_string()
}
fn decorate_em_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_em_end(&self) -> String {
"".to_string()
}
fn decorate_strong_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strong_end(&self) -> String {
"".to_string()
}
fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strikeout_end(&self) -> String {
"".to_string()
}
fn decorate_code_start(&self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_code_end(&self) -> String {
"".to_string()
}
fn decorate_preformat_first(&self) -> Self::Annotation {}
fn decorate_preformat_cont(&self) -> Self::Annotation {}
fn decorate_image(&mut self, _src: &str, title: &str) -> (String, Self::Annotation) {
(title.to_string(), ())
}
fn header_prefix(&self, _level: usize) -> String {
"".to_string()
}
fn quote_prefix(&self) -> String {
"".to_string()
}
fn unordered_item_prefix(&self) -> String {
"".to_string()
}
fn ordered_item_prefix(&self, _i: i64) -> String {
"".to_string()
}
fn make_subblock_decorator(&self) -> Self {
TrivialDecorator::new()
}
}
#[derive(Clone, Debug)]
pub struct RichDecorator {}
#[derive(PartialEq, Eq, Clone, Debug, Default)]
#[non_exhaustive]
pub enum RichAnnotation {
#[default]
Default,
Link(String),
Image(String),
Emphasis,
Strong,
Strikeout,
Code,
Preformat(bool),
Colour(crate::Colour),
BgColour(crate::Colour),
}
impl RichDecorator {
#[allow(clippy::new_without_default)]
pub fn new() -> RichDecorator {
RichDecorator {}
}
}
impl TextDecorator for RichDecorator {
type Annotation = RichAnnotation;
fn decorate_link_start(&mut self, url: &str) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Link(url.to_string()))
}
fn decorate_link_end(&mut self) -> String {
"".to_string()
}
fn decorate_em_start(&self) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Emphasis)
}
fn decorate_em_end(&self) -> String {
"".to_string()
}
fn decorate_strong_start(&self) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Strong)
}
fn decorate_strong_end(&self) -> String {
"".to_string()
}
fn decorate_strikeout_start(&self) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Strikeout)
}
fn decorate_strikeout_end(&self) -> String {
"".to_string()
}
fn decorate_code_start(&self) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Code)
}
fn decorate_code_end(&self) -> String {
"".to_string()
}
fn decorate_preformat_first(&self) -> Self::Annotation {
RichAnnotation::Preformat(false)
}
fn decorate_preformat_cont(&self) -> Self::Annotation {
RichAnnotation::Preformat(true)
}
fn decorate_image(&mut self, src: &str, title: &str) -> (String, Self::Annotation) {
(title.to_string(), RichAnnotation::Image(src.to_string()))
}
fn header_prefix(&self, level: usize) -> String {
"#".repeat(level) + " "
}
fn quote_prefix(&self) -> String {
"> ".to_string()
}
fn unordered_item_prefix(&self) -> String {
"* ".to_string()
}
fn ordered_item_prefix(&self, i: i64) -> String {
format!("{}. ", i)
}
fn make_subblock_decorator(&self) -> Self {
self.clone()
}
fn push_colour(&mut self, colour: Colour) -> Option<Self::Annotation> {
Some(RichAnnotation::Colour(colour))
}
fn pop_colour(&mut self) -> bool {
true
}
fn push_bgcolour(&mut self, colour: Colour) -> Option<Self::Annotation> {
Some(RichAnnotation::BgColour(colour))
}
fn pop_bgcolour(&mut self) -> bool {
true
}
}