1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 21:32:13 +03:00

text cursor glyph renders at native cell size

Previously we'd use the scaled-by-line-height-and-cell-width dimensions
for the text cursor, leading to oddly dimensioned block cursors when
`line_height` or `cell_width` were configured.

This commit captures the native cell dimensions into the RenderMetrics
which makes it feasible for the glyph and sprite rendering logic to
reason about it.

The cursor rendering now renders at the native size and position by
using a transform to scale and translate into the correct spot.

We could potentially use the same technique for eg: braille or
other non-drawing characters
(https://github.com/wez/wezterm/issues/1957) although that is more
complex than just this commit.

refs: https://github.com/wez/wezterm/issues/2882
This commit is contained in:
Wez Furlong 2023-08-26 13:48:21 -07:00
parent 360ad2a3a9
commit 2c95b98447
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
3 changed files with 87 additions and 11 deletions

View File

@ -89,6 +89,9 @@ As features stabilize some brief notes about them will accumulate here.
* macOS: system font fallback didn't always find a workable fallback font. #4099 #849 * macOS: system font fallback didn't always find a workable fallback font. #4099 #849
* F13-F24 keys are now supported. Thanks to @ovidiu-ionescu! #3937 * F13-F24 keys are now supported. Thanks to @ovidiu-ionescu! #3937
* Strikethrough position was not centered when setting `line_height` #4196 * Strikethrough position was not centered when setting `line_height` #4196
* Text cursor filled the scaled-by `line_height` and `cell_width` dimensions rather
than the native font dimensions and looked weird when either config option was
not set to `1.0`. #2882
#### Updated #### Updated
* Bundled harfbuzz to 8.1.1 * Bundled harfbuzz to 8.1.1

View File

@ -245,10 +245,17 @@ pub enum PolyStyle {
} }
impl PolyStyle { impl PolyStyle {
fn apply(self, width: f32, paint: &Paint, path: &Path, pixmap: &mut PixmapMut) { fn apply(
self,
width: f32,
paint: &Paint,
path: &Path,
pixmap: &mut PixmapMut,
transform: Transform,
) {
match self { match self {
PolyStyle::Fill => { PolyStyle::Fill => {
pixmap.fill_path(path, paint, FillRule::Winding, Transform::identity(), None); pixmap.fill_path(path, paint, FillRule::Winding, transform, None);
} }
PolyStyle::OutlineThin | PolyStyle::Outline | PolyStyle::OutlineHeavy => { PolyStyle::OutlineThin | PolyStyle::Outline | PolyStyle::OutlineHeavy => {
@ -259,7 +266,7 @@ impl PolyStyle {
} else if self == PolyStyle::OutlineThin { } else if self == PolyStyle::OutlineThin {
stroke.width = 1.2; stroke.width = 1.2;
} }
pixmap.stroke_path(path, paint, &stroke, Transform::identity(), None); pixmap.stroke_path(path, paint, &stroke, transform, None);
} }
} }
} }
@ -3727,6 +3734,7 @@ impl GlyphCache {
polys: &[Poly], polys: &[Poly],
buffer: &mut Image, buffer: &mut Image,
aa: PolyAA, aa: PolyAA,
transform: Transform,
) { ) {
let (width, height) = buffer.image_dimensions(); let (width, height) = buffer.image_dimensions();
let mut pixmap = let mut pixmap =
@ -3754,7 +3762,13 @@ impl GlyphCache {
item.to_skia(width, height, metrics.underline_height as f32, &mut pb); item.to_skia(width, height, metrics.underline_height as f32, &mut pb);
} }
let path = pb.finish().expect("poly path to be valid"); let path = pb.finish().expect("poly path to be valid");
style.apply(metrics.underline_height as f32, &paint, &path, &mut pixmap); style.apply(
metrics.underline_height as f32,
&paint,
&path,
&mut pixmap,
transform,
);
} }
} }
@ -3768,6 +3782,9 @@ impl GlyphCache {
return Ok(sprite.clone()); return Ok(sprite.clone());
} }
let x_scale = metrics.native_cell_size.width as f32 / metrics.cell_size.width as f32;
let y_scale = metrics.native_cell_size.height as f32 / metrics.cell_size.height as f32;
let mut metrics = metrics.scale_cell_width(width as f64); let mut metrics = metrics.scale_cell_width(width as f64);
if let Some(d) = &self.fonts.config().cursor_thickness { if let Some(d) = &self.fonts.config().cursor_thickness {
metrics.underline_height = d.evaluate_as_pixels(DimensionContext { metrics.underline_height = d.evaluate_as_pixels(DimensionContext {
@ -3777,6 +3794,12 @@ impl GlyphCache {
}) as isize; }) as isize;
} }
let thickness_scale = y_scale.max(x_scale);
let thickness = ((metrics.underline_height as f32) / thickness_scale).ceil() as isize;
let x_translate = thickness - metrics.underline_height;
metrics.underline_height = thickness;
let mut buffer = Image::new( let mut buffer = Image::new(
metrics.cell_size.width as usize, metrics.cell_size.width as usize,
metrics.cell_size.height as usize, metrics.cell_size.height as usize,
@ -3785,12 +3808,23 @@ impl GlyphCache {
let cell_rect = Rect::new(Point::new(0, 0), metrics.cell_size); let cell_rect = Rect::new(Point::new(0, 0), metrics.cell_size);
buffer.clear_rect(cell_rect, black); buffer.clear_rect(cell_rect, black);
let transform = if metrics.cell_size != metrics.native_cell_size {
Transform::from_scale(x_scale, y_scale).post_translate(
// No scaled X translation because cell_width just adds blank space
// into the right of the bitmap without adjusting any x-coords.
// We just need to compensate for the line thickness scaling so
// that we don't clip it out the left side of the pixmap
x_translate as f32,
// Y translation to parallel the centering effect of line_height
(metrics.native_cell_size.height as f32 - metrics.cell_size.height as f32) / -2.,
)
} else {
Transform::identity()
};
match shape { match shape {
None => {} None => {}
Some(CursorShape::Default) => { Some(CursorShape::Default) => {
buffer.clear_rect(cell_rect, SrgbaPixel::rgba(0xff, 0xff, 0xff, 0xff));
}
Some(CursorShape::BlinkingBlock | CursorShape::SteadyBlock) => {
self.draw_polys( self.draw_polys(
&metrics, &metrics,
&[Poly { &[Poly {
@ -3802,10 +3836,35 @@ impl GlyphCache {
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::Zero), PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::Zero),
], ],
intensity: BlockAlpha::Full, intensity: BlockAlpha::Full,
style: PolyStyle::Fill,
}],
&mut buffer,
PolyAA::MoarPixels,
transform,
);
}
Some(CursorShape::BlinkingBlock | CursorShape::SteadyBlock) => {
self.draw_polys(
&metrics,
&[Poly {
path: &[
PolyCommand::MoveTo(BlockCoord::Zero, BlockCoord::Zero),
PolyCommand::LineTo(BlockCoord::One, BlockCoord::Zero),
PolyCommand::LineTo(BlockCoord::One, BlockCoord::One),
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::One),
PolyCommand::LineTo(BlockCoord::Zero, BlockCoord::Zero),
// An extra, seemingly redundant path segment here is
// needed to workaround tiny-skia leaving a single blank
// pixel in the top left corner when the glyph metrics
// are scaled by cell_width and/or line_height
PolyCommand::LineTo(BlockCoord::One, BlockCoord::Zero),
],
intensity: BlockAlpha::Full,
style: PolyStyle::OutlineHeavy, style: PolyStyle::OutlineHeavy,
}], }],
&mut buffer, &mut buffer,
PolyAA::AntiAlias, PolyAA::MoarPixels,
transform,
); );
} }
Some(CursorShape::BlinkingBar | CursorShape::SteadyBar) => { Some(CursorShape::BlinkingBar | CursorShape::SteadyBar) => {
@ -3820,7 +3879,8 @@ impl GlyphCache {
style: PolyStyle::OutlineHeavy, style: PolyStyle::OutlineHeavy,
}], }],
&mut buffer, &mut buffer,
PolyAA::AntiAlias, PolyAA::MoarPixels,
transform,
); );
} }
Some(CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline) => { Some(CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline) => {
@ -3835,7 +3895,8 @@ impl GlyphCache {
style: PolyStyle::OutlineHeavy, style: PolyStyle::OutlineHeavy,
}], }],
&mut buffer, &mut buffer,
PolyAA::AntiAlias, PolyAA::MoarPixels,
transform,
); );
} }
} }
@ -3862,6 +3923,7 @@ impl GlyphCache {
underline_height: *underline_height, underline_height: *underline_height,
strike_row: 0, strike_row: 0,
cell_size: cell_size.clone(), cell_size: cell_size.clone(),
native_cell_size: render_metrics.native_cell_size,
}, },
_ => render_metrics.clone(), _ => render_metrics.clone(),
}; };
@ -4037,6 +4099,7 @@ impl GlyphCache {
} else { } else {
PolyAA::MoarPixels PolyAA::MoarPixels
}, },
Transform::identity(),
); );
} }
} }

