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

glyph: Add working braille custom glyphs #919

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ As features stabilize some brief notes about them will accumulate here.
* Fixed: key repeat on Wayland now respects the system specified key repeat rate, and doesn't "stick". [#669](https://github.com/wez/wezterm/issues/669)
* Fixed: `force_reverse_video_cursor` wasn't correctly swapping the cursor colors in all cases. [#706](https://github.com/wez/wezterm/issues/706)
* Fixed: allow multuple `IdentityFile` lines in an ssh_config block to be considered
* Improved: implement braille characters as custom glyphs, to have perfect rendering when `custom_block_glyphs` is enabled. Thanks to [@bew](http://github.com/bew)!

### 20210502-154244-3f7122cb

Expand Down
23 changes: 23 additions & 0 deletions test-data/braille-all-chars.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// [⡪] (Single one)

// ⠀ ⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏
// ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟
// ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯
// ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿
// ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏
// ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟
// ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯
// ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿
// ⢀ ⢁ ⢂ ⢃ ⢄ ⢅ ⢆ ⢇ ⢈ ⢉ ⢊ ⢋ ⢌ ⢍ ⢎ ⢏
// ⢐ ⢑ ⢒ ⢓ ⢔ ⢕ ⢖ ⢗ ⢘ ⢙ ⢚ ⢛ ⢜ ⢝ ⢞ ⢟
// ⢠ ⢡ ⢢ ⢣ ⢤ ⢥ ⢦ ⢧ ⢨ ⢩ ⢪ ⢫ ⢬ ⢭ ⢮ ⢯
// ⢰ ⢱ ⢲ ⢳ ⢴ ⢵ ⢶ ⢷ ⢸ ⢹ ⢺ ⢻ ⢼ ⢽ ⢾ ⢿
// ⣀ ⣁ ⣂ ⣃ ⣄ ⣅ ⣆ ⣇ ⣈ ⣉ ⣊ ⣋ ⣌ ⣍ ⣎ ⣏
// ⣐ ⣑ ⣒ ⣓ ⣔ ⣕ ⣖ ⣗ ⣘ ⣙ ⣚ ⣛ ⣜ ⣝ ⣞ ⣟
// ⣠ ⣡ ⣢ ⣣ ⣤ ⣥ ⣦ ⣧ ⣨ ⣩ ⣪ ⣫ ⣬ ⣭ ⣮ ⣯
// ⣰ ⣱ ⣲ ⣳ ⣴ ⣵ ⣶ ⣷ ⣸ ⣹ ⣺ ⣻ ⣼ ⣽ ⣾ ⣿

// ⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏
// ⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟
// ⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯
// ⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿
20 changes: 20 additions & 0 deletions test-data/braille-wezterm-logo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
⣴⣿⡿⠟⠻⢿⣿⣿⡿⠛⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦
⣿⣟⠀⠀⠀⠀⣿⣟⠀⠀⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣄⣀⣀⣠⣿⣿⣄⣀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⡿⠿⠟⠛⠃⠀⠀⠛⠛⠻⣿⣿⣿⣿⣿⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⢠⣿⣿⣿
⣿⣿⣿⡿⠋⠀⠀⠀⣀⣀⡀⢀⣀⡀⠀⠀⣿⣿⣿⣿⣿⡄⠀⠀⢺⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿
⣿⣿⣿⡇⠀⠀⢰⣿⣿⣿⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢾⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿
⣿⣿⣿⣇⠀⠀⠀⠛⢿⡿⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿⠉⠉⣿⣿⣿⡇⠀⠀⢸⣿⣿⣿
⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⡇⠀⠀⠹⣿⣿⡇⠀⠀⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣶⡄⠀⠀⠀⠈⠙⢿⣿⣿⣿⣿⣿⣿⡇⠀⠀⢸⣿⣿⠁⣰⡀⠀⣿⣿⡇⠀⠀⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⣶⣦⡀⠀⠀⠙⣿⣿⣿⣿⣿⣷⠀⠀⠀⣿⡇⢠⣿⣷⠀⠸⣿⡇⠀⠀⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⠁⢸⣿⣿⣿⠀⠀⠀⢸⣿⣿⣿⣿⣿⠀⠀⠀⣿⠁⣸⣿⣿⡄⠀⢿⡇⠀⠀⣿⣿⣿⣿
⣿⣿⣿⠉⠛⠛⠛⠋⠀⠘⠛⠛⠁⠀⠀⣠⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⢠⣿⣿⣿⣿⠀⠀⠁⠀⢸⣿⣿⣿⣿
⣿⣿⣇⣀⣀⣀⡀⠀⠀⣀⣀⣠⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⣼⣿⣿⣿⣿⡆⠀⠀⠀⣼⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⡇⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣿⣿⣿⣿⣿⣷⣶⣶⣶⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣀⣀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟
⠈⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁
80 changes: 80 additions & 0 deletions wezterm-gui/src/glyphcache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ pub enum BlockKey {
Quadrants(Quadrant),
/// A combination of sextants <https://unicode.org/charts/PDF/U1FB00.pdf>
Sextants(Sextant),
/// A braille dot pattern
Braille(u8),

Poly(&'static [Poly]),
}
Expand Down Expand Up @@ -3326,6 +3328,24 @@ impl BlockKey {
0x259f => Self::Quadrants(
Quadrant::UPPER_RIGHT | Quadrant::LOWER_LEFT | Quadrant::LOWER_RIGHT,
),
// Braille dot patterns
// ⠀ ⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏
// ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟
// ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯
// ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿
// ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏
// ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟
// ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯
// ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿
// ⢀ ⢁ ⢂ ⢃ ⢄ ⢅ ⢆ ⢇ ⢈ ⢉ ⢊ ⢋ ⢌ ⢍ ⢎ ⢏
// ⢐ ⢑ ⢒ ⢓ ⢔ ⢕ ⢖ ⢗ ⢘ ⢙ ⢚ ⢛ ⢜ ⢝ ⢞ ⢟
// ⢠ ⢡ ⢢ ⢣ ⢤ ⢥ ⢦ ⢧ ⢨ ⢩ ⢪ ⢫ ⢬ ⢭ ⢮ ⢯
// ⢰ ⢱ ⢲ ⢳ ⢴ ⢵ ⢶ ⢷ ⢸ ⢹ ⢺ ⢻ ⢼ ⢽ ⢾ ⢿
// ⣀ ⣁ ⣂ ⣃ ⣄ ⣅ ⣆ ⣇ ⣈ ⣉ ⣊ ⣋ ⣌ ⣍ ⣎ ⣏
// ⣐ ⣑ ⣒ ⣓ ⣔ ⣕ ⣖ ⣗ ⣘ ⣙ ⣚ ⣛ ⣜ ⣝ ⣞ ⣟
// ⣠ ⣡ ⣢ ⣣ ⣤ ⣥ ⣦ ⣧ ⣨ ⣩ ⣪ ⣫ ⣬ ⣭ ⣮ ⣯
// ⣰ ⣱ ⣲ ⣳ ⣴ ⣵ ⣶ ⣷ ⣸ ⣹ ⣺ ⣻ ⣼ ⣽ ⣾ ⣿
n @ 0x2800..=0x28ff => Self::Braille((n & 0xff) as u8),
// [🬀] BLOCK SEXTANT-1
0x1fb00 => Self::Sextants(Sextant::ONE),
// [🬁] BLOCK SEXTANT-2
Expand Down Expand Up @@ -4317,6 +4337,66 @@ impl<T: Texture2d> GlyphCache<T> {
);
}
}
BlockKey::Braille(dots_pattern) => {
// `dots_pattern` is a byte whose bits corresponds to dots
// on a 2 by 4 dots-grid.
// The position of a dot for a bit position (1-indexed) is as follow:
// 1 4 |
// 2 5 |<- These 3 lines are filled first (for the first 64 symbols)
// 3 6 |
// 7 8 <- This last line is filled last (for the remaining 192 symbols)
//
// NOTE: for simplicity & performance reasons, a dot is a square not a circle.
bew marked this conversation as resolved.
Show resolved Hide resolved

let cell_width = self.metrics.cell_size.width as f32 / 2.;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finding this naming a bit confusing; cell_width is used in many places in wezterm to refer to the width of the terminal cell and I'd assumed that was true here and getting confused about how the dots fit in the cell, but then I saw that this is actually half that size!

Could you make a pass through to clarify these?

I think square_length is the key metric being computed here, so I'd suggest simplifying these into something like this:

   let square_width = self.metrics.cell_size.width as f32 / 4;
   let topleft_offset_x = self.metrics.cell_size.width as f32 / 8;
   let square_height = self.metrics.cell_size.height as f32 / 4;
   let topleft_offset_y = self.metrics.cell_size.height as f32 / 8;

I think this makes it a bit easier to visualize the positioning when thinking about the cell overall and how that gets divided up into an interior grid.

I think it's probably a good idea to have a separate square_width and square_height because different fonts have different aspect ratios, which makes it potentially problematic to use a metric derived from the X in the Y direction.

Copy link
Sponsor Contributor Author

@bew bew Jul 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was thinking that cell_width isn't great naming either, sorry for the confusion 👀
As for the suggested vars definition, I actually find it harder to see how it works overall..

I'd be ok to rename the badly named var to sth like dot_area_width maybe (or you have better naming?), but calculating the topleft_offset_x like you suggest isn't great IMO, because as soon as you want to have a slightly bigger square, you're out of luck, and have to rethink all your formulas. However doing this with my use of variables I believe is as easy as changing the square_length value, and the rest will adapt.
I don't think it's worth removing the intermediate variables, as in a release build llvm will probably find ways to optimize things.. And it's not as if the code was a hot-path for the rendering, it's only called a few times to build the cache.

If you really want I can do what you suggest (or you can change the code yourself afterward 👀), but know that I personally wouldn't want to maintain these simplified definitions if it was my project.


I think it's probably a good idea to have a separate square_width and square_height because different fonts have different aspect ratios, which makes it potentially problematic to use a metric derived from the X in the Y direction.

I don't agree, if the dots were circles, would you like to have ovals? I don't think so..
And it's the same for sharp dots, I think these should be actual squares, not ~rectangles.

For the fonts with different aspect ratios, do you have something in mind that wouldn't look great? Maybe we can find an optimal square_length with some min/max of some other values?

Also, did you see my PR comment here: #919 (comment) ?

let cell_height = self.metrics.cell_size.height as f32 / 4.;
let square_length = cell_width / 2.;
let topleft_offset_x = cell_width / 2. - square_length / 2.;
let topleft_offset_y = cell_height / 2. - square_length / 2.;

let (width, height) = buffer.image_dimensions();
let mut pixmap = PixmapMut::from_bytes(
buffer.pixel_data_slice_mut(),
width as u32,
height as u32,
)
.expect("make pixmap from existing bitmap");
let mut paint = Paint::default();
paint.set_color(tiny_skia::Color::WHITE);
paint.force_hq_pipeline = true;
bew marked this conversation as resolved.
Show resolved Hide resolved
paint.anti_alias = true;
let identity = Transform::identity();

bew marked this conversation as resolved.
Show resolved Hide resolved
const BIT_MASK_AND_DOT_POSITION: [(u8, f32, f32); 8] = [
(1 << 0, 0., 0.),
(1 << 1, 0., 1.),
(1 << 2, 0., 2.),
(1 << 3, 1., 0.),
(1 << 4, 1., 1.),
(1 << 5, 1., 2.),
(1 << 6, 0., 3.),
(1 << 7, 1., 3.),
];
for (bit_mask, dot_pos_x, dot_pos_y) in &BIT_MASK_AND_DOT_POSITION {
if dots_pattern & bit_mask == 0 {
// Bit for this dot position is not set
continue;
}
let topleft_x = (*dot_pos_x) * cell_width + topleft_offset_x;
let topleft_y = (*dot_pos_y) * cell_height + topleft_offset_y;

let path = PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
topleft_x,
topleft_y,
square_length,
square_length,
)
.expect("valid rect"),
);
pixmap.fill_path(&path, &paint, FillRule::Winding, identity, None);
}
}
BlockKey::Poly(polys) => {
let (width, height) = buffer.image_dimensions();
let mut pixmap = PixmapMut::from_bytes(
Expand Down