From bfd9bb8a7cbb59880179e481e0a9e92ad1c89617 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Tue, 9 Apr 2024 17:25:16 -0700 Subject: [PATCH] Use shared text system on Linux and Windows (#10098) closes #10129 closes #10269 Release Notes: - N/A --- crates/gpui/src/platform.rs | 6 + crates/gpui/src/platform/cosmic_text.rs | 3 + .../{linux => cosmic_text}/text_system.rs | 25 +- crates/gpui/src/platform/linux.rs | 2 - crates/gpui/src/platform/linux/platform.rs | 8 +- crates/gpui/src/platform/test/platform.rs | 7 +- crates/gpui/src/platform/windows.rs | 2 - crates/gpui/src/platform/windows/platform.rs | 4 +- .../gpui/src/platform/windows/text_system.rs | 457 ------------------ 9 files changed, 33 insertions(+), 481 deletions(-) create mode 100644 crates/gpui/src/platform/cosmic_text.rs rename crates/gpui/src/platform/{linux => cosmic_text}/text_system.rs (95%) delete mode 100644 crates/gpui/src/platform/windows/text_system.rs diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d65eb6deb9a4cc0070f19c66d2816fd2ef91fab5..509160471c288c97fa802f8d856ef200103886c9 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -6,6 +6,9 @@ mod app_menu; mod keystroke; +#[cfg(not(target_os = "macos"))] +mod cosmic_text; + #[cfg(target_os = "linux")] mod linux; @@ -49,6 +52,9 @@ use uuid::Uuid; pub use app_menu::*; pub use keystroke::*; + +#[cfg(not(target_os = "macos"))] +pub(crate) use cosmic_text::*; #[cfg(target_os = "linux")] pub(crate) use linux::*; #[cfg(target_os = "macos")] diff --git a/crates/gpui/src/platform/cosmic_text.rs b/crates/gpui/src/platform/cosmic_text.rs new file mode 100644 index 0000000000000000000000000000000000000000..f7a54b65ba0012c6bce6042b9714f349c628b590 --- /dev/null +++ b/crates/gpui/src/platform/cosmic_text.rs @@ -0,0 +1,3 @@ +mod text_system; + +pub(crate) use text_system::*; diff --git a/crates/gpui/src/platform/linux/text_system.rs b/crates/gpui/src/platform/cosmic_text/text_system.rs similarity index 95% rename from crates/gpui/src/platform/linux/text_system.rs rename to crates/gpui/src/platform/cosmic_text/text_system.rs index 5164ebd93034b37a6ab0b1c48adac5cd709e61ee..13467361e42def0206b6e9f9dc1b72299a754121 100644 --- a/crates/gpui/src/platform/linux/text_system.rs +++ b/crates/gpui/src/platform/cosmic_text/text_system.rs @@ -18,9 +18,9 @@ use pathfinder_geometry::{ use smallvec::SmallVec; use std::{borrow::Cow, sync::Arc}; -pub(crate) struct LinuxTextSystem(RwLock); +pub(crate) struct CosmicTextSystem(RwLock); -struct LinuxTextSystemState { +struct CosmicTextSystemState { swash_cache: SwashCache, font_system: FontSystem, /// Contains all already loaded fonts, including all faces. Indexed by `FontId`. @@ -32,14 +32,14 @@ struct LinuxTextSystemState { postscript_names: HashMap, } -impl LinuxTextSystem { +impl CosmicTextSystem { pub(crate) fn new() -> Self { let mut font_system = FontSystem::new(); // todo(linux) make font loading non-blocking font_system.db_mut().load_system_fonts(); - Self(RwLock::new(LinuxTextSystemState { + Self(RwLock::new(CosmicTextSystemState { font_system, swash_cache: SwashCache::new(), loaded_fonts_store: Vec::new(), @@ -49,13 +49,13 @@ impl LinuxTextSystem { } } -impl Default for LinuxTextSystem { +impl Default for CosmicTextSystem { fn default() -> Self { Self::new() } } -impl PlatformTextSystem for LinuxTextSystem { +impl PlatformTextSystem for CosmicTextSystem { fn add_fonts(&self, fonts: Vec>) -> Result<()> { self.0.write().add_fonts(fonts) } @@ -189,7 +189,7 @@ impl PlatformTextSystem for LinuxTextSystem { } } -impl LinuxTextSystemState { +impl CosmicTextSystemState { #[profiling::function] fn add_fonts(&mut self, fonts: Vec>) -> Result<()> { let db = self.font_system.db_mut(); @@ -235,8 +235,15 @@ impl LinuxTextSystemState { .get_font(font_id) .ok_or_else(|| anyhow!("Could not load font"))?; - // HACK: to let the storybook run, we should actually do better font fallback - if font.as_swash().charmap().map('m') == 0 || postscript_name == "Segoe Fluent Icons" { + // 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; }; diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index f12c5432f579b6b1782b2fbe226586527c61f832..6bf7cc48402ea282f972169f12840932296e9eaf 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -3,12 +3,10 @@ mod dispatcher; mod platform; -mod text_system; mod wayland; mod x11; pub(crate) use dispatcher::*; pub(crate) use platform::*; -pub(crate) use text_system::*; pub(crate) use wayland::*; pub(crate) use x11::*; diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 0a962b67514ac59a93f97c3ff38159b7d6c6c4b3..79a2e8432f5bc3f174373ebc08278cb69fb10855 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -28,8 +28,8 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State}; use crate::platform::linux::wayland::WaylandClient; use crate::{ - px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, LinuxTextSystem, Menu, Modifiers, + px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle, + DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, Modifiers, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task, WindowAppearance, WindowOptions, WindowParams, @@ -134,7 +134,7 @@ pub(crate) struct PlatformHandlers { pub(crate) struct LinuxCommon { pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, - pub(crate) text_system: Arc, + pub(crate) text_system: Arc, pub(crate) callbacks: PlatformHandlers, pub(crate) signal: LoopSignal, } @@ -142,7 +142,7 @@ pub(crate) struct LinuxCommon { impl LinuxCommon { pub fn new(signal: LoopSignal) -> (Self, Channel) { let (main_sender, main_receiver) = calloop::channel::channel::(); - let text_system = Arc::new(LinuxTextSystem::new()); + let text_system = Arc::new(CosmicTextSystem::new()); let callbacks = PlatformHandlers::default(); let dispatcher = Arc::new(LinuxDispatcher::new(main_sender)); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index dde8cf1db69c80c10ee3a4e1f6244b7d8e93302e..dd324694af2efba08a4b8061496a5f766eb1e9aa 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -122,14 +122,11 @@ impl Platform for TestPlatform { } fn text_system(&self) -> Arc { - #[cfg(target_os = "linux")] - return Arc::new(crate::platform::linux::LinuxTextSystem::new()); - #[cfg(target_os = "macos")] return Arc::new(crate::platform::mac::MacTextSystem::new()); - #[cfg(target_os = "windows")] - return Arc::new(crate::platform::windows::WindowsTextSystem::new()); + #[cfg(not(target_os = "macos"))] + return Arc::new(crate::platform::cosmic_text::CosmicTextSystem::new()); } fn run(&self, _on_finish_launching: Box) { diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs index c2bc5dbdbdfeff45e19167d40ba431c8066a07b4..dbb1592d219b1c75d1f35405523ceacf92197978 100644 --- a/crates/gpui/src/platform/windows.rs +++ b/crates/gpui/src/platform/windows.rs @@ -1,14 +1,12 @@ mod dispatcher; mod display; mod platform; -mod text_system; mod util; mod window; pub(crate) use dispatcher::*; pub(crate) use display::*; pub(crate) use platform::*; -pub(crate) use text_system::*; pub(crate) use util::*; pub(crate) use window::*; diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 743b184f72030b9bd952b753b5b263cfa931dd78..0b68de9dcbe2bebbe9c85af5bffa0789e1f7d359 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -57,7 +57,7 @@ pub(crate) struct WindowsPlatformInner { background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, main_receiver: flume::Receiver, - text_system: Arc, + text_system: Arc, callbacks: Mutex, pub raw_window_handles: RwLock>, pub(crate) dispatch_event: OwnedHandle, @@ -155,7 +155,7 @@ impl WindowsPlatform { let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event.to_raw())); let background_executor = BackgroundExecutor::new(dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(dispatcher); - let text_system = Arc::new(WindowsTextSystem::new()); + let text_system = Arc::new(CosmicTextSystem::new()); let callbacks = Mutex::new(Callbacks::default()); let raw_window_handles = RwLock::new(SmallVec::new()); let settings = RefCell::new(WindowsPlatformSystemSettings::new()); diff --git a/crates/gpui/src/platform/windows/text_system.rs b/crates/gpui/src/platform/windows/text_system.rs deleted file mode 100644 index 6ad8c4be540de6c78532ee20f87bd1a47b1f4d63..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/windows/text_system.rs +++ /dev/null @@ -1,457 +0,0 @@ -use crate::{ - point, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, - FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, - ShapedGlyph, SharedString, Size, -}; -use anyhow::{anyhow, Context, Ok, Result}; -use collections::HashMap; -use cosmic_text::Font as CosmicTextFont; -use cosmic_text::{ - fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, FontSystem, SwashCache, -}; -use parking_lot::{RwLock, RwLockUpgradableReadGuard}; -use pathfinder_geometry::{ - rect::{RectF, RectI}, - vector::{Vector2F, Vector2I}, -}; -use smallvec::SmallVec; -use std::{borrow::Cow, sync::Arc}; - -pub(crate) struct WindowsTextSystem(RwLock); - -struct WindowsTextSystemState { - swash_cache: SwashCache, - font_system: FontSystem, - fonts: Vec>, - font_selections: HashMap, - font_ids_by_family_name: HashMap>, - postscript_names_by_font_id: HashMap, -} - -impl WindowsTextSystem { - pub(crate) fn new() -> Self { - let mut font_system = FontSystem::new(); - Self(RwLock::new(WindowsTextSystemState { - font_system, - swash_cache: SwashCache::new(), - fonts: Vec::new(), - font_selections: HashMap::default(), - // font_ids_by_postscript_name: HashMap::default(), - font_ids_by_family_name: HashMap::default(), - postscript_names_by_font_id: HashMap::default(), - })) - } -} - -impl Default for WindowsTextSystem { - fn default() -> Self { - Self::new() - } -} - -#[allow(unused)] -impl PlatformTextSystem for WindowsTextSystem { - fn add_fonts(&self, fonts: Vec>) -> Result<()> { - self.0.write().add_fonts(fonts) - } - - // todo(windows) ensure that this integrates with platform font loading - // do we need to do more than call load_system_fonts()? - fn all_font_names(&self) -> Vec { - self.0 - .read() - .font_system - .db() - .faces() - .map(|face| face.post_script_name.clone()) - .collect() - } - - // todo(windows) - fn all_font_families(&self) -> Vec { - Vec::new() - } - - fn font_id(&self, font: &Font) -> Result { - // todo(windows): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? - let lock = self.0.upgradable_read(); - if let Some(font_id) = lock.font_selections.get(font) { - Ok(*font_id) - } else { - let mut lock = RwLockUpgradableReadGuard::upgrade(lock); - let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family) - { - font_ids.as_slice() - } else { - let font_ids = lock.load_family(&font.family, font.features)?; - lock.font_ids_by_family_name - .insert(font.family.clone(), font_ids); - lock.font_ids_by_family_name[&font.family].as_ref() - }; - - let id = lock - .font_system - .db() - .query(&Query { - families: &[Family::Name(&font.family)], - weight: font.weight.into(), - style: font.style.into(), - stretch: Default::default(), - }) - .context("no font")?; - - let font_id = if let Some(font_id) = lock.fonts.iter().position(|font| font.id() == id) - { - FontId(font_id) - } else { - // Font isn't in fonts so add it there, this is because we query all the fonts in the db - // and maybe we haven't loaded it yet - let font_id = FontId(lock.fonts.len()); - let font = lock.font_system.get_font(id).unwrap(); - lock.fonts.push(font); - font_id - }; - - lock.font_selections.insert(font.clone(), font_id); - Ok(font_id) - } - } - - fn font_metrics(&self, font_id: FontId) -> FontMetrics { - let metrics = self.0.read().fonts[font_id.0].as_swash().metrics(&[]); - - FontMetrics { - units_per_em: metrics.units_per_em as u32, - ascent: metrics.ascent, - descent: -metrics.descent, // todo(windows) 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(windows): 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> { - let lock = self.0.read(); - let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]); - let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]); - let glyph_id = glyph_id.0 as u16; - // todo(windows): 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> { - self.0.read().advance(font_id, glyph_id) - } - - fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { - self.0.read().glyph_for_char(font_id, ch) - } - - fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result> { - self.0.write().raster_bounds(params) - } - - fn rasterize_glyph( - &self, - params: &RenderGlyphParams, - raster_bounds: Bounds, - ) -> Result<(Size, Vec)> { - 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) - } - - // todo(windows) Confirm that this has been superseded by the LineWrapper - fn wrap_line( - &self, - text: &str, - font_id: FontId, - font_size: Pixels, - width: Pixels, - ) -> Vec { - unimplemented!() - } -} - -impl WindowsTextSystemState { - #[profiling::function] - fn add_fonts(&mut self, fonts: Vec>) -> 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> { - // TODO: Determine the proper system UI font. - let name = if name == ".SystemUIFont" { - "Zed Sans" - } else { - name - }; - - let mut font_ids = SmallVec::new(); - 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::>(); - - for (font_id, postscript_name) in families { - let font = self - .font_system - .get_font(font_id) - .ok_or_else(|| anyhow!("Could not load font"))?; - // TODO: figure out why this is causing fluent icons from loading - // if font.as_swash().charmap().map('m') == 0 { - // self.font_system.db_mut().remove_face(font.id()); - // continue; - // }; - - let font_id = FontId(self.fonts.len()); - font_ids.push(font_id); - self.fonts.push(font); - self.postscript_names_by_font_id - .insert(font_id, postscript_name); - } - Ok(font_ids) - } - - fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { - let width = self.fonts[font_id.0] - .as_swash() - .glyph_metrics(&[]) - .advance_width(glyph_id.0 as u16); - let height = self.fonts[font_id.0] - .as_swash() - .glyph_metrics(&[]) - .advance_height(glyph_id.0 as u16); - Ok(Size { width, height }) - } - - fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { - let glyph_id = self.fonts[font_id.0].as_swash().charmap().map(ch); - if glyph_id == 0 { - None - } else { - Some(GlyphId(glyph_id.into())) - } - } - - fn is_emoji(&self, font_id: FontId) -> bool { - // todo(windows): implement this correctly - self.postscript_names_by_font_id - .get(&font_id) - .map_or(false, |postscript_name| { - postscript_name == "AppleColorEmoji" - }) - } - - // todo(windows) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system - fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result> { - let font = &self.fonts[params.font_id.0]; - let font_system = &mut self.font_system; - let image = self - .swash_cache - .get_image( - font_system, - CacheKey::new( - font.id(), - params.glyph_id.0 as u16, - (params.font_size * params.scale_factor).into(), - (0.0, 0.0), - cosmic_text::CacheKeyFlags::empty(), - ) - .0, - ) - .clone() - .unwrap(); - 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, - ) -> Result<(Size, Vec)> { - if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { - Err(anyhow!("glyph bounds are empty")) - } else { - // todo(windows) handle subpixel variants - let bitmap_size = glyph_bounds.size; - let font = &self.fonts[params.font_id.0]; - let font_system = &mut self.font_system; - let image = self - .swash_cache - .get_image( - font_system, - CacheKey::new( - font.id(), - params.glyph_id.0 as u16, - (params.font_size * params.scale_factor).into(), - (0.0, 0.0), - cosmic_text::CacheKeyFlags::empty(), - ) - .0, - ) - .clone() - .unwrap(); - - Ok((bitmap_size, image.data)) - } - } - - // todo(windows) This is all a quick first pass, maybe we should be using cosmic_text::Buffer - #[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 { - // todo(windows) We need to check we are doing utf properly - let font = &self.fonts[run.font_id.0]; - let font = self.font_system.db().face(font.id()).unwrap(); - attrs_list.add_span( - offs..offs + run.len, - Attrs::new() - .family(Family::Name(&font.families.first().unwrap().0)) - .stretch(font.stretch) - .style(font.style) - .weight(font.weight), - ); - offs += run.len; - } - let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced); - let layout = line.layout( - &mut self.font_system, - font_size.0, - f32::MAX, // todo(windows) we don't have a width cause this should technically not be wrapped I believe - cosmic_text::Wrap::None, - None, - ); - let mut runs = Vec::new(); - // todo(windows) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering - let layout = layout.first().unwrap(); - for glyph in &layout.glyphs { - let font_id = glyph.font_id; - let font_id = FontId( - self.fonts - .iter() - .position(|font| font.id() == font_id) - .unwrap(), - ); - let mut glyphs = SmallVec::new(); - // todo(windows) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction - glyphs.push(ShapedGlyph { - id: GlyphId(glyph.glyph_id as u32), - position: point((glyph.x).into(), glyph.y.into()), - index: glyph.start, - is_emoji: self.is_emoji(font_id), - }); - runs.push(crate::ShapedRun { font_id, glyphs }); - } - LineLayout { - font_size, - width: layout.w.into(), - ascent: layout.max_ascent.into(), - descent: layout.max_descent.into(), - runs, - len: text.len(), - } - } -} - -impl From for Bounds { - fn from(rect: RectF) -> Self { - Bounds { - origin: point(rect.origin_x(), rect.origin_y()), - size: size(rect.width(), rect.height()), - } - } -} - -impl From for Bounds { - fn from(rect: RectI) -> Self { - Bounds { - origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())), - size: size(DevicePixels(rect.width()), DevicePixels(rect.height())), - } - } -} - -impl From for Size { - fn from(value: Vector2I) -> Self { - size(value.x().into(), value.y().into()) - } -} - -impl From for Bounds { - fn from(rect: RectI) -> Self { - Bounds { - origin: point(rect.origin_x(), rect.origin_y()), - size: size(rect.width(), rect.height()), - } - } -} - -impl From> for Vector2I { - fn from(size: Point) -> Self { - Vector2I::new(size.x as i32, size.y as i32) - } -} - -impl From for Size { - fn from(vec: Vector2F) -> Self { - size(vec.x(), vec.y()) - } -} - -impl From for cosmic_text::Weight { - fn from(value: FontWeight) -> Self { - cosmic_text::Weight(value.0 as u16) - } -} - -impl From for cosmic_text::Style { - fn from(style: FontStyle) -> Self { - match style { - FontStyle::Normal => cosmic_text::Style::Normal, - FontStyle::Italic => cosmic_text::Style::Italic, - FontStyle::Oblique => cosmic_text::Style::Oblique, - } - } -}