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}
18pub trait IntoTextStyle<'a> {
19    fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a>;
20
21    fn with_color<C: Color>(self, color: C) -> TextStyleBuilder<'a, Self>
22    where
23        Self: Sized,
24    {
25        TextStyleBuilder {
26            base: self,
27            new_color: Some(color.to_backend_color()),
28            new_pos: None,
29            _phatom: std::marker::PhantomData,
30        }
31    }
32
33    fn with_anchor<C: Color>(self, pos: text_anchor::Pos) -> TextStyleBuilder<'a, Self>
34    where
35        Self: Sized,
36    {
37        TextStyleBuilder {
38            base: self,
39            new_pos: Some(pos),
40            new_color: None,
41            _phatom: std::marker::PhantomData,
42        }
43    }
44}
45
46pub struct TextStyleBuilder<'a, T: IntoTextStyle<'a>> {
47    base: T,
48    new_color: Option<BackendColor>,
49    new_pos: Option<text_anchor::Pos>,
50    _phatom: std::marker::PhantomData<&'a T>,
51}
52
53impl<'a, T: IntoTextStyle<'a>> IntoTextStyle<'a> for TextStyleBuilder<'a, T> {
54    fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> {
55        let mut base = self.base.into_text_style(parent);
56        if let Some(color) = self.new_color {
57            base.color = color;
58        }
59        if let Some(pos) = self.new_pos {
60            base = base.pos(pos);
61        }
62        base
63    }
64}
65
66impl<'a> TextStyle<'a> {
67    /// Sets the color of the style.
68    ///
69    /// - `color`: The required color
70    /// - **returns** The up-to-dated text style
71    ///
72    /// ```rust
73    /// use plotters::prelude::*;
74    ///
75    /// let style = TextStyle::from(("sans-serif", 20).into_font()).color(&RED);
76    /// ```
77    pub fn color<C: Color>(&self, color: &'a C) -> Self {
78        Self {
79            font: self.font.clone(),
80            color: color.to_backend_color(),
81            pos: self.pos,
82        }
83    }
84
85    /// Sets the font transformation of the style.
86    ///
87    /// - `trans`: The required transformation
88    /// - **returns** The up-to-dated text style
89    ///
90    /// ```rust
91    /// use plotters::prelude::*;
92    ///
93    /// let style = TextStyle::from(("sans-serif", 20).into_font()).transform(FontTransform::Rotate90);
94    /// ```
95    pub fn transform(&self, trans: FontTransform) -> Self {
96        Self {
97            font: self.font.clone().transform(trans),
98            color: self.color,
99            pos: self.pos,
100        }
101    }
102
103    /// Sets the anchor position.
104    ///
105    /// - `pos`: The required anchor position
106    /// - **returns** The up-to-dated text style
107    ///
108    /// ```rust
109    /// use plotters::prelude::*;
110    /// use plotters::style::text_anchor::{Pos, HPos, VPos};
111    ///
112    /// let pos = Pos::new(HPos::Left, VPos::Top);
113    /// let style = TextStyle::from(("sans-serif", 20).into_font()).pos(pos);
114    /// ```
115    pub fn pos(&self, pos: text_anchor::Pos) -> Self {
116        Self {
117            font: self.font.clone(),
118            color: self.color,
119            pos,
120        }
121    }
122}
123
124impl<'a> IntoTextStyle<'a> for FontDesc<'a> {
125    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
126        self.into()
127    }
128}
129
130impl<'a> IntoTextStyle<'a> for TextStyle<'a> {
131    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
132        self
133    }
134}
135
136impl<'a> IntoTextStyle<'a> for &'a str {
137    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
138        self.into()
139    }
140}
141
142impl<'a> IntoTextStyle<'a> for FontFamily<'a> {
143    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
144        self.into()
145    }
146}
147
148impl IntoTextStyle<'static> for u32 {
149    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'static> {
150        TextStyle::from((FontFamily::SansSerif, self))
151    }
152}
153
154impl IntoTextStyle<'static> for f64 {
155    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'static> {
156        TextStyle::from((FontFamily::SansSerif, self))
157    }
158}
159
160impl<'a, T: Color> IntoTextStyle<'a> for &'a T {
161    fn into_text_style<P: HasDimension>(self, _: &P) -> TextStyle<'a> {
162        TextStyle::from(FontFamily::SansSerif).color(self)
163    }
164}
165
166impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc> IntoTextStyle<'a> for (F, T) {
167    fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> {
168        (self.0.into(), self.1.in_pixels(parent)).into()
169    }
170}
171
172impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc, C: Color> IntoTextStyle<'a> for (F, T, &'a C) {
173    fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> {
174        IntoTextStyle::into_text_style((self.0, self.1), parent).color(self.2)
175    }
176}
177
178impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc> IntoTextStyle<'a> for (F, T, FontStyle) {
179    fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> {
180        (self.0.into(), self.1.in_pixels(parent), self.2).into()
181    }
182}
183
184impl<'a, F: Into<FontFamily<'a>>, T: SizeDesc, C: Color> IntoTextStyle<'a>
185    for (F, T, FontStyle, &'a C)
186{
187    fn into_text_style<P: HasDimension>(self, parent: &P) -> TextStyle<'a> {
188        IntoTextStyle::into_text_style((self.0, self.1, self.2), parent).color(self.3)
189    }
190}
191
192/// Make sure that we are able to automatically copy the `TextStyle`
193impl<'a, 'b: 'a> Into<TextStyle<'a>> for &'b TextStyle<'a> {
194    fn into(self) -> TextStyle<'a> {
195        self.clone()
196    }
197}
198
199impl<'a, T: Into<FontDesc<'a>>> From<T> for TextStyle<'a> {
200    fn from(font: T) -> Self {
201        Self {
202            font: font.into(),
203            color: BLACK.to_backend_color(),
204            pos: text_anchor::Pos::default(),
205        }
206    }
207}
208
209impl<'a> BackendTextStyle for TextStyle<'a> {
210    type FontError = FontError;
211    fn color(&self) -> BackendColor {
212        self.color
213    }
214
215    fn size(&self) -> f64 {
216        self.font.get_size()
217    }
218
219    fn transform(&self) -> FontTransform {
220        self.font.get_transform()
221    }
222
223    fn style(&self) -> FontStyle {
224        self.font.get_style()
225    }
226
227    #[allow(clippy::type_complexity)]
228    fn layout_box(&self, text: &str) -> Result<((i32, i32), (i32, i32)), Self::FontError> {
229        self.font.layout_box(text)
230    }
231
232    fn anchor(&self) -> text_anchor::Pos {
233        self.pos
234    }
235
236    fn family(&self) -> FontFamily {
237        self.font.get_family()
238    }
239
240    fn draw<E, DrawFunc: FnMut(i32, i32, BackendColor) -> Result<(), E>>(
241        &self,
242        text: &str,
243        pos: BackendCoord,
244        mut draw: DrawFunc,
245    ) -> Result<Result<(), E>, Self::FontError> {
246        let color = self.color.color();
247        self.font.draw(text, pos, move |x, y, a| {
248            let mix_color = color.mix(a as f64);
249            draw(x, y, mix_color)
250        })
251    }
252}