diff --git a/Cargo.lock b/Cargo.lock index 8c6b8a7..5b36f94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,7 @@ dependencies = [ "cgmath", "chrono", "fern", + "fontdue", "image", "log", "pollster", @@ -504,6 +505,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontdue" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +dependencies = [ + "hashbrown 0.11.2", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -578,7 +589,7 @@ checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ "bitflags", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -590,6 +601,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -666,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1319,6 +1339,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "unicode-ident" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index 9b66401..566f042 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ fern = { version = "^0.6.1", features = ["colored"] } bitflags = "^1.3.2" cgmath = { version = "^0.18.0", features = ["bytemuck"] } pollster = "^0.2.5" +fontdue = "0.7.2" # surface creation winit = "^0.26.1" @@ -33,3 +34,6 @@ features = ["png", "jpeg"] [patch.crates-io] cgmath = { path = "cgmath" } +[profile.release] +debug = 1 + diff --git a/assets/DejaVuSansMono.ttf b/assets/DejaVuSansMono.ttf new file mode 100644 index 0000000..f578602 Binary files /dev/null and b/assets/DejaVuSansMono.ttf differ diff --git a/src/canvas.rs b/src/canvas.rs index 83dd4cc..9da8f89 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -80,8 +80,12 @@ impl Canvas { ) } - pub fn create_text_sprite(&mut self, _size: Size, _scale: f32) -> TextSprite { - unimplemented!(); + pub fn create_text_sprite(&mut self, text: &'static str, size: Size) -> TextSprite { + TextSprite::new( + text, + size, + &self.renderer, + ) } pub fn create_shape_sprite(&mut self) -> ShapeSprite { diff --git a/src/main.rs b/src/main.rs index d842145..47a58e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use canvas::{ Application, Canvas, texture::TextureHandle, - sprite::TextureSprite, + sprite::{TextureSprite, TextSprite}, utils::Position, }; @@ -30,7 +30,7 @@ fn setup_logger() -> Result<(), fern::InitError> { message )) }) - .level(log::LevelFilter::Debug) + .level(log::LevelFilter::Info) .chain(std::io::stdout()) .chain(fern::log_file("output.log")?) .apply()?; Ok(()) @@ -40,6 +40,7 @@ struct ExampleState { pub texture: TextureHandle, pub tex_sprite: TextureSprite, pub sub_sprite: TextureSprite, + pub txt_sprites: Vec, pub last_instant: Instant, pub last_offset: u32, pub last_pos: Position, @@ -51,7 +52,10 @@ struct ExampleApp {} impl Application for ExampleApp { fn init(canvas: &mut Canvas) -> Result { - use canvas::utils::Size; + use canvas::{ + utils::Size, + sprite::Sprite, + }; //// 20 x 20 sprite of a picture let texture = canvas.create_texture_from_file("assets/camel.jpg", None, None, None) @@ -59,9 +63,18 @@ impl Application for ExampleApp { let mut tex_sprite = canvas.create_texture_sprite(Size {w: 1280, h: 720}); tex_sprite.set_texture(texture.clone(), Some(Position {x: 0, y: 0}), 1.0); - let mut sub_sprite = canvas.create_texture_sprite(Size {w: 200, h: 200}); + let mut sub_sprite = canvas.create_texture_sprite(Size {w: 100, h: 100}); sub_sprite.set_texture(texture.clone(), Some(Position {x: 350, y: 0}), 1.0); + let mut txt_sprites = Vec::::with_capacity(10); + for x in 0..5 { + for y in 0..2 { + let mut sprite = canvas.create_text_sprite("00", Size {w: 200, h: 200}); + sprite.set_position(Position { x: x * 100, y: y * 100}); + txt_sprites.push(sprite); + } + } + canvas.clear(); canvas.update(); @@ -71,6 +84,7 @@ impl Application for ExampleApp { texture, tex_sprite, sub_sprite, + txt_sprites, last_instant, last_offset: 0, last_pos: Position::origin(), @@ -85,6 +99,9 @@ impl Application for ExampleApp { let elapsed = now.duration_since(state.last_instant).as_millis(); state.last_instant = now; debug!("frame time: {}ms", elapsed); + for sprite in state.txt_sprites.iter_mut() { + sprite.set_text(&format!("{}", elapsed)); + } //state.sub_sprite.for_each(|pix| unsafe {pix.flat = pix.flat.wrapping_add(1);}); @@ -155,7 +172,10 @@ impl Application for ExampleApp { canvas.draw(&mut state.tex_sprite); - canvas.draw(&mut state.sub_sprite); + // canvas.draw(&mut state.sub_sprite); + for sprite in state.txt_sprites.iter_mut() { + canvas.draw(sprite); + } canvas.update(); Ok(()) diff --git a/src/sprite/text_sprite.rs b/src/sprite/text_sprite.rs index 1b30bc1..d3b5bd7 100644 --- a/src/sprite/text_sprite.rs +++ b/src/sprite/text_sprite.rs @@ -6,25 +6,114 @@ use log::{debug, error, info, trace, warn}; use crate::{ renderer::WgpuRenderer, sprite::Sprite, - utils::{Pixel, Position, Size}, + utils::{Pixel, Color, Position, Size}, }; +use super::TextureSprite; //--External imports-------------------------------------------------------------------------------- -use cgmath::Matrix4; - //--TextSprite struct------------------------------------------------------------------------------- -#[allow(dead_code)] pub struct TextSprite { - matrix: Matrix4, - size: Size, - text: &'static str, //TODO: temporary + texture_sprite: TextureSprite, + font: [fontdue::Font; 1], + layout: fontdue::layout::Layout, } impl TextSprite { - pub fn set_text(&mut self, _text: &'static str) { - todo!(); + pub fn new(text: &'static str, size: Size, renderer: &WgpuRenderer) -> Self { + use crate::{ + renderer::Texture, + texture::TextureHandle, + }; + use fontdue::layout::{CoordinateSystem, Layout, TextStyle}; + + //load fonts + //TODO add support for dynamic fonts + let font = include_bytes!("../../assets/DejaVuSansMono.ttf"); + let settings = fontdue::FontSettings { + scale: 11.0, + ..fontdue::FontSettings::default() + }; + let font = [fontdue::Font::from_bytes(&font[..], settings).unwrap()]; + + //configure text layout to fit sprite size, keep default settings + let mut layout = Layout::new(CoordinateSystem::PositiveYDown); + + //compute text layout + layout.append(&font, &TextStyle::new(text, 40.0, 0)); + + //draw text to the texture + let mut raw_texture : Vec:: + = vec![Color::NONE; (size.h * size.w).try_into().unwrap()]; + + //draw each glyph at the corresponding position on the texture + for glyph in layout.glyphs() { + let (_metrics, bitmap) = font[0].rasterize_config(glyph.key); + + let glyph_x = glyph.x as usize; + let glyph_y = glyph.y as usize; + let size_w = size.w as usize; + let size_h = size.h as usize; + + //only try to draw a glyph if its origin fits in the texture + if glyph_x < size_w && glyph_y < size_h { + let origin = glyph_x + size_w * glyph_y; + + //glyphs that do not fit on the texture are cropped at the edge of the latter + for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) { + for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) { + + raw_texture[origin + x + y * size_w] + = Color::BLACK.with_alpha(bitmap[x + y * glyph.width]); + } } } } + + //create the texture + let texture = Texture::create(renderer, bytemuck::cast_slice(&raw_texture), size); + let texture_handle = TextureHandle::from_texture(texture); + + let texture_sprite = TextureSprite::new(texture_handle, size, &renderer); + + Self { + texture_sprite, + font, + layout, + } + } + + pub fn set_text(&mut self, text: &str) { + + //compute text layout + self.layout.clear(); + self.layout.append(&self.font, &fontdue::layout::TextStyle::new(text, 40.0, 0)); + + let size = self.texture_sprite.get_size(); + self.texture_sprite.fill(Color::NONE); + + //draw each glyph at the corresponding position on the texture + for glyph in self.layout.glyphs() { + let (_metrics, bitmap) = self.font[0].rasterize_config(glyph.key); + + let glyph_x = glyph.x as usize; + let glyph_y = glyph.y as usize; + let size_w = size.w as usize; + let size_h = size.h as usize; + + //only try to draw a glyph if its origin fits in the texture + if glyph_x < size_w && glyph_y < size_h { + + //glyphs that do not fit on the texture are cropped at the edge of the latter + for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) { + for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) { + + self.texture_sprite.set_pixel( + Position { + x: (glyph_x + x) as u32, + y: (glyph_y + y) as u32, + }, + Color::BLACK.with_alpha(bitmap[x + y * glyph.width]) + ); + } } } } } pub fn set_color(&mut self, _color: Pixel) { @@ -34,24 +123,24 @@ impl TextSprite { impl Sprite for TextSprite { - fn set_position(&mut self, _pos: Position) { - todo!(); + fn set_position(&mut self, pos: Position) { + self.texture_sprite.set_position(pos); } - fn set_rotation(&mut self, _rot: f32) { - todo!(); + fn set_rotation(&mut self, rot: f32) { + self.texture_sprite.set_rotation(rot); } - fn set_alpha(&mut self, _alpha: u8) { - todo!(); + fn set_alpha(&mut self, alpha: u8) { + self.texture_sprite.set_alpha(alpha); } - fn set_scale(&mut self, _scale: f32) { - todo!(); + fn set_scale(&mut self, scale: f32) { + self.texture_sprite.set_scale(scale); } - fn render(&mut self, _renderer: &mut WgpuRenderer) { - todo!(); + fn render(&mut self, renderer: &mut WgpuRenderer) { + self.texture_sprite.render(renderer); } } diff --git a/src/sprite/texture_sprite.rs b/src/sprite/texture_sprite.rs index 110ca7e..854d0f0 100644 --- a/src/sprite/texture_sprite.rs +++ b/src/sprite/texture_sprite.rs @@ -42,8 +42,7 @@ impl TextureSprite { ]; pub fn new(texture: TextureHandle, - size: Size, - renderer: &WgpuRenderer) + size: Size, renderer: &WgpuRenderer) -> Self { // initialize pipeline if needed @@ -91,11 +90,20 @@ impl TextureSprite { self.texture.for_each_in_area(func, self.offset, self.inner_size); } + pub fn fill(&mut self, pixel: Pixel) { + self.texture.fill(pixel); + } + + pub fn get_size(&self) -> Size { + self.texture.get_size() + } + fn update_vertices(&mut self, renderer: &WgpuRenderer) { debug!("Updating vertices..."); let size = self.texture.get_size(); + // compute normalized coordinates let x_size = self.inner_size.w as f32 / size.w as f32 * self.scale; let y_size = self.inner_size.h as f32 / size.h as f32 * self.scale; @@ -235,7 +243,7 @@ fn initialize_pipeline(renderer: &WgpuRenderer) -> wgpu::RenderPipeline { entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: renderer.config.format, - blend: Some(wgpu::BlendState::REPLACE), + blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], }), diff --git a/src/texture.rs b/src/texture.rs index 56ebf5b..1572b47 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -60,6 +60,12 @@ impl TextureHandle { texture.is_synced = false; } + pub fn fill (&mut self, pixel: Pixel) { + let mut texture = self.texture.borrow_mut(); + texture.buffer.fill(pixel); + texture.is_synced = false; + } + pub fn texture(&self) -> &Rc> { //TODO improve that &self.texture diff --git a/src/utils.rs b/src/utils.rs index 4e5959e..2639f5a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -8,7 +8,7 @@ use cgmath::Vector3; //--Position struct--------------------------------------------------------------------------------- #[derive(Copy, Clone)] pub struct Position { - pub x: u32, + pub x: u32, pub y: u32, } @@ -23,7 +23,7 @@ impl Position { } impl From for Vector3 { - + fn from(pos: Position) -> Self { Self::new(pos.x as f32, pos.y as f32, 0.0) } @@ -52,7 +52,7 @@ impl From for winit::dpi::Size { fn from(size: Size) -> Self { winit::dpi::Size::Physical ( winit::dpi::PhysicalSize { - width: size.w, + width: size.w, height: size.h, }) } @@ -98,13 +98,23 @@ pub union Pixel { unsafe impl bytemuck::Pod for Pixel {} impl Pixel { - + pub fn rgb(r: u8, g: u8, b: u8) -> Pixel { Pixel { rgba: Rgba{r, g, b, a: 255}, } } + pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Pixel { + Pixel { + rgba: Rgba{r, g, b, a}, + } + } + + pub fn with_alpha(self, a: u8) -> Pixel { + unsafe { Pixel::rgba(self.rgba.r, self.rgba.g, self.rgba.b, a) } + } + pub fn to_rgba(&self) -> Rgba { unsafe { self.rgba } } @@ -117,10 +127,10 @@ pub struct Color; impl Color { pub const NONE: Pixel = Pixel {flat: 0x00000000}; pub const WHITE: Pixel = Pixel {flat: 0xffffffff}; - pub const BLACK: Pixel = Pixel {flat: 0x000000ff}; + pub const BLACK: Pixel = Pixel {flat: 0xff000000}; pub const RED: Pixel = Pixel {flat: 0xff0000ff}; - pub const GREEN: Pixel = Pixel {flat: 0x00ff00ff}; - pub const BLUE: Pixel = Pixel {flat: 0x0000ffff}; + pub const GREEN: Pixel = Pixel {flat: 0xff00ff00}; + pub const BLUE: Pixel = Pixel {flat: 0xffff0000}; }