plotters_unsable/element/
text.rs

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