1use std::path::Path;
2
3use ab_glyph::{Font, FontRef, PxScale, ScaleFont};
4use image::{Rgba, RgbaImage};
5use imageproc::drawing::draw_text_mut;
6
7use crate::error::{Error, Result};
8
9#[derive(Clone)]
10pub struct TextOptions {
11 pub font_size: f32,
12 pub color: Rgba<u8>,
13 pub background: Rgba<u8>,
14 pub padding: u32,
15}
16
17impl Default for TextOptions {
18 fn default() -> Self {
19 TextOptions {
20 font_size: 72.0,
21 color: Rgba([0, 0, 0, 255]),
22 background: Rgba([255, 255, 255, 255]),
23 padding: 40,
24 }
25 }
26}
27
28#[derive(Clone)]
29pub struct Input {
30 image: RgbaImage,
31 opaque: bool,
32 pub layer: u8,
33 pub xs: f64,
34 pub ys: f64,
35 pub xo: f64,
36 pub yo: f64,
37 pub xa: f64,
38 pub ya: f64,
39 pub in_scale: i32,
40 pub in_x0: f64,
41 pub in_y0: f64,
42}
43
44impl Default for Input {
45 fn default() -> Self {
46 Input {
47 image: RgbaImage::new(1, 1),
48 opaque: false,
49 layer: 1,
50 xs: 1.0,
51 ys: 1.0,
52 xo: 0.0,
53 yo: 0.0,
54 xa: 1.0,
55 ya: 0.0,
56 in_scale: 0,
57 in_x0: 0.0,
58 in_y0: 0.0,
59 }
60 }
61}
62
63impl Input {
64 pub fn new() -> Self {
65 Input::default()
66 }
67
68 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
69 Self::load_with_layer(path, 1)
70 }
71
72 pub fn load_with_layer<P: AsRef<Path>>(path: P, layer: u8) -> Result<Self> {
73 let path = path.as_ref();
74
75 if !path.exists() {
76 return Err(Error::FileNotFound(path.to_path_buf()));
77 }
78
79 let img = image::open(path)?;
80 let opaque = !img.color().has_alpha();
81 let rgba = img.to_rgba8();
82
83 let mut input = Input { image: rgba, opaque, layer, ..Default::default() };
84
85 input.compute_scale_params();
86
87 Ok(input)
88 }
89
90 pub fn from_image(image: RgbaImage, layer: u8) -> Self {
91 let opaque = image.pixels().all(|pixel| pixel[3] == 255);
92 let mut input = Input { image, opaque, layer, ..Default::default() };
93 input.compute_scale_params();
94 input
95 }
96
97 pub fn from_text(
98 text: &str,
99 layer: u8,
100 options: &TextOptions,
101 font_data: &[u8],
102 ) -> Result<Self> {
103 let font = FontRef::try_from_slice(font_data)
104 .map_err(|e| Error::TextRender(format!("Failed to load font: {}", e)))?;
105
106 let scale = PxScale::from(options.font_size);
107 let scaled_font = font.as_scaled(scale);
108
109 let mut width = 0.0f32;
110 for c in text.chars() {
111 let glyph_id = font.glyph_id(c);
112 width += scaled_font.h_advance(glyph_id);
113 }
114
115 let height = scaled_font.height();
116 let img_width = (width.ceil() as u32).max(1) + options.padding * 2;
117 let img_height = (height.ceil() as u32).max(1) + options.padding * 2;
118
119 let mut image = RgbaImage::from_pixel(img_width, img_height, options.background);
120
121 let x = options.padding as i32;
122 let y = options.padding as i32;
123
124 draw_text_mut(&mut image, options.color, x, y, scale, &font, text);
125
126 let opaque = options.color[3] == 255 && options.background[3] == 255;
127 let mut input = Input { image, opaque, layer, ..Default::default() };
128 input.compute_scale_params();
129
130 Ok(input)
131 }
132
133 fn compute_scale_params(&mut self) {
134 let w = self.image.width() as i32;
135 let h = self.image.height() as i32;
136
137 self.in_scale = w;
138 self.in_x0 = 0.0;
139 self.in_y0 = 0.0;
140
141 if h > w {
142 self.in_scale = h;
143 self.in_x0 = -((-w + h) as f64) / 2.0;
144 } else {
145 self.in_y0 = -((w - h) as f64) / 2.0;
146 }
147
148 self.xa = 1.0;
149 self.ya = 0.0;
150 }
151
152 pub fn get(&self) -> &RgbaImage {
153 &self.image
154 }
155
156 pub fn get_mut(&mut self) -> &mut RgbaImage {
157 self.opaque = false;
158 &mut self.image
159 }
160
161 pub fn is_opaque(&self) -> bool {
162 self.opaque
163 }
164
165 pub fn width(&self) -> u32 {
166 self.image.width()
167 }
168
169 pub fn height(&self) -> u32 {
170 self.image.height()
171 }
172
173 pub fn safe_pixel(&self, x: i32, y: i32) -> Rgba<u8> {
174 let w = self.image.width() as i32;
175 let h = self.image.height() as i32;
176
177 if x < 0 || y < 0 || x >= w || y >= h {
178 Rgba([0, 0, 0, 0])
179 } else {
180 *self.image.get_pixel(x as u32, y as u32)
181 }
182 }
183
184 pub fn set_rotation(&mut self, theta: f64) {
185 self.xa = theta.cos();
186 self.ya = theta.sin();
187 }
188}
189
190#[derive(Clone, Default)]
191pub struct Inputs {
192 data: Vec<Input>,
193}
194
195impl Inputs {
196 pub fn new() -> Self {
197 Inputs { data: Vec::new() }
198 }
199
200 pub fn add(&mut self) -> &mut Input {
201 self.data.push(Input::new());
202 self.data.last_mut().unwrap()
203 }
204
205 pub fn push(&mut self, input: Input) {
206 self.data.push(input);
207 }
208
209 pub fn get(&self) -> &[Input] {
210 &self.data
211 }
212
213 pub fn get_mut(&mut self) -> &mut [Input] {
214 &mut self.data
215 }
216
217 pub fn len(&self) -> usize {
218 self.data.len()
219 }
220
221 pub fn is_empty(&self) -> bool {
222 self.data.is_empty()
223 }
224
225 pub fn iter(&self) -> impl Iterator<Item = &Input> {
226 self.data.iter()
227 }
228
229 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Input> {
230 self.data.iter_mut()
231 }
232}
233
234impl std::ops::Index<usize> for Inputs {
235 type Output = Input;
236
237 fn index(&self, index: usize) -> &Self::Output {
238 &self.data[index]
239 }
240}
241
242impl std::ops::IndexMut<usize> for Inputs {
243 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
244 &mut self.data[index]
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn opacity_is_detected_and_invalidated_by_mutable_access() {
254 let image = RgbaImage::from_pixel(2, 2, Rgba([10, 20, 30, 255]));
255 let mut input = Input::from_image(image, 1);
256 assert!(input.is_opaque());
257
258 input.get_mut().put_pixel(0, 0, Rgba([10, 20, 30, 255]));
259 assert!(!input.is_opaque());
260
261 let image = RgbaImage::from_pixel(2, 2, Rgba([10, 20, 30, 254]));
262 assert!(!Input::from_image(image, 1).is_opaque());
263 }
264
265 #[test]
266 fn text_opacity_follows_foreground_and_background_alpha() {
267 let font = include_bytes!("../../../fonts/DejaVuSans-Bold.ttf");
268 let opaque = Input::from_text("opaque", 1, &TextOptions::default(), font)
269 .expect("render opaque text");
270 assert!(opaque.is_opaque());
271
272 for options in [
273 TextOptions { color: Rgba([0, 0, 0, 254]), ..Default::default() },
274 TextOptions { background: Rgba([255, 255, 255, 254]), ..Default::default() },
275 ] {
276 let transparent =
277 Input::from_text("transparent", 1, &options, font).expect("render text");
278 assert!(!transparent.is_opaque());
279 }
280 }
281}