1use std::borrow::Borrow;
2
3use super::{Drawable, PointCollection};
4use crate::style::{FontDesc, FontResult, LayoutBox, TextStyle};
5use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
6
7pub struct Text<'a, Coord, T: Borrow<str>> {
10 text: T,
11 coord: Coord,
12 style: TextStyle<'a>,
13}
14
15impl<'a, Coord, T: Borrow<str>> Text<'a, Coord, T> {
16 pub fn new<S: Into<TextStyle<'a>>>(text: T, points: Coord, style: S) -> Self {
22 Self {
23 text,
24 coord: points,
25 style: style.into(),
26 }
27 }
28}
29
30impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord> for &'a Text<'b, Coord, T> {
31 type Point = &'a Coord;
32 type IntoIter = std::iter::Once<&'a Coord>;
33 fn point_iter(self) -> Self::IntoIter {
34 std::iter::once(&self.coord)
35 }
36}
37
38impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB> for Text<'a, Coord, T> {
39 fn draw<I: Iterator<Item = BackendCoord>>(
40 &self,
41 mut points: I,
42 backend: &mut DB,
43 _: (u32, u32),
44 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
45 if let Some(a) = points.next() {
46 return backend.draw_text(self.text.borrow(), &self.style, a);
47 }
48 Ok(())
49 }
50}
51
52pub struct MultiLineText<'a, Coord, T: Borrow<str>> {
55 lines: Vec<T>,
56 coord: Coord,
57 style: TextStyle<'a>,
58 line_height: f64,
59}
60
61impl<'a, Coord, T: Borrow<str>> MultiLineText<'a, Coord, T> {
62 pub fn new<S: Into<TextStyle<'a>>>(pos: Coord, style: S) -> Self {
68 MultiLineText {
69 lines: vec![],
70 coord: pos,
71 style: style.into(),
72 line_height: 1.25,
73 }
74 }
75
76 pub fn set_line_height(&mut self, value: f64) -> &mut Self {
78 self.line_height = value;
79 self
80 }
81
82 pub fn push_line<L: Into<T>>(&mut self, line: L) {
85 self.lines.push(line.into());
86 }
87
88 pub fn estimate_dimension(&self) -> FontResult<(i32, i32)> {
90 let (mut mx, mut my) = (0, 0);
91
92 for ((x, y), t) in self.layout_lines((0, 0)).zip(self.lines.iter()) {
93 let (dx, dy) = self.style.font.box_size(t.borrow())?;
94 mx = mx.max(x + dx as i32);
95 my = my.max(y + dy as i32);
96 }
97
98 Ok((mx, my))
99 }
100
101 pub fn relocate(&mut self, coord: Coord) {
103 self.coord = coord
104 }
105
106 fn layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator<Item = BackendCoord> {
107 let font_height = self.style.font.get_size();
108 let actual_line_height = font_height * self.line_height;
109 (0..self.lines.len() as u32).map(move |idx| {
110 let y = f64::from(y0) + f64::from(idx) * actual_line_height;
111 let x = f64::from(x0);
113 (x.round() as i32, y.round() as i32)
114 })
115 }
116}
117
118fn layout_multiline_text<'a, F: FnMut(&'a str)>(
123 text: &'a str,
124 max_width: u32,
125 font: FontDesc<'a>,
126 mut func: F,
127) {
128 for line in text.lines() {
129 if max_width == 0 || line.is_empty() {
130 func(line);
131 } else {
132 let mut indices = line.char_indices().map(|(idx, _)| idx).peekable();
133
134 let it = std::iter::from_fn(|| {
135 let start_idx = match indices.next() {
136 Some(idx) => idx,
137 None => return None,
138 };
139
140 for idx in indices.by_ref() {
142 let substring = &line[start_idx..idx];
143 let width = font.box_size(substring).unwrap_or((0, 0)).0 as i32;
144 if width > max_width as i32 {
145 break;
146 }
147 }
148
149 let end_idx = match indices.peek() {
150 Some(idx) => *idx,
151 None => line.bytes().len(),
152 };
153
154 Some(&line[start_idx..end_idx])
155 });
156
157 for chunk in it {
158 func(chunk);
159 }
160 }
161 }
162}
163
164#[cfg(all(feature = "ttf", target_os = "linux"))]
167#[test]
168fn test_multi_layout() {
169 use plotters_backend::{FontFamily, FontStyle};
170
171 let font = FontDesc::new(FontFamily::SansSerif, 20 as f64, FontStyle::Bold);
172
173 layout_multiline_text("öäabcde", 40, font, |txt| {
174 println!("Got: {}", txt);
175 assert!(txt == "öäabc" || txt == "de");
176 });
177
178 let font = FontDesc::new(FontFamily::SansSerif, 20 as f64, FontStyle::Bold);
179 layout_multiline_text("öä", 100, font, |txt| {
180 println!("Got: {}", txt);
183 assert_eq!(txt, "öä")
184 });
185}
186
187impl<'a, T: Borrow<str>> MultiLineText<'a, BackendCoord, T> {
188 pub fn compute_line_layout(&self) -> FontResult<Vec<LayoutBox>> {
190 let mut ret = vec![];
191 for ((x, y), t) in self.layout_lines(self.coord).zip(self.lines.iter()) {
192 let (dx, dy) = self.style.font.box_size(t.borrow())?;
193 ret.push(((x, y), (x + dx as i32, y + dy as i32)));
194 }
195 Ok(ret)
196 }
197}
198
199impl<'a, Coord> MultiLineText<'a, Coord, &'a str> {
200 pub fn from_str<ST: Into<&'a str>, S: Into<TextStyle<'a>>>(
209 text: ST,
210 pos: Coord,
211 style: S,
212 max_width: u32,
213 ) -> Self {
214 let text = text.into();
215 let mut ret = MultiLineText::new(pos, style);
216
217 layout_multiline_text(text, max_width, ret.style.font.clone(), |l| {
218 ret.push_line(l)
219 });
220 ret
221 }
222}
223
224impl<'a, Coord> MultiLineText<'a, Coord, String> {
225 pub fn from_string<S: Into<TextStyle<'a>>>(
234 text: String,
235 pos: Coord,
236 style: S,
237 max_width: u32,
238 ) -> Self {
239 let mut ret = MultiLineText::new(pos, style);
240
241 layout_multiline_text(text.as_str(), max_width, ret.style.font.clone(), |l| {
242 ret.push_line(l.to_string())
243 });
244 ret
245 }
246}
247
248impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord>
249 for &'a MultiLineText<'b, Coord, T>
250{
251 type Point = &'a Coord;
252 type IntoIter = std::iter::Once<&'a Coord>;
253 fn point_iter(self) -> Self::IntoIter {
254 std::iter::once(&self.coord)
255 }
256}
257
258impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB>
259 for MultiLineText<'a, Coord, T>
260{
261 fn draw<I: Iterator<Item = BackendCoord>>(
262 &self,
263 mut points: I,
264 backend: &mut DB,
265 _: (u32, u32),
266 ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
267 if let Some(a) = points.next() {
268 for (point, text) in self.layout_lines(a).zip(self.lines.iter()) {
269 backend.draw_text(text.borrow(), &self.style, point)?;
270 }
271 }
272 Ok(())
273 }
274}