984 lines · 32.6 KB · Rust
RawDownload
mod font_fallbacks;mod font_features;mod line;mod line_layout;mod line_wrapper; pub use font_fallbacks::*;pub use font_features::*;pub use line::*;pub use line_layout::*;pub use line_wrapper::*;use schemars::JsonSchema;use serde::{Deserialize, Serialize}; use crate::{    Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,    StrikethroughStyle, TextRenderingMode, UnderlineStyle, px,};use anyhow::{Context as _, anyhow};use collections::FxHashMap;use core::fmt;use derive_more::{Add, Deref, FromStr, Sub};use itertools::Itertools;use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};use smallvec::{SmallVec, smallvec};use std::{    borrow::Cow,    cmp,    fmt::{Debug, Display, Formatter},    hash::{Hash, Hasher},    ops::{Deref, DerefMut, Range},    sync::Arc,}; /// An opaque identifier for a specific font.#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]#[repr(C)]pub struct FontId(pub usize); /// An opaque identifier for a specific font family.#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]pub struct FontFamilyId(pub usize); pub(crate) const SUBPIXEL_VARIANTS_X: u8 = 4; pub(crate) const SUBPIXEL_VARIANTS_Y: u8 =    if cfg!(target_os = "windows") || cfg!(target_os = "linux") {        1    } else {        SUBPIXEL_VARIANTS_X    }; /// The GPUI text rendering sub system.pub struct TextSystem {    platform_text_system: Arc<dyn PlatformTextSystem>,    font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,    font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,    raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,    wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,    font_runs_pool: Mutex<Vec<Vec<FontRun>>>,    fallback_font_stack: SmallVec<[Font; 2]>,} impl TextSystem {    pub(crate) fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {        TextSystem {            platform_text_system,            font_metrics: RwLock::default(),            raster_bounds: RwLock::default(),            font_ids_by_font: RwLock::default(),            wrapper_pool: Mutex::default(),            font_runs_pool: Mutex::default(),            fallback_font_stack: smallvec![                // TODO: Remove this when Linux have implemented setting fallbacks.                font(".ZedMono"),                font(".ZedSans"),                font("Helvetica"),                font("Segoe UI"),     // Windows                font("Ubuntu"),       // Gnome (Ubuntu)                font("Adwaita Sans"), // Gnome 47                font("Cantarell"),    // Gnome                font("Noto Sans"),    // KDE                font("DejaVu Sans"),                font("Arial"), // macOS, Windows            ],        }    }     /// Get a list of all available font names from the operating system.    pub fn all_font_names(&self) -> Vec<String> {        let mut names = self.platform_text_system.all_font_names();        names.extend(            self.fallback_font_stack                .iter()                .map(|font| font.family.to_string()),        );        names.push(".SystemUIFont".to_string());        names.sort();        names.dedup();        names    }     /// Add a font's data to the text system.    pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {        self.platform_text_system.add_fonts(fonts)    }     /// Get the FontId for the configure font family and style.    fn font_id(&self, font: &Font) -> Result<FontId> {        fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {            match font_id {                Ok(font_id) => Ok(*font_id),                Err(err) => Err(anyhow!("{err}")),            }        }         let font_id = self            .font_ids_by_font            .read()            .get(font)            .map(clone_font_id_result);        if let Some(font_id) = font_id {            font_id        } else {            let font_id = self.platform_text_system.font_id(font);            self.font_ids_by_font                .write()                .insert(font.clone(), clone_font_id_result(&font_id));            font_id        }    }     /// Get the Font for the Font Id.    pub fn get_font_for_id(&self, id: FontId) -> Option<Font> {        let lock = self.font_ids_by_font.read();        lock.iter()            .filter_map(|(font, result)| match result {                Ok(font_id) if *font_id == id => Some(font.clone()),                _ => None,            })            .next()    }     /// Resolves the specified font, falling back to the default font stack if    /// the font fails to load.    ///    /// # Panics    ///    /// Panics if the font and none of the fallbacks can be resolved.    pub fn resolve_font(&self, font: &Font) -> FontId {        if let Ok(font_id) = self.font_id(font) {            return font_id;        }        for fallback in &self.fallback_font_stack {            if let Ok(font_id) = self.font_id(fallback) {                return font_id;            }        }         panic!(            "failed to resolve font '{}' or any of the fallbacks: {}",            font.family,            self.fallback_font_stack                .iter()                .map(|fallback| &fallback.family)                .join(", ")        );    }     /// Get the bounding box for the given font and font size.    /// A font's bounding box is the smallest rectangle that could enclose all glyphs    /// in the font. superimposed over one another.    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {        self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))    }     /// Get the typographic bounds for the given character, in the given font and size.    pub fn typographic_bounds(        &self,        font_id: FontId,        font_size: Pixels,        character: char,    ) -> Result<Bounds<Pixels>> {        let glyph_id = self            .platform_text_system            .glyph_for_char(font_id, character)            .with_context(|| format!("glyph not found for character '{character}'"))?;        let bounds = self            .platform_text_system            .typographic_bounds(font_id, glyph_id)?;        Ok(self.read_metrics(font_id, |metrics| {            (bounds / metrics.units_per_em as f32 * font_size.0).map(px)        }))    }     /// Get the advance width for the given character, in the given font and size.    pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {        let glyph_id = self            .platform_text_system            .glyph_for_char(font_id, ch)            .with_context(|| format!("glyph not found for character '{ch}'"))?;        let result = self.platform_text_system.advance(font_id, glyph_id)?            / self.units_per_em(font_id) as f32;         Ok(result * font_size)    }     // Consider removing this?    /// Returns the shaped layout width of for the given character, in the given font and size.    pub fn layout_width(&self, font_id: FontId, font_size: Pixels, ch: char) -> Pixels {        let mut buffer = [0; 4];        let buffer = ch.encode_utf8(&mut buffer);        self.platform_text_system            .layout_line(                buffer,                font_size,                &[FontRun {                    len: buffer.len(),                    font_id,                }],            )            .width    }     /// Returns the width of an `em`.    ///    /// Uses the width of the `m` character in the given font and size.    pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {        Ok(self.typographic_bounds(font_id, font_size, 'm')?.size.width)    }     /// Returns the advance width of an `em`.    ///    /// Uses the advance width of the `m` character in the given font and size.    pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {        Ok(self.advance(font_id, font_size, 'm')?.width)    }     // Consider removing this?    /// Returns the shaped layout width of an `em`.    pub fn em_layout_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {        self.layout_width(font_id, font_size, 'm')    }     /// Returns the width of an `ch`.    ///    /// Uses the width of the `0` character in the given font and size.    pub fn ch_width(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {        Ok(self.typographic_bounds(font_id, font_size, '0')?.size.width)    }     /// Returns the advance width of an `ch`.    ///    /// Uses the advance width of the `0` character in the given font and size.    pub fn ch_advance(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {        Ok(self.advance(font_id, font_size, '0')?.width)    }     /// Get the number of font size units per 'em square',    /// Per MDN: "an abstract square whose height is the intended distance between    /// lines of type in the same type size"    pub fn units_per_em(&self, font_id: FontId) -> u32 {        self.read_metrics(font_id, |metrics| metrics.units_per_em)    }     /// Get the height of a capital letter in the given font and size.    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {        self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))    }     /// Get the height of the x character in the given font and size.    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {        self.read_metrics(font_id, |metrics| metrics.x_height(font_size))    }     /// Get the recommended distance from the baseline for the given font    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {        self.read_metrics(font_id, |metrics| metrics.ascent(font_size))    }     /// Get the recommended distance below the baseline for the given font,    /// in single spaced text.    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {        self.read_metrics(font_id, |metrics| metrics.descent(font_size))    }     /// Get the recommended baseline offset for the given font and line height.    pub fn baseline_offset(        &self,        font_id: FontId,        font_size: Pixels,        line_height: Pixels,    ) -> Pixels {        let ascent = self.ascent(font_id, font_size);        let descent = self.descent(font_id, font_size);        let padding_top = (line_height - ascent - descent) / 2.;        padding_top + ascent    }     fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {        let lock = self.font_metrics.upgradable_read();         if let Some(metrics) = lock.get(&font_id) {            read(metrics)        } else {            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);            let metrics = lock                .entry(font_id)                .or_insert_with(|| self.platform_text_system.font_metrics(font_id));            read(metrics)        }    }     /// Returns a handle to a line wrapper, for the given font and font size.    pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {        let lock = &mut self.wrapper_pool.lock();        let font_id = self.resolve_font(&font);        let wrappers = lock            .entry(FontIdWithSize { font_id, font_size })            .or_default();        let wrapper = wrappers            .pop()            .unwrap_or_else(|| LineWrapper::new(font_id, font_size, self.clone()));         LineWrapperHandle {            wrapper: Some(wrapper),            text_system: self.clone(),        }    }     /// Get the rasterized size and location of a specific, rendered glyph.    pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {        let raster_bounds = self.raster_bounds.upgradable_read();        if let Some(bounds) = raster_bounds.get(params) {            Ok(*bounds)        } else {            let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);            let bounds = self.platform_text_system.glyph_raster_bounds(params)?;            raster_bounds.insert(params.clone(), bounds);            Ok(bounds)        }    }     pub(crate) fn rasterize_glyph(        &self,        params: &RenderGlyphParams,    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {        let raster_bounds = self.raster_bounds(params)?;        self.platform_text_system            .rasterize_glyph(params, raster_bounds)    }     /// Returns the text rendering mode recommended by the platform for the given font and size.    /// The return value will never be [`TextRenderingMode::PlatformDefault`].    pub(crate) fn recommended_rendering_mode(        &self,        font_id: FontId,        font_size: Pixels,    ) -> TextRenderingMode {        self.platform_text_system            .recommended_rendering_mode(font_id, font_size)    }} /// The GPUI text layout subsystem.#[derive(Deref)]pub struct WindowTextSystem {    line_layout_cache: LineLayoutCache,    #[deref]    text_system: Arc<TextSystem>,} impl WindowTextSystem {    pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {        Self {            line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),            text_system,        }    }     pub(crate) fn layout_index(&self) -> LineLayoutIndex {        self.line_layout_cache.layout_index()    }     pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {        self.line_layout_cache.reuse_layouts(index)    }     pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {        self.line_layout_cache.truncate_layouts(index)    }     /// Shape the given line, at the given font_size, for painting to the screen.    /// Subsets of the line can be styled independently with the `runs` parameter.    ///    /// Note that this method can only shape a single line of text. It will panic    /// if the text contains newlines. If you need to shape multiple lines of text,    /// use [`Self::shape_text`] instead.    pub fn shape_line(        &self,        text: SharedString,        font_size: Pixels,        runs: &[TextRun],        force_width: Option<Pixels>,    ) -> ShapedLine {        debug_assert!(            text.find('\n').is_none(),            "text argument should not contain newlines"        );         let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();        for run in runs {            if let Some(last_run) = decoration_runs.last_mut()                && last_run.color == run.color                && last_run.underline == run.underline                && last_run.strikethrough == run.strikethrough                && last_run.background_color == run.background_color            {                last_run.len += run.len as u32;                continue;            }            decoration_runs.push(DecorationRun {                len: run.len as u32,                color: run.color,                background_color: run.background_color,                underline: run.underline,                strikethrough: run.strikethrough,            });        }         let layout = self.layout_line(&text, font_size, runs, force_width);         ShapedLine {            layout,            text,            decoration_runs,        }    }     /// Shape a multi line string of text, at the given font_size, for painting to the screen.    /// Subsets of the text can be styled independently with the `runs` parameter.    /// If `wrap_width` is provided, the line breaks will be adjusted to fit within the given width.    pub fn shape_text(        &self,        text: SharedString,        font_size: Pixels,        runs: &[TextRun],        wrap_width: Option<Pixels>,        line_clamp: Option<usize>,    ) -> Result<SmallVec<[WrappedLine; 1]>> {        let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();        let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();         let mut lines = SmallVec::new();        let mut max_wrap_lines = line_clamp;        let mut wrapped_lines = 0;         let mut process_line = |line_text: SharedString, line_start, line_end| {            font_runs.clear();             let mut decoration_runs = <Vec<DecorationRun>>::with_capacity(32);            let mut run_start = line_start;            while run_start < line_end {                let Some(run) = runs.peek_mut() else {                    log::warn!("`TextRun`s do not cover the entire to be shaped text");                    break;                };                 let run_len_within_line = cmp::min(line_end - run_start, run.len);                 let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()                    && last_run.color == run.color                    && last_run.underline == run.underline                    && last_run.strikethrough == run.strikethrough                    && last_run.background_color == run.background_color                {                    last_run.len += run_len_within_line as u32;                    false                } else {                    decoration_runs.push(DecorationRun {                        len: run_len_within_line as u32,                        color: run.color,                        background_color: run.background_color,                        underline: run.underline,                        strikethrough: run.strikethrough,                    });                    true                };                 let font_id = self.resolve_font(&run.font);                if let Some(font_run) = font_runs.last_mut()                    && font_id == font_run.font_id                    && !decoration_changed                {                    font_run.len += run_len_within_line;                } else {                    font_runs.push(FontRun {                        len: run_len_within_line,                        font_id,                    });

Lines 1–500 of 984.