use anyhow::{Context as _, Ok, Result};use collections::HashMap;use cosmic_text::{ Attrs, AttrsList, Ellipsize, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures, FontSystem, ShapeBuffer, ShapeLine,};use gpui::{ Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, GlyphId, LineLayout, Pixels, PlatformTextSystem, RenderGlyphParams, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, size,}; use itertools::Itertools;use parking_lot::RwLock;use smallvec::SmallVec;use std::{borrow::Cow, ops::Range, sync::Arc};use swash::{ scale::{Render, ScaleContext, Source, StrikeWith}, zeno::{Format, Vector},};use unicode_segmentation::UnicodeSegmentation; pub struct CosmicTextSystem(RwLock<CosmicTextSystemState>); #[derive(Debug, Clone, PartialEq, Eq, Hash)]struct FontKey { family: SharedString, features: FontFeatures, fallbacks: Option<FontFallbacks>,} impl FontKey { fn new(family: SharedString, features: FontFeatures, fallbacks: Option<FontFallbacks>) -> Self { Self { family, features, fallbacks, } }} struct CosmicTextSystemState { font_system: FontSystem, scratch: ShapeBuffer, swash_scale_context: ScaleContext, /// Contains all already loaded fonts, including all faces. Indexed by `FontId`. loaded_fonts: Vec<LoadedFont>, /// Caches the `FontId`s associated with a specific family to avoid iterating the font database /// for every font face in a family. font_ids_by_family_cache: HashMap<FontKey, SmallVec<[FontId; 4]>>, system_font_fallback: String,} struct LoadedFont { font: Arc<CosmicTextFont>, features: CosmicFontFeatures, is_known_emoji_font: bool, /// resolved at load time so `layout_line` shares one chain across faces. /// `Arc` keeps clone cheap on the per-run hot path. user_fallback_chain: Arc<[(FontId, SharedString)]>,} impl CosmicTextSystem { pub fn new(system_font_fallback: &str) -> Self { let font_system = FontSystem::new(); Self(RwLock::new(CosmicTextSystemState { font_system, scratch: ShapeBuffer::default(), swash_scale_context: ScaleContext::new(), loaded_fonts: Vec::new(), font_ids_by_family_cache: HashMap::default(), system_font_fallback: system_font_fallback.to_string(), })) } pub fn new_without_system_fonts(system_font_fallback: &str) -> Self { let font_system = FontSystem::new_with_locale_and_db( "en-US".to_string(), cosmic_text::fontdb::Database::new(), ); Self(RwLock::new(CosmicTextSystemState { font_system, scratch: ShapeBuffer::default(), swash_scale_context: ScaleContext::new(), loaded_fonts: Vec::new(), font_ids_by_family_cache: HashMap::default(), system_font_fallback: system_font_fallback.to_string(), })) }} impl PlatformTextSystem for CosmicTextSystem { fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { self.0.write().add_fonts(fonts) } fn all_font_names(&self) -> Vec<String> { let mut result = self .0 .read() .font_system .db() .faces() .filter_map(|face| face.families.first().map(|family| family.0.clone())) .collect_vec(); result.sort_unstable(); result.dedup(); result } fn font_id(&self, font: &Font) -> Result<FontId> { let mut state = self.0.write(); let key = FontKey::new( font.family.clone(), font.features.clone(), font.fallbacks.clone(), ); let candidates = if let Some(font_ids) = state.font_ids_by_family_cache.get(&key) { font_ids.as_slice() } else { let font_ids = state.load_family(&font.family, &font.features, font.fallbacks.as_ref())?; state.font_ids_by_family_cache.insert(key.clone(), font_ids); state.font_ids_by_family_cache[&key].as_ref() }; let ix = find_best_match(font, candidates, &state)?; Ok(candidates[ix]) } fn font_metrics(&self, font_id: FontId) -> FontMetrics { let metrics = self .0 .read() .loaded_font(font_id) .font .as_swash() .metrics(&[]); FontMetrics { units_per_em: metrics.units_per_em as u32, ascent: metrics.ascent, descent: -metrics.descent, line_gap: metrics.leading, underline_position: metrics.underline_offset, underline_thickness: metrics.stroke_size, cap_height: metrics.cap_height, x_height: metrics.x_height, bounding_box: Bounds { origin: point(0.0, 0.0), size: size(metrics.max_width, metrics.ascent + metrics.descent), }, } } fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> { let lock = self.0.read(); let glyph_metrics = lock.loaded_font(font_id).font.as_swash().glyph_metrics(&[]); let glyph_id = glyph_id.0 as u16; Ok(Bounds { origin: point(0.0, 0.0), size: size( glyph_metrics.advance_width(glyph_id), glyph_metrics.advance_height(glyph_id), ), }) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { self.0.read().advance(font_id, glyph_id) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { self.0.read().glyph_for_char(font_id, ch) } fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { self.0.write().raster_bounds(params) } fn rasterize_glyph( &self, params: &RenderGlyphParams, raster_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { self.0.write().rasterize_glyph(params, raster_bounds) } fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout { self.0.write().layout_line(text, font_size, runs) } fn recommended_rendering_mode( &self, _font_id: FontId, _font_size: Pixels, ) -> TextRenderingMode { TextRenderingMode::Subpixel }} impl CosmicTextSystemState { fn loaded_font(&self, font_id: FontId) -> &LoadedFont { &self.loaded_fonts[font_id.0] } #[profiling::function] fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { let db = self.font_system.db_mut(); for bytes in fonts { match bytes { Cow::Borrowed(embedded_font) => { db.load_font_data(embedded_font.to_vec()); } Cow::Owned(bytes) => { db.load_font_data(bytes); } } } Ok(()) } #[profiling::function] fn load_family( &mut self, name: &str, features: &FontFeatures, fallbacks: Option<&FontFallbacks>, ) -> Result<SmallVec<[FontId; 4]>> { // recurse with `fallbacks = None` so a fallback family cannot pull in // another chain. missing fallback families are dropped so a typo in // settings still lets the primary family load. let user_fallback_chain: Arc<[(FontId, SharedString)]> = match fallbacks { Some(fallbacks) if !fallbacks.fallback_list().is_empty() => { let mut chain: Vec<(FontId, SharedString)> = Vec::new(); for fallback_name in fallbacks.fallback_list() { let fb_key = FontKey::new( SharedString::from(fallback_name.clone()), features.clone(), None, ); let fb_ids = if let Some(cached) = self.font_ids_by_family_cache.get(&fb_key) { cached.clone() } else { let loaded = self.load_family(fallback_name, features, None)?; self.font_ids_by_family_cache .insert(fb_key.clone(), loaded.clone()); loaded }; let Some(&fb_id) = fb_ids.first() else { continue; }; let db_id = self.loaded_fonts[fb_id.0].font.id(); if let Some(face) = self.font_system.db().face(db_id) && let Some(family) = face.families.first() { chain.push((fb_id, SharedString::from(family.0.clone()))); } } Arc::from(chain) } _ => Arc::from(Vec::new()), }; let name = gpui::font_name_with_fallbacks(name, &self.system_font_fallback); let families = self .font_system .db() .faces() .filter(|face| face.families.iter().any(|family| *name == family.0)) .map(|face| (face.id, face.post_script_name.clone())) .collect::<SmallVec<[_; 4]>>(); let cosmic_features = cosmic_font_features(features)?; let mut loaded_font_ids = SmallVec::new(); for (font_id, postscript_name) in families { let font = self .font_system .get_font(font_id, cosmic_text::Weight::NORMAL) .context("Could not load font")?; // HACK: To let the storybook run and render Windows caption icons. We should actually do better font fallback. let allowed_bad_font_names = [ "SegoeFluentIcons", // NOTE: Segoe fluent icons postscript name is inconsistent "Segoe Fluent Icons", ]; if font.as_swash().charmap().map('m') == 0 && !allowed_bad_font_names.contains(&postscript_name.as_str()) { self.font_system.db_mut().remove_face(font.id()); continue; }; let font_id = FontId(self.loaded_fonts.len()); loaded_font_ids.push(font_id); self.loaded_fonts.push(LoadedFont { font, features: cosmic_features.clone(), is_known_emoji_font: check_is_known_emoji_font(&postscript_name), user_fallback_chain: Arc::clone(&user_fallback_chain), }); } Ok(loaded_font_ids) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { let glyph_metrics = self.loaded_font(font_id).font.as_swash().glyph_metrics(&[]); Ok(Size { width: glyph_metrics.advance_width(glyph_id.0 as u16), height: glyph_metrics.advance_height(glyph_id.0 as u16), }) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { let glyph_id = self.loaded_font(font_id).font.as_swash().charmap().map(ch); if glyph_id == 0 { None } else { Some(GlyphId(glyph_id.into())) } } fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { let image = self.render_glyph_image(params)?; Ok(Bounds { origin: point(image.placement.left.into(), (-image.placement.top).into()), size: size(image.placement.width.into(), image.placement.height.into()), }) } #[profiling::function] fn rasterize_glyph( &mut self, params: &RenderGlyphParams, glyph_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { anyhow::bail!("glyph bounds are empty"); } let mut image = self.render_glyph_image(params)?; let bitmap_size = glyph_bounds.size; match image.content { swash::scale::image::Content::Color | swash::scale::image::Content::SubpixelMask => { // Convert from RGBA to BGRA. for pixel in image.data.chunks_exact_mut(4) { pixel.swap(0, 2); } Ok((bitmap_size, image.data)) } swash::scale::image::Content::Mask => { if params.subpixel_rendering { // We must always return RGBA data when subpixel rendering is requested. let expanded = image.data.iter().flat_map(|&a| [a, a, a, a]).collect(); Ok((bitmap_size, expanded)) } else { Ok((bitmap_size, image.data)) } } } } fn render_glyph_image( &mut self, params: &RenderGlyphParams, ) -> Result<swash::scale::image::Image> { let loaded_font = &self.loaded_fonts[params.font_id.0]; let font_ref = loaded_font.font.as_swash(); let pixel_size = f32::from(params.font_size); let subpixel_offset = Vector::new( params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor, params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor, ); let mut scaler = self .swash_scale_context .builder(font_ref) .size(pixel_size * params.scale_factor) .hint(true) .build(); let sources: &[Source] = if params.is_emoji { &[ Source::ColorOutline(0), Source::ColorBitmap(StrikeWith::BestFit), Source::Outline, ] } else { &[Source::Bitmap(StrikeWith::ExactSize), Source::Outline] }; let mut renderer = Render::new(sources); if params.subpixel_rendering { // There seems to be a bug in Swash where the B and R values are swapped. renderer .format(Format::subpixel_bgra()) .offset(subpixel_offset); } else { renderer.format(Format::Alpha).offset(subpixel_offset); } let glyph_id: u16 = params.glyph_id.0.try_into()?; renderer .render(&mut scaler, glyph_id) .with_context(|| format!("unable to render glyph via swash for {params:?}")) } /// This is used when cosmic_text has chosen a fallback font instead of using the requested /// font, typically to handle some unicode characters. When this happens, `loaded_fonts` may not /// yet have an entry for this fallback font, and so one is added. /// /// Note that callers shouldn't use this `FontId` somewhere that will retrieve the corresponding /// `LoadedFont.features`, as it will have an arbitrarily chosen or empty value. The only /// current use of this field is for the *input* of `layout_line`, and so it's fine to use /// `font_id_for_cosmic_id` when computing the *output* of `layout_line`. fn font_id_for_cosmic_id(&mut self, id: cosmic_text::fontdb::ID) -> Result<FontId> { if let Some(ix) = self .loaded_fonts .iter() .position(|loaded_font| loaded_font.font.id() == id) { Ok(FontId(ix)) } else { let font = self .font_system .get_font(id, cosmic_text::Weight::NORMAL) .context("failed to get fallback font from cosmic-text font system")?; let face = self .font_system .db() .face(id) .context("fallback font face not found in cosmic-text database")?; let font_id = FontId(self.loaded_fonts.len()); self.loaded_fonts.push(LoadedFont { font, features: CosmicFontFeatures::new(), is_known_emoji_font: check_is_known_emoji_font(&face.post_script_name), user_fallback_chain: Arc::from(Vec::new()), }); Ok(font_id) } } #[profiling::function] fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { if contains_paragraph_separator(text) { self.layout_line_with_separators(text, font_size, font_runs) } else { self.layout_line_no_separators(text, font_size, font_runs) } } fn layout_line_with_separators( &mut self, text: &str, font_size: Pixels, font_runs: &[FontRun], ) -> LineLayout { let mut layout = LineLayout { font_size, len: text.len(), ..Default::default() }; let mut paragraph_start = 0; for (separator_start, separator) in text .char_indices() .filter(|(_, character)| is_paragraph_separator(*character)) { let separator_end = separator_start + separator.len_utf8(); self.shape_segment( text, paragraph_start..separator_start, font_size, font_runs, &mut layout, ); self.shape_segment( text, separator_start..separator_end, font_size, font_runs, &mut layout, ); paragraph_start = separator_end; } self.shape_segment( text,Lines 1–500 of 1419. Load more
use anyhow::{Context as _, Ok, Result}; use collections::HashMap; use cosmic_text::{ Attrs, AttrsList, Ellipsize, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures, FontSystem, ShapeBuffer, ShapeLine, }; use gpui::{ Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, GlyphId, LineLayout, Pixels, PlatformTextSystem, RenderGlyphParams, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, size, }; use itertools::Itertools; use parking_lot::RwLock; use smallvec::SmallVec; use std::{borrow::Cow, ops::Range, sync::Arc}; use swash::{ scale::{Render, ScaleContext, Source, StrikeWith}, zeno::{Format, Vector}, }; use unicode_segmentation::UnicodeSegmentation; pub struct CosmicTextSystem(RwLock<CosmicTextSystemState>); #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct FontKey { family: SharedString, features: FontFeatures, fallbacks: Option<FontFallbacks>, } impl FontKey { fn new(family: SharedString, features: FontFeatures, fallbacks: Option<FontFallbacks>) -> Self { Self { family, features, fallbacks, } } } struct CosmicTextSystemState { font_system: FontSystem, scratch: ShapeBuffer, swash_scale_context: ScaleContext, /// Contains all already loaded fonts, including all faces. Indexed by `FontId`. loaded_fonts: Vec<LoadedFont>, /// Caches the `FontId`s associated with a specific family to avoid iterating the font database /// for every font face in a family. font_ids_by_family_cache: HashMap<FontKey, SmallVec<[FontId; 4]>>, system_font_fallback: String, } struct LoadedFont { font: Arc<CosmicTextFont>, features: CosmicFontFeatures, is_known_emoji_font: bool, /// resolved at load time so `layout_line` shares one chain across faces. /// `Arc` keeps clone cheap on the per-run hot path. user_fallback_chain: Arc<[(FontId, SharedString)]>, } impl CosmicTextSystem { pub fn new(system_font_fallback: &str) -> Self { let font_system = FontSystem::new(); Self(RwLock::new(CosmicTextSystemState { font_system, scratch: ShapeBuffer::default(), swash_scale_context: ScaleContext::new(), loaded_fonts: Vec::new(), font_ids_by_family_cache: HashMap::default(), system_font_fallback: system_font_fallback.to_string(), })) } pub fn new_without_system_fonts(system_font_fallback: &str) -> Self { let font_system = FontSystem::new_with_locale_and_db( "en-US".to_string(), cosmic_text::fontdb::Database::new(), ); Self(RwLock::new(CosmicTextSystemState { font_system, scratch: ShapeBuffer::default(), swash_scale_context: ScaleContext::new(), loaded_fonts: Vec::new(), font_ids_by_family_cache: HashMap::default(), system_font_fallback: system_font_fallback.to_string(), })) } } impl PlatformTextSystem for CosmicTextSystem { fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { self.0.write().add_fonts(fonts) } fn all_font_names(&self) -> Vec<String> { let mut result = self .0 .read() .font_system .db() .faces() .filter_map(|face| face.families.first().map(|family| family.0.clone())) .collect_vec(); result.sort_unstable(); result.dedup(); result } fn font_id(&self, font: &Font) -> Result<FontId> { let mut state = self.0.write(); let key = FontKey::new( font.family.clone(), font.features.clone(), font.fallbacks.clone(), ); let candidates = if let Some(font_ids) = state.font_ids_by_family_cache.get(&key) { font_ids.as_slice() } else { let font_ids = state.load_family(&font.family, &font.features, font.fallbacks.as_ref())?; state.font_ids_by_family_cache.insert(key.clone(), font_ids); state.font_ids_by_family_cache[&key].as_ref() }; let ix = find_best_match(font, candidates, &state)?; Ok(candidates[ix]) } fn font_metrics(&self, font_id: FontId) -> FontMetrics { let metrics = self .0 .read() .loaded_font(font_id) .font .as_swash() .metrics(&[]); FontMetrics { units_per_em: metrics.units_per_em as u32, ascent: metrics.ascent, descent: -metrics.descent, line_gap: metrics.leading, underline_position: metrics.underline_offset, underline_thickness: metrics.stroke_size, cap_height: metrics.cap_height, x_height: metrics.x_height, bounding_box: Bounds { origin: point(0.0, 0.0), size: size(metrics.max_width, metrics.ascent + metrics.descent), }, } } fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> { let lock = self.0.read(); let glyph_metrics = lock.loaded_font(font_id).font.as_swash().glyph_metrics(&[]); let glyph_id = glyph_id.0 as u16; Ok(Bounds { origin: point(0.0, 0.0), size: size( glyph_metrics.advance_width(glyph_id), glyph_metrics.advance_height(glyph_id), ), }) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { self.0.read().advance(font_id, glyph_id) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { self.0.read().glyph_for_char(font_id, ch) } fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { self.0.write().raster_bounds(params) } fn rasterize_glyph( &self, params: &RenderGlyphParams, raster_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { self.0.write().rasterize_glyph(params, raster_bounds) } fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout { self.0.write().layout_line(text, font_size, runs) } fn recommended_rendering_mode( &self, _font_id: FontId, _font_size: Pixels, ) -> TextRenderingMode { TextRenderingMode::Subpixel } } impl CosmicTextSystemState { fn loaded_font(&self, font_id: FontId) -> &LoadedFont { &self.loaded_fonts[font_id.0] } #[profiling::function] fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> { let db = self.font_system.db_mut(); for bytes in fonts { match bytes { Cow::Borrowed(embedded_font) => { db.load_font_data(embedded_font.to_vec()); } Cow::Owned(bytes) => { db.load_font_data(bytes); } } } Ok(()) } #[profiling::function] fn load_family( &mut self, name: &str, features: &FontFeatures, fallbacks: Option<&FontFallbacks>, ) -> Result<SmallVec<[FontId; 4]>> { // recurse with `fallbacks = None` so a fallback family cannot pull in // another chain. missing fallback families are dropped so a typo in // settings still lets the primary family load. let user_fallback_chain: Arc<[(FontId, SharedString)]> = match fallbacks { Some(fallbacks) if !fallbacks.fallback_list().is_empty() => { let mut chain: Vec<(FontId, SharedString)> = Vec::new(); for fallback_name in fallbacks.fallback_list() { let fb_key = FontKey::new( SharedString::from(fallback_name.clone()), features.clone(), None, ); let fb_ids = if let Some(cached) = self.font_ids_by_family_cache.get(&fb_key) { cached.clone() } else { let loaded = self.load_family(fallback_name, features, None)?; self.font_ids_by_family_cache .insert(fb_key.clone(), loaded.clone()); loaded }; let Some(&fb_id) = fb_ids.first() else { continue; }; let db_id = self.loaded_fonts[fb_id.0].font.id(); if let Some(face) = self.font_system.db().face(db_id) && let Some(family) = face.families.first() { chain.push((fb_id, SharedString::from(family.0.clone()))); } } Arc::from(chain) } _ => Arc::from(Vec::new()), }; let name = gpui::font_name_with_fallbacks(name, &self.system_font_fallback); let families = self .font_system .db() .faces() .filter(|face| face.families.iter().any(|family| *name == family.0)) .map(|face| (face.id, face.post_script_name.clone())) .collect::<SmallVec<[_; 4]>>(); let cosmic_features = cosmic_font_features(features)?; let mut loaded_font_ids = SmallVec::new(); for (font_id, postscript_name) in families { let font = self .font_system .get_font(font_id, cosmic_text::Weight::NORMAL) .context("Could not load font")?; // HACK: To let the storybook run and render Windows caption icons. We should actually do better font fallback. let allowed_bad_font_names = [ "SegoeFluentIcons", // NOTE: Segoe fluent icons postscript name is inconsistent "Segoe Fluent Icons", ]; if font.as_swash().charmap().map('m') == 0 && !allowed_bad_font_names.contains(&postscript_name.as_str()) { self.font_system.db_mut().remove_face(font.id()); continue; }; let font_id = FontId(self.loaded_fonts.len()); loaded_font_ids.push(font_id); self.loaded_fonts.push(LoadedFont { font, features: cosmic_features.clone(), is_known_emoji_font: check_is_known_emoji_font(&postscript_name), user_fallback_chain: Arc::clone(&user_fallback_chain), }); } Ok(loaded_font_ids) } fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> { let glyph_metrics = self.loaded_font(font_id).font.as_swash().glyph_metrics(&[]); Ok(Size { width: glyph_metrics.advance_width(glyph_id.0 as u16), height: glyph_metrics.advance_height(glyph_id.0 as u16), }) } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { let glyph_id = self.loaded_font(font_id).font.as_swash().charmap().map(ch); if glyph_id == 0 { None } else { Some(GlyphId(glyph_id.into())) } } fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { let image = self.render_glyph_image(params)?; Ok(Bounds { origin: point(image.placement.left.into(), (-image.placement.top).into()), size: size(image.placement.width.into(), image.placement.height.into()), }) } #[profiling::function] fn rasterize_glyph( &mut self, params: &RenderGlyphParams, glyph_bounds: Bounds<DevicePixels>, ) -> Result<(Size<DevicePixels>, Vec<u8>)> { if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { anyhow::bail!("glyph bounds are empty"); } let mut image = self.render_glyph_image(params)?; let bitmap_size = glyph_bounds.size; match image.content { swash::scale::image::Content::Color | swash::scale::image::Content::SubpixelMask => { // Convert from RGBA to BGRA. for pixel in image.data.chunks_exact_mut(4) { pixel.swap(0, 2); } Ok((bitmap_size, image.data)) } swash::scale::image::Content::Mask => { if params.subpixel_rendering { // We must always return RGBA data when subpixel rendering is requested. let expanded = image.data.iter().flat_map(|&a| [a, a, a, a]).collect(); Ok((bitmap_size, expanded)) } else { Ok((bitmap_size, image.data)) } } } } fn render_glyph_image( &mut self, params: &RenderGlyphParams, ) -> Result<swash::scale::image::Image> { let loaded_font = &self.loaded_fonts[params.font_id.0]; let font_ref = loaded_font.font.as_swash(); let pixel_size = f32::from(params.font_size); let subpixel_offset = Vector::new( params.subpixel_variant.x as f32 / SUBPIXEL_VARIANTS_X as f32 / params.scale_factor, params.subpixel_variant.y as f32 / SUBPIXEL_VARIANTS_Y as f32 / params.scale_factor, ); let mut scaler = self .swash_scale_context .builder(font_ref) .size(pixel_size * params.scale_factor) .hint(true) .build(); let sources: &[Source] = if params.is_emoji { &[ Source::ColorOutline(0), Source::ColorBitmap(StrikeWith::BestFit), Source::Outline, ] } else { &[Source::Bitmap(StrikeWith::ExactSize), Source::Outline] }; let mut renderer = Render::new(sources); if params.subpixel_rendering { // There seems to be a bug in Swash where the B and R values are swapped. renderer .format(Format::subpixel_bgra()) .offset(subpixel_offset); } else { renderer.format(Format::Alpha).offset(subpixel_offset); } let glyph_id: u16 = params.glyph_id.0.try_into()?; renderer .render(&mut scaler, glyph_id) .with_context(|| format!("unable to render glyph via swash for {params:?}")) } /// This is used when cosmic_text has chosen a fallback font instead of using the requested /// font, typically to handle some unicode characters. When this happens, `loaded_fonts` may not /// yet have an entry for this fallback font, and so one is added. /// /// Note that callers shouldn't use this `FontId` somewhere that will retrieve the corresponding /// `LoadedFont.features`, as it will have an arbitrarily chosen or empty value. The only /// current use of this field is for the *input* of `layout_line`, and so it's fine to use /// `font_id_for_cosmic_id` when computing the *output* of `layout_line`. fn font_id_for_cosmic_id(&mut self, id: cosmic_text::fontdb::ID) -> Result<FontId> { if let Some(ix) = self .loaded_fonts .iter() .position(|loaded_font| loaded_font.font.id() == id) { Ok(FontId(ix)) } else { let font = self .font_system .get_font(id, cosmic_text::Weight::NORMAL) .context("failed to get fallback font from cosmic-text font system")?; let face = self .font_system .db() .face(id) .context("fallback font face not found in cosmic-text database")?; let font_id = FontId(self.loaded_fonts.len()); self.loaded_fonts.push(LoadedFont { font, features: CosmicFontFeatures::new(), is_known_emoji_font: check_is_known_emoji_font(&face.post_script_name), user_fallback_chain: Arc::from(Vec::new()), }); Ok(font_id) } } #[profiling::function] fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { if contains_paragraph_separator(text) { self.layout_line_with_separators(text, font_size, font_runs) } else { self.layout_line_no_separators(text, font_size, font_runs) } } fn layout_line_with_separators( &mut self, text: &str, font_size: Pixels, font_runs: &[FontRun], ) -> LineLayout { let mut layout = LineLayout { font_size, len: text.len(), ..Default::default() }; let mut paragraph_start = 0; for (separator_start, separator) in text .char_indices() .filter(|(_, character)| is_paragraph_separator(*character)) { let separator_end = separator_start + separator.len_utf8(); self.shape_segment( text, paragraph_start..separator_start, font_size, font_runs, &mut layout, ); self.shape_segment( text, separator_start..separator_end, font_size, font_runs, &mut layout, ); paragraph_start = separator_end; } self.shape_segment( text,