1use itertools::Itertools;
2use swash::{
3 FontRef,
4 scale::{Render, ScaleContext, Source},
5};
6use zeno::{Fill, Format, Mask, PathBuilder, Point, Scratch, Stroke, Vector};
7
8use crate::glycan::{RenderedGlycan, render::element::Element};
9
10use super::element::{TextAnchor, TextBaseline};
11
12impl RenderedGlycan {
13 pub fn to_bitmap(
20 &self,
21 format: Format,
22 font: FontRef,
23 context: &mut ScaleContext,
24 ) -> (Vec<u8>, usize) {
25 let mask_factor = if format == Format::Alpha { 1 } else { 4 };
26 let image_width = self.size.0.ceil() as usize;
27 let mut image = std::iter::repeat_n(
28 [
29 self.background[0],
30 self.background[1],
31 self.background[2],
32 0,
33 ],
34 image_width * self.size.1.ceil() as usize,
35 )
36 .flatten()
37 .collect_vec();
38
39 let mut scratch = Scratch::new();
40 let mut stroke_mask = Vec::new();
41 let mut fill_mask = Vec::new();
42 for element in &self.elements {
43 let (x, y, mask_width, fill, stroke) = match element {
45 Element::Line {
46 from,
47 to,
48 stroke,
49 stroke_size,
50 } => {
51 let xmin = (from.0.min(to.0) - stroke_size).floor();
52 let xmax = (from.0.max(to.0) + stroke_size).ceil();
53 let ymin = (from.1.min(to.1) - stroke_size).floor();
54 let ymax = (from.1.max(to.1) + stroke_size).ceil();
55 let width = (xmax - xmin) as usize;
56 let height = (ymax - ymin) as usize;
57 let commands = vec![
58 zeno::Command::MoveTo(Vector::new(
59 from.0 - xmin + stroke_size / 2.0,
60 from.1 - ymin + stroke_size / 2.0,
61 )),
62 zeno::Command::LineTo(Vector::new(
63 to.0 - xmin + stroke_size / 2.0,
64 to.1 - ymin + stroke_size / 2.0,
65 )),
66 zeno::Command::Close,
67 ];
68 stroke_mask.fill(0);
69 stroke_mask.resize(height * width * mask_factor, 0);
70 Mask::with_scratch(&commands, &mut scratch)
71 .format(format)
72 .style(Stroke::new(*stroke_size))
73 .size(width as u32, height as u32)
74 .render_into(&mut stroke_mask, None);
75 (xmin as usize, ymin as usize, width, None, Some(*stroke))
76 }
77 Element::Circle {
78 r,
79 center,
80 fill,
81 stroke,
82 stroke_size,
83 svg_header: _,
84 } => {
85 let width = (center.0.fract() + r * 2.0 + stroke_size).ceil() as usize;
86 let height = (center.1.fract() + r * 2.0 + stroke_size).ceil() as usize;
87 let mut commands = Vec::new();
88 commands.add_circle(
89 (
90 center.0.fract() + r + stroke_size / 2.0,
91 center.1.fract() + r + stroke_size / 2.0,
92 ),
93 *r,
94 );
95 if fill.is_some() {
96 fill_mask.fill(0);
97 fill_mask.resize(height * width * mask_factor, 0);
98 Mask::with_scratch(&commands, &mut scratch)
99 .format(format)
100 .style(Fill::NonZero)
101 .size(width as u32, height as u32)
102 .render_into(&mut fill_mask, None);
103 }
104 stroke_mask.fill(0);
105 stroke_mask.resize(height * width * mask_factor, 0);
106 Mask::with_scratch(&commands, &mut scratch)
107 .format(format)
108 .style(Stroke::new(*stroke_size))
109 .size(width as u32, height as u32)
110 .render_into(&mut stroke_mask, None);
111 (
112 (center.0 - r) as usize,
113 (center.1 - r) as usize,
114 width,
115 *fill,
116 Some(*stroke),
117 )
118 }
119 Element::Rectangle {
120 top,
121 w,
122 h,
123 fill,
124 stroke,
125 stroke_size,
126 svg_header: _,
127 } => {
128 let width = (top.0.fract() + w + stroke_size).ceil() as usize;
129 let height = (top.1.fract() + h + stroke_size).ceil() as usize;
130 let mut commands = Vec::new();
131 commands.add_rect(
132 (
133 top.0.fract() + stroke_size / 2.0,
134 top.1.fract() + stroke_size / 2.0,
135 ),
136 *w,
137 *h,
138 );
139 fill_mask.fill(0);
140 fill_mask.resize(height * width * mask_factor, 0);
141 Mask::with_scratch(&commands, &mut scratch)
142 .format(format)
143 .style(Fill::NonZero)
144 .size(width as u32, height as u32)
145 .render_into(&mut fill_mask, None);
146 stroke_mask.fill(0);
147 stroke_mask.resize(height * width * mask_factor, 0);
148 Mask::with_scratch(&commands, &mut scratch)
149 .format(format)
150 .style(Stroke::new(*stroke_size))
151 .size(width as u32, height as u32)
152 .render_into(&mut stroke_mask, None);
153 (
154 (top.0 - stroke_size / 2.0) as usize,
155 (top.1 - stroke_size / 2.0) as usize,
156 width,
157 Some(*fill),
158 Some(*stroke),
159 )
160 }
161 Element::Polygon {
162 points,
163 fill,
164 stroke,
165 stroke_size,
166 svg_header: _,
167 bevel,
168 } => {
169 let (xmin, xmax, ymin, ymax) = points
170 .iter()
171 .fold((f32::MAX, f32::MIN, f32::MAX, f32::MIN), |acc, (x, y)| {
172 (acc.0.min(*x), acc.1.max(*x), acc.2.min(*y), acc.3.max(*y))
173 });
174 let xmin = (xmin - stroke_size).floor();
175 let xmax = (xmax + stroke_size).ceil();
176 let ymin = (ymin - stroke_size).floor();
177 let ymax = (ymax + stroke_size).ceil();
178 let width = (xmax - xmin) as usize;
179 let height = (ymax - ymin) as usize;
180 let mut commands = Vec::with_capacity(points.len() + 2);
181 commands.push(zeno::Command::MoveTo(Point::new(
182 points[0].0 - xmin + stroke_size / 2.0,
183 points[0].1 - ymin + stroke_size / 2.0,
184 )));
185 for point in points {
186 commands.push(zeno::Command::LineTo(Point::new(
187 point.0 - xmin + stroke_size / 2.0,
188 point.1 - ymin + stroke_size / 2.0,
189 )));
190 }
191 commands.push(zeno::Command::Close);
192 fill_mask.fill(0);
193 fill_mask.resize(height * width * mask_factor, 0);
194 Mask::with_scratch(&commands, &mut scratch)
195 .format(format)
196 .style(Fill::NonZero)
197 .size(width as u32, height as u32)
198 .render_into(&mut fill_mask, None);
199 stroke_mask.fill(0);
200 stroke_mask.resize(height * width * mask_factor, 0);
201 Mask::with_scratch(&commands, &mut scratch)
202 .format(format)
203 .style(Stroke::new(*stroke_size).join(if *bevel {
204 zeno::Join::Bevel
205 } else {
206 zeno::Join::Miter
207 }))
208 .size(width as u32, height as u32)
209 .render_into(&mut stroke_mask, None);
210 (
211 xmin as usize,
212 ymin as usize,
213 width,
214 Some(*fill),
215 Some(*stroke),
216 )
217 }
218 Element::Text {
219 text,
220 position,
221 anchor,
222 baseline,
223 fill,
224 size,
225 italic: _, } => {
227 let mut scaler = context.builder(font).size(*size).hint(true).build();
228 let metrics = font.metrics(&[]);
229 let normalisation_factor = size / f32::from(metrics.units_per_em);
230 let y_offset = (match baseline {
231 TextBaseline::Hanging => metrics.ascent,
232 TextBaseline::Middle => metrics.ascent - metrics.x_height / 2.0,
233 TextBaseline::Ideographic => metrics.ascent + metrics.descent,
234 })
235 .mul_add(-normalisation_factor, position.1);
236 let mut width = 0.0;
237 for c in text.chars() {
238 let id = font.charmap().map(c);
239 width += font.glyph_metrics(&[]).advance_width(id);
240 }
241
242 let x_offset = (match anchor {
243 TextAnchor::Start => 0.0,
244 TextAnchor::Middle => width / 2.0,
245 TextAnchor::End => width,
246 })
247 .mul_add(-normalisation_factor, position.0);
248
249 let mut offset = 0.0;
250 for c in text.chars() {
251 let id = font.charmap().map(c);
252 let glyph_metrics = font.glyph_metrics(&[]);
253 let mask = Render::new(&[Source::Outline])
254 .format(format)
255 .offset(Vector::new(
256 (x_offset + offset).fract(),
257 y_offset.fract() - 1.0,
258 ))
259 .render(&mut scaler, id)
260 .unwrap();
261 draw_mask(
262 (&mut image, image_width),
263 (&mask.data, mask.placement.width as usize),
264 (x_offset + offset + mask.placement.left as f32) as usize,
265 (y_offset + mask.placement.top as f32) as usize,
266 *fill,
267 format,
268 );
269
270 offset += glyph_metrics.advance_width(id) * normalisation_factor;
271 }
272 (0, 0, 0, None, None)
273 }
274 Element::Curve {
275 start,
276 points,
277 stroke,
278 stroke_size,
279 } => {
280 let (xmin, xmax, ymin, ymax) = points.iter().fold(
281 (f32::MAX, f32::MIN, f32::MAX, f32::MIN),
282 |acc, (a, b, x, y)| {
283 (
284 acc.0.min(*x).min(*a),
285 acc.1.max(*x).max(*a),
286 acc.2.min(*y).min(*b),
287 acc.3.max(*y).max(*b),
288 )
289 },
290 );
291 let xmin = (xmin - stroke_size).floor();
292 let xmax = (xmax + stroke_size).ceil();
293 let ymin = (ymin - stroke_size).floor();
294 let ymax = (ymax + stroke_size).ceil();
295 let width = (xmax - xmin) as usize;
296 let height = (ymax - ymin) as usize;
297 let mut commands = Vec::with_capacity(points.len() + 1);
298 commands.push(zeno::Command::MoveTo(Point::new(
299 start.0 - xmin + stroke_size / 2.0,
300 start.1 - ymin + stroke_size / 2.0,
301 )));
302 for point in points {
303 commands.push(zeno::Command::QuadTo(
304 Point::new(
305 point.0 - xmin + stroke_size / 2.0,
306 point.1 - ymin + stroke_size / 2.0,
307 ),
308 Point::new(
309 point.2 - xmin + stroke_size / 2.0,
310 point.3 - ymin + stroke_size / 2.0,
311 ),
312 ));
313 }
314 stroke_mask.fill(0);
315 stroke_mask.resize(height * width * mask_factor, 0);
316 Mask::with_scratch(&commands, &mut scratch)
317 .format(format)
318 .style(Stroke::new(*stroke_size))
319 .size(width as u32, height as u32)
320 .render_into(&mut stroke_mask, None);
321 (xmin as usize, ymin as usize, width, None, Some(*stroke))
322 }
323 };
324 if let Some(fill) = fill {
325 draw_mask(
326 (&mut image, image_width),
327 (&fill_mask, mask_width),
328 x,
329 y,
330 fill,
331 format,
332 );
333 }
334 if let Some(stroke) = stroke {
335 draw_mask(
336 (&mut image, image_width),
337 (&stroke_mask, mask_width),
338 x,
339 y,
340 stroke,
341 format,
342 );
343 }
344 }
345 (image, image_width)
346 }
347}
348
349#[allow(clippy::identity_op, clippy::needless_pass_by_value)] fn draw_mask(
352 image: (&mut [u8], usize),
353 mask: (&[u8], usize),
354 x: usize,
355 y: usize,
356 colour: [u8; 3],
357 format: Format,
358) {
359 let mask_factor = if format == Format::Alpha { 1 } else { 4 };
360 let mask_height = mask.0.len() / mask_factor / mask.1;
361 for r in 0..mask_height {
362 for w in 0..mask.1 {
363 let image_pos = ((r + y) * image.1 + (w + x)) * 4;
364 let mask_pos = (r * mask.1 + w) * mask_factor;
365
366 if image_pos >= image.0.len() || mask_pos >= mask.0.len() {
367 continue;
368 }
369
370 if format == Format::Alpha {
371 image.0[image_pos + 0] = blend(mask.0[mask_pos], colour[0], image.0[image_pos + 0]);
372 image.0[image_pos + 1] = blend(mask.0[mask_pos], colour[1], image.0[image_pos + 1]);
373 image.0[image_pos + 2] = blend(mask.0[mask_pos], colour[2], image.0[image_pos + 2]);
374 } else {
375 image.0[image_pos + 0] =
376 blend(mask.0[mask_pos + 0], colour[0], image.0[image_pos + 0]);
377 image.0[image_pos + 1] =
378 blend(mask.0[mask_pos + 1], colour[1], image.0[image_pos + 1]);
379 image.0[image_pos + 2] =
380 blend(mask.0[mask_pos + 2], colour[2], image.0[image_pos + 2]);
381 }
382 image.0[image_pos + 3] = 255;
383 }
384 }
385}
386
387const fn blend(alpha: u8, foreground: u8, background: u8) -> u8 {
388 (((alpha as u16 * foreground as u16) + (255 - alpha) as u16 * background as u16) / 255) as u8
389}