diff --git a/Cargo.lock b/Cargo.lock index 389e46cfc3..f9f28f0380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,7 +530,7 @@ dependencies = [ [[package]] name = "copa" -version = "0.1.11" +version = "0.1.12" dependencies = [ "arrayvec", "rio-proc-macros", @@ -553,7 +553,7 @@ dependencies = [ [[package]] name = "corcovado" -version = "0.1.11" +version = "0.1.12" dependencies = [ "bytes", "cfg-if 0.1.10", @@ -1598,6 +1598,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +dependencies = [ + "hashbrown", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2349,7 +2358,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "rio-backend" -version = "0.1.11" +version = "0.1.12" dependencies = [ "base64", "bitflags 2.6.0", @@ -2378,7 +2387,7 @@ dependencies = [ [[package]] name = "rio-proc-macros" -version = "0.1.11" +version = "0.1.12" dependencies = [ "proc-macro2", "quote", @@ -2386,7 +2395,7 @@ dependencies = [ [[package]] name = "rio-window" -version = "0.1.11" +version = "0.1.12" dependencies = [ "ahash", "atomic-waker", @@ -2437,7 +2446,7 @@ dependencies = [ [[package]] name = "rioterm" -version = "0.1.11" +version = "0.1.12" dependencies = [ "ahash", "bitflags 2.6.0", @@ -2768,7 +2777,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "sugarloaf" -version = "0.1.11" +version = "0.1.12" dependencies = [ "ab_glyph", "approx", @@ -2787,6 +2796,7 @@ dependencies = [ "js-sys", "linked-hash-map", "log 0.4.22", + "lru", "memmap2", "ordered-float", "png", @@ -2841,7 +2851,7 @@ dependencies = [ [[package]] name = "teletypewriter" -version = "0.1.11" +version = "0.1.12" dependencies = [ "corcovado", "dirs", @@ -3167,7 +3177,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wa" -version = "0.1.11" +version = "0.1.12" dependencies = [ "bitflags 2.6.0", "fnv", diff --git a/Cargo.toml b/Cargo.toml index 7181d33e8f..7210e67265 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.11" +version = "0.1.12" authors = ["Raphael Amorim "] edition = "2021" license = "MIT" @@ -27,14 +27,14 @@ documentation = "https://github.com/raphamorim/rio#readme" # Note: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-locations # Sugarloaf example uses path when used locally, but uses # version from crates.io when published. -sugarloaf = { path = "sugarloaf", version = "0.1.11" } -corcovado = { path = "corcovado", version = "0.1.11" } -rio-config = { path = "rio-config", version = "0.1.11" } -rio-proc-macros = { path = "rio-proc-macros", version = "0.1.11" } -copa = { path = "copa", default-features = true, version = "0.1.11" } -teletypewriter = { path = "teletypewriter", version = "0.1.11" } -rio-backend = { path = "rio-backend", version = "0.1.11" } -rio-window = { path = "rio-window", version = "0.1.11", default-features = false } +sugarloaf = { path = "sugarloaf", version = "0.1.12" } +corcovado = { path = "corcovado", version = "0.1.12" } +rio-config = { path = "rio-config", version = "0.1.12" } +rio-proc-macros = { path = "rio-proc-macros", version = "0.1.12" } +copa = { path = "copa", default-features = true, version = "0.1.12" } +teletypewriter = { path = "teletypewriter", version = "0.1.12" } +rio-backend = { path = "rio-backend", version = "0.1.12" } +rio-window = { path = "rio-window", version = "0.1.12", default-features = false } wa = { path = "wa", version = "0.1.7" } raw-window-handle = { version = "0.6.2", features = ["std"] } diff --git a/README.md b/README.md index fa0ab743d6..7a2c1e84a1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ If you use and like Rio, please consider sponsoring it: your support helps to co - Alacritty 🥇 - Since version 0.0.22, Sugarloaf ported glyph-brush code which was originally written by @alexheretic and licensed under Apache-2.0 license 🥇 -- DepartureMono font https://departuremono.com/. - Components text render was originally from https://github.com/hecrj/wgpu_glyph - The legacy Rio logo was made using _Adobe Sketchbook_ on iPad (between versions 0.0.1 between 0.0.18). - WA was built originally from a fork from [Macroquad](https://github.com/not-fl3/macroquad) which is licensed under MIT license. diff --git a/docs/docs/config/fonts.md b/docs/docs/config/fonts.md index fd21e7d19f..0b55cb4970 100644 --- a/docs/docs/config/fonts.md +++ b/docs/docs/config/fonts.md @@ -15,6 +15,8 @@ You can also set family on root to overwrite all fonts. family = "cascadiacode" ``` +## Extra fonts + You can also specify extra fonts to load: ```toml @@ -22,6 +24,8 @@ You can also specify extra fonts to load: extras = [{ family = "Microsoft JhengHei" }] ``` +## Font features + In case you want to specify any font feature: ```toml @@ -31,7 +35,7 @@ features = ["ss02", "ss03", "ss05", "ss19"] Note: Font features do not have support to live reload on configuration, so to reflect your changes, you will need to close and reopen Rio. ---- +## Default configuration The font configuration default: @@ -59,4 +63,31 @@ weight = 400 family = "cascadiacode" style = "italic" weight = 800 +``` + +## Emojis + +You can also specify which emoji font you would like to use, by default will be loaded a built-in Twemoji color by Mozilla. + +In case you would like to change: + +```toml +# Apple +# [fonts.emoji] +# family = "Apple Color Emoji" + +# In case you have Noto Color Emoji installed +# [fonts.emoji] +# family = "Noto Color Emoji" +``` + +## User interface + +You can specify user interface font on Rio. + +Note: `fonts.ui` does not have live reload configuration update, you need to close and open Rio again. + +```toml +[fonts.ui] +family = "Departure Mono" ``` \ No newline at end of file diff --git a/docs/docs/config/renderer.md b/docs/docs/config/renderer.md index 7d55ad41c7..8e960acaa7 100644 --- a/docs/docs/config/renderer.md +++ b/docs/docs/config/renderer.md @@ -18,7 +18,7 @@ language: 'en' - `disable-unfocused-render` - This property disable renderer processes while Rio is unfocused. -- `max-fps` - Limits the maximum number of frames per second that rio terminal will attempt to draw. If you set as `0` then this limit will be ignored. +- `max-fps` - Limits the maximum number of frames per second that rio terminal will attempt to draw. If you set as `0` then this limit will be ignored. The default on MacOS is 120 and all other platforms is 60. Example: diff --git a/docs/docs/install/macos.md b/docs/docs/install/macos.md index 4b90490d6e..830bedfce1 100644 --- a/docs/docs/install/macos.md +++ b/docs/docs/install/macos.md @@ -5,7 +5,7 @@ language: 'en' You can download Rio terminal application for macOS platform: -- [Download Rio for MacOS v0.1.10](https://github.com/raphamorim/rio/releases/download/v0.1.10/Rio-v0.1.10.dmg) +- [Download Rio for MacOS v0.1.12](https://github.com/raphamorim/rio/releases/download/v0.1.12/Rio-v0.1.12.dmg) Alternatively you can install Rio through [Homebrew](https://brew.sh/)... diff --git a/docs/docs/install/windows.md b/docs/docs/install/windows.md index bdd430890a..b9d2a0834e 100644 --- a/docs/docs/install/windows.md +++ b/docs/docs/install/windows.md @@ -7,8 +7,8 @@ Note: Rio is only available for Windows 10 or later. Prebuilt binaries for Windows: -- [Download Microsoft installer](https://github.com/raphamorim/rio/releases/download/v0.1.10/Rio-installer.msi) -- [Download Microsoft executable](https://github.com/raphamorim/rio/releases/download/v0.1.10/Rio-portable.exe) +- [Download Microsoft installer](https://github.com/raphamorim/rio/releases/download/v0.1.12/Rio-installer.msi) +- [Download Microsoft executable](https://github.com/raphamorim/rio/releases/download/v0.1.12/Rio-portable.exe) - [Using Chocolatey package manager](https://community.chocolatey.org/packages/rio-terminal) ```sh diff --git a/docs/docs/releases.md b/docs/docs/releases.md index e5bc35e5da..a1c6011080 100644 --- a/docs/docs/releases.md +++ b/docs/docs/releases.md @@ -9,11 +9,40 @@ language: 'en' -- Introduce: `renderer.max-fps` (default is 60). + +- Implement LRU to cache on layout and draw methods. +- Reenable set subtitle on MacOS native tabs. + +## 0.1.12 + +- Introduce: `renderer.max-fps`. - Fix: Cursor making text with ligatures hidden. - Fix: Underline cursor not working. - Fix: sixel: Text doesn't overwrite sixels [#636](https://github.com/raphamorim/rio/issues/636). - Initial support to Sixel protocol. +- Support to `fonts.emoji`. You can also specify which emoji font you would like to use, by default will be loaded a built-in Twemoji color by Mozilla. + +In case you would like to change: + +```toml +# Apple +# [fonts.emoji] +# family = "Apple Color Emoji" + +# In case you have Noto Color Emoji installed +# [fonts.emoji] +# family = "Noto Color Emoji" +``` + +- Support to `fonts.ui`. You can specify user interface font on Rio. + +Note: `fonts.ui` does not have live reload configuration update, you need to close and open Rio again. + +```toml +[fonts.ui] +family = "Departure Mono" +``` + - **breaking:** Revamp the cursor configuration Before: diff --git a/frontends/rioterm/src/context.rs b/frontends/rioterm/src/context.rs index 9e54b22870..cf4cf11804 100644 --- a/frontends/rioterm/src/context.rs +++ b/frontends/rioterm/src/context.rs @@ -535,8 +535,17 @@ impl ContextManager { format!("{} ({})", terminal_title, program) }; - self.event_proxy - .send_event(RioEvent::Title(window_title), self.window_id); + if cfg!(target_os = "macos") { + self.event_proxy.send_event( + RioEvent::TitleWithSubtitle(window_title, path.clone()), + self.window_id, + ); + } else { + self.event_proxy.send_event( + RioEvent::Title(window_title), + self.window_id, + ); + } } id = diff --git a/misc/osx/Rio.app/Contents/Info.plist b/misc/osx/Rio.app/Contents/Info.plist index 2c56c75b7e..907ce0484f 100644 --- a/misc/osx/Rio.app/Contents/Info.plist +++ b/misc/osx/Rio.app/Contents/Info.plist @@ -85,7 +85,7 @@ CFBundleIconFile rio.icns CFBundleShortVersionString - 0.1.11 + 0.1.12 CFBundleVersion 20230528.115631 CFBundleURLTypes diff --git a/misc/windows/rio.wxs b/misc/windows/rio.wxs index a432250d9d..9b2d285bb2 100644 --- a/misc/windows/rio.wxs +++ b/misc/windows/rio.wxs @@ -4,7 +4,7 @@ UpgradeCode="87c21c74-dbd5-4584-89d5-46d9cd0c40a8" Language="1033" Codepage="1252" - Version="0.1.11" + Version="0.1.12" Manufacturer="Raphael Amorim" InstallerVersion="200"> diff --git a/rio-backend/src/config/defaults.rs b/rio-backend/src/config/defaults.rs index 7b5fc02a52..fac6745ae0 100644 --- a/rio-backend/src/config/defaults.rs +++ b/rio-backend/src/config/defaults.rs @@ -12,7 +12,11 @@ pub fn default_line_height() -> f32 { #[inline] pub fn default_max_fps() -> u64 { - 120 + if cfg!(target_os = "macos") { + 120 + } else { + 60 + } } #[inline] diff --git a/sugarloaf/Cargo.toml b/sugarloaf/Cargo.toml index 987f9d03ef..e77e0fb2f0 100644 --- a/sugarloaf/Cargo.toml +++ b/sugarloaf/Cargo.toml @@ -45,6 +45,7 @@ ab_glyph = "0.2.28" linked-hash-map = "0.5.6" xi-unicode = "0.3.0" approx = "0.5.1" +lru = "0.12.4" skrifa = "0.20.0" yazi = { version = "0.1.6", optional = true } zeno = { version = "0.2.2", optional = true, default-features = false } diff --git a/sugarloaf/examples/text.rs b/sugarloaf/examples/text.rs index c16ca3d81f..36b2b17a2e 100644 --- a/sugarloaf/examples/text.rs +++ b/sugarloaf/examples/text.rs @@ -133,7 +133,7 @@ impl ApplicationHandler for Application { ); content.finish_line(); content.add_text( - "㏑¼", + "│㏑¼", FragmentStyle { color: [0.0, 0.0, 0.0, 1.0], background_color: Some([1.0, 1.0, 1.0, 1.0]), @@ -152,7 +152,7 @@ impl ApplicationHandler for Application { ); content.finish_line(); content.add_text( - " regular -> ", + "│regular -> ", FragmentStyle { decoration: Some(FragmentStyleDecoration::Underline( UnderlineInfo { @@ -202,7 +202,7 @@ impl ApplicationHandler for Application { }, ); content.add_text( - "curly", + "│curly", FragmentStyle { decoration: Some(FragmentStyleDecoration::Underline( UnderlineInfo { @@ -219,7 +219,7 @@ impl ApplicationHandler for Application { ); content.finish_line(); content.add_text( - "dashed", + "│dashed", FragmentStyle { decoration: Some(FragmentStyleDecoration::Underline( UnderlineInfo { @@ -259,6 +259,15 @@ impl ApplicationHandler for Application { ..FragmentStyle::default() }, ); + content.finish_line(); + content.add_text( + "│ \u{E0B6}Hello There!\u{e0b4}", + FragmentStyle { + color: [1.0, 1.0, 1.0, 1.0], + background_color: Some([0.5, 0.5, 1.0, 1.0]), + ..FragmentStyle::default() + }, + ); sugarloaf.set_content(content.build()); sugarloaf.render(); event_loop.set_control_flow(ControlFlow::Wait); diff --git a/sugarloaf/src/components/rich_text/image_cache/glyph.rs b/sugarloaf/src/components/rich_text/image_cache/glyph.rs index 9fd0216d02..ac4bb1aa56 100644 --- a/sugarloaf/src/components/rich_text/image_cache/glyph.rs +++ b/sugarloaf/src/components/rich_text/image_cache/glyph.rs @@ -21,6 +21,7 @@ pub struct GlyphCache { scx: ScaleContext, fonts: FxHashMap, img: GlyphImage, + max_height: u16, } impl GlyphCache { @@ -29,6 +30,7 @@ impl GlyphCache { scx: ScaleContext::new(), fonts: FxHashMap::default(), img: GlyphImage::new(), + max_height: 0, } } @@ -55,11 +57,17 @@ impl GlyphCache { entry, images, scaler, + max_height: &self.max_height, scaled_image: &mut self.img, quant_size, } } + #[inline] + pub fn set_max_height(&mut self, max_height: u16) { + self.max_height = max_height; + } + // pub fn prune(&mut self, images: &mut ImageCache) { // self.fonts.retain(|_, entry| { // for glyph in &entry.glyphs { @@ -68,14 +76,6 @@ impl GlyphCache { // false // }); // } - - #[allow(unused)] - pub fn clear_evicted(&mut self, images: &mut ImageCache) { - self.fonts.retain(|_, entry| { - entry.glyphs.retain(|_, g| images.is_valid(g.image)); - !entry.glyphs.is_empty() - }); - } } fn get_entry<'a>( @@ -103,6 +103,7 @@ pub struct GlyphCacheSession<'a> { scaler: Scaler<'a>, scaled_image: &'a mut GlyphImage, quant_size: u16, + max_height: &'a u16, } impl<'a> GlyphCacheSession<'a> { @@ -149,11 +150,33 @@ impl<'a> GlyphCacheSession<'a> { data: ImageData::Borrowed(&self.scaled_image.data), }; let image = self.images.allocate(req)?; + + let mut top = p.top; + let mut height = h; + + // If dimension is None it means that we are running + // for the first time and in this case, we will obtain + // what the next glyph entries should respect in terms of + // top and height values + // + // e.g: Placement { left: 11, top: 42, width: 8, height: 50 } + // + // The calculation is made based on max_height + // If the rect max height is 50 and the glyph height is 68 + // and 48 top, then (68 - 50 = 18) height as difference and + // apply it to the top (bigger the top == up ^). + if self.max_height > &0 && &h > self.max_height { + let difference = h - self.max_height; + + top -= difference as i32; + height = *self.max_height; + } + let entry = GlyphEntry { left: p.left, - top: p.top, + top, width: w, - height: h, + height, image, is_bitmap: self.scaled_image.content == Content::Color, }; diff --git a/sugarloaf/src/components/rich_text/mod.rs b/sugarloaf/src/components/rich_text/mod.rs index 3990f512b1..0004b953fc 100644 --- a/sugarloaf/src/components/rich_text/mod.rs +++ b/sugarloaf/src/components/rich_text/mod.rs @@ -12,7 +12,8 @@ use crate::layout::SugarDimensions; use crate::sugarloaf::graphics::GraphicRenderRequest; use crate::Graphics; use compositor::{CachedRun, Compositor, DisplayList, Rect, Vertex}; -use rustc_hash::FxHashMap; +use lru::LruCache; +use std::num::NonZeroUsize; use std::{borrow::Cow, mem}; use text::{Glyph, TextRunStyle}; use wgpu::util::DeviceExt; @@ -243,7 +244,7 @@ impl RichTextBrush { images, textures_version: 0, glyphs: GlyphCache::new(), - draw_layout_cache: DrawLayoutCache::default(), + draw_layout_cache: DrawLayoutCache::new(), dlist, transform, pipeline, @@ -291,7 +292,6 @@ impl RichTextBrush { &state.current.layout.dimensions, graphics, ); - self.draw_layout_cache.clear_on_demand(); self.dlist.clear(); self.images.process_atlases(context); @@ -420,27 +420,25 @@ impl RichTextBrush { } } -#[derive(Default)] struct DrawLayoutCache { - inner: FxHashMap>, + inner: LruCache>, } impl DrawLayoutCache { - #[inline] - fn get(&self, hash: &u64) -> Option<&Vec> { - self.inner.get(hash) + fn new() -> Self { + Self { + inner: LruCache::new(NonZeroUsize::new(128).unwrap()), + } } #[inline] - fn insert(&mut self, hash: u64, data: Vec) { - self.inner.insert(hash, data); + fn get(&mut self, hash: &u64) -> Option<&Vec> { + self.inner.get(hash) } #[inline] - fn clear_on_demand(&mut self) { - if self.inner.len() > 640 { - self.inner.clear(); - } + fn put(&mut self, hash: u64, data: Vec) { + self.inner.put(hash, data); } #[inline] @@ -474,6 +472,8 @@ fn draw_layout( } } + glyphs_cache.set_max_height(rect.height as u16); + let mut session = glyphs_cache.session( image_cache, font_library[current_font].as_ref(), @@ -505,7 +505,6 @@ fn draw_layout( let char_width = run.char_width(); let mut cached_run = CachedRun::new(char_width); let font = *run.font(); - let char_width = run.char_width(); let run_x = px; glyphs.clear(); @@ -584,7 +583,7 @@ fn draw_layout( } if !cached_line_runs.is_empty() { - draw_layout_cache.insert(hash, cached_line_runs); + draw_layout_cache.put(hash, cached_line_runs); } } diff --git a/sugarloaf/src/font/constants.rs b/sugarloaf/src/font/constants.rs index 14a3d5dbc4..efabf8f91e 100644 --- a/sugarloaf/src/font/constants.rs +++ b/sugarloaf/src/font/constants.rs @@ -60,5 +60,4 @@ pub const FONT_CASCADIAMONO_SEMI_LIGHT_ITALIC: &[u8] = pub const FONT_SYMBOLS_NERD_FONT_MONO: &[u8] = font!("./resources/SymbolsNerdFontMono/SymbolsNerdFontMono-Regular.ttf"); -pub const FONT_DEPARTURE_MONO: &[u8] = - font!("./resources/DepartureMono/DepartureMono-Regular.otf"); +pub const FONT_TWEMOJI_EMOJI: &[u8] = font!("./resources/Twemoji/Twemoji.Mozilla.ttf"); diff --git a/sugarloaf/src/font/fallbacks/mod.rs b/sugarloaf/src/font/fallbacks/mod.rs index f50ae8b958..4137e71017 100644 --- a/sugarloaf/src/font/fallbacks/mod.rs +++ b/sugarloaf/src/font/fallbacks/mod.rs @@ -5,7 +5,8 @@ pub fn external_fallbacks() -> Vec { String::from("Menlo"), String::from("Geneva"), String::from("Arial Unicode MS"), - String::from("Apple Color Emoji"), + // String::from("Noto Emoji"), + // String::from("Noto Color Emoji"), ] } @@ -15,7 +16,7 @@ pub fn external_fallbacks() -> Vec { // Lucida Sans Unicode // Microsoft JhengHei String::from("Segoe UI"), - String::from("Segoe UI Emoji"), + // String::from("Segoe UI Emoji"), String::from("Segoe UI Symbol"), String::from("Segoe UI Historic"), ] @@ -26,6 +27,6 @@ pub fn external_fallbacks() -> Vec { vec![ String::from("Noto Sans"), String::from("FreeSans"), - String::from("Noto Color Emoji"), + // String::from("Noto Color Emoji"), ] } diff --git a/sugarloaf/src/font/fonts.rs b/sugarloaf/src/font/fonts.rs index 1a581d9a22..30bc8b3c5b 100644 --- a/sugarloaf/src/font/fonts.rs +++ b/sugarloaf/src/font/fonts.rs @@ -84,6 +84,10 @@ pub struct SugarloafFonts { pub bold_italic: SugarloafFont, #[serde(default = "default_font_italic")] pub italic: SugarloafFont, + #[serde(default = "Option::default")] + pub ui: Option, + #[serde(default = "Option::default")] + pub emoji: Option, #[serde(default = "Vec::default")] pub extras: Vec, } @@ -94,6 +98,8 @@ impl Default for SugarloafFonts { features: None, size: default_font_size(), family: None, + emoji: None, + ui: None, regular: default_font_regular(), bold: default_font_bold(), bold_italic: default_font_bold_italic(), diff --git a/sugarloaf/src/font/mod.rs b/sugarloaf/src/font/mod.rs index ff0a2d926a..44c4c7472e 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -9,7 +9,7 @@ pub const FONT_ID_REGULAR: usize = 0; use crate::font::constants::*; use crate::font_introspector::proxy::CharmapProxy; use crate::font_introspector::text::cluster::{CharCluster, Status}; -use crate::font_introspector::{Attributes, CacheKey, Charmap, FontRef, Synthesis}; +use crate::font_introspector::{Attributes, CacheKey, FontRef, Synthesis}; use crate::layout::FragmentStyle; use crate::SugarloafErrors; use ab_glyph::FontArc; @@ -52,7 +52,6 @@ pub fn lookup_for_font_match( for (current_font_id, font) in library.inner.iter().enumerate() { let (font, font_ref) = match font { FontSource::Data(font_data) => (font_data, font_data.as_ref()), - FontSource::Extension(_) => (&library.standard, library.standard.as_ref()), FontSource::Standard => (&library.standard, library.standard.as_ref()), }; @@ -71,7 +70,7 @@ pub fn lookup_for_font_match( } } - let charmap = font.charmap_proxy().materialize(&font_ref); + let charmap = font.charmap_proxy.materialize(&font_ref); let status = cluster.map(|ch| charmap.map(ch)); if status != Status::Discard { *synth = library[current_font_id].synth; @@ -96,7 +95,6 @@ impl FontContext { cluster: &mut CharCluster, synth: &mut Synthesis, library: &FontLibraryData, - fonts_to_load: &mut Vec<(usize, PathBuf)>, fragment_style: &FragmentStyle, ) -> Option { let is_italic = fragment_style.font_attrs.style() == Style::Italic; @@ -128,7 +126,7 @@ impl FontContext { if let Some(cached_font_id) = self.cache.get(&cache_key) { let cached_font_id = *cached_font_id; let charmap = library[cached_font_id] - .charmap_proxy() + .charmap_proxy .materialize(&library[cached_font_id].as_ref()); let status = cluster.map(|ch| charmap.map(ch)); if status != Status::Discard { @@ -149,39 +147,6 @@ impl FontContext { return Some(found_font_id); } - let mut emoji_font_id = None; - if cluster.info().is_emoji() { - for (id, font_source) in library.inner.iter().enumerate() { - match font_source { - FontSource::Data(font_data) => { - if font_data.is_emoji { - emoji_font_id = Some(id); - break; - } - } - FontSource::Extension(font_data_extension) => { - // In this case we will actually need to load - if font_data_extension.is_emoji { - emoji_font_id = Some(id); - fonts_to_load.push((id, font_data_extension.path.clone())); - break; - } - } - FontSource::Standard => {} - } - } - } - - if let Some(emoji_font_id) = emoji_font_id { - let charmap = library[emoji_font_id] - .charmap_proxy() - .materialize(&library[emoji_font_id].as_ref()); - let status = cluster.map(|ch| charmap.map(ch)); - if status != Status::Discard { - *synth = library[emoji_font_id].synth; - } - } - Some(0) } } @@ -226,17 +191,10 @@ impl Default for FontLibrary { pub enum FontSource { Standard, Data(FontData), - Extension(FontDataExtension), -} - -#[derive(Clone)] -pub struct FontDataExtension { - path: PathBuf, - is_emoji: bool, } pub struct FontLibraryData { - pub main: FontArc, + pub ui: FontArc, // Standard is fallback for everything, it is also the inner number 0 pub standard: FontData, pub inner: Vec, @@ -249,7 +207,7 @@ impl Default for FontLibraryData { db.load_system_fonts(); Self { db, - main: FontArc::try_from_slice(FONT_DEPARTURE_MONO).unwrap(), + ui: FontArc::try_from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), standard: FontData::from_slice(FONT_CASCADIAMONO_REGULAR).unwrap(), inner: vec![], } @@ -347,31 +305,40 @@ impl FontLibraryData { } for fallback in fallbacks::external_fallbacks() { - let is_emoji = fallback.to_lowercase().contains("emoji"); - - if is_emoji { - if let Some(path) = find_font_path(&self.db, fallback) { - self.inner.push(FontSource::Extension(FontDataExtension { - path, - is_emoji, - })); + match find_font( + &self.db, + SugarloafFont { + family: fallback, + ..SugarloafFont::default() + }, + ) { + FindResult::Found(data) => { + self.inner.push(FontSource::Data(data)); } - } else { - match find_font( - &self.db, - SugarloafFont { - family: fallback, - ..SugarloafFont::default() - }, - ) { - FindResult::Found(data) => { - self.inner.push(FontSource::Data(data)); - } - FindResult::NotFound(_spec) => { - // Fallback should not add errors + FindResult::NotFound(spec) => { + // Fallback should not add errors + log::info!("{:?}", spec); + } + } + } + + if let Some(emoji_font) = spec.emoji { + match find_font(&self.db, emoji_font) { + FindResult::Found(data) => { + self.inner.push(FontSource::Data(data)); + } + FindResult::NotFound(spec) => { + self.inner + .push(FontSource::Data(load_fallback_from_memory(&spec))); + if !spec.is_default_family() { + fonts_not_fount.push(spec); } } } + } else { + self.inner.push(FontSource::Data( + FontData::from_slice(FONT_TWEMOJI_EMOJI).unwrap(), + )); } for extra_font in spec.extras { @@ -396,6 +363,17 @@ impl FontLibraryData { FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO).unwrap(), )); + if let Some(ui_spec) = spec.ui { + match find_font(&self.db, ui_spec) { + FindResult::Found(data) => { + self.ui = FontArc::try_from_vec(data.data.to_vec()).unwrap(); + } + FindResult::NotFound(spec) => { + fonts_not_fount.push(spec); + } + } + } + fonts_not_fount } @@ -414,7 +392,6 @@ impl Index for FontLibraryData { fn index(&self, index: usize) -> &Self::Output { match &self.inner[index] { FontSource::Data(font_ref) => font_ref, - FontSource::Extension(_) => &self.standard, FontSource::Standard => &self.standard, } } @@ -424,7 +401,6 @@ impl IndexMut for FontLibraryData { fn index_mut(&mut self, index: usize) -> &mut FontData { match &mut self.inner[index] { FontSource::Data(font_ref) => font_ref, - FontSource::Extension(_) => &mut self.standard, FontSource::Standard => &mut self.standard, } } @@ -478,7 +454,7 @@ pub struct FontData { offset: u32, // Cache key pub key: CacheKey, - charmap_proxy: CharmapProxy, + pub charmap_proxy: CharmapProxy, pub weight: crate::font_introspector::Weight, pub style: crate::font_introspector::Style, pub stretch: crate::font_introspector::Stretch, @@ -560,16 +536,6 @@ impl FontData { self.as_ref().attributes() } - #[inline] - pub fn charmap(&self) -> Charmap { - self.as_ref().charmap() - } - - #[inline] - pub fn charmap_proxy(&self) -> CharmapProxy { - self.charmap_proxy - } - // Create the transient font reference for accessing this crate's // functionality. #[inline] @@ -699,6 +665,7 @@ fn load_fallback_from_memory(font_spec: &SugarloafFont) -> FontData { FontData::from_slice(font_to_load).unwrap() } +#[allow(dead_code)] fn find_font_path( db: &crate::font::loader::Database, font_family: String, diff --git a/sugarloaf/src/font/resources/DepartureMono/DepartureMono-Regular.otf b/sugarloaf/src/font/resources/DepartureMono/DepartureMono-Regular.otf deleted file mode 100644 index 85d2ee9ebc..0000000000 Binary files a/sugarloaf/src/font/resources/DepartureMono/DepartureMono-Regular.otf and /dev/null differ diff --git a/sugarloaf/src/font/resources/DepartureMono/LICENSE b/sugarloaf/src/font/resources/DepartureMono/LICENSE deleted file mode 100644 index de52476183..0000000000 --- a/sugarloaf/src/font/resources/DepartureMono/LICENSE +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2022–2024 Helena Zhang (helenazhang.com). - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -https://openfontlicense.org - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/sugarloaf/src/font/resources/DepartureMono/README.md b/sugarloaf/src/font/resources/DepartureMono/README.md deleted file mode 100644 index ef03141c09..0000000000 --- a/sugarloaf/src/font/resources/DepartureMono/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# HELLO! - -Thanks for trying Departure Mono (departuremono.com), licensed under the SIL OFL. Send your questions and suggestions to hello@helenazhang.com. Enjoy! - -— Helena Zhang (helenazhang.com) - -# FONT INFORMATION - -Version 1.346 supports 763 total glyphs, including: - -- Basic Latin, Latin-1, Latin Extended-A, and most Latinate languages -- Basic Greek -- Simple box-drawing characters and selected symbols - -# USAGE - -For pixel-perfect results, set the font size to increments of 11px. - -If you're okay with folding to half pixels, try -.5px tracking at 11px (-1px at 22px, etc.) for a more comfortable read. \ No newline at end of file diff --git a/sugarloaf/src/font/resources/Twemoji/LICENSE.md b/sugarloaf/src/font/resources/Twemoji/LICENSE.md new file mode 100644 index 0000000000..b932ae8032 --- /dev/null +++ b/sugarloaf/src/font/resources/Twemoji/LICENSE.md @@ -0,0 +1,32 @@ +## License for the Visual Design + +The Emoji art in the twe-svg.zip archive comes from [Twemoji](https://twitter.github.io/twemoji), +and is used and redistributed under the CC-BY-4.0 [license terms](https://github.com/twitter/twemoji#license) +offered by the Twemoji project. + +### Creative Commons Attribution 4.0 International (CC BY 4.0) +https://creativecommons.org/licenses/by/4.0/legalcode +or for the human readable summary: https://creativecommons.org/licenses/by/4.0/ + + +#### You are free to: +**Share** — copy and redistribute the material in any medium or format + +**Adapt** — remix, transform, and build upon the material for any purpose, even commercially. + +The licensor cannot revoke these freedoms as long as you follow the license terms. + + +#### Under the following terms: +**Attribution** — You must give appropriate credit, provide a link to the license, +and indicate if changes were made. +You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. + +**No additional restrictions** — You may not apply legal terms or **technological measures** +that legally restrict others from doing anything the license permits. + +#### Notices: +You do not have to comply with the license for elements of the material in the public domain +or where your use is permitted by an applicable exception or limitation. No warranties are given. +The license may not give you all of the permissions necessary for your intended use. +For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. diff --git a/sugarloaf/src/font/resources/Twemoji/Twemoji.Mozilla.ttf b/sugarloaf/src/font/resources/Twemoji/Twemoji.Mozilla.ttf new file mode 100644 index 0000000000..9f45178e9d Binary files /dev/null and b/sugarloaf/src/font/resources/Twemoji/Twemoji.Mozilla.ttf differ diff --git a/sugarloaf/src/layout/builder.rs b/sugarloaf/src/layout/builder.rs index bdcb658465..7fd484f993 100644 --- a/sugarloaf/src/layout/builder.rs +++ b/sugarloaf/src/layout/builder.rs @@ -18,23 +18,23 @@ use crate::font_introspector::text::cluster::{CharCluster, CharInfo, Parser, Tok use crate::font_introspector::text::{analyze, Script}; use crate::font_introspector::{Setting, Synthesis}; use crate::layout::render_data::{RenderData, RunCacheEntry}; -use rustc_hash::FxHashMap; -use std::path::PathBuf; +use lru::LruCache; +use std::num::NonZeroUsize; pub struct RunCache { - inner: FxHashMap, + inner: LruCache, } impl RunCache { #[inline] fn new() -> Self { Self { - inner: FxHashMap::default(), + inner: LruCache::new(NonZeroUsize::new(256).unwrap()), } } #[inline] - fn insert(&mut self, line_hash: u64, data: RunCacheEntry) { + fn put(&mut self, line_hash: u64, data: RunCacheEntry) { if data.runs.is_empty() { return; } @@ -42,17 +42,7 @@ impl RunCache { if let Some(line) = self.inner.get_mut(&line_hash) { *line = data; } else { - self.inner.insert(line_hash, data); - } - } - - #[inline] - fn clear_on_max_capacity(&mut self) -> bool { - if self.inner.len() > 512 { - self.inner.clear(); - true - } else { - false + self.inner.put(line_hash, data); } } } @@ -65,9 +55,8 @@ pub struct LayoutContext { scx: ShapeContext, state: BuilderState, cache: RunCache, - cache_analysis: FxHashMap>, + cache_analysis: LruCache>, shaper_cache: ShaperCache, - fonts_to_load: Vec<(usize, PathBuf)>, } impl LayoutContext { @@ -79,10 +68,9 @@ impl LayoutContext { scx: ShapeContext::new(), state: BuilderState::new(), cache: RunCache::new(), - shaper_cache: ShaperCache::default(), - fonts_to_load: vec![], + shaper_cache: ShaperCache::new(), font_features: vec![], - cache_analysis: FxHashMap::default(), + cache_analysis: LruCache::new(NonZeroUsize::new(256).unwrap()), } } @@ -111,6 +99,7 @@ impl LayoutContext { if prev_font_size != self.state.font_size { self.cache.inner.clear(); + self.shaper_cache.clear(); } ParagraphBuilder { fcx: &mut self.fcx, @@ -124,7 +113,6 @@ impl LayoutContext { cache: &mut self.cache, shaper_cache: &mut self.shaper_cache, cache_analysis: &mut self.cache_analysis, - fonts_to_load: &mut self.fonts_to_load, } } @@ -144,8 +132,7 @@ pub struct ParagraphBuilder<'a> { last_offset: u32, cache: &'a mut RunCache, shaper_cache: &'a mut ShaperCache, - cache_analysis: &'a mut FxHashMap>, - fonts_to_load: &'a mut Vec<(usize, PathBuf)>, + cache_analysis: &'a mut LruCache>, } impl<'a> ParagraphBuilder<'a> { @@ -252,9 +239,6 @@ impl<'a> ParagraphBuilder<'a> { } fn resolve(&mut self, render_data: &mut RenderData) { - // Cache needs to be cleaned before build lines - let should_clear = self.cache.clear_on_max_capacity(); - // let start = std::time::Instant::now(); for line_number in 0..self.s.lines.len() { // In case should render only requested lines @@ -276,7 +260,7 @@ impl<'a> ParagraphBuilder<'a> { line.text.info.push(char_info); cache.push(char_info); } - self.cache_analysis.insert(content_key, cache); + self.cache_analysis.put(content_key, cache); } self.itemize(line_number); @@ -287,29 +271,6 @@ impl<'a> ParagraphBuilder<'a> { } // let duration = start.elapsed(); // println!("Time elapsed in resolve is: {:?}", duration); - - // In this case, we actually have found fonts that have not been loaded yet - // We need to load and then restart the whole resolve function again - if !self.fonts_to_load.is_empty() { - { - let font_library = { &mut self.fonts.inner.write().unwrap() }; - while let Some(font_to_load) = self.fonts_to_load.pop() { - let (font_id, path) = font_to_load; - font_library.upsert(font_id, path); - } - } - - *render_data = RenderData::default(); - self.cache.inner.clear(); - for line_number in 0..self.s.lines.len() { - self.shape(render_data, line_number); - } - }; - - if should_clear { - self.shaper_cache.clear(); - self.cache_analysis.clear(); - } } #[inline] @@ -420,7 +381,6 @@ impl<'a> ParagraphBuilder<'a> { &mut char_cluster, &mut shape_state.synth, font_library, - self.fonts_to_load, &style, ); @@ -431,14 +391,12 @@ impl<'a> ParagraphBuilder<'a> { &mut shape_state, &mut parser, &mut char_cluster, - // dir, render_data, current_line, - self.fonts_to_load, self.shaper_cache, ) {} - self.cache.insert( + self.cache.put( self.s.lines[current_line].hash, render_data.last_cached_run.to_owned(), ); @@ -459,16 +417,23 @@ struct ShapeState<'a> { size: f32, } -#[derive(Default)] pub struct ShaperCache { - pub inner: FxHashMap>, + pub inner: LruCache>, stash: Vec, key: String, } impl ShaperCache { + pub fn new() -> Self { + ShaperCache { + inner: LruCache::new(NonZeroUsize::new(256).unwrap()), + stash: vec![], + key: String::new(), + } + } + #[inline] - pub fn shape_with(&self) -> Option<&Vec> { + pub fn shape_with(&mut self) -> Option<&Vec> { if self.key.is_empty() { return None; } @@ -487,6 +452,7 @@ impl ShaperCache { pub fn clear(&mut self) { self.stash.clear(); self.key.clear(); + self.inner.clear(); } #[inline] @@ -497,7 +463,7 @@ impl ShaperCache { #[inline] pub fn finish(&mut self) { if !self.key.is_empty() && !self.stash.is_empty() { - self.inner.insert( + self.inner.put( std::mem::take(&mut self.key), std::mem::take(&mut self.stash), ); @@ -518,7 +484,6 @@ fn shape_clusters( cluster: &mut CharCluster, render_data: &mut RenderData, current_line: usize, - fonts_to_load: &mut Vec<(usize, PathBuf)>, shaper_cache: &mut ShaperCache, ) -> bool where @@ -556,8 +521,7 @@ where return false; } - let next_font = - fcx.map_cluster(cluster, &mut synth, fonts, fonts_to_load, state.span); + let next_font = fcx.map_cluster(cluster, &mut synth, fonts, state.span); if next_font != state.font_id || synth != state.synth { render_data.push_run( state.span, diff --git a/sugarloaf/src/layout/layout_data.rs b/sugarloaf/src/layout/layout_data.rs index 8958748aff..68f6727f5a 100644 --- a/sugarloaf/src/layout/layout_data.rs +++ b/sugarloaf/src/layout/layout_data.rs @@ -430,12 +430,12 @@ fn commit_line( hash: run_data.hash, ascent: run_data.ascent.round(), descent: run_data.descent.round(), - leading: (run_data.leading * 0.5).round() * 2., + leading: (run_data.leading).round() * 2., ..Default::default() }; - let above = (line.ascent + line.leading * 0.5).round(); - let below = (line.descent + line.leading * 0.5).round(); + let above = line.ascent; + let below = line.descent; line.baseline = *y + above; *y = line.baseline + below; diff --git a/sugarloaf/src/sugarloaf.rs b/sugarloaf/src/sugarloaf.rs index 89ade7f68d..e69fc50500 100644 --- a/sugarloaf/src/sugarloaf.rs +++ b/sugarloaf/src/sugarloaf.rs @@ -121,7 +121,7 @@ impl Sugarloaf<'_> { let ctx = Context::new(window, renderer); let text_brush = { - let data = { &font_library.inner.read().unwrap().main }; + let data = { &font_library.inner.read().unwrap().ui }; text::GlyphBrushBuilder::using_fonts(vec![data.to_owned()]) .build(&ctx.device, ctx.format) }; diff --git a/sugarloaf/src/sugarloaf/compositors/advanced.rs b/sugarloaf/src/sugarloaf/compositors/advanced.rs index e4aef11ab3..0522a0d8a6 100644 --- a/sugarloaf/src/sugarloaf/compositors/advanced.rs +++ b/sugarloaf/src/sugarloaf/compositors/advanced.rs @@ -62,7 +62,6 @@ impl Advanced { #[inline] pub fn update_layout(&mut self, tree: &SugarTree) { - // let start = std::time::Instant::now(); self.render_data = RenderData::default(); let mut lb = self @@ -74,19 +73,11 @@ impl Advanced { self.render_data .break_lines() .break_without_advance_or_alignment(); - - // let duration = start.elapsed(); - // println!(" - advanced::update_layout() is: {:?}", duration); } #[inline] pub fn calculate_dimensions(&mut self, tree: &SugarTree) { let mut content_builder = Content::builder(); - // content_builder.enter_span(&[ - // SpanStyle::FontId(0), - // SpanStyle::Size(tree.layout.font_size), - // // S::features(&[("dlig", 1).into(), ("hlig", 1).into()][..]), - // ]); content_builder.add_char(' ', FragmentStyle::default()); let mut lb = self