slint/sixtyfps_runtime/corelib/font/canvasfont.rs
Simon Hausmann 49edd69a3a Fix multi-byte handling hit testing
Return a byte offset within the string for the hit test in TextInput.
2020-09-22 18:43:00 +02:00

136 lines
3.9 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
use std::hash::Hash;
#[derive(Clone)]
struct GlyphMetrics {
advance: f32,
}
pub struct Font {
pub pixel_size: f32,
font_family: String,
text_canvas: web_sys::HtmlCanvasElement,
canvas_context: web_sys::CanvasRenderingContext2d,
}
impl Font {
pub fn text_width(&self, text: &str) -> f32 {
let text_metrics = self.canvas_context.measure_text(text).unwrap();
text_metrics.width() as _
}
pub fn text_offset_for_x_position(&self, text: &str, x: f32) -> usize {
// This is pretty cruel ...
let mut last_width = 0.;
for offset in text.char_indices().map(|(offset, _)| offset) {
let new_width = self.text_width(&text[0..offset]);
if new_width > last_width {
let advance = new_width - last_width;
if last_width + advance / 2. >= x {
return offset;
}
last_width = new_width;
}
}
text.len()
}
pub fn height(&self) -> f32 {
self.pixel_size
}
pub fn render_text<'a>(&'a self, text: &str) -> &'a web_sys::HtmlCanvasElement {
let text_metrics = self.canvas_context.measure_text(text).unwrap();
self.text_canvas.set_width(text_metrics.width() as _);
self.text_canvas.set_height(self.pixel_size as _);
self.text_canvas
.style()
.set_property("width", &format!("{}px", text_metrics.width()))
.unwrap();
self.text_canvas.style().set_property("height", &format!("{}px", self.pixel_size)).unwrap();
// Re-apply after resize :(
self.canvas_context.set_font(&format!("{}px \"{}\"", self.pixel_size, self.font_family));
self.canvas_context.set_text_align("center");
self.canvas_context.set_text_baseline("middle");
self.canvas_context.set_fill_style(&wasm_bindgen::JsValue::from_str("transparent"));
self.canvas_context.fill_rect(
0.,
0.,
self.text_canvas.width() as _,
self.text_canvas.height() as _,
);
self.canvas_context.set_fill_style(&wasm_bindgen::JsValue::from_str("rgb(0, 0, 0)"));
self.canvas_context
.fill_text(
text,
(self.text_canvas.width() / 2) as _,
(self.text_canvas.height() / 2) as _,
)
.unwrap();
&self.text_canvas
}
}
#[derive(Clone)]
pub struct FontHandle(String);
impl Hash for FontHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl PartialEq for FontHandle {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl Eq for FontHandle {}
impl FontHandle {
pub fn load(&self, pixel_size: f32) -> Result<Font, ()> {
let font_family = &self.0;
let text_canvas = web_sys::window()
.unwrap()
.document()
.unwrap()
.create_element("canvas")
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
use wasm_bindgen::JsCast;
let canvas_context = text_canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
canvas_context.set_font(&format!("{}px \"{}\"", pixel_size, font_family));
Ok(Font { pixel_size, font_family: font_family.clone(), text_canvas, canvas_context })
}
pub fn new_from_match(family: &str) -> Self {
Self(family.to_owned())
}
}