Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: support to character ranges, builtin_box_drawing and unicode rendering improvements #980

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
27 changes: 27 additions & 0 deletions docs/docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ The font configuration default:
[fonts]
size = 18
features = []
builtin-box-drawing = true
symbol-map = []

[fonts.regular]
family = "cascadiacode"
Expand All @@ -233,6 +235,10 @@ width = "Normal"
weight = 800
```

## fonts.builtin-box-drawing

When true, Rio will use a custom built-in font for box drawing characters `(Unicode points U+2500 - U+259F)`, legacy computing symbols `(U+1FB00 - U+1FB3B)`, and powerline symbols `(U+E0B0 - U+E0B3)`.

## fonts.family

Note: You can set different font families but Rio terminal
Expand Down Expand Up @@ -286,6 +292,27 @@ Enable or disable font hinting. It is enabled by default.
fonts.hinting = true
```

## fonts.symbol-map

Has no default values. Example values are shown below:

```toml
fonts.symbol-map = [
{ start = "E0C0", end = "E0C7", font-family = "PowerlineSymbols" }
]
```

Map the specified Unicode codepoints to a particular font. Useful if you need special rendering for some symbols, such as for Powerline. Avoids the need for patched fonts.

In case you would like to map many codepoints:

```toml
fonts.symbol-map = [
{ start = "E0A0", end = "E0A3", font-family = "PowerlineSymbols" },
{ start = "E0C0", end = "E0C7", font-family = "PowerlineSymbols" },
]
```

## fonts.ui

You can specify user interface font on Rio.
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ language: 'en'

## 0.2.8 (unreleased)

