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::from_slice(data, 0)?;
360 Ok(FontFace { face })
361 }
362
363 pub fn ascent(&self, size: f32) -> f32 {
365 let ascent = self.face.ascender();
366 self.face
367 .units_per_em()
368 .and_then(|units_per_em| Some((ascent as f32 / units_per_em as f32) * size))
369 .expect("units_per_em")
370 }
371
372 pub fn descent(&self, size: f32) -> f32 {
374 let descender = self.face.descender();
375 self.face
376 .units_per_em()
377 .and_then(|units_per_em| Some((descender as f32 / units_per_em as f32) * size))
378 .expect("units_per_em")
379 }
380
381 pub fn capital_height(&self, size: f32) -> Option<f32> {
383 self.face.capital_height().and_then(|capital_height| {
384 self.face
385 .units_per_em()
386 .and_then(|units_per_em| Some((capital_height as f32 / units_per_em as f32) * size))
387 })
388 }
389}
390
391pub fn measure_text_width(face: &FontFace, font_size: f32, text: &str) -> f32 {
393 measure_text_size(face, font_size, text, false).width
394}
395
396pub fn measure_text_size(face: &FontFace, size: f32, text: &str, visual: bool) -> Size {
398 let mut bounding_box = Rect::zero();
399 let ascent = face.ascent(size);
400 let units_per_em = face.face.units_per_em().expect("units_per_em");
401 let scale = size / units_per_em as f32;
402 let y_offset = vec2(0.0, ascent).to_i32();
403 let chars = text.chars();
404 let mut x: f32 = 0.0;
405
406 for c in chars {
407 if let Some(glyph_index) = face.face.glyph_index(c) {
408 let glyph_bounding_box = face
409 .face
410 .glyph_bounding_box(glyph_index)
411 .and_then(|bounding_box| Some(pixel_size_rect(face, size, bounding_box)))
412 .unwrap_or_else(Rect::zero);
413 let horizontal_advance = face.face.glyph_hor_advance(glyph_index).unwrap_or(0);
414 let w = horizontal_advance as f32 * scale;
415 let position = y_offset + vec2(x, 0.0).to_i32();
416 if !glyph_bounding_box.is_empty() {
417 let glyph_bounding_box = &glyph_bounding_box.translate(position.to_f32());
418
419 if bounding_box.is_empty() {
420 bounding_box = *glyph_bounding_box;
421 } else {
422 bounding_box = bounding_box.union(&glyph_bounding_box);
423 }
424 }
425
426 x += w;
427 }
428 }
429 if visual { bounding_box.size } else { size2(x, size) }
430}
431
432pub fn linebreak_text(face: &FontFace, font_size: f32, text: &str, max_width: f32) -> Vec<String> {
435 let chunks: Vec<&str> = text.split_whitespace().collect();
436 let space_width = measure_text_width(face, font_size, " ");
437 let breaks: Vec<usize> = chunks
438 .iter()
439 .enumerate()
440 .scan(0.0, |width, (index, word)| {
441 let word_width = measure_text_width(face, font_size, word);
442 let resulting_line_len = *width + word_width + space_width;
443 if resulting_line_len > max_width {
444 *width = word_width + space_width;
445 Some(Some(index))
446 } else {
447 *width += word_width;
448 *width += space_width;
449 Some(None)
450 }
451 })
452 .flatten()
453 .chain(std::iter::once(chunks.len()))
454 .collect();
455 let lines: Vec<String> = breaks
456 .iter()
457 .scan(0, |first_word_index, last_word_index| {
458 let first = *first_word_index;
459 *first_word_index = *last_word_index;
460 let line = &chunks[first..*last_word_index];
461 let line_str = String::from(line.join(" "));
462 Some(line_str)
463 })
464 .collect();
465
466 lines
467}
468
469fn pixel_size_rect(face: &FontFace, font_size: f32, value: ttf_parser::Rect) -> Rect {
470 let units_per_em = face.face.units_per_em().expect("units_per_em");
471 let scale = font_size / units_per_em as f32;
472 let min_x = value.x_min as f32 * scale;
473 let max_y = -value.y_min as f32 * scale;
474 let max_x = value.x_max as f32 * scale;
475 let min_y = -value.y_max as f32 * scale;
476 Box2D::new(point2(min_x, min_y), point2(max_x, max_y)).to_rect()
477}
478
479fn scaled_point2(x: f32, y: f32, scale: &Vector2D<f32>) -> Point {
480 point2(x * scale.x, y * scale.y)
481}
482
483const ZERO_RECT: ttf_parser::Rect = ttf_parser::Rect { x_max: 0, x_min: 0, y_max: 0, y_min: 0 };
484
485struct GlyphBuilder<'a> {
486 scale: Vector2D<f32>,
487 offset: Vector2D<f32>,
488 context: &'a RenderContext,
489 path_builder: Option<PathBuilder>,
490 raster_builder: RasterBuilder,
491}
492
493impl<'a> GlyphBuilder<'a> {
494 fn new(scale: Vector2D<f32>, offset: Vector2D<f32>, context: &'a mut RenderContext) -> Self {
495 let path_builder = context.path_builder().expect("path_builder");
496 let raster_builder = context.raster_builder().expect("raster_builder");
497 Self { scale, offset, context, path_builder: Some(path_builder), raster_builder }
498 }
499
500 fn path_builder(&mut self) -> &mut PathBuilder {
501 self.path_builder.as_mut().expect("path_builder() PathBuilder")
502 }
503
504 fn raster(self) -> Raster {
505 self.raster_builder.build()
506 }
507}
508
509impl ttf_parser::OutlineBuilder for GlyphBuilder<'_> {
510 fn move_to(&mut self, x: f32, y: f32) {
511 let p = scaled_point2(x, -y, &self.scale) + self.offset;
512 self.path_builder().move_to(p);
513 }
514
515 fn line_to(&mut self, x: f32, y: f32) {
516 let p = scaled_point2(x, -y, &self.scale) + self.offset;
517 self.path_builder().line_to(p);
518 }
519
520 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
521 let p1 = scaled_point2(x1, -y1, &self.scale) + self.offset;
522 let p = scaled_point2(x, -y, &self.scale) + self.offset;
523 self.path_builder().quad_to(p1, p);
524 }
525
526 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
527 let p1 = scaled_point2(x1, -y1, &self.scale) + self.offset;
528 let p2 = scaled_point2(x2, -y2, &self.scale) + self.offset;
529 let p = scaled_point2(x, -y, &self.scale) + self.offset;
530 self.path_builder().cubic_to(p1, p2, p);
531 }
532
533 fn close(&mut self) {
534 let path_builder = self.path_builder.take().expect("take PathBuilder");
535 let path = path_builder.build();
536 self.raster_builder.add(&path, None);
537 let path_builder = self.context.path_builder().expect("path_builder");
538 self.path_builder = Some(path_builder);
539 }
540}
541
542#[derive(Debug)]
543#[allow(missing_docs)]
544pub struct Glyph {
545 pub raster: Raster,
546 pub bounding_box: Rect,
547}
548
549impl Glyph {
550 #[allow(missing_docs)]
551 pub fn new(
552 context: &mut RenderContext,
553 face: &FontFace,
554 size: f32,
555 id: Option<ttf_parser::GlyphId>,
556 ) -> Self {
557 let units_per_em = face.face.units_per_em().expect("units_per_em");
558 let xy_scale = size / units_per_em as f32;
559 let scale = vec2(xy_scale, xy_scale);
560 let offset = Vector2D::zero();
561 if let Some(id) = id {
562 Self::with_scale_and_offset(context, face, scale, offset, id)
563 } else {
564 let path_builder = context.path_builder().expect("path_builder");
565 let path = path_builder.build();
566 let mut raster_builder = context.raster_builder().expect("raster_builder");
567 raster_builder.add(&path, None);
568 let raster = raster_builder.build();
569
570 Self { bounding_box: Rect::zero(), raster }
571 }
572 }
573
574 #[allow(missing_docs)]
575 pub fn with_scale_and_offset(
576 context: &mut RenderContext,
577 face: &FontFace,
578 scale: Vector2D<f32>,
579 offset: Vector2D<f32>,
580 id: ttf_parser::GlyphId,
581 ) -> Self {
582 let mut builder = GlyphBuilder::new(scale, offset, context);
583 let glyph_bounding_box = &face.face.outline_glyph(id, &mut builder).unwrap_or(ZERO_RECT);
584 let min_x = glyph_bounding_box.x_min as f32 * scale.x + offset.x;
585 let max_y = -glyph_bounding_box.y_min as f32 * scale.y + offset.y;
586 let max_x = glyph_bounding_box.x_max as f32 * scale.x + offset.x;
587 let min_y = -glyph_bounding_box.y_max as f32 * scale.y + offset.y;
588 let bounding_box = Box2D::new(point2(min_x, min_y), point2(max_x, max_y)).to_rect();
589
590 Self { bounding_box, raster: builder.raster() }
591 }
592}
593
594#[derive(Debug)]
595#[allow(missing_docs)]
596pub struct GlyphMap {
597 pub glyphs: BTreeMap<ttf_parser::GlyphId, Glyph>,
598}
599
600impl GlyphMap {
601 #[allow(missing_docs)]
602 pub fn new() -> Self {
603 Self { glyphs: BTreeMap::new() }
604 }
605}
606
607#[allow(missing_docs)]
608pub struct Text {
609 pub raster: Raster,
610 pub bounding_box: Rect,
611}
612
613impl Text {
614 #[allow(missing_docs)]
615 pub fn new_with_lines(
616 context: &mut RenderContext,
617 lines: &Vec<String>,
618 size: f32,
619 face: &FontFace,
620 glyph_map: &mut GlyphMap,
621 ) -> Self {
622 let glyphs = &mut glyph_map.glyphs;
623 let mut bounding_box = Rect::zero();
624 let mut raster_union = {
625 let raster_builder = context.raster_builder().unwrap();
626 raster_builder.build()
627 };
628 let ascent = face.ascent(size);
629 let descent = face.descent(size);
630 let units_per_em = face.face.units_per_em().expect("units_per_em");
631 let scale = size / units_per_em as f32;
632 let mut y_offset = vec2(0.0, ascent).to_i32();
633 for line in lines.iter() {
634 let chars = line.chars();
635 let mut x: f32 = 0.0;
636
637 for c in chars {
638 if let Some(glyph_index) = face.face.glyph_index(c) {
639 let horizontal_advance = face.face.glyph_hor_advance(glyph_index).unwrap_or(0);
640 let w = horizontal_advance as f32 * scale;
641 let position = y_offset + vec2(x, 0.0).to_i32();
642 let glyph = glyphs
643 .entry(glyph_index)
644 .or_insert_with(|| Glyph::new(context, face, size, Some(glyph_index)));
645 if !glyph.bounding_box.is_empty() {
646 let raster = glyph
648 .raster
649 .clone()
650 .translate(position.cast_unit::<euclid::UnknownUnit>());
651 raster_union = raster_union + raster;
652
653 let glyph_bounding_box = &glyph.bounding_box.translate(position.to_f32());
655
656 if bounding_box.is_empty() {
657 bounding_box = *glyph_bounding_box;
658 } else {
659 bounding_box = bounding_box.union(&glyph_bounding_box);
660 }
661 }
662
663 x += w;
664 }
665 }
666 y_offset += vec2(0, (ascent - descent) as i32);
667 }
668
669 Self { raster: raster_union, bounding_box }
670 }
671
672 #[allow(missing_docs)]
673 pub fn new(
674 context: &mut RenderContext,
675 text: &str,
676 size: f32,
677 wrap: f32,
678 face: &FontFace,
679 glyph_map: &mut GlyphMap,
680 ) -> Self {
681 let lines = linebreak_text(face, size, text, wrap);
682 Self::new_with_lines(context, &lines, size, face, glyph_map)
683 }
684}
685
686#[allow(missing_docs)]
688pub struct TextGrid {
689 pub scale: Vector2D<f32>,
690 pub offset: Vector2D<f32>,
691 pub cell_size: Size,
692}
693
694impl TextGrid {
695 pub fn new(face: &FontFace, cell_size: &Size) -> Self {
699 let units_per_em = face.face.units_per_em().expect("units_per_em") as f32;
700 let ascent = face.face.ascender() as f32 / units_per_em;
701 let descent = face.face.descender() as f32 / units_per_em;
702
703 let font_size = cell_size.height / (ascent - descent);
705 let y_scale = font_size / units_per_em;
706
707 let x_scale = face.face.glyph_index('0').map_or(y_scale, |glyph_index| {
712 let horizontal_advance =
713 face.face.glyph_hor_advance(glyph_index).expect("glyph_hor_advance") as f32
714 / units_per_em
715 * font_size;
716 y_scale * cell_size.width / horizontal_advance
717 });
718
719 let scale = vec2(x_scale, y_scale);
720 let offset = vec2(0.0, ascent * font_size);
723
724 Self { scale, offset, cell_size: *cell_size }
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::{GlyphMap, Size, Text, TextGrid};
731 use crate::drawing::{DisplayRotation, FontFace, measure_text_size};
732 use crate::render::generic::{self, Backend};
733 use crate::render::{Context as RenderContext, ContextInner};
734 use euclid::approxeq::ApproxEq;
735 use euclid::{size2, vec2};
736 use fuchsia_async::{self as fasync, MonotonicInstant, TimeoutExt};
737 use fuchsia_framebuffer::FrameUsage;
738 use fuchsia_framebuffer::sysmem::BufferCollectionAllocator;
739 use std::sync::LazyLock;
740
741 const DEFAULT_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
742
743 static FONT_DATA: &'static [u8] = include_bytes!(
746 "../../../../../prebuilt/third_party/fonts/robotoslab/RobotoSlab-Regular.ttf"
747 );
748 static FONT_FACE: LazyLock<FontFace> =
749 LazyLock::new(|| FontFace::new(&FONT_DATA).expect("Failed to create font"));
750
751 #[fasync::run_singlethreaded(test)]
752 async fn test_text_bounding_box() {
753 let size = size2(800, 800);
754 let mut buffer_allocator = BufferCollectionAllocator::new(
755 size.width,
756 size.height,
757 fidl_fuchsia_images2::PixelFormat::B8G8R8A8,
758 FrameUsage::Cpu,
759 3,
760 )
761 .expect("BufferCollectionAllocator::new");
762 let context_token = buffer_allocator
763 .duplicate_token()
764 .on_timeout(MonotonicInstant::after(DEFAULT_TIMEOUT), || {
765 panic!("Timed out while waiting for duplicate_token")
766 })
767 .await
768 .expect("token");
769 let forma_context = generic::Forma::new_context(context_token, size, DisplayRotation::Deg0);
770 let _buffers_result = buffer_allocator
771 .allocate_buffers(true)
772 .on_timeout(MonotonicInstant::after(DEFAULT_TIMEOUT), || {
773 panic!("Timed out while waiting for sysmem bufers")
774 })
775 .await;
776 let mut render_context = RenderContext { inner: ContextInner::Forma(forma_context) };
777 let mut glyphs = GlyphMap::new();
778 let text =
779 Text::new(&mut render_context, "Good Morning", 20.0, 200.0, &FONT_FACE, &mut glyphs);
780
781 let expected_origin = euclid::point2(0.5371094, 4.765625);
782 let expected_size = vec2(132.33594, 19.501953);
783 let epsilon_point = euclid::point2(1.0, 1.0);
784 let epsilon_vec = euclid::vec2(1.0, 1.0);
785 assert!(
786 text.bounding_box.origin.approx_eq_eps(&expected_origin, &epsilon_point),
787 "Expected bounding box origin to be close to {:?} but found {:?}",
788 expected_origin,
789 text.bounding_box.origin
790 );
791 assert!(
792 text.bounding_box.size.to_vector().approx_eq_eps(&expected_size, &epsilon_vec),
793 "Expected bounding box origin to be close to {:?} but found {:?}",
794 expected_size,
795 text.bounding_box.size
796 );
797 }
798
799 #[fasync::run_singlethreaded(test)]
800 async fn test_textgridcell() {
801 let cell_size = Size::new(16.0, 32.0);
802 let grid = TextGrid::new(&FONT_FACE, &Size::new(16.0, 32.0));
803 assert_eq!(grid.cell_size, cell_size);
804 assert!(grid.offset != vec2(0.0, 0.0));
805 assert!(grid.scale != vec2(0.0, 0.0));
806 }
807
808 #[test]
809 fn test_measure_text() {
810 assert_eq!(measure_text_size(&FONT_FACE, 32.0, "", false).width, 0.0);
811 assert_eq!(measure_text_size(&FONT_FACE, 32.0, "", true).width, 0.0);
812 assert!(measure_text_size(&FONT_FACE, 32.0, "ahoy", false).width > 0.0);
813 assert!(measure_text_size(&FONT_FACE, 32.0, "ahoy", true).width > 0.0);
814 }
815}