diff --git a/Cargo.toml b/Cargo.toml index 0c5b949..dea5b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] +fontdue = "0.9.3" +lazy_static = "1.5.0" wasm-bindgen = {version = "0.2.104"} web-sys = {version = "0.3.81", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']} diff --git a/fonts/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf b/fonts/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf new file mode 100644 index 0000000..f0edd19 Binary files /dev/null and b/fonts/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf differ diff --git a/src/app/mod.rs b/src/app/mod.rs index d5d2bea..2a6683f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -54,7 +54,7 @@ impl App { } pub fn draw(&mut self) { - if let Some(view) = &self.root_view { + if let Some(view) = &mut self.root_view { let (width, height) = ( self.renderer.actual_width.clone() as f32, self.renderer.actual_height.clone() as f32, diff --git a/src/fonts.rs b/src/fonts.rs new file mode 100644 index 0000000..4fc3a6b --- /dev/null +++ b/src/fonts.rs @@ -0,0 +1,23 @@ +use fontdue::Font; +use lazy_static::lazy_static; + +// Include the fonts in the binary +pub static ATKINSON_HYPERLEGIBLE_REGULAR: &'static [u8] = + include_bytes!("../fonts/Atkinson_Hyperlegible/AtkinsonHyperlegible-Regular.ttf"); + +// Parse the fonts at runtime +lazy_static! { + pub static ref FONTS: Vec = vec![ + Font::from_bytes( + ATKINSON_HYPERLEGIBLE_REGULAR, + fontdue::FontSettings::default() + ) + .unwrap(), + ]; +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub enum FontHandle { + AtiksonHyperlegibleRegular = 0, +} diff --git a/src/lib.rs b/src/lib.rs index afba90c..53e1c82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ // mod activities; mod app; +mod fonts; mod render; mod views; @@ -28,32 +29,9 @@ macro_rules! log { #[wasm_bindgen] pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App { - let (cwidth, cheight, dist_x, dist_y) = render::calc_resolution(width, height); - canvas.set_width(cwidth); - canvas.set_height(cheight); - log!("WASM Successfully initialized!"); - // set_cursor(Cursor::Auto); - - let mut renderer = Renderer { - ctx: canvas - .get_context("2d") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap(), - img: ImgBuffer::new(width, height), - rand: Rnd::new(9825782), - - canvas_width: cwidth, - canvas_height: cheight, - actual_width: width, - actual_height: height, - - distortion_x: dist_x, - distortion_y: dist_y, - }; + let mut renderer = Renderer::new(canvas, width, height); renderer.resize(width, height); App::new(renderer) diff --git a/src/render/buffer.rs b/src/render/buffer.rs index 436a362..49155ed 100644 --- a/src/render/buffer.rs +++ b/src/render/buffer.rs @@ -29,6 +29,19 @@ impl ImgBuffer { } } + pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) { + for y in 0..other.height { + for x in 0..other.width { + let offset = (y + yoffset) * self.width as usize + (x + xoffset); + let color = other.data[y * other.width + x]; + self.data[offset * 4] = color; + self.data[offset * 4 + 1] = color; + self.data[offset * 4 + 2] = color; + self.data[offset * 4 + 3] = 255; + } + } + } + pub fn resize(&mut self, width: u32, height: u32) { self.data = vec![0; (width * height * 4) as usize]; self.width = width; @@ -371,3 +384,63 @@ impl ImgBuffer { // // self.data[y*self.width + x] = color; // // } // } + +pub struct Bitmap { + pub data: Vec, + pub width: usize, + pub height: usize, +} + +impl Bitmap { + pub fn new(width: usize, height: usize) -> Self { + Self { + data: vec![0; width * height], + width, + height, + } + } + + pub fn from_data(data: Vec, width: usize, height: usize) -> Self { + // assert!(data.len() == width * height, "Invalid data length!");z + Self { + data, + width, + height, + } + } + + pub fn overlay(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) { + for y in 0..other.height { + for x in 0..other.width { + self.data[(y + yoffset as usize) * self.width + (x + xoffset as usize)] = + other.data[y * other.width + x]; + } + } + } + + /// Scale using nearest-neighbor (faster but lower quality) + pub fn scale(&self, scale_x: f32, scale_y: f32) -> Bitmap { + let new_width = (self.width as f32 * scale_x).round() as usize; + let new_height = (self.height as f32 * scale_y).round() as usize; + + let mut new_data = vec![0u8; new_width * new_height]; + + for y in 0..new_height { + let src_y = ((y as f32) / scale_y) as usize; + let src_y = src_y.min(self.height - 1); + + for x in 0..new_width { + let src_x = ((x as f32) / scale_x) as usize; + let src_x = src_x.min(self.width - 1); + + new_data[y * new_width + x] = self.data[src_y * self.width + src_x]; + } + } + + Bitmap { + data: new_data, + width: new_width, + height: new_height, + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 05930dd..79b966a 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,7 +4,7 @@ mod renderer; pub use renderer::Renderer; -pub const RESOLUTION: u32 = 1200; +pub const RESOLUTION: u32 = 2000; pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) { let aspect = width as f32 / height as f32; diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 5f8bb0a..4de4013 100644 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -1,7 +1,13 @@ +use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; use wasm_bindgen::{Clamped, prelude::*}; use web_sys::ImageData; -use crate::render::{RESOLUTION, buffer::ImgBuffer, calc_resolution, rand::Rnd}; +use crate::render::{ + RESOLUTION, + buffer::{Bitmap, ImgBuffer}, + calc_resolution, + rand::Rnd, +}; #[wasm_bindgen] pub struct Renderer { @@ -16,6 +22,8 @@ pub struct Renderer { pub distortion_x: f32, pub distortion_y: f32, + pub ratio_x: f32, + pub ratio_y: f32, } impl Renderer { @@ -42,6 +50,8 @@ impl Renderer { actual_height: height, distortion_x: dist_x, distortion_y: dist_y, + ratio_x: cwidth as f32 / width as f32, + ratio_y: cheight as f32 / height as f32, } } @@ -55,6 +65,9 @@ impl Renderer { self.distortion_x = dist_x; self.distortion_y = dist_y; + self.ratio_x = cwidth as f32 / width as f32; + self.ratio_y = cheight as f32 / height as f32; + self.img.resize(cwidth, cheight); self.ctx.canvas().unwrap().set_width(cwidth); self.ctx.canvas().unwrap().set_height(cheight); @@ -77,10 +90,7 @@ impl Renderer { impl Renderer { pub fn undistort(&self, x: f32, y: f32) -> (f32, f32) { - ( - x * (self.canvas_width as f32 / self.actual_width as f32), - y * (self.canvas_height as f32 / self.actual_height as f32), - ) + (x * self.ratio_x, y * self.ratio_y) } /// Draw a rectangle centered at (cx, cy) with the given width and height. @@ -159,3 +169,25 @@ impl Renderer { } } } + +// // Fonts +// impl Renderer { +// pub fn rasterize_font( +// &mut self, +// x: i32, +// y: i32, +// text: &str, +// font_scale: f32, +// font: FontHandle, +// ) { +// } + +// pub fn measure_text_bounds( +// &mut self, +// text: &str, +// font_scale: i32, +// font: FontHandle, +// ) -> (i32, i32) { +// (100, 100) +// } +// } diff --git a/src/views/color_rect_view.rs b/src/views/color_rect_view.rs index a04e39e..2160b22 100644 --- a/src/views/color_rect_view.rs +++ b/src/views/color_rect_view.rs @@ -1,5 +1,4 @@ use crate::{ - log, render::Renderer, views::{Bounds, View}, }; @@ -15,11 +14,11 @@ impl ColorRectView { } impl View for ColorRectView { - fn draw(&self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) { + fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) { renderer.rect_xywh(x as i32, y as i32, w as i32, h as i32, self.color); - log!("Draw"); + // log!("Draw"); } fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) { - (Bounds::MatchParent, Bounds::Pixels(2200.)) + (Bounds::MatchParent, Bounds::Pixels(200.)) } } diff --git a/src/views/mod.rs b/src/views/mod.rs index a591b08..fdd06c2 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,13 +1,15 @@ use crate::render::Renderer; mod color_rect_view; +mod text_view; mod vertical_layout; use color_rect_view::ColorRectView; +use text_view::TextView; use vertical_layout::VerticalLayout; pub trait View { - fn draw(&self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32); + fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32); fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds); } @@ -23,6 +25,7 @@ pub enum Bounds { pub fn default_view() -> Box { Box::new(VerticalLayout::new(vec![ Box::new(ColorRectView::new(12, 34, 56)), + Box::new(TextView::new("Testing!\n12345".to_string())), Box::new(ColorRectView::new(20, 60, 80)), ])) } diff --git a/src/views/text_view.rs b/src/views/text_view.rs new file mode 100644 index 0000000..6660512 --- /dev/null +++ b/src/views/text_view.rs @@ -0,0 +1,85 @@ +use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; + +use crate::{ + fonts::{FONTS, FontHandle}, + log, + render::{Renderer, buffer::Bitmap}, + views::{Bounds, View}, +}; + +pub struct TextView { + layout: Layout, + text: String, + scale: f32, + font: FontHandle, +} + +impl TextView { + pub fn new(text: String) -> Self { + let mut layout = Layout::new(CoordinateSystem::PositiveYDown); + + layout.reset(&LayoutSettings { + ..LayoutSettings::default() + }); + + Self { + layout, + text, + scale: 20., + font: FontHandle::AtiksonHyperlegibleRegular, + } + } +} + +impl View for TextView { + fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) { + // renderer.rasterize_font( + // x as i32, + // y as i32, + // &self.text, + // 20., + // FontHandle::AtiksonHyperlegibleRegular, + // ); + // renderer. + + // renderer.rect_xywh(x as i32, y as i32, w as i32, h as i32, self.color); + + let (x, y) = renderer.undistort(x as f32, y as f32); + + let font = (self.font as usize).clone(); + + self.layout.clear(); + + self.layout + .append(&FONTS, &TextStyle::new(&self.text, self.scale, font)); + + let (mut width, mut height): (usize, usize) = (0, 0); + for glyph in self.layout.glyphs() { + width = width.max(glyph.x as usize + glyph.width); + height = height.max(glyph.y as usize + glyph.height); + } + + let x_padding = 0.; + let mut new_bitmap = Bitmap::new(width + 2 * (self.scale * x_padding) as usize, height); + + for glyph in self.layout.glyphs() { + let font = &FONTS[glyph.font_index]; + let (_, char_bitmap) = font.rasterize_config(glyph.key); + + new_bitmap.overlay( + &Bitmap::from_data(char_bitmap, glyph.width, glyph.height), + glyph.x as usize, + glyph.y as usize, + ); + } + + let scaled = new_bitmap.scale(renderer.ratio_x, renderer.ratio_y); + + renderer.img.overlay_bitmap(&scaled, x as usize, y as usize); + + // new_bitmap + } + fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) { + (Bounds::MatchParent, Bounds::Pixels(200.)) + } +} diff --git a/src/views/vertical_layout.rs b/src/views/vertical_layout.rs index da42031..8b667db 100644 --- a/src/views/vertical_layout.rs +++ b/src/views/vertical_layout.rs @@ -11,10 +11,10 @@ impl VerticalLayout { } impl View for VerticalLayout { - fn draw(&self, renderer: &mut crate::render::Renderer, x: f32, y: f32, w: f32, h: f32) { + fn draw(&mut self, renderer: &mut crate::render::Renderer, x: f32, y: f32, w: f32, h: f32) { let mut cur_y = y; - for view in &self.views { + for view in &mut self.views { let (vx, vy) = view.bounds(w, h); let vx = match vx {