View File

@ -17,6 +17,7 @@ pub struct RenderMetrics {
pub underline_height: IntPixelLength, pub underline_height: IntPixelLength,
pub strike_row: IntPixelLength, pub strike_row: IntPixelLength,
pub cell_size: Size, pub cell_size: Size,
pub native_cell_size: Size,
} }
impl RenderMetrics { impl RenderMetrics {
@ -33,13 +34,15 @@ impl RenderMetrics {
let descender_plus_two = let descender_plus_two =
(2 * underline_height + descender_row).min(cell_height as isize - underline_height); (2 * underline_height + descender_row).min(cell_height as isize - underline_height);
let strike_row = descender_row / 2; let strike_row = descender_row / 2;
let cell_size = Size::new(cell_width as isize, cell_height as isize);
Self { Self {
descender: metrics.descender, descender: metrics.descender,
descender_row, descender_row,
descender_plus_two, descender_plus_two,
strike_row, strike_row,
cell_size: Size::new(cell_width as isize, cell_height as isize), cell_size,
native_cell_size: cell_size,
underline_height, underline_height,
} }
} }
@ -59,6 +62,7 @@ impl RenderMetrics {
underline_height: self.underline_height, underline_height: self.underline_height,
strike_row: self.strike_row, strike_row: self.strike_row,
cell_size: size, cell_size: size,
native_cell_size: self.native_cell_size,
} }
} }
@ -73,6 +77,11 @@ impl RenderMetrics {
.default_font_metrics() .default_font_metrics()
.context("failed to get font metrics!?")?; .context("failed to get font metrics!?")?;
let native_cell_size = Size::new(
metrics.cell_width.get() as isize,
metrics.cell_height.get() as isize,
);
let line_height = fonts.config().line_height; let line_height = fonts.config().line_height;
let cell_width = fonts.config().cell_width; let cell_width = fonts.config().cell_width;
@ -131,6 +140,7 @@ impl RenderMetrics {
strike_row, strike_row,
cell_size: Size::new(cell_width as isize, cell_height as isize), cell_size: Size::new(cell_width as isize, cell_height as isize),
underline_height, underline_height,
native_cell_size,
}) })
} }
} }