use alloc::ffi::CString;
use alloc::string::String;
use core::ops::Range;
use crate::color::Rgb;
use crate::error::{Error, Result};
use crate::gradient::{LinearGradient, RadialGradient};
use crate::paint::{Paint, Point};
use thorvg_sys as sys;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TextWrap {
None,
Character,
Word,
Smart,
Ellipsis,
}
impl TextWrap {
fn to_raw(self) -> sys::Tvg_Text_Wrap {
match self {
TextWrap::None => sys::Tvg_Text_Wrap::TVG_TEXT_WRAP_NONE,
TextWrap::Character => sys::Tvg_Text_Wrap::TVG_TEXT_WRAP_CHARACTER,
TextWrap::Word => sys::Tvg_Text_Wrap::TVG_TEXT_WRAP_WORD,
TextWrap::Smart => sys::Tvg_Text_Wrap::TVG_TEXT_WRAP_SMART,
TextWrap::Ellipsis => sys::Tvg_Text_Wrap::TVG_TEXT_WRAP_ELLIPSIS,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TextMetrics {
pub ascent: f32,
pub descent: f32,
pub linegap: f32,
pub advance: f32,
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphMetrics {
pub advance: f32,
pub bearing: f32,
pub min: Point,
pub max: Point,
}
pub struct Text<'eng> {
raw: sys::Tvg_Paint,
owned: bool,
_engine: core::marker::PhantomData<&'eng ()>,
}
unsafe impl Send for Text<'_> {}
impl Text<'_> {
pub(crate) fn new() -> Result<Self> {
let raw = unsafe { sys::tvg_text_new() };
if raw.is_null() {
return Err(Error::FailedAllocation);
}
Ok(Self {
raw,
owned: true,
_engine: core::marker::PhantomData,
})
}
pub fn set_font(&mut self, name: &str) -> Result<()> {
let c_name = CString::new(name)?;
Error::from_raw(unsafe { sys::tvg_text_set_font(self.raw, c_name.as_ptr()) })
}
pub fn set_size(&mut self, size: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_set_size(self.raw, size) })
}
pub fn set_text(&mut self, text: &str) -> Result<()> {
let c_text = CString::new(text)?;
Error::from_raw(unsafe { sys::tvg_text_set_text(self.raw, c_text.as_ptr()) })
}
pub fn text(&self) -> Option<String> {
let ptr = unsafe { sys::tvg_text_get_text(self.raw) };
if ptr.is_null() {
None
} else {
Some(
unsafe { core::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned(),
)
}
}
pub fn set_color(&mut self, color: Rgb) -> Result<()> {
let Rgb { r, g, b } = color;
Error::from_raw(unsafe { sys::tvg_text_set_color(self.raw, r, g, b) })
}
pub fn set_align(&mut self, x: f32, y: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_align(self.raw, x, y) })
}
pub fn set_layout(&mut self, w: f32, h: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_layout(self.raw, w, h) })
}
pub fn set_italic(&mut self, shear: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_set_italic(self.raw, shear) })
}
pub fn set_wrap(&mut self, mode: TextWrap) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_wrap_mode(self.raw, mode.to_raw()) })
}
pub fn line_count(&self) -> u32 {
unsafe { sys::tvg_text_line_count(self.raw) }
}
pub fn set_spacing(&mut self, letter: f32, line: f32) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_spacing(self.raw, letter, line) })
}
pub fn set_outline(&mut self, width: f32, color: Rgb) -> Result<()> {
let Rgb { r, g, b } = color;
Error::from_raw(unsafe { sys::tvg_text_set_outline(self.raw, width, r, g, b) })
}
pub fn set_linear_gradient(&mut self, grad: LinearGradient<'_>) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_set_gradient(self.raw, grad.into_raw()) })
}
pub fn set_radial_gradient(&mut self, grad: RadialGradient<'_>) -> Result<()> {
Error::from_raw(unsafe { sys::tvg_text_set_gradient(self.raw, grad.into_raw()) })
}
pub fn text_metrics(&self) -> Result<TextMetrics> {
let mut m = sys::Tvg_Text_Metrics {
ascent: 0.0,
descent: 0.0,
linegap: 0.0,
advance: 0.0,
};
Error::from_raw(unsafe { sys::tvg_text_get_text_metrics(self.raw, &raw mut m) })?;
Ok(TextMetrics {
ascent: m.ascent,
descent: m.descent,
linegap: m.linegap,
advance: m.advance,
})
}
pub fn glyph_metrics(&self, ch: &str) -> Result<GlyphMetrics> {
let c_ch = CString::new(ch)?;
let mut m = zeroed_glyph_metrics();
Error::from_raw(unsafe {
sys::tvg_text_get_glyph_metrics(self.raw, c_ch.as_ptr(), &raw mut m, core::ptr::null_mut())
})?;
Ok(glyph_metrics_from_raw(&m))
}
pub fn glyph_metrics_iter(&self, text: &str) -> Result<GlyphMetricsIter<'_>> {
let text = CString::new(text)?;
let len = text.as_bytes().len();
Ok(GlyphMetricsIter {
raw: self.raw,
text,
offset: 0,
len,
done: false,
_borrow: core::marker::PhantomData,
})
}
}
fn zeroed_glyph_metrics() -> sys::Tvg_Glyph_Metrics {
sys::Tvg_Glyph_Metrics {
advance: 0.0,
bearing: 0.0,
min: sys::Tvg_Point { x: 0.0, y: 0.0 },
max: sys::Tvg_Point { x: 0.0, y: 0.0 },
}
}
fn glyph_metrics_from_raw(m: &sys::Tvg_Glyph_Metrics) -> GlyphMetrics {
GlyphMetrics {
advance: m.advance,
bearing: m.bearing,
min: Point {
x: m.min.x,
y: m.min.y,
},
max: Point {
x: m.max.x,
y: m.max.y,
},
}
}
pub struct GlyphMetricsIter<'t> {
raw: sys::Tvg_Paint,
text: CString,
offset: usize,
len: usize,
done: bool,
_borrow: core::marker::PhantomData<&'t Text<'t>>,
}
impl Iterator for GlyphMetricsIter<'_> {
type Item = Result<(GlyphMetrics, Range<usize>)>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.offset >= self.len {
return None;
}
let start = self.offset;
let base = self.text.as_ptr();
let cur = unsafe { base.add(start) };
let mut m = zeroed_glyph_metrics();
let mut next: *const core::ffi::c_char = core::ptr::null();
let res = unsafe {
sys::tvg_text_get_glyph_metrics(self.raw, cur, &raw mut m, &raw mut next)
};
if let Err(e) = Error::from_raw(res) {
self.done = true;
return Some(Err(e));
}
let end = if next.is_null() {
self.len
} else {
let diff = unsafe { next.offset_from(base) };
let Ok(end) = usize::try_from(diff) else {
self.done = true;
return None;
};
end
};
if end <= start || end > self.len {
self.done = true;
return None;
}
self.offset = end;
Some(Ok((glyph_metrics_from_raw(&m), start..end)))
}
}
impl crate::paint::sealed::Sealed for Text<'_> {}
impl Paint for Text<'_> {
fn raw(&self) -> sys::Tvg_Paint {
self.raw
}
fn into_raw(mut self) -> sys::Tvg_Paint {
self.owned = false;
self.raw
}
unsafe fn from_raw_paint(raw: sys::Tvg_Paint) -> Self {
Self {
raw,
owned: true,
_engine: core::marker::PhantomData,
}
}
}
impl Drop for Text<'_> {
fn drop(&mut self) {
if self.owned {
unsafe {
sys::tvg_paint_rel(self.raw);
}
}
}
}
impl core::fmt::Debug for Text<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Text").finish_non_exhaustive()
}
}