Skip to main content

gpui/text_system/
text_system.rs

1use crate::{
2    Bounds, Font, FontId, FontMetrics, ForegroundDependency, GlyphId, GlyphRenderMode, Pixels,
3    PlatformTextSystem, PreparedRasterStyle, RasterStyleRequest, RenderGlyphParams, Result, Size,
4    TextRenderingMode, ValidatedRasterizedGlyph, px,
5};
6use anyhow::{Context as _, anyhow};
7use collections::FxHashMap;
8use palette::Hsla;
9use parking_lot::{RwLock, RwLockUpgradableReadGuard};
10use smallvec::SmallVec;
11use std::{borrow::Cow, sync::Arc};
12
13/// The platform-independent font database and metrics cache, shared by all windows.
14///
15/// Get it from [`App::text_system`](crate::App::text_system) and use it to register fonts with
16/// [`TextSystem::add_fonts`], to measure glyphs and to resolve [`Font`]s to
17/// [`FontId`]s. To shape text for painting, use the [`WindowTextSystem`](crate::WindowTextSystem) of the
18/// window, which also caches the layouts.
19pub struct TextSystem {
20    pub(super) platform_text_system: Arc<dyn PlatformTextSystem>,
21    font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
22    font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
23}
24
25impl TextSystem {
26    /// Creates a text system that delegates font work to `platform_text_system`.
27    ///
28    /// GPUI creates the text system for each application. You don't need to call this
29    /// yourself, except in tests that supply their own platform text system.
30    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
31        TextSystem {
32            platform_text_system,
33            font_metrics: RwLock::default(),
34            font_ids_by_font: RwLock::default(),
35        }
36    }
37
38    /// Returns the font family names that this text system can resolve.
39    ///
40    /// Use these names as [`Font::family`] values. On the web, the list contains
41    /// only the families registered with [`TextSystem::add_fonts`] and the
42    /// `.SystemUIFont` alias, because the web has no system fonts.
43    pub fn all_font_names(&self) -> Vec<String> {
44        self.platform_text_system.all_font_names()
45    }
46
47    /// Registers font files so that [`Font`]s can resolve to them.
48    ///
49    /// Each entry is the bytes of one font file, such as a `.ttf` or `.otf`.
50    /// Use `Cow::Borrowed` for bytes compiled into the binary with
51    /// `include_bytes!`, and `Cow::Owned` for bytes loaded at runtime. The
52    /// family names come from each font's name table, so pass those names to
53    /// [`font`](crate::font) or set [`Font::family`] to use them.
54    ///
55    /// The web starts with an empty font database, so add the fonts your
56    /// application uses before you open a window.
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if an entry contains no font face that the platform
61    /// supports. The parley backend, used on macOS, Windows, Linux, and the web, checks
62    /// every entry before it registers any, so a failed call leaves the set of
63    /// fonts unchanged.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// # use gpui::TestApp;
69    /// # use std::borrow::Cow;
70    /// # let mut app = TestApp::new();
71    /// let bytes = include_bytes!(concat!(
72    ///     env!("CARGO_MANIFEST_DIR"),
73    ///     "/../../assets/fonts/lilex/Lilex-Regular.ttf"
74    /// ));
75    /// app.text_system()
76    ///     .add_fonts(vec![Cow::Borrowed(bytes.as_slice())])
77    ///     .expect("Lilex is a valid font");
78    /// ```
79    ///
80    /// See [text](crate::_documentation::guides::text) for how fonts are chosen.
81    pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
82        // Serialize registration with cache misses so an in-flight lookup cannot
83        // repopulate a stale miss after the newly registered fonts become available.
84        let mut font_ids = self.font_ids_by_font.write();
85        let result = self.platform_text_system.add_fonts(fonts);
86        font_ids.clear();
87        result
88    }
89
90    /// Resolves a font family and style to a cached [`FontId`].
91    fn font_id(&self, font: &Font) -> Result<FontId> {
92        fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
93            match font_id {
94                Ok(font_id) => Ok(*font_id),
95                Err(err) => Err(anyhow!("{err}")),
96            }
97        }
98
99        let font_id = self
100            .font_ids_by_font
101            .read()
102            .get(font)
103            .map(clone_font_id_result);
104        if let Some(font_id) = font_id {
105            font_id
106        } else {
107            let mut font_ids = self.font_ids_by_font.write();
108            if let Some(font_id) = font_ids.get(font) {
109                return clone_font_id_result(font_id);
110            }
111            let font_id = self.platform_text_system.font_id(font);
112            font_ids.insert(font.clone(), clone_font_id_result(&font_id));
113            font_id
114        }
115    }
116
117    /// Resolves a [`Font`] to the [`FontId`] of the face that will be used to draw it.
118    ///
119    /// Resolution applies the family, weight, style, width, and fallbacks, and
120    /// the result is cached. Use the returned id with the metric methods on this
121    /// type, such as [`TextSystem::ascent`].
122    ///
123    /// # Panics
124    ///
125    /// Panics if the platform cannot resolve the font. Pass the family name of
126    /// a font from [`TextSystem::all_font_names`] or one added with
127    /// [`TextSystem::add_fonts`] to avoid this.
128    pub fn resolve_font(&self, font: &Font) -> FontId {
129        self.font_id(font)
130            .unwrap_or_else(|error| panic!("failed to resolve font '{}': {error}", font.family))
131    }
132
133    /// Resolves each font and loads its platform caches ahead of time.
134    ///
135    /// Loading system fonts can be slow, so call this from a background
136    /// executor before the first frame that uses the fonts. Fonts that are not
137    /// prewarmed are loaded when they are first used.
138    ///
139    /// # Panics
140    ///
141    /// Panics if any font cannot be resolved, as [`TextSystem::resolve_font`] does.
142    pub fn prewarm_fonts(&self, fonts: &[Font]) {
143        let mut font_ids = SmallVec::<[FontId; 8]>::new();
144        for font in fonts {
145            let font_id = self.resolve_font(font);
146            if !font_ids.contains(&font_id) {
147                font_ids.push(font_id);
148            }
149        }
150        self.platform_text_system.prewarm_fonts(&font_ids);
151    }
152
153    /// Returns the font's bounding box at `font_size` in logical pixels.
154    ///
155    /// The bounding box is the smallest rectangle that encloses every glyph in
156    /// the font when they are all drawn at the same origin.
157    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
158        self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
159    }
160
161    /// Returns the bounding rectangle of the glyph for `character` at `font_size` in logical pixels.
162    ///
163    /// # Errors
164    ///
165    /// Returns an error if the font has no glyph for `character` or the platform
166    /// cannot read its bounds.
167    pub fn typographic_bounds(
168        &self,
169        font_id: FontId,
170        font_size: Pixels,
171        character: char,
172    ) -> Result<Bounds<Pixels>> {
173        let glyph_id = self
174            .platform_text_system
175            .glyph_for_char(font_id, character)
176            .with_context(|| format!("glyph not found for character '{character}'"))?;
177        let bounds = self
178            .platform_text_system
179            .typographic_bounds(font_id, glyph_id)?;
180        Ok(self.read_metrics(font_id, |metrics| {
181            (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
182        }))
183    }
184
185    /// Returns the advance of `ch` at `font_size`: how far the pen moves after drawing it.
186    ///
187    /// The `width` of the result is the advance, which is the glyph's horizontal
188    /// width in the font, not its ink width. Layout uses it to place the next character.
189    ///
190    /// # Errors
191    ///
192    /// Returns an error if the font has no glyph for `ch`.
193    pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
194        let glyph_id = self
195            .platform_text_system
196            .glyph_for_char(font_id, ch)
197            .with_context(|| format!("glyph not found for character '{ch}'"))?;
198        let result = self.platform_text_system.advance(font_id, glyph_id)?
199            / self.units_per_em(font_id) as f32;
200
201        Ok(result * font_size)
202    }
203
204    /// Returns the number of font units in the font's em square.
205    ///
206    /// The em square is the grid that font sizes are measured on. Divide a
207    /// font-unit measurement by this value to get a fraction of the font size.
208    pub fn units_per_em(&self, font_id: FontId) -> u32 {
209        self.read_metrics(font_id, |metrics| metrics.units_per_em)
210    }
211
212    /// Returns the distance from the baseline up to the top of the font's tallest glyphs, at `font_size`.
213    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
214        self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
215    }
216
217    /// Returns the distance from the baseline down to the bottom of the font's lowest glyphs, at `font_size`.
218    ///
219    /// The value is positive. It is the distance below the baseline, not a
220    /// signed coordinate.
221    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
222        self.read_metrics(font_id, |metrics| metrics.descent(font_size))
223    }
224
225    /// Returns the height of lowercase `x` at `font_size`.
226    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
227        self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
228    }
229
230    /// Returns the distance from the top of a line box of `line_height` to the baseline.
231    ///
232    /// The ascent and descent are centered in the line box, so extra
233    /// `line_height` is split evenly above and below the glyphs. Use this to
234    /// place text inside a box with a fixed height.
235    pub fn baseline_offset(
236        &self,
237        font_id: FontId,
238        font_size: Pixels,
239        line_height: Pixels,
240    ) -> Pixels {
241        let ascent = self.ascent(font_id, font_size);
242        let descent = self.descent(font_id, font_size);
243        let padding_top = (line_height - ascent - descent) / 2.;
244        padding_top + ascent
245    }
246
247    fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
248        let lock = self.font_metrics.upgradable_read();
249
250        if let Some(metrics) = lock.get(&font_id) {
251            read(metrics)
252        } else {
253            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
254            let metrics = lock
255                .entry(font_id)
256                .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
257            read(metrics)
258        }
259    }
260
261    /// Rasterizes one glyph to pixels, and checks that the result is well formed.
262    ///
263    /// GPUI calls this when it draws a glyph that is not in the glyph atlas yet.
264    /// Application code rarely needs it.
265    ///
266    /// # Errors
267    ///
268    /// Returns an error if the platform cannot rasterize the glyph, or if the
269    /// pixel buffer does not match the size and format it reports. See
270    /// [`RasterizedGlyph::validate`](crate::RasterizedGlyph::validate).
271    pub fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<ValidatedRasterizedGlyph> {
272        let glyph = self.platform_text_system.rasterize_glyph(params)?;
273
274        ValidatedRasterizedGlyph::new(glyph)
275    }
276
277    /// Normalizes a requested scene color and render mode into the settings which affect the
278    /// cached glyph raster.
279    pub(crate) fn prepare_raster_style(
280        &self,
281        font_id: FontId,
282        glyph_id: GlyphId,
283        scene_color: Hsla,
284        requested_mode: GlyphRenderMode,
285    ) -> PreparedRasterStyle {
286        self.platform_text_system
287            .prepare_raster_style(RasterStyleRequest {
288                font_id,
289                glyph_id,
290                scene_color: crate::hsla_to_rgba(scene_color),
291                requested_mode,
292                foreground_dependency: ForegroundDependency::Full,
293            })
294    }
295
296    /// Returns the text rendering mode recommended by the platform for the given font and size.
297    /// The return value will never be [`TextRenderingMode::PlatformDefault`].
298    pub(crate) fn recommended_rendering_mode(
299        &self,
300        font_id: FontId,
301        font_size: Pixels,
302    ) -> TextRenderingMode {
303        self.platform_text_system
304            .recommended_rendering_mode(font_id, font_size)
305    }
306}