plotters/style/
text.rs

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}