use crate::geom::{Range, Rect};
use crate::text::{self, FontSize, Scalar, Wrap};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Break {
Wrap {
byte: usize,
char: usize,
len_bytes: usize,
},
Newline {
byte: usize,
char: usize,
len_bytes: usize,
},
End {
byte: usize,
char: usize,
},
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct NextBreak {
pub break_: Break,
pub width: Scalar,
pub height: Scalar,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Info {
pub start_byte: usize,
pub start_char: usize,
pub end_break: Break,
pub width: Scalar,
pub height: Scalar,
}
pub struct Infos<'a, F> {
text: &'a str,
font: &'a text::Font,
font_size: FontSize,
max_width: Scalar,
next_break_fn: F,
start_byte: usize,
start_char: usize,
last_break: Option<Break>,
}
#[derive(Clone)]
pub struct Rects<I> {
infos: I,
x_align: text::Justify,
line_spacing: Scalar,
last_line_top: Scalar,
font_size: FontSize,
next: Option<Rect>,
}
pub struct SelectedRects<'a, I> {
selected_char_rects_per_line: text::glyph::SelectedRectsPerLine<'a, I>,
}
pub type NextBreakFnPtr = fn(&str, &text::Font, FontSize, Scalar) -> NextBreak;
impl Break {
pub fn byte_index(self) -> usize {
match self {
Break::Wrap { byte, .. } | Break::Newline { byte, .. } | Break::End { byte, .. } => {
byte
}
}
}
pub fn char_index(self) -> usize {
match self {
Break::Wrap { char, .. } | Break::Newline { char, .. } | Break::End { char, .. } => {
char
}
}
}
}
impl<'a, F> Clone for Infos<'a, F>
where
F: Clone,
{
fn clone(&self) -> Self {
Infos {
text: self.text,
font: self.font,
font_size: self.font_size,
max_width: self.max_width,
next_break_fn: self.next_break_fn.clone(),
start_byte: self.start_byte,
start_char: self.start_char,
last_break: None,
}
}
}
impl Info {
pub fn end_byte(&self) -> usize {
self.end_break.byte_index()
}
pub fn end_char(&self) -> usize {
self.end_break.char_index()
}
pub fn byte_range(self) -> std::ops::Range<usize> {
self.start_byte..self.end_byte()
}
pub fn char_range(self) -> std::ops::Range<usize> {
self.start_char..self.end_char()
}
}
impl<'a> Infos<'a, NextBreakFnPtr> {
pub fn wrap_by_character(mut self, max_width: Scalar) -> Self {
self.next_break_fn = next_break_by_character;
self.max_width = max_width;
self
}
pub fn wrap_by_whitespace(mut self, max_width: Scalar) -> Self {
self.next_break_fn = next_break_by_whitespace;
self.max_width = max_width;
self
}
}
fn advance_width_and_height(
ch: char,
font: &text::Font,
scale: text::Scale,
last_glyph: &mut Option<text::GlyphId>,
) -> (Scalar, Scalar) {
let g = font.glyph(ch).scaled(scale);
let kern = last_glyph
.map(|last| font.pair_kerning(scale, last, g.id()))
.unwrap_or(0.0);
let advance_width = g.h_metrics().advance_width;
let height = g
.exact_bounding_box()
.map(|bb| bb.min.y.abs() as Scalar)
.unwrap_or(0.0);
*last_glyph = Some(g.id());
let adv_w = (kern + advance_width) as Scalar;
(adv_w, height)
}
fn next_break(text: &str, font: &text::Font, font_size: FontSize) -> NextBreak {
let scale = text::pt_to_scale(font_size);
let mut width = 0.0;
let mut height = 0.0;
let mut char_i = 0;
let mut char_indices = text.char_indices().peekable();
let mut last_glyph = None;
while let Some((byte_i, ch)) = char_indices.next() {
if ch == '\r' {
if let Some(&(_, '\n')) = char_indices.peek() {
let break_ = Break::Newline {
byte: byte_i,
char: char_i,
len_bytes: 2,
};
return NextBreak {
break_,
width,
height,
};
}
} else if ch == '\n' {
let break_ = Break::Newline {
byte: byte_i,
char: char_i,
len_bytes: 1,
};
return NextBreak {
break_,
width,
height,
};
}
let (adv_w, h) = advance_width_and_height(ch, font, scale, &mut last_glyph);
width += adv_w;
height = height.max(h);
char_i += 1;
}
let break_ = Break::End {
byte: text.len(),
char: char_i,
};
NextBreak {
break_,
width,
height,
}
}
fn next_break_by_character(
text: &str,
font: &text::Font,
font_size: FontSize,
max_width: Scalar,
) -> NextBreak {
let scale = text::pt_to_scale(font_size);
let mut width = 0.0;
let mut height = 0.0;
let mut char_i = 0;
let mut char_indices = text.char_indices().peekable();
let mut last_glyph = None;
while let Some((byte_i, ch)) = char_indices.next() {
if ch == '\r' {
if let Some(&(_, '\n')) = char_indices.peek() {
let break_ = Break::Newline {
byte: byte_i,
char: char_i,
len_bytes: 2,
};
return NextBreak {
break_,
width,
height,
};
}
} else if ch == '\n' {
let break_ = Break::Newline {
byte: byte_i,
char: char_i,
len_bytes: 1,
};
return NextBreak {
break_,
width,
height,
};
}
let (adv_w, h) = advance_width_and_height(ch, font, scale, &mut last_glyph);
let new_width = width + adv_w;
if new_width > max_width {
let break_ = Break::Wrap {
byte: byte_i,
char: char_i,
len_bytes: 0,
};
return NextBreak {
break_,
width,
height,
};
}
height = height.max(h);
width = new_width;
char_i += 1;
}
let break_ = Break::End {
byte: text.len(),
char: char_i,
};
NextBreak {
break_,
width,
height,
}
}
fn next_break_by_whitespace(
text: &str,
font: &text::Font,
font_size: FontSize,
max_width: Scalar,
) -> NextBreak {
struct Last {
byte: usize,
char: usize,
width_before: Scalar,
}
let scale = text::pt_to_scale(font_size);
let mut last_whitespace_start = None;
let mut width = 0.0;
let mut height = 0.0;
let mut char_i = 0;
let mut char_indices = text.char_indices().peekable();
let mut last_glyph = None;
while let Some((byte_i, ch)) = char_indices.next() {
if ch == '\r' {
if let Some(&(_, '\n')) = char_indices.peek() {
let break_ = Break::Newline {
byte: byte_i,
char: char_i,
len_bytes: 2,
};
return NextBreak {
break_,
width,
height,
};
}
} else if ch == '\n' {
let break_ = Break::Newline {
byte: byte_i,
char: char_i,
len_bytes: 1,
};
return NextBreak {
break_,
width,
height,
};
}
let (adv_w, h) = advance_width_and_height(ch, font, scale, &mut last_glyph);
let new_width = width + adv_w;
if width > max_width {
match last_whitespace_start {
Some(Last {
byte,
char,
width_before,
}) => {
let break_ = Break::Wrap {
byte: byte,
char: char,
len_bytes: 1,
};
let width = width_before;
return NextBreak {
break_,
width,
height,
};
}
None => {
let break_ = Break::Wrap {
byte: byte_i,
char: char_i,
len_bytes: 0,
};
return NextBreak {
break_,
width,
height,
};
}
}
}
if ch.is_whitespace() {
last_whitespace_start = Some(Last {
byte: byte_i,
char: char_i,
width_before: width,
});
}
width = new_width;
height = height.max(h);
char_i += 1;
}
let break_ = Break::End {
byte: text.len(),
char: char_i,
};
NextBreak {
break_,
width,
height,
}
}
pub fn width(text: &str, font: &text::Font, font_size: FontSize) -> Scalar {
let scale = text::Scale::uniform(text::pt_to_px(font_size));
let point = text::rt::Point { x: 0.0, y: 0.0 };
let mut total_w = 0.0;
for g in font.layout(text, scale, point) {
match g.pixel_bounding_box() {
Some(bb) => total_w = bb.max.x as f32,
None => total_w += g.unpositioned().h_metrics().advance_width,
}
}
total_w as Scalar
}
pub fn infos_wrapped_by<'a, F>(
text: &'a str,
font: &'a text::Font,
font_size: FontSize,
max_width: Scalar,
next_break_fn: F,
) -> Infos<'a, F>
where
F: for<'b> FnMut(&'b str, &'b text::Font, FontSize, Scalar) -> NextBreak,
{
Infos {
text: text,
font: font,
font_size: font_size,
max_width: max_width,
next_break_fn: next_break_fn,
start_byte: 0,
start_char: 0,
last_break: None,
}
}
pub fn infos<'a>(
text: &'a str,
font: &'a text::Font,
font_size: FontSize,
) -> Infos<'a, NextBreakFnPtr> {
fn no_wrap(
text: &str,
font: &text::Font,
font_size: FontSize,
_max_width: Scalar,
) -> NextBreak {
next_break(text, font, font_size)
}
infos_wrapped_by(text, font, font_size, std::f32::MAX, no_wrap)
}
pub fn infos_maybe_wrapped<'a>(
text: &'a str,
font: &'a text::Font,
font_size: FontSize,
maybe_wrap: Option<Wrap>,
max_width: Scalar,
) -> Infos<'a, NextBreakFnPtr> {
match maybe_wrap {
None => infos(text, font, font_size),
Some(Wrap::Character) => infos(text, font, font_size).wrap_by_character(max_width),
Some(Wrap::Whitespace) => infos(text, font, font_size).wrap_by_whitespace(max_width),
}
}
pub fn rects<I>(
mut infos: I,
font_size: FontSize,
max_width: Scalar,
x_align: text::Justify,
line_spacing: Scalar,
) -> Rects<I>
where
I: Iterator<Item = Info>,
{
let first_rect = infos.next().map(|first_info| {
let x_bounds = Range::new(0.0, max_width);
let range = Range::new(0.0, first_info.width);
let x = match x_align {
text::Justify::Left => range.align_start_of(x_bounds),
text::Justify::Center => range.align_middle_of(x_bounds),
text::Justify::Right => range.align_end_of(x_bounds),
};
let y_start = -(font_size as Scalar);
let y_end = y_start + font_size as Scalar;
let y = Range::new(y_start, y_end);
Rect { x: x, y: y }
});
Rects {
infos: infos,
next: first_rect,
x_align: x_align,
last_line_top: 0.0,
font_size: font_size,
line_spacing: line_spacing,
}
}
pub fn selected_rects<'a, I>(
lines_with_rects: I,
font: &'a text::Font,
font_size: FontSize,
start: text::cursor::Index,
end: text::cursor::Index,
) -> SelectedRects<'a, I>
where
I: Iterator<Item = (&'a str, Rect)>,
{
SelectedRects {
selected_char_rects_per_line: text::glyph::selected_rects_per_line(
lines_with_rects,
font,
font_size,
start,
end,
),
}
}
impl<'a, F> Iterator for Infos<'a, F>
where
F: for<'b> FnMut(&'b str, &'b text::Font, FontSize, Scalar) -> NextBreak,
{
type Item = Info;
fn next(&mut self) -> Option<Self::Item> {
let Infos {
text,
font,
font_size,
max_width,
ref mut next_break_fn,
ref mut start_byte,
ref mut start_char,
ref mut last_break,
} = *self;
let next = next_break_fn(&text[*start_byte..], font, font_size, max_width);
match next.break_ {
Break::Newline { .. } | Break::Wrap { .. } => {
let next_break = match next.break_ {
Break::Newline {
byte,
char,
len_bytes,
} => Break::Newline {
byte: *start_byte + byte,
char: *start_char + char,
len_bytes: len_bytes,
},
Break::Wrap {
byte,
char,
len_bytes,
} => Break::Wrap {
byte: *start_byte + byte,
char: *start_char + char,
len_bytes: len_bytes,
},
_ => unreachable!(),
};
let info = Info {
start_byte: *start_byte,
start_char: *start_char,
end_break: next_break,
width: next.width,
height: next.height,
};
match next.break_ {
Break::Newline {
byte,
char,
len_bytes,
}
| Break::Wrap {
byte,
char,
len_bytes,
} => {
*start_byte = info.start_byte + byte + len_bytes;
*start_char = info.start_char + char + 1;
}
_ => unreachable!(),
};
*last_break = Some(next_break);
Some(info)
}
Break::End { char, .. } => {
let empty_line = {
match *last_break {
Some(last_break_) => match last_break_ {
Break::Newline { .. } => true,
_ => false,
},
None => true,
}
};
if *start_byte < text.len() || empty_line {
let total_bytes = text.len();
let total_chars = *start_char + char;
let end_break = Break::End {
byte: total_bytes,
char: total_chars,
};
let info = Info {
start_byte: *start_byte,
start_char: *start_char,
end_break: end_break,
width: next.width,
height: next.height,
};
*start_byte = total_bytes;
*start_char = total_chars;
*last_break = Some(end_break);
Some(info)
} else {
None
}
}
}
}
}
impl<I> Iterator for Rects<I>
where
I: Iterator<Item = Info>,
{
type Item = Rect;
fn next(&mut self) -> Option<Self::Item> {
let Rects {
ref mut next,
ref mut infos,
x_align,
ref mut last_line_top,
font_size,
line_spacing,
} = *self;
next.map(|line_rect| {
*next = infos.next().map(|info| {
let y = {
let line_top = *last_line_top - font_size as Scalar - line_spacing;
*last_line_top = line_top;
let y_start = line_top - font_size as Scalar;
let y_end = y_start + font_size as Scalar;
Range::new(y_start, y_end)
};
let x = {
let range = Range::new(0.0, info.width);
match x_align {
text::Justify::Left => range.align_start_of(line_rect.x),
text::Justify::Center => range.align_middle_of(line_rect.x),
text::Justify::Right => range.align_end_of(line_rect.x),
}
};
Rect { x: x, y: y }
});
line_rect
})
}
}
impl<'a, I> Iterator for SelectedRects<'a, I>
where
I: Iterator<Item = (&'a str, Rect)>,
{
type Item = Rect;
fn next(&mut self) -> Option<Self::Item> {
while let Some(mut rects) = self.selected_char_rects_per_line.next() {
if let Some((_, first_rect)) = rects.next() {
let total_selected_rect = rects.fold(first_rect, |mut total, (_, next)| {
total.x.end = next.x.end;
total
});
return Some(total_selected_rect);
}
}
None
}
}