maia_wasm/render/engine/
text.rs1use super::CanvasDims;
2use wasm_bindgen::prelude::*;
3use wasm_bindgen::JsCast;
4use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
5
6pub struct TextRender {
7 canvas: HtmlCanvasElement,
8 context: CanvasRenderingContext2d,
9}
10
11pub struct TextsDimensions {
16 pub texture_coordinates: Vec<f32>,
23 pub text_width: f32,
28 pub text_height: f32,
33}
34
35impl TextRender {
36 pub fn new(document: &web_sys::Document) -> Result<TextRender, JsValue> {
37 let canvas = document
38 .create_element("canvas")?
39 .dyn_into::<HtmlCanvasElement>()?;
40 let context = canvas
41 .get_context("2d")?
42 .ok_or("unable to get 2d context")?
43 .dyn_into::<CanvasRenderingContext2d>()?;
44 Ok(TextRender { canvas, context })
45 }
46
47 pub fn canvas(&self) -> &HtmlCanvasElement {
48 &self.canvas
49 }
50
51 pub fn text_width(&self, text: &str, dims: CanvasDims, height_px: u32) -> Result<f32, JsValue> {
52 self.set_font(height_px);
53 let width_px = self.context.measure_text(text)?.width();
54 let width_relative = 2.0 * width_px as f32 / dims.width as f32;
55 Ok(width_relative)
56 }
57
58 fn set_font(&self, height_px: u32) {
59 self.context.set_font(&format!("bold {height_px}px sans"))
60 }
61
62 pub fn render(
63 &self,
64 texts: &[String],
65 dims: CanvasDims,
66 height_px: u32,
67 ) -> Result<TextsDimensions, JsValue> {
68 self.set_font(height_px);
70 let mut max = None;
71 for text in texts.iter() {
72 let w = self.context.measure_text(text)?.width();
73 max = match (max, w) {
74 (Some(z), w) if z >= w => Some(z),
75 _ => Some(w),
76 };
77 }
78 let width_px = max.ok_or("no texts specified")?.ceil() as u32;
79
80 let height_px_margin = height_px + 2;
83 let margin = 0.5 * (1.0 - height_px as f32 / height_px_margin as f32);
84
85 let n = (texts.len() as f32 * width_px as f32 / height_px as f32)
87 .sqrt()
88 .round() as usize;
89 let m = (texts.len() + n - 1) / n;
90 let total_height_px = height_px_margin * n as u32;
91 let total_width_px = width_px * m as u32;
92 self.canvas.set_width(total_width_px);
93 self.canvas.set_height(total_height_px);
94
95 self.context.set_text_align("center");
96 self.context.set_text_baseline("middle");
97 self.set_font(height_px);
99 self.context
100 .clear_rect(0.0, 0.0, total_width_px as f64, total_height_px as f64);
101 self.context.set_fill_style_str("white");
102
103 let mut texture_coords = Vec::with_capacity(8 * texts.len());
107 for (j, text) in texts.iter().enumerate() {
108 let b = j / n;
109 let a = j - b * n;
110 self.context.fill_text(
111 text,
112 (b as f64 + 0.5) * width_px as f64,
113 (a as f64 + 0.5) * height_px_margin as f64,
114 )?;
115 texture_coords.push(b as f32 / m as f32);
116 texture_coords.push(((a + 1) as f32 - margin) / n as f32);
117 texture_coords.push((b + 1) as f32 / m as f32);
118 texture_coords.push(((a + 1) as f32 - margin) / n as f32);
119 texture_coords.push(b as f32 / m as f32);
120 texture_coords.push((a as f32 + margin) / n as f32);
121 texture_coords.push((b + 1) as f32 / m as f32);
122 texture_coords.push((a as f32 + margin) / n as f32);
123 }
124
125 let width_relative = 2.0 * width_px as f32 / dims.width as f32;
126 let height_relative = 2.0 * height_px as f32 / dims.height as f32;
127 Ok(TextsDimensions {
128 texture_coordinates: texture_coords,
129 text_width: width_relative,
130 text_height: height_relative,
131 })
132 }
133}