, // The current word (with no whitespace).
wordlen: usize,
pre_wrapped: bool, // If true, we've been forced to wrap a line.
}
impl WrappedBlock {
pub fn new(width: usize) -> WrappedBlock {
WrappedBlock {
width,
text: Vec::new(),
textlen: 0,
line: TaggedLine::new(),
linelen: 0,
spacetag: None,
word: TaggedLine::new(),
wordlen: 0,
pre_wrapped: false,
}
}
fn flush_word(&mut self) {
use self::TaggedLineElement::Str;
/* Finish the word. */
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 }; // space
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.take().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");
/* Start a new line */
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");
/* We need to split the word. */
let mut wordbits = self.word.drain_all();
/* Note: there's always at least one piece */
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 {
/* Split into two */
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 {
split_idx = idx;
break;
}
}
self.line.push(Str(TaggedString {
s: piece.s[..split_idx].into(),
tag: piece.tag.clone(),
}));
{
let mut tmp_line = TaggedLine::new();
mem::swap(&mut tmp_line, &mut self.line);
self.text.push(tmp_line);
}
lineleft = self.width;
self.linelen = 0;
html_trace!("linelen set to zero here");
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;
}
fn flush_line(&mut self) {
if !self.line.is_empty() {
let mut tmp_line = TaggedLine::new();
mem::swap(&mut tmp_line, &mut self.line);
self.text.push(tmp_line);
self.linelen = 0;
}
}
fn flush(&mut self) {
self.flush_word();
self.flush_line();
}
/// Consume self and return a vector of lines.
/*
pub fn into_untagged_lines(mut self) -> Vec {
self.flush();
let mut result = Vec::new();
for line in self.text.into_iter() {
let mut line_s = String::new();
for TaggedString{ s, .. } in line.into_iter() {
line_s.push_str(&s);
}
result.push(line_s);
}
result
}
*/
/// Consume self and return vector of lines including annotations.
pub fn into_lines(mut self) -> Vec> {
self.flush();
self.text
}
pub fn add_text(&mut self, text: &str, tag: &T) {
html_trace!("WrappedBlock::add_text({}), {:?}", text, tag);
for c in text.chars() {
if c.is_whitespace() {
/* Whitespace is mostly ignored, except to terminate words. */
self.flush_word();
self.spacetag = Some(tag.clone());
} else if let Some(charwidth) = UnicodeWidthChar::width(c) {
/* Not whitespace; add to the current word. */
self.word.push_char(c, tag);
self.wordlen += charwidth;
}
html_trace_quiet!(" Added char {:?}, wordlen={}", c, self.wordlen);
}
}
pub fn add_preformatted_text(&mut self, text: &str, tag_main: &T, tag_wrapped: &T) {
html_trace!(
"WrappedBlock::add_preformatted_text({}), {:?}/{:?}",
text,
tag_main,
tag_wrapped
);
// Make sure that any previous word has been sent to the line, as we
// bypass the word buffer.
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.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;
}
}
}
_ => {
eprintln!("Got character: {:?}", c);
}
}
}
html_trace_quiet!(" Added char {:?}", c);
}
}
pub fn add_element(&mut self, elt: TaggedLineElement) {
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
}
}
/// Allow decorating/styling text.
///
/// Decorating refers to adding extra text around the rendered version
/// of some elements, such as surrounding emphasised text with `*` like
/// in markdown: `Some *bold* text`. The decorations are formatted and
/// wrapped along with the rest of the rendered text. This is
///
/// In addition, instances of `TextDecorator` can also return annotations
/// of an associated type `Annotation` which will be associated with spans of
/// text. This can be anything from `()` as for `PlainDecorator` or a more
/// featured type such as `RichAnnotation`. The annotated spans (`TaggedLine`)
/// can be used by application code to add e.g. terminal colours or underlines.
pub trait TextDecorator {
/// An annotation which can be added to text, and which will
/// be attached to spans of text.
type Annotation: Eq + PartialEq + Debug + Clone + Default;
/// Return an annotation and rendering prefix for a link.
fn decorate_link_start(&mut self, url: &str) -> (String, Self::Annotation);
/// Return a suffix for after a link.
fn decorate_link_end(&mut self) -> String;
/// Return an annotation and rendering prefix for em
fn decorate_em_start(&mut self) -> (String, Self::Annotation);
/// Return a suffix for after an em.
fn decorate_em_end(&mut self) -> String;
/// Return an annotation and rendering prefix for strong
fn decorate_strong_start(&mut self) -> (String, Self::Annotation);
/// Return a suffix for after an strong.
fn decorate_strong_end(&mut self) -> String;
/// Return an annotation and rendering prefix for strikeout
fn decorate_strikeout_start(&mut self) -> (String, Self::Annotation);
/// Return a suffix for after an strikeout.
fn decorate_strikeout_end(&mut self) -> String;
/// Return an annotation and rendering prefix for code
fn decorate_code_start(&mut self) -> (String, Self::Annotation);
/// Return a suffix for after an code.
fn decorate_code_end(&mut self) -> String;
/// Return an annotation for the initial part of a preformatted line
fn decorate_preformat_first(&mut self) -> Self::Annotation;
/// Return an annotation for a continuation line when a preformatted
/// line doesn't fit.
fn decorate_preformat_cont(&mut self) -> Self::Annotation;
/// Return an annotation and rendering prefix for a link.
fn decorate_image(&mut self, title: &str) -> (String, Self::Annotation);
/// Return prefix string of header in specific level.
fn header_prefix(&mut self, level: usize) -> String;
/// Return prefix string of quoted block.
fn quote_prefix(&mut self) -> String;
/// Return prefix string of unordered list item.
fn unordered_item_prefix(&mut self) -> String;
/// Return prefix string of ith ordered list item.
fn ordered_item_prefix(&mut self, i: i64) -> String;
/// Return a new decorator of the same type which can be used
/// for sub blocks.
fn make_subblock_decorator(&self) -> Self;
/// Finish with a document, and return extra lines (eg footnotes)
/// to add to the rendered text.
fn finalise(self) -> Vec>;
}
/// A space on a horizontal row.
#[derive(Copy, Clone, Debug)]
pub enum BorderSegHoriz {
/// Pure horizontal line
Straight,
/// Joined with a line above
JoinAbove,
/// Joins with a line below
JoinBelow,
/// Joins both ways
JoinCross,
/// Horizontal line, but separating two table cells from a row
/// which wouldn't fit next to each other.
StraightVert,
}
/// A dividing line between table rows which tracks intersections
/// with vertical lines.
#[derive(Clone, Debug)]
pub struct BorderHoriz {
/// The segments for the line.
pub segments: Vec,
}
impl BorderHoriz {
/// Create a new blank border line.
pub fn new(width: usize) -> BorderHoriz {
BorderHoriz {
segments: vec![BorderSegHoriz::Straight; width],
}
}
/// Create a new blank border line.
pub fn new_type(width: usize, linetype: BorderSegHoriz) -> BorderHoriz {
BorderHoriz {
segments: vec![linetype; width],
}
}
/// Stretch the line to at least the specified width
pub fn stretch_to(&mut self, width: usize) {
use self::BorderSegHoriz::*;
while width > self.segments.len() {
self.segments.push(Straight);
}
}
/// Make a join to a line above at the xth cell
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,
}
}
/// Make a join to a line below at the xth cell
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,
}
}
/// Merge a (possibly partial) border line below into this one.
pub fn merge_from_below(&mut self, other: &BorderHoriz, 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);
}
}
}
}
/// Merge a (possibly partial) border line above into this one.
pub fn merge_from_above(&mut self, other: &BorderHoriz, 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);
}
}
}
}
/// Return a string of spaces and vertical lines which would match
/// just above this line.
pub fn to_vertical_lines_above(&self) -> String {
use self::BorderSegHoriz::*;
self.segments
.iter()
.map(|seg| match *seg {
Straight | JoinBelow | StraightVert => ' ',
JoinAbove | JoinCross => '│',
})
.collect()
}
/// Turn into a string with drawing characters
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::()
}
/// Return a string without destroying self
pub fn to_string(&self) -> String {
self.clone().into_string()
}
}
/// A line, which can either be text or a line.
#[derive(Clone, Debug)]
pub enum RenderLine {
/// Some rendered text
Text(TaggedLine),
/// A table border line
Line(BorderHoriz),
}
impl RenderLine {
/// Turn the rendered line into a String
pub fn into_string(self) -> String {
match self {
RenderLine::Text(tagged) => tagged.into_string(),
RenderLine::Line(border) => border.into_string(),
}
}
/// Convert into a `TaggedLine`, if necessary squashing the
/// BorderHoriz into one.
pub fn into_tagged_line(self) -> TaggedLine {
use self::TaggedLineElement::Str;
match self {
RenderLine::Text(tagged) => tagged,
RenderLine::Line(border) => {
let mut tagged = TaggedLine::new();
tagged.push(Str(TaggedString {
s: border.into_string(),
tag: T::default(),
}));
tagged
}
}
}
#[cfg(feature = "html_trace")]
/// For testing, return a simple string of the contents.
fn to_string(&self) -> String {
match self {
RenderLine::Text(tagged) => tagged.to_string(),
RenderLine::Line(border) => border.to_string(),
}
}
}
/// A renderer which just outputs plain text with
/// annotations depending on a decorator.
#[derive(Clone)]
pub struct TextRenderer {
width: usize,
lines: LinkedList>>,
/// True at the end of a block, meaning we should add
/// a blank line if any other text is added.
at_block_end: bool,
wrapping: Option>>,
decorator: Option,
ann_stack: Vec,
text_filter_stack: Vec Option>,
/// The depth of block stacking.
pre_depth: usize,
}
impl std::fmt::Debug for TextRenderer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("TextRenderer")
.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()
}
}
impl TextRenderer {
/// Construct a new empty TextRenderer.
pub fn new(width: usize, decorator: D) -> TextRenderer {
html_trace!("new({})", width);
TextRenderer {
width,
lines: LinkedList::new(),
at_block_end: false,
wrapping: None,
decorator: Some(decorator),
ann_stack: Vec::new(),
pre_depth: 0,
text_filter_stack: Vec::new(),
}
}
fn ensure_wrapping_exists(&mut self) {
if self.wrapping.is_none() {
self.wrapping = Some(WrappedBlock::new(self.width));
}
}
/// Get the current line wrapping context (and create if
/// needed).
fn current_text(&mut self) -> &mut WrappedBlock> {
self.ensure_wrapping_exists();
self.wrapping.as_mut().unwrap()
}
/// Add a prerendered (multiline) string with the current annotations.
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)
}));
}
/// Flushes the current wrapped block into the lines.
fn flush_wrapping(&mut self) {
if let Some(w) = self.wrapping.take() {
self.lines
.extend(w.into_lines().into_iter().map(RenderLine::Text))
}
}
/// Flush the wrapping text and border. Only one should have
/// anything to do.
fn flush_all(&mut self) {
self.flush_wrapping();
}
/// Consumes this renderer and return a multiline `String` with the result.
pub fn into_string(self) -> String {
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);
result
}
#[cfg(feature = "html_trace")]
/// Returns a string of the current builder contents (for testing).
fn to_string(&self) -> String {
let mut result = String::new();
for line in &self.lines {
result += &line.to_string();
result.push_str("\n");
}
result
}
/// Returns a `Vec` of `TaggedLine`s with therendered text.
pub fn into_lines(mut self) -> LinkedList>> {
self.flush_wrapping();
// And add the links
let mut trailer = self.decorator.take().unwrap().finalise();
if !trailer.is_empty() {
self.start_block();
for line in trailer.drain(0..) {
/* Hard wrap */
let mut pos = 0;
let mut wrapped_line = TaggedLine::new();
for ts in line.into_tagged_strings() {
// FIXME: should we percent-escape? This is probably
// an invalid URL to start with.
let s = ts.s.replace('\n', " ");
let tag = vec![ts.tag];
let width = s.width();
if pos + width > self.width {
// split the string and start a new line
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));
}
}
self.lines
}
fn add_horizontal_line(&mut self, line: BorderHoriz) {
self.flush_wrapping();
self.lines.push_back(RenderLine::Line(line));
}
}
fn filter_text_strikeout(s: &str) -> Option {
let mut result = String::new();
for c in s.chars() {
result.push(c);
if UnicodeWidthChar::width(c).unwrap_or(0) > 0 {
// This is a character with width (not a combining or other character)
// so add a strikethrough combiner.
result.push('\u{336}');
}
}
Some(result)
}
impl Renderer for TextRenderer {
fn add_empty_line(&mut self) {
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);
}
fn new_sub_renderer(&self, width: usize) -> Self {
TextRenderer::new(
width,
self.decorator.as_ref().unwrap().make_subblock_decorator(),
)
}
fn start_block(&mut self) {
html_trace!("start_block({})", self.width);
self.flush_all();
if !self.lines.is_empty() {
self.add_empty_line();
}
html_trace_quiet!("start_block; at_block_end <- false");
self.at_block_end = false;
}
fn new_line(&mut self) {
self.flush_all();
}
fn new_line_hard(&mut self) {
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) {
self.flush_wrapping();
self.lines
.push_back(RenderLine::Line(BorderHoriz::new(self.width)));
}
fn add_horizontal_border_width(&mut self, width: usize) {
self.flush_wrapping();
self.lines
.push_back(RenderLine::Line(BorderHoriz::new(width)));
}
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) {
html_trace!("add_inline_text({}, {})", self.width, text);
if self.pre_depth == 0 && self.at_block_end && text.chars().all(char::is_whitespace) {
// Ignore whitespace between blocks.
return;
}
if self.at_block_end {
self.start_block();
}
// ensure wrapping is set
let _ = self.current_text();
let mut s = None;
// Do any filtering of the text
for filter in &self.text_filter_stack {
// When we stop supporting Rust < 1.40, this can become:
//let srctext = s.as_deref().unwrap_or(text);
let srctext = s.as_ref().map(Deref::deref).unwrap_or(text);
if let Some(filtered) = filter(srctext) {
s = Some(filtered);
}
}
// When we stop supporting Rust < 1.40, this can become:
//let filtered_text = s.as_deref().unwrap_or(text);
let filtered_text = s.as_ref().map(Deref::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.as_mut().unwrap().decorate_preformat_first());
tag_cont.push(self.decorator.as_mut().unwrap().decorate_preformat_cont());
self.wrapping.as_mut().unwrap().add_preformatted_text(
filtered_text,
&tag_first,
&tag_cont,
);
}
}
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)
where
I: Iterator- ,
{
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)
}
}),
);
}
fn append_columns_with_borders(&mut self, cols: I, collapse: bool)
where
I: IntoIterator
- ,
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());
(
width,
sub_r
.into_lines()
.into_iter()
.map(|mut line| {
match line {
RenderLine::Text(ref mut tline) => {
tline.pad_to(width);
}
RenderLine::Line(ref mut border) => {
border.stretch_to(width);
}
}
line
})
.collect(),
)
})
.collect::>)>>();
tot_width += line_sets.len().saturating_sub(1);
let mut next_border = BorderHoriz::new(tot_width);
// Join the vertical lines to all the borders
{
let mut pos = 0;
if let &mut RenderLine::Line(ref mut prev_border) = self.lines.back_mut().unwrap() {
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;
}
} else {
panic!("Expected a border line");
}
}
// If we're collapsing bottom borders, then the bottom border of a
// nested table is being merged into the bottom border of the
// containing cell. If that cell happens not to be the tallest
// cell in the row, then we need to extend any vertical lines
// to the bottom. We'll remember what to do when we update the
// containing border.
let mut column_padding = vec![None; line_sets.len()];
// If we're collapsing borders, do so.
if collapse {
html_trace!("Collapsing borders.");
/* Collapse any top border */
let mut pos = 0;
for &mut (w, ref mut sublines) in &mut line_sets {
let starts_border = if sublines.len() > 0 {
if let RenderLine::Line(_) = sublines[0] {
true
} else {
false
}
} else {
false
};
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;
}
/* Collapse any bottom border */
let mut pos = 0;
for (col_no, &mut (w, ref mut sublines)) in line_sets.iter_mut().enumerate() {
let ends_border = if sublines.len() > 0 {
if let Some(&RenderLine::Line(_)) = sublines.last() {
true
} else {
false
}
} else {
false
};
if ends_border {
html_trace!("Ends border");
if let RenderLine::Line(line) = sublines.pop().unwrap() {
next_border.merge_from_above(&line, pos);
column_padding[col_no] = Some(line.to_vertical_lines_above())
}
}
pos += w + 1;
}
}
let cell_height = line_sets
.iter()
.map(|&(_, ref v)| v.len())
.max()
.unwrap_or(0);
let spaces: String = (0..tot_width).map(|_| ' ').collect();
let last_cellno = line_sets.len() - 1;
for i in 0..cell_height {
let mut line = TaggedLine::new();
for (cellno, &mut (width, ref mut ls)) in line_sets.iter_mut().enumerate() {
if let Some(piece) = ls.get_mut(i) {
match piece {
&mut RenderLine::Text(ref mut tline) => {
line.consume(tline);
}
&mut RenderLine::Line(ref bord) => {
line.push(Str(TaggedString {
s: bord.to_string(),
tag: self.ann_stack.clone(),
}));
}
};
} else {
line.push(Str(TaggedString {
s: column_padding[cellno]
.as_ref()
.map(|s| s.clone())
.unwrap_or_else(|| spaces[0..width].to_string()),
tag: self.ann_stack.clone(),
}));
}
if cellno != last_cellno {
line.push_char('│', &self.ann_stack);
}
}
self.lines.push_back(RenderLine::Text(line));
}
self.lines.push_back(RenderLine::Line(next_border));
}
fn append_vert_row(&mut self, cols: I)
where
I: IntoIterator
- ,
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 {
let border = BorderHoriz::new_type(width, BorderSegHoriz::StraightVert);
self.add_horizontal_line(border);
}
self.append_subrender(col, std::iter::repeat(""));
}
self.add_horizontal_border();
}
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, // FIXME: should borders count?
};
}
if let Some(ref w) = self.wrapping {
result += w.text_len();
}
result
}
fn start_link(&mut self, target: &str) {
if let Some((s, annotation)) = self
.decorator
.as_mut()
.map(|d| d.decorate_link_start(target))
{
self.ann_stack.push(annotation);
self.add_inline_text(&s);
}
}
fn end_link(&mut self) {
if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_link_end()) {
self.add_inline_text(&s);
self.ann_stack.pop();
}
}
fn start_emphasis(&mut self) {
if let Some((s, annotation)) = self.decorator.as_mut().map(|d| d.decorate_em_start()) {
self.ann_stack.push(annotation);
self.add_inline_text(&s);
}
}
fn end_emphasis(&mut self) {
if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_em_end()) {
self.add_inline_text(&s);
self.ann_stack.pop();
}
}
fn start_strong(&mut self) {
if let Some((s, annotation)) = self.decorator.as_mut().map(|d| d.decorate_strong_start()) {
self.ann_stack.push(annotation);
self.add_inline_text(&s);
}
}
fn end_strong(&mut self) {
if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_strong_end()) {
self.add_inline_text(&s);
self.ann_stack.pop();
}
}
fn start_strikeout(&mut self) {
if let Some((s, annotation)) = self
.decorator
.as_mut()
.map(|d| d.decorate_strikeout_start())
{
self.ann_stack.push(annotation);
self.add_inline_text(&s);
}
self.text_filter_stack.push(filter_text_strikeout);
}
fn end_strikeout(&mut self) {
self.text_filter_stack.pop().unwrap();
if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_strikeout_end()) {
self.add_inline_text(&s);
self.ann_stack.pop();
}
}
fn start_code(&mut self) {
if let Some((s, annotation)) = self.decorator.as_mut().map(|d| d.decorate_code_start()) {
self.ann_stack.push(annotation);
self.add_inline_text(&s);
}
}
fn end_code(&mut self) {
if let Some(s) = self.decorator.as_mut().map(|d| d.decorate_code_end()) {
self.add_inline_text(&s);
self.ann_stack.pop();
}
}
fn add_image(&mut self, title: &str) {
if let Some((s, tag)) = self.decorator.as_mut().map(|d| d.decorate_image(title)) {
self.ann_stack.push(tag);
self.add_inline_text(&s);
self.ann_stack.pop();
}
}
fn header_prefix(&mut self, level: usize) -> String {
if let Some(d) = self.decorator.as_mut() {
d.header_prefix(level)
} else {
"".to_owned()
}
}
fn quote_prefix(&mut self) -> String {
if let Some(d) = self.decorator.as_mut() {
d.quote_prefix()
} else {
"".to_owned()
}
}
fn unordered_item_prefix(&mut self) -> String {
if let Some(d) = self.decorator.as_mut() {
d.unordered_item_prefix()
} else {
"".to_owned()
}
}
fn ordered_item_prefix(&mut self, i: i64) -> String {
if let Some(d) = self.decorator.as_mut() {
d.ordered_item_prefix(i)
} else {
"".to_owned()
}
}
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()));
}
}
/// A decorator for use with `TextRenderer` which outputs plain UTF-8 text
/// with no annotations. Markup is rendered as text characters or footnotes.
#[derive(Clone, Debug)]
pub struct PlainDecorator {
links: Vec,
}
impl PlainDecorator {
/// Create a new `PlainDecorator`.
#[cfg_attr(feature = "clippy", allow(new_without_default_derive))]
pub fn new() -> PlainDecorator {
PlainDecorator { links: Vec::new() }
}
}
impl TextDecorator for PlainDecorator {
type Annotation = ();
fn decorate_link_start(&mut self, url: &str) -> (String, Self::Annotation) {
self.links.push(url.to_string());
("[".to_string(), ())
}
fn decorate_link_end(&mut self) -> String {
format!("][{}]", self.links.len())
}
fn decorate_em_start(&mut self) -> (String, Self::Annotation) {
("*".to_string(), ())
}
fn decorate_em_end(&mut self) -> String {
"*".to_string()
}
fn decorate_strong_start(&mut self) -> (String, Self::Annotation) {
("**".to_string(), ())
}
fn decorate_strong_end(&mut self) -> String {
"**".to_string()
}
fn decorate_strikeout_start(&mut self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strikeout_end(&mut self) -> String {
"".to_string()
}
fn decorate_code_start(&mut self) -> (String, Self::Annotation) {
("`".to_string(), ())
}
fn decorate_code_end(&mut self) -> String {
"`".to_string()
}
fn decorate_preformat_first(&mut self) -> Self::Annotation {
()
}
fn decorate_preformat_cont(&mut self) -> Self::Annotation {
()
}
fn decorate_image(&mut self, title: &str) -> (String, Self::Annotation) {
(format!("[{}]", title), ())
}
fn header_prefix(&mut self, level: usize) -> String {
"#".repeat(level) + " "
}
fn quote_prefix(&mut self) -> String {
"> ".to_string()
}
fn unordered_item_prefix(&mut self) -> String {
"* ".to_string()
}
fn ordered_item_prefix(&mut self, i: i64) -> String {
format!("{}. ", i)
}
fn finalise(self) -> Vec> {
self.links
.into_iter()
.enumerate()
.map(|(idx, s)| TaggedLine::from_string(format!("[{}]: {}", idx + 1, s), &()))
.collect()
}
fn make_subblock_decorator(&self) -> Self {
PlainDecorator::new()
}
}
/// A decorator for use with `TextRenderer` which outputs plain UTF-8 text
/// with no annotations or markup, emitting only the literal text.
#[derive(Clone, Debug)]
pub struct TrivialDecorator {}
impl TrivialDecorator {
/// Create a new `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(&mut self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_em_end(&mut self) -> String {
"".to_string()
}
fn decorate_strong_start(&mut self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strong_end(&mut self) -> String {
"".to_string()
}
fn decorate_strikeout_start(&mut self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_strikeout_end(&mut self) -> String {
"".to_string()
}
fn decorate_code_start(&mut self) -> (String, Self::Annotation) {
("".to_string(), ())
}
fn decorate_code_end(&mut self) -> String {
"".to_string()
}
fn decorate_preformat_first(&mut self) -> Self::Annotation {
()
}
fn decorate_preformat_cont(&mut self) -> Self::Annotation {
()
}
fn decorate_image(&mut self, title: &str) -> (String, Self::Annotation) {
// FIXME: this should surely be the alt text, not the title text
(title.to_string(), ())
}
fn header_prefix(&mut self, _level: usize) -> String {
"".to_string()
}
fn quote_prefix(&mut self) -> String {
"".to_string()
}
fn unordered_item_prefix(&mut self) -> String {
"".to_string()
}
fn ordered_item_prefix(&mut self, _i: i64) -> String {
"".to_string()
}
fn finalise(self) -> Vec> {
Vec::new()
}
fn make_subblock_decorator(&self) -> Self {
TrivialDecorator::new()
}
}
/// A decorator to generate rich text (styled) rather than
/// pure text output.
#[derive(Clone, Debug)]
pub struct RichDecorator {}
/// Annotation type for "rich" text. Text is associated with a set of
/// these.
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum RichAnnotation {
/// Normal text.
Default,
/// A link with the target.
Link(String),
/// An image (attached to the title text)
Image,
/// Emphasised text, which might be rendered in bold or another colour.
Emphasis,
/// Strong text, which might be rendered in bold or another colour.
Strong,
/// Stikeout text
Strikeout,
/// Code
Code,
/// Preformatted; true if a continuation line for an overly-long line.
Preformat(bool),
}
impl Default for RichAnnotation {
fn default() -> Self {
RichAnnotation::Default
}
}
impl RichDecorator {
/// Create a new `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(&mut self) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Emphasis)
}
fn decorate_em_end(&mut self) -> String {
"".to_string()
}
fn decorate_strong_start(&mut self) -> (String, Self::Annotation) {
("*".to_string(), RichAnnotation::Strong)
}
fn decorate_strong_end(&mut self) -> String {
"*".to_string()
}
fn decorate_strikeout_start(&mut self) -> (String, Self::Annotation) {
("".to_string(), RichAnnotation::Strikeout)
}
fn decorate_strikeout_end(&mut self) -> String {
"".to_string()
}
fn decorate_code_start(&mut self) -> (String, Self::Annotation) {
("`".to_string(), RichAnnotation::Code)
}
fn decorate_code_end(&mut self) -> String {
"`".to_string()
}
fn decorate_preformat_first(&mut self) -> Self::Annotation {
RichAnnotation::Preformat(false)
}
fn decorate_preformat_cont(&mut self) -> Self::Annotation {
RichAnnotation::Preformat(true)
}
fn decorate_image(&mut self, title: &str) -> (String, Self::Annotation) {
(title.to_string(), RichAnnotation::Image)
}
fn header_prefix(&mut self, level: usize) -> String {
"#".repeat(level) + " "
}
fn quote_prefix(&mut self) -> String {
"> ".to_string()
}
fn unordered_item_prefix(&mut self) -> String {
"* ".to_string()
}
fn ordered_item_prefix(&mut self, i: i64) -> String {
format!("{}. ", i)
}
fn finalise(self) -> Vec