plotters_unsable/element/
text.rs1use 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
8pub 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 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
57pub 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 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 pub fn set_line_height(&mut self, value: f64) -> &mut Self {
83 self.line_height = value;
84 self
85 }
86
87 pub fn push_line<L: Into<T>>(&mut self, line: L) {
90 self.lines.push(line.into());
91 }
92
93 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 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 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 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 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 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}