Text layout + rendering
This commit is contained in:
parent
dfdf8d73b0
commit
0e3b8ea611
36
src/dom.rs
36
src/dom.rs
|
@ -4,7 +4,9 @@ use std::str::FromStr;
|
|||
use crate::ast::{self, TagBody, WithSource};
|
||||
use crate::block::{AllowedChildren, BlockTagKind};
|
||||
use crate::node::{NodeId, NodeStore};
|
||||
use crate::text::{TaggedText, TextBlock, TextParseErrorKind, TextStyle, TextTagKind, TextParseError, TextLayout};
|
||||
use crate::text::{
|
||||
TaggedText, TextBlock, TextLayout, TextParseError, TextParseErrorKind, TextStyle, TextTagKind,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dom {
|
||||
|
@ -50,6 +52,34 @@ impl Dom {
|
|||
self.print_from(*child, indent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, width: usize) {
|
||||
self.render_from(self.root, width);
|
||||
}
|
||||
|
||||
pub fn render_from(&self, node: NodeId, width: usize) {
|
||||
if let NodeKind::Text = self.nodes.get(node).unwrap() {
|
||||
let style = TextStyle::default();
|
||||
|
||||
let layout: TextLayout = if let Some(layout) = self.nodes.get::<TextLayout>(node) {
|
||||
layout.to_owned()
|
||||
} else if let Some(block) = self.nodes.get::<TextBlock>(node) {
|
||||
block.layout(&style, 40)
|
||||
} else {
|
||||
panic!("Couldn't find text info on node {}", node);
|
||||
};
|
||||
|
||||
for line in layout.render(&style).lines.into_iter() {
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
println!("");
|
||||
}
|
||||
|
||||
for child in self.nodes.get::<NodeChildren>(node).unwrap().0.iter() {
|
||||
self.render_from(*child, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -163,7 +193,7 @@ impl<'a> DomWalkState<'a> {
|
|||
self.builder.insert(node, block);
|
||||
node
|
||||
}
|
||||
|
||||
|
||||
pub fn make_text_layout(&self, text: String) -> TextLayout {
|
||||
use BlockTagKind::*;
|
||||
match self.block.kind {
|
||||
|
@ -171,7 +201,7 @@ impl<'a> DomWalkState<'a> {
|
|||
let font = figlet_rs::FIGfont::standard().unwrap();
|
||||
let figure = font.convert(&text).expect("Failed to FIG-ify text");
|
||||
TextLayout::from_plain(&figure.to_string())
|
||||
},
|
||||
}
|
||||
Code => TextLayout::from_plain(&text),
|
||||
_ => unimplemented!("Text layout for {:?} tag", self.block.kind),
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ fn main() {
|
|||
Err(e) => panic!("DOM parse error:\n{}", e),
|
||||
};
|
||||
|
||||
let width = 40;
|
||||
let width = 80;
|
||||
dom.print();
|
||||
dom.render(width);
|
||||
}
|
||||
|
|
110
src/text.rs
110
src/text.rs
|
@ -40,11 +40,9 @@ impl TextTagKind {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumString)]
|
||||
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
|
||||
pub enum Color {
|
||||
Foreground,
|
||||
Background,
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
|
@ -55,6 +53,23 @@ pub enum Color {
|
|||
White,
|
||||
}
|
||||
|
||||
impl From<Color> for ansi_term::Color {
|
||||
fn from(c: Color) -> Self {
|
||||
use ansi_term::Color as C;
|
||||
use Color::*;
|
||||
match c {
|
||||
Black => C::Black,
|
||||
Red => C::Red,
|
||||
Green => C::Green,
|
||||
Yellow => C::Yellow,
|
||||
Blue => C::Blue,
|
||||
Magenta => C::Purple,
|
||||
Cyan => C::Cyan,
|
||||
White => C::White,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
pub struct TextStyleFlags: u32 {
|
||||
const ITALIC = 1 << 0;
|
||||
|
@ -66,16 +81,16 @@ bitflags::bitflags! {
|
|||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct TextStyle {
|
||||
pub fg: Color,
|
||||
pub bg: Color,
|
||||
pub fg: Option<Color>,
|
||||
pub bg: Option<Color>,
|
||||
pub flags: TextStyleFlags,
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fg: Color::Foreground,
|
||||
bg: Color::Background,
|
||||
fg: None,
|
||||
bg: None,
|
||||
flags: TextStyleFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +99,7 @@ impl Default for TextStyle {
|
|||
impl TextStyle {
|
||||
pub fn apply_tag(&mut self, kind: &TextTagKind) {
|
||||
if let Some(color) = kind.to_color() {
|
||||
self.fg = color;
|
||||
self.fg = Some(color);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -100,6 +115,26 @@ impl TextStyle {
|
|||
|
||||
self.flags |= flags;
|
||||
}
|
||||
|
||||
pub fn inherit(&self, base: &TextStyle) -> Self {
|
||||
Self {
|
||||
fg: self.fg.or(base.fg),
|
||||
bg: self.bg.or(base.bg),
|
||||
flags: self.flags | base.flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_painter(&self) -> ansi_term::Style {
|
||||
ansi_term::Style {
|
||||
foreground: self.fg.map(Into::into),
|
||||
background: self.bg.map(Into::into),
|
||||
is_bold: self.flags.contains(TextStyleFlags::BOLD),
|
||||
is_italic: self.flags.contains(TextStyleFlags::ITALIC),
|
||||
is_underline: self.flags.contains(TextStyleFlags::UNDERLINE),
|
||||
is_strikethrough: self.flags.contains(TextStyleFlags::STRIKETHROUGH),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
|
@ -115,6 +150,11 @@ impl StyledString {
|
|||
style: TextStyle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, base: &TextStyle) -> String {
|
||||
let painter = self.style.inherit(base).make_painter();
|
||||
painter.paint(&self.string).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
|
||||
|
@ -165,6 +205,17 @@ impl TaggedText {
|
|||
style.apply_tag(kind);
|
||||
Self::parse(&style, &body.children)
|
||||
}
|
||||
|
||||
pub fn render(&self, base: &TextStyle) -> String {
|
||||
let mut rendered = String::new();
|
||||
|
||||
for string in self.strings.iter() {
|
||||
let string = string.render(base);
|
||||
rendered.push_str(&string);
|
||||
}
|
||||
|
||||
rendered
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_plain(vals: &[ast::Value]) -> Result<String, TextParseError> {
|
||||
|
@ -210,6 +261,38 @@ pub struct TextBlock {
|
|||
pub text: TaggedText,
|
||||
}
|
||||
|
||||
impl TextBlock {
|
||||
// FIXME this function discards tag information
|
||||
pub fn layout(&self, base: &TextStyle, width: usize) -> TextLayout {
|
||||
let text = self.text.render(&base);
|
||||
|
||||
let initial_indent = self
|
||||
.initial_indent
|
||||
.as_ref()
|
||||
.map(|text| text.render(&base))
|
||||
.unwrap_or("".to_string());
|
||||
|
||||
let subsequent_indent = self
|
||||
.subsequent_indent
|
||||
.as_ref()
|
||||
.map(|text| text.render(&base))
|
||||
.unwrap_or("".to_string());
|
||||
|
||||
let options = textwrap::Options::new(width)
|
||||
.initial_indent(&initial_indent)
|
||||
.subsequent_indent(&subsequent_indent);
|
||||
|
||||
TextLayout {
|
||||
lines: textwrap::wrap(&text, options)
|
||||
.into_iter()
|
||||
.map(|line| TaggedText {
|
||||
strings: vec![StyledString::from_plain(&line)],
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextLayout {
|
||||
pub lines: Vec<TaggedText>,
|
||||
|
@ -221,4 +304,15 @@ impl TextLayout {
|
|||
lines: plain.lines().map(|s| TaggedText::from_plain(s)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, base: &TextStyle) -> RenderedText {
|
||||
RenderedText {
|
||||
lines: self.lines.iter().map(|text| text.render(base)).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderedText {
|
||||
pub lines: Vec<String>,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue