#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![deny(missing_docs)]
extern crate html5ever_atoms;
#[macro_use] extern crate html5ever;
extern crate unicode_width;
#[macro_use]
mod macros;
pub mod render;
use render::Renderer;
use render::text_renderer::{TextRenderer,
TextDecorator,PlainDecorator,RichDecorator,
RichAnnotation,TaggedLine,RenderLine};
use std::io;
use std::io::Write;
use std::cmp::max;
use std::iter::{once,repeat};
use std::ops::{Deref,DerefMut};
use std::cell::Cell;
use html5ever::{parse_document};
use html5ever::driver::ParseOpts;
use html5ever::tree_builder::TreeBuilderOpts;
use html5ever::rcdom::{self,RcDom,Handle,NodeData::{Element,Document,Comment}};
use html5ever::tendril::TendrilSink;
struct Discard {}
impl Write for Discard {
fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, io::Error> { Ok(bytes.len()) }
fn flush(&mut self) -> std::result::Result<(), io::Error> { Ok(()) }
}
const MIN_WIDTH: usize = 5;
#[derive(Debug,Copy,Clone)]
pub struct SizeEstimate {
size: usize,
min_width: usize,
}
impl Default for SizeEstimate {
fn default() -> SizeEstimate {
SizeEstimate {
size: 0,
min_width: 0,
}
}
}
impl SizeEstimate {
pub fn add(self, other: SizeEstimate) -> SizeEstimate {
SizeEstimate {
size: self.size + other.size,
min_width: max(self.min_width, other.min_width),
}
}
}
#[derive(Debug)]
pub struct RenderTableCell {
colspan: usize,
content: Vec<RenderNode>,
size_estimate: Cell<Option<SizeEstimate>>,
col_width: Option<usize>,
}
impl RenderTableCell {
pub fn render<T:Write, R:Renderer>(&mut self, _builder: &mut R, _err_out: &mut T)
{
unimplemented!()
}
pub fn get_size_estimate(&self) -> SizeEstimate {
if self.size_estimate.get().is_none() {
let size = self.content
.iter()
.map(|node| node.get_size_estimate())
.fold(Default::default(), SizeEstimate::add);
self.size_estimate.set(Some(size));
}
self.size_estimate.get().unwrap()
}
}
#[derive(Debug)]
pub struct RenderTableRow {
cells: Vec<RenderTableCell>,
col_sizes: Option<Vec<usize>>,
}
impl RenderTableRow {
pub fn cells(&self) -> std::slice::Iter<RenderTableCell> {
self.cells.iter()
}
pub fn cells_mut(&mut self) -> std::slice::IterMut<RenderTableCell> {
self.cells.iter_mut()
}
pub fn num_cells(&self) -> usize {
self.cells.iter().map(|cell| cell.colspan).sum()
}
pub fn cell_columns(&mut self) -> Vec<(usize, &mut RenderTableCell)> {
let mut result = Vec::new();
let mut colno = 0;
for cell in &mut self.cells {
let colspan = cell.colspan;
result.push((colno, cell));
colno += colspan;
}
result
}
pub fn into_cells(self) -> Vec<RenderNode> {
let mut result = Vec::new();
let mut colno = 0;
let col_sizes = self.col_sizes.unwrap();
for mut cell in self.cells {
let colspan = cell.colspan;
let col_width: usize = col_sizes[colno..colno+cell.colspan].iter().sum();
if col_width > 1 {
cell.col_width = Some(col_width - 1);
result.push(RenderNode::new(RenderNodeInfo::TableCell(cell)));
}
colno += colspan;
}
result
}
}
#[derive(Debug)]
pub struct RenderTable {
rows: Vec<RenderTableRow>,
num_columns: usize,
size_estimate: Cell<Option<SizeEstimate>>,
}
impl RenderTable {
pub fn new(rows: Vec<RenderTableRow>) -> RenderTable {
let num_columns = rows.iter()
.map(|r| r.num_cells()).max().unwrap_or(0);
RenderTable {
rows: rows,
num_columns: num_columns,
size_estimate: Cell::new(None),
}
}
pub fn rows(&self) -> std::slice::Iter<RenderTableRow> {
self.rows.iter()
}
pub fn rows_mut(&mut self) -> std::slice::IterMut<RenderTableRow> {
self.rows.iter_mut()
}
pub fn into_rows(self, col_sizes: Vec<usize>) -> Vec<RenderNode> {
self.rows
.into_iter()
.map(|mut tr| {
tr.col_sizes = Some(col_sizes.clone());
RenderNode::new(RenderNodeInfo::TableRow(tr))
})
.collect()
}
fn calc_size_estimate(&self) {
if self.num_columns == 0 {
self.size_estimate.set(Some(SizeEstimate { size: 0, min_width: 0 }));
return;
}
let mut sizes: Vec<SizeEstimate> = vec![Default::default(); self.num_columns];
for row in self.rows() {
let mut colno = 0usize;
for cell in row.cells() {
let cellsize = cell.get_size_estimate();
for colnum in 0..cell.colspan {
sizes[colno + colnum].size += cellsize.size / cell.colspan;
sizes[colno + colnum].min_width = max(sizes[colno+colnum].min_width/cell.colspan, cellsize.min_width);
}
colno += cell.colspan;
}
}
let size = sizes.iter().map(|s| s.size).sum();
let min_width = sizes.iter().map(|s| s.min_width).sum::<usize>() + self.num_columns-1;
self.size_estimate.set(Some(SizeEstimate { size: size, min_width: min_width }));
}
pub fn get_size_estimate(&self) -> SizeEstimate {
if self.size_estimate.get().is_none() {
self.calc_size_estimate();
}
self.size_estimate.get().unwrap()
}
}
#[derive(Debug)]
pub enum RenderNodeInfo {
Text(String),
Container(Vec<RenderNode>),
Link(String, Vec<RenderNode>),
Em(Vec<RenderNode>),
Strong(Vec<RenderNode>),
Code(Vec<RenderNode>),
Img(String),
Block(Vec<RenderNode>),
Header(usize, Vec<RenderNode>),
Div(Vec<RenderNode>),
Pre(Vec<RenderNode>),
BlockQuote(Vec<RenderNode>),
Ul(Vec<RenderNode>),
Ol(i64, Vec<RenderNode>),
Break,
Table(RenderTable),
TableBody(Vec<RenderTableRow>),
TableRow(RenderTableRow),
TableCell(RenderTableCell),
FragStart(String),
}
#[derive(Debug)]
pub struct RenderNode {
size_estimate: Cell<Option<SizeEstimate>>,
info: RenderNodeInfo,
}
impl RenderNode {
pub fn new(info: RenderNodeInfo) -> RenderNode {
RenderNode {
size_estimate: Cell::new(None),
info: info,
}
}
pub fn get_size_estimate(&self) -> SizeEstimate {
if let Some(s) = self.size_estimate.get() {
return s;
};
use RenderNodeInfo::*;
let estimate = match self.info {
Text(ref t) |
Img(ref t) => {
let len = t.trim().len();
SizeEstimate {
size: len,
min_width: if len > 0 { MIN_WIDTH } else { 0 },
}
},
Container(ref v) |
Link(_, ref v) |
Em(ref v) |
Strong(ref v) |
Code(ref v) |
Block(ref v) |
Div(ref v) |
Pre(ref v) |
BlockQuote(ref v) |
Ul(ref v) |
Ol(_, ref v) => {
v.iter()
.map(RenderNode::get_size_estimate)
.fold(Default::default(), SizeEstimate::add)
},
Header(level, ref v) => {
v.iter()
.map(RenderNode::get_size_estimate)
.fold(Default::default(), SizeEstimate::add).add(SizeEstimate {size:0, min_width: MIN_WIDTH+level+2})
},
Break => SizeEstimate { size: 1, min_width: 1 },
Table(ref t) => {
t.get_size_estimate()
},
TableRow(_)|TableBody(_)|TableCell(_) => {
unimplemented!()
},
FragStart(_) => Default::default(),
};
self.size_estimate.set(Some(estimate));
estimate
}
}
fn precalc_size_estimate<'a>(node: &'a RenderNode) -> TreeMapResult<(), &'a RenderNode, ()> {
use RenderNodeInfo::*;
if node.size_estimate.get().is_some() {
return TreeMapResult::Nothing;
}
match node.info {
Text(_) |
Img(_) |
Break |
FragStart(_) => {
let _ = node.get_size_estimate();
TreeMapResult::Nothing
},
Container(ref v) |
Link(_, ref v) |
Em(ref v) |
Strong(ref v) |
Code(ref v) |
Block(ref v) |
Div(ref v) |
Pre(ref v) |
BlockQuote(ref v) |
Ul(ref v) |
Ol(_, ref v) |
Header(_, ref v) => {
TreeMapResult::PendingChildren {
children: v.iter().collect(),
cons: Box::new(move |_, _cs| {
node.get_size_estimate();
None
}),
prefn: None,
postfn: None,
}
},
Table(ref t) => {
let mut children = Vec::new();
for row in &t.rows {
for cell in &row.cells {
children.extend(cell.content.iter());
}
}
TreeMapResult::PendingChildren {
children: children,
cons: Box::new(move |_, _cs| {
node.get_size_estimate();
None
}),
prefn: None,
postfn: None,
}
},
TableRow(_)|TableBody(_)|TableCell(_) => {
unimplemented!()
},
}
}
fn children_to_render_nodes<T:Write>(handle: Handle, err_out: &mut T) -> Vec<RenderNode> {
let children = handle.children
.borrow()
.iter()
.flat_map(|ch| dom_to_render_tree(ch.clone(), err_out))
.collect();
children
}
fn list_children_to_render_nodes<T:Write>(handle: Handle, err_out: &mut T) -> Vec<RenderNode> {
let mut children = Vec::new();
for child in handle.children.borrow().iter() {
match child.data {
Element { ref name, .. } => {
match name.expanded() {
expanded_name!(html "li") => {
let li_children = children_to_render_nodes(child.clone(), err_out);
children.push(RenderNode::new(RenderNodeInfo::Block(li_children)));
},
_ => {},
}
},
Comment { .. } => {},
_ => { html_trace!("Unhandled in list: {:?}\n", child); },
}
}
children
}
fn table_to_render_tree<'a, 'b, T:Write>(handle: Handle, _err_out: &'b mut T) -> TreeMapResult<'a, (), Handle, RenderNode> {
pending(handle, |_,rowset| {
let mut rows = vec![];
for bodynode in rowset {
if let RenderNodeInfo::TableBody(body) = bodynode.info {
rows.extend(body);
} else {
html_trace!("Found in table: {:?}", bodynode.info);
}
}
Some(RenderNode::new(RenderNodeInfo::Table(RenderTable::new(rows))))
})
}
fn tbody_to_render_tree<'a, 'b, T:Write>(handle: Handle, _err_out: &'b mut T) -> TreeMapResult<'a, (), Handle, RenderNode> {
pending(handle, |_,rowchildren| {
let rows = rowchildren.into_iter()
.flat_map(|rownode| {
if let RenderNodeInfo::TableRow(row) = rownode.info {
Some(row)
} else {
html_trace!(" [[tbody child: {:?}]]", rownode);
None
}})
.collect();
Some(RenderNode::new(RenderNodeInfo::TableBody(rows)))
})
}
fn tr_to_render_tree<'a, 'b, T:Write>(handle: Handle, _err_out: &'b mut T) -> TreeMapResult<'a, (), Handle, RenderNode> {
pending(handle, |_, cellnodes| {
let cells = cellnodes.into_iter()
.flat_map(|cellnode| {
if let RenderNodeInfo::TableCell(cell) = cellnode.info {
Some(cell)
} else {
html_trace!(" [[tr child: {:?}]]", cellnode);
None
}})
.collect();
Some(RenderNode::new(RenderNodeInfo::TableRow(RenderTableRow{cells, col_sizes: None})))
})
}
fn td_to_render_tree<'a, 'b, T:Write>(handle: Handle, _err_out: &'b mut T) -> TreeMapResult<'a, (), Handle, RenderNode> {
let mut colspan = 1;
if let Element { ref attrs, .. } = handle.data {
for attr in attrs.borrow().iter() {
if &attr.name.local == "colspan" {
let v:&str = &*attr.value;
colspan = v.parse().unwrap_or(1);
}
}
}
pending(handle, move |_, children| {
Some(RenderNode::new(RenderNodeInfo::TableCell(RenderTableCell {
colspan: colspan,
content: children,
size_estimate: Cell::new(None),
col_width: None,
})))
})
}
type ResultReducer<'a, C, R> = dyn Fn(&mut C, Vec<R>) -> Option<R>+'a;
type ChildPreFn<C, N> = dyn Fn(&mut C, &N);
type ChildPostFn<C, R> = dyn Fn(&mut C, &R);
enum TreeMapResult<'a, C, N, R> {
Finished(R),
PendingChildren {
children: Vec<N>,
cons: Box<ResultReducer<'a, C, R>>,
prefn: Option<Box<ChildPreFn<C, N>>>,
postfn: Option<Box<ChildPostFn<C, R>>>,
},
Nothing
}
fn tree_map_reduce<'a, C, N, R, M>(context: &mut C,
top: N,
mut process_node: M) -> Option<R>
where M: for<'c> FnMut(&'c mut C, N) -> TreeMapResult<'a, C, N, R>,
{
struct PendingNode<'a, C, R, N> {
construct: Box<ResultReducer<'a, C, R>>,
prefn: Option<Box<ChildPreFn<C, N>>>,
postfn: Option<Box<ChildPostFn<C, R>>>,
children: Vec<R>,
to_process: std::vec::IntoIter<N>,
}
let mut pending_stack = vec![
PendingNode {
construct: Box::new(|_, mut cs| cs.pop()),
prefn: None,
postfn: None,
children: Vec::new(),
to_process: vec![top].into_iter(),
}
];
loop {
let next_node = pending_stack.last_mut()
.unwrap()
.to_process
.next();
if let Some(h) = next_node {
pending_stack.last_mut().unwrap().prefn.as_ref().map(|ref f| f(context, &h));
match process_node(context, h) {
TreeMapResult::Finished(result) => {
pending_stack.last_mut().unwrap().postfn.as_ref().map(|ref f| f(context, &result));
pending_stack.last_mut().unwrap().children.push(result);
}
TreeMapResult::PendingChildren { children, cons, prefn, postfn } => {
pending_stack.push(PendingNode {
construct: cons,
prefn,
postfn,
children: Vec::new(),
to_process: children.into_iter(),
});
},
TreeMapResult::Nothing => {},
};
} else {
let completed = pending_stack.pop().unwrap();
let reduced = (completed.construct)(context, completed.children);
if let Some(node) = reduced {
if let Some(parent) = pending_stack.last_mut() {
parent.postfn.as_ref().map(|ref f| f(context, &node));
parent.children.push(node);
} else {
break Some(node);
}
} else {
if pending_stack.is_empty() {
break None;
}
}
}
}
}
pub fn dom_to_render_tree<T:Write>(handle: Handle, err_out: &mut T) -> Option<RenderNode> {
html_trace!("### dom_to_render_tree: HTML: {:?}", handle);
let result = tree_map_reduce(&mut (), handle,
|_, handle| process_dom_node(handle, err_out),
);
html_trace!("### dom_to_render_tree: out= {:#?}", result);
result
}
fn pending<'a, F>(handle: Handle, f: F) -> TreeMapResult<'a, (), Handle, RenderNode>
where
for<'r> F: Fn(&'r mut (), std::vec::Vec<RenderNode>) -> Option<RenderNode>+'static
{
TreeMapResult::PendingChildren {
children: handle.children.borrow().clone(),
cons: Box::new(f),
prefn: None,
postfn: None
}
}
fn prepend_marker(prefix: RenderNode, mut orig: RenderNode) -> RenderNode {
use RenderNodeInfo::*;
match orig.info {
Block(ref mut children) |
Div(ref mut children) |
Pre(ref mut children) |
BlockQuote(ref mut children) |
Container(ref mut children) |
TableCell(RenderTableCell { content: ref mut children, .. }) => {
children.insert(0, prefix);
},
TableRow(ref mut rrow) => {
if rrow.cells.len() > 0 {
rrow.cells[0].content.insert(0, prefix);
}
},
Table(ref mut rtable) => {
if rtable.rows.len() > 0 {
let rrow = &mut rtable.rows[0];
if rrow.cells.len() > 0 {
rrow.cells[0].content.insert(0, prefix);
}
}
},
_ => {
return RenderNode::new(Container(vec![prefix, orig]));
},
}
orig
}
fn process_dom_node<'a, 'b, T:Write>(handle: Handle, err_out: &'b mut T) -> TreeMapResult<'a, (), Handle, RenderNode> {
use TreeMapResult::*;
use RenderNodeInfo::*;
match handle.clone().data {
Document => pending(handle, |&mut (), cs| Some(RenderNode::new(Container(cs)))),
Comment { .. } => Nothing,
Element { ref name, ref attrs, .. } => {
let mut frag_from_name_attr = false;
let result = match name.expanded() {
expanded_name!(html "html") |
expanded_name!(html "span") |
expanded_name!(html "body") => {
pending(handle, |_,cs| Some(RenderNode::new(Container(cs))))
},
expanded_name!(html "link") |
expanded_name!(html "meta") |
expanded_name!(html "hr") |
expanded_name!(html "script") |
expanded_name!(html "style") |
expanded_name!(html "head") => {
Nothing
},
expanded_name!(html "a") => {
let borrowed = attrs.borrow();
let mut target = None;
frag_from_name_attr = true;
for attr in borrowed.iter() {
if &attr.name.local == "href" {
target = Some(&*attr.value);
break;
}
}
PendingChildren{
children: handle.children.borrow().clone(),
cons: if let Some(href) = target {
let href: String = href.into();
Box::new(move |_, cs| Some(RenderNode::new(Link(href.clone(), cs))))
} else {
Box::new(|_, cs| Some(RenderNode::new(Container(cs))))
},
prefn: None, postfn: None,
}
},
expanded_name!(html "em") => {
pending(handle, |_, cs| Some(RenderNode::new(Em(cs))))
},
expanded_name!(html "strong") => {
pending(handle, |_, cs| Some(RenderNode::new(Strong(cs))))
},
expanded_name!(html "code") => {
pending(handle, |_, cs| Some(RenderNode::new(Code(cs))))
},
expanded_name!(html "img") => {
let borrowed = attrs.borrow();
let mut title = None;
for attr in borrowed.iter() {
if &attr.name.local == "alt" {
title = Some(&*attr.value);
break;
}
}
if let Some(title) = title {
Finished(RenderNode::new(Img(title.into())))
} else {
Nothing
}
},
expanded_name!(html "h1") |
expanded_name!(html "h2") |
expanded_name!(html "h3") |
expanded_name!(html "h4") => {
let level: usize = name.local[1..].parse().unwrap();
pending(handle, move |_, cs| Some(RenderNode::new(Header(level, cs))))
},
expanded_name!(html "p") => {
pending(handle, |_, cs| Some(RenderNode::new(Block(cs))))
},
expanded_name!(html "div") => {
pending(handle, |_, cs| Some(RenderNode::new(Div(cs))))
},
expanded_name!(html "pre") => {
pending(handle, |_, cs| Some(RenderNode::new(Pre(cs))))
},
expanded_name!(html "br") => {
Finished(RenderNode::new(Break))
}
expanded_name!(html "table") => {
table_to_render_tree(handle.clone(), err_out)
},
expanded_name!(html "thead") |
expanded_name!(html "tbody") => {
tbody_to_render_tree(handle.clone(), err_out)
},
expanded_name!(html "tr") => {
tr_to_render_tree(handle.clone(), err_out)
},
expanded_name!(html "th") |
expanded_name!(html "td") => {
td_to_render_tree(handle.clone(), err_out)
}
expanded_name!(html "blockquote") => {
pending(handle, |_, cs| Some(RenderNode::new(BlockQuote(cs))))
},
expanded_name!(html "ul") => {
Finished(RenderNode::new(Ul(list_children_to_render_nodes(handle.clone(), err_out))))
},
expanded_name!(html "ol") => {
let borrowed = attrs.borrow();
let mut start = 1;
for attr in borrowed.iter() {
if &attr.name.local == "start" {
start = attr.value.parse().ok().unwrap_or(1);
break;
}
}
Finished(RenderNode::new(Ol(start, list_children_to_render_nodes(handle.clone(), err_out))))
},
_ => {
html_trace!("Unhandled element: {:?}\n", name.local);
pending(handle, |_, cs| Some(RenderNode::new(Container(cs))))
},
};
let mut fragment = None;
let borrowed = attrs.borrow();
for attr in borrowed.iter() {
if &attr.name.local == "id" ||
(frag_from_name_attr && &attr.name.local == "name")
{
fragment = Some(attr.value.to_string());
break;
}
}
if let Some(fragname) = fragment {
match result {
Finished(node) => Finished(prepend_marker(RenderNode::new(FragStart(fragname)), node)),
Nothing => Finished(RenderNode::new(FragStart(fragname))),
PendingChildren{children, cons, prefn, postfn} => {
let fragname: String = fragname.into();
PendingChildren {
children: children,
prefn: prefn,
postfn: postfn,
cons: Box::new(move |ctx,ch| {
let fragnode = RenderNode::new(FragStart(fragname.clone()));
match cons(ctx,ch) {
None => Some(fragnode),
Some(node) => Some(prepend_marker(fragnode, node)),
}
}),
}
},
}
} else {
result
}
},
rcdom::NodeData::Text { contents: ref tstr } => {
Finished(RenderNode::new(Text((&*tstr.borrow()).into())))
}
_ => {
write!(err_out, "Unhandled node type.\n").unwrap();
Nothing
},
}
}
struct BuilderStack<R:Renderer> {
builders: Vec<R>,
}
impl<R:Renderer> BuilderStack<R> {
pub fn new(builder: R) -> BuilderStack<R> {
BuilderStack {
builders: vec![builder],
}
}
pub fn push(&mut self, builder: R) {
self.builders.push(builder);
}
pub fn pop(&mut self) -> R {
self.builders.pop().unwrap()
}
pub fn into_inner(mut self) -> R {
assert_eq!(self.builders.len(), 1);
self.builders.pop().unwrap()
}
}
impl<R:Renderer> Deref for BuilderStack<R> {
type Target = R;
fn deref(&self) -> &R {
self.builders.last().expect("Underflow in BuilderStack")
}
}
impl<R:Renderer> DerefMut for BuilderStack<R> {
fn deref_mut(&mut self) -> &mut R {
self.builders.last_mut().expect("Underflow in BuilderStack")
}
}
fn render_tree_to_string<T:Write, R:Renderer>(builder: R, tree: RenderNode,
err_out: &mut T) -> R {
tree_map_reduce(&mut (), &tree,
|_, node| precalc_size_estimate(&node));
let mut bs = BuilderStack::new(builder);
tree_map_reduce(&mut bs, tree,
|builders, node| do_render_node(builders, node, err_out),
);
bs.into_inner()
}
fn pending2<'a, R: Renderer, F: Fn(&mut BuilderStack<R>, Vec<Option<R>>) -> Option<Option<R>> + 'static>(children: Vec<RenderNode>, f: F) -> TreeMapResult<'a, BuilderStack<R>, RenderNode, Option<R>> {
TreeMapResult::PendingChildren{
children: children,
cons: Box::new(f),
prefn: None,
postfn: None
}
}
fn do_render_node<'a, 'b, T: Write, R: Renderer>(builder: &mut BuilderStack<R>,
tree: RenderNode,
err_out: &'b mut T)
-> TreeMapResult<'static, BuilderStack<R>, RenderNode, Option<R>>
{
use TreeMapResult::*;
use RenderNodeInfo::*;
match tree.info {
Text(ref tstr) => {
builder.add_inline_text(tstr);
Finished(None)
},
Container(children) => {
pending2(children, |_, _| Some(None))
},
Link(href, children) => {
builder.start_link(&href);
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.end_link();
Some(None)
})
},
Em(children) => {
builder.start_emphasis();
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.end_emphasis();
Some(None)
})
},
Strong(children) => {
builder.start_strong();
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.end_strong();
Some(None)
})
},
Code(children) => {
builder.start_code();
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.end_code();
Some(None)
})
},
Img(title) => {
builder.add_image(&title);
Finished(None)
},
Block(children) => {
builder.start_block();
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.end_block();
Some(None)
})
},
Header(level, children) => {
let min_width = max(builder.width(), 1 + level + 1);
let sub_builder = builder.new_sub_renderer(min_width - (1 + level));
builder.push(sub_builder);
pending2(children, move |builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.pop();
let qs: String = "#".repeat(level) + " ";
builder.start_block();
builder.append_subrender(sub_builder, repeat(&qs[..]));
builder.end_block();
Some(None)
})
},
Div(children) => {
builder.new_line();
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.new_line();
Some(None)
})
},
Pre(children) => {
builder.new_line();
builder.start_pre();
pending2(children, |builder:&mut BuilderStack<R>, _| {
builder.new_line();
builder.end_pre();
Some(None)
})
},
BlockQuote(children) => {
let sub_builder = builder.new_sub_renderer(builder.width()-2);
builder.push(sub_builder);
pending2(children, |builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.pop();
builder.start_block();
builder.append_subrender(sub_builder, repeat("> "));
builder.end_block();
Some(None)
})
},
Ul(items) => {
builder.start_block();
TreeMapResult::PendingChildren{
children: items,
cons: Box::new(|_, _| Some(None)),
prefn: Some(Box::new(|builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.new_sub_renderer(builder.width()-2);
builder.push(sub_builder);
})),
postfn: Some(Box::new(|builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.pop();
builder.append_subrender(sub_builder, once("* ").chain(repeat(" ")));
})),
}
},
Ol(start, items) => {
builder.start_block();
let num_items = items.len();
let min_number = start;
let max_number = start + (num_items as i64) - 1;
let prefix_width_min = format!("{}", min_number).len() + 2;
let prefix_width_max = format!("{}", max_number).len() + 2;
let prefix_width = max(prefix_width_min, prefix_width_max);
let prefixn = format!("{: <width$}", "", width=prefix_width);
let i: Cell<_> = Cell::new(start);
TreeMapResult::PendingChildren{
children: items,
cons: Box::new(|_, _| Some(None)),
prefn: Some(Box::new(move |builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.new_sub_renderer(builder.width()-prefix_width);
builder.push(sub_builder);
})),
postfn: Some(Box::new(move |builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.pop();
let prefix1 = format!("{}.", i.get());
let prefix1 = format!("{: <width$}", prefix1, width=prefix_width);
builder.append_subrender(sub_builder, once(prefix1.as_str()).chain(repeat(prefixn.as_str())));
i.set(i.get() + 1);
})),
}
},
Break => {
builder.new_line_hard();
Finished(None)
},
Table(tab) => {
render_table_tree(builder.deref_mut(), tab, err_out)
},
TableRow(row) => {
render_table_row(builder.deref_mut(), row, err_out)
},
TableBody(_) => {
unimplemented!("Unexpected TableBody while rendering")
},
TableCell(cell) => {
render_table_cell(builder.deref_mut(), cell, err_out)
},
FragStart(fragname) => {
builder.record_frag_start(&fragname);
Finished(None)
},
}
}
fn render_table_tree<T:Write, R:Renderer>(builder: &mut R, table: RenderTable, _err_out: &mut T) -> TreeMapResult<'static, BuilderStack<R>, RenderNode, Option<R>>
{
let num_columns = table.num_columns;
let mut col_sizes: Vec<SizeEstimate> = vec![Default::default(); num_columns];
for row in table.rows() {
let mut colno = 0;
for cell in row.cells() {
let mut estimate = cell.get_size_estimate();
estimate.size /= cell.colspan;
estimate.min_width /= cell.colspan;
for i in 0..cell.colspan {
col_sizes[colno + i] = (col_sizes[colno + i]).add(estimate);
}
colno += cell.colspan;
}
}
let tot_size: usize = col_sizes.iter().map(|est| est.size).sum();
let width = builder.width();
let mut col_widths:Vec<usize> = col_sizes.iter()
.map(|sz| {
if sz.size == 0 {
0
} else {
max(sz.size * width / tot_size, sz.min_width)
}
}).collect();
while col_widths.iter().cloned().sum::<usize>() > width {
let (i, _) = col_widths.iter()
.cloned()
.enumerate()
.max_by_key(|&(colno, width)| (width.saturating_sub(col_sizes[colno].min_width), width, usize::max_value() - colno ))
.unwrap();
col_widths[i] -= 1;
}
if !col_widths.is_empty() {
let last = col_widths.len() - 1;
col_widths[last] += 1;
}
builder.start_block();
builder.add_horizontal_border();
TreeMapResult::PendingChildren{
children: table.into_rows(col_widths),
cons: Box::new(|_, _| Some(None)),
prefn: Some(Box::new(|_, _| { })),
postfn: Some(Box::new(|_, _| { })),
}
}
fn render_table_row<T:Write, R:Renderer>(_builder: &mut R, row: RenderTableRow, _err_out: &mut T) -> TreeMapResult<'static, BuilderStack<R>, RenderNode, Option<R>>
{
TreeMapResult::PendingChildren{
children: row.into_cells(),
cons: Box::new(|builders, children| {
let children: Vec<_> = children.into_iter().map(Option::unwrap).collect();
if children.iter().any(|c| !c.empty()) {
builders.append_columns_with_borders(children, true);
}
Some(None)
}),
prefn: Some(Box::new(|builder: &mut BuilderStack<R>, node| {
if let RenderNodeInfo::TableCell(ref cell) = node.info {
let sub_builder = builder.new_sub_renderer(cell.col_width.unwrap());
builder.push(sub_builder);
} else {
panic!()
}
})),
postfn: Some(Box::new(|_builder: &mut BuilderStack<R>, _| {
})),
}
}
fn render_table_cell<T:Write, R:Renderer>(_builder: &mut R, cell: RenderTableCell, _err_out: &mut T) -> TreeMapResult<'static, BuilderStack<R>, RenderNode, Option<R>>
{
pending2(cell.content, |builder: &mut BuilderStack<R>, _| {
let sub_builder = builder.pop();
Some(Some(sub_builder))
})
}
pub fn from_read_with_decorator<R, D>
(mut input: R, width: usize, decorator: D) -> String
where R: io::Read, D: TextDecorator
{
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,
..Default::default()
},
..Default::default()
};
let dom = parse_document(RcDom::default(), opts)
.from_utf8()
.read_from(&mut input)
.unwrap();
let builder = TextRenderer::new(width, decorator);
let render_tree = dom_to_render_tree(dom.document.clone(), &mut Discard{}).unwrap();
let builder = render_tree_to_string(builder, render_tree, &mut Discard{});
builder.into_string()
}
pub fn from_read<R>(input: R, width: usize) -> String where R: io::Read {
let decorator = PlainDecorator::new();
from_read_with_decorator(input, width, decorator)
}
pub fn from_read_rich<R>(mut input: R, width: usize) -> Vec<TaggedLine<Vec<RichAnnotation>>>
where R: io::Read
{
let opts = ParseOpts {
tree_builder: TreeBuilderOpts {
drop_doctype: true,
..Default::default()
},
..Default::default()
};
let dom = parse_document(RcDom::default(), opts)
.from_utf8()
.read_from(&mut input)
.unwrap();
let decorator = RichDecorator::new();
let builder = TextRenderer::new(width, decorator);
let render_tree = dom_to_render_tree(dom.document.clone(), &mut Discard{}).unwrap();
let builder = render_tree_to_string(builder, render_tree, &mut Discard{});
builder.into_lines().into_iter().map(RenderLine::into_tagged_line).collect()
}
#[cfg(test)]
mod tests;