use blinc_layout::text_measure::{TextLayoutOptions, TextMeasurer, TextMetrics};
use blinc_layout::GenericFont as LayoutGenericFont;
use blinc_text::{FontFace, FontRegistry, GenericFont, LayoutOptions, TextLayoutEngine};
use std::sync::{Arc, Mutex};
fn to_text_generic_font(layout_font: LayoutGenericFont) -> GenericFont {
match layout_font {
LayoutGenericFont::System => GenericFont::System,
LayoutGenericFont::Monospace => GenericFont::Monospace,
LayoutGenericFont::Serif => GenericFont::Serif,
LayoutGenericFont::SansSerif => GenericFont::SansSerif,
}
}
pub struct FontTextMeasurer {
font: Arc<Mutex<Option<FontFace>>>,
font_registry: Arc<Mutex<FontRegistry>>,
layout_engine: Mutex<TextLayoutEngine>,
}
impl FontTextMeasurer {
pub fn new() -> Self {
let mut measurer = Self {
font: Arc::new(Mutex::new(None)),
font_registry: blinc_text::global_font_registry(),
layout_engine: Mutex::new(TextLayoutEngine::new()),
};
measurer.load_system_font();
measurer
}
pub fn with_shared_registry(font_registry: Arc<Mutex<FontRegistry>>) -> Self {
Self {
font: Arc::new(Mutex::new(None)),
font_registry,
layout_engine: Mutex::new(TextLayoutEngine::new()),
}
}
fn load_system_font(&mut self) {
for font_path in crate::system_font_paths() {
let path = std::path::Path::new(font_path);
if path.exists() {
if let Ok(data) = std::fs::read(path) {
if let Ok(font) = FontFace::from_data(data) {
*self.font.lock().unwrap() = Some(font);
break;
}
}
}
}
}
pub fn load_font_data(&self, data: Vec<u8>) -> Result<(), blinc_text::TextError> {
let font = FontFace::from_data(data)?;
*self.font.lock().unwrap() = Some(font);
Ok(())
}
fn estimate_size(text: &str, font_size: f32, options: &TextLayoutOptions) -> TextMetrics {
let char_count = text.chars().count() as f32;
let word_count = text.split_whitespace().count().max(1) as f32;
let base_char_width = font_size * 0.55;
let base_width = char_count * base_char_width;
let letter_spacing_total = if char_count > 1.0 {
(char_count - 1.0) * options.letter_spacing
} else {
0.0
};
let word_spacing_total = if word_count > 1.0 {
(word_count - 1.0) * options.word_spacing
} else {
0.0
};
let total_width = base_width + letter_spacing_total + word_spacing_total;
let (width, line_count) = if let Some(max_width) = options.max_width {
if total_width > max_width && max_width > 0.0 {
let lines = (total_width / max_width).ceil() as u32;
(max_width, lines.max(1))
} else {
(total_width, 1)
}
} else {
(total_width, 1)
};
let line_height_px = font_size * options.line_height;
let height = line_height_px * line_count as f32;
TextMetrics {
width,
height,
ascender: font_size * 0.8,
descender: font_size * -0.2,
line_count,
}
}
}
impl Default for FontTextMeasurer {
fn default() -> Self {
Self::new()
}
}
impl TextMeasurer for FontTextMeasurer {
fn measure_with_options(
&self,
text: &str,
font_size: f32,
options: &TextLayoutOptions,
) -> TextMetrics {
let generic_font = to_text_generic_font(options.generic_font);
let registry = self.font_registry.lock().unwrap();
let font = match registry.get_for_render_with_style(
options.font_name.as_deref(),
generic_font,
options.font_weight,
options.italic,
) {
Some(f) => f,
None => return Self::estimate_size(text, font_size, options),
};
drop(registry);
let (max_width, line_break) = if let Some(mw) = options.max_width {
(Some(mw), blinc_text::LineBreakMode::Word)
} else {
(None, blinc_text::LineBreakMode::None)
};
let layout_opts = LayoutOptions {
line_height: options.line_height,
letter_spacing: options.letter_spacing,
max_width,
line_break,
..LayoutOptions::default()
};
let layout_engine = self.layout_engine.lock().unwrap();
let layout = layout_engine.layout(text, &font, font_size, &layout_opts);
let metrics = font.metrics();
let ascender = metrics.ascender_px(font_size);
let descender = metrics.descender_px(font_size);
TextMetrics {
width: layout.width,
height: layout.height,
ascender,
descender,
line_count: layout.lines.len() as u32,
}
}
}
pub fn init_text_measurer() {
let measurer = Arc::new(FontTextMeasurer::new());
blinc_layout::set_text_measurer(measurer);
}
pub fn init_text_measurer_with_registry(font_registry: Arc<Mutex<FontRegistry>>) {
let measurer = Arc::new(FontTextMeasurer::with_shared_registry(font_registry));
blinc_layout::set_text_measurer(measurer);
}