mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
Merge 69b83e7de4d6969e1709b38d187bc2f5ae5cc199 into 015efcf9e562ac89da49d50ba8b2b2612ef13d2a
This commit is contained in:
@ -531,7 +531,7 @@ pub fn add(
|
||||
const nf_symbols = b.dependency("nerd_fonts_symbols_only", .{});
|
||||
step.root_module.addAnonymousImport(
|
||||
"nerd_fonts_symbols_only",
|
||||
.{ .root_source_file = nf_symbols.path("SymbolsNerdFontMono-Regular.ttf") },
|
||||
.{ .root_source_file = nf_symbols.path("SymbolsNerdFont-Regular.ttf") },
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -124,8 +124,7 @@ pub const AdjustSizeError = font.Face.GetMetricsError;
|
||||
|
||||
// Calculate a size for the provided face that will match it with the primary
|
||||
// font, metrically, to improve consistency with fallback fonts. Right now we
|
||||
// match the font based on the ex height, or the ideograph width if the font
|
||||
// has ideographs in it.
|
||||
// match the font based on the a preferred metric specified per fallback font.
|
||||
//
|
||||
// This returns null if load options is null or if self.load_options is null.
|
||||
//
|
||||
@ -134,7 +133,7 @@ pub const AdjustSizeError = font.Face.GetMetricsError;
|
||||
//
|
||||
// TODO: In the future, provide config options that allow the user to select
|
||||
// which metric should be matched for fallback fonts, instead of hard
|
||||
// coding it as ex height.
|
||||
// coding it in the font init code.
|
||||
pub fn adjustedSize(
|
||||
self: *Collection,
|
||||
face: *Face,
|
||||
@ -151,38 +150,26 @@ pub fn adjustedSize(
|
||||
const primary_metrics = try primary_face.getMetrics();
|
||||
const face_metrics = try face.getMetrics();
|
||||
|
||||
// We use the ex height to match our font sizes, so that the height of
|
||||
// lower-case letters matches between all fonts in the fallback chain.
|
||||
//
|
||||
// We estimate ex height as 0.75 * cap height if it's not specifically
|
||||
// provided, and we estimate cap height as 0.75 * ascent in the same case.
|
||||
//
|
||||
// If the fallback font has an ic_width we prefer that, for normalization
|
||||
// of CJK font sizes when mixed with latin fonts.
|
||||
//
|
||||
// We estimate the ic_width as twice the cell width if it isn't provided.
|
||||
var primary_cap = primary_metrics.cap_height orelse 0.0;
|
||||
if (primary_cap <= 0) primary_cap = primary_metrics.ascent * 0.75;
|
||||
|
||||
var primary_ex = primary_metrics.ex_height orelse 0.0;
|
||||
if (primary_ex <= 0) primary_ex = primary_cap * 0.75;
|
||||
|
||||
var primary_ic = primary_metrics.ic_width orelse 0.0;
|
||||
if (primary_ic <= 0) primary_ic = primary_metrics.cell_width * 2;
|
||||
|
||||
var face_cap = face_metrics.cap_height orelse 0.0;
|
||||
if (face_cap <= 0) face_cap = face_metrics.ascent * 0.75;
|
||||
|
||||
var face_ex = face_metrics.ex_height orelse 0.0;
|
||||
if (face_ex <= 0) face_ex = face_cap * 0.75;
|
||||
|
||||
var face_ic = face_metrics.ic_width orelse 0.0;
|
||||
if (face_ic <= 0) face_ic = face_metrics.cell_width * 2;
|
||||
// The preferred metric to normalize by is specified by
|
||||
// face.reference_metric, however we don't want to normalize by a
|
||||
// metric not explicitly defined in `face`, so if needed we fall
|
||||
// back through the other possible reference metrics in the order
|
||||
// shown in the switch statement below. If the reference metric is
|
||||
// not defined in the primary font, we use the default estimate from
|
||||
// the face metrics.
|
||||
const line_height_scale = primary_metrics.lineHeight() / face_metrics.lineHeight();
|
||||
const scale: f64 = normalize_by: switch (face.reference_metric) {
|
||||
.ic_width => if (face_metrics.ic_width) |value| primary_metrics.icWidth() / value else continue :normalize_by .ex_height,
|
||||
.ex_height => if (face_metrics.ex_height) |value| primary_metrics.exHeight() / value else continue :normalize_by .cap_height,
|
||||
.cap_height => if (face_metrics.cap_height) |value| primary_metrics.capHeight() / value else continue :normalize_by .line_height,
|
||||
.line_height => line_height_scale,
|
||||
.em_size => 1.0,
|
||||
};
|
||||
|
||||
// If the line height of the scaled font would be larger than
|
||||
// the line height of the primary font, we don't want that, so
|
||||
// we take the minimum between matching the ic/ex and the line
|
||||
// height.
|
||||
// we take the minimum between matching the reference metric
|
||||
// and keeping the line heights within some margin.
|
||||
//
|
||||
// NOTE: We actually allow the line height to be up to 1.2
|
||||
// times the primary line height because empirically
|
||||
@ -190,19 +177,13 @@ pub fn adjustedSize(
|
||||
//
|
||||
// TODO: We should probably provide a config option that lets
|
||||
// the user pick what metric to use for size adjustment.
|
||||
const scale = @min(
|
||||
1.2 * primary_metrics.lineHeight() / face_metrics.lineHeight(),
|
||||
if (face_metrics.ic_width != null)
|
||||
primary_ic / face_ic
|
||||
else
|
||||
primary_ex / face_ex,
|
||||
);
|
||||
const capped_scale = @min(scale, 1.2 * line_height_scale);
|
||||
|
||||
// Make a copy of our load options, set the size to the size of
|
||||
// the provided face, and then multiply that by our scaling factor.
|
||||
var opts = load_options;
|
||||
opts.size = face.size;
|
||||
opts.size.points *= @as(f32, @floatCast(scale));
|
||||
opts.size.points *= @as(f32, @floatCast(capped_scale));
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
@ -120,6 +120,60 @@ pub const FaceMetrics = struct {
|
||||
pub inline fn lineHeight(self: FaceMetrics) f64 {
|
||||
return self.ascent - self.descent + self.line_gap;
|
||||
}
|
||||
|
||||
/// Convenience function for getting the cap height. If this is not
|
||||
/// defined in the font, we estimate it as 75% of the ascent.
|
||||
pub inline fn capHeight(self: FaceMetrics) f64 {
|
||||
if (self.cap_height) |value| if (value > 0) return value;
|
||||
return 0.75 * self.ascent;
|
||||
}
|
||||
|
||||
/// Convenience function for getting the ex height. If this is not
|
||||
/// defined in the font, we estimate it as 75% of the cap height.
|
||||
pub inline fn exHeight(self: FaceMetrics) f64 {
|
||||
if (self.ex_height) |value| if (value > 0) return value;
|
||||
return 0.75 * self.capHeight();
|
||||
}
|
||||
|
||||
/// Convenience function for getting the ideograph width. If this is
|
||||
/// not defined in the font, we estimate it as two cell widths.
|
||||
pub inline fn icWidth(self: FaceMetrics) f64 {
|
||||
if (self.ic_width) |value| if (value > 0) return value;
|
||||
return 2 * self.cell_width;
|
||||
}
|
||||
|
||||
/// Convenience function for getting the underline thickness. If
|
||||
/// this is not defined in the font, we estimate it as 15% of the ex
|
||||
/// height.
|
||||
pub inline fn underlineThickness(self: FaceMetrics) f64 {
|
||||
if (self.underline_thickness) |value| if (value > 0) return value;
|
||||
return 0.15 * self.exHeight();
|
||||
}
|
||||
|
||||
/// Convenience function for getting the strikethrough thickness. If
|
||||
/// this is not defined in the font, we set it equal to the
|
||||
/// underline thickness.
|
||||
pub inline fn strikethroughThickness(self: FaceMetrics) f64 {
|
||||
if (self.strikethrough_thickness) |value| if (value > 0) return value;
|
||||
return self.underlineThickness();
|
||||
}
|
||||
|
||||
// NOTE: The getters below return positions, not sizes, so both
|
||||
// positive and negative values are valid, hence no sign validation.
|
||||
|
||||
/// Convenience function for getting the underline position. If
|
||||
/// this is not defined in the font, we place it one underline
|
||||
/// thickness below the baseline.
|
||||
pub inline fn underlinePosition(self: FaceMetrics) f64 {
|
||||
return self.underline_position orelse -self.underlineThickness();
|
||||
}
|
||||
|
||||
/// Convenience function for getting the strikethrough position. If
|
||||
/// this is not defined in the font, we center it at half the ex
|
||||
/// height, so that it's perfectly centered on lower case text.
|
||||
pub inline fn strikethroughPosition(self: FaceMetrics) f64 {
|
||||
return self.strikethrough_position orelse (self.exHeight() + self.strikethroughThickness()) * 0.5;
|
||||
}
|
||||
};
|
||||
|
||||
/// Calculate our metrics based on values extracted from a font.
|
||||
@ -147,35 +201,13 @@ pub fn calc(face: FaceMetrics) Metrics {
|
||||
// We calculate a top_to_baseline to make following calculations simpler.
|
||||
const top_to_baseline = cell_height - cell_baseline;
|
||||
|
||||
// If we don't have a provided cap height,
|
||||
// we estimate it as 75% of the ascent.
|
||||
const cap_height = face.cap_height orelse face.ascent * 0.75;
|
||||
|
||||
// If we don't have a provided ex height,
|
||||
// we estimate it as 75% of the cap height.
|
||||
const ex_height = face.ex_height orelse cap_height * 0.75;
|
||||
|
||||
// If we don't have a provided underline thickness,
|
||||
// we estimate it as 15% of the ex height.
|
||||
const underline_thickness = @max(1, @ceil(face.underline_thickness orelse 0.15 * ex_height));
|
||||
|
||||
// If we don't have a provided strikethrough thickness
|
||||
// then we just use the underline thickness for it.
|
||||
const strikethrough_thickness = @max(1, @ceil(face.strikethrough_thickness orelse underline_thickness));
|
||||
|
||||
// If we don't have a provided underline position then
|
||||
// we place it 1 underline-thickness below the baseline.
|
||||
const underline_position = @round(top_to_baseline -
|
||||
(face.underline_position orelse
|
||||
-underline_thickness));
|
||||
|
||||
// If we don't have a provided strikethrough position
|
||||
// then we center the strikethrough stroke at half the
|
||||
// ex height, so that it's perfectly centered on lower
|
||||
// case text.
|
||||
const strikethrough_position = @round(top_to_baseline -
|
||||
(face.strikethrough_position orelse
|
||||
ex_height * 0.5 + strikethrough_thickness * 0.5));
|
||||
// Get the other font metrics or their estimates. See doc comments
|
||||
// in FaceMetrics for explanations of the estimation heuristics.
|
||||
const cap_height = face.capHeight();
|
||||
const underline_thickness = @max(1, @ceil(face.underlineThickness()));
|
||||
const strikethrough_thickness = @max(1, @ceil(face.strikethroughThickness()));
|
||||
const underline_position = @round(top_to_baseline - face.underlinePosition());
|
||||
const strikethrough_position = @round(top_to_baseline - face.strikethroughPosition());
|
||||
|
||||
// The calculation for icon height in the nerd fonts patcher
|
||||
// is two thirds cap height to one third line height, but we
|
||||
|
@ -305,7 +305,7 @@ fn collection(
|
||||
.{ .fallback_loaded = try Face.init(
|
||||
self.font_lib,
|
||||
font.embedded.symbols_nerd_font,
|
||||
load_options.faceOptions(),
|
||||
load_options.faceOptions().setReferenceMetric(.em_size),
|
||||
) },
|
||||
);
|
||||
|
||||
|
@ -39,6 +39,19 @@ pub const freetype_load_flags_default: FreetypeLoadFlags = if (FreetypeLoadFlags
|
||||
pub const Options = struct {
|
||||
size: DesiredSize,
|
||||
freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default,
|
||||
|
||||
// reference_metric defaults to ic_width to ensure appropriate
|
||||
// normalization of CJK font sizes when mixed with latin fonts. See
|
||||
// the implementation of Collections.adjustedSize() for fallback
|
||||
// rules when the font does not define the specified metric.
|
||||
reference_metric: ReferenceMetric = .ic_width,
|
||||
|
||||
// Convenience function to create a copy with a different reference metric.
|
||||
pub fn setReferenceMetric(self: Options, reference_metric: ReferenceMetric) Options {
|
||||
var opts = self;
|
||||
opts.reference_metric = reference_metric;
|
||||
return opts;
|
||||
}
|
||||
};
|
||||
|
||||
/// The desired size for loading a font.
|
||||
@ -57,6 +70,20 @@ pub const DesiredSize = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const ReferenceMetric = enum {
|
||||
// The font's ideograph width
|
||||
ic_width,
|
||||
// The font's ex height
|
||||
ex_height,
|
||||
// The font's cap height
|
||||
cap_height,
|
||||
// The font's line height
|
||||
line_height,
|
||||
// The font's em size (i.e., what point sizes like 12 refer to).
|
||||
// Usually equivalent to line height, but that's just convention.
|
||||
em_size,
|
||||
};
|
||||
|
||||
/// A font variation setting. The best documentation for this I know of
|
||||
/// is actually the CSS font-variation-settings property on MDN:
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings
|
||||
@ -222,7 +249,7 @@ pub const RenderOptions = struct {
|
||||
) GlyphSize {
|
||||
var g = glyph;
|
||||
|
||||
var available_width: f64 = @floatFromInt(
|
||||
const available_width: f64 = @floatFromInt(
|
||||
metrics.cell_width * @min(
|
||||
self.max_constraint_width,
|
||||
constraint_width,
|
||||
@ -233,22 +260,6 @@ pub const RenderOptions = struct {
|
||||
.icon => metrics.icon_height,
|
||||
});
|
||||
|
||||
// We make the opinionated choice here to reduce the width
|
||||
// of icon-height symbols by the same amount horizontally,
|
||||
// since otherwise wide aspect ratio icons like folders end
|
||||
// up far too wide.
|
||||
//
|
||||
// But we *only* do this if the constraint width is 2, since
|
||||
// otherwise it would make them way too small when sized for
|
||||
// a single cell.
|
||||
const is_icon_width = self.height == .icon and @min(self.max_constraint_width, constraint_width) > 1;
|
||||
const orig_avail_width = available_width;
|
||||
if (is_icon_width) {
|
||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
const ratio = available_height / cell_height;
|
||||
available_width *= ratio;
|
||||
}
|
||||
|
||||
const w = available_width -
|
||||
self.pad_left * available_width -
|
||||
self.pad_right * available_width;
|
||||
@ -372,11 +383,6 @@ pub const RenderOptions = struct {
|
||||
.center => g.y = (h - g.height) / 2,
|
||||
}
|
||||
|
||||
// Add offset for icon width restriction, to keep it centered.
|
||||
if (is_icon_width) {
|
||||
g.x += (orig_avail_width - available_width) / 2;
|
||||
}
|
||||
|
||||
// Re-add our padding before returning.
|
||||
g.x += self.pad_left * available_width;
|
||||
g.y += self.pad_bottom * available_height;
|
||||
|
@ -34,6 +34,10 @@ pub const Face = struct {
|
||||
/// The current size this font is set to.
|
||||
size: font.face.DesiredSize,
|
||||
|
||||
// The preferred font metric to use when normalizing the size of a
|
||||
// fallback font to the primary font.
|
||||
reference_metric: font.face.ReferenceMetric,
|
||||
|
||||
/// True if our build is using Harfbuzz. If we're not, we can avoid
|
||||
/// some Harfbuzz-specific code paths.
|
||||
const harfbuzz_shaper = font.options.backend.hasHarfbuzz();
|
||||
@ -110,6 +114,7 @@ pub const Face = struct {
|
||||
.hb_font = hb_font,
|
||||
.color = color,
|
||||
.size = opts.size,
|
||||
.reference_metric = opts.reference_metric,
|
||||
};
|
||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||
|
||||
|
@ -62,6 +62,10 @@ pub const Face = struct {
|
||||
/// The current size this font is set to.
|
||||
size: font.face.DesiredSize,
|
||||
|
||||
// The preferred font metric to use when normalizing the size of a
|
||||
// fallback font to the primary font.
|
||||
reference_metric: font.face.ReferenceMetric,
|
||||
|
||||
/// Initialize a new font face with the given source in-memory.
|
||||
pub fn initFile(
|
||||
lib: Library,
|
||||
@ -111,6 +115,7 @@ pub const Face = struct {
|
||||
.ft_mutex = ft_mutex,
|
||||
.load_flags = opts.freetype_load_flags,
|
||||
.size = opts.size,
|
||||
.reference_metric = opts.reference_metric,
|
||||
};
|
||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||
|
||||
|
Reference in New Issue
Block a user