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