Merge branch 'main' into ssh-integration

This commit is contained in:
Jason Rayne
2025-07-08 10:46:29 -07:00

View File

@ -354,21 +354,48 @@ pub const Face = struct {
opts.constraint_width, opts.constraint_width,
); );
const width = glyph_size.width; // We manually quantize the position and size of the glyph to whole
const height = glyph_size.height; // pixel boundaries. Since macOS doesn't do font hinting this helps
const x = glyph_size.x; // a lot for legibility at small sizes on low dpi displays.
const y = glyph_size.y; //
// Well, okay, so, it seems like macOS does have a rudimentary auto-
// hinter of sorts, except they call it "subpixel quantization"[^1].
//
// Why not just use that? Because it's unpredictable and would force
// us to have an extra pixel of padding in the atlas for most glyphs
// that don't need it, since it's hard to know whether a given glyph
// will have its bottom or left edge snapped out an extra pixel.
//
// Also, this empirically just looks a whole lot better than theirs.
// Admittedly this is a very specific use case, we're rendering for
// a monospace grid and don't really have to worry about sub-pixel
// positioning; I'm sure Apple's technique is better for cases with
// proportional text.
//
// An effort was made to more or less match Apple's quantization in
// terms of resulting whole-pixel glyph sizes. Oddly it looks like
// Apple is still horizontally quantizing to thirds of a pixel, as
// if they're doing subpixel rendering for a horizontally striped
// LCD, even though they haven't done subpixel rendering for years.
// We don't match them on that, it tends to just make it blurrier.
//
// [^1]: Well I'm 80% sure it's hinting since it seems to account for
// features inside of the glyph like crossbars, not just the bounding
// box like we do. The documentation is... sparse. Ref:
// https://developer.apple.com/documentation/coregraphics/cgcontext/setshouldsubpixelquantizefonts(_:)?language=objc
//
// TODO: Maybe gate this so it only applies at small font sizes,
// or else offer a user config option that can disable it.
const x = @round(glyph_size.x);
const y = @round(glyph_size.y);
// We subtract a third here so that we behave (somewhat) like the weird
// one third pixel quantization that Apple does. This is basically just
// a fudge factor though.
const width = @max(1.0, @ceil(glyph_size.width + glyph_size.x - x - 1.0 / 3.0));
const height = @max(1.0, @ceil(glyph_size.height + glyph_size.y - y));
// We have to include the fractional pixels that we won't be offsetting const px_width: u32 = @intFromFloat(@ceil(width));
// in our width and height calculations, that is, we offset by the floor const px_height: u32 = @intFromFloat(@ceil(height));
// of the bearings when we render the glyph, meaning there's still a bit
// of extra width to the area that's drawn in beyond just the width of
// the glyph itself, so we include that extra fraction of a pixel when
// calculating the width and height here.
const frac_x = rect.origin.x - @floor(rect.origin.x);
const frac_y = rect.origin.y - @floor(rect.origin.y);
const px_width: u32 = @intFromFloat(@ceil(width + frac_x));
const px_height: u32 = @intFromFloat(@ceil(height + frac_y));
// Settings that are specific to if we are rendering text or emoji. // Settings that are specific to if we are rendering text or emoji.
const color: struct { const color: struct {
@ -433,12 +460,23 @@ pub const Face = struct {
}, },
}); });
// "Font smoothing" is what we call "thickening", it's an attempt
// to compensate for optical thinning of fonts, but at this point
// it's just something that makes the text look closer to system
// applications if users want that.
context.setAllowsFontSmoothing(ctx, true); context.setAllowsFontSmoothing(ctx, true);
context.setShouldSmoothFonts(ctx, opts.thicken); // The amadeus "enthicken" context.setShouldSmoothFonts(ctx, opts.thicken);
context.setAllowsFontSubpixelQuantization(ctx, true);
context.setShouldSubpixelQuantizeFonts(ctx, true); // Subpixel positioning allows glyphs to be placed at non-integer
// coordinates. We need this for our alignment.
context.setAllowsFontSubpixelPositioning(ctx, true); context.setAllowsFontSubpixelPositioning(ctx, true);
context.setShouldSubpixelPositionFonts(ctx, true); context.setShouldSubpixelPositionFonts(ctx, true);
// See comments about quantization earlier in the function.
context.setAllowsFontSubpixelQuantization(ctx, false);
context.setShouldSubpixelQuantizeFonts(ctx, false);
// Anti-aliasing is self explanatory.
context.setAllowsAntialiasing(ctx, true); context.setAllowsAntialiasing(ctx, true);
context.setShouldAntialias(ctx, true); context.setShouldAntialias(ctx, true);
@ -459,6 +497,8 @@ pub const Face = struct {
context.setLineWidth(ctx, line_width); context.setLineWidth(ctx, line_width);
} }
// Scale the drawing context so that when we draw
// our glyph it's stretched to the constrained size.
context.scaleCTM( context.scaleCTM(
ctx, ctx,
width / rect.size.width, width / rect.size.width,
@ -469,8 +509,8 @@ pub const Face = struct {
// are offset by bearings, so we have to undo those bearings in order // are offset by bearings, so we have to undo those bearings in order
// to get them to 0,0. // to get them to 0,0.
self.font.drawGlyphs(&glyphs, &.{.{ self.font.drawGlyphs(&glyphs, &.{.{
.x = -@floor(rect.origin.x), .x = -rect.origin.x,
.y = -@floor(rect.origin.y), .y = -rect.origin.y,
}}, ctx); }}, ctx);
// Write our rasterized glyph to the atlas. // Write our rasterized glyph to the atlas.
@ -479,9 +519,7 @@ pub const Face = struct {
// This should be the distance from the bottom of // This should be the distance from the bottom of
// the cell to the top of the glyph's bounding box. // the cell to the top of the glyph's bounding box.
const offset_y: i32 = const offset_y: i32 = @as(i32, @intFromFloat(@ceil(y + height)));
@as(i32, @intFromFloat(@floor(y))) +
@as(i32, @intCast(px_height));
// This should be the distance from the left of // This should be the distance from the left of
// the cell to the left of the glyph's bounding box. // the cell to the left of the glyph's bounding box.
@ -514,13 +552,13 @@ pub const Face = struct {
// We also don't want to do anything if the advance is zero or // We also don't want to do anything if the advance is zero or
// less, since this is used for stuff like combining characters. // less, since this is used for stuff like combining characters.
if (advance > new_advance or advance <= 0.0) { if (advance > new_advance or advance <= 0.0) {
break :offset_x @intFromFloat(@ceil(x - frac_x)); break :offset_x @intFromFloat(@ceil(x));
} }
break :offset_x @intFromFloat( break :offset_x @intFromFloat(
@ceil(x - frac_x + (new_advance - advance) / 2), @round(x + (new_advance - advance) / 2),
); );
} else { } else {
break :offset_x @intFromFloat(@ceil(x - frac_x)); break :offset_x @intFromFloat(@ceil(x));
} }
}; };