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