diff options
Diffstat (limited to 'vendor/plotters/src/element/text.rs')
-rw-r--r-- | vendor/plotters/src/element/text.rs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/vendor/plotters/src/element/text.rs b/vendor/plotters/src/element/text.rs new file mode 100644 index 000000000..ca813c7c9 --- /dev/null +++ b/vendor/plotters/src/element/text.rs @@ -0,0 +1,242 @@ +use std::borrow::Borrow; +use std::i32; + +use super::{Drawable, PointCollection}; +use crate::style::{FontDesc, FontResult, LayoutBox, TextStyle}; +use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; + +/// 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<str>> { + text: T, + coord: Coord, + style: TextStyle<'a>, +} + +impl<'a, Coord, T: Borrow<str>> 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<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 Point = &'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, + _: (u32, u32), + ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + 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<str>> { + lines: Vec<T>, + coord: Coord, + style: TextStyle<'a>, + line_height: f64, +} + +impl<'a, Coord, T: Borrow<str>> 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<S: Into<TextStyle<'a>>>(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<L: Into<T>>(&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<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; + // 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<str>> MultiLineText<'a, BackendCoord, T> { + /// Compute the line layout + 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> { + /// 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<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> { + /// 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<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 Point = &'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, + _: (u32, u32), + ) -> 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, point)?; + } + } + Ok(()) + } +} |