use std::borrow::Borrow;
use std::i32;
use super::{Drawable, PointCollection};
use crate::drawing::backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
use crate::style::{FontDesc, FontResult, LayoutBox, TextStyle};
pub struct Text<'a, Coord, T: Borrow<str>> {
text: T,
coord: Coord,
style: TextStyle<'a>,
}
impl<'a, Coord, T: Borrow<str>> Text<'a, Coord, T> {
pub fn new<S: Into<TextStyle<'a>>>(text: T, points: Coord, style: S) -> Self {
Self {
text,
coord: points,
style: style.into(),
}
}
}
impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord> for &'a Text<'b, Coord, T> {
type Borrow = &'a Coord;
type IntoIter = std::iter::Once<&'a Coord>;
fn point_iter(self) -> Self::IntoIter {
std::iter::once(&self.coord)
}
}
impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB> for Text<'a, Coord, T> {
fn draw<I: Iterator<Item = BackendCoord>>(
&self,
mut points: I,
backend: &mut DB,
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
if let Some(a) = points.next() {
return backend.draw_text(self.text.borrow(), &self.style.font, a, &self.style.color);
}
Ok(())
}
}
pub struct MultiLineText<'a, Coord, T: Borrow<str>> {
lines: Vec<T>,
coord: Coord,
style: TextStyle<'a>,
line_height: f64,
}
impl<'a, Coord, T: Borrow<str>> MultiLineText<'a, Coord, T> {
pub fn new<S: Into<TextStyle<'a>>>(pos: Coord, style: S) -> Self {
MultiLineText {
lines: vec![],
coord: pos,
style: style.into(),
line_height: 1.25,
}
}
pub fn set_line_height(&mut self, value: f64) -> &mut Self {
self.line_height = value;
self
}
pub fn push_line<L: Into<T>>(&mut self, line: L) {
self.lines.push(line.into());
}
pub fn estimate_dimension(&self) -> FontResult<(i32, i32)> {
let (mut mx, mut my) = (0, 0);
for ((x, y), t) in self.layout_lines((0, 0)).zip(self.lines.iter()) {
let (dx, dy) = self.style.font.box_size(t.borrow())?;
mx = mx.max(x + dx as i32);
my = my.max(y + dy as i32);
}
Ok((mx, my))
}
pub fn relocate(&mut self, coord: Coord) {
self.coord = coord
}
fn layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator<Item = BackendCoord> {
let font_height = self.style.font.get_size();
let actual_line_height = font_height * self.line_height;
(0..self.lines.len() as u32).map(move |idx| {
let y = f64::from(y0) + f64::from(idx) * actual_line_height;
let x = f64::from(x0);
(x.round() as i32, y.round() as i32)
})
}
}
fn layout_multiline_text<'a, F: FnMut(&'a str)>(
text: &'a str,
max_width: u32,
font: FontDesc<'a>,
mut func: F,
) {
for line in text.lines() {
if max_width == 0 || line.is_empty() {
func(line);
} else {
let mut remaining = &line[0..];
while remaining.is_empty() {
let mut width = 0;
let mut left = 0;
while left < remaining.len() {
let char_width =
font.box_size(&remaining[left..=left]).unwrap_or((0, 0)).0 as i32;
width += char_width;
if width > max_width as i32 {
break;
}
left += 1;
}
if left == 0 {
left += 1;
}
let cur_line = &remaining[..left];
remaining = &remaining[left..];
func(cur_line);
}
}
}
}
impl<'a, T: Borrow<str>> MultiLineText<'a, BackendCoord, T> {
pub fn compute_line_layout(&self) -> FontResult<Vec<LayoutBox>> {
let mut ret = vec![];
for ((x, y), t) in self.layout_lines(self.coord).zip(self.lines.iter()) {
let (dx, dy) = self.style.font.box_size(t.borrow())?;
ret.push(((x, y), (x + dx as i32, y + dy as i32)));
}
Ok(ret)
}
}
impl<'a, Coord> MultiLineText<'a, Coord, &'a str> {
pub fn from_str<ST: Into<&'a str>, S: Into<TextStyle<'a>>>(
text: ST,
pos: Coord,
style: S,
max_width: u32,
) -> Self {
let text = text.into();
let mut ret = MultiLineText::new(pos, style);
layout_multiline_text(text, max_width, ret.style.font.clone(), |l| {
ret.push_line(l)
});
ret
}
}
impl<'a, Coord> MultiLineText<'a, Coord, String> {
pub fn from_string<S: Into<TextStyle<'a>>>(
text: String,
pos: Coord,
style: S,
max_width: u32,
) -> Self {
let mut ret = MultiLineText::new(pos, style);
layout_multiline_text(text.as_str(), max_width, ret.style.font.clone(), |l| {
ret.push_line(l.to_string())
});
ret
}
}
impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord>
for &'a MultiLineText<'b, Coord, T>
{
type Borrow = &'a Coord;
type IntoIter = std::iter::Once<&'a Coord>;
fn point_iter(self) -> Self::IntoIter {
std::iter::once(&self.coord)
}
}
impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB>
for MultiLineText<'a, Coord, T>
{
fn draw<I: Iterator<Item = BackendCoord>>(
&self,
mut points: I,
backend: &mut DB,
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
if let Some(a) = points.next() {
for (point, text) in self.layout_lines(a).zip(self.lines.iter()) {
backend.draw_text(text.borrow(), &self.style.font, point, &self.style.color)?;
}
}
Ok(())
}
}