Skip to main content

terminal/
paths.rs

1// Copyright 2022 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
5use crate::renderer::{CursorShape, CursorStyle};
6use carnelian::Size;
7use carnelian::drawing::TextGrid;
8use carnelian::render::{Context as RenderContext, Path};
9use euclid::{point2, vec2};
10// Thickness of lines is determined by multiplying thickness factor
11// with the cell height. 1/16 has been chosen as that results in 1px
12// thick lines for a 16px cell height.
13const LINE_THICKNESS_FACTOR: f32 = 1.0 / 16.0;
14
15fn path_for_block(size: &Size, render_context: &mut RenderContext) -> Path {
16    let mut path_builder = render_context.path_builder().expect("path_builder");
17    path_builder
18        .move_to(point2(0.0, 0.0))
19        .line_to(point2(size.width, 0.0))
20        .line_to(point2(size.width, size.height))
21        .line_to(point2(0.0, size.height))
22        .line_to(point2(0.0, 0.0));
23    path_builder.build()
24}
25
26pub struct Line {
27    position_y: f32,
28    thickness: f32,
29}
30
31impl Line {
32    pub fn new(line_metrics: ttf_parser::LineMetrics, textgrid: &TextGrid) -> Self {
33        let scale_y = textgrid.scale.y;
34        let offset_y = textgrid.offset.y;
35        let position = line_metrics.position as f32 * scale_y;
36        let thickness = line_metrics.thickness as f32 * scale_y;
37        let position_y = offset_y - position;
38        Self { thickness, position_y }
39    }
40}
41
42fn line_bounds(line: Option<Line>, default_bounds: (f32, f32), cell_height: f32) -> (f32, f32) {
43    line.map_or(default_bounds, |Line { thickness, position_y: line_y }| {
44        let top = line_y - thickness / 2.0;
45        let bottom = line_y + thickness / 2.0;
46        let (_, default_bottom) = default_bounds;
47
48        if bottom > cell_height {
49            return (default_bottom - thickness, default_bottom);
50        }
51        (top, bottom)
52    })
53}
54
55pub fn path_for_underline(
56    size: &Size,
57    render_context: &mut RenderContext,
58    line: Option<Line>,
59) -> Path {
60    let mut path_builder = render_context.path_builder().expect("path_builder");
61    let default_top = size.height - size.height * LINE_THICKNESS_FACTOR;
62    let default_bottom = size.height;
63    let (top, bottom) = line_bounds(line, (default_top, default_bottom), size.height);
64
65    path_builder
66        .move_to(point2(0.0, top))
67        .line_to(point2(size.width, top))
68        .line_to(point2(size.width, bottom))
69        .line_to(point2(0.0, bottom))
70        .line_to(point2(0.0, top));
71    path_builder.build()
72}
73
74fn path_for_beam(size: &Size, render_context: &mut RenderContext) -> Path {
75    let mut path_builder = render_context.path_builder().expect("path_builder");
76    let right = size.height * LINE_THICKNESS_FACTOR;
77    path_builder
78        .move_to(point2(0.0, 0.0))
79        .line_to(point2(right, 0.0))
80        .line_to(point2(right, size.height))
81        .line_to(point2(0.0, size.height))
82        .line_to(point2(0.0, 0.0));
83    path_builder.build()
84}
85
86fn path_for_hollow_block(size: &Size, render_context: &mut RenderContext) -> Path {
87    let mut path_builder = render_context.path_builder().expect("path_builder");
88    let inset = size.height * LINE_THICKNESS_FACTOR;
89    let bottom_start = size.height - inset;
90    let right_start = size.width - inset;
91    path_builder
92        // top
93        .move_to(point2(0.0, 0.0))
94        .line_to(point2(size.width, 0.0))
95        .line_to(point2(size.width, inset))
96        .line_to(point2(0.0, inset))
97        .line_to(point2(0.0, 0.0))
98        // bottom
99        .move_to(point2(0.0, bottom_start))
100        .line_to(point2(size.width, bottom_start))
101        .line_to(point2(size.width, size.height))
102        .line_to(point2(0.0, size.height))
103        .line_to(point2(0.0, bottom_start))
104        // left
105        .move_to(point2(0.0, inset))
106        .line_to(point2(inset, inset))
107        .line_to(point2(inset, bottom_start))
108        .line_to(point2(0.0, bottom_start))
109        .line_to(point2(0.0, inset))
110        // right
111        .move_to(point2(right_start, inset))
112        .line_to(point2(size.width, inset))
113        .line_to(point2(size.width, bottom_start))
114        .line_to(point2(right_start, bottom_start))
115        .line_to(point2(right_start, inset));
116    path_builder.build()
117}
118
119pub fn path_for_strikeout(
120    size: &Size,
121    render_context: &mut RenderContext,
122    line: Option<Line>,
123) -> Path {
124    let mut path_builder = render_context.path_builder().expect("path_builder");
125    let default_top = size.height / 2.0;
126    let default_bottom = default_top + size.height * LINE_THICKNESS_FACTOR;
127    let (top, bottom) = line_bounds(line, (default_top, default_bottom), size.height);
128
129    path_builder
130        .move_to(point2(0.0, top))
131        .line_to(point2(size.width, top))
132        .line_to(point2(size.width, bottom))
133        .line_to(point2(0.0, bottom))
134        .line_to(point2(0.0, top));
135    path_builder.build()
136}
137
138// Box Drawings Light Horizontal, "─".
139fn path_for_unicode_2500(size: &Size, render_context: &mut RenderContext) -> Path {
140    let mut path_builder = render_context.path_builder().expect("path_builder");
141    let thickness = size.height * LINE_THICKNESS_FACTOR;
142    let top = size.height / 2.0 - thickness / 2.0;
143    let bottom = top + thickness;
144    path_builder
145        .move_to(point2(0.0, top))
146        .line_to(point2(size.width, top))
147        .line_to(point2(size.width, bottom))
148        .line_to(point2(0.0, bottom))
149        .line_to(point2(0.0, top));
150    path_builder.build()
151}
152
153// Box Drawings Light Vertical, "│".
154fn path_for_unicode_2502(size: &Size, render_context: &mut RenderContext) -> Path {
155    let mut path_builder = render_context.path_builder().expect("path_builder");
156    let thickness = size.height * LINE_THICKNESS_FACTOR;
157    let left = size.width / 2.0 - thickness / 2.0;
158    let right = left + thickness;
159    path_builder
160        .move_to(point2(left, 0.0))
161        .line_to(point2(right, 0.0))
162        .line_to(point2(right, size.height))
163        .line_to(point2(left, size.height))
164        .line_to(point2(left, 0.0));
165    path_builder.build()
166}
167
168// Box Drawings Light Arc Down and Right, "╭".
169fn path_for_unicode_256d(size: &Size, render_context: &mut RenderContext) -> Path {
170    let mut path_builder = render_context.path_builder().expect("path_builder");
171    let thickness = size.height * LINE_THICKNESS_FACTOR;
172    let bottom_left = size.width / 2.0 - thickness / 2.0;
173    let bottom_right = bottom_left + thickness;
174    let right_top = size.height / 2.0 - thickness / 2.0;
175    let right_bottom = right_top + thickness;
176    let radius = (size.height * 0.25).min(size.width * 0.25);
177    let inner_radius = radius - thickness / 2.0;
178    let outer_radius = inner_radius + thickness;
179    let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
180    let outer_control_dist = kappa * outer_radius;
181    let inner_control_dist = kappa * inner_radius;
182    let center = point2(size.width / 2.0, size.height / 2.0) + vec2(radius, radius);
183    let inner_p1 = center + vec2(-inner_control_dist, -inner_radius);
184    let inner_p2 = center + vec2(-inner_radius, -inner_control_dist);
185    let outer_p1 = center + vec2(-outer_radius, -outer_control_dist);
186    let outer_p2 = center + vec2(-outer_control_dist, -outer_radius);
187    path_builder
188        .move_to(point2(size.width, right_top))
189        .line_to(point2(size.width, right_bottom))
190        .line_to(point2(center.x, right_bottom))
191        .cubic_to(inner_p1, inner_p2, point2(bottom_right, center.y))
192        .line_to(point2(bottom_right, size.height))
193        .line_to(point2(bottom_left, size.height))
194        .line_to(point2(bottom_left, center.y))
195        .cubic_to(outer_p1, outer_p2, point2(center.x, right_top))
196        .line_to(point2(size.width, right_top));
197    path_builder.build()
198}
199
200// Box Drawings Light Arc Down and Left, "╮".
201fn path_for_unicode_256e(size: &Size, render_context: &mut RenderContext) -> Path {
202    let mut path_builder = render_context.path_builder().expect("path_builder");
203    let thickness = size.height * LINE_THICKNESS_FACTOR;
204    let bottom_left = size.width / 2.0 - thickness / 2.0;
205    let bottom_right = bottom_left + thickness;
206    let left_top = size.height / 2.0 - thickness / 2.0;
207    let left_bottom = left_top + thickness;
208    let radius = (size.height * 0.25).min(size.width * 0.25);
209    let inner_radius = radius - thickness / 2.0;
210    let outer_radius = inner_radius + thickness;
211    let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
212    let outer_control_dist = kappa * outer_radius;
213    let inner_control_dist = kappa * inner_radius;
214    let center = point2(size.width / 2.0, size.height / 2.0) + vec2(-radius, radius);
215    let inner_p1 = center + vec2(inner_radius, -inner_control_dist);
216    let inner_p2 = center + vec2(inner_control_dist, -inner_radius);
217    let outer_p1 = center + vec2(outer_control_dist, -outer_radius);
218    let outer_p2 = center + vec2(outer_radius, -outer_control_dist);
219    path_builder
220        .move_to(point2(0.0, left_top))
221        .line_to(point2(center.x, left_top))
222        .cubic_to(outer_p1, outer_p2, point2(bottom_right, center.y))
223        .line_to(point2(bottom_right, size.height))
224        .line_to(point2(bottom_left, size.height))
225        .line_to(point2(bottom_left, center.y))
226        .cubic_to(inner_p1, inner_p2, point2(center.x, left_bottom))
227        .line_to(point2(0.0, left_bottom))
228        .line_to(point2(0.0, left_top));
229    path_builder.build()
230}
231
232// Box Drawings Light Arc Up and Left, "╯".
233fn path_for_unicode_256f(size: &Size, render_context: &mut RenderContext) -> Path {
234    let mut path_builder = render_context.path_builder().expect("path_builder");
235    let thickness = size.height * LINE_THICKNESS_FACTOR;
236    let top_left = size.width / 2.0 - thickness / 2.0;
237    let top_right = top_left + thickness;
238    let left_top = size.height / 2.0 - thickness / 2.0;
239    let left_bottom = left_top + thickness;
240    let radius = (size.height * 0.25).min(size.width * 0.25);
241    let inner_radius = radius - thickness / 2.0;
242    let outer_radius = inner_radius + thickness;
243    let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
244    let outer_control_dist = kappa * outer_radius;
245    let inner_control_dist = kappa * inner_radius;
246    let center = point2(size.width / 2.0, size.height / 2.0) + vec2(-radius, -radius);
247    let inner_p1 = center + vec2(inner_control_dist, inner_radius);
248    let inner_p2 = center + vec2(inner_radius, inner_control_dist);
249    let outer_p1 = center + vec2(outer_radius, outer_control_dist);
250    let outer_p2 = center + vec2(outer_control_dist, outer_radius);
251    path_builder
252        .move_to(point2(top_left, 0.0))
253        .line_to(point2(top_right, 0.0))
254        .line_to(point2(top_right, center.y))
255        .cubic_to(outer_p1, outer_p2, point2(center.x, left_bottom))
256        .line_to(point2(0.0, left_bottom))
257        .line_to(point2(0.0, left_top))
258        .line_to(point2(center.x, left_top))
259        .cubic_to(inner_p1, inner_p2, point2(top_left, center.y))
260        .line_to(point2(top_left, 0.0));
261    path_builder.build()
262}
263
264// Box Drawings Light Arc Up and Right, "╰".
265fn path_for_unicode_2570(size: &Size, render_context: &mut RenderContext) -> Path {
266    let mut path_builder = render_context.path_builder().expect("path_builder");
267    let thickness = size.height * LINE_THICKNESS_FACTOR;
268    let top_left = size.width / 2.0 - thickness / 2.0;
269    let top_right = top_left + thickness;
270    let right_top = size.height / 2.0 - thickness / 2.0;
271    let right_bottom = right_top + thickness;
272    let radius = (size.height * 0.25).min(size.width * 0.25);
273    let inner_radius = radius - thickness / 2.0;
274    let outer_radius = inner_radius + thickness;
275    let kappa = 4.0 / 3.0 * (std::f32::consts::PI / 8.0).tan();
276    let outer_control_dist = kappa * outer_radius;
277    let inner_control_dist = kappa * inner_radius;
278    let center = point2(size.width / 2.0, size.height / 2.0) + vec2(radius, -radius);
279    let inner_p1 = center + vec2(-inner_radius, inner_control_dist);
280    let inner_p2 = center + vec2(-inner_control_dist, inner_radius);
281    let outer_p1 = center + vec2(-outer_control_dist, outer_radius);
282    let outer_p2 = center + vec2(-outer_radius, outer_control_dist);
283    path_builder
284        .move_to(point2(top_left, 0.0))
285        .line_to(point2(top_right, 0.0))
286        .line_to(point2(top_right, center.y))
287        .cubic_to(inner_p1, inner_p2, point2(center.x, right_top))
288        .line_to(point2(size.width, right_top))
289        .line_to(point2(size.width, right_bottom))
290        .line_to(point2(center.x, right_bottom))
291        .cubic_to(outer_p1, outer_p2, point2(top_left, center.y))
292        .line_to(point2(top_left, 0.0));
293    path_builder.build()
294}
295
296// Light Shade, "░".
297fn path_for_unicode_2591(size: &Size, render_context: &mut RenderContext) -> Path {
298    let mut path_builder = render_context.path_builder().expect("path_builder");
299    const GRID_SIZE: usize = 8;
300    let scale_x = size.width / GRID_SIZE as f32;
301    let scale_y = size.height / GRID_SIZE as f32;
302    for y in 0..GRID_SIZE {
303        for x in 0..GRID_SIZE {
304            let offset = if y % 2 == 0 { x } else { x + 2 };
305            // Fill every fourth grid cell.
306            if offset % 4 == 0 {
307                let x0 = x as f32 * scale_x;
308                let y0 = y as f32 * scale_y;
309                let x1 = (x + 1) as f32 * scale_x;
310                let y1 = (y + 1) as f32 * scale_y;
311                path_builder
312                    .move_to(point2(x0, y0))
313                    .line_to(point2(x1, y0))
314                    .line_to(point2(x1, y1))
315                    .line_to(point2(x0, y1))
316                    .line_to(point2(x0, y0));
317            }
318        }
319    }
320    path_builder.build()
321}
322
323// Medium Shade, "▒".
324fn path_for_unicode_2592(size: &Size, render_context: &mut RenderContext) -> Path {
325    let mut path_builder = render_context.path_builder().expect("path_builder");
326    const GRID_SIZE: usize = 9;
327    let scale_x = size.width / GRID_SIZE as f32;
328    let scale_y = size.height / GRID_SIZE as f32;
329    for y in 0..GRID_SIZE {
330        for x in 0..GRID_SIZE {
331            let offset = if y % 2 == 0 { x } else { x + 1 };
332            // Fill every other grid cell.
333            if offset % 2 == 0 {
334                let x0 = x as f32 * scale_x;
335                let y0 = y as f32 * scale_y;
336                let x1 = (x + 1) as f32 * scale_x;
337                let y1 = (y + 1) as f32 * scale_y;
338                path_builder
339                    .move_to(point2(x0, y0))
340                    .line_to(point2(x1, y0))
341                    .line_to(point2(x1, y1))
342                    .line_to(point2(x0, y1))
343                    .line_to(point2(x0, y0));
344            }
345        }
346    }
347    path_builder.build()
348}
349
350// Dark Shade, "▓".
351fn path_for_unicode_2593(size: &Size, render_context: &mut RenderContext) -> Path {
352    let mut path_builder = render_context.path_builder().expect("path_builder");
353    const GRID_SIZE: usize = 8;
354    let scale_x = size.width / GRID_SIZE as f32;
355    let scale_y = size.height / GRID_SIZE as f32;
356    for y in 0..GRID_SIZE {
357        for x in 0..GRID_SIZE {
358            let offset = if y % 2 == 0 { x } else { x + 2 };
359            // Skip every fourth grid cell.
360            if offset % 4 != 0 {
361                let x0 = x as f32 * scale_x;
362                let y0 = y as f32 * scale_y;
363                let x1 = (x + 1) as f32 * scale_x;
364                let y1 = (y + 1) as f32 * scale_y;
365                path_builder
366                    .move_to(point2(x0, y0))
367                    .line_to(point2(x1, y0))
368                    .line_to(point2(x1, y1))
369                    .line_to(point2(x0, y1))
370                    .line_to(point2(x0, y0));
371            }
372        }
373    }
374    path_builder.build()
375}
376
377// Heavy Check Mark, "✔".
378fn path_for_unicode_2714(size: &Size, render_context: &mut RenderContext) -> Path {
379    let mut path_builder = render_context.path_builder().expect("path_builder");
380    const OFFSET_FACTOR: f32 = 1.0 / 12.0;
381    let offset = size.height * OFFSET_FACTOR;
382    let center_offset = offset * 3.0;
383    let left_top = 0.4 * size.height;
384    let right_top = 0.25 * size.height;
385    let center = 0.3 * size.width;
386    let bottom = 0.75 * size.height;
387    let left = 0.05;
388    let right = size.width - 0.05;
389    path_builder
390        .move_to(point2(left, left_top + offset))
391        .line_to(point2(left + offset, left_top))
392        .line_to(point2(center, bottom - center_offset))
393        .line_to(point2(right - offset, right_top))
394        .line_to(point2(right, right_top + offset))
395        .line_to(point2(center, bottom))
396        .line_to(point2(left, left_top + offset));
397    path_builder.build()
398}
399
400// Heavy Ballot X, "✘".
401fn path_for_unicode_2718(size: &Size, render_context: &mut RenderContext) -> Path {
402    let mut path_builder = render_context.path_builder().expect("path_builder");
403    const OFFSET_FACTOR: f32 = 1.0 / 12.0;
404    let offset = size.height * OFFSET_FACTOR;
405    let center_offset = offset;
406    let top = 0.25 * size.height;
407    let center_x = size.width / 2.0;
408    let center_y = size.height / 2.0;
409    let bottom = 0.75 * size.height;
410    let left = 0.05;
411    let right = size.width - 0.05;
412    path_builder
413        .move_to(point2(left, top + offset))
414        .line_to(point2(left + offset, top))
415        .line_to(point2(center_x, center_y - center_offset))
416        .line_to(point2(right - offset, top))
417        .line_to(point2(right, top + offset))
418        .line_to(point2(center_x + center_offset, center_y))
419        .line_to(point2(right, bottom - offset))
420        .line_to(point2(right - offset, bottom))
421        .line_to(point2(center_x, center_y + center_offset))
422        .line_to(point2(left + offset, bottom))
423        .line_to(point2(left, bottom - offset))
424        .line_to(point2(center_x - center_offset, center_y))
425        .line_to(point2(left, top + offset));
426    path_builder.build()
427}
428
429// Heavy Left-Pointing Angle Quotation Mark Ornament, "❮".
430fn path_for_unicode_276e(size: &Size, render_context: &mut RenderContext) -> Path {
431    let mut path_builder = render_context.path_builder().expect("path_builder");
432    const THICKNESS_FACTOR: f32 = 1.0 / 8.0;
433    let thickness = size.height * THICKNESS_FACTOR;
434    let top = 0.25 * size.height;
435    let bottom = 0.85 * size.height;
436    let left = 0.2 * size.width;
437    let right = 0.8 * size.width;
438    let center_y = top + (bottom - top) / 2.0;
439    path_builder
440        .move_to(point2(right - thickness, top))
441        .line_to(point2(right, top))
442        .line_to(point2(left + thickness, center_y))
443        .line_to(point2(right, bottom))
444        .line_to(point2(right - thickness, bottom))
445        .line_to(point2(left, center_y))
446        .line_to(point2(right - thickness, top));
447    path_builder.build()
448}
449
450// Heavy Right-Pointing Angle Quotation Mark Ornament, "❯".
451fn path_for_unicode_276f(size: &Size, render_context: &mut RenderContext) -> Path {
452    let mut path_builder = render_context.path_builder().expect("path_builder");
453    const THICKNESS_FACTOR: f32 = 1.0 / 8.0;
454    let thickness = size.height * THICKNESS_FACTOR;
455    let top = 0.25 * size.height;
456    let bottom = 0.85 * size.height;
457    let left = 0.2 * size.width;
458    let right = 0.8 * size.width;
459    let center_y = top + (bottom - top) / 2.0;
460    path_builder
461        .move_to(point2(left, top))
462        .line_to(point2(left + thickness, top))
463        .line_to(point2(right, center_y))
464        .line_to(point2(left + thickness, bottom))
465        .line_to(point2(left, bottom))
466        .line_to(point2(right - thickness, center_y))
467        .line_to(point2(left, top));
468    path_builder.build()
469}
470
471// Triangle right, "".
472fn path_for_unicode_e0b0(size: &Size, render_context: &mut RenderContext) -> Path {
473    let mut path_builder = render_context.path_builder().expect("path_builder");
474    path_builder
475        .move_to(point2(0.0, 0.0))
476        .line_to(point2(size.width, size.height / 2.0))
477        .line_to(point2(0.0, size.height))
478        .line_to(point2(0.0, 0.0));
479    path_builder.build()
480}
481
482// Angle right, "".
483fn path_for_unicode_e0b1(size: &Size, render_context: &mut RenderContext) -> Path {
484    let mut path_builder = render_context.path_builder().expect("path_builder");
485    // Requires 45 degree angle for correct thickness.
486    let thickness = size.height * LINE_THICKNESS_FACTOR;
487    let offset = (2.0 * thickness * thickness).sqrt() / 2.0;
488    path_builder
489        .move_to(point2(offset, 0.0))
490        .line_to(point2(size.width + offset, size.height / 2.0))
491        .line_to(point2(offset, size.height))
492        .line_to(point2(-offset, size.height))
493        .line_to(point2(size.width - offset, size.height / 2.0))
494        .line_to(point2(-offset, 0.0))
495        .line_to(point2(offset, 0.0));
496    path_builder.build()
497}
498
499// Triangle left, "".
500fn path_for_unicode_e0b2(size: &Size, render_context: &mut RenderContext) -> Path {
501    let mut path_builder = render_context.path_builder().expect("path_builder");
502    path_builder
503        .move_to(point2(size.width, 0.0))
504        .line_to(point2(size.width, size.height))
505        .line_to(point2(0.0, size.height / 2.0))
506        .line_to(point2(size.width, 0.0));
507    path_builder.build()
508}
509
510// Angle left, "".
511fn path_for_unicode_e0b3(size: &Size, render_context: &mut RenderContext) -> Path {
512    let mut path_builder = render_context.path_builder().expect("path_builder");
513    // Requires 45 degree angle for correct thickness.
514    let thickness = size.height * LINE_THICKNESS_FACTOR;
515    let offset = (2.0 * thickness * thickness).sqrt() / 2.0;
516    path_builder
517        .move_to(point2(size.width + offset, 0.0))
518        .line_to(point2(offset, size.height / 2.0))
519        .line_to(point2(size.width + offset, size.height))
520        .line_to(point2(size.width - offset, size.height))
521        .line_to(point2(-offset, size.height / 2.0))
522        .line_to(point2(size.width - offset, 0.0))
523        .line_to(point2(size.width + offset, 0.0));
524    path_builder.build()
525}
526
527// Lower right triangle, "".
528fn path_for_unicode_e0ba(size: &Size, render_context: &mut RenderContext) -> Path {
529    let mut path_builder = render_context.path_builder().expect("path_builder");
530    path_builder
531        .move_to(point2(size.width, 0.0))
532        .line_to(point2(size.width, size.height))
533        .line_to(point2(0.0, size.height))
534        .line_to(point2(size.width, 0.0));
535    path_builder.build()
536}
537
538// Upper left triangle, "".
539fn path_for_unicode_e0bc(size: &Size, render_context: &mut RenderContext) -> Path {
540    let mut path_builder = render_context.path_builder().expect("path_builder");
541    path_builder
542        .move_to(point2(0.0, 0.0))
543        .line_to(point2(size.width, 0.0))
544        .line_to(point2(0.0, size.height))
545        .line_to(point2(0.0, 0.0));
546    path_builder.build()
547}
548
549pub fn maybe_path_for_cursor_style(
550    render_context: &mut RenderContext,
551    cursor_style: CursorStyle,
552    cell_size: &Size,
553) -> Option<Path> {
554    match cursor_style.shape {
555        CursorShape::Block => Some(path_for_block(cell_size, render_context)),
556        CursorShape::Underline => Some(path_for_underline(cell_size, render_context, None)),
557        CursorShape::Beam => Some(path_for_beam(cell_size, render_context)),
558        CursorShape::HollowBlock => Some(path_for_hollow_block(cell_size, render_context)),
559        CursorShape::Hidden => None,
560    }
561}
562
563pub fn maybe_path_for_char(
564    render_context: &mut RenderContext,
565    c: char,
566    cell_size: &Size,
567) -> Option<Path> {
568    match c {
569        '\u{2500}' => Some(path_for_unicode_2500(cell_size, render_context)),
570        '\u{2502}' => Some(path_for_unicode_2502(cell_size, render_context)),
571        '\u{256d}' => Some(path_for_unicode_256d(cell_size, render_context)),
572        '\u{256e}' => Some(path_for_unicode_256e(cell_size, render_context)),
573        '\u{256f}' => Some(path_for_unicode_256f(cell_size, render_context)),
574        '\u{2570}' => Some(path_for_unicode_2570(cell_size, render_context)),
575        '\u{2591}' => Some(path_for_unicode_2591(cell_size, render_context)),
576        '\u{2592}' => Some(path_for_unicode_2592(cell_size, render_context)),
577        '\u{2593}' => Some(path_for_unicode_2593(cell_size, render_context)),
578        '\u{2714}' => Some(path_for_unicode_2714(cell_size, render_context)),
579        '\u{2718}' => Some(path_for_unicode_2718(cell_size, render_context)),
580        '\u{276e}' => Some(path_for_unicode_276e(cell_size, render_context)),
581        '\u{276f}' => Some(path_for_unicode_276f(cell_size, render_context)),
582        '\u{e0b0}' => Some(path_for_unicode_e0b0(cell_size, render_context)),
583        '\u{e0b1}' => Some(path_for_unicode_e0b1(cell_size, render_context)),
584        '\u{e0b2}' => Some(path_for_unicode_e0b2(cell_size, render_context)),
585        '\u{e0b3}' => Some(path_for_unicode_e0b3(cell_size, render_context)),
586        '\u{e0ba}' => Some(path_for_unicode_e0ba(cell_size, render_context)),
587        '\u{e0bc}' => Some(path_for_unicode_e0bc(cell_size, render_context)),
588        _ => None,
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use super::*;
595    use anyhow::Error;
596    use carnelian::drawing::{DisplayRotation, FontFace};
597    use carnelian::render::{ContextInner, generic};
598    use euclid::size2;
599
600    // This font creation method isn't ideal. The correct method would be to ask the Fuchsia
601    // font service for the font data.
602    static FONT_DATA: &'static [u8] = include_bytes!(
603        "../../../../../prebuilt/third_party/fonts/robotomono/RobotoMono-Regular.ttf"
604    );
605    static FONT_FACE: std::sync::LazyLock<FontFace> =
606        std::sync::LazyLock::new(|| FontFace::new(&FONT_DATA).expect("Failed to create font"));
607
608    #[test]
609    fn check_cursor_paths() -> Result<(), Error> {
610        const SUPPORTED_CURSOR_STYLES: &[CursorStyle] = &[
611            CursorStyle { shape: CursorShape::Block, blinking: false },
612            CursorStyle { shape: CursorShape::Underline, blinking: false },
613            CursorStyle { shape: CursorShape::Beam, blinking: false },
614            CursorStyle { shape: CursorShape::HollowBlock, blinking: false },
615        ];
616        let size = size2(64, 64);
617        let forma_context = generic::Forma::new_context_without_token(size, DisplayRotation::Deg0);
618        let mut render_context = RenderContext { inner: ContextInner::Forma(forma_context) };
619        let cell_size = Size::new(8.0, 16.0);
620        for cursor_style in SUPPORTED_CURSOR_STYLES {
621            let result =
622                maybe_path_for_cursor_style(&mut render_context, *cursor_style, &cell_size);
623            assert_eq!(result.is_some(), true);
624        }
625        Ok(())
626    }
627
628    #[test]
629    fn check_strikeout_path() -> Result<(), Error> {
630        let size = size2(64, 64);
631        let forma_context = generic::Forma::new_context_without_token(size, DisplayRotation::Deg0);
632        let mut render_context = RenderContext { inner: ContextInner::Forma(forma_context) };
633        let cell_size = Size::new(8.0, 16.0);
634        let textgrid = TextGrid::new(&FONT_FACE, &cell_size);
635        let _ = path_for_strikeout(
636            &cell_size,
637            &mut render_context,
638            FONT_FACE
639                .face
640                .strikeout_metrics()
641                .map(|line_metrics| Line::new(line_metrics, &textgrid)),
642        );
643        Ok(())
644    }
645
646    #[test]
647    fn check_underline_path() -> Result<(), Error> {
648        let size = size2(64, 64);
649        let forma_context = generic::Forma::new_context_without_token(size, DisplayRotation::Deg0);
650        let mut render_context = RenderContext { inner: ContextInner::Forma(forma_context) };
651        let cell_size = Size::new(8.0, 16.0);
652        let textgrid = TextGrid::new(&FONT_FACE, &cell_size);
653        let _ = path_for_underline(
654            &cell_size,
655            &mut render_context,
656            FONT_FACE
657                .face
658                .underline_metrics()
659                .map(|line_metrics| Line::new(line_metrics, &textgrid)),
660        );
661        Ok(())
662    }
663
664    #[test]
665    fn check_unicode_paths() -> Result<(), Error> {
666        const SUPPORTED_UNICODE_CHARS: &[char] = &[
667            '\u{2500}', '\u{2502}', '\u{256d}', '\u{256e}', '\u{256f}', '\u{2570}', '\u{2591}',
668            '\u{2592}', '\u{2593}', '\u{2714}', '\u{2718}', '\u{276e}', '\u{276f}', '\u{e0b0}',
669            '\u{e0b1}', '\u{e0b2}', '\u{e0b3}', '\u{e0ba}', '\u{e0bc}',
670        ];
671        let size = size2(64, 64);
672        let forma_context = generic::Forma::new_context_without_token(size, DisplayRotation::Deg0);
673        let mut render_context = RenderContext { inner: ContextInner::Forma(forma_context) };
674        let cell_size = Size::new(8.0, 16.0);
675        for c in SUPPORTED_UNICODE_CHARS {
676            let result = maybe_path_for_char(&mut render_context, *c, &cell_size);
677            assert_eq!(result.is_some(), true);
678        }
679        Ok(())
680    }
681}