carnelian/
drawing.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Functions for drawing in Carnelian
6//! Carnelian uses the Render abstraction over Forma and Spinel
7//! to put pixels on screen. The items in this module are higher-
8//! level drawing primitives.
9
10use 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/// Some Fuchsia device displays are mounted rotated. This value represents
27/// The supported rotations and can be used by views to rotate their content
28/// to display appropriately when running on the frame buffer.
29#[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    /// Create a transformation to accommodate the screen rotation.
40    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    /// Create a transformation to undo the screen rotation.
52    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
110/// Create a render path for the specified rectangle.
111pub 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
122/// Create a render path for the specified rounded rectangle.
123pub 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
172/// Create a render path for the specified circle.
173pub 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
211/// Create a render path for the specified polygon.
212pub 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
230/// Create a path for knocking out the points of a rectangle, giving it a
231/// rounded appearance.
232pub 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
289/// Create a render path for a fuchsia-style teardrop cursor.
290pub 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/// Struct combining a foreground and background color.
316#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
317pub struct Paint {
318    /// Color for foreground painting
319    pub fg: Color,
320    /// Color for background painting
321    pub bg: Color,
322}
323
324impl Paint {
325    /// Create a paint from a pair of hash codes
326    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
331/// Load a font from the provided path.
332pub 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/// Struct containing a font data.
350#[derive(Clone)]
351pub struct FontFace {
352    /// Font.
353    pub face: Face<'static>,
354}
355
356impl FontFace {
357    /// Create a new FontFace.
358    pub fn new(data: &'static [u8]) -> Result<FontFace, Error> {
359        let face = Face::from_slice(data, 0)?;
360        Ok(FontFace { face })
361    }
362
363    /// Get the ascent, in pixels, for this font at the specified size.
364    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    /// Get the descent, in pixels, for this font at the specified size.
373    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    /// Get the capital height, in pixels, for this font at the specified size.
382    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
391/// Return the width in pixels for the specified text, face and size.
392pub fn measure_text_width(face: &FontFace, font_size: f32, text: &str) -> f32 {
393    measure_text_size(face, font_size, text, false).width
394}
395
396/// Return the size in pixels for the specified text, face and size.
397pub 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
432/// Break up text into chunks guaranteed to be no wider than max_width when rendered with
433/// face at font_size.
434pub 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                        // Clone and translate raster.
647                        let raster = glyph
648                            .raster
649                            .clone()
650                            .translate(position.cast_unit::<euclid::UnknownUnit>());
651                        raster_union = raster_union + raster;
652
653                        // Expand bounding box.
654                        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/// Struct containing text grid details.
687#[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    /// Creates a new text grid using the specified font and cell size.
696    /// This determines the appropriate scale and offset for outlines
697    /// to provide optimal glyph layout in cells.
698    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        // Use a font size that allows the full ascent and descent to fit in cell.
704        let font_size = cell_size.height / (ascent - descent);
705        let y_scale = font_size / units_per_em;
706
707        // Use the horizontal advance for character '0' as the reference cell
708        // width and scale glyph outlines horizontally to match this size.
709        // This is important in order to allow glyph outlines to align with
710        // the right edge of the cell.
711        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        // Offset glyph outlines by ascent so integer translation to cell origin
721        // is enough for correct placement.
722        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    // This font creation method isn't ideal. The correct method would be to ask the Fuchsia
744    // font service for the font data.
745    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}