From a38b37878e28c664edf0c11306520b2e979a631b Mon Sep 17 00:00:00 2001
From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com>
Date: Mon, 10 Nov 2025 11:08:23 -0700
Subject: [PATCH] Font scale
---
Cargo.toml | 9 +-
pages/main.xml | 10 +-
src/app/mod.rs | 25 +++--
src/lib.rs | 46 +++++++++-
src/parser/mod.rs | 3 +-
src/render/buffer.rs | 25 +----
src/render/buffer_overlay.rs | 162 +++++++++++++++++++++++++++++++++
src/render/mod.rs | 2 +
src/views/box_view.rs | 4 +-
src/views/color_rect_view.rs | 2 +-
src/views/constraint_layout.rs | 2 +-
src/views/mod.rs | 9 +-
src/views/text_view.rs | 48 +++++++---
src/views/vertical_layout.rs | 3 +-
14 files changed, 283 insertions(+), 67 deletions(-)
create mode 100644 src/render/buffer_overlay.rs
diff --git a/Cargo.toml b/Cargo.toml
index b12683c..882ceef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,8 +10,13 @@ crate-type = ["cdylib"]
fontdue = "0.9.3"
lazy_static = "1.5.0"
quick-xml = "0.38.3"
-wasm-bindgen = {version = "0.2.104"}
-web-sys = {version = "0.3.81", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']}
+wasm-bindgen = "0.2.105"
+# web-sys = "0.3.82"
+# fontdue = "0.9.3"
+# lazy_static = "1.5.0"
+# quick-xml = "0.38.3"
+# wasm-bindgen = {version = "0.2.104"}
+web-sys = {version = "0.3.82", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window', 'ImageData']}
# [dependencies.web-sys]
# features =
diff --git a/pages/main.xml b/pages/main.xml
index 1ec8acf..c0cbd80 100644
--- a/pages/main.xml
+++ b/pages/main.xml
@@ -1,8 +1,4 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/src/app/mod.rs b/src/app/mod.rs
index f7f33ba..fbcb861 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -5,7 +5,7 @@ pub use cursors::{Cursor, set_cursor};
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{
- log,
+ AppErr, log,
parser::{self, TEST_XML},
render::Renderer,
views::View,
@@ -35,7 +35,7 @@ impl AppState {
}
impl App {
- pub fn new(renderer: Renderer) -> Self {
+ pub fn new(renderer: Renderer) -> Result {
let (width, height) = (renderer.actual_width, renderer.actual_height);
let root_view = parser::parse(TEST_XML);
@@ -48,12 +48,12 @@ impl App {
state: AppState::new(),
};
- this.resize(width, height);
+ this.resize(width, height)?;
- this
+ Ok(this)
}
- pub fn draw(&mut self) {
+ pub fn draw(&mut self) -> Result<(), AppErr> {
// self.renderer.img.randomize(&mut self.renderer.rand);
if let Some(view) = &mut self.root_view {
let (width, height) = (
@@ -64,32 +64,39 @@ impl App {
view.draw(&mut self.renderer, 0., 0., width, height);
self.renderer.update();
}
+
+ Ok(())
}
}
// App events
impl App {
- pub fn resize(&mut self, width: usize, height: usize) {
+ pub fn resize(&mut self, width: usize, height: usize) -> Result<(), AppErr> {
self.renderer.resize(width, height);
if let Some(view) = &mut self.root_view {
- view.resize(0., 0., width as f32, height as f32);
+ view.resize(&mut self.renderer, 0., 0., width as f32, height as f32);
}
self.draw();
+ Ok(())
}
- pub fn mouse_move(&mut self, x: f32, y: f32) {
+ pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), AppErr> {
self.state.mouse_x = x;
self.state.mouse_y = y;
+ Ok(())
+
// if let Some(current_activity) = self.current_activity {
// self.activities[current_activity].mouse_move(&mut self.renderer, &self.state);
// }
}
- pub fn l_click(&mut self, x: f32, y: f32) {
+ pub fn l_click(&mut self, x: f32, y: f32) -> Result<(), AppErr> {
self.state.mouse_x = x;
self.state.mouse_y = y;
+ Ok(())
+
// if let Some(current_activity) = self.current_activity {
// self.activities[current_activity].l_click(&mut self.renderer, &self.state);
// }
diff --git a/src/lib.rs b/src/lib.rs
index 3c351fc..ada5fa7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,8 @@ mod parser;
mod render;
mod views;
+use std::error::Error;
+
use wasm_bindgen::{Clamped, prelude::*};
use web_sys::ImageData;
@@ -17,6 +19,14 @@ use crate::{
render::Renderer,
};
+#[derive(Debug)]
+pub enum AppErr {
+ ParseErr(String),
+ RenderErr(String),
+ ArrangeErr(String),
+ RasterErr(String),
+}
+
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
@@ -35,28 +45,54 @@ pub fn init(canvas: &web_sys::HtmlCanvasElement, width: usize, height: usize) ->
let mut renderer = Renderer::new(canvas, width, height);
renderer.resize(width, height);
- App::new(renderer)
+ match App::new(renderer) {
+ Ok(app) => app,
+ Err(e) => {
+ log!("Error initializing: {:?}", e);
+ panic!();
+ }
+ }
}
#[wasm_bindgen]
pub fn resize(app: &mut App, width: usize, height: usize) {
- app.resize(width, height);
+ match app.resize(width, height) {
+ Ok(_) => {}
+ Err(e) => {
+ log!("Error resizing: {:?}", e);
+ }
+ }
}
#[wasm_bindgen]
pub fn draw(app: &mut App) {
// app.renderer.img.randomize(&mut app.renderer.rand);
- app.draw();
+ match app.draw() {
+ Ok(_) => {}
+ Err(e) => {
+ log!("Error drawing: {:?}", e);
+ }
+ }
}
#[wasm_bindgen]
pub fn click(app: &mut App, x: f32, y: f32) {
// ctx.img.randomize(&mut ctx.rand);
// draw::draw(ctx);
- app.l_click(x, y);
+ match app.l_click(x, y) {
+ Ok(_) => {}
+ Err(e) => {
+ log!("Error on click: {:?}", e);
+ }
+ }
}
#[wasm_bindgen]
pub fn mouse_move(app: &mut App, x: f32, y: f32) {
- app.mouse_move(x, y);
+ match app.mouse_move(x, y) {
+ Ok(_) => {}
+ Err(e) => {
+ log!("Error on mouse move: {:?}", e);
+ }
+ }
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 401e9a6..3ef1170 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{
log,
- views::{Bounds, BoxView, ColorRectView, ConstraintLayout, View},
+ views::{Bounds, BoxView, ColorRectView, ConstraintLayout, TextView, View},
};
mod parser;
@@ -22,6 +22,7 @@ impl Tag {
"ColoredRectView" => ColorRectView::from_tag(&self.attributes, &self.children),
"ConstraintLayout" => ConstraintLayout::from_tag(&self.attributes, &self.children),
"BoxView" => BoxView::from_tag(&self.attributes, &self.children),
+ "TextView" => TextView::from_tag(&self.attributes, &self.children),
_ => panic!("Unknown tag: {}", self.name),
}
diff --git a/src/render/buffer.rs b/src/render/buffer.rs
index dff9cd5..6571827 100644
--- a/src/render/buffer.rs
+++ b/src/render/buffer.rs
@@ -1,9 +1,9 @@
use crate::render::rand;
pub struct ImgBuffer {
- pub data: Vec,
- width: usize,
- height: usize,
+ pub(crate) data: Vec,
+ pub(crate) width: usize,
+ pub(crate) height: usize,
}
impl ImgBuffer {
@@ -24,25 +24,6 @@ impl ImgBuffer {
}
}
- pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) {
- let length = self.data.len();
- for y in 0..other.height {
- for x in 0..other.width {
- // Calculate the offset for the current pixel in the buffer
- // This uses bitshift instead of multiplication x4 for performance
- let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2;
- if offset >= length {
- return;
- }
- let color = other.data[y * other.width + x];
- self.data[offset] = color;
- self.data[offset + 1] = color;
- self.data[offset + 2] = color;
- self.data[offset + 3] = 255;
- }
- }
- }
-
pub fn resize(&mut self, width: usize, height: usize) {
self.data = vec![0; width * height * 4];
self.width = width;
diff --git a/src/render/buffer_overlay.rs b/src/render/buffer_overlay.rs
new file mode 100644
index 0000000..379d42e
--- /dev/null
+++ b/src/render/buffer_overlay.rs
@@ -0,0 +1,162 @@
+use crate::render::buffer::{Bitmap, ImgBuffer};
+
+impl ImgBuffer {
+ pub fn overlay_bitmap_fg_bg(
+ &mut self,
+ other: &Bitmap,
+ xoffset: usize,
+ yoffset: usize,
+ foreground_color: (u8, u8, u8, u8),
+ background_color: (u8, u8, u8, u8),
+ ) {
+ let length = self.data.len();
+ for y in 0..other.height {
+ for x in 0..other.width {
+ let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2;
+ if offset >= length {
+ return;
+ }
+ let weight = other.data[y * other.width + x];
+
+ let (mut pixel_r, mut pixel_g, mut pixel_b, mut pixel_a) = (
+ self.data[offset],
+ self.data[offset + 1],
+ self.data[offset + 2],
+ self.data[offset + 3],
+ );
+
+ (pixel_r, pixel_g, pixel_b, pixel_a) = Self::blend_bitmap_pixel_fg_bg(
+ (pixel_r, pixel_b, pixel_g, pixel_a),
+ weight,
+ foreground_color,
+ background_color,
+ );
+
+ self.data[offset] = pixel_r;
+ self.data[offset + 1] = pixel_g;
+ self.data[offset + 2] = pixel_b;
+ self.data[offset + 3] = pixel_a;
+
+ // = color;
+ // self.data[offset + 1] = color;
+ // self.data[offset + 2] = color;
+ // self.data[offset + 3] = 255;
+ }
+ }
+ }
+
+ pub fn overlay_bitmap_fg(
+ &mut self,
+ other: &Bitmap,
+ xoffset: usize,
+ yoffset: usize,
+ foreground_color: (u8, u8, u8),
+ ) {
+ let length = self.data.len();
+ for y in 0..other.height {
+ for x in 0..other.width {
+ let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2;
+ if offset >= length {
+ return;
+ }
+ let weight = other.data[y * other.width + x];
+
+ let (mut pixel_r, mut pixel_g, mut pixel_b, mut pixel_a) = (
+ self.data[offset],
+ self.data[offset + 1],
+ self.data[offset + 2],
+ self.data[offset + 3],
+ );
+
+ (pixel_r, pixel_g, pixel_b, pixel_a) = Self::blend_bitmap_pixel_fg(
+ (pixel_r, pixel_b, pixel_g, pixel_a),
+ weight,
+ foreground_color,
+ );
+
+ self.data[offset] = pixel_r;
+ self.data[offset + 1] = pixel_g;
+ self.data[offset + 2] = pixel_b;
+ self.data[offset + 3] = pixel_a;
+
+ // = color;
+ // self.data[offset + 1] = color;
+ // self.data[offset + 2] = color;
+ // self.data[offset + 3] = 255;
+ }
+ }
+ }
+
+ pub fn blend_bitmap_pixel_fg_bg(
+ original: (u8, u8, u8, u8),
+ bitmap_weight: u8,
+ fg_color: (u8, u8, u8, u8),
+ bg_color: (u8, u8, u8, u8),
+ ) -> (u8, u8, u8, u8) {
+ // Step 1: Interpolate between bg_color and fg_color based on bitmap_weight
+
+ let w = bitmap_weight as u32;
+ let inv_w = 255u32 - w;
+
+ // Blend RGB channels using (x * 257) >> 16 approximation for /255
+ let br = (((bg_color.0 as u32 * inv_w + fg_color.0 as u32 * w) * 257) >> 16) as u8;
+ let bg = (((bg_color.1 as u32 * inv_w + fg_color.1 as u32 * w) * 257) >> 16) as u8;
+ let bb = (((bg_color.2 as u32 * inv_w + fg_color.2 as u32 * w) * 257) >> 16) as u8;
+ let ba = (((bg_color.3 as u32 * inv_w + fg_color.3 as u32 * w) * 257) >> 16) as u8;
+
+ // Step 2: Alpha blend the interpolated color over the original pixel
+
+ if ba == 0 {
+ // Fully transparent, return original
+ return original;
+ }
+
+ if ba == 255 {
+ // Fully opaque, return blended color
+ return (br, bg, bb, 255);
+ }
+
+ let a = ba as u32;
+ let inv_a = 255u32 - a;
+
+ let r = (((original.0 as u32 * inv_a + br as u32 * a) * 257) >> 16) as u8;
+ let g = (((original.1 as u32 * inv_a + bg as u32 * a) * 257) >> 16) as u8;
+ let b = (((original.2 as u32 * inv_a + bb as u32 * a) * 257) >> 16) as u8;
+
+ // Alpha channel: blend using "over" operation
+ let final_a = (((original.3 as u32 * inv_a) * 257) >> 16) + a;
+ let final_a = final_a.min(255) as u8;
+
+ (r, g, b, final_a)
+ }
+
+ pub fn blend_bitmap_pixel_fg(
+ original: (u8, u8, u8, u8),
+ bitmap_weight: u8,
+ fg_color: (u8, u8, u8),
+ ) -> (u8, u8, u8, u8) {
+ if bitmap_weight == 0 {
+ // Fully transparent, return original
+ return original;
+ }
+
+ if bitmap_weight == 255 {
+ // Fully opaque, return foreground color
+ return (fg_color.0, fg_color.1, fg_color.2, 255);
+ }
+
+ // Alpha blend: original + (fg - original) * weight / 255
+ let w = bitmap_weight as u32;
+ let inv_w = 255u32 - w;
+
+ let r = (((original.0 as u32 * inv_w + fg_color.0 as u32 * w) * 257) >> 16) as u8;
+ let g = (((original.1 as u32 * inv_w + fg_color.1 as u32 * w) * 257) >> 16) as u8;
+ let b = (((original.2 as u32 * inv_w + fg_color.2 as u32 * w) * 257) >> 16) as u8;
+
+ // Alpha channel: blend to full opacity based on weight
+ let final_a = (((original.3 as u32 * inv_w) * 257) >> 16) + w;
+ let final_a = final_a.min(255) as u8;
+
+ (r, g, b, final_a)
+ }
+}
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 429f48b..9e6eb0d 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -1,4 +1,5 @@
pub mod buffer;
+pub mod buffer_overlay;
pub mod rand;
mod renderer;
@@ -13,6 +14,7 @@ pub fn calc_resolution(width: usize, height: usize) -> (usize, usize, f32, f32)
let new_width = (distortion_x * RESOLUTION as f32).round() as usize;
let new_height = (distortion_y * RESOLUTION as f32).round() as usize;
+
(new_width, new_height, distortion_x, distortion_y)
}
diff --git a/src/views/box_view.rs b/src/views/box_view.rs
index 729fcc5..574575c 100644
--- a/src/views/box_view.rs
+++ b/src/views/box_view.rs
@@ -67,8 +67,8 @@ impl View for BoxView {
self.view.draw(renderer, x, y, w, h);
}
- fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {
- self.view.resize(x, y, w, h);
+ fn resize(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) {
+ self.view.resize(renderer, x, y, w, h);
}
fn from_tag(
diff --git a/src/views/color_rect_view.rs b/src/views/color_rect_view.rs
index 2e93958..55cc069 100644
--- a/src/views/color_rect_view.rs
+++ b/src/views/color_rect_view.rs
@@ -21,7 +21,7 @@ impl View for ColorRectView {
// log!("Draw");
}
- fn resize(&mut self, _x: f32, _y: f32, _w: f32, _h: f32) {}
+ fn resize(&mut self, _renderer: &mut Renderer, _x: f32, _y: f32, _w: f32, _h: f32) {}
fn from_tag(
attributes: &std::collections::HashMap,
diff --git a/src/views/constraint_layout.rs b/src/views/constraint_layout.rs
index 9a4dc47..eee6d31 100644
--- a/src/views/constraint_layout.rs
+++ b/src/views/constraint_layout.rs
@@ -171,7 +171,7 @@ impl View for ConstraintLayout {
}
}
- fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {
+ fn resize(&mut self, _renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) {
self.recalculate_positions(x, y, w, h);
}
diff --git a/src/views/mod.rs b/src/views/mod.rs
index a7df1ac..79a418a 100644
--- a/src/views/mod.rs
+++ b/src/views/mod.rs
@@ -1,8 +1,4 @@
-use std::{
- any::{Any, TypeId},
- collections::HashMap,
- fmt::Debug,
-};
+use std::{any::Any, collections::HashMap, fmt::Debug};
use crate::{parser::Tag, render::Renderer};
@@ -15,10 +11,11 @@ mod vertical_layout;
pub use box_view::BoxView;
pub use color_rect_view::ColorRectView;
pub use constraint_layout::ConstraintLayout;
+pub use text_view::TextView;
pub trait View: Debug {
fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32);
- fn resize(&mut self, x: f32, y: f32, w: f32, h: f32);
+ fn resize(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32);
fn from_tag(attributes: &HashMap, children: &Vec) -> Box
where
diff --git a/src/views/text_view.rs b/src/views/text_view.rs
index 40e0cb9..0e5913e 100644
--- a/src/views/text_view.rs
+++ b/src/views/text_view.rs
@@ -4,20 +4,21 @@ use std::fmt::Debug;
use crate::{
fonts::{FONTS, FontHandle},
+ log,
parser::Tag,
- render::{Renderer, buffer::Bitmap},
+ render::{RESOLUTION, Renderer, buffer::Bitmap},
views::{Bounds, View},
};
pub struct TextView {
- layout: Layout,
- text: String,
- scale: f32,
- font: FontHandle,
+ pub layout: Layout,
+ pub text: String,
+ pub scale: f32,
+ pub font: FontHandle,
}
impl TextView {
- pub fn new(text: String) -> Self {
+ pub fn new(text: String, scale: f32) -> Self {
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
layout.reset(&LayoutSettings {
@@ -27,7 +28,7 @@ impl TextView {
let mut s = Self {
layout,
text,
- scale: 20.,
+ scale,
font: FontHandle::AtiksonHyperlegibleRegular,
};
@@ -68,13 +69,24 @@ impl View for TextView {
let scaled = new_bitmap.scale(renderer.ratio_x, renderer.ratio_y);
- renderer.img.overlay_bitmap(&scaled, x as usize, y as usize);
+ renderer
+ .img
+ .overlay_bitmap_fg(&scaled, x as usize, y as usize, (255, 255, 255));
// new_bitmap
}
- fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {
- // todo!()
+ fn resize(&mut self, renderer: &mut Renderer, _x: f32, _y: f32, _w: f32, _h: f32) {
+ self.layout.clear();
+
+ let font = (self.font as usize).clone();
+ let e = (renderer.canvas_height as f32 / renderer.actual_height as f32)
+ * (renderer.canvas_width as f32 / renderer.actual_width as f32);
+
+ let scale = self.scale * e;
+
+ self.layout
+ .append(&FONTS, &TextStyle::new(&self.text, scale, font));
}
fn from_tag(
@@ -85,8 +97,20 @@ impl View for TextView {
Self: Sized,
{
assert!(children.is_empty(), "TextView does not support children");
- todo!()
- // Self::new(attributes)
+
+ let text = attributes.get("text").unwrap();
+
+ let mut scale = 30.;
+
+ if let Some(font_size_str) = attributes.get("font_size") {
+ if let Some(font_size) = font_size_str.parse::().ok() {
+ scale = font_size;
+ }
+ }
+
+ let this = Self::new(text.clone(), scale);
+
+ Box::new(this)
}
fn as_any(self: Box) -> Box {
diff --git a/src/views/vertical_layout.rs b/src/views/vertical_layout.rs
index fae9f79..f3dbddf 100644
--- a/src/views/vertical_layout.rs
+++ b/src/views/vertical_layout.rs
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use crate::{
parser::Tag,
+ render::Renderer,
views::{Bounds, View, box_view::BoxView},
};
@@ -47,7 +48,7 @@ impl View for VerticalLayout {
}
}
}
- fn resize(&mut self, x: f32, y: f32, w: f32, h: f32) {}
+ fn resize(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32) {}
fn from_tag(attributes: &HashMap, children: &Vec) -> Box
where