use smallvec::SmallVec;
use crate::conv::to_usize;
use crate::{shaper, Action, Vec2};
mod glyph_pos;
mod text_runs;
mod wrap_lines;
pub use glyph_pos::{Effect, EffectFlags, MarkerPos, MarkerPosIter};
pub(crate) use text_runs::RunSpecial;
use wrap_lines::{Line, RunPart};
#[derive(Clone, Copy, Default, Debug, thiserror::Error)]
#[error("not ready")]
pub struct NotReady;
#[derive(Clone, Debug)]
pub struct TextDisplay {
runs: SmallVec<[shaper::GlyphRun; 1]>,
pub(crate) action: Action,
wrapped_runs: SmallVec<[RunPart; 1]>,
lines: SmallVec<[Line; 1]>,
#[cfg(feature = "num_glyphs")]
num_glyphs: u32,
l_bound: f32,
r_bound: f32,
}
#[cfg(test)]
#[test]
fn size_of_elts() {
use std::mem::size_of;
assert_eq!(size_of::<SmallVec<[u8; 0]>>(), 32);
assert_eq!(size_of::<shaper::GlyphRun>(), 128);
assert_eq!(size_of::<RunPart>(), 24);
assert_eq!(size_of::<Line>(), 24);
}
impl Default for TextDisplay {
fn default() -> Self {
TextDisplay {
runs: Default::default(),
action: Action::All, wrapped_runs: Default::default(),
lines: Default::default(),
#[cfg(feature = "num_glyphs")]
num_glyphs: 0,
l_bound: 0.0,
r_bound: 0.0,
}
}
}
impl TextDisplay {
#[inline]
pub fn required_action(&self) -> Action {
self.action
}
#[inline]
pub fn require_action(&mut self, action: Action) {
self.action = self.action.max(action);
}
pub fn num_lines(&self) -> Result<usize, NotReady> {
if !self.action.is_ready() {
return Err(NotReady);
}
Ok(self.lines.len())
}
pub fn bounding_box(&self) -> Result<(Vec2, Vec2), NotReady> {
if self.action > Action::VAlign {
return Err(NotReady);
}
if self.lines.is_empty() {
return Ok((Vec2::ZERO, Vec2::ZERO));
}
let top = self.lines.first().unwrap().top;
let bottom = self.lines.last().unwrap().bottom;
Ok((Vec2(self.l_bound, top), Vec2(self.r_bound, bottom)))
}
pub fn find_line(
&self,
index: usize,
) -> Result<Option<(usize, std::ops::Range<usize>)>, NotReady> {
if !self.action.is_ready() {
return Err(NotReady);
}
let mut first = None;
for (n, line) in self.lines.iter().enumerate() {
if line.text_range.end() == index {
first = Some((n, line.text_range.to_std()));
} else if line.text_range.includes(index) {
return Ok(Some((n, line.text_range.to_std())));
}
}
Ok(first)
}
pub fn line_range(&self, line: usize) -> Result<Option<std::ops::Range<usize>>, NotReady> {
if !self.action.is_ready() {
return Err(NotReady);
}
Ok(self.lines.get(line).map(|line| line.text_range.to_std()))
}
pub fn line_is_rtl(&self, line: usize) -> Result<Option<bool>, NotReady> {
if !self.action.is_ready() {
return Err(NotReady);
}
if let Some(line) = self.lines.get(line) {
let first_run = line.run_range.start();
let glyph_run = to_usize(self.wrapped_runs[first_run].glyph_run);
Ok(Some(self.runs[glyph_run].level.is_rtl()))
} else {
Ok(None)
}
}
pub fn text_index_nearest(&self, pos: Vec2) -> Result<usize, NotReady> {
if !self.action.is_ready() {
return Err(NotReady);
}
let mut n = 0;
for (i, line) in self.lines.iter().enumerate() {
if line.top > pos.1 {
break;
}
n = i;
}
self.line_index_nearest(n, pos.0)
.map(|opt_line| opt_line.unwrap_or(0))
}
pub fn line_index_nearest(&self, line: usize, x: f32) -> Result<Option<usize>, NotReady> {
if !self.action.is_ready() {
return Err(NotReady);
}
if line >= self.lines.len() {
return Ok(None);
}
let line = &self.lines[line];
let run_range = line.run_range.to_std();
let mut best = line.text_range.start;
let mut best_dist = f32::INFINITY;
let mut try_best = |dist, index| {
if dist < best_dist {
best = index;
best_dist = dist;
}
};
for run_part in &self.wrapped_runs[run_range] {
let glyph_run = &self.runs[to_usize(run_part.glyph_run)];
let rel_pos = x - run_part.offset.0;
let end_index;
if glyph_run.level.is_ltr() {
for glyph in &glyph_run.glyphs[run_part.glyph_range.to_std()] {
let dist = (glyph.position.0 - rel_pos).abs();
try_best(dist, glyph.index);
}
end_index = run_part.text_end;
} else {
let mut index = run_part.text_end;
for glyph in &glyph_run.glyphs[run_part.glyph_range.to_std()] {
let dist = (glyph.position.0 - rel_pos).abs();
try_best(dist, index);
index = glyph.index
}
end_index = index;
}
let end_pos = if run_part.glyph_range.end() < glyph_run.glyphs.len() {
glyph_run.glyphs[run_part.glyph_range.end()].position.0
} else {
glyph_run.caret
};
try_best((end_pos - rel_pos).abs(), end_index);
}
Ok(Some(to_usize(best)))
}
}