use super::{HorizontalAlign, SectionGlyph, SectionText, VerticalAlign};
use crate::{linebreak::LineBreaker, words::*};
use ab_glyph::*;
use std::iter::{FusedIterator, Iterator, Peekable};
#[derive(Default)]
pub(crate) struct Line {
pub glyphs: Vec<SectionGlyph>,
pub max_v_metrics: VMetrics,
pub rightmost: f32,
}
impl Line {
#[inline]
pub(crate) fn line_height(&self) -> f32 {
self.max_v_metrics.ascent - self.max_v_metrics.descent + self.max_v_metrics.line_gap
}
pub fn aligned_on_screen(
mut self,
screen_position: (f32, f32),
h_align: HorizontalAlign,
v_align: VerticalAlign,
) -> Vec<SectionGlyph> {
if self.glyphs.is_empty() {
return Vec::new();
}
let screen_left = match h_align {
HorizontalAlign::Left => point(screen_position.0, screen_position.1),
HorizontalAlign::Center | HorizontalAlign::Right => {
let mut shift_left = self.rightmost;
if h_align == HorizontalAlign::Center {
shift_left /= 2.0;
}
point(screen_position.0 - shift_left, screen_position.1)
}
};
let screen_pos = match v_align {
VerticalAlign::Top => screen_left,
VerticalAlign::Center => {
let mut screen_pos = screen_left;
screen_pos.y -= self.line_height() / 2.0;
screen_pos
}
VerticalAlign::Bottom => {
let mut screen_pos = screen_left;
screen_pos.y -= self.line_height();
screen_pos
}
};
self.glyphs
.iter_mut()
.for_each(|sg| sg.glyph.position += screen_pos);
self.glyphs
}
}
pub(crate) struct Lines<'a, 'b, L, F, S>
where
L: LineBreaker,
F: Font,
S: Iterator<Item = SectionText<'a>>,
{
pub(crate) words: Peekable<Words<'a, 'b, L, F, S>>,
pub(crate) width_bound: f32,
}
impl<'a, L, F, S> Iterator for Lines<'a, '_, L, F, S>
where
L: LineBreaker,
F: Font,
S: Iterator<Item = SectionText<'a>>,
{
type Item = Line;
fn next(&mut self) -> Option<Self::Item> {
let mut caret = point(0.0, 0.0);
let mut line = Line::default();
let mut progressed = false;
while let Some(word) = self.words.peek() {
let word_wrap_width = match word.hard_break {
false => word.layout_width_no_trail,
true => word.layout_width,
};
let word_right = caret.x + word_wrap_width;
let word_in_bounds =
word_right < self.width_bound || approx::relative_eq!(word_right, self.width_bound);
if !word_in_bounds && progressed {
break;
}
let word = self.words.next().unwrap();
progressed = true;
line.rightmost = word_right;
if (line.glyphs.is_empty() || !word.glyphs.is_empty())
&& word.max_v_metrics.height() > line.max_v_metrics.height()
{
let diff_y = word.max_v_metrics.ascent - caret.y;
caret.y += diff_y;
for SectionGlyph { glyph, .. } in &mut line.glyphs {
glyph.position.y += diff_y;
}
line.max_v_metrics = word.max_v_metrics;
}
line.glyphs.extend(word.glyphs.into_iter().map(|mut sg| {
sg.glyph.position += caret;
sg
}));
caret.x += word.layout_width;
if word.hard_break {
break;
}
}
Some(line).filter(|_| progressed)
}
}
impl<'a, L, F, S> FusedIterator for Lines<'a, '_, L, F, S>
where
L: LineBreaker,
F: Font,
S: Iterator<Item = SectionText<'a>>,
{
}