Skip to main content

cotis_raylib/
functions_raylib.rs

1//! Text measurement helpers for layout integration.
2
3use crate::fonts::ScalableFont;
4use cotis_defaults::element_configs::text_config::TextConfig;
5use cotis_utils::math::Dimensions;
6use raylib::ffi::GetFontDefault;
7use raylib::prelude::Font;
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10
11/// Measures text dimensions using raylib font glyph metrics.
12///
13/// Used by [`RendererTextMeasuringProvider`](cotis_utils::text::RendererTextMeasuringProvider) for
14/// [`RaylibRender`](crate::renderer::RaylibRender). The `user_data` argument is the renderer's font table.
15///
16/// # Behavior
17///
18/// - Missing `font_id` entries fall back to raylib's default font.
19/// - Newlines split lines; height is `line_count * font_size` (`line_height` from config is not used).
20/// - Letter spacing is applied using the **last line's** character count only.
21/// - Glyph advance uses `advanceX` when non-zero, otherwise bounding-box width plus offset.
22pub fn raylib_measure_text_inner(
23    text: &str,
24    config: &TextConfig,
25    user_data: Arc<Mutex<HashMap<usize, ScalableFont>>>,
26) -> Dimensions {
27    let mut text_size = Dimensions {
28        width: 0.0,
29        height: 0.0,
30    };
31    let mut max_text_width: f32 = 0.0;
32    let mut line_text_width: f32 = 0.0;
33    let mut line_char_count: i32 = 0;
34    let mut line_count: i32 = 1;
35
36    let fonts_guard = user_data.lock().unwrap();
37    let opt_font_to_use = fonts_guard.get(&(config.font_id as usize));
38
39    let def_font;
40    let font_to_use: &Font = if let Some(scalable) = opt_font_to_use {
41        scalable.get_font_for_size_borrow(config.font_size as usize)
42    } else {
43        def_font = unsafe { Font::from_raw(GetFontDefault()) };
44        &def_font
45    };
46
47    let scale_factor = config.font_size / font_to_use.baseSize as f32;
48
49    let glyphs =
50        unsafe { std::slice::from_raw_parts(font_to_use.glyphs, font_to_use.glyphCount as usize) };
51    let recs =
52        unsafe { std::slice::from_raw_parts(font_to_use.recs, font_to_use.glyphCount as usize) };
53
54    for char in text.chars() {
55        if char == '\n' {
56            max_text_width = max_text_width.max(line_text_width);
57            line_text_width = 0.0;
58            line_char_count = 0;
59            line_count += 1;
60            continue;
61        }
62
63        let index: usize = (char as usize).saturating_sub(32);
64        if index >= font_to_use.glyphCount as usize {
65            line_char_count += 1;
66            continue;
67        }
68
69        if glyphs[index].advanceX != 0 {
70            line_text_width += glyphs[index].advanceX as f32;
71        } else {
72            line_text_width += recs[index].width + glyphs[index].offsetX as f32;
73        }
74        line_char_count += 1;
75    }
76
77    max_text_width = max_text_width.max(line_text_width);
78
79    text_size.width =
80        max_text_width * scale_factor + (line_char_count as f32 * config.letter_spacing);
81
82    let line_height = config.font_size;
83    text_size.height = line_count as f32 * line_height;
84
85    text_size
86}