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
540impl TextSystem for DefaultTextSystem {
541    fn take_missing_glyph_receiver(&self) -> Option<Box<dyn MissingGlyphReports>> {
542        self.missing_glyph_receiver
543            .try_lock()
544            .and_then(|mut receiver| receiver.take())
545            .map(|receiver| Box::new(receiver) as Box<dyn MissingGlyphReports>)
546    }
547
548    fn enable_missing_glyph_reporting(&self) {
549        self.platform_text_system
550            .set_missing_glyph_sink(Some(self.missing_glyph_reporter.clone()));
551    }
552
553    fn disable_missing_glyph_reporting(&self) {
554        self.platform_text_system.set_missing_glyph_sink(None);
555        self.missing_glyph_reporter.reset();
556    }
557
558    fn report_missing_glyphs_in_test(&self, missing_glyphs: Vec<MissingGlyph>) {
559        self.missing_glyph_reporter.report(missing_glyphs);
560    }
561
562    fn platform_text_system(&self) -> &Arc<dyn PlatformTextSystem> {
563        &self.platform_text_system
564    }
565
566    fn all_font_names(&self) -> Vec<String> {
567        self.all_font_names()
568    }
569
570    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
571        self.add_fonts(fonts)
572    }
573
574    fn get_font_for_id(&self, id: FontId) -> Option<Font> {
575        self.get_font_for_id(id)
576    }
577
578    fn resolve_font(&self, font: &Font) -> FontId {
579        self.resolve_font(font)
580    }
581
582    fn prewarm_fonts(&self, fonts: &[Font]) {
583        self.prewarm_fonts(fonts)
584    }
585
586    fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
587        self.bounding_box(font_id, font_size)
588    }
589
590    fn typographic_bounds(
591        &self,
592        font_id: FontId,
593        font_size: Pixels,
594        character: char,
595    ) -> Result<Bounds<Pixels>> {
596        self.typographic_bounds(font_id, font_size, character)
597    }
598
599    fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
600        self.advance(font_id, font_size, ch)
601    }
602
603    fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {
604        self.layout_width(font_id, font_size, ch)
605    }
606
607    fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
608        self.em_width(font_id, font_size)
609    }
610
611    fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
612        self.em_advance(font_id, font_size)
613    }
614
615    fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
616        self.ch_width(font_id, font_size)
617    }
618
619    fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
620        self.ch_advance(font_id, font_size)
621    }
622
623    fn units_per_em(&self, font_id: FontId) -> u32 {
624        self.units_per_em(font_id)
625    }
626
627    fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
628        self.cap_height(font_id, font_size)
629    }
630
631    fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
632        self.x_height(font_id, font_size)
633    }
634
635    fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
636        self.ascent(font_id, font_size)
637    }
638
639    fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
640        self.descent(font_id, font_size)
641    }
642
643    fn baseline_offset(&self, font_id: FontId, font_size: Pixels, line_height: Pixels) -> Pixels {
644        self.baseline_offset(font_id, font_size, line_height)
645    }
646
647    fn take_font_runs(&self) -> Vec<FontRun> {
648        self.take_font_runs()
649    }
650
651    fn recycle_font_runs(&self, font_runs: Vec<FontRun>) {
652        self.recycle_font_runs(font_runs)
653    }
654
655    fn line_wrapper(self: Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
656        DefaultTextSystem::line_wrapper(self, font, font_size)
657    }
658
659    fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
660        self.raster_bounds(params)
661    }
662
663    fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)> {
664        self.rasterize_glyph(params)
665    }
666
667    fn glyph_dilation_for_color(&self, color: Hsla) -> u8 {
668        self.glyph_dilation_for_color(color)
669    }
670
671    fn recommended_rendering_mode(&self, font_id: FontId, font_size: Pixels) -> TextRenderingMode {
672        self.recommended_rendering_mode(font_id, font_size)
673    }
674
675    fn layout_index(&self) -> LineLayoutIndex {
676        self.line_layout_cache.layout_index()
677    }
678
679    fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
680        self.line_layout_cache.reuse_layouts(range)
681    }
682
683    fn truncate_layouts(&self, index: LineLayoutIndex) {
684        self.line_layout_cache.truncate_layouts(index)
685    }
686
687    fn finish_frame(&self) {
688        self.line_layout_cache.finish_frame()
689    }
690
691    fn layout_wrapped_line(
692        &self,
693        text: &str,
694        font_size: Pixels,
695        runs: &[FontRun],
696        wrap_width: Option<Pixels>,
697        max_lines: Option<usize>,
698    ) -> Arc<WrappedLineLayout> {
699        self.line_layout_cache
700            .layout_wrapped_line(text, font_size, runs, wrap_width, max_lines)
701    }
702
703    fn layout_line(
704        &self,
705        text: &str,
706        font_size: Pixels,
707        runs: &[FontRun],
708        force_width: Option<Pixels>,
709    ) -> Arc<LineLayout> {
710        self.line_layout_cache
711            .layout_line(text, font_size, runs, force_width)
712    }
713
714    fn try_layout_line_by_hash(
715        &self,
716        text_hash: u64,
717        text_len: usize,
718        font_size: Pixels,
719        runs: &[FontRun],
720        force_width: Option<Pixels>,
721    ) -> Option<Arc<LineLayout>> {
722        self.line_layout_cache.try_layout_line_by_hash(
723            text_hash,
724            text_len,
725            font_size,
726            runs,
727            force_width,
728        )
729    }
730
731    fn layout_line_by_hash(
732        &self,
733        text_hash: u64,
734        text_len: usize,
735        font_size: Pixels,
736        runs: &[FontRun],
737        force_width: Option<Pixels>,
738        materialize_text: Box<dyn FnOnce() -> SharedString>,
739    ) -> Arc<LineLayout> {
740        self.line_layout_cache.layout_line_by_hash(
741            text_hash,
742            text_len,
743            font_size,
744            runs,
745            force_width,
746            materialize_text,
747        )
748    }
749}
750
751#[cfg(test)]
752mod missing_glyph_tests {
753    use super::*;
754    use futures::FutureExt as _;
755    use gpui_engine::FallbackFontClass;
756
757    #[test]
758    fn bounds_retained_missing_glyphs() {
759        let (reporter, mut receiver) = missing_glyph_channel();
760        reporter.report(
761            (0..MAX_REPORTED_MISSING_GLYPHS)
762                .map(|index| {
763                    MissingGlyph::new(index.to_string().into(), FallbackFontClass::Proportional)
764                })
765                .collect(),
766        );
767        assert!(receiver.recv().now_or_never().unwrap().is_ok());
768
769        let newest = MissingGlyph::new("newest".into(), FallbackFontClass::Monospace);
770        reporter.report(vec![newest.clone()]);
771        assert!(receiver.recv().now_or_never().unwrap().is_ok());
772
773        let state = &receiver.state;
774        assert_eq!(state.reported.len(), MAX_REPORTED_MISSING_GLYPHS);
775        assert_eq!(state.reported_order.len(), MAX_REPORTED_MISSING_GLYPHS);
776        assert!(state.reported.contains(&newest));
777    }
778
779    #[test]
780    fn dropping_receiver_closes_and_clears_reports() {
781        let (reporter, receiver) = missing_glyph_channel();
782        reporter.report(vec![missing_glyph("missing")]);
783
784        drop(receiver);
785
786        assert!(reporter.sender.is_closed());
787        assert!(reporter.sender.is_empty());
788    }
789
790    fn missing_glyph_channel() -> (MissingGlyphReporter, MissingGlyphReceiver) {
791        let (sender, receiver) = async_channel::bounded(MAX_REPORTED_MISSING_GLYPHS);
792        let generation = Arc::<AtomicUsize>::default();
793        let reporter = MissingGlyphReporter {
794            generation: generation.clone(),
795            sender,
796        };
797        let receiver = MissingGlyphReceiver {
798            state: MissingGlyphState::default(),
799            generation,
800            receiver,
801        };
802        (reporter, receiver)
803    }
804
805    fn missing_glyph(grapheme: &'static str) -> MissingGlyph {
806        MissingGlyph::new(grapheme.into(), FallbackFontClass::Proportional)
807    }
808}