Skip to main content

plotchart/style/font/
font_desc.rs

1use super::{FontData, FontDataInternal};
2use crate::style::text_anchor::Pos;
3use crate::style::{Color, TextStyle};
4
5use std::convert::From;
6
7/// The error type for the font implementation
8pub type FontError = <FontDataInternal as FontData>::ErrorType;
9
10/// The type we used to represent a result of any font operations
11pub type FontResult<T> = Result<T, FontError>;
12
13/// Specifying text transformations
14#[derive(Clone)]
15pub enum FontTransform {
16    /// Nothing to transform
17    None,
18    /// Rotating the text 90 degree clockwise
19    Rotate90,
20    /// Rotating the text 180 degree clockwise
21    Rotate180,
22    /// Rotating the text 270 degree clockwise
23    Rotate270,
24}
25
26impl FontTransform {
27    /// Transform the coordinate to perform the rotation
28    ///
29    /// - `x`: The x coordinate in pixels before transform
30    /// - `y`: The y coordinate in pixels before transform
31    /// - **returns**: The coordinate after transform
32    pub fn transform(&self, x: i32, y: i32) -> (i32, i32) {
33        match self {
34            FontTransform::None => (x, y),
35            FontTransform::Rotate90 => (-y, x),
36            FontTransform::Rotate180 => (-x, -y),
37            FontTransform::Rotate270 => (y, -x),
38        }
39    }
40}
41
42/// Describes a font
43#[derive(Clone)]
44pub struct FontDesc<'a> {
45    size: f64,
46    family: FontFamily<'a>,
47    data: FontResult<FontDataInternal>,
48    transform: FontTransform,
49    style: FontStyle,
50}
51
52/// Describes font family.
53/// This can be either a specific font family name, such as "arial",
54/// or a general font family class, such as "serif" and "sans-serif"
55#[derive(Clone, Copy)]
56pub enum FontFamily<'a> {
57    /// The system default serif font family
58    Serif,
59    /// The system default sans-serif font family
60    SansSerif,
61    /// The system default monospace font
62    Monospace,
63    /// A specific font family name
64    Name(&'a str),
65}
66
67impl<'a> FontFamily<'a> {
68    /// Make a CSS compatible string for the font family name.
69    /// This can be used as the value of `font-family` attribute in SVG.
70    pub fn as_str(&self) -> &str {
71        match self {
72            FontFamily::Serif => "宋体",
73            FontFamily::SansSerif => "宋体",
74            FontFamily::Monospace => "monospace",
75            FontFamily::Name(face) => face,
76        }
77    }
78}
79
80impl<'a> From<&'a str> for FontFamily<'a> {
81    fn from(from: &'a str) -> FontFamily<'a> {
82        match from.to_lowercase().as_str() {
83            "serif" => FontFamily::Serif,
84            "sans-serif" => FontFamily::SansSerif,
85            "monospace" => FontFamily::Monospace,
86            _ => FontFamily::Name(from),
87        }
88    }
89}
90
91/// Describes the font style. Such as Italic, Oblique, etc.
92#[derive(Clone, Copy)]
93pub enum FontStyle {
94    /// The normal style
95    Normal,
96    /// The oblique style
97    Oblique,
98    /// The italic style
99    Italic,
100    /// The bold style
101    Bold,
102}
103
104impl FontStyle {
105    /// Convert the font style into a CSS compatible string which can be used in `font-style` attribute.
106    pub fn as_str(&self) -> &str {
107        match self {
108            FontStyle::Normal => "normal",
109            FontStyle::Italic => "italic",
110            FontStyle::Oblique => "oblique",
111            FontStyle::Bold => "bold",
112        }
113    }
114}
115
116impl<'a> From<&'a str> for FontStyle {
117    fn from(from: &'a str) -> FontStyle {
118        match from.to_lowercase().as_str() {
119            "normal" => FontStyle::Normal,
120            "italic" => FontStyle::Italic,
121            "oblique" => FontStyle::Oblique,
122            "bold" => FontStyle::Bold,
123            _ => FontStyle::Normal,
124        }
125    }
126}
127
128impl<'a> From<&'a str> for FontDesc<'a> {
129    fn from(from: &'a str) -> FontDesc<'a> {
130        FontDesc::new(from.into(), 1.0, FontStyle::Normal)
131    }
132}
133
134impl<'a> From<FontFamily<'a>> for FontDesc<'a> {
135    fn from(family: FontFamily<'a>) -> FontDesc<'a> {
136        FontDesc::new(family, 1.0, FontStyle::Normal)
137    }
138}
139
140impl<'a, T: Into<f64>> From<(FontFamily<'a>, T)> for FontDesc<'a> {
141    fn from((family, size): (FontFamily<'a>, T)) -> FontDesc<'a> {
142        FontDesc::new(family, size.into(), FontStyle::Normal)
143    }
144}
145
146impl<'a, T: Into<f64>> From<(&'a str, T)> for FontDesc<'a> {
147    fn from((typeface, size): (&'a str, T)) -> FontDesc<'a> {
148        FontDesc::new(typeface.into(), size.into(), FontStyle::Normal)
149    }
150}
151
152impl<'a, T: Into<f64>, S: Into<FontStyle>> From<(FontFamily<'a>, T, S)> for FontDesc<'a> {
153    fn from((family, size, style): (FontFamily<'a>, T, S)) -> FontDesc<'a> {
154        FontDesc::new(family, size.into(), style.into())
155    }
156}
157
158impl<'a, T: Into<f64>, S: Into<FontStyle>> From<(&'a str, T, S)> for FontDesc<'a> {
159    fn from((typeface, size, style): (&'a str, T, S)) -> FontDesc<'a> {
160        FontDesc::new(typeface.into(), size.into(), style.into())
161    }
162}
163
164/// The trait that allows some type turns into a font description
165pub trait IntoFont<'a> {
166    /// Make the font description from the source type
167    fn into_font(self) -> FontDesc<'a>;
168}
169
170impl<'a, T: Into<FontDesc<'a>>> IntoFont<'a> for T {
171    fn into_font(self) -> FontDesc<'a> {
172        self.into()
173    }
174}
175
176impl<'a> FontDesc<'a> {
177    /// Create a new font
178    ///
179    /// - `family`: The font family name
180    /// - `size`: The size of the font
181    /// - `style`: The font variations
182    /// - **returns** The newly created font description
183    pub fn new(family: FontFamily<'a>, size: f64, style: FontStyle) -> Self {
184        Self {
185            size,
186            family,
187            data: FontDataInternal::new(family, style),
188            transform: FontTransform::None,
189            style,
190        }
191    }
192
193    /// Create a new font desc with the same font but different size
194    ///
195    /// - `size`: The new size to set
196    /// - **returns** The newly created font descriptor with a new size
197    pub fn resize(&self, size: f64) -> FontDesc<'a> {
198        Self {
199            size,
200            family: self.family,
201            data: self.data.clone(),
202            transform: self.transform.clone(),
203            style: self.style,
204        }
205    }
206
207    /// Set the style of the font
208    ///
209    /// - `style`: The new style
210    /// - **returns** The new font description with this style applied
211    pub fn style(&self, style: FontStyle) -> Self {
212        Self {
213            size: self.size,
214            family: self.family,
215            data: self.data.clone(),
216            transform: self.transform.clone(),
217            style,
218        }
219    }
220
221    /// Set the font transformation
222    ///
223    /// - `trans`: The new transformation
224    /// - **returns** The new font description with this font transformation applied
225    pub fn transform(&self, trans: FontTransform) -> Self {
226        Self {
227            size: self.size,
228            family: self.family,
229            data: self.data.clone(),
230            transform: trans,
231            style: self.style,
232        }
233    }
234
235    /// Get the font transformation description
236    pub fn get_transform(&self) -> FontTransform {
237        self.transform.clone()
238    }
239
240    /// Set the color of the font and return the result text style object
241    pub fn color<C: Color>(&self, color: &C) -> TextStyle<'a> {
242        TextStyle {
243            font: self.clone(),
244            color: color.to_rgba(),
245            pos: Pos::default(),
246        }
247    }
248
249    /// Get the name of the font
250    pub fn get_name(&self) -> &str {
251        self.family.as_str()
252    }
253
254    /// Get the name of the style
255    pub fn get_style(&self) -> FontStyle {
256        self.style
257    }
258
259    /// Get the size of font
260    pub fn get_size(&self) -> f64 {
261        self.size
262    }
263
264    /// Get the size of the text if rendered in this font
265    ///
266    /// For a TTF type, zero point of the layout box is the left most baseline char of the string
267    /// Thus the upper bound of the box is most likely be negative
268    pub fn layout_box(&self, text: &str) -> FontResult<((i32, i32), (i32, i32))> {
269        match &self.data {
270            Ok(ref font) => font.estimate_layout(self.size, text),
271            Err(e) => Err(e.clone()),
272        }
273    }
274
275    /// Get the size of the text if rendered in this font.
276    /// This is similar to `layout_box` function, but it apply the font transformation
277    /// and estimate the overall size of the font
278    pub fn box_size(&self, text: &str) -> FontResult<(u32, u32)> {
279        let ((min_x, min_y), (max_x, max_y)) = self.layout_box(text)?;
280        let (w, h) = self.get_transform().transform(max_x - min_x, max_y - min_y);
281        Ok((w.abs() as u32, h.abs() as u32))
282    }
283
284    /// Actually draws a font with a drawing function
285    pub fn draw<E, DrawFunc: FnMut(i32, i32, f32) -> Result<(), E>>(
286        &self,
287        text: &str,
288        (x, y): (i32, i32),
289        draw: DrawFunc,
290    ) -> FontResult<Result<(), E>> {
291        match &self.data {
292            Ok(ref font) => font.draw((x, y), self.size, text, draw),
293            Err(e) => Err(e.clone()),
294        }
295    }
296}