keyset_drawing/
lib.rs

1#![warn(
2    missing_docs,
3    clippy::all,
4    clippy::correctness,
5    clippy::suspicious,
6    clippy::style,
7    clippy::complexity,
8    clippy::perf,
9    clippy::pedantic,
10    clippy::cargo,
11    clippy::nursery
12)]
13#![allow(
14    missing_docs, // TODO
15)]
16
17mod imp;
18#[cfg(feature = "pdf")]
19mod pdf;
20#[cfg(feature = "png")]
21mod png;
22#[cfg(feature = "svg")]
23mod svg;
24
25use font::Font;
26use geom::{Point, Rect, Size};
27use key::Key;
28use profile::Profile;
29
30#[allow(unused_imports)] // Path is unused if no format is enabled, but who would do that?
31pub(crate) use imp::{KeyDrawing, Path};
32
33#[derive(Debug, Clone)]
34#[allow(dead_code)] // Struct fields are unused if no format is enabled, but who would do that?
35pub struct Drawing {
36    bounds: Rect,
37    keys: Vec<KeyDrawing>,
38    scale: f64,
39}
40
41impl Drawing {
42    #[must_use]
43    pub fn new(keys: &[Key], options: &Options) -> Self {
44        let bounds = keys
45            .iter()
46            .map(|k| k.shape.outer_rect().with_origin(k.position))
47            .fold(
48                Rect::from_origin_size(Point::ORIGIN, Size::new(1., 1.)),
49                |rect, key| rect.union(key),
50            );
51
52        let keys = keys
53            .iter()
54            .map(|key| KeyDrawing::new(key, options))
55            .collect();
56
57        Self {
58            bounds,
59            keys,
60            scale: options.scale,
61        }
62    }
63
64    #[cfg(feature = "pdf")]
65    #[must_use]
66    pub fn to_svg(&self) -> String {
67        svg::draw(self)
68    }
69
70    #[cfg(feature = "pdf")]
71    #[must_use]
72    pub fn to_png(&self, dpi: f64) -> Vec<u8> {
73        png::draw(self, dpi)
74    }
75
76    #[cfg(feature = "pdf")]
77    #[must_use]
78    pub fn to_pdf(&self) -> Vec<u8> {
79        pdf::draw(self)
80    }
81
82    #[cfg(feature = "pdf")]
83    #[must_use]
84    pub fn to_ai(&self) -> Vec<u8> {
85        // An Illustrator file typically contains both an Illustrator-native and a PDF copy of an
86        // image. Most other software (including Adobe's own InDesign) use the PDF data and not the
87        // native Illustrator format. Illustrator also happily opens a PDF with .ai file extension,
88        // so we just generate a PDF when exporting an Illustrator file. I have not come across any
89        // compatibility issues using this approach, but I do recommend checking your files in
90        // Illustrator just in case.
91        pdf::draw(self)
92    }
93}
94
95#[derive(Debug, Clone)]
96pub struct Options<'a> {
97    profile: &'a Profile,
98    font: &'a Font,
99    scale: f64,
100    outline_width: f64,
101    show_keys: bool,
102    show_margin: bool,
103}
104
105impl<'a> Default for Options<'a> {
106    fn default() -> Self {
107        Self {
108            profile: &Profile::DEFAULT,
109            font: Font::default_ref(),
110            scale: 1.0,
111            outline_width: 10.0,
112            show_keys: true,
113            show_margin: false,
114        }
115    }
116}
117
118impl<'a> Options<'a> {
119    #[must_use]
120    pub fn new() -> Self {
121        Self::default()
122    }
123
124    #[must_use]
125    pub const fn profile(self, profile: &'a Profile) -> Self {
126        Self { profile, ..self }
127    }
128
129    #[must_use]
130    pub const fn font(self, font: &'a Font) -> Self {
131        Self { font, ..self }
132    }
133
134    #[must_use]
135    pub const fn scale(self, scale: f64) -> Self {
136        Self { scale, ..self }
137    }
138
139    #[must_use]
140    pub const fn outline_width(self, outline_width: f64) -> Self {
141        Self {
142            outline_width,
143            ..self
144        }
145    }
146
147    #[must_use]
148    pub const fn show_keys(self, show_keys: bool) -> Self {
149        Self { show_keys, ..self }
150    }
151
152    #[must_use]
153    pub const fn show_margin(self, show_margin: bool) -> Self {
154        Self {
155            show_margin,
156            ..self
157        }
158    }
159
160    #[must_use]
161    pub fn draw(&self, keys: &[Key]) -> Drawing {
162        Drawing::new(keys, self)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use assert_approx_eq::assert_approx_eq;
169
170    use profile::Profile;
171
172    use super::*;
173
174    #[test]
175    fn test_drawing_options() {
176        let options = Options::default();
177
178        assert_approx_eq!(options.scale, 1.);
179        assert_eq!(options.font.num_glyphs(), 1); // .notdef
180
181        let profile = Profile::default();
182        let font = Font::from_ttf(std::fs::read(env!("DEMO_TTF")).unwrap()).unwrap();
183        let options = Options::new()
184            .profile(&profile)
185            .font(&font)
186            .scale(2.)
187            .outline_width(20.)
188            .show_keys(false)
189            .show_margin(true);
190
191        assert_eq!(options.profile.typ.depth(), 1.0);
192        assert_eq!(options.font.num_glyphs(), 3); // .notdef, A, V
193        assert_eq!(options.scale, 2.0);
194    }
195
196    #[test]
197    fn test_drawing_options_draw() {
198        let options = Options::new();
199        let keys = [Key::example()];
200
201        let drawing = options.draw(&keys);
202
203        assert_eq!(drawing.bounds.width(), 1.);
204        assert_eq!(drawing.bounds.height(), 1.);
205        assert_eq!(drawing.keys.len(), 1);
206        assert_eq!(drawing.scale, options.scale);
207    }
208}