Skip to main content

gpui_engine_default/
text_system.rs

1//! Shaping, metric, and wrapping caches for text rendering.
2//!
3//! `TextSystem` wraps a platform text system with the engine's font-id,
4//! metric, raster-bounds, and line-wrapper pools. It has no knowledge of
5//! windows; the facade's window-scoped layer drives it.
6
7use anyhow::{Context as _, Result, anyhow};
8use collections::{FxHashMap, FxHashSet};
9use gpui_engine::{
10    Font, FontId, FontMetrics, FontRun, LineLayout, LineLayoutIndex, LineWrapper,
11    LineWrapperHandle, MissingGlyph, MissingGlyphReports, MissingGlyphSink, PlatformTextSystem,
12    RenderGlyphParams, TextRenderingMode, TextSystem, WrappedLineLayout, font,
13};
14use gpui_shared_string::SharedString;
15use gpui_types::{Bounds, DevicePixels, Hsla, Pixels, Size, px};
16use itertools::Itertools;
17use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
18use smallvec::{SmallVec, smallvec};
19use std::borrow::Cow;
20use std::collections::VecDeque;
21use std::future::Future;
22use std::ops::Range;
23use std::pin::Pin;
24use std::sync::{
25    Arc,
26    atomic::{AtomicUsize, Ordering},
27};
28
29// Leave enough room below the underline for its stroke while keeping it below the baseline.
30const UNDERLINE_DESCENT_OFFSET_FACTOR: f32 = 0.618;
31
32/// Returns the vertical offset used to paint an underline within a line.
33pub fn underline_y_offset(line_height: Pixels, ascent: Pixels, descent: Pixels) -> Pixels {
34    let padding_top = (line_height - ascent - descent) / 2.;
35    padding_top + ascent + descent * UNDERLINE_DESCENT_OFFSET_FACTOR
36}
37
38const MAX_REPORTED_MISSING_GLYPHS: usize = 1024;
39
40#[derive(Default)]
41struct MissingGlyphState {
42    reported: FxHashSet<MissingGlyph>,
43    reported_order: VecDeque<MissingGlyph>,
44    generation: usize,
45}
46
47impl MissingGlyphState {
48    fn reset(&mut self, generation: usize) {
49        self.reported.clear();
50        self.reported_order.clear();
51        self.generation = generation;
52    }
53}
54
55struct QueuedMissingGlyph {
56    generation: usize,
57    missing_glyph: MissingGlyph,
58}
59
60/// Collects missing-glyph reports without invoking application code during layout.
61struct MissingGlyphReporter {
62    generation: Arc<AtomicUsize>,
63    sender: async_channel::Sender<QueuedMissingGlyph>,
64}
65
66impl MissingGlyphSink for MissingGlyphReporter {
67    fn report(&self, missing_glyphs: Vec<MissingGlyph>) {
68        if self.sender.is_closed() {
69            return;
70        }
71
72        let generation = self.generation.load(Ordering::Acquire);
73        // Repetitions within a line must not fill the queue before its other
74        // missing glyphs. Cross-report deduplication belongs to the receiver.
75        for missing_glyph in missing_glyphs.into_iter().unique() {
76            let queued = QueuedMissingGlyph {
77                generation,
78                missing_glyph,
79            };
80            if self.sender.try_send(queued).is_err() {
81                break;
82            }
83        }
84    }
85}
86
87impl MissingGlyphReporter {
88    fn reset(&self) {
89        self.generation.fetch_add(1, Ordering::AcqRel);
90    }
91}
92
93/// Receives batches of grapheme clusters that exhausted font fallback.
94pub struct MissingGlyphReceiver {
95    state: MissingGlyphState,
96    generation: Arc<AtomicUsize>,
97    receiver: async_channel::Receiver<QueuedMissingGlyph>,
98}
99
100impl MissingGlyphReceiver {
101    /// Waits until at least one new missing glyph has been observed.
102    ///
103    /// # Errors
104    ///
105    /// Returns [`async_channel::RecvError`] if the reporting channel is closed.
106    pub async fn recv(
107        &mut self,
108    ) -> std::result::Result<Vec<MissingGlyph>, async_channel::RecvError> {
109        loop {
110            let queued = self.receiver.recv().await?;
111            let mut missing_glyphs = Vec::new();
112            for queued in std::iter::once(queued)
113                .chain(std::iter::from_fn(|| self.receiver.try_recv().ok()))
114                .take(MAX_REPORTED_MISSING_GLYPHS)
115            {
116                let generation = self.generation.load(Ordering::Acquire);
117                if self.state.generation != generation {
118                    self.state.reset(generation);
119                    missing_glyphs.clear();
120                }
121                if queued.generation != generation
122                    || !self.state.reported.insert(queued.missing_glyph.clone())
123                {
124                    continue;
125                }
126                self.state
127                    .reported_order
128                    .push_back(queued.missing_glyph.clone());
129                missing_glyphs.push(queued.missing_glyph);
130                if self.state.reported.len() > MAX_REPORTED_MISSING_GLYPHS
131                    && let Some(expired) = self.state.reported_order.pop_front()
132                {
133                    self.state.reported.remove(&expired);
134                }
135            }
136            if !missing_glyphs.is_empty() {
137                return Ok(missing_glyphs);
138            }
139            // A producer can keep refilling the queue with already-reported
140            // glyphs. Bound work per poll even when every report is filtered out.
141            let mut yielded = false;
142            std::future::poll_fn(|cx| {
143                if std::mem::replace(&mut yielded, true) {
144                    std::task::Poll::Ready(())
145                } else {
146                    cx.waker().wake_by_ref();
147                    std::task::Poll::Pending
148                }
149            })
150            .await;
151        }
152    }
153}
154
155impl Drop for MissingGlyphReceiver {
156    fn drop(&mut self) {
157        self.receiver.close();
158        while self.receiver.try_recv().is_ok() {}
159    }
160}
161
162impl MissingGlyphReports for MissingGlyphReceiver {
163    fn recv(&mut self) -> Pin<Box<dyn Future<Output = Option<Vec<MissingGlyph>>> + Send + '_>> {
164        Box::pin(async move { MissingGlyphReceiver::recv(self).await.ok() })
165    }
166}
167
168use crate::LineLayoutCache;
169
170/// The GPUI text rendering sub system.
171pub struct DefaultTextSystem {
172    platform_text_system: Arc<dyn PlatformTextSystem>,
173    font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
174    font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
175    raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
176    wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
177    font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
178    fallback_font_stack: SmallVec<[Font; 2]>,
179    font_generation: Arc<AtomicUsize>,
180    missing_glyph_reporter: Arc<MissingGlyphReporter>,
181    missing_glyph_receiver: Mutex<Option<MissingGlyphReceiver>>,
182    line_layout_cache: LineLayoutCache,
183}
184
185impl DefaultTextSystem {
186    /// Create a new DefaultTextSystem with the given platform text system.
187    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
188        let font_generation = Arc::<AtomicUsize>::default();
189        let line_layout_cache =
190            LineLayoutCache::new(platform_text_system.clone(), font_generation.clone());
191        let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
192        let missing_glyph_generation = Arc::<AtomicUsize>::default();
193        DefaultTextSystem {
194            platform_text_system,
195            font_metrics: RwLock::default(),
196            raster_bounds: RwLock::default(),
197            font_ids_by_font: RwLock::default(),
198            wrapper_pool: Mutex::default(),
199            font_runs_pool: Mutex::default(),
200            fallback_font_stack: smallvec![
201                // TODO: Remove this when Linux have implemented setting fallbacks.
202                font(".ZedMono"),
203                font(".ZedSans"),
204                font("Helvetica"),
205                font("Segoe UI"),     // Windows
206                font("Ubuntu"),       // Gnome (Ubuntu)
207                font("Adwaita Sans"), // Gnome 47
208                font("Cantarell"),    // Gnome
209                font("Noto Sans"),    // KDE
210                font("DejaVu Sans"),
211                font("Arial"), // macOS, Windows
212            ],
213            font_generation,
214            missing_glyph_reporter: Arc::new(MissingGlyphReporter {
215                generation: missing_glyph_generation.clone(),
216                sender,
217            }),
218            missing_glyph_receiver: Mutex::new(Some(MissingGlyphReceiver {
219                state: MissingGlyphState::default(),
220                generation: missing_glyph_generation,
221                receiver,
222            })),
223            line_layout_cache,
224        }
225    }
226
227    /// The platform text system this engine wraps.
228    pub fn platform_text_system(&self) -> &Arc<dyn PlatformTextSystem> {
229        &self.platform_text_system
230    }
231
232    /// The generation that advances whenever [`Self::add_fonts`] installs fonts.
233    pub fn font_generation(&self) -> &Arc<AtomicUsize> {
234        &self.font_generation
235    }
236
237    /// Takes a pooled font-run buffer, or an empty one when the pool is dry.
238    pub fn take_font_runs(&self) -> Vec<FontRun> {
239        self.font_runs_pool.lock().pop().unwrap_or_default()
240    }
241
242    /// Returns a font-run buffer to the pool for reuse.
243    pub fn recycle_font_runs(&self, font_runs: Vec<FontRun>) {
244        self.font_runs_pool.lock().push(font_runs);
245    }
246
247    /// Get sorted, unique font family names available to the platform text system.
248    ///
249    /// Includes fonts registered with [`Self::add_fonts`].
250    pub fn all_font_names(&self) -> Vec<String> {
251        let mut names = self.platform_text_system.all_font_names();
252        names.sort_unstable();
253        names.dedup();
254        names
255    }
256
257    /// Add a font's data to the text system.
258    ///
259    /// Cached font resolution and line layouts are invalidated after installation.
260    /// Layouts already in progress may complete against the previous font set.
261    pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
262        self.platform_text_system.add_fonts(fonts)?;
263        self.font_ids_by_font.write().clear();
264        self.missing_glyph_reporter.reset();
265        self.font_generation.fetch_add(1, Ordering::Release);
266        Ok(())
267    }
268
269    /// Get the FontId for the configure font family and style.
270    fn font_id(&self, font: &Font) -> Result<FontId> {
271        fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
272            match font_id {
273                Ok(font_id) => Ok(*font_id),
274                Err(err) => Err(anyhow!("{err}")),
275            }
276        }
277
278        let font_id = self
279            .font_ids_by_font
280            .read()
281            .get(font)
282            .map(clone_font_id_result);
283        if let Some(font_id) = font_id {
284            font_id
285        } else {
286            let font_id = self.platform_text_system.font_id(font);
287            self.font_ids_by_font
288                .write()
289                .insert(font.clone(), clone_font_id_result(&font_id));
290            font_id
291        }
292    }
293
294    /// Get the Font for the Font Id.
295    pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {
296        let lock = self.font_ids_by_font.read();
297        lock.iter()
298            .filter_map(|(font, result)| match result {
299                Ok(font_id) if *font_id == id => Some(font.clone()),
300                _ => None,
301            })
302            .next()
303    }
304
305    /// Resolves the specified font, falling back to the default font stack if
306    /// the font fails to load.
307    ///
308    /// # Panics
309    ///
310    /// Panics if the font and none of the fallbacks can be resolved.
311    pub fn resolve_font(&self, font: &Font) -> FontId {
312        if let Ok(font_id) = self.font_id(font) {
313            return font_id;
314        }
315        for fallback in &self.fallback_font_stack {
316            if let Ok(font_id) = self.font_id(fallback) {
317                return font_id;
318            }
319        }
320
321        panic!(
322            "failed to resolve font '{}' or any of the fallbacks: {}",
323            font.family,
324            self.fallback_font_stack
325                .iter()
326                .map(|fallback| &fallback.family)
327                .join(", ")
328        );
329    }
330
331    /// Prewarm any system font caches needed to shape text.
332    ///
333    /// This may be expensive, so callers should generally invoke it on a
334    /// background executor. Missing entries are still populated on demand by
335    /// the normal shaping path.
336    pub fn prewarm_fonts(&self, fonts: &[Font]) {
337        let mut font_ids = SmallVec::<[FontId; 8]>::new();
338        for font in fonts {
339            let font_id = self.resolve_font(font);
340            if !font_ids.contains(&font_id) {
341                font_ids.push(font_id);
342            }
343        }
344        self.platform_text_system.prewarm_fonts(&font_ids);
345    }
346
347    /// Get the bounding box for the given font and font size.
348    /// A font's bounding box is the smallest rectangle that could enclose all glyphs
349    /// in the font. superimposed over one another.
350    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
351        self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
352    }
353
354    /// Get the typographic bounds for the given character, in the given font and size.
355    pub fn typographic_bounds(
356        &self,
357        font_id: FontId,
358        font_size: Pixels,
359        character: char,
360    ) -> Result<Bounds<Pixels>> {
361        let glyph_id = self
362            .platform_text_system
363            .glyph_for_char(font_id, character)
364            .with_context(|| format!("glyph not found for character '{character}'"))?;
365        let bounds = self
366            .platform_text_system
367            .typographic_bounds(font_id, glyph_id)?;
368        Ok(self.read_metrics(font_id, |metrics| {
369            (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
370        }))
371    }
372
373    /// Get the advance width for the given character, in the given font and size.
374    pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
375        let glyph_id = self
376            .platform_text_system
377            .glyph_for_char(font_id, ch)
378            .with_context(|| format!("glyph not found for character '{ch}'"))?;
379        let result = self.platform_text_system.advance(font_id, glyph_id)?
380            / self.units_per_em(font_id) as f32;
381
382        Ok(result * font_size)
383    }
384
385    // Consider removing this?
386    /// Returns the shaped layout width of for the given character, in the given font and size.
387    pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
388        let mut buffer = [0; 4];
389        let buffer = ch.encode_utf8(&mut buffer);
390        self.platform_text_system
391            .layout_line(
392                buffer,
393                font_size,
394                &[FontRun {
395                    len: buffer.len(),
396                    font_id,
397                }],
398            )
399            .width
400    }
401
402    /// Returns the width of an `em`.
403    ///
404    /// Uses the width of the `m` character in the given font and size.
405    pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
406        Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)
407    }
408
409    /// Returns the advance width of an `em`.
410    ///
411    /// Uses the advance width of the `m` character in the given font and size.
412    pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
413        Ok(self.advance(font_id, font_size, 'm')?.width)
414    }
415
416    /// Returns the width of an `ch`.
417    ///
418    /// Uses the width of the `0` character in the given font and size.
419    pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
420        Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)
421    }
422
423    /// Returns the advance width of an `ch`.
424    ///
425    /// Uses the advance width of the `0` character in the given font and size.
426    pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
427        Ok(self.advance(font_id, font_size, '0')?.width)
428    }
429
430    /// Get the number of font size units per 'em square',
431    /// Per MDN: "an abstract square whose height is the intended distance between
432    /// lines of type in the same type size"
433    pub fn units_per_em(&self, font_id: FontId) -> u32 {
434        self.read_metrics(font_id, |metrics| metrics.units_per_em)
435    }
436
437    /// Get the height of a capital letter in the given font and size.
438    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
439        self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
440    }
441
442    /// Get the height of the x character in the given font and size.
443    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
444        self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
445    }
446
447    /// Get the recommended distance from the baseline for the given font
448    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
449        self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
450    }
451
452    /// Get the recommended distance below the baseline for the given font,
453    /// in single spaced text.
454    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
455        self.read_metrics(font_id, |metrics| metrics.descent(font_size))
456    }
457
458    /// Get the recommended baseline offset for the given font and line height.
459    pub fn baseline_offset(
460        &self,
461        font_id: FontId,
462        font_size: Pixels,
463        line_height: Pixels,
464    ) -> Pixels {
465        let ascent = self.ascent(font_id, font_size);
466        let descent = self.descent(font_id, font_size);
467        let padding_top = (line_height - ascent - descent) / 2.;
468        padding_top + ascent
469    }
470
471    fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
472        let lock = self.font_metrics.upgradable_read();
473
474        if let Some(metrics) = lock.get(&font_id) {
475            read(metrics)
476        } else {
477            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
478            let metrics = lock
479                .entry(font_id)
480                .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
481            read(metrics)
482        }
483    }
484
485    /// Returns a handle to a line wrapper, for the given font and font size.
486    pub fn line_wrapper(self: Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
487        let font_id = self.resolve_font(&font);
488        let wrapper = {
489            let mut lock = self.wrapper_pool.lock();
490            lock.entry(FontIdWithSize { font_id, font_size })
491                .or_default()
492                .pop()
493                .unwrap_or_else(|| LineWrapper::new(font_id, font_size, self.clone()))
494        };
495        LineWrapperHandle::new(wrapper, move |wrapper| {
496            let mut lock = self.wrapper_pool.lock();
497            lock.get_mut(&FontIdWithSize { font_id, font_size })
498                .expect("wrapper pool entry exists")
499                .push(wrapper);
500        })
501    }
502
503    /// Get the rasterized size and location of a specific, rendered glyph.
504    pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
505        let raster_bounds = self.raster_bounds.upgradable_read();
506        if let Some(bounds) = raster_bounds.get(params) {
507            Ok(*bounds)
508        } else {
509            let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
510            let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
511            raster_bounds.insert(params.clone(), bounds);
512            Ok(bounds)
513        }
514    }
515
516    /// Rasterizes a glyph, returning its size and coverage bitmap.
517    pub fn rasterize_glyph(
518        &self,
519        params: &RenderGlyphParams,
520    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
521        let raster_bounds = self.raster_bounds(params)?;
522        self.platform_text_system
523            .rasterize_glyph(params, raster_bounds)
524    }
525
526    /// Returns the dilation level to use for a glyph painted in the given color.
527    pub fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
528        self.platform_text_system.glyph_dilation_for_color(color)
529    }
530
531    /// Returns the text rendering mode recommended by the platform for the given font and size.
532    /// The return value will never be [`TextRenderingMode::PlatformDefault`].
533    pub fn recommended_rendering_mode(
534        &self,
535        font_id: FontId,
536        font_size: Pixels,
537    ) -> TextRenderingMode {
538        self.platform_text_system
539            .recommended_rendering_mode(font_id, font_size)
540    }
541}
542
543#[derive(Hash, Eq, PartialEq)]
544struct FontIdWithSize {
545    font_id: FontId,
546    font_size: Pixels,
547}
548
549/// Affordances of the default text system that the shared [`TextSystem`] SPI does
550/// not carry, in the same spirit as `gpui_parley`'s `as_parley`.
551pub trait DefaultTextSystemExt {
552    /// This text system as a [`DefaultTextSystem`], when it is one.
553    fn as_default_text_system(&self) -> Option<&DefaultTextSystem>;
554}
555
556impl DefaultTextSystemExt for dyn TextSystem + '_ {
557    fn as_default_text_system(&self) -> Option<&DefaultTextSystem> {
558        self.as_any().downcast_ref::<DefaultTextSystem>()
559    }
560}
561
562impl DefaultTextSystem {
563    /// Reports missing glyphs as if the platform text system had observed them.
564    ///
565    /// A test affordance: the production path is
566    /// [`Self::enable_missing_glyph_reporting`] plus
567    /// [`TextSystem::take_missing_glyph_receiver`]. Test code reaches it by
568    /// downcasting, through [`DefaultTextSystemExt::as_default_text_system`].
569    #[cfg(any(test, feature = "test-support"))]
570    pub fn report_missing_glyphs_in_test(&self, missing_glyphs: Vec<MissingGlyph>) {
571        self.missing_glyph_reporter.report(missing_glyphs);
572    }
573}
574
575impl TextSystem for DefaultTextSystem {
576    fn take_missing_glyph_receiver(&self) -> Option<Box<dyn MissingGlyphReports>> {
577        self.missing_glyph_receiver
578            .try_lock()
579            .and_then(|mut receiver| receiver.take())
580            .map(|receiver| Box::new(receiver) as Box<dyn MissingGlyphReports>)
581    }
582
583    fn enable_missing_glyph_reporting(&self) {
584        self.platform_text_system
585            .set_missing_glyph_sink(Some(self.missing_glyph_reporter.clone()));
586    }
587
588    fn disable_missing_glyph_reporting(&self) {
589        self.platform_text_system.set_missing_glyph_sink(None);
590        self.missing_glyph_reporter.reset();
591    }
592
593    fn platform_text_system(&self) -> &Arc<dyn PlatformTextSystem> {
594        &self.platform_text_system
595    }
596
597    fn all_font_names(&self) -> Vec<String> {
598        self.all_font_names()
599    }
600
601    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
602        self.add_fonts(fonts)
603    }
604
605    fn get_font_for_id(&self, id: FontId) -> Option<Font> {
606        self.get_font_for_id(id)
607    }
608
609    fn resolve_font(&self, font: &Font) -> FontId {
610        self.resolve_font(font)
611    }
612
613    fn prewarm_fonts(&self, fonts: &[Font]) {
614        self.prewarm_fonts(fonts)
615    }
616
617    fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
618        self.bounding_box(font_id, font_size)
619    }
620
621    fn typographic_bounds(
622        &self,
623        font_id: FontId,
624        font_size: Pixels,
625        character: char,
626    ) -> Result<Bounds<Pixels>> {
627        self.typographic_bounds(font_id, font_size, character)
628    }
629
630    fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
631        self.advance(font_id, font_size, ch)
632    }
633
634    fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
635        self.layout_width(font_id, font_size, ch)
636    }
637
638    fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
639        self.em_width(font_id, font_size)
640    }
641
642    fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
643        self.em_advance(font_id, font_size)
644    }
645
646    fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
647        self.ch_width(font_id, font_size)
648    }
649
650    fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
651        self.ch_advance(font_id, font_size)
652    }
653
654    fn units_per_em(&self, font_id: FontId) -> u32 {
655        self.units_per_em(font_id)
656    }
657
658    fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
659        self.cap_height(font_id, font_size)
660    }
661
662    fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
663        self.x_height(font_id, font_size)
664    }
665
666    fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
667        self.ascent(font_id, font_size)
668    }
669
670    fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
671        self.descent(font_id, font_size)
672    }
673
674    fn baseline_offset(&self, font_id: FontId, font_size: Pixels, line_height: Pixels) -> Pixels {
675        self.baseline_offset(font_id, font_size, line_height)
676    }
677
678    fn take_font_runs(&self) -> Vec<FontRun> {
679        self.take_font_runs()
680    }
681
682    fn recycle_font_runs(&self, font_runs: Vec<FontRun>) {
683        self.recycle_font_runs(font_runs)
684    }
685
686    fn line_wrapper(self: Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
687        DefaultTextSystem::line_wrapper(self, font, font_size)
688    }
689
690    fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
691        self.raster_bounds(params)
692    }
693
694    fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)> {
695        self.rasterize_glyph(params)
696    }
697
698    fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
699        self.glyph_dilation_for_color(color)
700    }
701
702    fn recommended_rendering_mode(&self, font_id: FontId, font_size: Pixels) -> TextRenderingMode {
703        self.recommended_rendering_mode(font_id, font_size)
704    }
705
706    fn layout_index(&self) -> LineLayoutIndex {
707        self.line_layout_cache.layout_index()
708    }
709
710    fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
711        self.line_layout_cache.reuse_layouts(range)
712    }
713
714    fn truncate_layouts(&self, index: LineLayoutIndex) {
715        self.line_layout_cache.truncate_layouts(index)
716    }
717
718    fn finish_frame(&self) {
719        self.line_layout_cache.finish_frame()
720    }
721
722    fn layout_wrapped_line(
723        &self,
724        text: &str,
725        font_size: Pixels,
726        runs: &[FontRun],
727        wrap_width: Option<Pixels>,
728        max_lines: Option<usize>,
729    ) -> Arc<WrappedLineLayout> {
730        self.line_layout_cache
731            .layout_wrapped_line(text, font_size, runs, wrap_width, max_lines)
732    }
733
734    fn layout_line(
735        &self,
736        text: &str,
737        font_size: Pixels,
738        runs: &[FontRun],
739        force_width: Option<Pixels>,
740    ) -> Arc<LineLayout> {
741        self.line_layout_cache
742            .layout_line(text, font_size, runs, force_width)
743    }
744
745    fn try_layout_line_by_hash(
746        &self,
747        text_hash: u64,
748        text_len: usize,
749        font_size: Pixels,
750        runs: &[FontRun],
751        force_width: Option<Pixels>,
752    ) -> Option<Arc<LineLayout>> {
753        self.line_layout_cache.try_layout_line_by_hash(
754            text_hash,
755            text_len,
756            font_size,
757            runs,
758            force_width,
759        )
760    }
761
762    fn layout_line_by_hash(
763        &self,
764        text_hash: u64,
765        text_len: usize,
766        font_size: Pixels,
767        runs: &[FontRun],
768        force_width: Option<Pixels>,
769        materialize_text: Box<dyn FnOnce() -> SharedString>,
770    ) -> Arc<LineLayout> {
771        self.line_layout_cache.layout_line_by_hash(
772            text_hash,
773            text_len,
774            font_size,
775            runs,
776            force_width,
777            materialize_text,
778        )
779    }
780}
781
782#[cfg(test)]
783mod missing_glyph_tests {
784    use super::*;
785    use futures::FutureExt as _;
786    use gpui_engine::FallbackFontClass;
787
788    #[test]
789    fn bounds_retained_missing_glyphs() {
790        let (reporter, mut receiver) = missing_glyph_channel();
791        reporter.report(
792            (0..MAX_REPORTED_MISSING_GLYPHS)
793                .map(|index| {
794                    MissingGlyph::new(index.to_string().into(), FallbackFontClass::Proportional)
795                })
796                .collect(),
797        );
798        assert!(receiver.recv().now_or_never().unwrap().is_ok());
799
800        let newest = MissingGlyph::new("newest".into(), FallbackFontClass::Monospace);
801        reporter.report(vec![newest.clone()]);
802        assert!(receiver.recv().now_or_never().unwrap().is_ok());
803
804        let state = &receiver.state;
805        assert_eq!(state.reported.len(), MAX_REPORTED_MISSING_GLYPHS);
806        assert_eq!(state.reported_order.len(), MAX_REPORTED_MISSING_GLYPHS);
807        assert!(state.reported.contains(&newest));
808    }
809
810    #[test]
811    fn dropping_receiver_closes_and_clears_reports() {
812        let (reporter, receiver) = missing_glyph_channel();
813        reporter.report(vec![missing_glyph("missing")]);
814
815        drop(receiver);
816
817        assert!(reporter.sender.is_closed());
818        assert!(reporter.sender.is_empty());
819    }
820
821    fn missing_glyph_channel() -> (MissingGlyphReporter, MissingGlyphReceiver) {
822        let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
823        let generation = Arc::<AtomicUsize>::default();
824        let reporter = MissingGlyphReporter {
825            generation: generation.clone(),
826            sender,
827        };
828        let receiver = MissingGlyphReceiver {
829            state: MissingGlyphState::default(),
830            generation,
831            receiver,
832        };
833        (reporter, receiver)
834    }
835
836    fn missing_glyph(grapheme: &'static str) -> MissingGlyph {
837        MissingGlyph::new(grapheme.into(), FallbackFontClass::Proportional)
838    }
839}