1 | use super::color::Color; |
2 | use super::font::{FontDesc, FontError, FontFamily, FontStyle, FontTransform}; |
3 | use super::size::{HasDimension, SizeDesc}; |
4 | use super::BLACK; |
5 | pub use plotters_backend::text_anchor; |
6 | use plotters_backend::{BackendColor, BackendCoord, BackendStyle, BackendTextStyle}; |
7 | |
8 | /// Style of a text |
9 | #[derive (Clone)] |
10 | pub struct TextStyle<'a> { |
11 | /// The font description |
12 | pub font: FontDesc<'a>, |
13 | /// The text color |
14 | pub color: BackendColor, |
15 | /// The anchor point position |
16 | pub pos: text_anchor::Pos, |
17 | } |
18 | |
19 | /// Trait for values that can be converted into `TextStyle` values |
20 | pub trait IntoTextStyle<'a> { |
21 | /** Converts the value into a TextStyle value. |
22 | |
23 | `parent` is used in some cases to convert a font size from points to pixels. |
24 | |
25 | # Example |
26 | |
27 | ``` |
28 | use plotters::prelude::*; |
29 | let drawing_area = SVGBackend::new("into_text_style.svg" , (200, 100)).into_drawing_area(); |
30 | drawing_area.fill(&WHITE).unwrap(); |
31 | let text_style = ("sans-serif" , 20, &RED).into_text_style(&drawing_area); |
32 | drawing_area.draw_text("This is a big red label" , &text_style, (10, 50)).unwrap(); |
33 | ``` |
34 | |
35 | The result is a text label styled accordingly: |
36 | |
37 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@f030ed3/apidoc/into_text_style.svg) |
38 | |
39 | */ |
40 | fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a>; |
41 | |
42 | /** Specifies the color of the text element |
43 | |
44 | # Example |
45 | |
46 | ``` |
47 | use plotters::prelude::*; |
48 | let drawing_area = SVGBackend::new("with_color.svg" , (200, 100)).into_drawing_area(); |
49 | drawing_area.fill(&WHITE).unwrap(); |
50 | let text_style = ("sans-serif" , 20).with_color(RED).into_text_style(&drawing_area); |
51 | drawing_area.draw_text("This is a big red label" , &text_style, (10, 50)).unwrap(); |
52 | ``` |
53 | |
54 | The result is a text label styled accordingly: |
55 | |
56 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@f030ed3/apidoc/with_color.svg) |
57 | |
58 | # See also |
59 | |
60 | [`FontDesc::color()`] |
61 | |
62 | [`IntoTextStyle::into_text_style()`] for a more succinct example |
63 | |
64 | */ |
65 | fn with_color<C: Color>(self, color: C) -> TextStyleBuilder<'a, Self> |
66 | where |
67 | Self: Sized, |
68 | { |
69 | TextStyleBuilder { |
70 | base: self, |
71 | new_color: Some(color.to_backend_color()), |
72 | new_pos: None, |
73 | _phatom: std::marker::PhantomData, |
74 | } |
75 | } |
76 | |
77 | /** Specifies the position of the text anchor relative to the text element |
78 | |
79 | # Example |
80 | |
81 | ``` |
82 | use plotters::{prelude::*,style::text_anchor::{HPos, Pos, VPos}}; |
83 | let anchor_position = (200,100); |
84 | let anchor_left_bottom = Pos::new(HPos::Left, VPos::Bottom); |
85 | let anchor_right_top = Pos::new(HPos::Right, VPos::Top); |
86 | let drawing_area = SVGBackend::new("with_anchor.svg" , (400, 200)).into_drawing_area(); |
87 | drawing_area.fill(&WHITE).unwrap(); |
88 | drawing_area.draw(&Circle::new(anchor_position, 5, RED.filled())); |
89 | let text_style_right_top = BLACK.with_anchor::<RGBColor>(anchor_right_top).into_text_style(&drawing_area); |
90 | drawing_area.draw_text("The anchor sits at the right top of this label" , &text_style_right_top, anchor_position); |
91 | let text_style_left_bottom = BLACK.with_anchor::<RGBColor>(anchor_left_bottom).into_text_style(&drawing_area); |
92 | drawing_area.draw_text("The anchor sits at the left bottom of this label" , &text_style_left_bottom, anchor_position); |
93 | ``` |
94 | |
95 | The result has a red pixel at the center and two text labels positioned accordingly: |
96 | |
97 | ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@b0b94d5/apidoc/with_anchor.svg) |
98 | |
99 | # See also |
100 | |
101 | [`TextStyle::pos()`] |
102 | |
103 | */ |
104 | fn with_anchor<C: Color>(self, pos: text_anchor::Pos) -> TextStyleBuilder<'a, Self> |
105 | where |
106 | Self: Sized, |
107 | { |
108 | TextStyleBuilder { |
109 | base: self, |
110 | new_pos: Some(pos), |
111 | new_color: None, |
112 | _phatom: std::marker::PhantomData, |
113 | } |
114 | } |
115 | } |
116 | |
117 | pub struct TextStyleBuilder<'a, T: IntoTextStyle<'a>> { |
118 | base: T, |
119 | new_color: Option<BackendColor>, |
120 | new_pos: Option<text_anchor::Pos>, |
121 | _phatom: std::marker::PhantomData<&'a T>, |
122 | } |
123 | |
124 | impl<'a, T: IntoTextStyle<'a>> IntoTextStyle<'a> for TextStyleBuilder<'a, T> { |
125 | fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> { |
126 | let mut base: TextStyle<'_> = self.base.into_text_style(parent); |
127 | if let Some(color: BackendColor) = self.new_color { |
128 | base.color = color; |
129 | } |
130 | if let Some(pos: Pos) = self.new_pos { |
131 | base = base.pos(pos); |
132 | } |
133 | base |
134 | } |
135 | } |
136 | |
137 | impl<'a> TextStyle<'a> { |
138 | /// Sets the color of the style. |
139 | /// |
140 | /// - `color`: The required color |
141 | /// - **returns** The up-to-dated text style |
142 | /// |
143 | /// ```rust |
144 | /// use plotters::prelude::*; |
145 | /// |
146 | /// let style = TextStyle::from(("sans-serif" , 20).into_font()).color(&RED); |
147 | /// ``` |
148 | pub fn color<C: Color>(&self, color: &'a C) -> Self { |
149 | Self { |
150 | font: self.font.clone(), |
151 | color: color.to_backend_color(), |
152 | pos: self.pos, |
153 | } |
154 | } |
155 | |
156 | /// Sets the font transformation of the style. |
157 | /// |
158 | /// - `trans`: The required transformation |
159 | /// - **returns** The up-to-dated text style |
160 | /// |
161 | /// ```rust |
162 | /// use plotters::prelude::*; |
163 | /// |
164 | /// let style = TextStyle::from(("sans-serif" , 20).into_font()).transform(FontTransform::Rotate90); |
165 | /// ``` |
166 | pub fn transform(&self, trans: FontTransform) -> Self { |
167 | Self { |
168 | font: self.font.clone().transform(trans), |
169 | color: self.color, |
170 | pos: self.pos, |
171 | } |
172 | } |
173 | |
174 | /// Sets the anchor position. |
175 | /// |
176 | /// - `pos`: The required anchor position |
177 | /// - **returns** The up-to-dated text style |
178 | /// |
179 | /// ```rust |
180 | /// use plotters::prelude::*; |
181 | /// use plotters::style::text_anchor::{Pos, HPos, VPos}; |
182 | /// |
183 | /// let pos = Pos::new(HPos::Left, VPos::Top); |
184 | /// let style = TextStyle::from(("sans-serif" , 20).into_font()).pos(pos); |
185 | /// ``` |
186 | /// |
187 | /// # See also |
188 | /// |
189 | /// [`IntoTextStyle::with_anchor()`] |
190 | pub fn pos(&self, pos: text_anchor::Pos) -> Self { |
191 | Self { |
192 | font: self.font.clone(), |
193 | color: self.color, |
194 | pos, |
195 | } |
196 | } |
197 | } |
198 | |
199 | impl<'a> IntoTextStyle<'a> for FontDesc<'a> { |
200 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> { |
201 | self.into() |
202 | } |
203 | } |
204 | |
205 | impl<'a> IntoTextStyle<'a> for TextStyle<'a> { |
206 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> { |
207 | self |
208 | } |
209 | } |
210 | |
211 | impl<'a> IntoTextStyle<'a> for &'a str { |
212 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> { |
213 | self.into() |
214 | } |
215 | } |
216 | |
217 | impl<'a> IntoTextStyle<'a> for FontFamily<'a> { |
218 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> { |
219 | self.into() |
220 | } |
221 | } |
222 | |
223 | impl IntoTextStyle<'static> for u32 { |
224 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'static> { |
225 | TextStyle::from((FontFamily::SansSerif, self)) |
226 | } |
227 | } |
228 | |
229 | impl IntoTextStyle<'static> for f64 { |
230 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'static> { |
231 | TextStyle::from((FontFamily::SansSerif, self)) |
232 | } |
233 | } |
234 | |
235 | impl<'a, T: Color> IntoTextStyle<'a> for &'a T { |
236 | fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> { |
237 | TextStyle::from(FontFamily::SansSerif).color(self) |
238 | } |
239 | } |
240 | |
241 | impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc> IntoTextStyle<'a> for (F, T) { |
242 | fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> { |
243 | (self.0.into(), self.1.in_pixels(parent)).into() |
244 | } |
245 | } |
246 | |
247 | impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc, C: Color> IntoTextStyle<'a> for (F, T, &'a C) { |
248 | fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> { |
249 | IntoTextStyle::into_text_style((self.0, self.1), parent).color(self.2) |
250 | } |
251 | } |
252 | |
253 | impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc> IntoTextStyle<'a> for (F, T, FontStyle) { |
254 | fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> { |
255 | (self.0.into(), self.1.in_pixels(parent), self.2).into() |
256 | } |
257 | } |
258 | |
259 | impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc, C: Color> IntoTextStyle<'a> |
260 | for (F, T, FontStyle, &'a C) |
261 | { |
262 | fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> { |
263 | IntoTextStyle::into_text_style((self.0, self.1, self.2), parent).color(self.3) |
264 | } |
265 | } |
266 | |
267 | /// Make sure that we are able to automatically copy the `TextStyle` |
268 | impl<'a, 'b: 'a> From<&'b TextStyle<'a>> for TextStyle<'a> { |
269 | fn from(this: &'b TextStyle<'a>) -> Self { |
270 | this.clone() |
271 | } |
272 | } |
273 | |
274 | impl<'a, T: Into<FontDesc<'a>>> From<T> for TextStyle<'a> { |
275 | fn from(font: T) -> Self { |
276 | Self { |
277 | font: font.into(), |
278 | color: BLACK.to_backend_color(), |
279 | pos: text_anchor::Pos::default(), |
280 | } |
281 | } |
282 | } |
283 | |
284 | impl<'a> BackendTextStyle for TextStyle<'a> { |
285 | type FontError = FontError; |
286 | fn color(&self) -> BackendColor { |
287 | self.color |
288 | } |
289 | |
290 | fn size(&self) -> f64 { |
291 | self.font.get_size() |
292 | } |
293 | |
294 | fn transform(&self) -> FontTransform { |
295 | self.font.get_transform() |
296 | } |
297 | |
298 | fn style(&self) -> FontStyle { |
299 | self.font.get_style() |
300 | } |
301 | |
302 | #[allow (clippy::type_complexity)] |
303 | fn layout_box(&self, text: &str) -> Result<((i32, i32), (i32, i32)), Self::FontError> { |
304 | self.font.layout_box(text) |
305 | } |
306 | |
307 | fn anchor(&self) -> text_anchor::Pos { |
308 | self.pos |
309 | } |
310 | |
311 | fn family(&self) -> FontFamily { |
312 | self.font.get_family() |
313 | } |
314 | |
315 | fn draw<E, DrawFunc: FnMut(i32, i32, BackendColor) -> Result<(), E>>( |
316 | &self, |
317 | text: &str, |
318 | pos: BackendCoord, |
319 | mut draw: DrawFunc, |
320 | ) -> Result<Result<(), E>, Self::FontError> { |
321 | let color = self.color.color(); |
322 | self.font.draw(text, pos, move |x, y, a| { |
323 | let mix_color = color.mix(a as f64); |
324 | draw(x, y, mix_color) |
325 | }) |
326 | } |
327 | } |
328 | |