1use super::color::Color;
2use super::font::{FontDesc, FontError, FontFamily, FontStyle, FontTransform};
3use super::size::{HasDimension, SizeDesc};
4use super::BLACK;
5pub use plotters_backend::text_anchor;
6use plotters_backend::{BackendColor, BackendCoord, BackendStyle, BackendTextStyle};
7
8/// Style of a text
9#[derive(Clone)]
10pub 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
20pub 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
117pub 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
124impl<'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 = self.base.into_text_style(parent);
127 if let Some(color) = self.new_color {
128 base.color = color;
129 }
130 if let Some(pos) = self.new_pos {
131 base = base.pos(pos);
132 }
133 base
134 }
135}
136
137impl<'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
199impl<'a> IntoTextStyle<'a> for FontDesc<'a> {
200 fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
201 self.into()
202 }
203}
204
205impl<'a> IntoTextStyle<'a> for TextStyle<'a> {
206 fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
207 self
208 }
209}
210
211impl<'a> IntoTextStyle<'a> for &'a str {
212 fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
213 self.into()
214 }
215}
216
217impl<'a> IntoTextStyle<'a> for FontFamily<'a> {
218 fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
219 self.into()
220 }
221}
222
223impl IntoTextStyle<'static> for u32 {
224 fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'static> {
225 TextStyle::from((FontFamily::SansSerif, self))
226 }
227}
228
229impl IntoTextStyle<'static> for f64 {
230 fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'static> {
231 TextStyle::from((FontFamily::SansSerif, self))
232 }
233}
234
235impl<'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
241impl<'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
247impl<'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
253impl<'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
259impl<'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`
268impl<'a, 'b: 'a> From<&'b TextStyle<'a>> for TextStyle<'a> {
269 fn from(this: &'b TextStyle<'a>) -> Self {
270 this.clone()
271 }
272}
273
274impl<'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
284impl<'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