mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
Fix up font raster position + other small fixes (#8206)
I've cleaned up the code we use for scaling and positioning glyphs for raster, under both CoreText and FreeType. Before we had some imprecision, and under CoreText we were sometimes stretching glyphs in unseemly ways. These changes make it so that our constraints can position and size glyphs *exactly* and we don't have any chopped-off row/column issues for CoreText. With this, PowerLine Extra symbols now always align *perfectly* with the cell height: ||Before|After| |-:|-|-| |**CoreText**|<img width="105" height="245" alt="image" src="https://github.com/user-attachments/assets/d3c1b1cb-a798-4e18-a0e0-59551893369c" />|<img width="106" height="246" alt="image" src="https://github.com/user-attachments/assets/dac10c49-9ec1-4f4f-8825-a5e8c2fd3402" />| |**FreeType**|<img width="105" height="245" alt="image" src="https://github.com/user-attachments/assets/160e1e35-4a3c-42d0-9042-215301e636a1" />|<img width="106" height="245" alt="image" src="https://github.com/user-attachments/assets/89bf1538-7271-4baf-88c0-51ebc4d360df" />| The other changes are mainly just cleanup stuff, though one of the changes makes it so that we do once again properly apply constraints to symbols from the dingbats block (it was a regression, noted in #7955, that we stopped doing that). ### Future work This has been a problem since we introduced the custom constraints, but I noticed it while preparing the before/after images: the left-edge PLE symbols (meant to connect to a full block on the right) expand out to the *right*, so if they're followed immediately by another character than they actually get squished and don't match the right-edge symbols: <img width="75" height="114" alt="image" src="https://github.com/user-attachments/assets/1420b9a5-9950-4210-9934-8ef7cd7a1e19" /> I have a WIP change to move constraint logic to the shapers, and at that point we can maybe do something to allow the constraint to grow in to whitespace on the left side instead of on the right side.
This commit is contained in:
@@ -38,6 +38,10 @@ cursor_height: u32,
|
||||
/// The constraint height for nerd fonts icons.
|
||||
icon_height: u32,
|
||||
|
||||
/// Original cell width in pixels. This is used to keep
|
||||
/// glyphs centered if the cell width is adjusted wider.
|
||||
original_cell_width: ?u32 = null,
|
||||
|
||||
/// Minimum acceptable values for some fields to prevent modifiers
|
||||
/// from being able to, for example, cause 0-thickness underlines.
|
||||
const Minimums = struct {
|
||||
@@ -263,6 +267,11 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
||||
const new = @max(entry.value_ptr.apply(original), 1);
|
||||
if (new == original) continue;
|
||||
|
||||
// Preserve the original cell width if not set.
|
||||
if (self.original_cell_width == null) {
|
||||
self.original_cell_width = self.cell_width;
|
||||
}
|
||||
|
||||
// Set the new value
|
||||
@field(self, @tagName(tag)) = new;
|
||||
|
||||
|
@@ -222,6 +222,16 @@ pub const RenderOptions = struct {
|
||||
y: f64,
|
||||
};
|
||||
|
||||
/// Returns true if the constraint does anything. If it doesn't,
|
||||
/// because it neither sizes nor positions the glyph, then this
|
||||
/// returns false.
|
||||
pub inline fn doesAnything(self: Constraint) bool {
|
||||
return self.size_horizontal != .none or
|
||||
self.align_horizontal != .none or
|
||||
self.size_vertical != .none or
|
||||
self.align_vertical != .none;
|
||||
}
|
||||
|
||||
/// Apply this constraint to the provided glyph
|
||||
/// size, given the available width and height.
|
||||
pub fn constrain(
|
||||
|
@@ -346,89 +346,76 @@ pub const Face = struct {
|
||||
|
||||
const metrics = opts.grid_metrics;
|
||||
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||
// const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
|
||||
// Next we apply any constraints to get the final size of the glyph.
|
||||
var constraint = opts.constraint;
|
||||
|
||||
// We eliminate any negative vertical padding since these overlap
|
||||
// values aren't needed under CoreText with how precisely we apply
|
||||
// constraints, and they can lead to extra height that looks bad
|
||||
// for things like powerline glyphs.
|
||||
var constraint = opts.constraint;
|
||||
// values aren't needed with how precisely we apply constraints,
|
||||
// and they can lead to extra height that looks bad for things like
|
||||
// powerline glyphs.
|
||||
constraint.pad_top = @max(0.0, constraint.pad_top);
|
||||
constraint.pad_bottom = @max(0.0, constraint.pad_bottom);
|
||||
|
||||
// We need to add the baseline position before passing to the constrain
|
||||
// function since it operates on cell-relative positions, not baseline.
|
||||
const cell_baseline: f64 = @floatFromInt(metrics.cell_baseline);
|
||||
|
||||
const glyph_size = constraint.constrain(
|
||||
.{
|
||||
.width = rect.size.width,
|
||||
.height = rect.size.height,
|
||||
.x = rect.origin.x,
|
||||
.y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)),
|
||||
.y = rect.origin.y + cell_baseline,
|
||||
},
|
||||
metrics,
|
||||
opts.constraint_width,
|
||||
);
|
||||
|
||||
// These calculations are an attempt to mostly imitate the effect of
|
||||
// `shouldSubpixelQuantizeFonts`[^1], which helps maximize legibility
|
||||
// at small pixel sizes (low DPI). We do this math ourselves instead
|
||||
// of letting CoreText do it because it's not entirely clear how the
|
||||
// math in CoreText works and we've run in to edge cases where glyphs
|
||||
// have their bottom or left row cut off due to bad rounding.
|
||||
//
|
||||
// This math seems to have a mostly comparable result to whatever it
|
||||
// is that CoreText does, and is even (in my opinion) better in some
|
||||
// cases.
|
||||
//
|
||||
// I'm not entirely certain but I suspect that when you enable the
|
||||
// CoreText option it also does some sort of rudimentary hinting,
|
||||
// but it doesn't seem to make that big of a difference in terms
|
||||
// of legibility in the end.
|
||||
//
|
||||
// [^1]: https://developer.apple.com/documentation/coregraphics/cgcontext/setshouldsubpixelquantizefonts(_:)?language=objc
|
||||
var x = glyph_size.x;
|
||||
var y = glyph_size.y;
|
||||
var width = glyph_size.width;
|
||||
var height = glyph_size.height;
|
||||
|
||||
// We only want to apply quantization if we don't have any
|
||||
// constraints and this isn't a bitmap glyph, since CoreText
|
||||
// doesn't seem to apply its quantization to bitmap glyphs.
|
||||
//
|
||||
// TODO: Maybe gate this so it only applies at small font sizes,
|
||||
// or else offer a user config option that can disable it.
|
||||
const should_quantize = !sbix and std.meta.eql(opts.constraint, .none);
|
||||
// If this is a bitmap glyph, it will always render as full pixels,
|
||||
// not fractional pixels, so we need to quantize its position and
|
||||
// size accordingly to align to full pixels so we get good results.
|
||||
if (sbix) {
|
||||
width = cell_width - @round(cell_width - width - x) - @round(x);
|
||||
height = cell_height - @round(cell_height - height - y) - @round(y);
|
||||
x = @round(x);
|
||||
y = @round(y);
|
||||
}
|
||||
|
||||
// We offset our glyph by its bearings when we draw it, using `@floor`
|
||||
// here rounds it *up* since we negate it right outside. Moving it by
|
||||
// whole pixels ensures that we don't disturb the pixel alignment of
|
||||
// the glyph, fractional pixels will still be drawn on all sides as
|
||||
// necessary.
|
||||
const draw_x = -@floor(rect.origin.x);
|
||||
const draw_y = -@floor(rect.origin.y);
|
||||
// If the cell width was adjusted wider, we re-center all glyphs
|
||||
// in the new width, so that they aren't weirdly off to the left.
|
||||
if (metrics.original_cell_width) |original| recenter: {
|
||||
// We don't do this if the constraint has a horizontal alignment,
|
||||
// since in that case the position was already calculated with the
|
||||
// new cell width in mind.
|
||||
if (opts.constraint.align_horizontal != .none) break :recenter;
|
||||
|
||||
// We use `x` and `y` for our full pixel bearings post-raster.
|
||||
// We need to subtract the fractional pixel of difference from
|
||||
// the edge of the draw area to the edge of the actual glyph.
|
||||
const frac_x = rect.origin.x + draw_x;
|
||||
const frac_y = rect.origin.y + draw_y;
|
||||
const x = glyph_size.x - frac_x;
|
||||
const y = glyph_size.y - frac_y;
|
||||
// If the original width was wider then we don't do anything.
|
||||
if (original >= metrics.cell_width) break :recenter;
|
||||
|
||||
// We never modify the width.
|
||||
//
|
||||
// When using the CoreText option the widths do seem to be
|
||||
// modified extremely subtly, but even at very small font
|
||||
// sizes it's hardly a noticeable difference.
|
||||
const width = glyph_size.width;
|
||||
// We add half the difference to re-center.
|
||||
x += (cell_width - @as(f64, @floatFromInt(original))) / 2;
|
||||
}
|
||||
|
||||
// If the top of the glyph (taking in to account the y position)
|
||||
// is within half a pixel of an exact pixel edge, we round up the
|
||||
// height, otherwise leave it alone.
|
||||
//
|
||||
// This seems to match what CoreText does.
|
||||
const frac_top = (glyph_size.height + frac_y) - @floor(glyph_size.height + frac_y);
|
||||
const height =
|
||||
if (should_quantize)
|
||||
if (frac_top >= 0.5)
|
||||
glyph_size.height + 1 - frac_top
|
||||
else
|
||||
glyph_size.height
|
||||
else
|
||||
glyph_size.height;
|
||||
// Our whole-pixel bearings for the final glyph.
|
||||
// The fractional portion will be included in the rasterized position.
|
||||
const px_x: i32 = @intFromFloat(@floor(x));
|
||||
const px_y: i32 = @intFromFloat(@floor(y));
|
||||
|
||||
// We offset our glyph by its bearings when we draw it, so that it's
|
||||
// rendered fully inside our canvas area, but we make sure to keep the
|
||||
// fractional pixel offset so that we rasterize with the appropriate
|
||||
// sub-pixel position.
|
||||
const frac_x = x - @floor(x);
|
||||
const frac_y = y - @floor(y);
|
||||
const draw_x = -rect.origin.x + frac_x;
|
||||
const draw_y = -rect.origin.y + frac_y;
|
||||
|
||||
// Add the fractional pixel to the width and height and take
|
||||
// the ceiling to get a canvas size that will definitely fit
|
||||
@@ -511,7 +498,9 @@ pub const Face = struct {
|
||||
context.setAllowsFontSubpixelPositioning(ctx, true);
|
||||
context.setShouldSubpixelPositionFonts(ctx, true);
|
||||
|
||||
// See comments about quantization earlier in the function.
|
||||
// We don't want subpixel quantization, since we very carefully
|
||||
// manage the position of our glyphs ourselves, and dont want to
|
||||
// mess that up.
|
||||
context.setAllowsFontSubpixelQuantization(ctx, false);
|
||||
context.setShouldSubpixelQuantizeFonts(ctx, false);
|
||||
|
||||
@@ -553,46 +542,11 @@ pub const Face = struct {
|
||||
|
||||
// This should be the distance from the bottom of
|
||||
// the cell to the top of the glyph's bounding box.
|
||||
const offset_y: i32 = @as(i32, @intFromFloat(@round(y))) + @as(i32, @intCast(px_height));
|
||||
const offset_y: i32 = px_y + @as(i32, @intCast(px_height));
|
||||
|
||||
// This should be the distance from the left of
|
||||
// the cell to the left of the glyph's bounding box.
|
||||
const offset_x: i32 = offset_x: {
|
||||
// If the glyph's advance is narrower than the cell width then we
|
||||
// center the advance of the glyph within the cell width. At first
|
||||
// I implemented this to proportionally scale the center position
|
||||
// of the glyph but that messes up glyphs that are meant to align
|
||||
// vertically with others, so this is a compromise.
|
||||
//
|
||||
// This makes it so that when the `adjust-cell-width` config is
|
||||
// used, or when a fallback font with a different advance width
|
||||
// is used, we don't get weirdly aligned glyphs.
|
||||
//
|
||||
// We don't do this if the constraint has a horizontal alignment,
|
||||
// since in that case the position was already calculated with the
|
||||
// new cell width in mind.
|
||||
if (opts.constraint.align_horizontal == .none) {
|
||||
const advance = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, null);
|
||||
const new_advance =
|
||||
cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1));
|
||||
// If the original advance is greater than the cell width then
|
||||
// it's possible that this is a ligature or other glyph that is
|
||||
// intended to overflow the cell to one side or the other, and
|
||||
// adjusting the bearings could mess that up, so we just leave
|
||||
// it alone if that's the case.
|
||||
//
|
||||
// We also don't want to do anything if the advance is zero or
|
||||
// less, since this is used for stuff like combining characters.
|
||||
if (advance > new_advance or advance <= 0.0) {
|
||||
break :offset_x @intFromFloat(@round(x));
|
||||
}
|
||||
break :offset_x @intFromFloat(
|
||||
@round(x + (new_advance - advance) / 2),
|
||||
);
|
||||
} else {
|
||||
break :offset_x @intFromFloat(@round(x));
|
||||
}
|
||||
};
|
||||
const offset_x: i32 = px_x;
|
||||
|
||||
return .{
|
||||
.width = px_width,
|
||||
|
@@ -363,19 +363,11 @@ pub const Face = struct {
|
||||
self.ft_mutex.lock();
|
||||
defer self.ft_mutex.unlock();
|
||||
|
||||
// We enable hinting by default, and disable it if either of the
|
||||
// constraint alignments are not center or none, since this means
|
||||
// that the glyph needs to be aligned flush to the cell edge, and
|
||||
// hinting can mess that up.
|
||||
const do_hinting = self.load_flags.hinting and
|
||||
switch (opts.constraint.align_horizontal) {
|
||||
.start, .end => false,
|
||||
.center, .none => true,
|
||||
} and
|
||||
switch (opts.constraint.align_vertical) {
|
||||
.start, .end => false,
|
||||
.center, .none => true,
|
||||
};
|
||||
// Hinting should only be enabled if the configured load flags specify
|
||||
// it and the provided constraint doesn't actually do anything, since
|
||||
// if it does, then it'll mess up the hinting anyway when it moves or
|
||||
// resizes the glyph.
|
||||
const do_hinting = self.load_flags.hinting and !opts.constraint.doesAnything();
|
||||
|
||||
// Load the glyph.
|
||||
try self.face.loadGlyph(glyph_index, .{
|
||||
@@ -391,6 +383,11 @@ pub const Face = struct {
|
||||
.force_autohint = self.load_flags.@"force-autohint",
|
||||
.no_autohint = !self.load_flags.autohint,
|
||||
|
||||
// If we're gonna be rendering this glyph in monochrome,
|
||||
// then we should use the monochrome hinter as well, or
|
||||
// else it won't look very good at all.
|
||||
.target_mono = self.load_flags.monochrome,
|
||||
|
||||
// NO_SVG set to true because we don't currently support rendering
|
||||
// SVG glyphs under FreeType, since that requires bundling another
|
||||
// dependency to handle rendering the SVG.
|
||||
@@ -398,14 +395,45 @@ pub const Face = struct {
|
||||
});
|
||||
const glyph = self.face.handle.*.glyph;
|
||||
|
||||
const glyph_width: f64 = f26dot6ToF64(glyph.*.metrics.width);
|
||||
const glyph_height: f64 = f26dot6ToF64(glyph.*.metrics.height);
|
||||
// We get a rect that represents the position
|
||||
// and size of the glyph before any changes.
|
||||
const rect: struct {
|
||||
x: f64,
|
||||
y: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
} = metrics: {
|
||||
// If we're dealing with an outline glyph then we get the
|
||||
// outline's bounding box instead of using the built-in
|
||||
// metrics, since that's more precise and allows better
|
||||
// cell-fitting.
|
||||
if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_OUTLINE) {
|
||||
// Get the glyph's bounding box before we transform it at all.
|
||||
// We use this rather than the metrics, since it's more precise.
|
||||
var bbox: freetype.c.FT_BBox = undefined;
|
||||
_ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox);
|
||||
|
||||
break :metrics .{
|
||||
.x = f26dot6ToF64(bbox.xMin),
|
||||
.y = f26dot6ToF64(bbox.yMin),
|
||||
.width = f26dot6ToF64(bbox.xMax - bbox.xMin),
|
||||
.height = f26dot6ToF64(bbox.yMax - bbox.yMin),
|
||||
};
|
||||
}
|
||||
|
||||
break :metrics .{
|
||||
.x = f26dot6ToF64(glyph.*.metrics.horiBearingX),
|
||||
.y = f26dot6ToF64(glyph.*.metrics.horiBearingY - glyph.*.metrics.height),
|
||||
.width = f26dot6ToF64(glyph.*.metrics.width),
|
||||
.height = f26dot6ToF64(glyph.*.metrics.height),
|
||||
};
|
||||
};
|
||||
|
||||
// If our glyph is smaller than a quarter pixel in either axis
|
||||
// then it has no outlines or they're too small to render.
|
||||
//
|
||||
// In this case we just return 0-sized glyph struct.
|
||||
if (glyph_width < 0.25 or glyph_height < 0.25)
|
||||
if (rect.width < 0.25 or rect.height < 0.25)
|
||||
return font.Glyph{
|
||||
.width = 0,
|
||||
.height = 0,
|
||||
@@ -426,31 +454,70 @@ pub const Face = struct {
|
||||
_ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount));
|
||||
}
|
||||
|
||||
// Next we need to apply any constraints.
|
||||
const metrics = opts.grid_metrics;
|
||||
|
||||
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||
// const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
|
||||
const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX);
|
||||
const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height;
|
||||
// Next we apply any constraints to get the final size of the glyph.
|
||||
var constraint = opts.constraint;
|
||||
|
||||
const glyph_size = opts.constraint.constrain(
|
||||
// We eliminate any negative vertical padding since these overlap
|
||||
// values aren't needed with how precisely we apply constraints,
|
||||
// and they can lead to extra height that looks bad for things like
|
||||
// powerline glyphs.
|
||||
constraint.pad_top = @max(0.0, constraint.pad_top);
|
||||
constraint.pad_bottom = @max(0.0, constraint.pad_bottom);
|
||||
|
||||
// We need to add the baseline position before passing to the constrain
|
||||
// function since it operates on cell-relative positions, not baseline.
|
||||
const cell_baseline: f64 = @floatFromInt(metrics.cell_baseline);
|
||||
|
||||
const glyph_size = constraint.constrain(
|
||||
.{
|
||||
.width = glyph_width,
|
||||
.height = glyph_height,
|
||||
.x = glyph_x,
|
||||
.y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)),
|
||||
.width = rect.width,
|
||||
.height = rect.height,
|
||||
.x = rect.x,
|
||||
.y = rect.y + cell_baseline,
|
||||
},
|
||||
metrics,
|
||||
opts.constraint_width,
|
||||
);
|
||||
|
||||
const width = glyph_size.width;
|
||||
const height = glyph_size.height;
|
||||
// This may need to be adjusted later on.
|
||||
var width = glyph_size.width;
|
||||
var height = glyph_size.height;
|
||||
var x = glyph_size.x;
|
||||
const y = glyph_size.y;
|
||||
var y = glyph_size.y;
|
||||
|
||||
// If this is a bitmap glyph, it will always render as full pixels,
|
||||
// not fractional pixels, so we need to quantize its position and
|
||||
// size accordingly to align to full pixels so we get good results.
|
||||
if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_BITMAP) {
|
||||
width = cell_width - @round(cell_width - width - x) - @round(x);
|
||||
height = cell_height - @round(cell_height - height - y) - @round(y);
|
||||
x = @round(x);
|
||||
y = @round(y);
|
||||
}
|
||||
|
||||
// If the cell width was adjusted wider, we re-center all glyphs
|
||||
// in the new width, so that they aren't weirdly off to the left.
|
||||
if (metrics.original_cell_width) |original| recenter: {
|
||||
// We don't do this if the constraint has a horizontal alignment,
|
||||
// since in that case the position was already calculated with the
|
||||
// new cell width in mind.
|
||||
if (opts.constraint.align_horizontal != .none) break :recenter;
|
||||
|
||||
// If the original width was wider then we don't do anything.
|
||||
if (original >= metrics.cell_width) break :recenter;
|
||||
|
||||
// We add half the difference to re-center.
|
||||
//
|
||||
// NOTE: We round this to a whole-pixel amount because under
|
||||
// FreeType, the outlines will be hinted, which isn't
|
||||
// the case under CoreText. If we move the outlines by
|
||||
// a non-whole-pixel amount, it completely ruins the
|
||||
// hinting.
|
||||
x += @round((cell_width - @as(f64, @floatFromInt(original))) / 2);
|
||||
}
|
||||
|
||||
// Now we can render the glyph.
|
||||
var bitmap: freetype.c.FT_Bitmap = undefined;
|
||||
@@ -464,8 +531,8 @@ pub const Face = struct {
|
||||
// matrix, since that has 16.16 coefficients, and also I was having
|
||||
// weird issues that I can only assume where due to freetype doing
|
||||
// some bad caching or something when I did this using the matrix.
|
||||
const scale_x = width / glyph_width;
|
||||
const scale_y = height / glyph_height;
|
||||
const scale_x = width / rect.width;
|
||||
const scale_y = height / rect.height;
|
||||
const skew: f64 =
|
||||
if (self.synthetic.italic)
|
||||
// We skew by 12 degrees to synthesize italics.
|
||||
@@ -473,19 +540,24 @@ pub const Face = struct {
|
||||
else
|
||||
0.0;
|
||||
|
||||
var bbox_before: freetype.c.FT_BBox = undefined;
|
||||
_ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_before);
|
||||
|
||||
const outline = &glyph.*.outline;
|
||||
for (outline.points[0..@intCast(outline.n_points)]) |*p| {
|
||||
// Convert to f64 for processing
|
||||
var px = f26dot6ToF64(p.x);
|
||||
var py = f26dot6ToF64(p.y);
|
||||
|
||||
// Subtract original bearings
|
||||
px -= rect.x;
|
||||
py -= rect.y;
|
||||
|
||||
// Scale
|
||||
px *= scale_x;
|
||||
py *= scale_y;
|
||||
|
||||
// Add new bearings
|
||||
px += x;
|
||||
py += y - cell_baseline;
|
||||
|
||||
// Skew
|
||||
px += py * skew;
|
||||
|
||||
@@ -494,16 +566,6 @@ pub const Face = struct {
|
||||
p.y = @as(i32, @bitCast(F26Dot6.from(py)));
|
||||
}
|
||||
|
||||
var bbox_after: freetype.c.FT_BBox = undefined;
|
||||
_ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_after);
|
||||
|
||||
// If our bounding box changed, account for the lsb difference.
|
||||
//
|
||||
// This can happen when we skew glyphs that have a bit sticking
|
||||
// out to the left higher up, like the top of the T or the serif
|
||||
// on the lower case l in many monospace fonts.
|
||||
x += f26dot6ToF64(bbox_after.xMin) - f26dot6ToF64(bbox_before.xMin);
|
||||
|
||||
try self.face.renderGlyph(
|
||||
if (self.load_flags.monochrome)
|
||||
.mono
|
||||
@@ -601,6 +663,10 @@ pub const Face = struct {
|
||||
) != 0) {
|
||||
return error.BitmapHandlingError;
|
||||
}
|
||||
|
||||
// Update the bearings to account for the new positioning.
|
||||
glyph.*.bitmap_top = @intFromFloat(@floor(y - cell_baseline + height));
|
||||
glyph.*.bitmap_left = @intFromFloat(@floor(x));
|
||||
},
|
||||
|
||||
else => |f| {
|
||||
@@ -635,6 +701,20 @@ pub const Face = struct {
|
||||
},
|
||||
}
|
||||
|
||||
// Our whole-pixel bearings for the final glyph.
|
||||
// The fractional portion will be included in the rasterized position.
|
||||
//
|
||||
// For the Y position, FreeType's `bitmap_top` is the distance from the
|
||||
// baseline to the top of the glyph, but we need the distance from the
|
||||
// bottom of the cell to the bottom of the glyph, so first we add the
|
||||
// baseline to get the distance from the bottom of the cell to the top
|
||||
// of the glyph, then we subtract the height of the glyph to get the
|
||||
// bottom.
|
||||
const px_x: i32 = glyph.*.bitmap_left;
|
||||
const px_y: i32 = glyph.*.bitmap_top +
|
||||
@as(i32, @intCast(metrics.cell_baseline)) -
|
||||
@as(i32, @intCast(bitmap.rows));
|
||||
|
||||
const px_width = bitmap.width;
|
||||
const px_height = bitmap.rows;
|
||||
const len: usize = @intCast(
|
||||
@@ -670,48 +750,11 @@ pub const Face = struct {
|
||||
|
||||
// This should be the distance from the bottom of
|
||||
// the cell to the top of the glyph's bounding box.
|
||||
const offset_y: i32 =
|
||||
@as(i32, @intFromFloat(@floor(y))) +
|
||||
@as(i32, @intCast(px_height));
|
||||
const offset_y: i32 = px_y + @as(i32, @intCast(px_height));
|
||||
|
||||
// This should be the distance from the left of
|
||||
// the cell to the left of the glyph's bounding box.
|
||||
const offset_x: i32 = offset_x: {
|
||||
// If the glyph's advance is narrower than the cell width then we
|
||||
// center the advance of the glyph within the cell width. At first
|
||||
// I implemented this to proportionally scale the center position
|
||||
// of the glyph but that messes up glyphs that are meant to align
|
||||
// vertically with others, so this is a compromise.
|
||||
//
|
||||
// This makes it so that when the `adjust-cell-width` config is
|
||||
// used, or when a fallback font with a different advance width
|
||||
// is used, we don't get weirdly aligned glyphs.
|
||||
//
|
||||
// We don't do this if the constraint has a horizontal alignment,
|
||||
// since in that case the position was already calculated with the
|
||||
// new cell width in mind.
|
||||
if (opts.constraint.align_horizontal == .none) {
|
||||
const advance = f26dot6ToFloat(glyph.*.advance.x);
|
||||
const new_advance =
|
||||
cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1));
|
||||
// If the original advance is greater than the cell width then
|
||||
// it's possible that this is a ligature or other glyph that is
|
||||
// intended to overflow the cell to one side or the other, and
|
||||
// adjusting the bearings could mess that up, so we just leave
|
||||
// it alone if that's the case.
|
||||
//
|
||||
// We also don't want to do anything if the advance is zero or
|
||||
// less, since this is used for stuff like combining characters.
|
||||
if (advance > new_advance or advance <= 0.0) {
|
||||
break :offset_x @intFromFloat(@floor(x));
|
||||
}
|
||||
break :offset_x @intFromFloat(
|
||||
@floor(x + (new_advance - advance) / 2),
|
||||
);
|
||||
} else {
|
||||
break :offset_x @intFromFloat(@floor(x));
|
||||
}
|
||||
};
|
||||
const offset_x: i32 = px_x;
|
||||
|
||||
return Glyph{
|
||||
.width = px_width,
|
||||
|
@@ -247,7 +247,7 @@ pub const RunIterator = struct {
|
||||
if (j == self.i) current_font = font_info.idx;
|
||||
|
||||
// If our fonts are not equal, then we're done with our run.
|
||||
if (font_info.idx.int() != current_font.int()) break;
|
||||
if (font_info.idx != current_font) break;
|
||||
|
||||
// If we're a fallback character, add that and continue; we
|
||||
// don't want to add the entire grapheme.
|
||||
|
@@ -229,23 +229,39 @@ pub fn isCovering(cp: u21) bool {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns true of the codepoint is a "symbol-like" character, which
|
||||
/// for now we define as anything in a private use area and anything
|
||||
/// in the "dingbats" unicode block.
|
||||
///
|
||||
/// In the future it may be prudent to expand this to encompass more
|
||||
/// symbol-like characters, and/or exclude some PUA sections.
|
||||
pub fn isSymbol(cp: u21) bool {
|
||||
return ziglyph.general_category.isPrivateUse(cp) or
|
||||
ziglyph.blocks.isDingbats(cp);
|
||||
}
|
||||
|
||||
/// Returns the appropriate `constraint_width` for
|
||||
/// the provided cell when rendering its glyph(s).
|
||||
pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
||||
const cell = cell_pin.rowAndCell().cell;
|
||||
const cp = cell.codepoint();
|
||||
|
||||
if (!ziglyph.general_category.isPrivateUse(cp) and
|
||||
!ziglyph.blocks.isDingbats(cp))
|
||||
{
|
||||
return cell.gridWidth();
|
||||
}
|
||||
const grid_width = cell.gridWidth();
|
||||
|
||||
// If the grid width of the cell is 2, the constraint
|
||||
// width will always be 2, so we can just return early.
|
||||
if (grid_width > 1) return grid_width;
|
||||
|
||||
// We allow "symbol-like" glyphs to extend to 2 cells wide if there's
|
||||
// space, and if the previous glyph wasn't also a symbol. So if this
|
||||
// codepoint isn't a symbol then we can return the grid width.
|
||||
if (!isSymbol(cp)) return grid_width;
|
||||
|
||||
// If we are at the end of the screen it must be constrained to one cell.
|
||||
if (cell_pin.x == cell_pin.node.data.size.cols - 1) return 1;
|
||||
|
||||
// If we have a previous cell and it was PUA then we need to
|
||||
// also constrain. This is so that multiple PUA glyphs align.
|
||||
// If we have a previous cell and it was a symbol then we need
|
||||
// to also constrain. This is so that multiple PUA glyphs align.
|
||||
// As an exception, we ignore powerline glyphs since they are
|
||||
// used for box drawing and we consider them whitespace.
|
||||
if (cell_pin.x > 0) prev: {
|
||||
@@ -259,13 +275,13 @@ pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
||||
// We consider powerline glyphs whitespace.
|
||||
if (isPowerline(prev_cp)) break :prev;
|
||||
|
||||
if (ziglyph.general_category.isPrivateUse(prev_cp)) {
|
||||
if (isSymbol(prev_cp)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If the next cell is whitespace, then
|
||||
// we allow it to be up to two cells wide.
|
||||
// If the next cell is whitespace, then we
|
||||
// allow the glyph to be up to two cells wide.
|
||||
const next_cp = next_cp: {
|
||||
var copy = cell_pin;
|
||||
copy.x += 1;
|
||||
@@ -279,7 +295,7 @@ pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Must be constrained
|
||||
// Otherwise, this has to be 1 cell wide.
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user