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}; /// A single line text element. This can be owned or borrowed string, dependents on /// `String` or `str` moved into. pub struct Text<'a, Coord, T: Borrow> { text: T, coord: Coord, style: TextStyle<'a>, } impl<'a, Coord, T: Borrow> Text<'a, Coord, T> { /// Create a new text element /// - `text`: The text for the element /// - `points`: The upper left conner for the text element /// - `style`: The text style /// - Return the newly created text element pub fn new>>(text: T, points: Coord, style: S) -> Self { Self { text, coord: points, style: style.into(), } } } impl<'b, 'a, Coord: 'a, T: Borrow + '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> Drawable for Text<'a, Coord, T> { fn draw>( &self, mut points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind> { if let Some(a) = points.next() { return backend.draw_text(self.text.borrow(), &self.style, a); } Ok(()) } } /// An multi-line text element. The `Text` element allows only single line text /// and the `MultiLineText` supports drawing multiple lines pub struct MultiLineText<'a, Coord, T: Borrow> { lines: Vec, coord: Coord, style: TextStyle<'a>, line_height: f64, } impl<'a, Coord, T: Borrow> MultiLineText<'a, Coord, T> { /// Create an empty multi-line text element. /// Lines can be append to the empty multi-line by calling `push_line` method /// /// `pos`: The upper left corner /// `style`: The style of the text pub fn new>>(pos: Coord, style: S) -> Self { MultiLineText { lines: vec![], coord: pos, style: style.into(), line_height: 1.25, } } /// Set the line height of the multi-line text element pub fn set_line_height(&mut self, value: f64) -> &mut Self { self.line_height = value; self } /// Push a new line into the given multi-line text /// `line`: The line to be pushed pub fn push_line>(&mut self, line: L) { self.lines.push(line.into()); } /// Estimate the multi-line text element's dimension 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)) } /// Move the location to the specified location pub fn relocate(&mut self, coord: Coord) { self.coord = coord } fn layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator { 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; // TODO: Support text alignment as well, currently everything is left aligned 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 left = 0; while left < remaining.len() { let width = font.box_size(&remaining[0..=left]).unwrap_or((0, 0)).0 as i32; 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> MultiLineText<'a, BackendCoord, T> { /// Compute the line layout pub fn compute_line_layout(&self) -> FontResult> { 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> { /// Parse a multi-line text into an multi-line element. /// /// `text`: The text that is parsed /// `pos`: The position of the text /// `style`: The style for this text /// `max_width`: The width of the multi-line text element, the line will break /// into two lines if the line is wider than the max_width. If 0 is given, do not /// do any line wrapping pub fn from_str, S: Into>>( 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> { /// Parse a multi-line text into an multi-line element. /// /// `text`: The text that is parsed /// `pos`: The position of the text /// `style`: The style for this text /// `max_width`: The width of the multi-line text element, the line will break /// into two lines if the line is wider than the max_width. If 0 is given, do not /// do any line wrapping pub fn from_string>>( 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 + '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> Drawable for MultiLineText<'a, Coord, T> { fn draw>( &self, mut points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind> { 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, point)?; } } Ok(()) } }