text_svg_plus/
text.rs

1use rusttype::{Font, Point, Rect, Scale};
2use svg::node::element::Path;
3
4pub struct Text {
5    pub path: Path,
6    pub data: String,
7    pub bounding_box: Rect<f32>,
8}
9
10impl Text {
11    pub fn new(path: Path, bounding_box: Rect<f32>, data: String) -> Self {
12        Self {
13            path,
14            bounding_box,
15            data,
16        }
17    }
18
19    pub fn builder() -> Builder<'static> {
20        Default::default()
21    }
22}
23
24pub struct Builder<'a> {
25    pub fill: &'a str,
26    pub size: f32,
27    pub start: Point<f32>,
28    pub letter_spacing: f32,
29}
30
31impl Default for Builder<'static> {
32    fn default() -> Self {
33        Self {
34            fill: "#000",
35            size: 32.,
36            start: Point { x: 0., y: 0. },
37            letter_spacing: 1.,
38        }
39    }
40}
41
42impl Builder<'_> {
43    pub fn size(mut self, size: f32) -> Self {
44        self.size = size;
45        self
46    }
47    pub fn start(mut self, start: Point<f32>) -> Self {
48        self.start = start;
49        self
50    }
51
52    pub fn build(&self, font: &Font, text: &str) -> Text {
53        let mut d = String::new();
54        let mut x = self.start.x;
55
56        let scale = Scale::uniform(self.size);
57        let v_metrics = font.v_metrics(scale);
58        let glyphs_height = v_metrics.ascent - v_metrics.descent;
59
60        for glyph in font.layout(
61            text,
62            scale,
63            Point {
64                x,
65                y: self.start.y + v_metrics.ascent,
66            },
67        ) {
68            let bounding_box = glyph.unpositioned().exact_bounding_box().unwrap();
69            x += bounding_box.min.x;
70
71            glyph.build_outline(&mut crate::Builder {
72                x: x,
73                y: glyphs_height + bounding_box.min.y,
74                d: &mut d,
75            });
76
77            x += bounding_box.width() + self.letter_spacing;
78        }
79
80        let bounding_box = Rect {
81            min: self.start,
82            max: Point {
83                x,
84                y: glyphs_height,
85            },
86        };
87        Text::new(
88            Path::new().set("d", d.clone()).set("fill", "#000"),
89            bounding_box,
90            d.clone(),
91        )
92    }
93}