mirror of
https://github.com/Astatin3/IntroToWebAuthoring.git
synced 2026-06-08 16:18:01 -06:00
Fonts and text
This commit is contained in:
@@ -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']}
|
||||
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -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,
|
||||
|
||||
@@ -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<Font> = 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,
|
||||
}
|
||||
+2
-24
@@ -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::<web_sys::CanvasRenderingContext2d>()
|
||||
.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)
|
||||
|
||||
@@ -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<u8>,
|
||||
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<u8>, 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+37
-5
@@ -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)
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -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.))
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -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<dyn View> {
|
||||
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)),
|
||||
]))
|
||||
}
|
||||
|
||||
@@ -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.))
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user