small software-rendered rust tty
foreground colors
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | cjk.ttc | bin | 0 -> 18354360 bytes | |||
| -rw-r--r-- | src/colors.rs | 22 | ||||
| -rw-r--r-- | src/main.rs | 38 | ||||
| -rw-r--r-- | src/render.rs | 101 | ||||
| -rw-r--r-- | src/term.rs | 43 |
6 files changed, 156 insertions, 51 deletions
@@ -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" Binary files differdiff --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] => { |