mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-30 00:20:34 +00:00
2cfe82a47e
- Simplify `assert_buffer_eq!` logic. - Deprecate `assert_buffer_eq!`. - Introduce `TestBackend::assert_buffer_lines`. Also simplify many tests involving buffer comparisons. For the deprecation, just use `assert_eq` instead of `assert_buffer_eq`: ```diff -assert_buffer_eq!(actual, expected); +assert_eq!(actual, expected); ``` --- I noticed `assert_buffer_eq!` creating no test coverage reports and looked into this macro. First I simplified it. Then I noticed a bunch of `assert_eq!(buffer, …)` and other indirect usages of this macro (like `TestBackend::assert_buffer`). The good thing here is that it's mainly used in tests so not many changes to the library code.
815 lines
36 KiB
Rust
815 lines
36 KiB
Rust
//! [tui-big-text] is a rust crate that renders large pixel text as a [Ratatui] widget using the
|
|
//! glyphs from the [font8x8] crate.
|
|
//!
|
|
//! ![Hello World example](https://vhs.charm.sh/vhs-2UxNc2SJgiNqHoowbsXAMW.gif)
|
|
//!
|
|
//! # Installation
|
|
//!
|
|
//! ```shell
|
|
//! cargo add ratatui tui-big-text
|
|
//! ```
|
|
//!
|
|
//! # Usage
|
|
//!
|
|
//! Create a [`BigText`] widget using `BigTextBuilder` and pass it to [`Frame::render_widget`] to
|
|
//! render be rendered. The builder allows you to customize the [`Style`] of the widget and the
|
|
//! [`PixelSize`] of the glyphs. The [`PixelSize`] can be used to control how many character cells
|
|
//! are used to represent a single pixel of the 8x8 font.
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```rust
|
|
//! use anyhow::Result;
|
|
//! use ratatui::prelude::*;
|
|
//! use tui_big_text::{BigTextBuilder, PixelSize};
|
|
//!
|
|
//! fn render(frame: &mut Frame) -> Result<()> {
|
|
//! let big_text = BigTextBuilder::default()
|
|
//! .pixel_size(PixelSize::Full)
|
|
//! .style(Style::new().blue())
|
|
//! .lines(vec![
|
|
//! "Hello".red().into(),
|
|
//! "World".white().into(),
|
|
//! "~~~~~".into(),
|
|
//! ])
|
|
//! .build()?;
|
|
//! frame.render_widget(big_text, frame.size());
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! [tui-big-text]: https://crates.io/crates/tui-big-text
|
|
//! [Ratatui]: https://crates.io/crates/ratatui
|
|
//! [font8x8]: https://crates.io/crates/font8x8
|
|
//! [`BigText`]: crate::BigText
|
|
//! [`PixelSize`]: crate::PixelSize
|
|
//! [`Frame::render_widget`]: ratatui::Frame::render_widget
|
|
//! [`Style`]: ratatui::style::Style
|
|
|
|
use std::cmp::min;
|
|
|
|
use derive_builder::Builder;
|
|
use font8x8::UnicodeFonts;
|
|
use ratatui::{prelude::*, text::StyledGrapheme};
|
|
|
|
#[allow(unused)]
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
|
|
pub enum PixelSize {
|
|
#[default]
|
|
/// A pixel from the 8x8 font is represented by a full character cell in the terminal.
|
|
Full,
|
|
/// A pixel from the 8x8 font is represented by a half (upper/lower) character cell in the
|
|
/// terminal.
|
|
HalfHeight,
|
|
/// A pixel from the 8x8 font is represented by a half (left/right) character cell in the
|
|
/// terminal.
|
|
HalfWidth,
|
|
/// A pixel from the 8x8 font is represented by a quadrant of a character cell in the terminal.
|
|
Quadrant,
|
|
}
|
|
|
|
/// Displays one or more lines of text using 8x8 pixel characters.
|
|
///
|
|
/// The text is rendered using the [font8x8](https://crates.io/crates/font8x8) crate.
|
|
///
|
|
/// Using the `pixel_size` method, you can also chose, how 'big' a pixel should be.
|
|
/// Currently a pixel of the 8x8 font can be represented by one full or half
|
|
/// (horizontal/vertical/both) character cell of the terminal.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```rust
|
|
/// use ratatui::prelude::*;
|
|
/// use tui_big_text::{BigTextBuilder, PixelSize};
|
|
///
|
|
/// BigText::builder()
|
|
/// .pixel_size(PixelSize::Full)
|
|
/// .style(Style::new().white())
|
|
/// .lines(vec![
|
|
/// "Hello".red().into(),
|
|
/// "World".blue().into(),
|
|
/// "=====".into(),
|
|
/// ])
|
|
/// .build();
|
|
/// ```
|
|
///
|
|
/// Renders:
|
|
///
|
|
/// ```plain
|
|
/// ██ ██ ███ ███
|
|
/// ██ ██ ██ ██
|
|
/// ██ ██ ████ ██ ██ ████
|
|
/// ██████ ██ ██ ██ ██ ██ ██
|
|
/// ██ ██ ██████ ██ ██ ██ ██
|
|
/// ██ ██ ██ ██ ██ ██ ██
|
|
/// ██ ██ ████ ████ ████ ████
|
|
///
|
|
/// ██ ██ ███ ███
|
|
/// ██ ██ ██ ██
|
|
/// ██ ██ ████ ██ ███ ██ ██
|
|
/// ██ █ ██ ██ ██ ███ ██ ██ █████
|
|
/// ███████ ██ ██ ██ ██ ██ ██ ██
|
|
/// ███ ███ ██ ██ ██ ██ ██ ██
|
|
/// ██ ██ ████ ████ ████ ███ ██
|
|
///
|
|
/// ███ ██ ███ ██ ███ ██ ███ ██ ███ ██
|
|
/// ██ ███ ██ ███ ██ ███ ██ ███ ██ ███
|
|
/// ```
|
|
#[derive(Debug, Builder, Clone, PartialEq, Eq, Hash)]
|
|
pub struct BigText<'a> {
|
|
/// The text to display
|
|
#[builder(setter(into))]
|
|
lines: Vec<Line<'a>>,
|
|
|
|
/// The style of the widget
|
|
///
|
|
/// Defaults to `Style::default()`
|
|
#[builder(default)]
|
|
style: Style,
|
|
|
|
/// The size of single glyphs
|
|
///
|
|
/// Defaults to `BigTextSize::default()` (=> `BigTextSize::Full`)
|
|
#[builder(default)]
|
|
pixel_size: PixelSize,
|
|
}
|
|
|
|
impl Widget for BigText<'_> {
|
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
let layout = layout(area, self.pixel_size);
|
|
for (line, line_layout) in self.lines.iter().zip(layout) {
|
|
for (g, cell) in line.styled_graphemes(self.style).zip(line_layout) {
|
|
render_symbol(&g, cell, buf, self.pixel_size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns how many cells are needed to display a full 8x8 glyphe using the given font size
|
|
const fn cells_per_glyph(size: PixelSize) -> (u16, u16) {
|
|
match size {
|
|
PixelSize::Full => (8, 8),
|
|
PixelSize::HalfHeight => (8, 4),
|
|
PixelSize::HalfWidth => (4, 8),
|
|
PixelSize::Quadrant => (4, 4),
|
|
}
|
|
}
|
|
|
|
/// Chunk the area into as many x*y cells as possible returned as a 2D iterator of `Rect`s
|
|
/// representing the rows of cells.
|
|
/// The size of each cell depends on given font size
|
|
fn layout(
|
|
area: Rect,
|
|
pixel_size: PixelSize,
|
|
) -> impl IntoIterator<Item = impl IntoIterator<Item = Rect>> {
|
|
let (width, height) = cells_per_glyph(pixel_size);
|
|
(area.top()..area.bottom())
|
|
.step_by(height as usize)
|
|
.map(move |y| {
|
|
(area.left()..area.right())
|
|
.step_by(width as usize)
|
|
.map(move |x| {
|
|
let width = min(area.right() - x, width);
|
|
let height = min(area.bottom() - y, height);
|
|
Rect::new(x, y, width, height)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Render a single grapheme into a cell by looking up the corresponding 8x8 bitmap in the
|
|
/// `BITMAPS` array and setting the corresponding cells in the buffer.
|
|
fn render_symbol(grapheme: &StyledGrapheme, area: Rect, buf: &mut Buffer, pixel_size: PixelSize) {
|
|
buf.set_style(area, grapheme.style);
|
|
let c = grapheme.symbol.chars().next().unwrap(); // TODO: handle multi-char graphemes
|
|
if let Some(glyph) = font8x8::BASIC_FONTS.get(c) {
|
|
render_glyph(glyph, area, buf, pixel_size);
|
|
}
|
|
}
|
|
|
|
/// Get the correct unicode symbol for two vertical "pixels"
|
|
const fn get_symbol_half_height(top: u8, bottom: u8) -> char {
|
|
match top {
|
|
0 => match bottom {
|
|
0 => ' ',
|
|
_ => '▄',
|
|
},
|
|
_ => match bottom {
|
|
0 => '▀',
|
|
_ => '█',
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Get the correct unicode symbol for two horizontal "pixels"
|
|
const fn get_symbol_half_width(left: u8, right: u8) -> char {
|
|
match left {
|
|
0 => match right {
|
|
0 => ' ',
|
|
_ => '▐',
|
|
},
|
|
_ => match right {
|
|
0 => '▌',
|
|
_ => '█',
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Get the correct unicode symbol for 2x2 "pixels"
|
|
const fn get_symbol_half_size(
|
|
top_left: u8,
|
|
top_right: u8,
|
|
bottom_left: u8,
|
|
bottom_right: u8,
|
|
) -> char {
|
|
const QUADRANT_SYMBOLS: [char; 16] = [
|
|
' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',
|
|
];
|
|
|
|
let top_left = if top_left > 0 { 1 } else { 0 };
|
|
let top_right = if top_right > 0 { 1 << 1 } else { 0 };
|
|
let bottom_left = if bottom_left > 0 { 1 << 2 } else { 0 };
|
|
let bottom_right = if bottom_right > 0 { 1 << 3 } else { 0 };
|
|
|
|
QUADRANT_SYMBOLS[top_left + top_right + bottom_left + bottom_right]
|
|
}
|
|
|
|
/// Render a single 8x8 glyph into a cell by setting the corresponding cells in the buffer.
|
|
fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer, pixel_size: PixelSize) {
|
|
let (width, height) = cells_per_glyph(pixel_size);
|
|
|
|
let glyph_vertical_index = (0..glyph.len()).step_by(8 / height as usize);
|
|
let glyph_horizontal_bit_selector = (0..8).step_by(8 / width as usize);
|
|
|
|
for (row, y) in glyph_vertical_index.zip(area.top()..area.bottom()) {
|
|
for (col, x) in glyph_horizontal_bit_selector
|
|
.clone()
|
|
.zip(area.left()..area.right())
|
|
{
|
|
let cell = buf.get_mut(x, y);
|
|
let symbol_character = match pixel_size {
|
|
PixelSize::Full => match glyph[row] & (1 << col) {
|
|
0 => ' ',
|
|
_ => '█',
|
|
},
|
|
PixelSize::HalfHeight => {
|
|
let top = glyph[row] & (1 << col);
|
|
let bottom = glyph[row + 1] & (1 << col);
|
|
get_symbol_half_height(top, bottom)
|
|
}
|
|
PixelSize::HalfWidth => {
|
|
let left = glyph[row] & (1 << col);
|
|
let right = glyph[row] & (1 << (col + 1));
|
|
get_symbol_half_width(left, right)
|
|
}
|
|
PixelSize::Quadrant => {
|
|
let top_left = glyph[row] & (1 << col);
|
|
let top_right = glyph[row] & (1 << (col + 1));
|
|
let bottom_left = glyph[row + 1] & (1 << col);
|
|
let bottom_right = glyph[row + 1] & (1 << (col + 1));
|
|
get_symbol_half_size(top_left, top_right, bottom_left, bottom_right)
|
|
}
|
|
};
|
|
cell.set_char(symbol_character);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
|
|
#[test]
|
|
fn build() -> Result<()> {
|
|
let lines = vec![Line::from(vec!["Hello".red(), "World".blue()])];
|
|
let style = Style::new().green();
|
|
let pixel_size = PixelSize::default();
|
|
assert_eq!(
|
|
BigTextBuilder::default()
|
|
.lines(lines.clone())
|
|
.style(style)
|
|
.build()?,
|
|
BigText {
|
|
lines,
|
|
style,
|
|
pixel_size
|
|
}
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_single_line() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.lines(vec![Line::from("SingleLine")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 8));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
" ████ ██ ███ ████ ██ ",
|
|
"██ ██ ██ ██ ",
|
|
"███ ███ █████ ███ ██ ██ ████ ██ ███ █████ ████ ",
|
|
" ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ",
|
|
" ███ ██ ██ ██ ██ ██ ██ ██████ ██ █ ██ ██ ██ ██████ ",
|
|
"██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ",
|
|
" ████ ████ ██ ██ ██ ████ ████ ███████ ████ ██ ██ ████ ",
|
|
" █████ ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_truncated() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.lines(vec![Line::from("Truncated")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 70, 6));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"██████ █ ███",
|
|
"█ ██ █ ██ ██",
|
|
" ██ ██ ███ ██ ██ █████ ████ ████ █████ ████ ██",
|
|
" ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █████",
|
|
" ██ ██ ██ ██ ██ ██ ██ ██ █████ ██ ██████ ██ ██",
|
|
" ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ ██",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_multiple_lines() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.lines(vec![Line::from("Multi"), Line::from("Lines")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 16));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"██ ██ ███ █ ██ ",
|
|
"███ ███ ██ ██ ",
|
|
"███████ ██ ██ ██ █████ ███ ",
|
|
"███████ ██ ██ ██ ██ ██ ",
|
|
"██ █ ██ ██ ██ ██ ██ ██ ",
|
|
"██ ██ ██ ██ ██ ██ █ ██ ",
|
|
"██ ██ ███ ██ ████ ██ ████ ",
|
|
" ",
|
|
"████ ██ ",
|
|
" ██ ",
|
|
" ██ ███ █████ ████ █████ ",
|
|
" ██ ██ ██ ██ ██ ██ ██ ",
|
|
" ██ █ ██ ██ ██ ██████ ████ ",
|
|
" ██ ██ ██ ██ ██ ██ ██ ",
|
|
"███████ ████ ██ ██ ████ █████ ",
|
|
" ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_widget_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.lines(vec![Line::from("Styled")])
|
|
.style(Style::new().bold())
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 48, 8));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
" ████ █ ███ ███ ".bold(),
|
|
"██ ██ ██ ██ ██ ".bold(),
|
|
"███ █████ ██ ██ ██ ████ ██ ".bold(),
|
|
" ███ ██ ██ ██ ██ ██ ██ █████ ".bold(),
|
|
" ███ ██ ██ ██ ██ ██████ ██ ██ ".bold(),
|
|
"██ ██ ██ █ █████ ██ ██ ██ ██ ".bold(),
|
|
" ████ ██ ██ ████ ████ ███ ██ ".bold(),
|
|
" █████ ".bold(),
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_line_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.lines(vec![
|
|
Line::from("Red".red()),
|
|
Line::from("Green".green()),
|
|
Line::from("Blue".blue()),
|
|
])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 24));
|
|
big_text.render(buf.area, &mut buf);
|
|
let mut expected = Buffer::with_lines([
|
|
"██████ ███ ",
|
|
" ██ ██ ██ ",
|
|
" ██ ██ ████ ██ ",
|
|
" █████ ██ ██ █████ ",
|
|
" ██ ██ ██████ ██ ██ ",
|
|
" ██ ██ ██ ██ ██ ",
|
|
"███ ██ ████ ███ ██ ",
|
|
" ",
|
|
" ████ ",
|
|
" ██ ██ ",
|
|
"██ ██ ███ ████ ████ █████ ",
|
|
"██ ███ ██ ██ ██ ██ ██ ██ ██ ",
|
|
"██ ███ ██ ██ ██████ ██████ ██ ██ ",
|
|
" ██ ██ ██ ██ ██ ██ ██ ",
|
|
" █████ ████ ████ ████ ██ ██ ",
|
|
" ",
|
|
"██████ ███ ",
|
|
" ██ ██ ██ ",
|
|
" ██ ██ ██ ██ ██ ████ ",
|
|
" █████ ██ ██ ██ ██ ██ ",
|
|
" ██ ██ ██ ██ ██ ██████ ",
|
|
" ██ ██ ██ ██ ██ ██ ",
|
|
"██████ ████ ███ ██ ████ ",
|
|
" ",
|
|
]);
|
|
expected.set_style(Rect::new(0, 0, 24, 8), Style::new().red());
|
|
expected.set_style(Rect::new(0, 8, 40, 8), Style::new().green());
|
|
expected.set_style(Rect::new(0, 16, 32, 8), Style::new().blue());
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_height_single_line() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfHeight)
|
|
.lines(vec![Line::from("SingleLine")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 80, 4));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▄█▀▀█▄ ▀▀ ▀██ ▀██▀ ▀▀ ",
|
|
"▀██▄ ▀██ ██▀▀█▄ ▄█▀▀▄█▀ ██ ▄█▀▀█▄ ██ ▀██ ██▀▀█▄ ▄█▀▀█▄ ",
|
|
"▄▄ ▀██ ██ ██ ██ ▀█▄▄██ ██ ██▀▀▀▀ ██ ▄█ ██ ██ ██ ██▀▀▀▀ ",
|
|
" ▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_height_truncated() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfHeight)
|
|
.lines(vec![Line::from("Truncated")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 70, 3));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"█▀██▀█ ▄█ ▀██",
|
|
" ██ ▀█▄█▀█▄ ██ ██ ██▀▀█▄ ▄█▀▀█▄ ▀▀▀█▄ ▀██▀▀ ▄█▀▀█▄ ▄▄▄██",
|
|
" ██ ██ ▀▀ ██ ██ ██ ██ ██ ▄▄ ▄█▀▀██ ██ ▄ ██▀▀▀▀ ██ ██",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_height_multiple_lines() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfHeight)
|
|
.lines(vec![Line::from("Multi"), Line::from("Lines")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 8));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"██▄ ▄██ ▀██ ▄█ ▀▀ ",
|
|
"███████ ██ ██ ██ ▀██▀▀ ▀██ ",
|
|
"██ ▀ ██ ██ ██ ██ ██ ▄ ██ ",
|
|
"▀▀ ▀▀ ▀▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀▀▀ ",
|
|
"▀██▀ ▀▀ ",
|
|
" ██ ▀██ ██▀▀█▄ ▄█▀▀█▄ ▄█▀▀▀▀ ",
|
|
" ██ ▄█ ██ ██ ██ ██▀▀▀▀ ▀▀▀█▄ ",
|
|
"▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀▀▀▀ ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_height_widget_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfHeight)
|
|
.lines(vec![Line::from("Styled")])
|
|
.style(Style::new().bold())
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 48, 4));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▄█▀▀█▄ ▄█ ▀██ ▀██ ".bold(),
|
|
"▀██▄ ▀██▀▀ ██ ██ ██ ▄█▀▀█▄ ▄▄▄██ ".bold(),
|
|
"▄▄ ▀██ ██ ▄ ▀█▄▄██ ██ ██▀▀▀▀ ██ ██ ".bold(),
|
|
" ▀▀▀▀ ▀▀ ▄▄▄▄█▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ".bold(),
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_height_line_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfHeight)
|
|
.lines(vec![
|
|
Line::from("Red".red()),
|
|
Line::from("Green".green()),
|
|
Line::from("Blue".blue()),
|
|
])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 12));
|
|
big_text.render(buf.area, &mut buf);
|
|
let mut expected = Buffer::with_lines([
|
|
"▀██▀▀█▄ ▀██ ",
|
|
" ██▄▄█▀ ▄█▀▀█▄ ▄▄▄██ ",
|
|
" ██ ▀█▄ ██▀▀▀▀ ██ ██ ",
|
|
"▀▀▀ ▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ",
|
|
" ▄█▀▀█▄ ",
|
|
"██ ▀█▄█▀█▄ ▄█▀▀█▄ ▄█▀▀█▄ ██▀▀█▄ ",
|
|
"▀█▄ ▀██ ██ ▀▀ ██▀▀▀▀ ██▀▀▀▀ ██ ██ ",
|
|
" ▀▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀ ▀▀ ",
|
|
"▀██▀▀█▄ ▀██ ",
|
|
" ██▄▄█▀ ██ ██ ██ ▄█▀▀█▄ ",
|
|
" ██ ██ ██ ██ ██ ██▀▀▀▀ ",
|
|
"▀▀▀▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀ ▀▀▀▀ ",
|
|
]);
|
|
expected.set_style(Rect::new(0, 0, 24, 4), Style::new().red());
|
|
expected.set_style(Rect::new(0, 4, 40, 4), Style::new().green());
|
|
expected.set_style(Rect::new(0, 8, 32, 4), Style::new().blue());
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_width_single_line() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfWidth)
|
|
.lines(vec![Line::from("SingleLine")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 8));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▐█▌ █ ▐█ ██ █ ",
|
|
"█ █ █ ▐▌ ",
|
|
"█▌ ▐█ ██▌ ▐█▐▌ █ ▐█▌ ▐▌ ▐█ ██▌ ▐█▌ ",
|
|
"▐█ █ █ █ █ █ █ █ █ ▐▌ █ █ █ █ █ ",
|
|
" ▐█ █ █ █ █ █ █ ███ ▐▌ ▌ █ █ █ ███ ",
|
|
"█ █ █ █ █ ▐██ █ █ ▐▌▐▌ █ █ █ █ ",
|
|
"▐█▌ ▐█▌ █ █ █ ▐█▌ ▐█▌ ███▌▐█▌ █ █ ▐█▌ ",
|
|
" ██▌ ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_width_truncated() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfWidth)
|
|
.lines(vec![Line::from("Truncated")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 35, 6));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"███ ▐ ▐█",
|
|
"▌█▐ █ █",
|
|
" █ █▐█ █ █ ██▌ ▐█▌ ▐█▌ ▐██ ▐█▌ █",
|
|
" █ ▐█▐▌█ █ █ █ █ █ █ █ █ █ ▐██",
|
|
" █ ▐▌▐▌█ █ █ █ █ ▐██ █ ███ █ █",
|
|
" █ ▐▌ █ █ █ █ █ █ █ █ █▐ █ █ █",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_width_multiple_lines() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfWidth)
|
|
.lines(vec![Line::from("Multi"), Line::from("Lines")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 16));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"█ ▐▌ ▐█ ▐ █ ",
|
|
"█▌█▌ █ █ ",
|
|
"███▌█ █ █ ▐██ ▐█ ",
|
|
"███▌█ █ █ █ █ ",
|
|
"█▐▐▌█ █ █ █ █ ",
|
|
"█ ▐▌█ █ █ █▐ █ ",
|
|
"█ ▐▌▐█▐▌▐█▌ ▐▌ ▐█▌ ",
|
|
" ",
|
|
"██ █ ",
|
|
"▐▌ ",
|
|
"▐▌ ▐█ ██▌ ▐█▌ ▐██ ",
|
|
"▐▌ █ █ █ █ █ █ ",
|
|
"▐▌ ▌ █ █ █ ███ ▐█▌ ",
|
|
"▐▌▐▌ █ █ █ █ █ ",
|
|
"███▌▐█▌ █ █ ▐█▌ ██▌ ",
|
|
" ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_width_widget_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfWidth)
|
|
.lines(vec![Line::from("Styled")])
|
|
.style(Style::new().bold())
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 24, 8));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▐█▌ ▐ ▐█ ▐█ ".bold(),
|
|
"█ █ █ █ █ ".bold(),
|
|
"█▌ ▐██ █ █ █ ▐█▌ █ ".bold(),
|
|
"▐█ █ █ █ █ █ █ ▐██ ".bold(),
|
|
" ▐█ █ █ █ █ ███ █ █ ".bold(),
|
|
"█ █ █▐ ▐██ █ █ █ █ ".bold(),
|
|
"▐█▌ ▐▌ █ ▐█▌ ▐█▌ ▐█▐▌".bold(),
|
|
" ██▌ ".bold(),
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_width_line_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::HalfWidth)
|
|
.lines(vec![
|
|
Line::from("Red".red()),
|
|
Line::from("Green".green()),
|
|
Line::from("Blue".blue()),
|
|
])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 24));
|
|
big_text.render(buf.area, &mut buf);
|
|
let mut expected = Buffer::with_lines([
|
|
"███ ▐█ ",
|
|
"▐▌▐▌ █ ",
|
|
"▐▌▐▌▐█▌ █ ",
|
|
"▐██ █ █ ▐██ ",
|
|
"▐▌█ ███ █ █ ",
|
|
"▐▌▐▌█ █ █ ",
|
|
"█▌▐▌▐█▌ ▐█▐▌ ",
|
|
" ",
|
|
" ██ ",
|
|
"▐▌▐▌ ",
|
|
"█ █▐█ ▐█▌ ▐█▌ ██▌ ",
|
|
"█ ▐█▐▌█ █ █ █ █ █ ",
|
|
"█ █▌▐▌▐▌███ ███ █ █ ",
|
|
"▐▌▐▌▐▌ █ █ █ █ ",
|
|
" ██▌██ ▐█▌ ▐█▌ █ █ ",
|
|
" ",
|
|
"███ ▐█ ",
|
|
"▐▌▐▌ █ ",
|
|
"▐▌▐▌ █ █ █ ▐█▌ ",
|
|
"▐██ █ █ █ █ █ ",
|
|
"▐▌▐▌ █ █ █ ███ ",
|
|
"▐▌▐▌ █ █ █ █ ",
|
|
"███ ▐█▌ ▐█▐▌▐█▌ ",
|
|
" ",
|
|
]);
|
|
expected.set_style(Rect::new(0, 0, 12, 8), Style::new().red());
|
|
expected.set_style(Rect::new(0, 8, 20, 8), Style::new().green());
|
|
expected.set_style(Rect::new(0, 16, 16, 8), Style::new().blue());
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn check_half_size_symbols() -> Result<()> {
|
|
assert_eq!(get_symbol_half_size(0, 0, 0, 0), ' ');
|
|
assert_eq!(get_symbol_half_size(1, 0, 0, 0), '▘');
|
|
assert_eq!(get_symbol_half_size(0, 1, 0, 0), '▝');
|
|
assert_eq!(get_symbol_half_size(1, 1, 0, 0), '▀');
|
|
assert_eq!(get_symbol_half_size(0, 0, 1, 0), '▖');
|
|
assert_eq!(get_symbol_half_size(1, 0, 1, 0), '▌');
|
|
assert_eq!(get_symbol_half_size(0, 1, 1, 0), '▞');
|
|
assert_eq!(get_symbol_half_size(1, 1, 1, 0), '▛');
|
|
assert_eq!(get_symbol_half_size(0, 0, 0, 1), '▗');
|
|
assert_eq!(get_symbol_half_size(1, 0, 0, 1), '▚');
|
|
assert_eq!(get_symbol_half_size(0, 1, 0, 1), '▐');
|
|
assert_eq!(get_symbol_half_size(1, 1, 0, 1), '▜');
|
|
assert_eq!(get_symbol_half_size(0, 0, 1, 1), '▄');
|
|
assert_eq!(get_symbol_half_size(1, 0, 1, 1), '▙');
|
|
assert_eq!(get_symbol_half_size(0, 1, 1, 1), '▟');
|
|
assert_eq!(get_symbol_half_size(1, 1, 1, 1), '█');
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_size_single_line() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::Quadrant)
|
|
.lines(vec![Line::from("SingleLine")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 4));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▟▀▙ ▀ ▝█ ▜▛ ▀ ",
|
|
"▜▙ ▝█ █▀▙ ▟▀▟▘ █ ▟▀▙ ▐▌ ▝█ █▀▙ ▟▀▙ ",
|
|
"▄▝█ █ █ █ ▜▄█ █ █▀▀ ▐▌▗▌ █ █ █ █▀▀ ",
|
|
"▝▀▘ ▝▀▘ ▀ ▀ ▄▄▛ ▝▀▘ ▝▀▘ ▀▀▀▘▝▀▘ ▀ ▀ ▝▀▘ ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_size_truncated() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::Quadrant)
|
|
.lines(vec![Line::from("Truncated")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 35, 3));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▛█▜ ▟ ▝█",
|
|
" █ ▜▟▜▖█ █ █▀▙ ▟▀▙ ▝▀▙ ▝█▀ ▟▀▙ ▗▄█",
|
|
" █ ▐▌▝▘█ █ █ █ █ ▄ ▟▀█ █▗ █▀▀ █ █",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_size_multiple_lines() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::Quadrant)
|
|
.lines(vec![Line::from("Multi"), Line::from("Lines")])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 8));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"█▖▟▌ ▝█ ▟ ▀ ",
|
|
"███▌█ █ █ ▝█▀ ▝█ ",
|
|
"█▝▐▌█ █ █ █▗ █ ",
|
|
"▀ ▝▘▝▀▝▘▝▀▘ ▝▘ ▝▀▘ ",
|
|
"▜▛ ▀ ",
|
|
"▐▌ ▝█ █▀▙ ▟▀▙ ▟▀▀ ",
|
|
"▐▌▗▌ █ █ █ █▀▀ ▝▀▙ ",
|
|
"▀▀▀▘▝▀▘ ▀ ▀ ▝▀▘ ▀▀▘ ",
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_size_widget_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::Quadrant)
|
|
.lines(vec![Line::from("Styled")])
|
|
.style(Style::new().bold())
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 24, 4));
|
|
big_text.render(buf.area, &mut buf);
|
|
let expected = Buffer::with_lines([
|
|
"▟▀▙ ▟ ▝█ ▝█ ".bold(),
|
|
"▜▙ ▝█▀ █ █ █ ▟▀▙ ▗▄█ ".bold(),
|
|
"▄▝█ █▗ ▜▄█ █ █▀▀ █ █ ".bold(),
|
|
"▝▀▘ ▝▘ ▄▄▛ ▝▀▘ ▝▀▘ ▝▀▝▘".bold(),
|
|
]);
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn render_half_size_line_style() -> Result<()> {
|
|
let big_text = BigTextBuilder::default()
|
|
.pixel_size(PixelSize::Quadrant)
|
|
.lines(vec![
|
|
Line::from("Red".red()),
|
|
Line::from("Green".green()),
|
|
Line::from("Blue".blue()),
|
|
])
|
|
.build()?;
|
|
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 12));
|
|
big_text.render(buf.area, &mut buf);
|
|
let mut expected = Buffer::with_lines([
|
|
"▜▛▜▖ ▝█ ",
|
|
"▐▙▟▘▟▀▙ ▗▄█ ",
|
|
"▐▌▜▖█▀▀ █ █ ",
|
|
"▀▘▝▘▝▀▘ ▝▀▝▘ ",
|
|
"▗▛▜▖ ",
|
|
"█ ▜▟▜▖▟▀▙ ▟▀▙ █▀▙ ",
|
|
"▜▖▜▌▐▌▝▘█▀▀ █▀▀ █ █ ",
|
|
" ▀▀▘▀▀ ▝▀▘ ▝▀▘ ▀ ▀ ",
|
|
"▜▛▜▖▝█ ",
|
|
"▐▙▟▘ █ █ █ ▟▀▙ ",
|
|
"▐▌▐▌ █ █ █ █▀▀ ",
|
|
"▀▀▀ ▝▀▘ ▝▀▝▘▝▀▘ ",
|
|
]);
|
|
expected.set_style(Rect::new(0, 0, 12, 4), Style::new().red());
|
|
expected.set_style(Rect::new(0, 4, 20, 4), Style::new().green());
|
|
expected.set_style(Rect::new(0, 8, 16, 4), Style::new().blue());
|
|
assert_eq!(buf, expected);
|
|
Ok(())
|
|
}
|
|
}
|