Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/windowing/winit/fonts/text_layout.rs
1use super::str_index_map::StrIndexMap;
2use crate::fonts::FontId;
3use crate::text_layout::{Glyph, Run, TextStyle};
4use cosmic_text::LayoutGlyph;
5use pathfinder_geometry::vector::vec2f;
6 
7/// Helper struct to construct [`Run`]s from a series of shaped glyphs.
8pub(super) struct RunBuilder<'a> {
9 runs: Vec<Run>,
10 font_in_current_run: FontId,
11 current_run_style: TextStyle,
12 current_run_width: f32,
13 glyphs_in_current_run: Vec<Glyph>,
14 styles_map: &'a TextStylesMap,
15 str_index_map: &'a StrIndexMap,
16}
17 
18impl<'a> RunBuilder<'a> {
19 pub(super) fn new(
20 styles_map: &'a TextStylesMap,
21 initial_font_id: FontId,
22 str_index_map: &'a StrIndexMap,
23 ) -> Self {
24 Self {
25 runs: vec![],
26 font_in_current_run: initial_font_id,
27 current_run_style: TextStyle::default(),
28 current_run_width: 0.0,
29 glyphs_in_current_run: vec![],
30 styles_map,
31 str_index_map,
32 }
33 }
34 
35 /// Reserves space for the provided number of glyphs in the current run.
36 pub fn reserve_capacity(&mut self, total: usize) {
37 self.glyphs_in_current_run
38 .reserve_exact(total.saturating_sub(self.glyphs_in_current_run.capacity()))
39 }
40 
41 /// Flushes the current style run by appending the current run into the runs list.
42 /// NOTE: if there are no glyphs in the run, it is not appended.
43 fn flush_current_style_run(&mut self) {
44 if !self.glyphs_in_current_run.is_empty() {
45 let excess_capacity =
46 self.glyphs_in_current_run.capacity() - self.glyphs_in_current_run.len();
47 let mut new_glyphs = Vec::with_capacity(excess_capacity);
48 std::mem::swap(&mut new_glyphs, &mut self.glyphs_in_current_run);
49 self.runs.push(Run {
50 font_id: self.font_in_current_run,
51 glyphs: new_glyphs,
52 styles: self.current_run_style,
53 width: self.current_run_width,
54 });
55 }
56 }
57 
58 /// Pushes a new laid out glyph into the `RunBuilder`. Internally, `font_id_fn` will be called
59 /// to get the `FontId` for the `glyph`.
60 pub(super) fn push_glyph<F: FnOnce(&fontdb::ID) -> FontId>(
61 &mut self,
62 glyph: LayoutGlyph,
63 font_id_fn: F,
64 ) {
65 let font_id = font_id_fn(&glyph.font_id);
66 let text_style = self.styles_map.get(glyph.metadata);
67 
68 // A run is a series of continuous glyphs that have the same style. We use the combination
69 // of font id (which is a proxy of the font properties such as bold or italic) and the
70 // `TextStyle` to determine when a new run should be created.
71 if font_id != self.font_in_current_run || text_style != self.current_run_style {
72 self.flush_current_style_run();
73 
74 self.current_run_width = 0.;
75 self.current_run_style = text_style;
76 self.font_in_current_run = font_id;
77 }
78 
79 let glyph_char_index = self
80 .str_index_map
81 .char_index(glyph.start)
82 .unwrap_or_else(|| self.str_index_map.num_chars());
83 
84 self.glyphs_in_current_run.push(Glyph {
85 id: glyph.glyph_id as u32,
86 position_along_baseline: vec2f(glyph.x, glyph.y),
87 index: glyph_char_index,
88 width: glyph.w,
89 });
90 
91 self.current_run_width += glyph.w;
92 }
93 
94 /// Returns the final list of [`Run`]s that were computed.
95 pub(super) fn build(mut self) -> Vec<Run> {
96 self.flush_current_style_run();
97 self.runs
98 }
99}
100 
101/// Simple map that maps an index to a [`TextStyle`].
102/// [`cosmic_text`] only supports setting a `usize` as metadata, so this struct is used to generate
103/// a mapping of an index to its corresponding `TextStyle`.
104///
105/// Though this is modeled internally as a `Vec`, use a new type to limit the API since some
106/// functions on a `Vec` (such as reordering) would break the mapping of index to text style.
107pub(super) struct TextStylesMap {
108 styles: Vec<TextStyle>,
109}
110 
111impl TextStylesMap {
112 pub(super) fn insert(&mut self, text_style: TextStyle) -> usize {
113 let size = self.styles.len();
114 self.styles.push(text_style);
115 size
116 }
117 
118 /// Gets the [`TextStyle`] at the given index. If no style is at the index, a default
119 /// `TextStyle` is returned.
120 pub(super) fn get(&self, index: usize) -> TextStyle {
121 self.styles.get(index).copied().unwrap_or_default()
122 }
123 
124 pub(super) fn new() -> Self {
125 Self {
126 styles: Default::default(),
127 }
128 }
129}
130