StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use super::str_index_map::StrIndexMap; |
| 2 | use crate::fonts::FontId; |
| 3 | use crate::text_layout::{Glyph, Run, TextStyle}; |
| 4 | use cosmic_text::LayoutGlyph; |
| 5 | use pathfinder_geometry::vector::vec2f; |
| 6 | |
| 7 | /// Helper struct to construct [`Run`]s from a series of shaped glyphs. |
| 8 | pub(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 | |
| 18 | impl<'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. |
| 107 | pub(super) struct TextStylesMap { |
| 108 | styles: Vec<TextStyle>, |
| 109 | } |
| 110 | |
| 111 | impl 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 |