small software-rendered rust tty
foreground colors
bendn 9 months ago
parent db08811 · commit 2b06ae5
-rw-r--r--Cargo.toml3
-rw-r--r--cjk.ttcbin0 -> 18354360 bytes
-rw-r--r--src/colors.rs22
-rw-r--r--src/main.rs38
-rw-r--r--src/render.rs101
-rw-r--r--src/term.rs43
6 files changed, 156 insertions, 51 deletions
diff --git a/Cargo.toml b/Cargo.toml
index b4397ca..c65b6fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,8 +7,9 @@ edition = "2024"
anstream = "0.6.18"
anyhow = "1.0.98"
atools = "0.1.6"
+color-hex = "0.2.0"
ctlfun = { git = "https://github.com/bend-n/ctlfun" }
-fimg = "0.4.44"
+fimg = { git = "https://github.com/bend-n/fimg" }
implicit-fn = "0.1.0"
libc = "0.2.172"
minifb = "0.28.0"
diff --git a/cjk.ttc b/cjk.ttc
new file mode 100644
index 0000000..323cebd
--- /dev/null
+++ b/cjk.ttc
Binary files differ
diff --git a/src/colors.rs b/src/colors.rs
new file mode 100644
index 0000000..683ffea
--- /dev/null
+++ b/src/colors.rs
@@ -0,0 +1,22 @@
+pub const BACKGROUND: [u8; 3] = [33u8, 39u8, 51u8];
+pub const FOREGROUND: [u8; 3] = [217u8, 215u8, 206u8];
+pub const CURSOR: [u8; 3] = [255u8, 204u8, 102u8];
+pub const FOUR: [[u8; 3]; 16] = [
+ [25, 30, 42],
+ [237, 130, 116],
+ [166, 204, 112],
+ [250, 208, 123],
+ [109, 203, 250],
+ [207, 186, 250],
+ [144, 225, 198],
+ [199, 199, 199],
+ //
+ [104, 104, 104],
+ [242, 135, 121],
+ [186, 230, 126],
+ [255, 213, 128],
+ [115, 208, 255],
+ [212, 191, 255],
+ [149, 230, 203],
+ [255, 255, 255],
+];
diff --git a/src/main.rs b/src/main.rs
index 7d383e7..33f8a0e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-#![feature(deadline_api, deref_patterns)]
+#![feature(deadline_api, deref_patterns, generic_const_exprs)]
use std::fs::File;
use std::io::Write;
use std::iter::successors;
@@ -8,6 +8,8 @@ use std::sync::mpsc;
use std::thread::sleep;
use std::time::Duration;
+pub mod colors;
+
use anyhow::Result;
use ctlfun::TerminalInputParser;
use fimg::Image;
@@ -21,7 +23,8 @@ fn spawn(shell: &str) -> Result<OwnedFd> {
let x = unsafe { forkpty(None, None)? };
match x {
ForkptyResult::Child => {
- let sh = Command::new(shell).spawn()?.wait();
+ let sh =
+ Command::new(shell).env("TERM", "vt100").spawn()?.wait();
// std::thread::sleep(Duration::from_millis(5000));
// exit(0);
@@ -105,7 +108,13 @@ fn main() -> Result<()> {
shifting = true;
continue;
}
- Enter => b"\n",
+ Enter => &b"\n"[..],
+ Up => b"\x1b[A",
+ Down => b"\x1b[B",
+ Right => b"\x1b[C",
+ Left => b"\x1d[D",
+ Apostrophe if shifting => b"\"",
+ Apostrophe => b"'",
Space => b" ",
Period => b".",
Slash => b"/",
@@ -155,21 +164,20 @@ fn main() -> Result<()> {
let rows = (w.get_size().1 as f32 / fh).floor() as u16;
dbg!(rows, cols);
let mut t = Terminal {
+ style: Default::default(),
cursor: (1, 1),
size: (cols, rows),
scrollback: Scrollback::default(),
- cells: vec![
- Cell {
- bg: [0; 3],
- color: [0; 3],
- style: 0,
- letter: None,
- };
- cols as usize * rows as usize
- ],
+ cells: vec![Cell::default(); cols as usize * rows as usize],
p: Default::default(),
mode: Mode::Normal,
};
+ let cj =
+ swash::FontRef::from_index(&include_bytes!("../cjk.ttc")[..], 0)
+ .unwrap();
+ let m = cj.metrics(&[]);
+ dbg!(m);
+
let mut f = File::create("x").unwrap();
loop {
while let Ok(x) = trx.recv_timeout(Duration::from_millis(16)) {
@@ -187,10 +195,12 @@ fn main() -> Result<()> {
}
#[test]
-fn tparse() {
+fn tpaxrse() {
println!("-------------------");
let mut x = TerminalInputParser::new();
- for c in "\x1b[32;1m greninator \x1b[0m".as_bytes() {
+ for c in
+ "\x1b[32;1mgren\x1b[33myellow\x1b[42mbggreen\x1b[0m".as_bytes()
+ {
use ctlfun::TerminalInput::*;
match x.parse_byte(*c) {
Char(x) => {
diff --git a/src/render.rs b/src/render.rs
index b52ce56..64a2f2a 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1,18 +1,22 @@
use std::sync::LazyLock;
+use atools::Join;
+use fimg::BlendingOverlayAt;
use swash::FontRef;
use swash::scale::{Render, ScaleContext, Source};
use swash::zeno::Format;
+use crate::colors;
+
#[implicit_fn::implicit_fn]
pub fn render(
x: &super::Terminal,
(w, h): (usize, usize),
ppem: f32,
-) -> Image<Vec<u8>, 3> {
+) -> Image<Box<[u8]>, 3> {
let m = FONT.metrics(&[]);
let sz = ppem * (m.max_width / m.units_per_em as f32);
- let mut i = Image::alloc(w as _, h as _);
+ let mut i = Image::build(w as _, h as _).fill(colors::BACKGROUND);
for (col, k) in x.cells.chunks_exact(x.size.0 as _).zip(0..).skip(1) {
for (cell, j) in
col.iter().skip(2).zip(0..).filter(_.0.letter.is_some())
@@ -33,19 +37,34 @@ pub fn render(
if x.placement.width == 0 {
continue;
}
- i.as_mut().overlay_at(
- &Image::<Box<[u8]>, 4>::from(
- Image::<_, 1>::build(
- x.placement.width,
- x.placement.height,
- )
- .buf(&*x.data),
- )
- .as_ref(),
+ let item = Image::<_, 4>::build(
+ x.placement.width,
+ x.placement.height,
+ )
+ .buf(
+ x.data
+ .iter()
+ .flat_map(|&x| cell.style.color.join(x))
+ .collect::<Vec<u8>>(),
+ );
+ // let mut o =
+ // Image::build(x.placement.width, x.placement.height)
+ // .fill(cell.style.bg);
+ // o.overlay_blended(&item);
+
+ i.as_mut().overlay_blended_at(
+ &item.as_ref(),
+ // &Image::<Box<[u8]>, 4>::from(
+ // Image::<_, 1>::build(
+ // x.placement.width,
+ // x.placement.height,
+ // )
+ // .buf(&*x.data),
+ // )
+ // .as_ref(),
4 + ((j as f32 * sz) + x.placement.left as f32) as u32,
((k as f32 * (ppem * 1.25)) as u32)
.saturating_sub(x.placement.top as u32),
- // x.placement.height - x.placement.top as u32,
);
}
}
@@ -63,38 +82,29 @@ pub static FONT: LazyLock<FontRef<'static>> = LazyLock::new(|| {
.unwrap()
});
-use fimg::{Image, OverlayAt};
+use fimg::{BlendingOverlay, Image, OverlayAt};
// let x = b"echo -e \"\x1b(0lqqqk\nx \x1b(Bx\nmqqqj";
// let x = String::from_utf8_lossy(&x);
// println!("{}", x);
#[test]
fn t() {
let f =
- FontRef::from_index(&include_bytes!("../CascadiaCode.ttf")[..], 0)
- .unwrap();
+ FontRef::from_index(&include_bytes!("../cjk.ttc")[..], 0).unwrap();
dbg!(f.attributes());
- let m = f.metrics(&[]);
- dbg!(m);
+ let m = f.metrics(&[f.charmap().map('行') as _]);
+
let ppem = 30.0;
- let sz = ppem * (m.max_width / m.units_per_em as f32);
- dbg!(
- &mut ScaleContext::new()
- .builder(f)
- .size(15.0)
- .build()
- .scale_outline(f.charmap().map('a'))
- .unwrap()
- .len()
- );
- let mut grid = Image::<_, 4>::alloc(2000, 150);
+ let d = dims(&FONT, ppem);
+ let sz = d.0;
+ let mut grid = Image::<_, 4>::alloc(2000, 500);
unsafe { grid.chunked_mut().for_each(|x| *x = [0, 0, 0, 255]) };
- for (letter, i) in "the quick brown fox jumped over the lazy dog"
+ for (letter, i) in "素早い茶色のキツネは怠け者の犬を飛び越えた"
.chars()
.zip(0..)
{
+ // grid.as_ref().show();
let id = f.charmap().map(letter);
-
let x = Render::new(&[Source::Outline])
.format(Format::Alpha)
.render(
@@ -115,9 +125,36 @@ fn t() {
.buf(&*x.data),
)
.as_ref(),
- ((i * sz as i32) + x.placement.left) as _,
+ ((i as f32 * sz * 2.0) as i32 + x.placement.left) as _,
ppem as u32 - x.placement.top as u32,
- // x.placement.height - x.placement.top as u32,
+ );
+ }
+ }
+ for (letter, i) in "#".repeat(21 * 2).chars().zip(0..) {
+ // grid.as_ref().show();
+ let id = FONT.charmap().map(letter);
+ let x = Render::new(&[Source::Outline])
+ .format(Format::Alpha)
+ .render(
+ &mut ScaleContext::new().builder(*FONT).size(ppem).build(),
+ id,
+ )
+ .unwrap();
+ unsafe {
+ if x.placement.width == 0 {
+ continue;
+ }
+ grid.as_mut().overlay_at(
+ &Image::<Box<[u8]>, 4>::from(
+ Image::<_, 1>::build(
+ x.placement.width,
+ x.placement.height,
+ )
+ .buf(&*x.data),
+ )
+ .as_ref(),
+ ((i as f32 * sz) as i32 + x.placement.left) as _,
+ 30 + ppem as u32 - x.placement.top as u32,
);
}
}
diff --git a/src/term.rs b/src/term.rs
index 32d0529..482b0c5 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -3,6 +3,7 @@ use ctlfun::TerminalInput::*;
use ctlfun::{ControlFunction, TerminalInputParser};
pub struct Terminal {
+ pub style: Style,
pub cursor: (u16, u16),
pub size: (u16, u16),
pub scrollback: Scrollback,
@@ -23,14 +24,30 @@ pub struct Scrollback {
impl Scrollback {}
#[derive(Clone, Copy)]
-pub struct Cell {
+pub struct Style {
pub bg: [u8; 3],
pub color: [u8; 3],
pub style: u8,
+}
+use crate::colors;
+impl std::default::Default for Style {
+ fn default() -> Self {
+ Self {
+ bg: colors::BACKGROUND,
+ style: 0,
+ color: colors::FOREGROUND,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Default)]
+pub struct Cell {
+ pub style: Style,
pub letter: Option<char>,
}
impl Terminal {
+ #[implicit_fn::implicit_fn]
pub fn rx(&mut self, x: u8) {
match self.p.parse_byte(x) {
Continue => {}
@@ -42,9 +59,11 @@ impl Terminal {
self.cursor.0 = 1;
self.cursor.1 += 1;
}
- self.cells[(self.cursor.1 * self.size.0 + self.cursor.0)
- as usize]
- .letter = Some(x);
+ let c = &mut self.cells[(self.cursor.1 * self.size.0
+ + self.cursor.0)
+ as usize];
+ c.letter = Some(x);
+ c.style = self.style;
}
Control(ControlFunction { start: 8, .. }) => {
self.cursor.0 -= 1;
@@ -52,6 +71,22 @@ impl Terminal {
Control(ControlFunction {
start: b'[',
params,
+ end: b'm',
+ ..
+ }) => match params.get(0).map(*_).unwrap() {
+ Value(0) => self.style = Style::default(),
+ Value(x @ (30..=37)) => {
+ self.style.color = colors::FOUR[x as usize - 30]
+ }
+ Value(39) => self.style.color = colors::FOREGROUND,
+ Value(x @ (90..=97)) => {
+ self.style.color = colors::FOUR[x as usize - 72]
+ }
+ _ => {}
+ },
+ Control(ControlFunction {
+ start: b'[',
+ params,
end: b'K',
..
}) if params == &[Default] => {