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, )]
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)] pub(crate) use imp::{KeyDrawing, Path};
32
33#[derive(Debug, Clone)]
34#[allow(dead_code)] pub 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 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); 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); 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}