- Support to `.rpm` files! (thanks [@vedantmgoyal9](https://github.com/vedantmgoyal9) and [@caarlos0](https://github.com/caarlos0))
- OSC 7 Escape sequences to advise the terminal of the working directory.
- Use [GoReleaser](https://goreleaser.com) to build & release Rio ([#921](https://github.com/raphamorim/rio/pull/921)), thanks [@caarlos0](https://github.com/caarlos0) and [@vedantmgoyal9](https://github.com/vedantmgoyal9)
- Cache GSUB and GPOS features independently.
- Support to symbol map configuration: `fonts.symbol-map`.
- Support to builtin box drawing by using `fonts.builtin-box-drawing = true` (enabled by default).
- Fix issue whenever the first main font cannot be found.
- Updated `windows-sys` to `v0.59`.
- To match the corresponding changes in `windows-sys`, the `HWND`, `HMONITOR`, and `HMENU` types now alias to `*mut c_void` instead of `isize`.

Expand Down
1 change: 1 addition & 0 deletions frontends/rioterm/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ impl Renderer {
width = 2.0;
}
}

style.width = width;

self.font_cache.insert(
Expand Down
27 changes: 27 additions & 0 deletions rio-backend/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,4 +1041,31 @@ mod tests {
assert_eq!(result.colors.tabs_active, colors::defaults::tabs_active());
assert_eq!(result.colors.cursor, colors::defaults::cursor());
}

#[test]
fn test_symbol_map() {
fn unsafe_parse_unicode(input: &str) -> char {
let unicode = u32::from_str_radix(input, 16).unwrap();
char::from_u32(unicode).unwrap()
}

let result = create_temporary_config(
"symbol-map",
r#"
fonts.symbol-map = [
{ start = "E0C0", end = "E0C7", font-family = "PowerlineSymbols" }
]
"#,
);

assert!(result.fonts.symbol_map.is_some());
let symbol_map = result.fonts.symbol_map.unwrap();
assert_eq!(symbol_map.len(), 1);
assert_eq!(symbol_map[0].font_family, "PowerlineSymbols");
assert_eq!(symbol_map[0].start, "E0C0");
assert_eq!(symbol_map[0].end, "E0C7");

assert_eq!(unsafe_parse_unicode(&symbol_map[0].start), '\u{E0C0}');
assert_eq!(unsafe_parse_unicode(&symbol_map[0].end), '\u{E0C7}');
}
}
36 changes: 34 additions & 2 deletions sugarloaf/examples/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use sugarloaf::{
};

fn main() {
let width = 600.0;
let height = 400.0;
let width = 1200.0;
let height = 500.0;
let window_event_loop = rio_window::event_loop::EventLoop::new().unwrap();
let mut application = Application::new(&window_event_loop, width, height);
let _ = application.run(window_event_loop);
Expand Down Expand Up @@ -265,6 +265,38 @@ impl ApplicationHandler for Application {
..FragmentStyle::default()
},
)
.new_line()

/*
Box drawing alignment tests: █
╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
▝▀▘▙▄▟
*/

.add_text("Box drawing alignment tests: █", FragmentStyle::default())
.new_line()
.add_text("╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳", FragmentStyle::default())
.new_line()
.add_text("║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳", FragmentStyle::default())
.new_line()
.add_text("║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳", FragmentStyle::default())
.new_line()
.add_text("╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳", FragmentStyle::default())
.new_line()
.add_text("║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎", FragmentStyle::default())
.new_line()
.add_text("║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏", FragmentStyle::default())
.new_line()
.add_text("╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█", FragmentStyle::default())
.new_line()
.add_text(" ▝▀▘▙▄▟", FragmentStyle::default())
.build();

sugarloaf.set_objects(vec![Object::RichText(RichText {
Expand Down
18 changes: 16 additions & 2 deletions sugarloaf/src/font/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub fn default_font_size() -> f32 {
}

#[inline]
pub fn default_font_hinting() -> bool {
pub fn default_bool_true() -> bool {
true
}

Expand Down Expand Up @@ -105,11 +105,19 @@ pub fn default_font_bold_italic() -> SugarloafFont {
}
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SymbolMap {
pub start: String,
pub end: String,
#[serde(rename = "font-family")]
pub font_family: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct SugarloafFonts {
#[serde(default = "default_font_size")]
pub size: f32,
#[serde(default = "default_font_hinting")]
#[serde(default = "default_bool_true")]
pub hinting: bool,
#[serde(default = "Option::default")]
pub features: Option<Vec<String>>,
Expand All @@ -129,6 +137,10 @@ pub struct SugarloafFonts {
pub emoji: Option<SugarloafFont>,
#[serde(default = "Vec::default")]
pub extras: Vec<SugarloafFont>,
#[serde(default = "default_bool_true", rename = "builtin-box-drawing")]
pub builtin_box_drawing: bool,
#[serde(default = "Option::default", rename = "symbol-map")]
pub symbol_map: Option<Vec<SymbolMap>>,
}

impl Default for SugarloafFonts {
Expand All @@ -145,6 +157,8 @@ impl Default for SugarloafFonts {
bold_italic: default_font_bold_italic(),
italic: default_font_italic(),
extras: vec![],
builtin_box_drawing: true,
symbol_map: None,
}
}
}
140 changes: 79 additions & 61 deletions sugarloaf/src/font/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ use std::sync::Arc;

pub use crate::font_introspector::{Style, Weight};

// const POWERLINE_TRIANGLE_LTR: char = '\u{e0b0}';
// const POWERLINE_ARROW_LTR: char = '\u{e0b1}';
// const POWERLINE_TRIANGLE_RTL: char = '\u{e0b2}';
// const POWERLINE_ARROW_RTL: char = '\u{e0b3}';
// match (self.builtin_box_drawing, square_content) {
// // Box drawing characters and block elements.
// (true, '\u{2500}'..='\u{259f}' | '\u{1fb00}'..='\u{1fb3b}') => {
// style.font_id = 0;
// }

// // Powerline symbols: '','','',''
// (true, POWERLINE_TRIANGLE_LTR..=POWERLINE_ARROW_RTL) => {
// style.font_id = 0;
// }

// _ => {

pub fn lookup_for_font_match(
cluster: &mut CharCluster,
synth: &mut Synthesis,
Expand Down Expand Up @@ -267,10 +284,11 @@ impl FontLibraryData {
}
FindResult::NotFound(spec) => {
if !spec.is_default_family() {
fonts_not_fount.push(spec);
} else {
self.insert(load_fallback_from_memory(&spec));
fonts_not_fount.push(spec.to_owned());
}

// The first font should always have a fallback
self.insert(load_fallback_from_memory(&spec));
}
}

Expand Down Expand Up @@ -313,64 +331,64 @@ impl FontLibraryData {
}
}

for fallback in fallbacks::external_fallbacks() {
match find_font(
&db,
SugarloafFont {
family: fallback,
..SugarloafFont::default()
},
true,
false,
) {
FindResult::Found(data) => {
self.insert(data);
}
FindResult::NotFound(spec) => {
// Fallback should not add errors
tracing::info!("{:?}", spec);
}
}
}

if let Some(emoji_font) = spec.emoji {
match find_font(&db, emoji_font, true, true) {
FindResult::Found(data) => {
self.insert(data);
}
FindResult::NotFound(spec) => {
self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap());
if !spec.is_default_family() {
fonts_not_fount.push(spec);
}
}
}
} else {
self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap());
}

for extra_font in spec.extras {
match find_font(
&db,
SugarloafFont {
family: extra_font.family,
style: extra_font.style,
weight: extra_font.weight,
width: extra_font.width,
},
true,
true,
) {
FindResult::Found(data) => {
self.insert(data);
}
FindResult::NotFound(spec) => {
fonts_not_fount.push(spec);
}
}
}

self.insert(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO, false).unwrap());
// for fallback in fallbacks::external_fallbacks() {
// match find_font(
// &db,
// SugarloafFont {
// family: fallback,
// ..SugarloafFont::default()
// },
// true,
// false,
// ) {
// FindResult::Found(data) => {
// self.insert(data);
// }
// FindResult::NotFound(spec) => {
// // Fallback should not add errors
// tracing::info!("{:?}", spec);
// }
// }
// }

// if let Some(emoji_font) = spec.emoji {
// match find_font(&db, emoji_font, true, true) {
// FindResult::Found(data) => {
// self.insert(data);
// }
// FindResult::NotFound(spec) => {
// self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap());
// if !spec.is_default_family() {
// fonts_not_fount.push(spec);
// }
// }
// }
// } else {
// self.insert(FontData::from_slice(FONT_TWEMOJI_EMOJI, true).unwrap());
// }

// for extra_font in spec.extras {
// match find_font(
// &db,
// SugarloafFont {
// family: extra_font.family,
// style: extra_font.style,
// weight: extra_font.weight,
// width: extra_font.width,
// },
// true,
// true,
// ) {
// FindResult::Found(data) => {
// self.insert(data);
// }
// FindResult::NotFound(spec) => {
// fonts_not_fount.push(spec);
// }
// }
// }

// self.insert(FontData::from_slice(FONT_SYMBOLS_NERD_FONT_MONO, false).unwrap());

if let Some(ui_spec) = spec.ui {
match find_font(&db, ui_spec, false, false) {
Expand Down
Loading