1use crate::color::Color;
11use crate::geometry::{Coord, Corners, Point, Rect, Size};
12use crate::render::{Context as RenderContext, Path, PathBuilder, Raster, RasterBuilder};
13use anyhow::{Context, Error, anyhow};
14use euclid::default::{Box2D, Size2D, Transform2D, Vector2D};
15use euclid::{Angle, point2, size2, vec2};
16
17use serde::{Deserialize, Serialize};
18use std::collections::BTreeMap;
19use std::convert::TryFrom;
20use std::fs::File;
21use std::path::PathBuf;
22use std::slice;
23use std::str::FromStr;
24use ttf_parser::Face;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
30#[allow(missing_docs)]
31pub enum DisplayRotation {
32 Deg0,
33 Deg90,
34 Deg180,
35 Deg270,
36}
37
38impl DisplayRotation {
39 pub fn transform(&self, target_size: &Size2D<Coord>) -> Transform2D<Coord> {
41 let w = target_size.width;
42 let h = target_size.height;
43 match self {
44 Self::Deg0 => Transform2D::identity(),
45 Self::Deg90 => Transform2D::from_array([0.0, -1.0, 1.0, 0.0, 0.0, h]),
46 Self::Deg180 => Transform2D::from_array([-1.0, 0.0, 0.0, -1.0, w, h]),
47 Self::Deg270 => Transform2D::from_array([0.0, 1.0, -1.0, 0.0, w, 0.0]),
48 }
49 }
50
51 pub fn inv_transform(&self, target_size: &Size2D<Coord>) -> Transform2D<Coord> {
53 let w = target_size.width;
54 let h = target_size.height;
55 match self {
56 Self::Deg0 => Transform2D::identity(),
57 Self::Deg90 => Transform2D::from_array([0.0, 1.0, -1.0, 0.0, h, 0.0]),
58 Self::Deg180 => Transform2D::from_array([-1.0, 0.0, 0.0, -1.0, w, h]),
59 Self::Deg270 => Transform2D::from_array([0.0, -1.0, 1.0, 0.0, 0.0, w]),
60 }
61 }
62}
63
64impl Default for DisplayRotation {
65 fn default() -> Self {
66 Self::Deg0
67 }
68}
69
70impl From<DisplayRotation> for Angle<Coord> {
71 fn from(display_rotation: DisplayRotation) -> Self {
72 let degrees = match display_rotation {
73 DisplayRotation::Deg0 => 0.0,
74 DisplayRotation::Deg90 => 90.0,
75 DisplayRotation::Deg180 => 180.0,
76 DisplayRotation::Deg270 => 270.0,
77 };
78 Angle::degrees(degrees)
79 }
80}
81
82impl TryFrom<u32> for DisplayRotation {
83 type Error = Error;
84
85 fn try_from(num: u32) -> Result<Self, Self::Error> {
86 match num {
87 0 => Ok(DisplayRotation::Deg0),
88 90 => Ok(DisplayRotation::Deg90),
89 180 => Ok(DisplayRotation::Deg180),
90 270 => Ok(DisplayRotation::Deg270),
91 _ => Err(anyhow!("Invalid DisplayRotation {}", num)),
92 }
93 }
94}
95
96impl FromStr for DisplayRotation {
97 type Err = Error;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 match s {
101 "0" => Ok(DisplayRotation::Deg0),
102 "90" => Ok(DisplayRotation::Deg90),
103 "180" => Ok(DisplayRotation::Deg180),
104 "270" => Ok(DisplayRotation::Deg270),
105 _ => Err(anyhow!("Invalid DisplayRotation {}", s)),
106 }
107 }
108}
109
110pub fn path_for_rectangle(bounds: &Rect, render_context: &mut RenderContext) -> Path {
112 let mut path_builder = render_context.path_builder().expect("path_builder");
113 path_builder
114 .move_to(bounds.origin)
115 .line_to(bounds.top_right())
116 .line_to(bounds.bottom_right())
117 .line_to(bounds.bottom_left())
118 .line_to(bounds.origin);
119 path_builder.build()
120}
121
122pub fn path_for_rounded_rectangle(
124 bounds: &Rect,
125 corner_radius: Coord,
126 render_context: &mut RenderContext,
127) -> Path {
128 let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
129 let control_dist = kappa * corner_radius;
130
131 let top_left_arc_start = bounds.origin + vec2(0.0, corner_radius);
132 let top_left_arc_end = bounds.origin + vec2(corner_radius, 0.0);
133 let top_left_curve_center = bounds.origin + vec2(corner_radius, corner_radius);
134 let top_left_p1 = top_left_curve_center + vec2(-corner_radius, -control_dist);
135 let top_left_p2 = top_left_curve_center + vec2(-control_dist, -corner_radius);
136
137 let top_right = bounds.top_right();
138 let top_right_arc_start = top_right + vec2(-corner_radius, 0.0);
139 let top_right_arc_end = top_right + vec2(0.0, corner_radius);
140 let top_right_curve_center = top_right + vec2(-corner_radius, corner_radius);
141 let top_right_p1 = top_right_curve_center + vec2(control_dist, -corner_radius);
142 let top_right_p2 = top_right_curve_center + vec2(corner_radius, -control_dist);
143
144 let bottom_right = bounds.bottom_right();
145 let bottom_right_arc_start = bottom_right + vec2(0.0, -corner_radius);
146 let bottom_right_arc_end = bottom_right + vec2(-corner_radius, 0.0);
147 let bottom_right_curve_center = bottom_right + vec2(-corner_radius, -corner_radius);
148 let bottom_right_p1 = bottom_right_curve_center + vec2(corner_radius, control_dist);
149 let bottom_right_p2 = bottom_right_curve_center + vec2(control_dist, corner_radius);
150
151 let bottom_left = bounds.bottom_left();
152 let bottom_left_arc_start = bottom_left + vec2(corner_radius, 0.0);
153 let bottom_left_arc_end = bottom_left + vec2(0.0, -corner_radius);
154 let bottom_left_curve_center = bottom_left + vec2(corner_radius, -corner_radius);
155 let bottom_left_p1 = bottom_left_curve_center + vec2(-control_dist, corner_radius);
156 let bottom_left_p2 = bottom_left_curve_center + vec2(-corner_radius, control_dist);
157
158 let mut path_builder = render_context.path_builder().expect("path_builder");
159 path_builder
160 .move_to(top_left_arc_start)
161 .cubic_to(top_left_p1, top_left_p2, top_left_arc_end)
162 .line_to(top_right_arc_start)
163 .cubic_to(top_right_p1, top_right_p2, top_right_arc_end)
164 .line_to(bottom_right_arc_start)
165 .cubic_to(bottom_right_p1, bottom_right_p2, bottom_right_arc_end)
166 .line_to(bottom_left_arc_start)
167 .cubic_to(bottom_left_p1, bottom_left_p2, bottom_left_arc_end)
168 .line_to(top_left_arc_start);
169 path_builder.build()
170}
171
172pub fn path_for_circle(center: Point, radius: Coord, render_context: &mut RenderContext) -> Path {
174 let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
175 let control_dist = kappa * radius;
176
177 let mut path_builder = render_context.path_builder().expect("path_builder");
178 let left = center + vec2(-radius, 0.0);
179 let top = center + vec2(0.0, -radius);
180 let right = center + vec2(radius, 0.0);
181 let bottom = center + vec2(0.0, radius);
182 let left_p1 = center + vec2(-radius, -control_dist);
183 let left_p2 = center + vec2(-control_dist, -radius);
184 let top_p1 = center + vec2(control_dist, -radius);
185 let top_p2 = center + vec2(radius, -control_dist);
186 let right_p1 = center + vec2(radius, control_dist);
187 let right_p2 = center + vec2(control_dist, radius);
188 let bottom_p1 = center + vec2(-control_dist, radius);
189 let bottom_p2 = center + vec2(-radius, control_dist);
190 path_builder
191 .move_to(left)
192 .cubic_to(left_p1, left_p2, top)
193 .cubic_to(top_p1, top_p2, right)
194 .cubic_to(right_p1, right_p2, bottom)
195 .cubic_to(bottom_p1, bottom_p2, left);
196 path_builder.build()
197}
198
199fn point_for_segment_index(
200 index: usize,
201 center: Point,
202 radius: Coord,
203 segment_angle: f32,
204) -> Point {
205 let angle = index as f32 * segment_angle;
206 let x = radius * angle.cos();
207 let y = radius * angle.sin();
208 center + vec2(x, y)
209}
210
211pub fn path_for_polygon(
213 center: Point,
214 radius: Coord,
215 segment_count: usize,
216 render_context: &mut RenderContext,
217) -> Path {
218 let segment_angle = (2.0 * std::f32::consts::PI) / segment_count as f32;
219 let mut path_builder = render_context.path_builder().expect("path_builder");
220 let first_point = point_for_segment_index(0, center, radius, segment_angle);
221 path_builder.move_to(first_point);
222 for index in 1..segment_count {
223 let pt = point_for_segment_index(index, center, radius, segment_angle);
224 path_builder.line_to(pt);
225 }
226 path_builder.line_to(first_point);
227 path_builder.build()
228}
229
230pub fn path_for_corner_knockouts(
233 bounds: &Rect,
234 corner_radius: Coord,
235 render_context: &mut RenderContext,
236) -> Path {
237 let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
238 let control_dist = kappa * corner_radius;
239
240 let top_left = bounds.top_left();
241 let top_left_arc_start = bounds.origin + vec2(0.0, corner_radius);
242 let top_left_arc_end = bounds.origin + vec2(corner_radius, 0.0);
243 let top_left_curve_center = bounds.origin + vec2(corner_radius, corner_radius);
244 let top_left_p1 = top_left_curve_center + vec2(-corner_radius, -control_dist);
245 let top_left_p2 = top_left_curve_center + vec2(-control_dist, -corner_radius);
246
247 let top_right = bounds.top_right();
248 let top_right_arc_start = top_right + vec2(-corner_radius, 0.0);
249 let top_right_arc_end = top_right + vec2(0.0, corner_radius);
250 let top_right_curve_center = top_right + vec2(-corner_radius, corner_radius);
251 let top_right_p1 = top_right_curve_center + vec2(control_dist, -corner_radius);
252 let top_right_p2 = top_right_curve_center + vec2(corner_radius, -control_dist);
253
254 let bottom_right = bounds.bottom_right();
255 let bottom_right_arc_start = bottom_right + vec2(0.0, -corner_radius);
256 let bottom_right_arc_end = bottom_right + vec2(-corner_radius, 0.0);
257 let bottom_right_curve_center = bottom_right + vec2(-corner_radius, -corner_radius);
258 let bottom_right_p1 = bottom_right_curve_center + vec2(corner_radius, control_dist);
259 let bottom_right_p2 = bottom_right_curve_center + vec2(control_dist, corner_radius);
260
261 let bottom_left = bounds.bottom_left();
262 let bottom_left_arc_start = bottom_left + vec2(corner_radius, 0.0);
263 let bottom_left_arc_end = bottom_left + vec2(0.0, -corner_radius);
264 let bottom_left_curve_center = bottom_left + vec2(corner_radius, -corner_radius);
265 let bottom_left_p1 = bottom_left_curve_center + vec2(-control_dist, corner_radius);
266 let bottom_left_p2 = bottom_left_curve_center + vec2(-corner_radius, control_dist);
267
268 let mut path_builder = render_context.path_builder().expect("path_builder");
269 path_builder
270 .move_to(top_left)
271 .line_to(top_left_arc_start)
272 .cubic_to(top_left_p1, top_left_p2, top_left_arc_end)
273 .line_to(top_left)
274 .move_to(top_right)
275 .line_to(top_right_arc_start)
276 .cubic_to(top_right_p1, top_right_p2, top_right_arc_end)
277 .line_to(top_right)
278 .move_to(bottom_right)
279 .line_to(bottom_right_arc_start)
280 .cubic_to(bottom_right_p1, bottom_right_p2, bottom_right_arc_end)
281 .line_to(bottom_right)
282 .move_to(bottom_left)
283 .line_to(bottom_left_arc_start)
284 .cubic_to(bottom_left_p1, bottom_left_p2, bottom_left_arc_end)
285 .line_to(bottom_left);
286 path_builder.build()
287}
288
289pub fn path_for_cursor(hot_spot: Point, radius: Coord, render_context: &mut RenderContext) -> Path {
291 let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
292 let control_dist = kappa * radius;
293 let mut path_builder = render_context.path_builder().expect("path_builder");
294 let center = hot_spot + vec2(radius, radius);
295 let left = center + vec2(-radius, 0.0);
296 let top = center + vec2(0.0, -radius);
297 let right = center + vec2(radius, 0.0);
298 let bottom = center + vec2(0.0, radius);
299 let top_p1 = center + vec2(control_dist, -radius);
300 let top_p2 = center + vec2(radius, -control_dist);
301 let right_p1 = center + vec2(radius, control_dist);
302 let right_p2 = center + vec2(control_dist, radius);
303 let bottom_p1 = center + vec2(-control_dist, radius);
304 let bottom_p2 = center + vec2(-radius, control_dist);
305 path_builder
306 .move_to(hot_spot)
307 .line_to(top)
308 .cubic_to(top_p1, top_p2, right)
309 .cubic_to(right_p1, right_p2, bottom)
310 .cubic_to(bottom_p1, bottom_p2, left)
311 .line_to(hot_spot);
312 path_builder.build()
313}
314
315#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
317pub struct Paint {
318 pub fg: Color,
320 pub bg: Color,
322}
323
324impl Paint {
325 pub fn from_hash_codes(fg: &str, bg: &str) -> Result<Paint, Error> {
327 Ok(Paint { fg: Color::from_hash_code(fg)?, bg: Color::from_hash_code(bg)? })
328 }
329}
330
331pub fn load_font(path: PathBuf) -> Result<FontFace, Error> {
333 let file = File::open(path).context("File::open")?;
334 let vmo = fdio::get_vmo_copy_from_file(&file).context("fdio::get_vmo_copy_from_file")?;
335 let size = file.metadata()?.len() as usize;
336 let root_vmar = fuchsia_runtime::vmar_root_self();
337 let address = root_vmar.map(
338 0,
339 &vmo,
340 0,
341 size,
342 zx::VmarFlags::PERM_READ | zx::VmarFlags::MAP_RANGE | zx::VmarFlags::REQUIRE_NON_RESIZABLE,
343 )?;
344
345 let mapped_font_data = unsafe { slice::from_raw_parts(address as *mut u8, size) };
346 Ok(FontFace::new(&*mapped_font_data)?)
347}
348
349#[derive(Clone)]
351pub struct FontFace {
352 pub face: Face<'static>,
354}
355
356impl FontFace {
357 pub fn new(data: &'static [u8]) -> Result<FontFace, Error> {
359 let face = Face::parse(data, 0)?;
360 Ok(FontFace { face })
361 }
362
363 pub fn ascent(&self, size: f32) -> f32 {
365 let ascent = self.face.ascender();
366 (ascent as f32 / self.face.units_per_em() as f32) * size
367 }
368
369 pub fn descent(&self, size: f32) -> f32 {
371 let descender = self.face.descender();
372 (descender as f32 / self.face.units_per_em() as f32) * size
373 }
374
375 pub fn capital_height(&self, size: f32) -> Option<f32> {
377 self.face
378 .capital_height()
379 .map(|capital_height| (capital_height as f32 / self.face.units_per_em() as f32) * size)
380 }
381}
382
383pub fn measure_text_width(face: &FontFace, font_size: f32, text: &str) -> f32 {
385 measure_text_size(face, font_size, text, false).width
386}
387
388pub fn measure_text_size(face: &FontFace, size: f32, text: &str, visual: bool) -> Size {
390 let mut bounding_box = Rect::zero();
391 let ascent = face.ascent(size);
392 let units_per_em = face.face.units_per_em();
393 let scale = size / units_per_em as f32;
394 let y_offset = vec2(0.0, ascent).to_i32();
395 let chars = text.chars();
396 let mut x: f32 = 0.0;
397
398 for c in chars {
399 if let Some(glyph_index) = face.face.glyph_index(c) {
400 let glyph_bounding_box = face
401 .face
402 .glyph_bounding_box(glyph_index)
403 .and_then(|bounding_box| Some(pixel_size_rect(face, size, bounding_box)))
404 .unwrap_or_else(Rect::zero);
405 let horizontal_advance = face.face.glyph_hor_advance(glyph_index).unwrap_or(0);
406 let w = horizontal_advance as f32 * scale;
407 let position = y_offset + vec2(x, 0.0).to_i32();
408 if !glyph_bounding_box.is_empty() {
409 let glyph_bounding_box = &glyph_bounding_box.translate(position.to_f32());
410
411 if bounding_box.is_empty() {
412 bounding_box = *glyph_bounding_box;
413 } else {
414 bounding_box = bounding_box.union(&glyph_bounding_box);
415 }
416 }
417
418 x += w;
419 }
420 }
421 if visual { bounding_box.size } else { size2(x, size) }
422}
423
424pub fn linebreak_text(face: &FontFace, font_size: f32, text: &str, max_width: f32) -> Vec<String> {
427 let chunks: Vec<&str> = text.split_whitespace().collect();
428 let space_width = measure_text_width(face, font_size, " ");
429 let breaks: Vec<usize> = chunks
430 .iter()
431 .enumerate()
432 .scan(0.0, |width, (index, word)| {
433 let word_width = measure_text_width(face, font_size, word);
434 let resulting_line_len = *width + word_width + space_width;
435 if resulting_line_len > max_width {
436 *width = word_width + space_width;
437 Some(Some(index))
438 } else {
439 *width += word_width;
440 *width += space_width;
441 Some(None)
442 }
443 })
444 .flatten()
445 .chain(std::iter::once(chunks.len()))
446 .collect();
447 let lines: Vec<String> = breaks
448 .iter()
449 .scan(0, |first_word_index, last_word_index| {
450 let first = *first_word_index;
451 *first_word_index = *last_word_index;
452 let line = &chunks[first..*last_word_index];
453 let line_str = String::from(line.join(" "));
454 Some(line_str)
455 })
456 .collect();
457
458 lines
459}
460
461fn pixel_size_rect(face: &FontFace, font_size: f32, value: ttf_parser::Rect) -> Rect {
462 let units_per_em = face.face.units_per_em();
463 let scale = font_size / units_per_em as f32;
464 let min_x = value.x_min as f32 * scale;
465 let max_y = -value.y_min as f32 * scale;
466 let max_x = value.x_max as f32 * scale;
467 let min_y = -value.y_max as f32 * scale;
468 Box2D::new(point2(min_x, min_y), point2(max_x, max_y)).to_rect()
469}
470
471fn scaled_point2(x: f32, y: f32, scale: &Vector2D<f32>) -> Point {
472 point2(x * scale.x, y * scale.y)
473}
474
475const ZERO_RECT: ttf_parser::Rect = ttf_parser::Rect { x_max: 0, x_min: 0, y_max: 0, y_min: 0 };
476
477struct GlyphBuilder<'a> {
478 scale: Vector2D<f32>,
479 offset: Vector2D<f32>,
480 context: &'a RenderContext,
481 path_builder: Option<PathBuilder>,
482 raster_builder: RasterBuilder,
483}
484
485impl<'a> GlyphBuilder<'a> {
486 fn new(scale: Vector2D<f32>, offset: Vector2D<f32>, context: &'a mut RenderContext) -> Self {
487 let path_builder = context.path_builder().expect("path_builder");
488 let raster_builder = context.raster_builder().expect("raster_builder");
489 Self { scale, offset, context, path_builder: Some(path_builder), raster_builder }
490 }
491
492 fn path_builder(&mut self) -> &mut PathBuilder {
493 self.path_builder.as_mut().expect("path_builder() PathBuilder")
494 }
495
496 fn raster(self) -> Raster {
497 self.raster_builder.build()
498 }
499}
500
501impl ttf_parser::OutlineBuilder for GlyphBuilder<'_> {
502 fn move_to(&mut self, x: f32, y: f32) {
503 let p = scaled_point2(x, -y, &self.scale) + self.offset;
504 self.path_builder().move_to(p);
505 }
506
507 fn line_to(&mut self, x: f32, y: f32) {
508 let p = scaled_point2(x, -y, &self.scale) + self.offset;
509 self.path_builder().line_to(p);
510 }
511
512 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
513 let p1 = scaled_point2(x1, -y1, &self.scale) + self.offset;
514 let p = scaled_point2(x, -y, &self.scale) + self.offset;
515 self.path_builder().quad_to(p1, p);
516 }
517
518 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
519 let p1 = scaled_point2(x1, -y1, &self.scale) + self.offset;
520 let p2 = scaled_point2(x2, -y2, &self.scale) + self.offset;
521 let p = scaled_point2(x, -y, &self.scale) + self.offset;
522 self.path_builder().cubic_to(p1, p2, p);
523 }
524
525 fn close(&mut self) {
526 let path_builder = self.path_builder.take().expect("take PathBuilder");
527 let path = path_builder.build();
528 self.raster_builder.add(&path, None);
529 let path_builder = self.context.path_builder().expect("path_builder");
530 self.path_builder = Some(path_builder);
531 }
532}
533
534#[derive(Debug)]
535#[allow(missing_docs)]
536pub struct Glyph {
537 pub raster: Raster,
538 pub bounding_box: Rect,
539}
540
541impl Glyph {
542 #[allow(missing_docs)]
543 pub fn new(
544 context: &mut RenderContext,
545 face: &FontFace,
546 size: f32,
547 id: Option<ttf_parser::GlyphId>,
548 ) -> Self {
549 let units_per_em = face.face.units_per_em();
550 let xy_scale = size / units_per_em as f32;
551 let scale = vec2(xy_scale, xy_scale);
552 let offset = Vector2D::zero();
553 if let Some(id) = id {
554 Self::with_scale_and_offset(context, face, scale, offset, id)
555 } else {
556 let path_builder = context.path_builder().expect("path_builder");
557 let path = path_builder.build();
558 let mut raster_builder = context.raster_builder().expect("raster_builder");
559 raster_builder.add(&path, None);
560 let raster = raster_builder.build();
561
562 Self { bounding_box: Rect::zero(), raster }
563 }
564 }
565
566 #[allow(missing_docs)]
567 pub fn with_scale_and_offset(
568 context: &mut RenderContext,
569 face: &FontFace,
570 scale: Vector2D<f32>,
571 offset: Vector2D<f32>,
572 id: ttf_parser::GlyphId,
573 ) -> Self {
574 let mut builder = GlyphBuilder::new(scale, offset, context);
575 let glyph_bounding_box = &face.face.outline_glyph(id, &mut builder).unwrap_or(ZERO_RECT);
576 let min_x = glyph_bounding_box.x_min as f32 * scale.x + offset.x;
577 let max_y = -glyph_bounding_box.y_min as f32 * scale.y + offset.y;
578 let max_x = glyph_bounding_box.x_max as f32 * scale.x + offset.x;
579 let min_y = -glyph_bounding_box.y_max as f32 * scale.y + offset.y;
580 let bounding_box = Box2D::new(point2(min_x, min_y), point2(max_x, max_y)).to_rect();
581
582 Self { bounding_box, raster: builder.raster() }
583 }
584}
585
586#[derive(Debug)]
587#[allow(missing_docs)]
588pub struct GlyphMap {
589 pub glyphs: BTreeMap<ttf_parser::GlyphId, Glyph>,
590}
591
592impl GlyphMap {
593 #[allow(missing_docs)]
594 pub fn new() -> Self {
595 Self { glyphs: BTreeMap::new() }
596 }
597}
598
599#[allow(missing_docs)]
600pub struct Text {
601 pub raster: Raster,
602 pub bounding_box: Rect,
603}
604
605impl Text {
606 #[allow(missing_docs)]
607 pub fn new_with_lines(
608 context: &mut RenderContext,
609 lines: &Vec<String>,
610 size: f32,
611 face: &FontFace,
612 glyph_map: &mut GlyphMap,
613 ) -> Self {
614 let glyphs = &mut glyph_map.glyphs;
615 let mut bounding_box = Rect::zero();
616 let mut raster_union = {
617 let raster_builder = context.raster_builder().unwrap();
618 raster_builder.build()
619 };
620 let ascent = face.ascent(size);
621 let descent = face.descent(size);
622 let units_per_em = face.face.units_per_em();
623 let scale = size / units_per_em as f32;
624 let mut y_offset = vec2(0.0, ascent).to_i32();
625 for line in lines.iter() {
626 let chars = line.chars();
627 let mut x: f32 = 0.0;
628
629 for c in chars {
630 if let Some(glyph_index) = face.face.glyph_index(c) {
631 let horizontal_advance = face.face.glyph_hor_advance(glyph_index).unwrap_or(0);
632 let w = horizontal_advance as f32 * scale;
633 let position = y_offset + vec2(x, 0.0).to_i32();
634 let glyph = glyphs
635 .entry(glyph_index)
636 .or_insert_with(|| Glyph::new(context, face, size, Some(glyph_index)));
637 if !glyph.bounding_box.is_empty() {
638 let raster = glyph
640 .raster
641 .clone()
642 .translate(position.cast_unit::<euclid::UnknownUnit>());
643 raster_union = raster_union + raster;
644
645 let glyph_bounding_box = &glyph.bounding_box.translate(position.to_f32());
647
648 if bounding_box.is_empty() {
649 bounding_box = *glyph_bounding_box;
650 } else {
651 bounding_box = bounding_box.union(&glyph_bounding_box);
652 }
653 }
654
655 x += w;
656 }
657 }
658 y_offset += vec2(0, (ascent - descent) as i32);
659 }
660
661 Self { raster: raster_union, bounding_box }
662 }
663
664 #[allow(missing_docs)]
665 pub fn new(
666 context: &mut RenderContext,
667 text: &str,
668 size: f32,
669 wrap: f32,
670 face: &FontFace,
671 glyph_map: &mut GlyphMap,
672 ) -> Self {
673 let lines = linebreak_text(face, size, text, wrap);
674 Self::new_with_lines(context, &lines, size, face, glyph_map)
675 }
676}
677
678#[allow(missing_docs)]
680pub struct TextGrid {
681 pub scale: Vector2D<f32>,
682 pub offset: Vector2D<f32>,
683 pub cell_size: Size,
684}
685
686impl TextGrid {
687 pub fn new(face: &FontFace, cell_size: &Size) -> Self {
691 let units_per_em = face.face.units_per_em() as f32;
692 let ascent = face.face.ascender() as f32 / units_per_em;
693 let descent = face.face.descender() as f32 / units_per_em;
694
695 let font_size = cell_size.height / (ascent - descent);
697 let y_scale = font_size / units_per_em;
698
699 let x_scale = face.face.glyph_index('0').map_or(y_scale, |glyph_index| {
704 let horizontal_advance =
705 face.face.glyph_hor_advance(glyph_index).expect("glyph_hor_advance") as f32
706 / units_per_em
707 * font_size;
708 y_scale * cell_size.width / horizontal_advance
709 });
710
711 let scale = vec2(x_scale, y_scale);
712 let offset = vec2(0.0, ascent * font_size);
715
716 Self { scale, offset, cell_size: *cell_size }
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::{GlyphMap, Size, Text, TextGrid};
723 use crate::drawing::{DisplayRotation, FontFace, measure_text_size};
724 use crate::render::generic::{self, Backend};
725 use crate::render::{Context as RenderContext, ContextInner};
726 use euclid::approxeq::ApproxEq;
727 use euclid::{size2, vec2};
728 use fuchsia_async::{self as fasync, MonotonicInstant, TimeoutExt};
729 use fuchsia_framebuffer::FrameUsage;
730 use fuchsia_framebuffer::sysmem::BufferCollectionAllocator;
731 use std::sync::LazyLock;
732
733 const DEFAULT_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
734
735 static FONT_DATA: &'static [u8] = include_bytes!(
738 "../../../../../prebuilt/third_party/fonts/robotoslab/RobotoSlab-Regular.ttf"
739 );
740 static FONT_FACE: LazyLock<FontFace> =
741 LazyLock::new(|| FontFace::new(&FONT_DATA).expect("Failed to create font"));
742
743 #[fasync::run_singlethreaded(test)]
744 async fn test_text_bounding_box() {
745 let size = size2(800, 800);
746 let mut buffer_allocator = BufferCollectionAllocator::new(
747 size.width,
748 size.height,
749 fidl_fuchsia_images2::PixelFormat::B8G8R8A8,
750 FrameUsage::Cpu,
751 3,
752 )
753 .expect("BufferCollectionAllocator::new");
754 let context_token = buffer_allocator
755 .duplicate_token()
756 .on_timeout(MonotonicInstant::after(DEFAULT_TIMEOUT), || {
757 panic!("Timed out while waiting for duplicate_token")
758 })
759 .await
760 .expect("token");
761 let forma_context = generic::Forma::new_context(context_token, size, DisplayRotation::Deg0);
762 let _buffers_result = buffer_allocator
763 .allocate_buffers(true)
764 .on_timeout(MonotonicInstant::after(DEFAULT_TIMEOUT), || {
765 panic!("Timed out while waiting for sysmem bufers")
766 })
767 .await;
768 let mut render_context = RenderContext { inner: ContextInner::Forma(forma_context) };
769 let mut glyphs = GlyphMap::new();
770 let text =
771 Text::new(&mut render_context, "Good Morning", 20.0, 200.0, &FONT_FACE, &mut glyphs);
772
773 let expected_origin = euclid::point2(0.5371094, 4.765625);
774 let expected_size = vec2(132.33594, 19.501953);
775 let epsilon_point = euclid::point2(1.0, 1.0);
776 let epsilon_vec = euclid::vec2(1.0, 1.0);
777 assert!(
778 text.bounding_box.origin.approx_eq_eps(&expected_origin, &epsilon_point),
779 "Expected bounding box origin to be close to {:?} but found {:?}",
780 expected_origin,
781 text.bounding_box.origin
782 );
783 assert!(
784 text.bounding_box.size.to_vector().approx_eq_eps(&expected_size, &epsilon_vec),
785 "Expected bounding box origin to be close to {:?} but found {:?}",
786 expected_size,
787 text.bounding_box.size
788 );
789 }
790
791 #[fasync::run_singlethreaded(test)]
792 async fn test_textgridcell() {
793 let cell_size = Size::new(16.0, 32.0);
794 let grid = TextGrid::new(&FONT_FACE, &Size::new(16.0, 32.0));
795 assert_eq!(grid.cell_size, cell_size);
796 assert!(grid.offset != vec2(0.0, 0.0));
797 assert!(grid.scale != vec2(0.0, 0.0));
798 }
799
800 #[test]
801 fn test_measure_text() {
802 assert_eq!(measure_text_size(&FONT_FACE, 32.0, "", false).width, 0.0);
803 assert_eq!(measure_text_size(&FONT_FACE, 32.0, "", true).width, 0.0);
804 assert!(measure_text_size(&FONT_FACE, 32.0, "ahoy", false).width > 0.0);
805 assert!(measure_text_size(&FONT_FACE, 32.0, "ahoy", true).width > 0.0);
806 }
807}