summaryrefslogtreecommitdiffstats
path: root/vendor/plotters/src/element/text.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/plotters/src/element/text.rs')
-rw-r--r--vendor/plotters/src/element/text.rs242
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(())
+ }
+}