use super::CanvasDims;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
pub struct TextRender {
canvas: HtmlCanvasElement,
context: CanvasRenderingContext2d,
}
pub struct TextsDimensions {
pub texture_coordinates: Vec<f32>,
pub text_width: f32,
pub text_height: f32,
}
impl TextRender {
pub fn new(document: &web_sys::Document) -> Result<TextRender, JsValue> {
let canvas = document
.create_element("canvas")?
.dyn_into::<HtmlCanvasElement>()?;
let context = canvas
.get_context("2d")?
.ok_or("unable to get 2d context")?
.dyn_into::<CanvasRenderingContext2d>()?;
Ok(TextRender { canvas, context })
}
pub fn canvas(&self) -> &HtmlCanvasElement {
&self.canvas
}
pub fn text_width(&self, text: &str, dims: CanvasDims, height_px: u32) -> Result<f32, JsValue> {
self.set_font(height_px);
let width_px = self.context.measure_text(text)?.width();
let width_relative = 2.0 * width_px as f32 / dims.width as f32;
Ok(width_relative)
}
fn set_font(&self, height_px: u32) {
self.context.set_font(&format!("bold {height_px}px sans"))
}
pub fn render(
&self,
texts: &[String],
dims: CanvasDims,
height_px: u32,
) -> Result<TextsDimensions, JsValue> {
self.set_font(height_px);
let mut max = None;
for text in texts.iter() {
let w = self.context.measure_text(text)?.width();
max = match (max, w) {
(Some(z), w) if z >= w => Some(z),
_ => Some(w),
};
}
let width_px = max.ok_or("no texts specified")?.ceil() as u32;
let height_px_margin = height_px + 2;
let margin = 0.5 * (1.0 - height_px as f32 / height_px_margin as f32);
let n = (texts.len() as f32 * width_px as f32 / height_px as f32)
.sqrt()
.round() as usize;
let m = (texts.len() + n - 1) / n;
let total_height_px = height_px_margin * n as u32;
let total_width_px = width_px * m as u32;
self.canvas.set_width(total_width_px);
self.canvas.set_height(total_height_px);
self.context.set_text_align("center");
self.context.set_text_baseline("middle");
self.set_font(height_px);
self.context
.clear_rect(0.0, 0.0, total_width_px as f64, total_height_px as f64);
self.context.set_fill_style(&JsValue::from_str("white"));
let mut texture_coords = Vec::with_capacity(8 * texts.len());
for (j, text) in texts.iter().enumerate() {
let b = j / n;
let a = j - b * n;
self.context.fill_text(
text,
(b as f64 + 0.5) * width_px as f64,
(a as f64 + 0.5) * height_px_margin as f64,
)?;
texture_coords.push(b as f32 / m as f32);
texture_coords.push(((a + 1) as f32 - margin) / n as f32);
texture_coords.push((b + 1) as f32 / m as f32);
texture_coords.push(((a + 1) as f32 - margin) / n as f32);
texture_coords.push(b as f32 / m as f32);
texture_coords.push((a as f32 + margin) / n as f32);
texture_coords.push((b + 1) as f32 / m as f32);
texture_coords.push((a as f32 + margin) / n as f32);
}
let width_relative = 2.0 * width_px as f32 / dims.width as f32;
let height_relative = 2.0 * height_px as f32 / dims.height as f32;
Ok(TextsDimensions {
texture_coordinates: texture_coords,
text_width: width_relative,
text_height: height_relative,
})
}
}