#[allow(unused)]
use crate::Text;
use crate::conv::to_usize;
use crate::{Direction, Vec2, shaper};
use smallvec::SmallVec;
use tinyvec::TinyVec;
mod glyph_pos;
mod text_runs;
mod wrap_lines;
pub use glyph_pos::{Effect, EffectFlags, GlyphRun, MarkerPos, MarkerPosIter};
pub(crate) use text_runs::RunSpecial;
pub use wrap_lines::Line;
use wrap_lines::RunPart;
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, thiserror::Error)]
#[error("not ready")]
pub struct NotReady;
#[derive(Clone, Debug)]
pub struct TextDisplay {
runs: SmallVec<[shaper::GlyphRun; 1]>,
wrapped_runs: TinyVec<[RunPart; 1]>,
lines: TinyVec<[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::<TinyVec<[u8; 0]>>(), 24);
assert_eq!(size_of::<shaper::GlyphRun>(), 112);
assert_eq!(size_of::<RunPart>(), 24);
assert_eq!(size_of::<Line>(), 24);
#[cfg(not(feature = "num_glyphs"))]
assert_eq!(size_of::<TextDisplay>(), 200);
#[cfg(feature = "num_glyphs")]
assert_eq!(size_of::<TextDisplay>(), 208);
}
impl Default for TextDisplay {
fn default() -> Self {
TextDisplay {
runs: Default::default(),
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 num_lines(&self) -> usize {
self.lines.len()
}
#[inline]
pub fn get_line(&self, index: usize) -> Option<&Line> {
self.lines.get(index)
}
#[inline]
pub fn lines(&self) -> impl Iterator<Item = &Line> {
self.lines.iter()
}
pub fn bounding_box(&self) -> (Vec2, Vec2) {
if self.lines.is_empty() {
return (Vec2::ZERO, Vec2::ZERO);
}
let top = self.lines.first().unwrap().top;
let bottom = self.lines.last().unwrap().bottom;
(Vec2(self.l_bound, top), Vec2(self.r_bound, bottom))
}
pub fn find_line(&self, index: usize) -> Option<(usize, std::ops::Range<usize>)> {
let mut first = None;
for (n, line) in self.lines.iter().enumerate() {
let text_range = line.text_range();
if text_range.end == index {
first = Some((n, text_range));
} else if text_range.contains(&index) {
return Some((n, text_range));
}
}
first
}
pub fn text_is_rtl(&self, text: &str, direction: Direction) -> bool {
let (is_auto, mut is_rtl) = match direction {
Direction::Ltr => (false, false),
Direction::Rtl => (false, true),
Direction::Auto => (true, false),
Direction::AutoRtl => (true, true),
};
if is_auto {
match unicode_bidi::get_base_direction(text) {
unicode_bidi::Direction::Ltr => is_rtl = false,
unicode_bidi::Direction::Rtl => is_rtl = true,
unicode_bidi::Direction::Mixed => (),
}
}
is_rtl
}
pub fn line_is_rtl(&self, line: usize) -> Option<bool> {
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);
Some(self.runs[glyph_run].level.is_rtl())
} else {
None
}
}
pub fn text_index_nearest(&self, pos: Vec2) -> usize {
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).unwrap_or(0)
}
pub fn line_index_nearest(&self, line: usize, x: f32) -> Option<usize> {
if line >= self.lines.len() {
return 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: u32| {
if dist < best_dist {
best = to_usize(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);
}
Some(best)
}
}