From 03843f9d44c39ab838ee62385bcc2259ee834efc Mon Sep 17 00:00:00 2001 From: yiyang Date: Fri, 12 Apr 2024 17:30:57 +0800 Subject: [PATCH] Added: cjk character support --- .github/workflows/build.yml | 1 + examples/fountain.rs | 8 ++- examples/heart.rs | 8 ++- examples/vortex.rs | 8 ++- src/bin/firework/args.rs | 6 ++ src/bin/firework/gen.rs | 25 ++++++- src/bin/firework/main.rs | 26 +++++-- src/config.rs | 10 +++ src/demo.rs | 10 ++- src/lib.rs | 1 + src/term.rs | 133 ++++++++++++++++++++++++++++++------ 11 files changed, 194 insertions(+), 42 deletions(-) create mode 100644 src/config.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aed2147..f4f6f29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v0.3.0 schedule: - cron: '00 01 * * *' diff --git a/examples/fountain.rs b/examples/fountain.rs index ab3e0b3..9843c2b 100644 --- a/examples/fountain.rs +++ b/examples/fountain.rs @@ -11,6 +11,7 @@ use crossterm::{ execute, terminal, }; use firework_rs::{ + config::Config, fireworks::{ExplosionForm, Firework, FireworkConfig, FireworkManager}, particle::ParticleConfig, term::Terminal, @@ -23,6 +24,7 @@ fn main() -> Result<()> { let mut stdout = stdout(); let (_width, _height) = terminal::size()?; let mut is_running = true; + let cfg = Config::default(); terminal::enable_raw_mode()?; execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?; @@ -44,7 +46,7 @@ fn main() -> Result<()> { } event::Event::Resize(_, _) => { fm.reset(); - term.reinit(); + term.reinit(&cfg); } _ => {} }; @@ -54,8 +56,8 @@ fn main() -> Result<()> { fm.update(time, delta_time); time = SystemTime::now(); - term.render(&fm); - term.print(&mut stdout); + term.render(&fm, &cfg); + term.print(&mut stdout, &cfg); if delta_time < Duration::from_secs_f32(0.05) { let rem = Duration::from_secs_f32(0.05) - delta_time; diff --git a/examples/heart.rs b/examples/heart.rs index 8fc8717..71783f9 100644 --- a/examples/heart.rs +++ b/examples/heart.rs @@ -11,6 +11,7 @@ use crossterm::{ execute, terminal, }; use firework_rs::{ + config::Config, fireworks::{ExplosionForm, Firework, FireworkConfig, FireworkManager}, particle::ParticleConfig, term::Terminal, @@ -23,6 +24,7 @@ fn main() -> Result<()> { let mut stdout = stdout(); let (_width, _height) = terminal::size()?; let mut is_running = true; + let cfg = Config::default(); terminal::enable_raw_mode()?; execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?; @@ -44,7 +46,7 @@ fn main() -> Result<()> { } event::Event::Resize(_, _) => { fm.reset(); - term.reinit(); + term.reinit(&cfg); } _ => {} }; @@ -54,8 +56,8 @@ fn main() -> Result<()> { fm.update(time, delta_time); time = SystemTime::now(); - term.render(&fm); - term.print(&mut stdout); + term.render(&fm, &cfg); + term.print(&mut stdout, &cfg); if delta_time < Duration::from_secs_f32(0.05) { let rem = Duration::from_secs_f32(0.05) - delta_time; diff --git a/examples/vortex.rs b/examples/vortex.rs index 97efd81..08f8042 100644 --- a/examples/vortex.rs +++ b/examples/vortex.rs @@ -10,6 +10,7 @@ use crossterm::{ execute, terminal, }; use firework_rs::{ + config::Config, fireworks::{ExplosionForm, Firework, FireworkConfig, FireworkManager}, particle::ParticleConfig, term::Terminal, @@ -22,6 +23,7 @@ fn main() -> Result<()> { let mut stdout = stdout(); let (_width, _height) = terminal::size()?; let mut is_running = true; + let cfg = Config::default(); terminal::enable_raw_mode()?; execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?; @@ -43,7 +45,7 @@ fn main() -> Result<()> { } event::Event::Resize(_, _) => { fm.reset(); - term.reinit(); + term.reinit(&cfg); } _ => {} }; @@ -53,8 +55,8 @@ fn main() -> Result<()> { fm.update(time, delta_time); time = SystemTime::now(); - term.render(&fm); - term.print(&mut stdout); + term.render(&fm, &cfg); + term.print(&mut stdout, &cfg); if delta_time < Duration::from_secs_f32(0.05) { let rem = Duration::from_secs_f32(0.05) - delta_time; diff --git a/src/bin/firework/args.rs b/src/bin/firework/args.rs index fcb5f0d..5b8888e 100644 --- a/src/bin/firework/args.rs +++ b/src/bin/firework/args.rs @@ -25,4 +25,10 @@ pub struct Cli { /// If this is not specified, the default fps is 12 #[arg(long, value_name = "FRAME-RATE")] pub fps: Option, + + /// Set whether to enable cjk character + /// + /// If enabled, each character will take up two Latin character space + #[arg(long)] + pub cjk: bool, } diff --git a/src/bin/firework/gen.rs b/src/bin/firework/gen.rs index 90177fa..cef2a0e 100644 --- a/src/bin/firework/gen.rs +++ b/src/bin/firework/gen.rs @@ -1,10 +1,16 @@ use std::time::Duration; -use firework_rs::{demo::demo_firework_0, fireworks::FireworkManager}; +use firework_rs::{config::Config, demo::demo_firework_0, fireworks::FireworkManager}; use glam::Vec2; use rand::{seq::IteratorRandom, thread_rng, Rng}; -pub fn dyn_gen(fm: &mut FireworkManager, width: u16, height: u16, enable_gradient: bool) { +pub fn dyn_gen( + fm: &mut FireworkManager, + width: u16, + height: u16, + enable_gradient: bool, + cfg: &Config, +) { let colors = [ vec![ (255, 102, 75), @@ -47,8 +53,20 @@ pub fn dyn_gen(fm: &mut FireworkManager, width: u16, height: u16, enable_gradien (255, 155, 84), ], vec![(0, 29, 61), (0, 53, 102), (255, 195, 0), (255, 214, 10)], + vec![ + (61, 52, 139), + (118, 120, 237), + (247, 184, 1), + (241, 135, 1), + (243, 91, 4), + ], ]; - if fm.fireworks.len() < (width as usize * height as usize) / 1300 + 3 { + let limit = if cfg.enable_cjk { + (width as usize * height as usize) / 1800 + 3 + } else { + (width as usize * height as usize) / 1300 + 3 + }; + if fm.fireworks.len() < limit { let x: isize = thread_rng().gen_range(-3..(width as isize + 3)); let y: isize = thread_rng().gen_range(-1..(height as isize + 1)); fm.add_firework(demo_firework_0( @@ -56,6 +74,7 @@ pub fn dyn_gen(fm: &mut FireworkManager, width: u16, height: u16, enable_gradien Duration::from_secs_f32(thread_rng().gen_range(0.0..2.0)), enable_gradient, colors.iter().choose(&mut thread_rng()).unwrap().to_owned(), + cfg, )); } } diff --git a/src/bin/firework/main.rs b/src/bin/firework/main.rs index 8dada84..181ce95 100644 --- a/src/bin/firework/main.rs +++ b/src/bin/firework/main.rs @@ -16,8 +16,8 @@ use crossterm::{ event::{self, KeyCode}, execute, terminal, }; -use firework_rs::fireworks::FireworkManager; use firework_rs::term::Terminal; +use firework_rs::{config::Config, fireworks::FireworkManager}; use firework_rs::{ demo::{ demo_firework_2, demo_firework_comb_0, demo_firework_comb_1, demo_firework_comb_2, @@ -29,9 +29,13 @@ use gen::dyn_gen; use glam::Vec2; fn main() -> Result<()> { + let mut cfg = Config::default(); let mut fps: u8 = 20; let mut is_running = true; let cli = Cli::parse(); + if cli.cjk { + cfg = Config { enable_cjk: true }; + } if let Some(f) = cli.fps { if !(5..=30).contains(&f) { return Err(Error::new( @@ -84,7 +88,7 @@ fn main() -> Result<()> { execute!(stdout, terminal::EnterAlternateScreen, cursor::Hide)?; let mut time = SystemTime::now(); - let mut term = Terminal::default(); + let mut term = Terminal::new(&cfg); while is_running { if event::poll(Duration::ZERO)? { @@ -96,7 +100,7 @@ fn main() -> Result<()> { } event::Event::Resize(_, _) => { fm.reset(); - term.reinit(); + term.reinit(&cfg); } _ => {} }; @@ -105,12 +109,22 @@ fn main() -> Result<()> { (_width, _height) = terminal::size()?; let delta_time = SystemTime::now().duration_since(time).unwrap(); if fm.install_form == FireworkInstallForm::DynamicInstall { - dyn_gen(&mut fm, _width, _height, cli.gradient); + dyn_gen( + &mut fm, + if cfg.enable_cjk { + (_width - 1) / 2 + } else { + _width + }, + _height, + cli.gradient, + &cfg, + ); } fm.update(time, delta_time); time = SystemTime::now(); - term.render(&fm); - term.print(&mut stdout); + term.render(&fm, &cfg); + term.print(&mut stdout, &cfg); if delta_time < Duration::from_secs_f32(1. / fps as f32) { let rem = Duration::from_secs_f32(1. / fps as f32) - delta_time; diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..aceed0b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,10 @@ +/// Configuration of the program +pub struct Config { + pub enable_cjk: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { enable_cjk: false } + } +} diff --git a/src/demo.rs b/src/demo.rs index 2fc9042..0c70258 100644 --- a/src/demo.rs +++ b/src/demo.rs @@ -9,6 +9,7 @@ use glam::Vec2; use rand::{seq::IteratorRandom, thread_rng, Rng}; use crate::{ + config::Config, fireworks::{ExplosionForm, Firework, FireworkConfig}, particle::ParticleConfig, utils::{ @@ -23,11 +24,16 @@ pub fn demo_firework_0( spawn_after: Duration, enable_gradient: bool, colors: Vec<(u8, u8, u8)>, + cfg: &Config, ) -> Firework { let mut particles = Vec::new(); for v in gen_points_circle_normal( - thread_rng().gen_range(230.0..400.0), - thread_rng().gen_range(33..47), + thread_rng().gen_range(if cfg.enable_cjk { + 400.0..600.0 + } else { + 230.0..400.0 + }), + thread_rng().gen_range(if cfg.enable_cjk { 20..35 } else { 33..47 }), ) .iter() { diff --git a/src/lib.rs b/src/lib.rs index 414e507..d1e5d5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod demo; pub mod fireworks; pub mod particle; diff --git a/src/term.rs b/src/term.rs index b041036..b665db1 100644 --- a/src/term.rs +++ b/src/term.rs @@ -7,6 +7,7 @@ use glam::Vec2; use rand::{seq::IteratorRandom, thread_rng}; use crate::{ + config::Config, fireworks::{FireworkManager, FireworkState}, particle::LifeState, utils::distance_squared, @@ -52,9 +53,31 @@ impl Default for Terminal { } impl Terminal { + pub fn new(cfg: &Config) -> Self { + let mut size = terminal::size().expect("Fail to get terminal size."); + if cfg.enable_cjk { + size.0 = (size.0 - 1) / 2; + } + let mut screen = Vec::new(); + (0..size.1).for_each(|_| { + let mut line = Vec::new(); + (0..size.0).for_each(|_| { + line.push(Char { + text: ' ', + color: style::Color::White, + }) + }); + screen.push(line); + }); + Self { size, screen } + } + /// Reload terminal to adapt new window size - pub fn reinit(&mut self) { - let size = terminal::size().expect("Fail to get terminal size."); + pub fn reinit(&mut self, cfg: &Config) { + let mut size = terminal::size().expect("Fail to get terminal size."); + if cfg.enable_cjk { + size.0 = (size.0 - 1) / 2; + } let mut screen = Vec::new(); (0..size.1).for_each(|_| { let mut line = Vec::new(); @@ -88,12 +111,19 @@ impl Terminal { } /// Print the data out to terminal - pub fn print(&self, w: &mut Stdout) { + pub fn print(&self, w: &mut Stdout, cfg: &Config) { self.screen.iter().enumerate().for_each(|(y, line)| { line.iter().enumerate().for_each(|(x, c)| { queue!( w, - MoveTo(x as u16, y as u16), + MoveTo( + if cfg.enable_cjk { + (x * 2) as u16 + } else { + x as u16 + }, + y as u16 + ), style::SetForegroundColor(c.color), style::Print(c.text) ) @@ -104,7 +134,7 @@ impl Terminal { } /// Write the rendering data of all `Fireworks` and `Particles` to `Terminal` - pub fn render(&mut self, fm: &FireworkManager) { + pub fn render(&mut self, fm: &FireworkManager, cfg: &Config) { self.clear_screen(); for firework in fm.fireworks.iter().rev() { if firework.state == FireworkState::Alive { @@ -120,7 +150,13 @@ impl Terminal { particle .trail .iter() - .map(|p| Vec2::new(p.x * 2., p.y)) + .map(|p| { + if cfg.enable_cjk { + *p + } else { + Vec2::new(p.x * 2., p.y) + } + }) .rev() .collect::>() .windows(2) @@ -133,9 +169,15 @@ impl Terminal { && self.screen[p.1 as usize][p.0 as usize].text == ' ' { if let Some(c) = match particle.life_state { - LifeState::Alive => Some(get_char_alive(density)), - LifeState::Declining => Some(get_char_declining(density)), - LifeState::Dying => Some(get_char_dying(density)), + LifeState::Alive => { + Some(get_char_alive(density, cfg.enable_cjk)) + } + LifeState::Declining => { + Some(get_char_declining(density, cfg.enable_cjk)) + } + LifeState::Dying => { + Some(get_char_dying(density, cfg.enable_cjk)) + } LifeState::Dead => None, } { self.screen[p.1 as usize][p.0 as usize] = Char { @@ -221,15 +263,34 @@ fn shift_gradient(color: (u8, u8, u8), scale: f32) -> (u8, u8, u8) { ) } -fn get_char_alive(density: f32) -> char { +fn get_char_alive(density: f32, cjk: bool) -> char { let palette = if density < 0.3 { - "`'. " + if cjk { + "。,”“』 『¥" + } else { + "`'. " + } } else if density < 0.5 { - "/\\|()1{}[]?" + if cjk { + "一二三二三五十十已于上下义天" + // "いうよへくひとフーク " + } else { + "/\\|()1{}[]?" + } } else if density < 0.7 { - "oahkbdpqwmZO0QLCJUYXzcvunxrjft*" + if cjk { + "时中自字木月日目火田左右点以" + // "探しているのが誰かなのかどこかなのかそれともただ単に就職先なのか自分でもよくわからない" + } else { + "oahkbdpqwmZO0QLCJUYXzcvunxrjft*" + } } else { - "$@B%8&WM#" + if cjk { + "𰻞" + // "東京福岡横浜縄" + } else { + "$@B%8&WM#" + } }; palette .chars() @@ -237,15 +298,34 @@ fn get_char_alive(density: f32) -> char { .expect("Fail to choose character.") } -fn get_char_declining(density: f32) -> char { +fn get_char_declining(density: f32, cjk: bool) -> char { let palette = if density < 0.2 { - "` '. " + if cjk { + "?。, 『』 ||" + } else { + "` '. " + } } else if density < 0.6 { - "-_ +~<> i!lI;:,\"^" + if cjk { + "()【】*¥|十一二三六" + // "()【】*¥|ソファー" + } else { + "-_ +~<> i!lI;:,\"^" + } } else if density < 0.85 { - "/\\| ()1{}[ ]?" + if cjk { + "人中亿入上下火土" + // "人ならざるものに出会うかもしれない" + } else { + "/\\| ()1{}[ ]?" + } } else { - "xrjft*" + if cjk { + "繁荣昌盛国泰民安龍龖龠龜耋" + // "時間言葉目覚" + } else { + "xrjft*" + } }; palette .chars() @@ -253,11 +333,20 @@ fn get_char_declining(density: f32) -> char { .expect("Fail to choose character.") } -fn get_char_dying(density: f32) -> char { +fn get_char_dying(density: f32, cjk: bool) -> char { let palette = if density < 0.6 { - ". ,`. ^,' . " + if cjk { + "。 『 』 、: |。,— ……" + } else { + ". ,`. ^,' . " + } } else { - " /\\| ( ) 1{} [ ]?i !l I;: ,\"^ " + if cjk { + "|¥人 上十入乙小 下" + // "イントマトナイフ" + } else { + " /\\| ( ) 1{} [ ]?i !l I;: ,\"^ " + } }; palette .chars()