use crate::Colour;
use crate::Error;
use super::Renderer;
use std::cell::Cell;
use std::mem;
use std::ops::Deref;
use std::ops::DerefMut;
use std::rc::Rc;
use std::vec;
use std::{collections::LinkedList, fmt::Debug};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
#[derive(Clone, Debug)]
pub 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) -> crate::Result<()> {
self.links.push(target.to_string());
self.subrender.last_mut().unwrap().start_link(target)?;
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: Debug + PartialEq> TaggedString<T> {
pub fn width(&self) -> usize {
self.s.width()
}
}
#[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,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TaggedLine<T> {
v: Vec<TaggedLineElement<T>>,
}
impl<T: Debug + Eq + PartialEq + Clone + Default> TaggedLine<T> {
pub fn new() -> TaggedLine<T> {
TaggedLine { v: Vec::new() }
}
pub fn from_string(s: String, tag: &T) -> TaggedLine<T> {
TaggedLine {
v: vec![TaggedLineElement::Str(TaggedString {
s,
tag: tag.clone(),
})],
}
}
pub fn into_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
}
pub fn is_empty(&self) -> bool {
for elt in &self.v {
if elt.has_content() {
return false;
}
}
true
}
pub fn push_str(&mut self, ts: TaggedString<T>) {
use self::TaggedLineElement::Str;
if !self.v.is_empty() {
if let Str(ref mut ts_prev) = self.v.last_mut().unwrap() {
if ts_prev.tag == ts.tag {
ts_prev.s.push_str(&ts.s);
return;
}
}
}
self.v.push(Str(ts));
}
pub 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);
}
}
pub fn insert_front(&mut self, ts: TaggedString<T>) {
use self::TaggedLineElement::Str;
self.v.insert(0, Str(ts));
}
pub fn push_char(&mut self, c: char, tag: &T) {
use self::TaggedLineElement::Str;
if !self.v.is_empty() {
if let Str(ref mut ts_prev) = self.v.last_mut().unwrap() {
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(),
}));
}
pub fn consume(&mut self, tl: &mut TaggedLine<T>) {
for ts in tl.v.drain(..) {
self.push(ts);
}
}
pub fn drain_all(&mut self) -> vec::Drain<TaggedLineElement<T>> {
self.v.drain(..)
}
#[cfg_attr(feature = "clippy", allow(needless_lifetimes))]
pub fn chars<'a>(&'a self) -> impl Iterator<Item = char> + 'a {
use self::TaggedLineElement::Str;
self.v.iter().flat_map(|tle| {
if let Str(ts) = tle {
ts.s.chars()
} else {
"".chars()
}
})
}
#[cfg(feature = "html_trace")]
fn to_string(&self) -> String {
self.chars().collect()
}
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &TaggedLineElement<T>> + 'a {
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,
})
}
pub 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,
})
}
pub fn width(&self) -> usize {
self.tagged_strings().map(TaggedString::width).sum()
}
pub fn pad_to(&mut self, width: usize, tag: &T) {
use self::TaggedLineElement::Str;
let my_width = self.width();
if width > my_width {
self.v.push(Str(TaggedString {
s: format!("{: <width$}", "", width = width - my_width),
tag: tag.clone(),
}));
}
}
}
#[derive(Debug, Clone)]
struct WrappedBlock<T> {
width: usize,
text: Vec<TaggedLine<T>>,
textlen: usize,
line: TaggedLine<T>,
linelen: usize,
spacetag: Option<T>, word: TaggedLine<T>, wordlen: usize,
pre_wrapped: bool, pad_blocks: bool,
allow_overflow: bool,
}
impl<T: Clone + Eq + Debug + Default> WrappedBlock<T> {
pub fn new(width: usize, pad_blocks: bool, allow_overflow: bool) -> WrappedBlock<T> {
WrappedBlock {
width,
text: Vec::new(),
textlen: 0,
line: TaggedLine::new(),
linelen: 0,
spacetag: None,
word: TaggedLine::new(),
wordlen: 0,
pre_wrapped: false,
pad_blocks,
allow_overflow,
}
}
fn flush_word(&mut self) -> Result<(), Error> {
use self::TaggedLineElement::Str;
html_trace_quiet!("flush_word: word={:?}, linelen={}", self.word, self.linelen);
if !self.word.is_empty() {
self.pre_wrapped = false;
let space_in_line = self.width - self.linelen;
let space_needed = self.wordlen + if self.linelen > 0 { 1 } else { 0 }; if space_needed <= space_in_line {
html_trace!("Got enough space");
if self.linelen > 0 {
self.line.push(Str(TaggedString {
s: " ".into(),
tag: self.spacetag.clone().unwrap_or_else(|| Default::default()),
}));
self.linelen += 1;
html_trace!("linelen incremented to {}", self.linelen);
}
self.line.consume(&mut self.word);
self.linelen += self.wordlen;
html_trace!("linelen increased by wordlen to {}", self.linelen);
} else {
html_trace!("Not enough space");
self.flush_line();
if self.wordlen <= self.width {
html_trace!("wordlen <= width");
let mut new_word = TaggedLine::new();
mem::swap(&mut new_word, &mut self.word);
mem::swap(&mut self.line, &mut new_word);
self.linelen = self.wordlen;
html_trace!("linelen set to wordlen {}", self.linelen);
} else {
html_trace!("Splitting the word");
let mut word = TaggedLine::new();
mem::swap(&mut word, &mut self.word);
let mut wordbits = word.drain_all();
let mut opt_elt = wordbits.next();
let mut lineleft = self.width;
while let Some(elt) = opt_elt.take() {
html_trace!("Take element {:?}", elt);
if let Str(piece) = elt {
let w = piece.width();
if w <= lineleft {
self.line.push(Str(piece));
lineleft -= w;
self.linelen += w;
html_trace!("linelen had w={} added to {}", w, self.linelen);
opt_elt = wordbits.next();
} else {
let mut split_idx = 0;
for (idx, c) in piece.s.char_indices() {
let c_w = UnicodeWidthChar::width(c).unwrap();
if c_w <= lineleft {
lineleft -= c_w;
} else {
if idx == 0 && self.line.width() == 0 {
if self.allow_overflow {
split_idx = c.len_utf8();
break;
} else {
return Err(Error::TooNarrow);
}
}
split_idx = idx;
break;
}
}
self.line.push(Str(TaggedString {
s: piece.s[..split_idx].into(),
tag: piece.tag.clone(),
}));
self.force_flush_line();
lineleft = self.width;
if split_idx == piece.s.len() {
opt_elt = None;
} else {
opt_elt = Some(Str(TaggedString {
s: piece.s[split_idx..].into(),
tag: piece.tag,
}));
}
}
} else {
self.line.push(elt);
opt_elt = wordbits.next();
}
}
}
}
}
self.wordlen = 0;
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 {
let tmp_tag;
let tag = if let Some(st) = self.spacetag.as_ref() {
st
} else {
tmp_tag = Default::default();
&tmp_tag
};
tmp_line.pad_to(self.width, tag);
}
self.text.push(tmp_line);
self.linelen = 0;
}
fn flush(&mut self) -> Result<(), Error> {
self.flush_word()?;
self.flush_line();
Ok(())
}
pub fn into_lines(mut self) -> Result<Vec<TaggedLine<T>>, Error> {
self.flush()?;
Ok(self.text)
}
pub fn add_text(&mut self, text: &str, tag: &T) -> Result<(), Error> {
html_trace!("WrappedBlock::add_text({}), {:?}", text, tag);
for c in text.chars() {
if c.is_whitespace() {
self.flush_word()?;
self.spacetag = Some(tag.clone());
} else if let Some(charwidth) = UnicodeWidthChar::width(c) {
self.word.push_char(c, tag);
self.wordlen += charwidth;
}
html_trace_quiet!(" Added char {:?}, wordlen={}", c, self.wordlen);
}
Ok(())
}
pub fn add_preformatted_text(
&mut self,
text: &str,
tag_main: &T,
tag_wrapped: &T,
) -> Result<(), Error> {
html_trace!(
"WrappedBlock::add_preformatted_text({}), {:?}/{:?}",
text,
tag_main,
tag_wrapped
);
self.flush_word()?;
for c in text.chars() {
if let Some(charwidth) = UnicodeWidthChar::width(c) {
if self.linelen + charwidth > self.width {
self.flush_line();
self.pre_wrapped = true;
}
self.line.push_char(
c,
if self.pre_wrapped {
tag_wrapped
} else {
tag_main
},
);
self.linelen += charwidth;
} else {
match c {
'\n' => {
self.force_flush_line();
self.pre_wrapped = false;
}
'\t' => {
let tab_stop = 8;
let mut at_least_one_space = false;
while self.linelen % tab_stop != 0 || !at_least_one_space {
if self.linelen >= self.width {
self.flush_line();
} else {
self.line.push_char(
' ',
if self.pre_wrapped {
tag_wrapped
} else {
tag_main
},
);
self.linelen += 1;
at_least_one_space = true;
}
}
}
_ => {}
}
}
html_trace_quiet!(" Added char {:?}", c);
}
Ok(())
}
pub fn add_element(&mut self, elt: TaggedLineElement<T>) {
self.word.push(elt);
}
pub fn text_len(&self) -> usize {
self.textlen + self.linelen + self.wordlen
}
pub 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, links: Vec<String>) -> Vec<TaggedLine<Self::Annotation>>;
}
#[derive(Copy, Clone, Debug)]
pub enum BorderSegHoriz {
Straight,
JoinAbove,
JoinBelow,
JoinCross,
StraightVert,
}
#[derive(Clone, Debug)]
pub struct BorderHoriz<T> {
pub segments: Vec<BorderSegHoriz>,
pub tag: T,
}
impl<T: Clone> BorderHoriz<T> {
pub fn new(width: usize, tag: T) -> Self {
BorderHoriz {
segments: vec![BorderSegHoriz::Straight; width],
tag,
}
}
pub fn new_type(width: usize, linetype: BorderSegHoriz, tag: T) -> Self {
BorderHoriz {
segments: vec![linetype; width],
tag,
}
}
pub fn stretch_to(&mut self, width: usize) {
use self::BorderSegHoriz::*;
while width > self.segments.len() {
self.segments.push(Straight);
}
}
pub 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 {
Straight | JoinAbove => JoinAbove,
JoinBelow | JoinCross => JoinCross,
StraightVert => StraightVert,
}
}
pub 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 {
Straight | JoinBelow => JoinBelow,
JoinAbove | JoinCross => JoinCross,
StraightVert => StraightVert,
}
}
pub fn merge_from_below(&mut self, other: &BorderHoriz<T>, pos: usize) {
use self::BorderSegHoriz::*;
for (idx, seg) in other.segments.iter().enumerate() {
match *seg {
Straight | StraightVert => (),
JoinAbove | JoinBelow | JoinCross => {
self.join_below(idx + pos);
}
}
}
}
pub fn merge_from_above(&mut self, other: &BorderHoriz<T>, pos: usize) {
use self::BorderSegHoriz::*;
for (idx, seg) in other.segments.iter().enumerate() {
match *seg {
Straight | StraightVert => (),
JoinAbove | JoinBelow | JoinCross => {
self.join_above(idx + pos);
}
}
}
}
pub fn to_vertical_lines_above(&self) -> String {
use self::BorderSegHoriz::*;
self.segments
.iter()
.map(|seg| match *seg {
Straight | JoinBelow | StraightVert => ' ',
JoinAbove | JoinCross => '│',
})
.collect()
}
pub fn into_string(self) -> String {
self.segments
.into_iter()
.map(|seg| match seg {
BorderSegHoriz::Straight => '─',
BorderSegHoriz::StraightVert => '/',
BorderSegHoriz::JoinAbove => '┴',
BorderSegHoriz::JoinBelow => '┬',
BorderSegHoriz::JoinCross => '┼',
})
.collect::<String>()
}
pub fn to_string(&self) -> String {
self.clone().into_string()
}
}
#[derive(Clone, Debug)]
pub enum RenderLine<T> {
Text(TaggedLine<T>),
Line(BorderHoriz<T>),
}
impl<T: PartialEq + Eq + Clone + Debug + Default> RenderLine<T> {
pub fn into_string(self) -> String {
match self {
RenderLine::Text(tagged) => tagged.into_string(),
RenderLine::Line(border) => border.into_string(),
}
}
pub fn into_tagged_line(self) -> TaggedLine<T> {
use self::TaggedLineElement::Str;
match self {
RenderLine::Text(tagged) => tagged,
RenderLine::Line(border) => {
let mut tagged = TaggedLine::new();
let tag = border.tag.clone();
tagged.push(Str(TaggedString {
s: border.into_string(),
tag,
}));
tagged
}
}
}
#[cfg(feature = "html_trace")]
fn to_string(&self) -> String {
match self {
RenderLine::Text(tagged) => tagged.to_string(),
RenderLine::Line(border) => border.to_string(),
}
}
fn has_content(&self) -> bool {
match self {
RenderLine::Text(line) => !line.is_empty(),
RenderLine::Line(_) => false,
}
}
}
#[derive(Clone)]
pub struct SubRenderer<D: TextDecorator> {
pub width: usize,
pub options: RenderOptions,
lines: LinkedList<RenderLine<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,
}
impl<D: TextDecorator + Debug> 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("decorator", &self.decorator)
.field("ann_stack", &self.ann_stack)
.field("pre_depth", &self.pre_depth)
.finish()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct RenderOptions {
pub wrap_width: Option<usize>,
pub min_wrap_width: usize,
pub allow_width_overflow: bool,
pub pad_block_width: bool,
pub raw: bool,
pub draw_borders: bool,
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
wrap_width: Default::default(),
min_wrap_width: crate::MIN_WIDTH,
allow_width_overflow: Default::default(),
pad_block_width: Default::default(),
raw: false,
draw_borders: true,
}
}
}
impl<D: TextDecorator> SubRenderer<D> {
pub fn finalise(&mut self, links: Vec<String>) -> Vec<TaggedLine<D::Annotation>> {
self.decorator.finalise(links)
}
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(),
pre_depth: 0,
text_filter_stack: Vec::new(),
}
}
fn ensure_wrapping_exists(&mut self) {
if self.wrapping.is_none() {
let wwidth = match self.options.wrap_width {
Some(ww) => ww.min(self.width),
None => self.width,
};
self.wrapping = Some(WrappedBlock::new(
wwidth,
self.options.pad_block_width,
self.options.allow_width_overflow,
));
}
}
fn current_text(&mut self) -> &mut WrappedBlock<Vec<D::Annotation>> {
self.ensure_wrapping_exists();
self.wrapping.as_mut().unwrap()
}
pub fn add_subblock(&mut self, s: &str) {
use self::TaggedLineElement::Str;
html_trace!("add_subblock({}, {})", self.width, s);
let tag = self.ann_stack.clone();
self.lines.extend(s.lines().map(|l| {
let mut line = TaggedLine::new();
line.push(Str(TaggedString {
s: l.into(),
tag: tag.clone(),
}));
RenderLine::Text(line)
}));
}
fn flush_wrapping(&mut self) -> Result<(), Error> {
if let Some(w) = self.wrapping.take() {
self.lines
.extend(w.into_lines()?.into_iter().map(RenderLine::Text))
}
Ok(())
}
fn flush_all(&mut self) -> Result<(), Error> {
self.flush_wrapping()?;
Ok(())
}
pub fn into_string(self) -> Result<String, Error> {
let mut result = String::new();
#[cfg(feature = "html_trace")]
let width: usize = self.width;
for line in self.into_lines()? {
result.push_str(&line.into_string());
result.push('\n');
}
html_trace!("into_string({}, {:?})", width, result);
Ok(result)
}
#[cfg(feature = "html_trace")]
pub fn to_string(&self) -> String {
let mut result = String::new();
for line in &self.lines {
result += &line.to_string();
result.push_str("\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 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.lines.push_back(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.lines.push_back(RenderLine::Text(wrapped_line));
}
}
pub fn into_lines(mut self) -> Result<LinkedList<RenderLine<Vec<D::Annotation>>>, Error> {
self.flush_wrapping()?;
Ok(self.lines)
}
fn add_horizontal_line(&mut self, line: BorderHoriz<Vec<D::Annotation>>) -> Result<(), Error> {
self.flush_wrapping()?;
self.lines.push_back(RenderLine::Line(line));
Ok(())
}
pub(crate) fn width_minus(&self, prefix_len: usize, min_width: usize) -> crate::Result<usize> {
let new_width = self.width.saturating_sub(prefix_len);
if new_width < min_width && !self.options.allow_width_overflow {
return Err(Error::TooNarrow);
}
Ok(new_width.max(min_width))
}
}
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)
}
impl<D: TextDecorator> Renderer for SubRenderer<D> {
fn add_empty_line(&mut self) -> crate::Result<()> {
html_trace!("add_empty_line()");
self.flush_all()?;
self.lines.push_back(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) -> crate::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) -> crate::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 new_line(&mut self) -> crate::Result<()> {
self.flush_all()
}
fn new_line_hard(&mut self) -> Result<(), Error> {
match self.wrapping {
None => self.add_empty_line(),
Some(WrappedBlock {
linelen: 0,
wordlen: 0,
..
}) => self.add_empty_line(),
Some(_) => self.flush_all(),
}
}
fn add_horizontal_border(&mut self) -> Result<(), Error> {
self.flush_wrapping()?;
self.lines.push_back(RenderLine::Line(BorderHoriz::new(
self.width,
self.ann_stack.clone(),
)));
Ok(())
}
fn add_horizontal_border_width(&mut self, width: usize) -> Result<(), Error> {
self.flush_wrapping()?;
self.lines.push_back(RenderLine::Line(BorderHoriz::new(
width,
self.ann_stack.clone(),
)));
Ok(())
}
fn start_pre(&mut self) {
self.pre_depth += 1;
}
fn end_pre(&mut self) {
if self.pre_depth > 0 {
self.pre_depth -= 1;
} else {
panic!("Attempt to end a preformatted block which wasn't opened.");
}
}
fn end_block(&mut self) {
self.at_block_end = true;
}
fn add_inline_text(&mut self, text: &str) -> crate::Result<()> {
html_trace!("add_inline_text({}, {})", self.width, text);
if self.pre_depth == 0 && self.at_block_end && text.chars().all(char::is_whitespace) {
return Ok(());
}
if self.at_block_end {
self.start_block()?;
}
let _ = self.current_text();
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);
if self.pre_depth == 0 {
self.wrapping
.as_mut()
.unwrap()
.add_text(filtered_text, &self.ann_stack)?;
} else {
let mut tag_first = self.ann_stack.clone();
let mut tag_cont = self.ann_stack.clone();
tag_first.push(self.decorator.decorate_preformat_first());
tag_cont.push(self.decorator.decorate_preformat_cont());
self.wrapping.as_mut().unwrap().add_preformatted_text(
filtered_text,
&tag_first,
&tag_cont,
)?;
}
Ok(())
}
fn width(&self) -> usize {
self.width
}
fn add_block_line(&mut self, line: &str) {
self.add_subblock(line);
}
fn append_subrender<'a, I>(&mut self, other: Self, prefixes: I) -> Result<(), Error>
where
I: Iterator<Item = &'a str>,
{
use self::TaggedLineElement::Str;
self.flush_wrapping()?;
let tag = self.ann_stack.clone();
self.lines.extend(
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.into_string(),
tag: tag.clone(),
}));
RenderLine::Text(tline)
}
}),
);
Ok(())
}
fn append_columns_with_borders<I>(&mut self, cols: I, collapse: bool) -> Result<(), Error>
where
I: IntoIterator<Item = Self>,
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| {
let width = sub_r.width;
tot_width += width;
html_trace!("Adding column:\n{}", sub_r.to_string());
Ok((
width,
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<(usize, Vec<RenderLine<_>>)>, Error>>()?;
tot_width += line_sets.len().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 &(w, _) in &line_sets[..line_sets.len() - 1] {
html_trace!("pos={}, w={}", pos, w);
prev_border.join_below(pos + w);
next_border.join_above(pos + w);
pos += w + 1;
}
}
let mut column_padding = vec![None; line_sets.len()];
if collapse {
html_trace!("Collapsing borders.");
let mut pos = 0;
for &mut (w, ref mut sublines) in &mut line_sets {
let starts_border = matches!(sublines.first(), 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 RenderLine::Line(line) = sublines.remove(0) {
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, &mut (w, ref mut sublines)) in line_sets.iter_mut().enumerate() {
if let Some(RenderLine::Line(line)) = sublines.last() {
html_trace!("Ends border");
next_border.merge_from_above(line, pos);
column_padding[col_no] = Some(line.to_vertical_lines_above());
sublines.pop();
}
pos += w + 1;
}
}
let cell_height = line_sets.iter().map(|(_, v)| v.len()).max().unwrap_or(0);
let spaces: String = (0..tot_width).map(|_| ' ').collect();
let last_cellno = line_sets.len() - 1;
let mut line = TaggedLine::new();
for i in 0..cell_height {
for (cellno, &mut (width, ref mut ls)) in line_sets.iter_mut().enumerate() {
match ls.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..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.lines.push_back(RenderLine::Text(line));
line = TaggedLine::new();
}
if self.options.draw_borders {
self.lines.push_back(RenderLine::Line(next_border));
}
Ok(())
}
fn append_vert_row<I>(&mut self, cols: I) -> Result<(), Error>
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::StraightVert,
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 text_len(&self) -> usize {
let mut result = 0;
for line in &self.lines {
result += match *line {
RenderLine::Text(ref tline) => tline.width(),
RenderLine::Line(_) => 0, };
}
if let Some(ref w) = self.wrapping {
result += w.text_len();
}
result
}
fn start_link(&mut self, target: &str) -> crate::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) -> crate::Result<()> {
let s = self.decorator.decorate_link_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_emphasis(&mut self) -> crate::Result<()> {
let (s, annotation) = self.decorator.decorate_em_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)
}
fn end_emphasis(&mut self) -> crate::Result<()> {
let s = self.decorator.decorate_em_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_strong(&mut self) -> crate::Result<()> {
let (s, annotation) = self.decorator.decorate_strong_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)
}
fn end_strong(&mut self) -> crate::Result<()> {
let s = self.decorator.decorate_strong_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
fn start_strikeout(&mut self) -> crate::Result<()> {
let (s, annotation) = self.decorator.decorate_strikeout_start();
self.ann_stack.push(annotation);
self.add_inline_text(&s)?;
self.text_filter_stack.push(filter_text_strikeout);
Ok(())
}
fn end_strikeout(&mut self) -> crate::Result<()> {
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) -> crate::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) -> crate::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) -> crate::Result<()> {
let (s, tag) = 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;
self.ensure_wrapping_exists();
self.wrapping
.as_mut()
.unwrap()
.add_element(FragmentStart(fragname.to_string()));
}
fn push_colour(&mut self, colour: Colour) {
if let Some(ann) = self.decorator.push_colour(colour) {
self.ann_stack.push(ann);
}
}
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) -> crate::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) -> crate::Result<()> {
let s = self.decorator.decorate_superscript_end();
self.add_inline_text(&s)?;
self.ann_stack.pop();
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct PlainDecorator {
nlinks: Rc<Cell<usize>>,
}
impl PlainDecorator {
#[cfg_attr(feature = "clippy", allow(new_without_default_derive))]
pub fn new() -> PlainDecorator {
PlainDecorator {
nlinks: Rc::new(Cell::new(0)),
}
}
}
impl TextDecorator for PlainDecorator {
type Annotation = ();
fn decorate_link_start(&mut self, _url: &str) -> (String, Self::Annotation) {
self.nlinks.set(self.nlinks.get() + 1);
("[".to_string(), ())
}
fn decorate_link_end(&mut self) -> String {
format!("][{}]", self.nlinks.get())
}
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 finalise(&mut self, links: Vec<String>) -> Vec<TaggedLine<()>> {
links
.into_iter()
.enumerate()
.map(|(idx, s)| TaggedLine::from_string(format!("[{}]: {}", idx + 1, s), &()))
.collect()
}
fn make_subblock_decorator(&self) -> Self {
self.clone()
}
}
#[derive(Clone, Debug)]
pub struct TrivialDecorator {}
impl TrivialDecorator {
#[cfg_attr(feature = "clippy", allow(new_without_default_derive))]
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 finalise(&mut self, _links: Vec<String>) -> Vec<TaggedLine<()>> {
Vec::new()
}
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 {
#[cfg_attr(feature = "clippy", allow(new_without_default_derive))]
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 finalise(&mut self, _links: Vec<String>) -> Vec<TaggedLine<RichAnnotation>> {
Vec::new()
}
fn make_subblock_decorator(&self) -> Self {
RichDecorator::new()
}
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
}
}