tml/src/layout.rs

185 lines
5.9 KiB
Rust

use crate::style::Stylesheet;
use crate::tag::*;
impl TextTag {
pub fn layout<'a>(
&self,
style: &Stylesheet,
options: impl Into<textwrap::Options<'a>>,
) -> Vec<String> {
let styled = self.style(style);
textwrap::wrap(&styled, options)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn style_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
let child_string = match child {
TextNode::Tag(tag) => tag.style(style),
TextNode::Text(string) => string.to_owned(),
};
string.push_str(&child_string);
}
string
}
pub fn style(&self, style: &Stylesheet) -> String {
let string = self.style_children(style);
use TextTagKind::*;
let painter = match self.kind {
Plain => return string,
Underline => style.underline,
Bold => style.bold,
Italic => style.italic,
Strikethrough => style.strikethrough,
};
painter.paint(string).to_string()
}
}
impl BlockTag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
use BlockTagKind::*;
match self.kind {
H1 | H2 | H3 => {
const INDENT: usize = 4;
let styled = self.style_text_children(style);
let bullets = match self.kind {
H1 => "# ",
H2 => "## ",
H3 => "### ",
_ => unreachable!(),
};
let indent: String = std::iter::repeat(" ").take(INDENT).collect();
let options = textwrap::Options::new(available_width)
.initial_indent(&bullets)
.subsequent_indent(&indent);
let lines = textwrap::wrap(&styled, options);
let mut max_len = 0;
lines
.iter()
.enumerate()
.map(|(index, line)| {
let mut line = line.to_string();
max_len = max_len.max(line.len());
let painter = if index == lines.len() - 1 {
while line.len() < max_len {
line.push(' ');
}
&style.header_style_floor
} else {
&style.header_style_float
};
painter.paint(line).to_string()
})
.collect()
}
P => self.layout_text_children(style, available_width),
Ul => self.layout_list_items(style, available_width, false),
Ol => self.layout_list_items(style, available_width, true),
Li | Tr | Td => panic!("{:?} tag cannot be directly laid out", self.kind),
_ => unimplemented!(),
}
}
pub fn layout_list_items(
&self,
style: &Stylesheet,
available_width: usize,
ordered: bool,
) -> Vec<String> {
let indent = style.list_indent;
let mut lines = Vec::new();
let mut index = 1;
let spacer: String = std::iter::repeat(" ").take(indent).collect();
let prefix = format!("{:>width$} ", style.ul_prefix, width = indent - 2);
let mut prefix = style.bullet.paint(prefix).to_string();
let available_width = if available_width > indent {
available_width - indent
} else {
0
};
for child in self.children.iter() {
if ordered {
prefix = format!("{:>width$}. ", index, width = indent - 2);
prefix = style.bullet.paint(prefix).to_string();
}
let options = textwrap::Options::new(available_width)
.initial_indent(&prefix)
.subsequent_indent(&spacer);
match child {
BlockNode::Text(tag) => {
index += 1;
let mut li = tag.layout(style, options);
lines.append(&mut li);
}
BlockNode::Block(tag) => {
let li = match tag.kind {
BlockTagKind::Ol => tag.layout_list_items(style, available_width, true),
BlockTagKind::Ul => tag.layout_list_items(style, available_width, false),
BlockTagKind::Li => {
index += 1;
let mut li = tag.layout_text_children(style, options);
lines.append(&mut li);
continue;
}
_ => panic!("Unexpected {:?} in {:?}", tag.kind, self.kind),
};
lines.extend(li.into_iter().map(|line| format!("{}{}", spacer, line)));
}
BlockNode::Leaf(_) => panic!("Unexpected leaf tag in {:?}", self.kind),
}
}
lines
}
pub fn layout_text_children<'a>(
&self,
style: &Stylesheet,
options: impl Into<textwrap::Options<'a>>,
) -> Vec<String> {
let styled = self.style_text_children(style);
textwrap::wrap(&styled, options)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn style_text_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
match child {
BlockNode::Text(tag) => string.push_str(&tag.style(style)),
_ => panic!("{:?} node attempted to style a non-text child", self.kind),
}
}
string
}
}