use crate::{ Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, size,};use anyhow::{Context as _, Ok, Result};use collections::HashMap;use cosmic_text::{ Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures, FontSystem, ShapeBuffer, ShapeLine,}; use itertools::Itertools;use parking_lot::RwLock;use pathfinder_geometry::{ rect::{RectF, RectI}, vector::{Vector2F, Vector2I},};use smallvec::SmallVec;use std::{borrow::Cow, sync::Arc};use swash::{ scale::{Render, ScaleContext, Source, StrikeWith}, zeno::{Format, Vector},}; pub(crate) struct CosmicTextSystem(RwLock<CosmicTextSystemState>); #[derive(Debug, Clone, PartialEq, Eq, Hash)]struct FontKey { family: SharedString, features: FontFeatures,} impl FontKey { fn new(family: SharedString, features: FontFeatures) -> Self { Self { family, features } }} 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]>>,} struct LoadedFont { font: Arc<CosmicTextFont>, features: CosmicFontFeatures, is_known_emoji_font: bool,} impl CosmicTextSystem { pub(crate) fn new() -> Self { // todo(linux) make font loading non-blocking let mut 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(), })) }} impl Default for CosmicTextSystem { fn default() -> Self { Self::new() }} 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(); result.dedup(); result } fn font_id(&self, font: &Font) -> Result<FontId> { // todo(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? let mut state = self.0.write(); let key = FontKey::new(font.family.clone(), font.features.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)?; state.font_ids_by_family_cache.insert(key.clone(), font_ids); state.font_ids_by_family_cache[&key].as_ref() }; // todo(linux) ideally we would make fontdb's `find_best_match` pub instead of using font-kit here let candidate_properties = candidates .iter() .map(|font_id| { let database_id = state.loaded_font(*font_id).font.id(); let face_info = state.font_system.db().face(database_id).expect(""); face_info_into_properties(face_info) }) .collect::<SmallVec<[_; 4]>>(); let ix = font_kit::matching::find_best_match(&candidate_properties, &font_into_properties(font)) .context("requested font family contains no font matching the other parameters")?; 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, // todo(linux) confirm this is correct line_gap: metrics.leading, underline_position: metrics.underline_offset, underline_thickness: metrics.stroke_size, cap_height: metrics.cap_height, x_height: metrics.x_height, // todo(linux): Compute this correctly 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; // todo(linux): Compute this correctly // see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620 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 { // Ideally, we'd use fontconfig to read the user preference. 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, ) -> Result<SmallVec<[FontId; 4]>> { // TODO: Determine the proper system UI font. let name = crate::text_system::font_name_with_fallbacks(name, "IBM Plex Sans"); 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 mut loaded_font_ids = SmallVec::new(); for (font_id, postscript_name) in families { let font = self .font_system .get_font(font_id) .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: features.try_into()?, is_known_emoji_font: check_is_known_emoji_font(&postscript_name), }); } 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 => 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 = params.font_size.0; 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::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) -> FontId { if let Some(ix) = self .loaded_fonts .iter() .position(|loaded_font| loaded_font.font.id() == id) { FontId(ix) } else { let font = self.font_system.get_font(id).unwrap(); let face = self.font_system.db().face(id).unwrap(); 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), }); font_id } } #[profiling::function] fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { let mut attrs_list = AttrsList::new(&Attrs::new()); let mut offs = 0; for run in font_runs { let loaded_font = self.loaded_font(run.font_id); let font = self.font_system.db().face(loaded_font.font.id()).unwrap(); attrs_list.add_span( offs..(offs + run.len), &Attrs::new() .metadata(run.font_id.0) .family(Family::Name(&font.families.first().unwrap().0)) .stretch(font.stretch) .style(font.style) .weight(font.weight) .font_features(loaded_font.features.clone()), ); offs += run.len; } let line = ShapeLine::new( &mut self.font_system, text, &attrs_list, cosmic_text::Shaping::Advanced, 4, ); let mut layout_lines = Vec::with_capacity(1); line.layout_to_buffer( &mut self.scratch, font_size.0, None, // We do our own wrapping cosmic_text::Wrap::None, None, &mut layout_lines, None, ); let layout = layout_lines.first().unwrap(); let mut runs: Vec<ShapedRun> = Vec::new(); for glyph in &layout.glyphs { let mut font_id = FontId(glyph.metadata); let mut loaded_font = self.loaded_font(font_id); if loaded_font.font.id() != glyph.font_id { font_id = self.font_id_for_cosmic_id(glyph.font_id); loaded_font = self.loaded_font(font_id); } let is_emoji = loaded_font.is_known_emoji_font; // HACK: Prevent crash caused by variation selectors. if glyph.glyph_id == 3 && is_emoji { continue; } let shaped_glyph = ShapedGlyph { id: GlyphId(glyph.glyph_id as u32), position: point(glyph.x.into(), glyph.y.into()), index: glyph.start, is_emoji, }; if let Some(last_run) = runs .last_mut() .filter(|last_run| last_run.font_id == font_id) { last_run.glyphs.push(shaped_glyph); } else { runs.push(ShapedRun { font_id, glyphs: vec![shaped_glyph], }); } } LineLayout { font_size, width: layout.w.into(), ascent: layout.max_ascent.into(), descent: layout.max_descent.into(), runs, len: text.len(), } }} impl TryFrom<&FontFeatures> for CosmicFontFeatures { type Error = anyhow::Error; fn try_from(features: &FontFeatures) -> Result<Self> { let mut result = CosmicFontFeatures::new(); for feature in features.0.iter() { let name_bytes: [u8; 4] = feature .0 .as_bytes() .try_into() .context("Incorrect feature flag format")?; let tag = cosmic_text::FeatureTag::new(&name_bytes); result.set(tag, feature.1); } Ok(result) }Lines 1–500 of 604. Load more
use crate::{ Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, SUBPIXEL_VARIANTS_X, SUBPIXEL_VARIANTS_Y, ShapedGlyph, ShapedRun, SharedString, Size, TextRenderingMode, point, size, }; use anyhow::{Context as _, Ok, Result}; use collections::HashMap; use cosmic_text::{ Attrs, AttrsList, Family, Font as CosmicTextFont, FontFeatures as CosmicFontFeatures, FontSystem, ShapeBuffer, ShapeLine, }; use itertools::Itertools; use parking_lot::RwLock; use pathfinder_geometry::{ rect::{RectF, RectI}, vector::{Vector2F, Vector2I}, }; use smallvec::SmallVec; use std::{borrow::Cow, sync::Arc}; use swash::{ scale::{Render, ScaleContext, Source, StrikeWith}, zeno::{Format, Vector}, }; pub(crate) struct CosmicTextSystem(RwLock<CosmicTextSystemState>); #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct FontKey { family: SharedString, features: FontFeatures, } impl FontKey { fn new(family: SharedString, features: FontFeatures) -> Self { Self { family, features } } } 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]>>, } struct LoadedFont { font: Arc<CosmicTextFont>, features: CosmicFontFeatures, is_known_emoji_font: bool, } impl CosmicTextSystem { pub(crate) fn new() -> Self { // todo(linux) make font loading non-blocking let mut 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(), })) } } impl Default for CosmicTextSystem { fn default() -> Self { Self::new() } } 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(); result.dedup(); result } fn font_id(&self, font: &Font) -> Result<FontId> { // todo(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? let mut state = self.0.write(); let key = FontKey::new(font.family.clone(), font.features.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)?; state.font_ids_by_family_cache.insert(key.clone(), font_ids); state.font_ids_by_family_cache[&key].as_ref() }; // todo(linux) ideally we would make fontdb's `find_best_match` pub instead of using font-kit here let candidate_properties = candidates .iter() .map(|font_id| { let database_id = state.loaded_font(*font_id).font.id(); let face_info = state.font_system.db().face(database_id).expect(""); face_info_into_properties(face_info) }) .collect::<SmallVec<[_; 4]>>(); let ix = font_kit::matching::find_best_match(&candidate_properties, &font_into_properties(font)) .context("requested font family contains no font matching the other parameters")?; 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, // todo(linux) confirm this is correct line_gap: metrics.leading, underline_position: metrics.underline_offset, underline_thickness: metrics.stroke_size, cap_height: metrics.cap_height, x_height: metrics.x_height, // todo(linux): Compute this correctly 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; // todo(linux): Compute this correctly // see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620 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 { // Ideally, we'd use fontconfig to read the user preference. 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, ) -> Result<SmallVec<[FontId; 4]>> { // TODO: Determine the proper system UI font. let name = crate::text_system::font_name_with_fallbacks(name, "IBM Plex Sans"); 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 mut loaded_font_ids = SmallVec::new(); for (font_id, postscript_name) in families { let font = self .font_system .get_font(font_id) .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: features.try_into()?, is_known_emoji_font: check_is_known_emoji_font(&postscript_name), }); } 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 => 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 = params.font_size.0; 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::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) -> FontId { if let Some(ix) = self .loaded_fonts .iter() .position(|loaded_font| loaded_font.font.id() == id) { FontId(ix) } else { let font = self.font_system.get_font(id).unwrap(); let face = self.font_system.db().face(id).unwrap(); 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), }); font_id } } #[profiling::function] fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { let mut attrs_list = AttrsList::new(&Attrs::new()); let mut offs = 0; for run in font_runs { let loaded_font = self.loaded_font(run.font_id); let font = self.font_system.db().face(loaded_font.font.id()).unwrap(); attrs_list.add_span( offs..(offs + run.len), &Attrs::new() .metadata(run.font_id.0) .family(Family::Name(&font.families.first().unwrap().0)) .stretch(font.stretch) .style(font.style) .weight(font.weight) .font_features(loaded_font.features.clone()), ); offs += run.len; } let line = ShapeLine::new( &mut self.font_system, text, &attrs_list, cosmic_text::Shaping::Advanced, 4, ); let mut layout_lines = Vec::with_capacity(1); line.layout_to_buffer( &mut self.scratch, font_size.0, None, // We do our own wrapping cosmic_text::Wrap::None, None, &mut layout_lines, None, ); let layout = layout_lines.first().unwrap(); let mut runs: Vec<ShapedRun> = Vec::new(); for glyph in &layout.glyphs { let mut font_id = FontId(glyph.metadata); let mut loaded_font = self.loaded_font(font_id); if loaded_font.font.id() != glyph.font_id { font_id = self.font_id_for_cosmic_id(glyph.font_id); loaded_font = self.loaded_font(font_id); } let is_emoji = loaded_font.is_known_emoji_font; // HACK: Prevent crash caused by variation selectors. if glyph.glyph_id == 3 && is_emoji { continue; } let shaped_glyph = ShapedGlyph { id: GlyphId(glyph.glyph_id as u32), position: point(glyph.x.into(), glyph.y.into()), index: glyph.start, is_emoji, }; if let Some(last_run) = runs .last_mut() .filter(|last_run| last_run.font_id == font_id) { last_run.glyphs.push(shaped_glyph); } else { runs.push(ShapedRun { font_id, glyphs: vec![shaped_glyph], }); } } LineLayout { font_size, width: layout.w.into(), ascent: layout.max_ascent.into(), descent: layout.max_descent.into(), runs, len: text.len(), } } } impl TryFrom<&FontFeatures> for CosmicFontFeatures { type Error = anyhow::Error; fn try_from(features: &FontFeatures) -> Result<Self> { let mut result = CosmicFontFeatures::new(); for feature in features.0.iter() { let name_bytes: [u8; 4] = feature .0 .as_bytes() .try_into() .context("Incorrect feature flag format")?; let tag = cosmic_text::FeatureTag::new(&name_bytes); result.set(tag, feature.1); } Ok(result) }