cotis-raylib 0.1.0-alpha

Raylib-backed renderer for Cotis
Documentation
//! Text measurement helpers for layout integration.

use crate::fonts::ScalableFont;
use cotis_defaults::element_configs::text_config::TextConfig;
use cotis_utils::math::Dimensions;
use raylib::ffi::GetFontDefault;
use raylib::prelude::Font;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

/// Measures text dimensions using raylib font glyph metrics.
///
/// Used by [`RendererTextMeasuringProvider`](cotis_utils::text::RendererTextMeasuringProvider) for
/// [`RaylibRender`](crate::renderer::RaylibRender). The `user_data` argument is the renderer's font table.
///
/// # Behavior
///
/// - Missing `font_id` entries fall back to raylib's default font.
/// - Newlines split lines; height is `line_count * font_size` (`line_height` from config is not used).
/// - Letter spacing is applied using the **last line's** character count only.
/// - Glyph advance uses `advanceX` when non-zero, otherwise bounding-box width plus offset.
pub fn raylib_measure_text_inner(
    text: &str,
    config: &TextConfig,
    user_data: Arc<Mutex<HashMap<usize, ScalableFont>>>,
) -> Dimensions {
    let mut text_size = Dimensions {
        width: 0.0,
        height: 0.0,
    };
    let mut max_text_width: f32 = 0.0;
    let mut line_text_width: f32 = 0.0;
    let mut line_char_count: i32 = 0;
    let mut line_count: i32 = 1;

    let fonts_guard = user_data.lock().unwrap();
    let opt_font_to_use = fonts_guard.get(&(config.font_id as usize));

    let def_font;
    let font_to_use: &Font = if let Some(scalable) = opt_font_to_use {
        scalable.get_font_for_size_borrow(config.font_size as usize)
    } else {
        def_font = unsafe { Font::from_raw(GetFontDefault()) };
        &def_font
    };

    let scale_factor = config.font_size / font_to_use.baseSize as f32;

    let glyphs =
        unsafe { std::slice::from_raw_parts(font_to_use.glyphs, font_to_use.glyphCount as usize) };
    let recs =
        unsafe { std::slice::from_raw_parts(font_to_use.recs, font_to_use.glyphCount as usize) };

    for char in text.chars() {
        if char == '\n' {
            max_text_width = max_text_width.max(line_text_width);
            line_text_width = 0.0;
            line_char_count = 0;
            line_count += 1;
            continue;
        }

        let index: usize = (char as usize).saturating_sub(32);
        if index >= font_to_use.glyphCount as usize {
            line_char_count += 1;
            continue;
        }

        if glyphs[index].advanceX != 0 {
            line_text_width += glyphs[index].advanceX as f32;
        } else {
            line_text_width += recs[index].width + glyphs[index].offsetX as f32;
        }
        line_char_count += 1;
    }

    max_text_width = max_text_width.max(line_text_width);

    text_size.width =
        max_text_width * scale_factor + (line_char_count as f32 * config.letter_spacing);

    let line_height = config.font_size;
    text_size.height = line_count as f32 * line_height;

    text_size
}