use core::ptr::NonNull;
use std::ffi::{CString, c_void};
use crate::ffi::{
noesis_base_component_release, noesis_formatted_text_create, noesis_formatted_text_get_bounds,
noesis_formatted_text_get_glyph_position, noesis_formatted_text_get_line_info,
noesis_formatted_text_get_num_lines, noesis_formatted_text_has_visual_brush,
noesis_formatted_text_hit_test, noesis_formatted_text_is_empty, noesis_formatted_text_measure,
};
pub mod font_weight {
pub const THIN: i32 = 100;
pub const EXTRA_LIGHT: i32 = 200;
pub const LIGHT: i32 = 300;
pub const SEMI_LIGHT: i32 = 350;
pub const NORMAL: i32 = 400;
pub const MEDIUM: i32 = 500;
pub const SEMI_BOLD: i32 = 600;
pub const BOLD: i32 = 700;
pub const EXTRA_BOLD: i32 = 800;
pub const BLACK: i32 = 900;
pub const EXTRA_BLACK: i32 = 950;
}
pub mod font_style {
pub const NORMAL: i32 = 0;
pub const OBLIQUE: i32 = 1;
pub const ITALIC: i32 = 2;
}
pub mod font_stretch {
pub const ULTRA_CONDENSED: i32 = 1;
pub const EXTRA_CONDENSED: i32 = 2;
pub const CONDENSED: i32 = 3;
pub const SEMI_CONDENSED: i32 = 4;
pub const NORMAL: i32 = 5;
pub const SEMI_EXPANDED: i32 = 6;
pub const EXPANDED: i32 = 7;
pub const EXTRA_EXPANDED: i32 = 8;
pub const ULTRA_EXPANDED: i32 = 9;
}
pub mod text_alignment {
pub const LEFT: i32 = 0;
pub const RIGHT: i32 = 1;
pub const CENTER: i32 = 2;
pub const JUSTIFY: i32 = 3;
}
pub mod text_trimming {
pub const NONE: i32 = 0;
pub const CHARACTER_ELLIPSIS: i32 = 1;
pub const WORD_ELLIPSIS: i32 = 2;
}
pub mod text_wrapping {
pub const NO_WRAP: i32 = 0;
pub const WRAP: i32 = 1;
pub const WRAP_WITH_OVERFLOW: i32 = 2;
}
pub mod line_stacking_strategy {
pub const BLOCK_LINE_HEIGHT: i32 = 0;
pub const MAX_HEIGHT: i32 = 1;
}
pub mod flow_direction {
pub const LEFT_TO_RIGHT: i32 = 0;
pub const RIGHT_TO_LEFT: i32 = 1;
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct LineInfo {
pub num_glyphs: u32,
pub height: f32,
pub baseline: f32,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct HitTest {
pub index: u32,
pub is_inside: bool,
pub is_trailing: bool,
}
#[derive(Clone, Debug)]
pub struct Builder {
text: String,
font_family: String,
weight: i32,
stretch: i32,
style: i32,
font_size: f32,
flow_direction: i32,
max_width: f32,
max_height: f32,
line_height: f32,
text_alignment: i32,
text_trimming: i32,
foreground: Option<[f32; 4]>,
}
impl Builder {
#[must_use]
pub fn new(text: impl Into<String>, font_family: impl Into<String>, font_size: f32) -> Self {
Self {
text: text.into(),
font_family: font_family.into(),
weight: font_weight::NORMAL,
stretch: font_stretch::NORMAL,
style: font_style::NORMAL,
font_size,
flow_direction: flow_direction::LEFT_TO_RIGHT,
max_width: -1.0,
max_height: -1.0,
line_height: 0.0,
text_alignment: text_alignment::LEFT,
text_trimming: text_trimming::WORD_ELLIPSIS,
foreground: None,
}
}
#[must_use]
pub fn weight(mut self, weight: i32) -> Self {
self.weight = weight;
self
}
#[must_use]
pub fn stretch(mut self, stretch: i32) -> Self {
self.stretch = stretch;
self
}
#[must_use]
pub fn style(mut self, style: i32) -> Self {
self.style = style;
self
}
#[must_use]
pub fn flow_direction(mut self, flow_direction: i32) -> Self {
self.flow_direction = flow_direction;
self
}
#[must_use]
pub fn max_width(mut self, max_width: f32) -> Self {
self.max_width = max_width;
self
}
#[must_use]
pub fn max_height(mut self, max_height: f32) -> Self {
self.max_height = max_height;
self
}
#[must_use]
pub fn line_height(mut self, line_height: f32) -> Self {
self.line_height = line_height;
self
}
#[must_use]
pub fn text_alignment(mut self, text_alignment: i32) -> Self {
self.text_alignment = text_alignment;
self
}
#[must_use]
pub fn text_trimming(mut self, text_trimming: i32) -> Self {
self.text_trimming = text_trimming;
self
}
#[must_use]
pub fn foreground(mut self, rgba: [f32; 4]) -> Self {
self.foreground = Some(rgba);
self
}
#[must_use]
pub fn build(&self) -> FormattedText {
let text = CString::new(self.text.as_str()).expect("text contained interior NUL");
let family =
CString::new(self.font_family.as_str()).expect("font_family contained interior NUL");
let fg_ptr = self
.foreground
.as_ref()
.map_or(core::ptr::null(), |rgba| rgba.as_ptr());
let ptr = unsafe {
noesis_formatted_text_create(
text.as_ptr(),
family.as_ptr(),
self.weight,
self.stretch,
self.style,
self.font_size,
self.flow_direction,
self.max_width,
self.max_height,
self.line_height,
self.text_alignment,
self.text_trimming,
fg_ptr,
)
};
FormattedText {
ptr: NonNull::new(ptr).expect("noesis_formatted_text_create returned null"),
}
}
}
pub struct FormattedText {
ptr: NonNull<c_void>,
}
unsafe impl Send for FormattedText {}
impl FormattedText {
#[must_use]
pub fn builder(
text: impl Into<String>,
font_family: impl Into<String>,
font_size: f32,
) -> Builder {
Builder::new(text, font_family, font_size)
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub fn bounds(&self) -> [f32; 4] {
let mut out = [0.0f32; 4];
unsafe {
noesis_formatted_text_get_bounds(self.ptr.as_ptr(), out.as_mut_ptr());
}
out
}
#[must_use]
pub fn width(&self) -> f32 {
self.bounds()[2]
}
#[must_use]
pub fn height(&self) -> f32 {
self.bounds()[3]
}
#[must_use]
pub fn num_lines(&self) -> u32 {
let n = unsafe { noesis_formatted_text_get_num_lines(self.ptr.as_ptr()) };
u32::try_from(n).unwrap_or(0)
}
#[must_use]
pub fn line_info(&self, index: u32) -> Option<LineInfo> {
let mut num_glyphs = 0u32;
let mut height = 0.0f32;
let mut baseline = 0.0f32;
let ok = unsafe {
noesis_formatted_text_get_line_info(
self.ptr.as_ptr(),
index,
&mut num_glyphs,
&mut height,
&mut baseline,
)
};
ok.then_some(LineInfo {
num_glyphs,
height,
baseline,
})
}
#[must_use]
pub fn is_empty(&self) -> bool {
let mut out = false;
unsafe {
noesis_formatted_text_is_empty(self.ptr.as_ptr(), &mut out);
}
out
}
#[must_use]
pub fn has_visual_brush(&self) -> bool {
let mut out = false;
unsafe {
noesis_formatted_text_has_visual_brush(self.ptr.as_ptr(), &mut out);
}
out
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn measure(
&self,
alignment: i32,
wrapping: i32,
trimming: i32,
max_width: f32,
max_height: f32,
line_height: f32,
line_stacking: i32,
flow_direction: i32,
) -> (f32, f32) {
let mut w = 0.0f32;
let mut h = 0.0f32;
unsafe {
noesis_formatted_text_measure(
self.ptr.as_ptr(),
alignment,
wrapping,
trimming,
max_width,
max_height,
line_height,
line_stacking,
flow_direction,
&mut w,
&mut h,
);
}
(w, h)
}
#[must_use]
pub fn glyph_position(&self, ch_index: u32, after_char: bool) -> (f32, f32) {
let mut x = 0.0f32;
let mut y = 0.0f32;
unsafe {
noesis_formatted_text_get_glyph_position(
self.ptr.as_ptr(),
ch_index,
after_char,
&mut x,
&mut y,
);
}
(x, y)
}
#[must_use]
pub fn hit_test(&self, x: f32, y: f32) -> HitTest {
let mut index = 0u32;
let mut is_inside = false;
let mut is_trailing = false;
unsafe {
noesis_formatted_text_hit_test(
self.ptr.as_ptr(),
x,
y,
&mut index,
&mut is_inside,
&mut is_trailing,
);
}
HitTest {
index,
is_inside,
is_trailing,
}
}
}
impl Drop for FormattedText {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}