use crate::display::{Effect, MarkerPosIter, NotReady, TextDisplay};
use crate::fonts::{FontSelector, NoFontMatch};
use crate::format::FormattableText;
use crate::{Align, Direction, GlyphRun, Line, Status, Vec2};
use std::num::NonZeroUsize;
#[derive(Clone, Debug)]
pub struct Text<T: FormattableText + ?Sized> {
bounds: Vec2,
font: FontSelector,
dpem: f32,
wrap_width: f32,
align: (Align, Align),
direction: Direction,
status: Status,
display: TextDisplay,
text: T,
}
impl<T: Default + FormattableText> Default for Text<T> {
#[inline]
fn default() -> Self {
Text::new(T::default())
}
}
impl<T: FormattableText> Text<T> {
#[inline]
pub fn new(text: T) -> Self {
Text {
bounds: Vec2::INFINITY,
font: FontSelector::default(),
dpem: 16.0,
wrap_width: f32::INFINITY,
align: Default::default(),
direction: Direction::default(),
status: Status::New,
text,
display: Default::default(),
}
}
#[inline]
pub fn with_display(mut self, display: TextDisplay) -> Self {
self.display = display;
self
}
#[inline]
pub fn into_parts(self) -> (TextDisplay, T) {
(self.display, self.text)
}
pub fn clone_text(&self) -> T
where
T: Clone,
{
self.text.clone()
}
#[inline]
pub fn take_text(self) -> T {
self.text
}
#[inline]
pub fn text(&self) -> &T {
&self.text
}
pub fn set_text(&mut self, text: T) {
if self.text == text {
return; }
self.text = text;
self.set_max_status(Status::New);
}
}
impl<T: FormattableText + ?Sized> Text<T> {
#[inline]
pub fn str_len(&self) -> usize {
self.as_str().len()
}
#[inline]
pub fn as_str(&self) -> &str {
self.text.as_str()
}
#[inline]
pub fn clone_string(&self) -> String {
self.text.as_str().to_string()
}
#[inline]
pub fn font(&self) -> FontSelector {
self.font
}
#[inline]
pub fn set_font(&mut self, font: FontSelector) {
if font != self.font {
self.font = font;
self.set_max_status(Status::New);
}
}
#[inline]
pub fn font_size(&self) -> f32 {
self.dpem
}
#[inline]
pub fn set_font_size(&mut self, dpem: f32) {
if dpem != self.dpem {
self.dpem = dpem;
self.set_max_status(Status::ResizeLevelRuns);
}
}
#[inline]
pub fn set_font_size_pt(&mut self, pt_size: f32, scale_factor: f32) {
self.set_font_size(pt_size * scale_factor * (96.0 / 72.0));
}
#[inline]
pub fn direction(&self) -> Direction {
self.direction
}
#[inline]
pub fn set_direction(&mut self, direction: Direction) {
if direction != self.direction {
self.direction = direction;
self.set_max_status(Status::New);
}
}
#[inline]
pub fn wrap_width(&self) -> f32 {
self.wrap_width
}
#[inline]
pub fn set_wrap_width(&mut self, wrap_width: f32) {
debug_assert!(wrap_width >= 0.0);
if wrap_width != self.wrap_width {
self.wrap_width = wrap_width;
self.set_max_status(Status::LevelRuns);
}
}
#[inline]
pub fn align(&self) -> (Align, Align) {
self.align
}
#[inline]
pub fn set_align(&mut self, align: (Align, Align)) {
if align != self.align {
if align.0 == self.align.0 {
self.set_max_status(Status::Wrapped);
} else {
self.set_max_status(Status::LevelRuns);
}
self.align = align;
}
}
#[inline]
pub fn bounds(&self) -> Vec2 {
self.bounds
}
#[inline]
pub fn set_bounds(&mut self, bounds: Vec2) {
debug_assert!(bounds.is_finite());
if bounds != self.bounds {
if bounds.0 != self.bounds.0 {
self.set_max_status(Status::LevelRuns);
} else {
self.set_max_status(Status::Wrapped);
}
self.bounds = bounds;
}
}
pub fn text_is_rtl(&self) -> bool {
let cached_is_rtl = match self.line_is_rtl(0) {
Ok(None) => Some(self.direction == Direction::Rtl),
Ok(Some(is_rtl)) => Some(is_rtl),
Err(NotReady) => None,
};
#[cfg(not(debug_assertions))]
if let Some(cached) = cached_is_rtl {
return cached;
}
let is_rtl = self.display.text_is_rtl(self.as_str(), self.direction);
if let Some(cached) = cached_is_rtl {
debug_assert_eq!(cached, is_rtl);
}
is_rtl
}
#[inline]
pub fn effect_tokens(&self) -> &[Effect] {
self.text.effect_tokens()
}
}
impl<T: FormattableText + ?Sized> Text<T> {
#[inline]
pub fn check_status(&self, status: Status) -> Result<(), NotReady> {
if self.status >= status {
Ok(())
} else {
Err(NotReady)
}
}
#[inline]
pub fn is_prepared(&self) -> bool {
self.status == Status::Ready
}
#[inline]
fn set_max_status(&mut self, status: Status) {
self.status = self.status.min(status);
}
#[inline]
pub fn unchecked_display(&self) -> &TextDisplay {
&self.display
}
#[inline]
pub fn display(&self) -> Result<&TextDisplay, NotReady> {
self.check_status(Status::Ready)?;
Ok(self.unchecked_display())
}
#[inline]
pub fn wrapped_display(&self) -> Result<&TextDisplay, NotReady> {
self.check_status(Status::Wrapped)?;
Ok(self.unchecked_display())
}
#[inline]
fn prepare_runs(&mut self) -> Result<(), NoFontMatch> {
match self.status {
Status::New => {
self.display
.prepare_runs(&self.text, self.direction, self.font, self.dpem)?
}
Status::ResizeLevelRuns => self.display.resize_runs(&self.text, self.dpem),
_ => (),
}
self.status = Status::LevelRuns;
Ok(())
}
pub fn measure_width(&mut self, max_width: f32) -> Result<f32, NoFontMatch> {
self.prepare_runs()?;
Ok(self.display.measure_width(max_width))
}
pub fn measure_height(&mut self, max_lines: Option<NonZeroUsize>) -> Result<f32, NoFontMatch> {
if self.status >= Status::Wrapped {
let (tl, br) = self.display.bounding_box();
return Ok(br.1 - tl.1);
}
self.prepare_runs()?;
Ok(self.display.measure_height(self.wrap_width, max_lines))
}
pub fn prepare(&mut self) -> Result<bool, NotReady> {
if self.is_prepared() {
return Ok(false);
} else if !self.bounds.is_finite() {
return Err(NotReady);
}
self.prepare_runs().unwrap();
debug_assert!(self.status >= Status::LevelRuns);
if self.status == Status::LevelRuns {
self.display
.prepare_lines(self.wrap_width, self.bounds.0, self.align.0);
}
if self.status <= Status::Wrapped {
self.display.vertically_align(self.bounds.1, self.align.1);
}
self.status = Status::Ready;
Ok(true)
}
#[inline]
pub fn bounding_box(&self) -> Result<(Vec2, Vec2), NotReady> {
Ok(self.wrapped_display()?.bounding_box())
}
#[inline]
pub fn num_lines(&self) -> Result<usize, NotReady> {
Ok(self.wrapped_display()?.num_lines())
}
#[inline]
pub fn get_line(&self, index: usize) -> Result<Option<&Line>, NotReady> {
Ok(self.wrapped_display()?.get_line(index))
}
#[inline]
pub fn lines(&self) -> Result<impl Iterator<Item = &Line>, NotReady> {
Ok(self.wrapped_display()?.lines())
}
#[inline]
pub fn find_line(
&self,
index: usize,
) -> Result<Option<(usize, std::ops::Range<usize>)>, NotReady> {
Ok(self.wrapped_display()?.find_line(index))
}
#[inline]
pub fn line_is_rtl(&self, line: usize) -> Result<Option<bool>, NotReady> {
Ok(self.wrapped_display()?.line_is_rtl(line))
}
#[inline]
pub fn text_index_nearest(&self, pos: Vec2) -> Result<usize, NotReady> {
Ok(self.display()?.text_index_nearest(pos))
}
#[inline]
pub fn line_index_nearest(&self, line: usize, x: f32) -> Result<Option<usize>, NotReady> {
Ok(self.wrapped_display()?.line_index_nearest(line, x))
}
pub fn text_glyph_pos(&self, index: usize) -> Result<MarkerPosIter, NotReady> {
Ok(self.display()?.text_glyph_pos(index))
}
#[inline]
#[cfg(feature = "num_glyphs")]
pub fn num_glyphs(&self) -> Result<usize, NotReady> {
Ok(self.wrapped_display()?.num_glyphs())
}
pub fn runs<'a>(
&'a self,
offset: Vec2,
effects: &'a [Effect],
) -> Result<impl Iterator<Item = GlyphRun<'a>> + 'a, NotReady> {
Ok(self.display()?.runs(offset, effects))
}
pub fn highlight_range<F>(
&self,
range: std::ops::Range<usize>,
mut f: F,
) -> Result<(), NotReady>
where
F: FnMut(Vec2, Vec2),
{
Ok(self.display()?.highlight_range(range, &mut f))
}
}