From 143066093303adb890c438a562275042ad19350c Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 8 Jul 2025 11:56:49 -0600 Subject: [PATCH 1/2] font: constrain width of two-cell icon-height icons This stops things like folder icons from becoming over-wide. The patcher typically makes these glyphs always 1 cell wide, but since we know how it will be displayed we have the benefit of being able to make it more than 1 cell when there's room. This makes our dynamic scaling *better* than a static patched font :D --- src/font/face.zig | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/font/face.zig b/src/font/face.zig index 8c1171fb4..dc36b0286 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -205,7 +205,7 @@ pub const RenderOptions = struct { ) GlyphSize { var g = glyph; - const available_width: f64 = @floatFromInt( + var available_width: f64 = @floatFromInt( metrics.cell_width * @min( self.max_constraint_width, constraint_width, @@ -216,6 +216,22 @@ pub const RenderOptions = struct { .icon => metrics.icon_height, }); + // We make the opinionated choice here to reduce the width + // of icon-height symbols by the same amount horizontally, + // since otherwise wide aspect ratio icons like folders end + // up far too wide. + // + // But we *only* do this if the constraint width is 2, since + // otherwise it would make them way too small when sized for + // a single cell. + const is_icon_width = self.height == .icon and @min(self.max_constraint_width, constraint_width) > 1; + const orig_avail_width = available_width; + if (is_icon_width) { + const cell_height: f64 = @floatFromInt(metrics.cell_height); + const ratio = available_height / cell_height; + available_width *= ratio; + } + const w = available_width - self.pad_left * available_width - self.pad_right * available_width; @@ -327,6 +343,11 @@ pub const RenderOptions = struct { .center => g.y = (h - g.height) / 2, } + // Add offset for icon width restriction, to keep it centered. + if (is_icon_width) { + g.x += (orig_avail_width - available_width) / 2; + } + // Re-add our padding before returning. g.x += self.pad_left * available_width; g.y += self.pad_bottom * available_height; From 8b8e0bedadf2c4c4b2b08f9c78a0c333c358a68b Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Tue, 8 Jul 2025 12:00:22 -0600 Subject: [PATCH 2/2] font: add scale groups to nerd font constraints We do this by characterizing the shared bounding boxes in a static copy of the symbols only nerd font when we're doing the codegen. This allows us to get results of our scaling that are just as good as in a patched font, since related glyphs can now be sized and positioned relative to each other. --- src/font/face.zig | 29 ++ src/font/nerd_font_attributes.zig | 707 +++++++++++++++++++++++++++++- src/font/nerd_font_codegen.py | 91 +++- 3 files changed, 818 insertions(+), 9 deletions(-) diff --git a/src/font/face.zig b/src/font/face.zig index dc36b0286..fc5118c3d 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -144,6 +144,23 @@ 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, + /// Maximum ratio of width to height when resizing. max_xy_ratio: ?f64 = null, @@ -245,6 +262,10 @@ pub const RenderOptions = struct { 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) { @@ -323,6 +344,14 @@ pub const RenderOptions = struct { }, } + // 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; diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig index e72c7a00e..4ec55d2ff 100644 --- a/src/font/nerd_font_attributes.zig +++ b/src/font/nerd_font_attributes.zig @@ -34,7 +34,35 @@ pub fn getConstraint(cp: u21) Constraint { .pad_top = 0.1, .pad_bottom = 0.1, }, - 0x276c...0x2771, + 0x276c...0x276d, + => .{ + .size_horizontal = .cover, + .size_vertical = .fit, + .max_constraint_width = 1, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.4028056112224450, + .group_height = 1.1222570532915361, + .group_x = 0.1428571428571428, + .group_y = 0.0349162011173184, + .pad_top = 0.15, + .pad_bottom = 0.15, + }, + 0x276e...0x276f, + => .{ + .size_horizontal = .cover, + .size_vertical = .fit, + .max_constraint_width = 1, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0115606936416186, + .group_height = 1.1222570532915361, + .group_x = 0.0057142857142857, + .group_y = 0.0125698324022346, + .pad_top = 0.15, + .pad_bottom = 0.15, + }, + 0x2770...0x2771, => .{ .size_horizontal = .cover, .size_vertical = .fit, @@ -357,8 +385,46 @@ pub fn getConstraint(cp: u21) Constraint { 0x2b58, 0xe000...0xe0a9, 0xe4fa...0xe7ef, - 0xea60...0xec1e, - 0xed00...0xf847, + 0xea60, + 0xea62...0xea7c, + 0xea7e...0xea98, + 0xeaa3...0xeab3, + 0xeab8...0xead3, + 0xead7...0xeb42, + 0xeb44...0xeb6d, + 0xeb72...0xeb89, + 0xeb8b...0xeb99, + 0xeb9b...0xebd4, + 0xebd6, + 0xebd8...0xec06, + 0xec08...0xec0a, + 0xec0d...0xec1e, + 0xed00...0xf018, + 0xf01a...0xf02f, + 0xf031...0xf03c, + 0xf041...0xf043, + 0xf045...0xf049, + 0xf04b...0xf050, + 0xf054...0xf059, + 0xf05c...0xf070, + 0xf072...0xf077, + 0xf079...0xf07a, + 0xf07c...0xf080, + 0xf082...0xf08b, + 0xf08d...0xf091, + 0xf093...0xf09b, + 0xf09d...0xf09e, + 0xf0a0, + 0xf0a5...0xf0a9, + 0xf0ab...0xf0c9, + 0xf0cb...0xf0d5, + 0xf0d7...0xf0dd, + 0xf0df...0xf0e6, + 0xf0e8...0xf295, + 0xf297...0xf2c1, + 0xf2c6...0xf2ef, + 0xf2f1...0xf305, + 0xf307...0xf847, 0xf0001...0xf1af0, => .{ .size_horizontal = .fit, @@ -367,6 +433,641 @@ pub fn getConstraint(cp: u21) Constraint { .align_horizontal = .center, .align_vertical = .center, }, + 0xea61, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.3315669947009841, + .group_height = 1.0763840224246670, + .group_x = 0.0847072200113701, + .group_y = 0.0709635416666667, + }, + 0xea7d, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1913145539906103, + .group_height = 1.1428571428571428, + .group_x = 0.0916256157635468, + .group_y = 0.0415039062500000, + }, + 0xea99, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0639412997903563, + .group_height = 2.0940695296523519, + .group_x = 0.0295566502463054, + .group_y = 0.2270507812500000, + }, + 0xea9a, + 0xeaa1, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.3029525032092426, + .group_height = 1.1729667812142039, + .group_x = 0.1527093596059113, + .group_y = 0.0751953125000000, + }, + 0xea9b, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1639908256880733, + .group_height = 1.3128205128205128, + .group_x = 0.0719211822660099, + .group_y = 0.0869140625000000, + }, + 0xea9c, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1639908256880733, + .group_height = 1.3195876288659794, + .group_x = 0.0719211822660099, + .group_y = 0.0830078125000000, + }, + 0xea9d, + 0xeaa0, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 2.4457831325301207, + .group_height = 1.9692307692307693, + .group_x = 0.2857142857142857, + .group_y = 0.2763671875000000, + }, + 0xea9e...0xea9f, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.9556840077071291, + .group_height = 2.4674698795180725, + .group_x = 0.2137931034482759, + .group_y = 0.3066406250000000, + }, + 0xeaa2, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.2412121212121212, + .group_height = 1.0591799039527152, + .group_x = 0.0683593750000000, + .group_y = 0.0146484375000000, + }, + 0xeab4, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0049115913555993, + .group_height = 1.8998144712430427, + .group_y = 0.2026367187500000, + }, + 0xeab5, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.8979591836734695, + .group_height = 1.0054000981836033, + .group_x = 0.2023460410557185, + .group_y = 0.0053710937500000, + }, + 0xeab6, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.8979591836734695, + .group_height = 1.0054000981836033, + .group_x = 0.2707722385141740, + }, + 0xeab7, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0049115913555993, + .group_height = 1.8980537534754403, + .group_x = 0.0048875855327468, + .group_y = 0.2709960937500000, + }, + 0xead4...0xead5, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.4152542372881356, + .group_x = 0.1486118671747414, + }, + 0xead6, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_height = 1.1390433815350389, + .group_y = 0.0688476562500000, + }, + 0xeb43, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.3635153129161119, + .group_height = 1.0002360944082516, + .group_x = 0.1992187500000000, + .group_y = 0.0002360386808388, + }, + 0xeb6e, + 0xeb71, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_height = 2.0197238658777121, + .group_y = 0.2524414062500000, + }, + 0xeb6f, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 2.0098619329388558, + .group_x = 0.2492639842983317, + }, + 0xeb70, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 2.0098619329388558, + .group_height = 1.0039215686274510, + .group_x = 0.2492639842983317, + .group_y = 0.0039062500000000, + }, + 0xeb8a, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 2.8826979472140764, + .group_height = 2.9804097167804766, + .group_x = 0.2634791454730417, + .group_y = 0.3314678485576923, + }, + 0xeb9a, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1441340782122904, + .group_height = 1.0591799039527152, + .group_x = 0.0683593750000000, + .group_y = 0.0146484375000000, + }, + 0xebd5, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0723270440251573, + .group_height = 1.0728129910948141, + .group_y = 0.0678710937500000, + }, + 0xebd7, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_height = 1.0000418544302916, + .group_y = 0.0000418526785714, + }, + 0xec07, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 2.8615369874243446, + .group_height = 2.9789458113505249, + .group_x = 0.2609446802539727, + .group_y = 0.3313029661016949, + }, + 0xec0b, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0722513089005237, + .group_height = 1.0002360944082516, + .group_y = 0.0002360386808388, + }, + 0xec0c, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.2487804878048780, + .group_x = 0.1992187500000000, + }, + 0xf019, + 0xf08c, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0004882812500000, + }, + 0xf030, + 0xf03e, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0004882812500000, + .group_height = 1.1428571428571428, + .group_y = 0.0625000000000000, + }, + 0xf03d, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0004882812500000, + .group_height = 1.5014662756598240, + .group_y = 0.1669921875000000, + }, + 0xf03f, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.6018762826150690, + .group_x = 0.1876220107369448, + }, + 0xf040, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0006976906439684, + .group_height = 1.0001808776182035, + .group_x = 0.0006972042111134, + .group_y = 0.0001808449074074, + }, + 0xf044, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1029147024980515, + .group_height = 1.1024142703367676, + .group_x = 0.0463592039005675, + .group_y = 0.0430325010461710, + }, + 0xf04a, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0004882812500000, + .group_height = 1.3312975252838291, + .group_y = 0.1245571402616279, + }, + 0xf051, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.6007812500000000, + .group_height = 1.3312170271945341, + .group_x = 0.1874084919472914, + .group_y = 0.1245117187500000, + }, + 0xf052, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1436671384194865, + .group_height = 1.1430165816326530, + .group_x = 0.0624629273607646, + .group_y = 0.0625610266424885, + }, + 0xf053, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.8765709864847797, + .group_height = 1.0707191397207079, + .group_x = 0.2332599943628554, + .group_y = 0.0332682382480123, + }, + 0xf05a...0xf05b, + 0xf081, + 0xf092, + 0xf0aa, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0005935142780173, + .group_height = 1.0001395089285714, + .group_y = 0.0000697447342726, + }, + 0xf071, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0014662756598240, + .group_height = 1.1428571428571428, + .group_x = 0.0004880429477794, + .group_y = 0.0625000000000000, + }, + 0xf078, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0717654378877508, + .group_height = 1.8757195185766613, + .group_x = 0.0331834301604062, + .group_y = 0.1670386385827870, + }, + 0xf07b, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_height = 1.1428571428571428, + .group_y = 0.0625000000000000, + }, + 0xf09c, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_height = 1.0810546875000000, + }, + 0xf09f, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.7506617925122907, + .group_height = 1.0810546875000000, + .group_x = 0.2143937211981567, + }, + 0xf0a1, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0009775171065494, + .group_x = 0.0004882812500000, + }, + 0xf0a2, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.1433271023627367, + .group_height = 1.0001395089285714, + .group_x = 0.0624235731978609, + .group_y = 0.0000697447342726, + }, + 0xf0a3, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0005760656161586, + .group_height = 1.0001220681837999, + .group_x = 0.0004792774839344, + .group_y = 0.0000610266424885, + }, + 0xf0a4, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0005935142780173, + .group_height = 1.3335193452380951, + .group_y = 0.1250523085507044, + }, + 0xf0ca, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0005935142780173, + .group_height = 1.1922501247297521, + .group_y = 0.0806249128190822, + }, + 0xf0d6, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_height = 1.5014662756598240, + .group_y = 0.1669921875000000, + }, + 0xf0de, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.2253421114919656, + .group_height = 2.5216400911161729, + .group_x = 0.0918898809523810, + .group_y = 0.6034327009936766, + }, + 0xf0e7, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.3336843856081169, + .group_x = 0.1247597299147187, + }, + 0xf296, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0005148743038617, + .group_height = 1.0385966606705219, + .group_x = 0.0005146093447336, + .group_y = 0.0186218440507742, + }, + 0xf2c2...0xf2c3, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0000770970394737, + .group_height = 1.2864321608040201, + .group_y = 0.1113281250000000, + }, + 0xf2c4, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0344231791600214, + .group_x = 0.0166002826673519, + }, + 0xf2c5, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0004538836055876, + .group_height = 1.4840579710144928, + .group_x = 0.0004536776887225, + .group_y = 0.1630859375000000, + }, + 0xf2f0, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.0005935142780173, + .group_height = 1.0334438518091393, + .group_y = 0.0161807783512345, + }, + 0xf306, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .height = .icon, + .align_horizontal = .center, + .align_vertical = .center, + .group_width = 1.2427184466019416, + .group_x = 0.0976562500000000, + }, else => .none, }; } diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py index 4087b9ac6..ad5cd0814 100644 --- a/src/font/nerd_font_codegen.py +++ b/src/font/nerd_font_codegen.py @@ -7,11 +7,16 @@ attributes and scaling rules. This does include an `eval` call! This is spooky, but we trust the nerd fonts code to be safe and not malicious or anything. -This script requires Python 3.12 or greater. +This script requires Python 3.12 or greater, requires that the `fontTools` +python module is installed, and requires that the path to a copy of the +SymbolsNerdFontMono font is passed as the first argument to the script. """ import ast +import sys import math +from fontTools.ttLib import TTFont +from fontTools.pens.boundsPen import BoundsPen from collections import defaultdict from contextlib import suppress from pathlib import Path @@ -19,7 +24,18 @@ from types import SimpleNamespace from typing import Literal, TypedDict, cast type PatchSetAttributes = dict[Literal["default"] | int, PatchSetAttributeEntry] -type AttributeHash = tuple[str | None, str | None, str, float, float, float] +type AttributeHash = tuple[ + str | None, + str | None, + str, + float, + float, + float, + float, + float, + float, + float, +] type ResolvedSymbol = PatchSetAttributes | PatchSetScaleRules | int | None @@ -34,6 +50,11 @@ class PatchSetAttributeEntry(TypedDict): stretch: str params: dict[str, float | bool] + group_x: float + group_y: float + group_width: float + group_height: float + class PatchSet(TypedDict): SymStart: int @@ -137,6 +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)), ) @@ -162,6 +187,11 @@ 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) + overlap = params.get("overlap", 0.0) xy_ratio = params.get("xy-ratio", -1.0) y_padding = params.get("ypadding", 0.0) @@ -192,7 +222,6 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) 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`. @@ -204,6 +233,15 @@ 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" + # `overlap` and `ypadding` are mutually exclusive, # this is asserted in the nerd fonts patcher itself. if overlap: @@ -226,16 +264,53 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) return s -def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str: +def generate_zig_switch_arms( + patch_sets: list[PatchSet], + nerd_font: TTFont, +) -> str: + cmap = nerd_font.getBestCmap() + glyphs = nerd_font.getGlyphSet() + entries: dict[int, PatchSetAttributeEntry] = {} for entry in patch_sets: attributes = entry["Attributes"] for cp in range(entry["SymStart"], entry["SymEnd"] + 1): - entries[cp] = attributes["default"] + entries[cp] = attributes["default"].copy() entries |= {k: v for k, v in attributes.items() if isinstance(k, int)} + if entry["ScaleRules"] is not None and "ScaleGroups" in entry["ScaleRules"]: + for group in entry["ScaleRules"]["ScaleGroups"]: + xMin = math.inf + yMin = math.inf + xMax = -math.inf + yMax = -math.inf + individual_bounds: dict[int, tuple[int, int, int ,int]] = {} + for cp in group: + if cp not in cmap: + continue + glyph = glyphs[cmap[cp]] + bounds = BoundsPen(glyphSet=glyphs) + glyph.draw(bounds) + individual_bounds[cp] = bounds.bounds + xMin = min(bounds.bounds[0], xMin) + yMin = min(bounds.bounds[1], yMin) + xMax = max(bounds.bounds[2], xMax) + yMax = max(bounds.bounds[3], yMax) + group_width = xMax - xMin + group_height = yMax - yMin + for cp in group: + if cp not in cmap or cp not in entries: + continue + 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 + del entries[0] # Group codepoints by attribute key @@ -256,6 +331,10 @@ def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str: if __name__ == "__main__": project_root = Path(__file__).resolve().parents[2] + nf_path = sys.argv[1] + + nerd_font = TTFont(nf_path) + patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py" source = patcher_path.read_text(encoding="utf-8") patch_set = extract_patch_set_values(source) @@ -275,5 +354,5 @@ const Constraint = @import("face.zig").RenderOptions.Constraint; pub fn getConstraint(cp: u21) Constraint { return switch (cp) { """) - f.write(generate_zig_switch_arms(patch_set)) + f.write(generate_zig_switch_arms(patch_set, nerd_font)) f.write("\n else => .none,\n };\n}\n")