mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-09 19:36:45 +00:00
Rewrite constraint code for improved icon scaling/alignment
This commit is contained in:

committed by
Mitchell Hashimoto

parent
81027f2211
commit
812dc7cf2f
@@ -409,9 +409,12 @@ pub const compatibility = std.StaticStringMap(
|
|||||||
/// necessarily force them to be. Decreasing this value will make nerd font
|
/// necessarily force them to be. Decreasing this value will make nerd font
|
||||||
/// icons smaller.
|
/// icons smaller.
|
||||||
///
|
///
|
||||||
/// The default value for the icon height is 1.2 times the height of capital
|
/// This value only applies to icons that are constrained to a single cell by
|
||||||
/// letters in your primary font, so something like -16.6% would make icons
|
/// neighboring characters. An icon that is free to spread across two cells
|
||||||
/// roughly the same height as capital letters.
|
/// can always use up to the full line height of the primary font.
|
||||||
|
///
|
||||||
|
/// The default value is 2/3 times the height of capital letters in your primary
|
||||||
|
/// font plus 1/3 times the font's line height.
|
||||||
///
|
///
|
||||||
/// See the notes about adjustments in `adjust-cell-width`.
|
/// See the notes about adjustments in `adjust-cell-width`.
|
||||||
///
|
///
|
||||||
|
@@ -1213,6 +1213,9 @@ test "metrics" {
|
|||||||
// and 1em should be the point size * dpi scale, so 12 * (96/72)
|
// and 1em should be the point size * dpi scale, so 12 * (96/72)
|
||||||
// which is 16, and 16 * 1.049 = 16.784, which finally is rounded
|
// which is 16, and 16 * 1.049 = 16.784, which finally is rounded
|
||||||
// to 17.
|
// to 17.
|
||||||
|
//
|
||||||
|
// The icon height is (2 * cap_height + face_height) / 3
|
||||||
|
// = (2 * 623 + 1049) / 3 = 765, and 16 * 0.765 = 12.24.
|
||||||
.cell_height = 17,
|
.cell_height = 17,
|
||||||
.cell_baseline = 3,
|
.cell_baseline = 3,
|
||||||
.underline_position = 17,
|
.underline_position = 17,
|
||||||
@@ -1223,7 +1226,10 @@ test "metrics" {
|
|||||||
.overline_thickness = 1,
|
.overline_thickness = 1,
|
||||||
.box_thickness = 1,
|
.box_thickness = 1,
|
||||||
.cursor_height = 17,
|
.cursor_height = 17,
|
||||||
.icon_height = 11,
|
.icon_height = 12.24,
|
||||||
|
.face_width = 8.0,
|
||||||
|
.face_height = 16.784,
|
||||||
|
.face_y = @round(3.04) - @as(f64, 3.04), // use f64, not comptime float, for exact match with runtime value
|
||||||
}, c.metrics);
|
}, c.metrics);
|
||||||
|
|
||||||
// Resize should change metrics
|
// Resize should change metrics
|
||||||
@@ -1240,7 +1246,10 @@ test "metrics" {
|
|||||||
.overline_thickness = 2,
|
.overline_thickness = 2,
|
||||||
.box_thickness = 2,
|
.box_thickness = 2,
|
||||||
.cursor_height = 34,
|
.cursor_height = 34,
|
||||||
.icon_height = 23,
|
.icon_height = 24.48,
|
||||||
|
.face_width = 16.0,
|
||||||
|
.face_height = 33.568,
|
||||||
|
.face_y = @round(6.08) - @as(f64, 6.08), // use f64, not comptime float, for exact match with runtime value
|
||||||
}, c.metrics);
|
}, c.metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,11 +36,17 @@ cursor_thickness: u32 = 1,
|
|||||||
cursor_height: u32,
|
cursor_height: u32,
|
||||||
|
|
||||||
/// The constraint height for nerd fonts icons.
|
/// The constraint height for nerd fonts icons.
|
||||||
icon_height: u32,
|
icon_height: f64,
|
||||||
|
|
||||||
/// Original cell width in pixels. This is used to keep
|
/// The unrounded face width, used in scaling calculations.
|
||||||
/// glyphs centered if the cell width is adjusted wider.
|
face_width: f64,
|
||||||
original_cell_width: ?u32 = null,
|
|
||||||
|
/// The unrounded face height, used in scaling calculations.
|
||||||
|
face_height: f64,
|
||||||
|
|
||||||
|
/// The vertical bearing of face within the pixel-rounded
|
||||||
|
/// and possibly height-adjusted cell
|
||||||
|
face_y: f64,
|
||||||
|
|
||||||
/// Minimum acceptable values for some fields to prevent modifiers
|
/// Minimum acceptable values for some fields to prevent modifiers
|
||||||
/// from being able to, for example, cause 0-thickness underlines.
|
/// from being able to, for example, cause 0-thickness underlines.
|
||||||
@@ -53,7 +59,9 @@ const Minimums = struct {
|
|||||||
const box_thickness = 1;
|
const box_thickness = 1;
|
||||||
const cursor_thickness = 1;
|
const cursor_thickness = 1;
|
||||||
const cursor_height = 1;
|
const cursor_height = 1;
|
||||||
const icon_height = 1;
|
const icon_height = 1.0;
|
||||||
|
const face_height = 1.0;
|
||||||
|
const face_width = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Metrics extracted from a font face, based on
|
/// Metrics extracted from a font face, based on
|
||||||
@@ -195,8 +203,10 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||||||
// We use the ceiling of the provided cell width and height to ensure
|
// We use the ceiling of the provided cell width and height to ensure
|
||||||
// that the cell is large enough for the provided size, since we cast
|
// that the cell is large enough for the provided size, since we cast
|
||||||
// it to an integer later.
|
// it to an integer later.
|
||||||
const cell_width = @ceil(face.cell_width);
|
const face_width = face.cell_width;
|
||||||
const cell_height = @ceil(face.lineHeight());
|
const face_height = face.lineHeight();
|
||||||
|
const cell_width = @ceil(face_width);
|
||||||
|
const cell_height = @ceil(face_height);
|
||||||
|
|
||||||
// We split our line gap in two parts, and put half of it on the top
|
// We split our line gap in two parts, and put half of it on the top
|
||||||
// of the cell and the other half on the bottom, so that our text never
|
// of the cell and the other half on the bottom, so that our text never
|
||||||
@@ -205,7 +215,11 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||||||
|
|
||||||
// Unlike all our other metrics, `cell_baseline` is relative to the
|
// Unlike all our other metrics, `cell_baseline` is relative to the
|
||||||
// BOTTOM of the cell.
|
// BOTTOM of the cell.
|
||||||
const cell_baseline = @round(half_line_gap - face.descent);
|
const face_baseline = half_line_gap - face.descent;
|
||||||
|
const cell_baseline = @round(face_baseline);
|
||||||
|
|
||||||
|
// We keep track of the vertical bearing of the face in the cell
|
||||||
|
const face_y = cell_baseline - face_baseline;
|
||||||
|
|
||||||
// We calculate a top_to_baseline to make following calculations simpler.
|
// We calculate a top_to_baseline to make following calculations simpler.
|
||||||
const top_to_baseline = cell_height - cell_baseline;
|
const top_to_baseline = cell_height - cell_baseline;
|
||||||
@@ -218,16 +232,8 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||||||
const underline_position = @round(top_to_baseline - face.underlinePosition());
|
const underline_position = @round(top_to_baseline - face.underlinePosition());
|
||||||
const strikethrough_position = @round(top_to_baseline - face.strikethroughPosition());
|
const strikethrough_position = @round(top_to_baseline - face.strikethroughPosition());
|
||||||
|
|
||||||
// The calculation for icon height in the nerd fonts patcher
|
// Same heuristic as the font_patcher script
|
||||||
// is two thirds cap height to one third line height, but we
|
const icon_height = (2 * cap_height + face_height) / 3;
|
||||||
// use an opinionated default of 1.2 * cap height instead.
|
|
||||||
//
|
|
||||||
// Doing this prevents fonts with very large line heights
|
|
||||||
// from having excessively oversized icons, and allows fonts
|
|
||||||
// with very small line heights to still have roomy icons.
|
|
||||||
//
|
|
||||||
// We do cap it at `cell_height` though for obvious reasons.
|
|
||||||
const icon_height = @min(cell_height, cap_height * 1.2);
|
|
||||||
|
|
||||||
var result: Metrics = .{
|
var result: Metrics = .{
|
||||||
.cell_width = @intFromFloat(cell_width),
|
.cell_width = @intFromFloat(cell_width),
|
||||||
@@ -241,7 +247,10 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||||||
.overline_thickness = @intFromFloat(underline_thickness),
|
.overline_thickness = @intFromFloat(underline_thickness),
|
||||||
.box_thickness = @intFromFloat(underline_thickness),
|
.box_thickness = @intFromFloat(underline_thickness),
|
||||||
.cursor_height = @intFromFloat(cell_height),
|
.cursor_height = @intFromFloat(cell_height),
|
||||||
.icon_height = @intFromFloat(icon_height),
|
.icon_height = icon_height,
|
||||||
|
.face_width = face_width,
|
||||||
|
.face_height = face_height,
|
||||||
|
.face_y = face_y,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure all metrics are within their allowable range.
|
// Ensure all metrics are within their allowable range.
|
||||||
@@ -267,11 +276,6 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||||||
const new = @max(entry.value_ptr.apply(original), 1);
|
const new = @max(entry.value_ptr.apply(original), 1);
|
||||||
if (new == original) continue;
|
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
|
// Set the new value
|
||||||
@field(self, @tagName(tag)) = new;
|
@field(self, @tagName(tag)) = new;
|
||||||
|
|
||||||
@@ -288,6 +292,7 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||||||
const diff = new - original;
|
const diff = new - original;
|
||||||
const diff_bottom = diff / 2;
|
const diff_bottom = diff / 2;
|
||||||
const diff_top = diff - diff_bottom;
|
const diff_top = diff - diff_bottom;
|
||||||
|
self.face_y += @floatFromInt(diff_bottom);
|
||||||
self.cell_baseline +|= diff_bottom;
|
self.cell_baseline +|= diff_bottom;
|
||||||
self.underline_position +|= diff_top;
|
self.underline_position +|= diff_top;
|
||||||
self.strikethrough_position +|= diff_top;
|
self.strikethrough_position +|= diff_top;
|
||||||
@@ -296,6 +301,7 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||||||
const diff = original - new;
|
const diff = original - new;
|
||||||
const diff_bottom = diff / 2;
|
const diff_bottom = diff / 2;
|
||||||
const diff_top = diff - diff_bottom;
|
const diff_top = diff - diff_bottom;
|
||||||
|
self.face_y -= @floatFromInt(diff_bottom);
|
||||||
self.cell_baseline -|= diff_bottom;
|
self.cell_baseline -|= diff_bottom;
|
||||||
self.underline_position -|= diff_top;
|
self.underline_position -|= diff_top;
|
||||||
self.strikethrough_position -|= diff_top;
|
self.strikethrough_position -|= diff_top;
|
||||||
@@ -398,25 +404,35 @@ pub const Modifier = union(enum) {
|
|||||||
/// Apply a modifier to a numeric value.
|
/// Apply a modifier to a numeric value.
|
||||||
pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
|
pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
|
||||||
const T = @TypeOf(v);
|
const T = @TypeOf(v);
|
||||||
const signed = @typeInfo(T).int.signedness == .signed;
|
const Tinfo = @typeInfo(T);
|
||||||
return switch (self) {
|
return switch (comptime Tinfo) {
|
||||||
.percent => |p| percent: {
|
.int, .comptime_int => switch (self) {
|
||||||
const p_clamped: f64 = @max(0, p);
|
.percent => |p| percent: {
|
||||||
const v_f64: f64 = @floatFromInt(v);
|
const p_clamped: f64 = @max(0, p);
|
||||||
const applied_f64: f64 = @round(v_f64 * p_clamped);
|
const v_f64: f64 = @floatFromInt(v);
|
||||||
const applied_T: T = @intFromFloat(applied_f64);
|
const applied_f64: f64 = @round(v_f64 * p_clamped);
|
||||||
break :percent applied_T;
|
const applied_T: T = @intFromFloat(applied_f64);
|
||||||
},
|
break :percent applied_T;
|
||||||
|
},
|
||||||
|
|
||||||
.absolute => |abs| absolute: {
|
.absolute => |abs| absolute: {
|
||||||
const v_i64: i64 = @intCast(v);
|
const v_i64: i64 = @intCast(v);
|
||||||
const abs_i64: i64 = @intCast(abs);
|
const abs_i64: i64 = @intCast(abs);
|
||||||
const applied_i64: i64 = v_i64 +| abs_i64;
|
const applied_i64: i64 = v_i64 +| abs_i64;
|
||||||
const clamped_i64: i64 = if (signed) applied_i64 else @max(0, applied_i64);
|
const clamped_i64: i64 = if (Tinfo.int.signedness == .signed)
|
||||||
const applied_T: T = std.math.cast(T, clamped_i64) orelse
|
applied_i64
|
||||||
std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
|
else
|
||||||
break :absolute applied_T;
|
@max(0, applied_i64);
|
||||||
|
const applied_T: T = std.math.cast(T, clamped_i64) orelse
|
||||||
|
std.math.maxInt(T) * @as(T, @intCast(std.math.sign(clamped_i64)));
|
||||||
|
break :absolute applied_T;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
.float, .comptime_float => return switch (self) {
|
||||||
|
.percent => |p| v * @max(0, p),
|
||||||
|
.absolute => |abs| v + @as(T, @floatFromInt(abs)),
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +478,7 @@ pub const Key = key: {
|
|||||||
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
for (field_infos, 0..) |field, i| {
|
for (field_infos, 0..) |field, i| {
|
||||||
if (field.type != u32 and field.type != i32) continue;
|
if (field.type != u32 and field.type != i32 and field.type != f64) continue;
|
||||||
enumFields[i] = .{ .name = field.name, .value = i };
|
enumFields[i] = .{ .name = field.name, .value = i };
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
@@ -493,7 +509,10 @@ fn init() Metrics {
|
|||||||
.overline_thickness = 0,
|
.overline_thickness = 0,
|
||||||
.box_thickness = 0,
|
.box_thickness = 0,
|
||||||
.cursor_height = 0,
|
.cursor_height = 0,
|
||||||
.icon_height = 0,
|
.icon_height = 0.0,
|
||||||
|
.face_width = 0.0,
|
||||||
|
.face_height = 0.0,
|
||||||
|
.face_y = 0.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,6 +542,7 @@ test "Metrics: adjust cell height smaller" {
|
|||||||
try set.put(alloc, .cell_height, .{ .percent = 0.75 });
|
try set.put(alloc, .cell_height, .{ .percent = 0.75 });
|
||||||
|
|
||||||
var m: Metrics = init();
|
var m: Metrics = init();
|
||||||
|
m.face_y = 0.33;
|
||||||
m.cell_baseline = 50;
|
m.cell_baseline = 50;
|
||||||
m.underline_position = 55;
|
m.underline_position = 55;
|
||||||
m.strikethrough_position = 30;
|
m.strikethrough_position = 30;
|
||||||
@@ -530,6 +550,7 @@ test "Metrics: adjust cell height smaller" {
|
|||||||
m.cell_height = 100;
|
m.cell_height = 100;
|
||||||
m.cursor_height = 100;
|
m.cursor_height = 100;
|
||||||
m.apply(set);
|
m.apply(set);
|
||||||
|
try testing.expectEqual(-11.67, m.face_y);
|
||||||
try testing.expectEqual(@as(u32, 75), m.cell_height);
|
try testing.expectEqual(@as(u32, 75), m.cell_height);
|
||||||
try testing.expectEqual(@as(u32, 38), m.cell_baseline);
|
try testing.expectEqual(@as(u32, 38), m.cell_baseline);
|
||||||
try testing.expectEqual(@as(u32, 42), m.underline_position);
|
try testing.expectEqual(@as(u32, 42), m.underline_position);
|
||||||
@@ -551,6 +572,7 @@ test "Metrics: adjust cell height larger" {
|
|||||||
try set.put(alloc, .cell_height, .{ .percent = 1.75 });
|
try set.put(alloc, .cell_height, .{ .percent = 1.75 });
|
||||||
|
|
||||||
var m: Metrics = init();
|
var m: Metrics = init();
|
||||||
|
m.face_y = 0.33;
|
||||||
m.cell_baseline = 50;
|
m.cell_baseline = 50;
|
||||||
m.underline_position = 55;
|
m.underline_position = 55;
|
||||||
m.strikethrough_position = 30;
|
m.strikethrough_position = 30;
|
||||||
@@ -558,6 +580,7 @@ test "Metrics: adjust cell height larger" {
|
|||||||
m.cell_height = 100;
|
m.cell_height = 100;
|
||||||
m.cursor_height = 100;
|
m.cursor_height = 100;
|
||||||
m.apply(set);
|
m.apply(set);
|
||||||
|
try testing.expectEqual(37.33, m.face_y);
|
||||||
try testing.expectEqual(@as(u32, 175), m.cell_height);
|
try testing.expectEqual(@as(u32, 175), m.cell_height);
|
||||||
try testing.expectEqual(@as(u32, 87), m.cell_baseline);
|
try testing.expectEqual(@as(u32, 87), m.cell_baseline);
|
||||||
try testing.expectEqual(@as(u32, 93), m.underline_position);
|
try testing.expectEqual(@as(u32, 93), m.underline_position);
|
||||||
|
@@ -270,11 +270,9 @@ pub fn renderGlyph(
|
|||||||
// Always use these constraints for emoji.
|
// Always use these constraints for emoji.
|
||||||
if (p == .emoji) {
|
if (p == .emoji) {
|
||||||
render_opts.constraint = .{
|
render_opts.constraint = .{
|
||||||
// Make the emoji as wide as possible, scaling proportionally,
|
// Scale emoji to be as large as possible
|
||||||
// but then scale it down as necessary if its new size exceeds
|
// while preserving their aspect ratio.
|
||||||
// the cell height.
|
.size = .cover,
|
||||||
.size_horizontal = .cover,
|
|
||||||
.size_vertical = .fit,
|
|
||||||
|
|
||||||
// Center the emoji in its cells.
|
// Center the emoji in its cells.
|
||||||
.align_horizontal = .center,
|
.align_horizontal = .center,
|
||||||
|
@@ -136,10 +136,8 @@ pub const RenderOptions = struct {
|
|||||||
/// Don't constrain the glyph in any way.
|
/// Don't constrain the glyph in any way.
|
||||||
pub const none: Constraint = .{};
|
pub const none: Constraint = .{};
|
||||||
|
|
||||||
/// Vertical sizing rule.
|
/// Sizing rule.
|
||||||
size_vertical: Size = .none,
|
size: Size = .none,
|
||||||
/// Horizontal sizing rule.
|
|
||||||
size_horizontal: Size = .none,
|
|
||||||
|
|
||||||
/// Vertical alignment rule.
|
/// Vertical alignment rule.
|
||||||
align_vertical: Align = .none,
|
align_vertical: Align = .none,
|
||||||
@@ -155,42 +153,40 @@ pub const RenderOptions = struct {
|
|||||||
/// Bottom padding when resizing.
|
/// Bottom padding when resizing.
|
||||||
pad_bottom: f64 = 0.0,
|
pad_bottom: f64 = 0.0,
|
||||||
|
|
||||||
// This acts as a multiple of the provided width when applying
|
// Size and bearings of the glyph relative
|
||||||
// constraints, so if this is 1.6 for example, then a width of
|
// to the bounding box of its scale group.
|
||||||
// 10 would be treated as though it were 16.
|
relative_width: f64 = 1.0,
|
||||||
group_width: f64 = 1.0,
|
relative_height: f64 = 1.0,
|
||||||
// This acts as a multiple of the provided height when applying
|
relative_x: f64 = 0.0,
|
||||||
// constraints, so if this is 1.6 for example, then a height of
|
relative_y: f64 = 0.0,
|
||||||
// 10 would be treated as though it were 16.
|
|
||||||
group_height: f64 = 1.0,
|
|
||||||
// This is an x offset for the actual width within the group width.
|
|
||||||
// If this is 0.5 then the glyph will be offset so that its left
|
|
||||||
// edge sits at the halfway point of the group width.
|
|
||||||
group_x: f64 = 0.0,
|
|
||||||
// This is a y offset for the actual height within the group height.
|
|
||||||
// If this is 0.5 then the glyph will be offset so that its bottom
|
|
||||||
// edge sits at the halfway point of the group height.
|
|
||||||
group_y: f64 = 0.0,
|
|
||||||
|
|
||||||
/// Maximum ratio of width to height when resizing.
|
/// Maximum aspect ratio (width/height) to allow when stretching.
|
||||||
max_xy_ratio: ?f64 = null,
|
max_xy_ratio: ?f64 = null,
|
||||||
|
|
||||||
/// Maximum number of cells horizontally to use.
|
/// Maximum number of cells horizontally to use.
|
||||||
max_constraint_width: u2 = 2,
|
max_constraint_width: u2 = 2,
|
||||||
|
|
||||||
/// What to use as the height metric when constraining the glyph.
|
/// What to use as the height metric when constraining the glyph and
|
||||||
|
/// the constraint width is 1,
|
||||||
height: Height = .cell,
|
height: Height = .cell,
|
||||||
|
|
||||||
pub const Size = enum {
|
pub const Size = enum {
|
||||||
/// Don't change the size of this glyph.
|
/// Don't change the size of this glyph.
|
||||||
none,
|
none,
|
||||||
/// Move the glyph and optionally scale it down
|
/// Scale the glyph down if needed to fit within the bounds,
|
||||||
/// proportionally to fit within the given axis.
|
/// preserving aspect ratio.
|
||||||
fit,
|
fit,
|
||||||
/// Move and resize the glyph proportionally to
|
/// Scale the glyph up or down to exactly match the bounds,
|
||||||
/// cover the given axis.
|
/// preserving aspect ratio.
|
||||||
cover,
|
cover,
|
||||||
/// Same as `cover` but not proportional.
|
/// Scale the glyph down if needed to fit within the bounds,
|
||||||
|
/// preserving aspect ratio. If the glyph doesn't cover a
|
||||||
|
/// single cell, scale up. If the glyph exceeds a single
|
||||||
|
/// cell but is within the bounds, do nothing.
|
||||||
|
/// (Nerd Font specific rule.)
|
||||||
|
fit_cover1,
|
||||||
|
/// Stretch the glyph to exactly fit the bounds in both
|
||||||
|
/// directions, disregarding aspect ratio.
|
||||||
stretch,
|
stretch,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -205,12 +201,18 @@ pub const RenderOptions = struct {
|
|||||||
end,
|
end,
|
||||||
/// Move the glyph so that it is centered on this axis.
|
/// Move the glyph so that it is centered on this axis.
|
||||||
center,
|
center,
|
||||||
|
/// Move the glyph so that it is centered on this axis,
|
||||||
|
/// but always with respect to the first cell even for
|
||||||
|
/// multi-cell constraints. (Nerd Font specific rule.)
|
||||||
|
center1,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Height = enum {
|
pub const Height = enum {
|
||||||
/// Use the full height of the cell for constraining this glyph.
|
/// Always use the full height of the cell for constraining this glyph.
|
||||||
cell,
|
cell,
|
||||||
/// Use the "icon height" from the grid metrics as the height.
|
/// When the constraint width is 1, use the "icon height" from the grid
|
||||||
|
/// metrics as the height. (When the constraint width is >1, the
|
||||||
|
/// constraint height is always the full cell height.)
|
||||||
icon,
|
icon,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,9 +228,8 @@ pub const RenderOptions = struct {
|
|||||||
/// because it neither sizes nor positions the glyph, then this
|
/// because it neither sizes nor positions the glyph, then this
|
||||||
/// returns false.
|
/// returns false.
|
||||||
pub inline fn doesAnything(self: Constraint) bool {
|
pub inline fn doesAnything(self: Constraint) bool {
|
||||||
return self.size_horizontal != .none or
|
return self.size != .none or
|
||||||
self.align_horizontal != .none or
|
self.align_horizontal != .none or
|
||||||
self.size_vertical != .none or
|
|
||||||
self.align_vertical != .none;
|
self.align_vertical != .none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,156 +242,202 @@ pub const RenderOptions = struct {
|
|||||||
/// Number of cells horizontally available for this glyph.
|
/// Number of cells horizontally available for this glyph.
|
||||||
constraint_width: u2,
|
constraint_width: u2,
|
||||||
) GlyphSize {
|
) GlyphSize {
|
||||||
var g = glyph;
|
if (!self.doesAnything()) return glyph;
|
||||||
|
|
||||||
const available_width: f64 = @floatFromInt(
|
// For extra wide font faces, never stretch glyphs across two cells.
|
||||||
metrics.cell_width * @min(
|
// This mirrors font_patcher.
|
||||||
self.max_constraint_width,
|
const min_constraint_width: u2 = if ((self.size == .stretch) and (metrics.face_width > 0.9 * metrics.face_height))
|
||||||
constraint_width,
|
1
|
||||||
),
|
else
|
||||||
);
|
@min(self.max_constraint_width, constraint_width);
|
||||||
const available_height: f64 = @floatFromInt(switch (self.height) {
|
|
||||||
.cell => metrics.cell_height,
|
|
||||||
.icon => metrics.icon_height,
|
|
||||||
});
|
|
||||||
|
|
||||||
const w = available_width -
|
// The bounding box for the glyph's scale group.
|
||||||
self.pad_left * available_width -
|
// Scaling and alignment rules are calculated for
|
||||||
self.pad_right * available_width;
|
// this box and then applied to the glyph.
|
||||||
const h = available_height -
|
var group: GlyphSize = group: {
|
||||||
self.pad_top * available_height -
|
const group_width = glyph.width / self.relative_width;
|
||||||
self.pad_bottom * available_height;
|
const group_height = glyph.height / self.relative_height;
|
||||||
|
break :group .{
|
||||||
// Subtract padding from the bearings so that our
|
.width = group_width,
|
||||||
// alignment and sizing code works correctly. We
|
.height = group_height,
|
||||||
// re-add before returning.
|
.x = glyph.x - (group_width * self.relative_x),
|
||||||
g.x -= self.pad_left * available_width;
|
.y = glyph.y - (group_height * self.relative_y),
|
||||||
g.y -= self.pad_bottom * available_height;
|
};
|
||||||
|
|
||||||
// Multiply by group width and height for better sizing.
|
|
||||||
g.width *= self.group_width;
|
|
||||||
g.height *= self.group_height;
|
|
||||||
|
|
||||||
switch (self.size_horizontal) {
|
|
||||||
.none => {},
|
|
||||||
.fit => if (g.width > w) {
|
|
||||||
const orig_height = g.height;
|
|
||||||
// Adjust our height and width to proportionally
|
|
||||||
// scale them to fit the glyph to the cell width.
|
|
||||||
g.height *= w / g.width;
|
|
||||||
g.width = w;
|
|
||||||
// Set our x to 0 since anything else would mean
|
|
||||||
// the glyph extends outside of the cell width.
|
|
||||||
g.x = 0;
|
|
||||||
// Compensate our y to keep things vertically
|
|
||||||
// centered as they're scaled down.
|
|
||||||
g.y += (orig_height - g.height) / 2;
|
|
||||||
} else if (g.width + g.x > w) {
|
|
||||||
// If the width of the glyph can fit in the cell but
|
|
||||||
// is currently outside due to the left bearing, then
|
|
||||||
// we reduce the left bearing just enough to fit it
|
|
||||||
// back in the cell.
|
|
||||||
g.x = w - g.width;
|
|
||||||
} else if (g.x < 0) {
|
|
||||||
g.x = 0;
|
|
||||||
},
|
|
||||||
.cover => {
|
|
||||||
const orig_height = g.height;
|
|
||||||
|
|
||||||
g.height *= w / g.width;
|
|
||||||
g.width = w;
|
|
||||||
|
|
||||||
g.x = 0;
|
|
||||||
|
|
||||||
g.y += (orig_height - g.height) / 2;
|
|
||||||
},
|
|
||||||
.stretch => {
|
|
||||||
g.width = w;
|
|
||||||
g.x = 0;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (self.size_vertical) {
|
|
||||||
.none => {},
|
|
||||||
.fit => if (g.height > h) {
|
|
||||||
const orig_width = g.width;
|
|
||||||
// Adjust our height and width to proportionally
|
|
||||||
// scale them to fit the glyph to the cell height.
|
|
||||||
g.width *= h / g.height;
|
|
||||||
g.height = h;
|
|
||||||
// Set our y to 0 since anything else would mean
|
|
||||||
// the glyph extends outside of the cell height.
|
|
||||||
g.y = 0;
|
|
||||||
// Compensate our x to keep things horizontally
|
|
||||||
// centered as they're scaled down.
|
|
||||||
g.x += (orig_width - g.width) / 2;
|
|
||||||
} else if (g.height + g.y > h) {
|
|
||||||
// If the height of the glyph can fit in the cell but
|
|
||||||
// is currently outside due to the bottom bearing, then
|
|
||||||
// we reduce the bottom bearing just enough to fit it
|
|
||||||
// back in the cell.
|
|
||||||
g.y = h - g.height;
|
|
||||||
} else if (g.y < 0) {
|
|
||||||
g.y = 0;
|
|
||||||
},
|
|
||||||
.cover => {
|
|
||||||
const orig_width = g.width;
|
|
||||||
|
|
||||||
g.width *= h / g.height;
|
|
||||||
g.height = h;
|
|
||||||
|
|
||||||
g.y = 0;
|
|
||||||
|
|
||||||
g.x += (orig_width - g.width) / 2;
|
|
||||||
},
|
|
||||||
.stretch => {
|
|
||||||
g.height = h;
|
|
||||||
g.y = 0;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add group-relative position
|
|
||||||
g.x += self.group_x * g.width;
|
|
||||||
g.y += self.group_y * g.height;
|
|
||||||
|
|
||||||
// Divide group width and height back out before we align.
|
|
||||||
g.width /= self.group_width;
|
|
||||||
g.height /= self.group_height;
|
|
||||||
|
|
||||||
if (self.max_xy_ratio) |ratio| if (g.width > g.height * ratio) {
|
|
||||||
const orig_width = g.width;
|
|
||||||
g.width = g.height * ratio;
|
|
||||||
g.x += (orig_width - g.width) / 2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (self.align_horizontal) {
|
// The new, constrained glyph size
|
||||||
.none => {},
|
var constrained_glyph = glyph;
|
||||||
.start => g.x = 0,
|
|
||||||
.end => g.x = w - g.width,
|
// Apply prescribed scaling
|
||||||
.center => g.x = (w - g.width) / 2,
|
const width_factor, const height_factor = self.scale_factors(group, metrics, min_constraint_width);
|
||||||
|
constrained_glyph.width *= width_factor;
|
||||||
|
constrained_glyph.x *= width_factor;
|
||||||
|
constrained_glyph.height *= height_factor;
|
||||||
|
constrained_glyph.y *= height_factor;
|
||||||
|
|
||||||
|
// NOTE: font_patcher jumps through a lot of hoops at this
|
||||||
|
// point to ensure that the glyph remains within the target
|
||||||
|
// bounding box after rounding to font definition units.
|
||||||
|
// This is irrelevant here as we're not rounding, we're
|
||||||
|
// staying in f64 and heading straight to rendering.
|
||||||
|
|
||||||
|
// Align vertically
|
||||||
|
if (self.align_vertical != .none) {
|
||||||
|
// Vertically scale group bounding box.
|
||||||
|
group.height *= height_factor;
|
||||||
|
group.y *= height_factor;
|
||||||
|
|
||||||
|
// Calculate offset and shift the glyph
|
||||||
|
constrained_glyph.y += self.offset_vertical(group, metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (self.align_vertical) {
|
// Align horizontally
|
||||||
.none => {},
|
if (self.align_horizontal != .none) {
|
||||||
.start => g.y = 0,
|
// Horizontally scale group bounding box.
|
||||||
.end => g.y = h - g.height,
|
group.width *= width_factor;
|
||||||
.center => g.y = (h - g.height) / 2,
|
group.x *= width_factor;
|
||||||
|
|
||||||
|
// Calculate offset and shift the glyph
|
||||||
|
constrained_glyph.x += self.offset_horizontal(group, metrics, min_constraint_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-add our padding before returning.
|
return constrained_glyph;
|
||||||
g.x += self.pad_left * available_width;
|
}
|
||||||
g.y += self.pad_bottom * available_height;
|
|
||||||
|
|
||||||
// If the available height is less than the cell height, we
|
/// Return width and height scaling factors for this scaling group.
|
||||||
// add half of the difference to center it in the full height.
|
fn scale_factors(
|
||||||
//
|
self: Constraint,
|
||||||
// If necessary, in the future, we can adjust this to account
|
group: GlyphSize,
|
||||||
// for alignment, but that isn't necessary with any of the nf
|
metrics: Metrics,
|
||||||
// icons afaict.
|
min_constraint_width: u2,
|
||||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
) struct { f64, f64 } {
|
||||||
g.y += (cell_height - available_height) / 2;
|
if (self.size == .none) {
|
||||||
|
return .{ 1.0, 1.0 };
|
||||||
|
}
|
||||||
|
|
||||||
return g;
|
const multi_cell = (min_constraint_width > 1);
|
||||||
|
|
||||||
|
const pad_width_factor = @as(f64, @floatFromInt(min_constraint_width)) - (self.pad_left + self.pad_right);
|
||||||
|
const pad_height_factor = 1 - (self.pad_bottom + self.pad_top);
|
||||||
|
|
||||||
|
const target_width = pad_width_factor * metrics.face_width;
|
||||||
|
const target_height = pad_height_factor * switch (self.height) {
|
||||||
|
.cell => metrics.face_height,
|
||||||
|
// icon_height only applies with single-cell constraints.
|
||||||
|
// This mirrors font_patcher.
|
||||||
|
.icon => if (multi_cell)
|
||||||
|
metrics.face_height
|
||||||
|
else
|
||||||
|
metrics.icon_height,
|
||||||
|
};
|
||||||
|
|
||||||
|
var width_factor = target_width / group.width;
|
||||||
|
var height_factor = target_height / group.height;
|
||||||
|
|
||||||
|
switch (self.size) {
|
||||||
|
.none => unreachable,
|
||||||
|
.fit => {
|
||||||
|
// Scale down to fit if needed
|
||||||
|
height_factor = @min(1, width_factor, height_factor);
|
||||||
|
width_factor = height_factor;
|
||||||
|
},
|
||||||
|
.cover => {
|
||||||
|
// Scale to cover
|
||||||
|
height_factor = @min(width_factor, height_factor);
|
||||||
|
width_factor = height_factor;
|
||||||
|
},
|
||||||
|
.fit_cover1 => {
|
||||||
|
// Scale down to fit or up to cover at least one cell
|
||||||
|
// NOTE: This is similar to font_patcher's "pa" mode,
|
||||||
|
// however, font_patcher will only do the upscaling
|
||||||
|
// part if the constraint width is 1, resulting in
|
||||||
|
// some icons becoming smaller when the constraint
|
||||||
|
// width increases. You'd see icons shrinking when
|
||||||
|
// opening up a space after them. This makes no
|
||||||
|
// sense, so we've fixed the rule such that these
|
||||||
|
// icons are scaled to the same size for multi-cell
|
||||||
|
// constraints as they would be for single-cell.
|
||||||
|
height_factor = @min(width_factor, height_factor);
|
||||||
|
if (multi_cell and (height_factor > 1)) {
|
||||||
|
// Call back into this function with
|
||||||
|
// constraint width 1 to get single-cell scale
|
||||||
|
// factors. We use the height factor as width
|
||||||
|
// could have been modified by max_xy_ratio.
|
||||||
|
_, const single_height_factor = self.scale_factors(group, metrics, 1);
|
||||||
|
height_factor = @max(1, single_height_factor);
|
||||||
|
}
|
||||||
|
width_factor = height_factor;
|
||||||
|
},
|
||||||
|
.stretch => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce aspect ratio if required
|
||||||
|
if (self.max_xy_ratio) |ratio| {
|
||||||
|
if (group.width * width_factor > group.height * height_factor * ratio) {
|
||||||
|
width_factor = group.height * height_factor * ratio / group.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ width_factor, height_factor };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return vertical offset needed to align this group
|
||||||
|
fn offset_vertical(
|
||||||
|
self: Constraint,
|
||||||
|
group: GlyphSize,
|
||||||
|
metrics: Metrics,
|
||||||
|
) f64 {
|
||||||
|
// We use face_height and offset by face_y, rather than
|
||||||
|
// using cell_height directly, to account for the asymmetry
|
||||||
|
// of the pixel cell around the face (a consequence of
|
||||||
|
// aligning the baseline with a pixel boundary rather than
|
||||||
|
// vertically centering the face).
|
||||||
|
const new_group_y = metrics.face_y + switch (self.align_vertical) {
|
||||||
|
.none => return 0.0,
|
||||||
|
.start => self.pad_bottom * metrics.face_height,
|
||||||
|
.end => end: {
|
||||||
|
const pad_top_dy = self.pad_top * metrics.face_height;
|
||||||
|
break :end metrics.face_height - pad_top_dy - group.height;
|
||||||
|
},
|
||||||
|
.center, .center1 => (metrics.face_height - group.height) / 2,
|
||||||
|
};
|
||||||
|
return new_group_y - group.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return horizontal offset needed to align this group
|
||||||
|
fn offset_horizontal(
|
||||||
|
self: Constraint,
|
||||||
|
group: GlyphSize,
|
||||||
|
metrics: Metrics,
|
||||||
|
min_constraint_width: u2,
|
||||||
|
) f64 {
|
||||||
|
// For multi-cell constraints, we align relative to the span
|
||||||
|
// from the left edge of the first face cell to the right
|
||||||
|
// edge of the last face cell as they sit within the rounded
|
||||||
|
// and adjusted pixel cell (centered if narrower than the
|
||||||
|
// pixel cell, left-aligned if wider).
|
||||||
|
const face_x, const full_face_span = facecalcs: {
|
||||||
|
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||||
|
const full_width: f64 = @floatFromInt(min_constraint_width * metrics.cell_width);
|
||||||
|
const cell_margin = cell_width - metrics.face_width;
|
||||||
|
break :facecalcs .{ @max(0, cell_margin / 2), full_width - cell_margin };
|
||||||
|
};
|
||||||
|
const pad_left_x = self.pad_left * metrics.face_width;
|
||||||
|
const new_group_x = face_x + switch (self.align_horizontal) {
|
||||||
|
.none => return 0.0,
|
||||||
|
.start => pad_left_x,
|
||||||
|
.end => end: {
|
||||||
|
const pad_right_dx = self.pad_right * metrics.face_width;
|
||||||
|
break :end @max(pad_left_x, full_face_span - pad_right_dx - group.width);
|
||||||
|
},
|
||||||
|
.center => @max(pad_left_x, (full_face_span - group.width) / 2),
|
||||||
|
// NOTE: .center1 implements the font_patcher rule of centering
|
||||||
|
// in the first cell even for multi-cell constraints. Since glyphs
|
||||||
|
// are not allowed to protrude to the left, this results in the
|
||||||
|
// left-alignment like .start when the glyph is wider than a cell.
|
||||||
|
.center1 => @max(pad_left_x, (metrics.face_width - group.width) / 2),
|
||||||
|
};
|
||||||
|
return new_group_x - group.x;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -388,19 +388,16 @@ pub const Face = struct {
|
|||||||
y = @round(y);
|
y = @round(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the cell width was adjusted wider, we re-center all glyphs
|
// We center all glyphs within the pixel-rounded and adjusted
|
||||||
// in the new width, so that they aren't weirdly off to the left.
|
// cell width if it's larger than the face width, so that they
|
||||||
if (metrics.original_cell_width) |original| recenter: {
|
// aren't weirdly off to the left.
|
||||||
// We don't do this if the constraint has a horizontal alignment,
|
//
|
||||||
// since in that case the position was already calculated with the
|
// We don't do this if the constraint has a horizontal alignment,
|
||||||
// new cell width in mind.
|
// since in that case the position was already calculated with the
|
||||||
if (opts.constraint.align_horizontal != .none) break :recenter;
|
// new cell width in mind.
|
||||||
|
if ((opts.constraint.align_horizontal == .none) and (metrics.face_width < cell_width)) {
|
||||||
// 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.
|
// We add half the difference to re-center.
|
||||||
x += (cell_width - @as(f64, @floatFromInt(original))) / 2;
|
x += (cell_width - metrics.face_width) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our whole-pixel bearings for the final glyph.
|
// Our whole-pixel bearings for the final glyph.
|
||||||
|
@@ -498,17 +498,14 @@ pub const Face = struct {
|
|||||||
y = @round(y);
|
y = @round(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the cell width was adjusted wider, we re-center all glyphs
|
// We center all glyphs within the pixel-rounded and adjusted
|
||||||
// in the new width, so that they aren't weirdly off to the left.
|
// cell width if it's larger than the face width, so that they
|
||||||
if (metrics.original_cell_width) |original| recenter: {
|
// aren't weirdly off to the left.
|
||||||
// We don't do this if the constraint has a horizontal alignment,
|
//
|
||||||
// since in that case the position was already calculated with the
|
// We don't do this if the constraint has a horizontal alignment,
|
||||||
// new cell width in mind.
|
// since in that case the position was already calculated with the
|
||||||
if (opts.constraint.align_horizontal != .none) break :recenter;
|
// new cell width in mind.
|
||||||
|
if ((opts.constraint.align_horizontal == .none) and (metrics.face_width < cell_width)) {
|
||||||
// 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.
|
// We add half the difference to re-center.
|
||||||
//
|
//
|
||||||
// NOTE: We round this to a whole-pixel amount because under
|
// NOTE: We round this to a whole-pixel amount because under
|
||||||
@@ -516,7 +513,7 @@ pub const Face = struct {
|
|||||||
// the case under CoreText. If we move the outlines by
|
// the case under CoreText. If we move the outlines by
|
||||||
// a non-whole-pixel amount, it completely ruins the
|
// a non-whole-pixel amount, it completely ruins the
|
||||||
// hinting.
|
// hinting.
|
||||||
x += @round((cell_width - @as(f64, @floatFromInt(original))) / 2);
|
x += @round((cell_width - metrics.face_width) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can render the glyph.
|
// Now we can render the glyph.
|
||||||
@@ -1187,25 +1184,31 @@ test "color emoji" {
|
|||||||
alloc,
|
alloc,
|
||||||
&atlas,
|
&atlas,
|
||||||
ft_font.glyphIndex('🥸').?,
|
ft_font.glyphIndex('🥸').?,
|
||||||
.{ .grid_metrics = .{
|
.{
|
||||||
.cell_width = 13,
|
.grid_metrics = .{
|
||||||
.cell_height = 24,
|
.cell_width = 13,
|
||||||
.cell_baseline = 0,
|
.cell_height = 24,
|
||||||
.underline_position = 0,
|
.cell_baseline = 0,
|
||||||
.underline_thickness = 0,
|
.underline_position = 0,
|
||||||
.strikethrough_position = 0,
|
.underline_thickness = 0,
|
||||||
.strikethrough_thickness = 0,
|
.strikethrough_position = 0,
|
||||||
.overline_position = 0,
|
.strikethrough_thickness = 0,
|
||||||
.overline_thickness = 0,
|
.overline_position = 0,
|
||||||
.box_thickness = 0,
|
.overline_thickness = 0,
|
||||||
.cursor_height = 0,
|
.box_thickness = 0,
|
||||||
.icon_height = 0,
|
.cursor_height = 0,
|
||||||
}, .constraint_width = 2, .constraint = .{
|
.icon_height = 0,
|
||||||
.size_horizontal = .cover,
|
.face_width = 13,
|
||||||
.size_vertical = .cover,
|
.face_height = 24,
|
||||||
.align_horizontal = .center,
|
.face_y = 0,
|
||||||
.align_vertical = .center,
|
},
|
||||||
} },
|
.constraint_width = 2,
|
||||||
|
.constraint = .{
|
||||||
|
.size = .fit,
|
||||||
|
.align_horizontal = .center,
|
||||||
|
.align_vertical = .center,
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
try testing.expectEqual(@as(u32, 24), glyph.height);
|
try testing.expectEqual(@as(u32, 24), glyph.height);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -50,10 +50,10 @@ class PatchSetAttributeEntry(TypedDict):
|
|||||||
stretch: str
|
stretch: str
|
||||||
params: dict[str, float | bool]
|
params: dict[str, float | bool]
|
||||||
|
|
||||||
group_x: float
|
relative_x: float
|
||||||
group_y: float
|
relative_y: float
|
||||||
group_width: float
|
relative_width: float
|
||||||
group_height: float
|
relative_height: float
|
||||||
|
|
||||||
|
|
||||||
class PatchSet(TypedDict):
|
class PatchSet(TypedDict):
|
||||||
@@ -143,7 +143,7 @@ def parse_alignment(val: str) -> str | None:
|
|||||||
return {
|
return {
|
||||||
"l": ".start",
|
"l": ".start",
|
||||||
"r": ".end",
|
"r": ".end",
|
||||||
"c": ".center",
|
"c": ".center1", # font-patcher specific centering rule, see face.zig
|
||||||
"": None,
|
"": None,
|
||||||
}.get(val, ".none")
|
}.get(val, ".none")
|
||||||
|
|
||||||
@@ -158,10 +158,10 @@ def attr_key(attr: PatchSetAttributeEntry) -> AttributeHash:
|
|||||||
float(params.get("overlap", 0.0)),
|
float(params.get("overlap", 0.0)),
|
||||||
float(params.get("xy-ratio", -1.0)),
|
float(params.get("xy-ratio", -1.0)),
|
||||||
float(params.get("ypadding", 0.0)),
|
float(params.get("ypadding", 0.0)),
|
||||||
float(attr.get("group_x", 0.0)),
|
float(attr.get("relative_x", 0.0)),
|
||||||
float(attr.get("group_y", 0.0)),
|
float(attr.get("relative_y", 0.0)),
|
||||||
float(attr.get("group_width", 1.0)),
|
float(attr.get("relative_width", 1.0)),
|
||||||
float(attr.get("group_height", 1.0)),
|
float(attr.get("relative_height", 1.0)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -187,10 +187,10 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry)
|
|||||||
stretch = attr.get("stretch", "")
|
stretch = attr.get("stretch", "")
|
||||||
params = attr.get("params", {})
|
params = attr.get("params", {})
|
||||||
|
|
||||||
group_x = attr.get("group_x", 0.0)
|
relative_x = attr.get("relative_x", 0.0)
|
||||||
group_y = attr.get("group_y", 0.0)
|
relative_y = attr.get("relative_y", 0.0)
|
||||||
group_width = attr.get("group_width", 1.0)
|
relative_width = attr.get("relative_width", 1.0)
|
||||||
group_height = attr.get("group_height", 1.0)
|
relative_height = attr.get("relative_height", 1.0)
|
||||||
|
|
||||||
overlap = params.get("overlap", 0.0)
|
overlap = params.get("overlap", 0.0)
|
||||||
xy_ratio = params.get("xy-ratio", -1.0)
|
xy_ratio = params.get("xy-ratio", -1.0)
|
||||||
@@ -204,28 +204,30 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry)
|
|||||||
|
|
||||||
s = f"{keys}\n => .{{\n"
|
s = f"{keys}\n => .{{\n"
|
||||||
|
|
||||||
# These translations don't quite capture the way
|
# This maps the font_patcher stretch rules to a Constrain instance
|
||||||
# the actual patcher does scaling, but they're a
|
# NOTE: some comments in font_patcher indicate that only x or y
|
||||||
# good enough compromise.
|
# would also be a valid spec, but no icons use it, so we won't
|
||||||
if "xy" in stretch:
|
# support it until we have to.
|
||||||
s += " .size_horizontal = .stretch,\n"
|
if "pa" in stretch:
|
||||||
s += " .size_vertical = .stretch,\n"
|
if "!" in stretch or overlap:
|
||||||
elif "!" in stretch or "^" in stretch:
|
s += " .size = .cover,\n"
|
||||||
s += " .size_horizontal = .cover,\n"
|
else:
|
||||||
s += " .size_vertical = .fit,\n"
|
s += " .size = .fit_cover1,\n"
|
||||||
|
elif "xy" in stretch:
|
||||||
|
s += " .size = .stretch,\n"
|
||||||
else:
|
else:
|
||||||
s += " .size_horizontal = .fit,\n"
|
print(f"Warning: Unknown stretch rule {stretch}")
|
||||||
s += " .size_vertical = .fit,\n"
|
|
||||||
|
|
||||||
# `^` indicates that scaling should fill
|
# `^` indicates that scaling should use the
|
||||||
# the whole cell, not just the icon height.
|
# full cell height, not just the icon height,
|
||||||
|
# even when the constraint width is 1
|
||||||
if "^" not in stretch:
|
if "^" not in stretch:
|
||||||
s += " .height = .icon,\n"
|
s += " .height = .icon,\n"
|
||||||
|
|
||||||
# There are two cases where we want to limit the constraint width to 1:
|
# There are two cases where we want to limit the constraint width to 1:
|
||||||
# - If there's a `1` in the stretch mode string.
|
# - If there's a `1` in the stretch mode string.
|
||||||
# - If the stretch mode is `xy` and there's not an explicit `2`.
|
# - If the stretch mode is not `pa` and there's not an explicit `2`.
|
||||||
if "1" in stretch or ("xy" in stretch and "2" not in stretch):
|
if "1" in stretch or ("pa" not in stretch and "2" not in stretch):
|
||||||
s += " .max_constraint_width = 1,\n"
|
s += " .max_constraint_width = 1,\n"
|
||||||
|
|
||||||
if align is not None:
|
if align is not None:
|
||||||
@@ -233,14 +235,14 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry)
|
|||||||
if valign is not None:
|
if valign is not None:
|
||||||
s += f" .align_vertical = {valign},\n"
|
s += f" .align_vertical = {valign},\n"
|
||||||
|
|
||||||
if group_width != 1.0:
|
if relative_width != 1.0:
|
||||||
s += f" .group_width = {group_width:.16f},\n"
|
s += f" .relative_width = {relative_width:.16f},\n"
|
||||||
if group_height != 1.0:
|
if relative_height != 1.0:
|
||||||
s += f" .group_height = {group_height:.16f},\n"
|
s += f" .relative_height = {relative_height:.16f},\n"
|
||||||
if group_x != 0.0:
|
if relative_x != 0.0:
|
||||||
s += f" .group_x = {group_x:.16f},\n"
|
s += f" .relative_x = {relative_x:.16f},\n"
|
||||||
if group_y != 0.0:
|
if relative_y != 0.0:
|
||||||
s += f" .group_y = {group_y:.16f},\n"
|
s += f" .relative_y = {relative_y:.16f},\n"
|
||||||
|
|
||||||
# `overlap` and `ypadding` are mutually exclusive,
|
# `overlap` and `ypadding` are mutually exclusive,
|
||||||
# this is asserted in the nerd fonts patcher itself.
|
# this is asserted in the nerd fonts patcher itself.
|
||||||
@@ -286,7 +288,7 @@ def generate_zig_switch_arms(
|
|||||||
yMin = math.inf
|
yMin = math.inf
|
||||||
xMax = -math.inf
|
xMax = -math.inf
|
||||||
yMax = -math.inf
|
yMax = -math.inf
|
||||||
individual_bounds: dict[int, tuple[int, int, int ,int]] = {}
|
individual_bounds: dict[int, tuple[int, int, int, int]] = {}
|
||||||
for cp in group:
|
for cp in group:
|
||||||
if cp not in cmap:
|
if cp not in cmap:
|
||||||
continue
|
continue
|
||||||
@@ -306,10 +308,10 @@ def generate_zig_switch_arms(
|
|||||||
this_bounds = individual_bounds[cp]
|
this_bounds = individual_bounds[cp]
|
||||||
this_width = this_bounds[2] - this_bounds[0]
|
this_width = this_bounds[2] - this_bounds[0]
|
||||||
this_height = this_bounds[3] - this_bounds[1]
|
this_height = this_bounds[3] - this_bounds[1]
|
||||||
entries[cp]["group_width"] = group_width / this_width
|
entries[cp]["relative_width"] = this_width / group_width
|
||||||
entries[cp]["group_height"] = group_height / this_height
|
entries[cp]["relative_height"] = this_height / group_height
|
||||||
entries[cp]["group_x"] = (this_bounds[0] - xMin) / group_width
|
entries[cp]["relative_x"] = (this_bounds[0] - xMin) / group_width
|
||||||
entries[cp]["group_y"] = (this_bounds[1] - yMin) / group_height
|
entries[cp]["relative_y"] = (this_bounds[1] - yMin) / group_height
|
||||||
|
|
||||||
del entries[0]
|
del entries[0]
|
||||||
|
|
||||||
@@ -350,7 +352,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
const Constraint = @import("face.zig").RenderOptions.Constraint;
|
const Constraint = @import("face.zig").RenderOptions.Constraint;
|
||||||
|
|
||||||
/// Get the a constraints for the provided codepoint.
|
/// Get the constraints for the provided codepoint.
|
||||||
pub fn getConstraint(cp: u21) ?Constraint {
|
pub fn getConstraint(cp: u21) ?Constraint {
|
||||||
return switch (cp) {
|
return switch (cp) {
|
||||||
""")
|
""")
|
||||||
|
@@ -3073,8 +3073,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||||||
// its cell(s), we don't modify the alignment at all.
|
// its cell(s), we don't modify the alignment at all.
|
||||||
.constraint = getConstraint(cp) orelse
|
.constraint = getConstraint(cp) orelse
|
||||||
if (cellpkg.isSymbol(cp)) .{
|
if (cellpkg.isSymbol(cp)) .{
|
||||||
.size_horizontal = .fit,
|
.size = .fit,
|
||||||
.size_vertical = .fit,
|
|
||||||
} else .none,
|
} else .none,
|
||||||
.constraint_width = constraintWidth(cell_pin),
|
.constraint_width = constraintWidth(cell_pin),
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user