use super::FontId;
use crate::sys;
use std::cell::UnsafeCell;
fn assert_finite_f32(caller: &str, name: &str, value: f32) {
assert!(value.is_finite(), "{caller} {name} must be finite");
}
fn assert_non_negative_f32(caller: &str, name: &str, value: f32) {
assert_finite_f32(caller, name, value);
assert!(value >= 0.0, "{caller} {name} must be non-negative");
}
fn assert_positive_f32(caller: &str, name: &str, value: f32) {
assert_finite_f32(caller, name, value);
assert!(value > 0.0, "{caller} {name} must be positive");
}
#[repr(transparent)]
#[derive(Debug)]
pub struct Font(UnsafeCell<sys::ImFont>);
const _: [(); std::mem::size_of::<sys::ImFont>()] = [(); std::mem::size_of::<Font>()];
const _: [(); std::mem::align_of::<sys::ImFont>()] = [(); std::mem::align_of::<Font>()];
impl Font {
pub(crate) unsafe fn from_raw<'a>(raw: *const sys::ImFont) -> &'a Self {
unsafe { &*(raw as *const Self) }
}
pub(crate) unsafe fn from_raw_mut<'a>(raw: *mut sys::ImFont) -> &'a mut Self {
unsafe { &mut *(raw as *mut Self) }
}
pub fn id(&self) -> FontId {
FontId(self.raw() as *const sys::ImFont)
}
pub fn raw(&self) -> *mut sys::ImFont {
self.0.get()
}
#[doc(alias = "IsGlyphInFont")]
pub fn is_glyph_in_font(&self, c: char) -> bool {
let codepoint = c as u32;
if std::mem::size_of::<sys::ImWchar>() == 2 && codepoint > 0xFFFF {
return false;
}
unsafe { sys::ImFont_IsGlyphInFont(self.raw(), codepoint as sys::ImWchar) }
}
#[doc(alias = "CalcTextSizeA")]
pub fn calc_text_size(
&self,
size: f32,
max_width: f32,
wrap_width: f32,
text: &str,
) -> [f32; 2] {
assert_positive_f32("Font::calc_text_size()", "size", size);
assert_non_negative_f32("Font::calc_text_size()", "max_width", max_width);
assert_non_negative_f32("Font::calc_text_size()", "wrap_width", wrap_width);
unsafe {
let text_start = text.as_ptr() as *const std::os::raw::c_char;
let text_end = text_start.add(text.len());
let mut out_remaining: *const std::os::raw::c_char = std::ptr::null();
let out = sys::ImFont_CalcTextSizeA(
self.raw(),
size,
max_width,
wrap_width,
text_start,
text_end,
&mut out_remaining,
);
[out.x, out.y]
}
}
#[doc(alias = "CalcWordWrapPosition")]
pub fn calc_word_wrap_position(&self, size: f32, text: &str, wrap_width: f32) -> usize {
assert_positive_f32("Font::calc_word_wrap_position()", "size", size);
assert_non_negative_f32("Font::calc_word_wrap_position()", "wrap_width", wrap_width);
unsafe {
let text_start = text.as_ptr() as *const std::os::raw::c_char;
let text_end = text_start.add(text.len());
let wrap_pos = sys::ImFont_CalcWordWrapPosition(
self.raw(),
size,
text_start,
text_end,
wrap_width,
);
let off = wrap_pos.offset_from(text_start);
if off <= 0 {
0
} else {
(off as usize).min(text.len())
}
}
}
#[doc(alias = "ClearOutputData")]
pub fn clear_output_data(&mut self) {
unsafe { sys::ImFont_ClearOutputData(self.raw()) }
}
#[doc(alias = "AddRemapChar")]
pub fn add_remap_char(&mut self, from: char, to: char) {
let from = from as u32;
let to = to as u32;
if std::mem::size_of::<sys::ImWchar>() == 2 && (from > 0xFFFF || to > 0xFFFF) {
return;
}
unsafe { sys::ImFont_AddRemapChar(self.raw(), from as sys::ImWchar, to as sys::ImWchar) }
}
#[doc(alias = "IsGlyphRangeUnused")]
pub fn is_glyph_range_unused(&self, c_begin: u32, c_last: u32) -> bool {
const IMWCHAR_MAX: u32 = if std::mem::size_of::<sys::ImWchar>() == 2 {
0xFFFF
} else {
0x10FFFF
};
if c_begin > IMWCHAR_MAX {
return true;
}
let c_last = c_last.min(IMWCHAR_MAX);
unsafe {
sys::ImFont_IsGlyphRangeUnused(
self.raw(),
c_begin as sys::ImWchar,
c_last as sys::ImWchar,
)
}
}
}
#[cfg(test)]
mod tests {
fn setup_context() -> crate::Context {
let mut ctx = crate::Context::create();
let _ = ctx.font_atlas_mut().build();
ctx.io_mut().set_display_size([128.0, 128.0]);
ctx.io_mut().set_delta_time(1.0 / 60.0);
ctx
}
#[test]
fn calc_text_size_validates_runtime_sizes_before_ffi() {
let mut ctx = setup_context();
let ui = ctx.frame();
let font = ui.current_font();
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = font.calc_text_size(0.0, f32::MAX, 0.0, "hello");
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = font.calc_text_size(13.0, f32::NAN, 0.0, "hello");
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = font.calc_text_size(13.0, f32::MAX, -1.0, "hello");
}))
.is_err()
);
}
#[test]
fn calc_word_wrap_position_validates_runtime_sizes_before_ffi() {
let mut ctx = setup_context();
let ui = ctx.frame();
let font = ui.current_font();
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = font.calc_word_wrap_position(f32::INFINITY, "hello", 32.0);
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = font.calc_word_wrap_position(13.0, "hello", -1.0);
}))
.is_err()
);
}
}