fix(font): Rewrite constraint code for improved icon scaling/alignment (#8563)
> This PR will probably need a rebase on the final outcome of #8550 and ~#8552~ #8580, but I'm putting it out here so folks can begin taking a look if they want. This is a rewrite of the code that applies scaling and alignment constraints. The main intention is to further improve how Nerd Font icons are matched to the primary font. This PR aligns the calculations more closely with how the Nerd Font `font-patcher` script works, except in two cases where we can easily do something unambiguously better (one because of what's arguably a bug in the script, and one because we do multi-cell alignment with knowledge of the pixel-rounded cell grid). A goal of the rewrite is to make the scaling and alignment calculations as clear and easy to follow as possible. I'll lead with some screenshots. First the status quo, then this PR. <img width="505" height="357" alt="Screenshot 2025-09-07 at 17 23 51" src="https://github.com/user-attachments/assets/8e3ff9fd-3b66-4d54-be38-d54cf3b6cc5b" /><img width="505" height="357" alt="Screenshot 2025-09-07 at 17 20 39" src="https://github.com/user-attachments/assets/84fbe076-2e3f-4879-b9b2-91ce86b9ef5f" /> Relevant specs: macOS; 1920x1080; Ghostty config: ```ini font-family = "CommitMono" font-size = "15" adjust-cell-height = "+20%" ``` **Points to note** * Icons are generally larger, making better use of the available space. * Icons are aligned nearly a pixel lower, better matching the text. This is because alignment is now calculated from face metrics/bearings, not the pixel-rounded cell. (See more below.) * Relative sizes are better matched. Note especially that tall and narrow icons, like the git branch symbol and icons depicting sheets of paper, look conspicuously small in the status quo. With this PR, they're better matched to other icons. * Look at the letter Z icon I use as prompt character for zsh. It's _tiny_ in the status quo, but properly sized with this PR. This demonstrates the most important and clear-cut improvement we make over `font-patcher`. (See more below.) * Icons wider than a single cell are now left-aligned rather than centered across two cells. I think this is preferable and makes better use of space in most relevant contexts. - Consider a Neovim bufferline showing the buffer title as a filetype icon followed by the file name. Padding on the left would be a waste of space, but having that extra space on the right can improve legibility. - In listings, such as in the screenshots, columns look tidier when their left edges are straight rather than ragged. - This is how `font-patcher` does alignment, and thus what Nerd Font users and UI designers expect. **Implementation details** I won't get too deep in the weeds here; see the code and comments. In brief: * `size_horizontal` and `size_vertical` are combined to a single `size`, which can be `.none, .stretch, .fit, .cover` or `.fit_cover1`. The latter implements the `pa` rule from `font-patcher`, except it works better for icons that are small before scaling, like the letter Z prompt in the screenshots. In short, it preserves aspect ratio while clamping the size such that the icon `.cover`s at least one cell and `.fit`s within the available space. See code comments and ryanoasis/nerd-fonts/pull/1926 for details. * An alignment mode `.center1` is added, implementing the centering rule from `font-patcher` that I explained/defended above. In short, we center the icon _in the first cell_, even it's allowed to span multiple cells. For icons wider than a single cell, the lower bound that prevents them from protruding to the left kicks in and turns this into left-alignment. We keep the regular `.center` rule around for use with emojis, et cetera. * Scaling and alignment calculations only use the unrounded face metrics and bearings. This ensures that pixel rounding of the cell and baseline, and `adjust-cell-{width,height}`, don't affect scaling or relative alignment; the icons are always scaled and aligned to the _face_. (The one place we need to use cell metrics in the calculations is when we use `cell_width` to obtain the inter-cell padding needed to correctly center or right-align a glyph across two cells.) - We can do this with impunity because we're blessed with sprite glyphs in place of the "icons" that are actually box drawing and block graphics characters 🙌 **Guide** The meat of the changes is 100 % in `src/font/face.zig` and `src/font/nerd_font_codegen.py`. Changes to other files only amount to a) adding/changing some struct fields to get numbers to where they need to be (see `src/font/Metrics.zig`), and b) collateral updates to make otherwise unchanged code and tests work with/take advantage of the modified structs. Most files should have a clear and friendly diff. The exception is the bottom half of `src/font/face.zig`, where the diff is meaningless and the new code should just be reviewed on its own merits. This is the part where the `constrain` function is rewritten and refactored. Scarred by countless hours perusing `font-patcher`, I tried hard to make the math and logic easy to follow here. I hope I have succeeded 🤞pull/8962/head
commit
25dab0ecd8
|
|
@ -416,9 +416,12 @@ pub const compatibility = std.StaticStringMap(
|
|||
/// necessarily force them to be. Decreasing this value will make nerd font
|
||||
/// icons smaller.
|
||||
///
|
||||
/// The default value for the icon height is 1.2 times the height of capital
|
||||
/// letters in your primary font, so something like -16.6% would make icons
|
||||
/// roughly the same height as capital letters.
|
||||
/// This value only applies to icons that are constrained to a single cell by
|
||||
/// neighboring characters. An icon that is free to spread across two cells
|
||||
/// 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`.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1213,6 +1213,9 @@ test "metrics" {
|
|||
// 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
|
||||
// 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_baseline = 3,
|
||||
.underline_position = 17,
|
||||
|
|
@ -1223,7 +1226,10 @@ test "metrics" {
|
|||
.overline_thickness = 1,
|
||||
.box_thickness = 1,
|
||||
.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);
|
||||
|
||||
// Resize should change metrics
|
||||
|
|
@ -1240,7 +1246,10 @@ test "metrics" {
|
|||
.overline_thickness = 2,
|
||||
.box_thickness = 2,
|
||||
.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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,11 +36,17 @@ cursor_thickness: u32 = 1,
|
|||
cursor_height: u32,
|
||||
|
||||
/// The constraint height for nerd fonts icons.
|
||||
icon_height: u32,
|
||||
icon_height: f64,
|
||||
|
||||
/// Original cell width in pixels. This is used to keep
|
||||
/// glyphs centered if the cell width is adjusted wider.
|
||||
original_cell_width: ?u32 = null,
|
||||
/// The unrounded face width, used in scaling calculations.
|
||||
face_width: f64,
|
||||
|
||||
/// 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
|
||||
/// from being able to, for example, cause 0-thickness underlines.
|
||||
|
|
@ -53,7 +59,9 @@ const Minimums = struct {
|
|||
const box_thickness = 1;
|
||||
const cursor_thickness = 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
|
||||
|
|
@ -214,8 +222,10 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||
// 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
|
||||
// it to an integer later.
|
||||
const cell_width = @ceil(face.cell_width);
|
||||
const cell_height = @ceil(face.lineHeight());
|
||||
const face_width = face.cell_width;
|
||||
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
|
||||
// of the cell and the other half on the bottom, so that our text never
|
||||
|
|
@ -224,7 +234,11 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||
|
||||
// Unlike all our other metrics, `cell_baseline` is relative to the
|
||||
// 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.
|
||||
const top_to_baseline = cell_height - cell_baseline;
|
||||
|
|
@ -237,16 +251,8 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||
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
|
||||
// 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);
|
||||
// Same heuristic as the font_patcher script
|
||||
const icon_height = (2 * cap_height + face_height) / 3;
|
||||
|
||||
var result: Metrics = .{
|
||||
.cell_width = @intFromFloat(cell_width),
|
||||
|
|
@ -260,7 +266,10 @@ pub fn calc(face: FaceMetrics) Metrics {
|
|||
.overline_thickness = @intFromFloat(underline_thickness),
|
||||
.box_thickness = @intFromFloat(underline_thickness),
|
||||
.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.
|
||||
|
|
@ -286,11 +295,6 @@ 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;
|
||||
|
||||
|
|
@ -307,6 +311,7 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||
const diff = new - original;
|
||||
const diff_bottom = diff / 2;
|
||||
const diff_top = diff - diff_bottom;
|
||||
self.face_y += @floatFromInt(diff_bottom);
|
||||
self.cell_baseline +|= diff_bottom;
|
||||
self.underline_position +|= diff_top;
|
||||
self.strikethrough_position +|= diff_top;
|
||||
|
|
@ -315,6 +320,7 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
|||
const diff = original - new;
|
||||
const diff_bottom = diff / 2;
|
||||
const diff_top = diff - diff_bottom;
|
||||
self.face_y -= @floatFromInt(diff_bottom);
|
||||
self.cell_baseline -|= diff_bottom;
|
||||
self.underline_position -|= diff_top;
|
||||
self.strikethrough_position -|= diff_top;
|
||||
|
|
@ -417,25 +423,35 @@ pub const Modifier = union(enum) {
|
|||
/// Apply a modifier to a numeric value.
|
||||
pub fn apply(self: Modifier, v: anytype) @TypeOf(v) {
|
||||
const T = @TypeOf(v);
|
||||
const signed = @typeInfo(T).int.signedness == .signed;
|
||||
return switch (self) {
|
||||
.percent => |p| percent: {
|
||||
const p_clamped: f64 = @max(0, p);
|
||||
const v_f64: f64 = @floatFromInt(v);
|
||||
const applied_f64: f64 = @round(v_f64 * p_clamped);
|
||||
const applied_T: T = @intFromFloat(applied_f64);
|
||||
break :percent applied_T;
|
||||
},
|
||||
const Tinfo = @typeInfo(T);
|
||||
return switch (comptime Tinfo) {
|
||||
.int, .comptime_int => switch (self) {
|
||||
.percent => |p| percent: {
|
||||
const p_clamped: f64 = @max(0, p);
|
||||
const v_f64: f64 = @floatFromInt(v);
|
||||
const applied_f64: f64 = @round(v_f64 * p_clamped);
|
||||
const applied_T: T = @intFromFloat(applied_f64);
|
||||
break :percent applied_T;
|
||||
},
|
||||
|
||||
.absolute => |abs| absolute: {
|
||||
const v_i64: i64 = @intCast(v);
|
||||
const abs_i64: i64 = @intCast(abs);
|
||||
const applied_i64: i64 = v_i64 +| abs_i64;
|
||||
const clamped_i64: i64 = if (signed) applied_i64 else @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;
|
||||
.absolute => |abs| absolute: {
|
||||
const v_i64: i64 = @intCast(v);
|
||||
const abs_i64: i64 = @intCast(abs);
|
||||
const applied_i64: i64 = v_i64 +| abs_i64;
|
||||
const clamped_i64: i64 = if (Tinfo.int.signedness == .signed)
|
||||
applied_i64
|
||||
else
|
||||
@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 => {},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +497,7 @@ pub const Key = key: {
|
|||
var enumFields: [field_infos.len]std.builtin.Type.EnumField = undefined;
|
||||
var count: usize = 0;
|
||||
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 };
|
||||
count += 1;
|
||||
}
|
||||
|
|
@ -512,7 +528,10 @@ fn init() Metrics {
|
|||
.overline_thickness = 0,
|
||||
.box_thickness = 0,
|
||||
.cursor_height = 0,
|
||||
.icon_height = 0,
|
||||
.icon_height = 0.0,
|
||||
.face_width = 0.0,
|
||||
.face_height = 0.0,
|
||||
.face_y = 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -542,6 +561,7 @@ test "Metrics: adjust cell height smaller" {
|
|||
try set.put(alloc, .cell_height, .{ .percent = 0.75 });
|
||||
|
||||
var m: Metrics = init();
|
||||
m.face_y = 0.33;
|
||||
m.cell_baseline = 50;
|
||||
m.underline_position = 55;
|
||||
m.strikethrough_position = 30;
|
||||
|
|
@ -549,6 +569,7 @@ test "Metrics: adjust cell height smaller" {
|
|||
m.cell_height = 100;
|
||||
m.cursor_height = 100;
|
||||
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, 38), m.cell_baseline);
|
||||
try testing.expectEqual(@as(u32, 42), m.underline_position);
|
||||
|
|
@ -570,6 +591,7 @@ test "Metrics: adjust cell height larger" {
|
|||
try set.put(alloc, .cell_height, .{ .percent = 1.75 });
|
||||
|
||||
var m: Metrics = init();
|
||||
m.face_y = 0.33;
|
||||
m.cell_baseline = 50;
|
||||
m.underline_position = 55;
|
||||
m.strikethrough_position = 30;
|
||||
|
|
@ -577,6 +599,7 @@ test "Metrics: adjust cell height larger" {
|
|||
m.cell_height = 100;
|
||||
m.cursor_height = 100;
|
||||
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, 87), m.cell_baseline);
|
||||
try testing.expectEqual(@as(u32, 93), m.underline_position);
|
||||
|
|
|
|||
|
|
@ -270,11 +270,9 @@ pub fn renderGlyph(
|
|||
// Always use these constraints for emoji.
|
||||
if (p == .emoji) {
|
||||
render_opts.constraint = .{
|
||||
// Make the emoji as wide as possible, scaling proportionally,
|
||||
// but then scale it down as necessary if its new size exceeds
|
||||
// the cell height.
|
||||
.size_horizontal = .cover,
|
||||
.size_vertical = .fit,
|
||||
// Scale emoji to be as large as possible
|
||||
// while preserving their aspect ratio.
|
||||
.size = .cover,
|
||||
|
||||
// Center the emoji in its cells.
|
||||
.align_horizontal = .center,
|
||||
|
|
|
|||
|
|
@ -136,10 +136,8 @@ pub const RenderOptions = struct {
|
|||
/// Don't constrain the glyph in any way.
|
||||
pub const none: Constraint = .{};
|
||||
|
||||
/// Vertical sizing rule.
|
||||
size_vertical: Size = .none,
|
||||
/// Horizontal sizing rule.
|
||||
size_horizontal: Size = .none,
|
||||
/// Sizing rule.
|
||||
size: Size = .none,
|
||||
|
||||
/// Vertical alignment rule.
|
||||
align_vertical: Align = .none,
|
||||
|
|
@ -155,42 +153,40 @@ pub const RenderOptions = struct {
|
|||
/// Bottom padding when resizing.
|
||||
pad_bottom: f64 = 0.0,
|
||||
|
||||
// This acts as a multiple of the provided width when applying
|
||||
// constraints, so if this is 1.6 for example, then a width of
|
||||
// 10 would be treated as though it were 16.
|
||||
group_width: f64 = 1.0,
|
||||
// This acts as a multiple of the provided height when applying
|
||||
// constraints, so if this is 1.6 for example, then a height of
|
||||
// 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,
|
||||
// Size and bearings of the glyph relative
|
||||
// to the bounding box of its scale group.
|
||||
relative_width: f64 = 1.0,
|
||||
relative_height: f64 = 1.0,
|
||||
relative_x: f64 = 0.0,
|
||||
relative_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,
|
||||
|
||||
/// Maximum number of cells horizontally to use.
|
||||
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,
|
||||
|
||||
pub const Size = enum {
|
||||
/// Don't change the size of this glyph.
|
||||
none,
|
||||
/// Move the glyph and optionally scale it down
|
||||
/// proportionally to fit within the given axis.
|
||||
/// Scale the glyph down if needed to fit within the bounds,
|
||||
/// preserving aspect ratio.
|
||||
fit,
|
||||
/// Move and resize the glyph proportionally to
|
||||
/// cover the given axis.
|
||||
/// Scale the glyph up or down to exactly match the bounds,
|
||||
/// preserving aspect ratio.
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -205,12 +201,18 @@ pub const RenderOptions = struct {
|
|||
end,
|
||||
/// Move the glyph so that it is centered on this axis.
|
||||
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 {
|
||||
/// Use the full height of the cell for constraining this glyph.
|
||||
/// Always use the full height of the cell for constraining this glyph.
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -226,9 +228,8 @@ pub const RenderOptions = struct {
|
|||
/// 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
|
||||
return self.size != .none or
|
||||
self.align_horizontal != .none or
|
||||
self.size_vertical != .none or
|
||||
self.align_vertical != .none;
|
||||
}
|
||||
|
||||
|
|
@ -241,156 +242,202 @@ pub const RenderOptions = struct {
|
|||
/// Number of cells horizontally available for this glyph.
|
||||
constraint_width: u2,
|
||||
) GlyphSize {
|
||||
var g = glyph;
|
||||
if (!self.doesAnything()) return glyph;
|
||||
|
||||
const available_width: f64 = @floatFromInt(
|
||||
metrics.cell_width * @min(
|
||||
self.max_constraint_width,
|
||||
constraint_width,
|
||||
),
|
||||
);
|
||||
const available_height: f64 = @floatFromInt(switch (self.height) {
|
||||
.cell => metrics.cell_height,
|
||||
.icon => metrics.icon_height,
|
||||
});
|
||||
// For extra wide font faces, never stretch glyphs across two cells.
|
||||
// This mirrors font_patcher.
|
||||
const min_constraint_width: u2 = if ((self.size == .stretch) and (metrics.face_width > 0.9 * metrics.face_height))
|
||||
1
|
||||
else
|
||||
@min(self.max_constraint_width, constraint_width);
|
||||
|
||||
const w = available_width -
|
||||
self.pad_left * available_width -
|
||||
self.pad_right * available_width;
|
||||
const h = available_height -
|
||||
self.pad_top * available_height -
|
||||
self.pad_bottom * available_height;
|
||||
|
||||
// Subtract padding from the bearings so that our
|
||||
// alignment and sizing code works correctly. We
|
||||
// re-add before returning.
|
||||
g.x -= self.pad_left * available_width;
|
||||
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;
|
||||
// The bounding box for the glyph's scale group.
|
||||
// Scaling and alignment rules are calculated for
|
||||
// this box and then applied to the glyph.
|
||||
var group: GlyphSize = group: {
|
||||
const group_width = glyph.width / self.relative_width;
|
||||
const group_height = glyph.height / self.relative_height;
|
||||
break :group .{
|
||||
.width = group_width,
|
||||
.height = group_height,
|
||||
.x = glyph.x - (group_width * self.relative_x),
|
||||
.y = glyph.y - (group_height * self.relative_y),
|
||||
};
|
||||
};
|
||||
|
||||
switch (self.align_horizontal) {
|
||||
.none => {},
|
||||
.start => g.x = 0,
|
||||
.end => g.x = w - g.width,
|
||||
.center => g.x = (w - g.width) / 2,
|
||||
// The new, constrained glyph size
|
||||
var constrained_glyph = glyph;
|
||||
|
||||
// Apply prescribed scaling
|
||||
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) {
|
||||
.none => {},
|
||||
.start => g.y = 0,
|
||||
.end => g.y = h - g.height,
|
||||
.center => g.y = (h - g.height) / 2,
|
||||
// Align horizontally
|
||||
if (self.align_horizontal != .none) {
|
||||
// Horizontally scale group bounding box.
|
||||
group.width *= width_factor;
|
||||
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.
|
||||
g.x += self.pad_left * available_width;
|
||||
g.y += self.pad_bottom * available_height;
|
||||
return constrained_glyph;
|
||||
}
|
||||
|
||||
// If the available height is less than the cell height, we
|
||||
// add half of the difference to center it in the full height.
|
||||
//
|
||||
// If necessary, in the future, we can adjust this to account
|
||||
// for alignment, but that isn't necessary with any of the nf
|
||||
// icons afaict.
|
||||
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||
g.y += (cell_height - available_height) / 2;
|
||||
/// Return width and height scaling factors for this scaling group.
|
||||
fn scale_factors(
|
||||
self: Constraint,
|
||||
group: GlyphSize,
|
||||
metrics: Metrics,
|
||||
min_constraint_width: u2,
|
||||
) struct { f64, f64 } {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 center all glyphs within the pixel-rounded and adjusted
|
||||
// cell width if it's larger than the face width, so that they
|
||||
// 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
|
||||
// new cell width in mind.
|
||||
if ((opts.constraint.align_horizontal == .none) and (metrics.face_width < cell_width)) {
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -498,17 +498,14 @@ pub const Face = struct {
|
|||
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 center all glyphs within the pixel-rounded and adjusted
|
||||
// cell width if it's larger than the face width, so that they
|
||||
// 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
|
||||
// new cell width in mind.
|
||||
if ((opts.constraint.align_horizontal == .none) and (metrics.face_width < cell_width)) {
|
||||
// We add half the difference to re-center.
|
||||
//
|
||||
// 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
|
||||
// a non-whole-pixel amount, it completely ruins the
|
||||
// hinting.
|
||||
x += @round((cell_width - @as(f64, @floatFromInt(original))) / 2);
|
||||
x += @round((cell_width - metrics.face_width) / 2);
|
||||
}
|
||||
|
||||
// Now we can render the glyph.
|
||||
|
|
@ -1211,25 +1208,31 @@ test "color emoji" {
|
|||
alloc,
|
||||
&atlas,
|
||||
ft_font.glyphIndex('🥸').?,
|
||||
.{ .grid_metrics = .{
|
||||
.cell_width = 13,
|
||||
.cell_height = 24,
|
||||
.cell_baseline = 0,
|
||||
.underline_position = 0,
|
||||
.underline_thickness = 0,
|
||||
.strikethrough_position = 0,
|
||||
.strikethrough_thickness = 0,
|
||||
.overline_position = 0,
|
||||
.overline_thickness = 0,
|
||||
.box_thickness = 0,
|
||||
.cursor_height = 0,
|
||||
.icon_height = 0,
|
||||
}, .constraint_width = 2, .constraint = .{
|
||||
.size_horizontal = .cover,
|
||||
.size_vertical = .cover,
|
||||
.align_horizontal = .center,
|
||||
.align_vertical = .center,
|
||||
} },
|
||||
.{
|
||||
.grid_metrics = .{
|
||||
.cell_width = 13,
|
||||
.cell_height = 24,
|
||||
.cell_baseline = 0,
|
||||
.underline_position = 0,
|
||||
.underline_thickness = 0,
|
||||
.strikethrough_position = 0,
|
||||
.strikethrough_thickness = 0,
|
||||
.overline_position = 0,
|
||||
.overline_thickness = 0,
|
||||
.box_thickness = 0,
|
||||
.cursor_height = 0,
|
||||
.icon_height = 0,
|
||||
.face_width = 13,
|
||||
.face_height = 24,
|
||||
.face_y = 0,
|
||||
},
|
||||
.constraint_width = 2,
|
||||
.constraint = .{
|
||||
.size = .fit,
|
||||
.align_horizontal = .center,
|
||||
.align_vertical = .center,
|
||||
},
|
||||
},
|
||||
);
|
||||
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
|
||||
params: dict[str, float | bool]
|
||||
|
||||
group_x: float
|
||||
group_y: float
|
||||
group_width: float
|
||||
group_height: float
|
||||
relative_x: float
|
||||
relative_y: float
|
||||
relative_width: float
|
||||
relative_height: float
|
||||
|
||||
|
||||
class PatchSet(TypedDict):
|
||||
|
|
@ -143,7 +143,7 @@ def parse_alignment(val: str) -> str | None:
|
|||
return {
|
||||
"l": ".start",
|
||||
"r": ".end",
|
||||
"c": ".center",
|
||||
"c": ".center1", # font-patcher specific centering rule, see face.zig
|
||||
"": None,
|
||||
}.get(val, ".none")
|
||||
|
||||
|
|
@ -158,10 +158,10 @@ def attr_key(attr: PatchSetAttributeEntry) -> AttributeHash:
|
|||
float(params.get("overlap", 0.0)),
|
||||
float(params.get("xy-ratio", -1.0)),
|
||||
float(params.get("ypadding", 0.0)),
|
||||
float(attr.get("group_x", 0.0)),
|
||||
float(attr.get("group_y", 0.0)),
|
||||
float(attr.get("group_width", 1.0)),
|
||||
float(attr.get("group_height", 1.0)),
|
||||
float(attr.get("relative_x", 0.0)),
|
||||
float(attr.get("relative_y", 0.0)),
|
||||
float(attr.get("relative_width", 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", "")
|
||||
params = attr.get("params", {})
|
||||
|
||||
group_x = attr.get("group_x", 0.0)
|
||||
group_y = attr.get("group_y", 0.0)
|
||||
group_width = attr.get("group_width", 1.0)
|
||||
group_height = attr.get("group_height", 1.0)
|
||||
relative_x = attr.get("relative_x", 0.0)
|
||||
relative_y = attr.get("relative_y", 0.0)
|
||||
relative_width = attr.get("relative_width", 1.0)
|
||||
relative_height = attr.get("relative_height", 1.0)
|
||||
|
||||
overlap = params.get("overlap", 0.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"
|
||||
|
||||
# These translations don't quite capture the way
|
||||
# the actual patcher does scaling, but they're a
|
||||
# good enough compromise.
|
||||
if "xy" in stretch:
|
||||
s += " .size_horizontal = .stretch,\n"
|
||||
s += " .size_vertical = .stretch,\n"
|
||||
elif "!" in stretch or "^" in stretch:
|
||||
s += " .size_horizontal = .cover,\n"
|
||||
s += " .size_vertical = .fit,\n"
|
||||
# This maps the font_patcher stretch rules to a Constrain instance
|
||||
# NOTE: some comments in font_patcher indicate that only x or y
|
||||
# would also be a valid spec, but no icons use it, so we won't
|
||||
# support it until we have to.
|
||||
if "pa" in stretch:
|
||||
if "!" in stretch or overlap:
|
||||
s += " .size = .cover,\n"
|
||||
else:
|
||||
s += " .size = .fit_cover1,\n"
|
||||
elif "xy" in stretch:
|
||||
s += " .size = .stretch,\n"
|
||||
else:
|
||||
s += " .size_horizontal = .fit,\n"
|
||||
s += " .size_vertical = .fit,\n"
|
||||
print(f"Warning: Unknown stretch rule {stretch}")
|
||||
|
||||
# `^` indicates that scaling should fill
|
||||
# the whole cell, not just the icon height.
|
||||
# `^` indicates that scaling should use the
|
||||
# full cell height, not just the icon height,
|
||||
# even when the constraint width is 1
|
||||
if "^" not in stretch:
|
||||
s += " .height = .icon,\n"
|
||||
|
||||
# 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 the stretch mode is `xy` and there's not an explicit `2`.
|
||||
if "1" in stretch or ("xy" in stretch and "2" not in stretch):
|
||||
# - If the stretch mode is not `pa` and there's not an explicit `2`.
|
||||
if "1" in stretch or ("pa" not in stretch and "2" not in stretch):
|
||||
s += " .max_constraint_width = 1,\n"
|
||||
|
||||
if align is not None:
|
||||
|
|
@ -233,14 +235,14 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry)
|
|||
if valign is not None:
|
||||
s += f" .align_vertical = {valign},\n"
|
||||
|
||||
if group_width != 1.0:
|
||||
s += f" .group_width = {group_width:.16f},\n"
|
||||
if group_height != 1.0:
|
||||
s += f" .group_height = {group_height:.16f},\n"
|
||||
if group_x != 0.0:
|
||||
s += f" .group_x = {group_x:.16f},\n"
|
||||
if group_y != 0.0:
|
||||
s += f" .group_y = {group_y:.16f},\n"
|
||||
if relative_width != 1.0:
|
||||
s += f" .relative_width = {relative_width:.16f},\n"
|
||||
if relative_height != 1.0:
|
||||
s += f" .relative_height = {relative_height:.16f},\n"
|
||||
if relative_x != 0.0:
|
||||
s += f" .relative_x = {relative_x:.16f},\n"
|
||||
if relative_y != 0.0:
|
||||
s += f" .relative_y = {relative_y:.16f},\n"
|
||||
|
||||
# `overlap` and `ypadding` are mutually exclusive,
|
||||
# this is asserted in the nerd fonts patcher itself.
|
||||
|
|
@ -286,7 +288,7 @@ def generate_zig_switch_arms(
|
|||
yMin = math.inf
|
||||
xMax = -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:
|
||||
if cp not in cmap:
|
||||
continue
|
||||
|
|
@ -306,10 +308,10 @@ def generate_zig_switch_arms(
|
|||
this_bounds = individual_bounds[cp]
|
||||
this_width = this_bounds[2] - this_bounds[0]
|
||||
this_height = this_bounds[3] - this_bounds[1]
|
||||
entries[cp]["group_width"] = group_width / this_width
|
||||
entries[cp]["group_height"] = group_height / this_height
|
||||
entries[cp]["group_x"] = (this_bounds[0] - xMin) / group_width
|
||||
entries[cp]["group_y"] = (this_bounds[1] - yMin) / group_height
|
||||
entries[cp]["relative_width"] = this_width / group_width
|
||||
entries[cp]["relative_height"] = this_height / group_height
|
||||
entries[cp]["relative_x"] = (this_bounds[0] - xMin) / group_width
|
||||
entries[cp]["relative_y"] = (this_bounds[1] - yMin) / group_height
|
||||
|
||||
del entries[0]
|
||||
|
||||
|
|
@ -350,7 +352,7 @@ if __name__ == "__main__":
|
|||
|
||||
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 {
|
||||
return switch (cp) {
|
||||
""")
|
||||
|
|
|
|||
|
|
@ -3093,8 +3093,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
// its cell(s), we don't modify the alignment at all.
|
||||
.constraint = getConstraint(cp) orelse
|
||||
if (cellpkg.isSymbol(cp)) .{
|
||||
.size_horizontal = .fit,
|
||||
.size_vertical = .fit,
|
||||
.size = .fit,
|
||||
} else .none,
|
||||
.constraint_width = constraintWidth(cell_pin),
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue