• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::borrow::Borrow;
2 use std::i32;
3 
4 use super::{Drawable, PointCollection};
5 use crate::style::{FontDesc, FontResult, LayoutBox, TextStyle};
6 use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
7 
8 /// A single line text element. This can be owned or borrowed string, dependents on
9 /// `String` or `str` moved into.
10 pub struct Text<'a, Coord, T: Borrow<str>> {
11     text: T,
12     coord: Coord,
13     style: TextStyle<'a>,
14 }
15 
16 impl<'a, Coord, T: Borrow<str>> Text<'a, Coord, T> {
17     /// Create a new text element
18     /// - `text`: The text for the element
19     /// - `points`: The upper left conner for the text element
20     /// - `style`: The text style
21     /// - Return the newly created text element
new<S: Into<TextStyle<'a>>>(text: T, points: Coord, style: S) -> Self22     pub fn new<S: Into<TextStyle<'a>>>(text: T, points: Coord, style: S) -> Self {
23         Self {
24             text,
25             coord: points,
26             style: style.into(),
27         }
28     }
29 }
30 
31 impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord> for &'a Text<'b, Coord, T> {
32     type Point = &'a Coord;
33     type IntoIter = std::iter::Once<&'a Coord>;
point_iter(self) -> Self::IntoIter34     fn point_iter(self) -> Self::IntoIter {
35         std::iter::once(&self.coord)
36     }
37 }
38 
39 impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB> for Text<'a, Coord, T> {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>40     fn draw<I: Iterator<Item = BackendCoord>>(
41         &self,
42         mut points: I,
43         backend: &mut DB,
44         _: (u32, u32),
45     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
46         if let Some(a) = points.next() {
47             return backend.draw_text(self.text.borrow(), &self.style, a);
48         }
49         Ok(())
50     }
51 }
52 
53 /// An multi-line text element. The `Text` element allows only single line text
54 /// and the `MultiLineText` supports drawing multiple lines
55 pub struct MultiLineText<'a, Coord, T: Borrow<str>> {
56     lines: Vec<T>,
57     coord: Coord,
58     style: TextStyle<'a>,
59     line_height: f64,
60 }
61 
62 impl<'a, Coord, T: Borrow<str>> MultiLineText<'a, Coord, T> {
63     /// Create an empty multi-line text element.
64     /// Lines can be append to the empty multi-line by calling `push_line` method
65     ///
66     /// `pos`: The upper left corner
67     /// `style`: The style of the text
new<S: Into<TextStyle<'a>>>(pos: Coord, style: S) -> Self68     pub fn new<S: Into<TextStyle<'a>>>(pos: Coord, style: S) -> Self {
69         MultiLineText {
70             lines: vec![],
71             coord: pos,
72             style: style.into(),
73             line_height: 1.25,
74         }
75     }
76 
77     /// Set the line height of the multi-line text element
set_line_height(&mut self, value: f64) -> &mut Self78     pub fn set_line_height(&mut self, value: f64) -> &mut Self {
79         self.line_height = value;
80         self
81     }
82 
83     /// Push a new line into the given multi-line text
84     /// `line`: The line to be pushed
push_line<L: Into<T>>(&mut self, line: L)85     pub fn push_line<L: Into<T>>(&mut self, line: L) {
86         self.lines.push(line.into());
87     }
88 
89     /// Estimate the multi-line text element's dimension
estimate_dimension(&self) -> FontResult<(i32, i32)>90     pub fn estimate_dimension(&self) -> FontResult<(i32, i32)> {
91         let (mut mx, mut my) = (0, 0);
92 
93         for ((x, y), t) in self.layout_lines((0, 0)).zip(self.lines.iter()) {
94             let (dx, dy) = self.style.font.box_size(t.borrow())?;
95             mx = mx.max(x + dx as i32);
96             my = my.max(y + dy as i32);
97         }
98 
99         Ok((mx, my))
100     }
101 
102     /// Move the location to the specified location
relocate(&mut self, coord: Coord)103     pub fn relocate(&mut self, coord: Coord) {
104         self.coord = coord
105     }
106 
layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator<Item = BackendCoord>107     fn layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator<Item = BackendCoord> {
108         let font_height = self.style.font.get_size();
109         let actual_line_height = font_height * self.line_height;
110         (0..self.lines.len() as u32).map(move |idx| {
111             let y = f64::from(y0) + f64::from(idx) * actual_line_height;
112             // TODO: Support text alignment as well, currently everything is left aligned
113             let x = f64::from(x0);
114             (x.round() as i32, y.round() as i32)
115         })
116     }
117 }
118 
layout_multiline_text<'a, F: FnMut(&'a str)>( text: &'a str, max_width: u32, font: FontDesc<'a>, mut func: F, )119 fn layout_multiline_text<'a, F: FnMut(&'a str)>(
120     text: &'a str,
121     max_width: u32,
122     font: FontDesc<'a>,
123     mut func: F,
124 ) {
125     for line in text.lines() {
126         if max_width == 0 || line.is_empty() {
127             func(line);
128         } else {
129             let mut remaining = &line[0..];
130 
131             while !remaining.is_empty() {
132                 let mut left = 0;
133                 while left < remaining.len() {
134                     let width = font.box_size(&remaining[0..=left]).unwrap_or((0, 0)).0 as i32;
135 
136                     if width > max_width as i32 {
137                         break;
138                     }
139                     left += 1;
140                 }
141 
142                 if left == 0 {
143                     left += 1;
144                 }
145 
146                 let cur_line = &remaining[..left];
147                 remaining = &remaining[left..];
148 
149                 func(cur_line);
150             }
151         }
152     }
153 }
154 
155 impl<'a, T: Borrow<str>> MultiLineText<'a, BackendCoord, T> {
156     /// Compute the line layout
compute_line_layout(&self) -> FontResult<Vec<LayoutBox>>157     pub fn compute_line_layout(&self) -> FontResult<Vec<LayoutBox>> {
158         let mut ret = vec![];
159         for ((x, y), t) in self.layout_lines(self.coord).zip(self.lines.iter()) {
160             let (dx, dy) = self.style.font.box_size(t.borrow())?;
161             ret.push(((x, y), (x + dx as i32, y + dy as i32)));
162         }
163         Ok(ret)
164     }
165 }
166 
167 impl<'a, Coord> MultiLineText<'a, Coord, &'a str> {
168     /// Parse a multi-line text into an multi-line element.
169     ///
170     /// `text`: The text that is parsed
171     /// `pos`: The position of the text
172     /// `style`: The style for this text
173     /// `max_width`: The width of the multi-line text element, the line will break
174     /// into two lines if the line is wider than the max_width. If 0 is given, do not
175     /// do any line wrapping
from_str<ST: Into<&'a str>, S: Into<TextStyle<'a>>>( text: ST, pos: Coord, style: S, max_width: u32, ) -> Self176     pub fn from_str<ST: Into<&'a str>, S: Into<TextStyle<'a>>>(
177         text: ST,
178         pos: Coord,
179         style: S,
180         max_width: u32,
181     ) -> Self {
182         let text = text.into();
183         let mut ret = MultiLineText::new(pos, style);
184 
185         layout_multiline_text(text, max_width, ret.style.font.clone(), |l| {
186             ret.push_line(l)
187         });
188         ret
189     }
190 }
191 
192 impl<'a, Coord> MultiLineText<'a, Coord, String> {
193     /// Parse a multi-line text into an multi-line element.
194     ///
195     /// `text`: The text that is parsed
196     /// `pos`: The position of the text
197     /// `style`: The style for this text
198     /// `max_width`: The width of the multi-line text element, the line will break
199     /// into two lines if the line is wider than the max_width. If 0 is given, do not
200     /// do any line wrapping
from_string<S: Into<TextStyle<'a>>>( text: String, pos: Coord, style: S, max_width: u32, ) -> Self201     pub fn from_string<S: Into<TextStyle<'a>>>(
202         text: String,
203         pos: Coord,
204         style: S,
205         max_width: u32,
206     ) -> Self {
207         let mut ret = MultiLineText::new(pos, style);
208 
209         layout_multiline_text(text.as_str(), max_width, ret.style.font.clone(), |l| {
210             ret.push_line(l.to_string())
211         });
212         ret
213     }
214 }
215 
216 impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord>
217     for &'a MultiLineText<'b, Coord, T>
218 {
219     type Point = &'a Coord;
220     type IntoIter = std::iter::Once<&'a Coord>;
point_iter(self) -> Self::IntoIter221     fn point_iter(self) -> Self::IntoIter {
222         std::iter::once(&self.coord)
223     }
224 }
225 
226 impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB>
227     for MultiLineText<'a, Coord, T>
228 {
draw<I: Iterator<Item = BackendCoord>>( &self, mut points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>229     fn draw<I: Iterator<Item = BackendCoord>>(
230         &self,
231         mut points: I,
232         backend: &mut DB,
233         _: (u32, u32),
234     ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
235         if let Some(a) = points.next() {
236             for (point, text) in self.layout_lines(a).zip(self.lines.iter()) {
237                 backend.draw_text(text.borrow(), &self.style, point)?;
238             }
239         }
240         Ok(())
241     }
242 }
243