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}