Text layout + rendering

This commit is contained in:
mars 2022-10-13 00:49:47 -06:00
parent dfdf8d73b0
commit 0e3b8ea611
3 changed files with 137 additions and 12 deletions

View File

@ -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),
}

View File

@ -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);
}

View File

@ -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>,
}