use crate::{TextCaretPosition, TextClusterSpan, TextLayout, TextLine, TextMetrics};
use incular_core::Offset;
use incular_rendering::{GlyphPosition, GlyphRun};
use std::sync::Arc;
pub trait TextGeometry {
fn metrics(&self) -> TextMetrics;
fn line_count(&self) -> usize;
fn get_line(&self, index: usize) -> Option<TextLineRef<'_>>;
fn lines(&self) -> TextLines<'_>;
fn lines_between(&self, top: f32, bottom: f32) -> TextLines<'_>;
fn line_for_offset(&self, byte: usize) -> usize;
fn line_caret_positions(&self, line: usize) -> &[TextCaretPosition];
fn line(&self, index: usize) -> TextLineRef<'_> {
self.get_line(index).expect("text line index out of range")
}
}
impl<T: TextGeometry + ?Sized> TextGeometry for Arc<T> {
fn metrics(&self) -> TextMetrics {
(**self).metrics()
}
fn line_count(&self) -> usize {
(**self).line_count()
}
fn get_line(&self, index: usize) -> Option<TextLineRef<'_>> {
(**self).get_line(index)
}
fn lines(&self) -> TextLines<'_> {
(**self).lines()
}
fn lines_between(&self, top: f32, bottom: f32) -> TextLines<'_> {
(**self).lines_between(top, bottom)
}
fn line_for_offset(&self, byte: usize) -> usize {
(**self).line_for_offset(byte)
}
fn line_caret_positions(&self, line: usize) -> &[TextCaretPosition] {
(**self).line_caret_positions(line)
}
}
impl TextGeometry for TextLayout {
fn metrics(&self) -> TextMetrics {
self.metrics
}
fn line_count(&self) -> usize {
self.lines.len()
}
fn get_line(&self, index: usize) -> Option<TextLineRef<'_>> {
let line = self.lines.get(index)?;
Some(TextLineRef::new(line, index, 0, 0.))
}
fn lines(&self) -> TextLines<'_> {
TextLines::flat(&self.lines, 0, self.lines.len())
}
fn lines_between(&self, top: f32, bottom: f32) -> TextLines<'_> {
let first = self
.lines
.partition_point(|line| line.baseline < top)
.saturating_sub(1);
let end = (self.lines.partition_point(|line| line.baseline <= bottom) + 1)
.min(self.lines.len())
.max(first);
TextLines::flat(&self.lines, first, end)
}
fn line_for_offset(&self, byte: usize) -> usize {
let last = self.lines.len().saturating_sub(1);
let line = self.lines.partition_point(|line| line.start <= byte);
match line.checked_sub(1) {
Some(line) if byte < self.lines[line].end => line,
_ => last,
}
}
fn line_caret_positions(&self, line: usize) -> &[TextCaretPosition] {
TextLayout::line_caret_positions(self, line)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TextLineRef<'a> {
line: &'a TextLine,
index: usize,
byte: usize,
y: f32,
}
impl<'a> TextLineRef<'a> {
pub(crate) const fn new(line: &'a TextLine, index: usize, byte: usize, y: f32) -> Self {
Self {
line,
index,
byte,
y,
}
}
#[must_use]
pub const fn index(&self) -> usize {
self.index
}
#[must_use]
pub const fn start(&self) -> usize {
self.line.start + self.byte
}
#[must_use]
pub const fn caret_end(&self) -> usize {
self.line.caret_end + self.byte
}
#[must_use]
pub const fn end(&self) -> usize {
self.line.end + self.byte
}
#[must_use]
pub fn baseline(&self) -> f32 {
self.line.baseline + self.y
}
#[must_use]
pub const fn width(&self) -> f32 {
self.line.width
}
#[must_use]
pub const fn offset(&self) -> f32 {
self.line.offset
}
#[must_use]
pub fn runs(&self) -> &'a [Arc<GlyphRun>] {
&self.line.runs
}
#[must_use]
pub const fn paint_origin(&self) -> Offset {
Offset::new(0., self.y)
}
pub fn glyphs(&self) -> impl ExactSizeIterator<Item = GlyphPosition> + 'a {
let (byte, y) = (self.byte as u32, self.y);
self.line.glyphs.iter().map(move |glyph| GlyphPosition {
offset: Offset::new(glyph.offset.x, glyph.offset.y - y),
cluster: glyph.cluster + byte,
..*glyph
})
}
pub fn clusters(&self) -> impl ExactSizeIterator<Item = TextClusterSpan> + 'a {
let byte = self.byte as u32;
self.line.clusters.iter().map(move |span| TextClusterSpan {
start: span.start + byte,
end: span.end + byte,
..*span
})
}
#[must_use]
pub fn selection_spans(&self, start: usize, end: usize) -> Vec<(f32, f32)> {
if end < self.byte {
return Vec::new();
}
self.line.selection_spans(
start.saturating_sub(self.byte),
end.saturating_sub(self.byte),
)
}
#[must_use]
pub const fn shaped(&self) -> &'a TextLine {
self.line
}
}
#[derive(Clone, Debug)]
pub(crate) struct ParagraphSlot {
pub(crate) byte: usize,
pub(crate) y: f32,
pub(crate) first_line: usize,
pub(crate) layout: Arc<TextLayout>,
}
#[derive(Clone, Copy, Debug)]
struct LineSegment<'a> {
lines: &'a [TextLine],
first_line: usize,
byte: usize,
y: f32,
}
impl<'a> LineSegment<'a> {
fn of(slot: &'a ParagraphSlot) -> Self {
Self {
lines: &slot.layout.lines,
first_line: slot.first_line,
byte: slot.byte,
y: slot.y,
}
}
}
#[derive(Clone, Debug)]
pub struct TextLines<'a> {
current: LineSegment<'a>,
next: usize,
rest: &'a [ParagraphSlot],
remaining: usize,
}
impl<'a> TextLines<'a> {
fn flat(lines: &'a [TextLine], first: usize, end: usize) -> Self {
Self {
current: LineSegment {
lines,
first_line: 0,
byte: 0,
y: 0.,
},
next: first,
rest: &[],
remaining: end.saturating_sub(first),
}
}
pub(crate) fn paragraphs(slots: &'a [ParagraphSlot]) -> Self {
let remaining = slots.iter().map(|slot| slot.layout.lines.len()).sum();
match slots.split_first() {
Some((first, rest)) => Self {
current: LineSegment::of(first),
next: 0,
rest,
remaining,
},
None => Self::flat(&[], 0, 0),
}
}
}
impl<'a> Iterator for TextLines<'a> {
type Item = TextLineRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
while self.next >= self.current.lines.len() {
let (slot, rest) = self.rest.split_first()?;
self.current = LineSegment::of(slot);
self.rest = rest;
self.next = 0;
}
let segment = self.current;
let index = self.next;
self.next += 1;
self.remaining -= 1;
Some(TextLineRef::new(
&segment.lines[index],
segment.first_line + index,
segment.byte,
segment.y,
))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for TextLines<'_> {}