From ad993b05297901f26ab74e45d245dbf5830f31ab Mon Sep 17 00:00:00 2001 From: Daniel Wennberg Date: Sun, 7 Sep 2025 01:31:37 -0700 Subject: [PATCH 1/8] Scale non-mono icons to be at least as large as mono --- font-patcher | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/font-patcher b/font-patcher index 9398a73294..98857a2e6c 100755 --- a/font-patcher +++ b/font-patcher @@ -1376,13 +1376,7 @@ class font_patcher: logger.critical("Can not detect sane font height") sys.exit(1) - self.font_dim['iconheight'] = self.font_dim['height'] - if self.args.single and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): - # Limit the icon height on monospaced fonts because very slender and tall icons render - # excessively tall otherwise. We ignore that effect for the other variants because it - # does not look so much out of place there. - # Icons can be bigger than the letter capitals, but not the whole cell: - self.font_dim['iconheight'] = (self.sourceFont.capHeight * 2 + self.font_dim['height']) / 3 + self.font_dim['iconheight'] = self.get_iconheight() # Make all metrics equal self.sourceFont.os2_typolinegap = 0 @@ -1467,6 +1461,18 @@ class font_patcher: if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0: self.xavgwidth[-1] = get_old_average_x_width(self.sourceFont) + def get_iconheight(self, single_width=None): + if single_width is None: + single_width = self.args.single + + iconheight = self.font_dim['height'] + if single_width and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): + # Limit the icon height on monospaced fonts because very slender and tall icons render + # excessively tall otherwise. We ignore that effect for the other variants because it + # does not look so much out of place there. + # Icons can be bigger than the letter capitals, but not the whole cell: + iconheight = (self.sourceFont.capHeight * 2 + self.font_dim['height']) / 3 + return iconheight def get_target_width(self, stretch): """ Get the target width (1 or 2 'cell') for a given stretch parameter """ @@ -1482,9 +1488,10 @@ class font_patcher: if not sym_dim['width'] or not sym_dim['height']: return (1.0, 1.0) - target_width = self.font_dim['width'] * self.get_target_width(stretch) + single_width = self.font_dim['width'] + target_width = single_width * self.get_target_width(stretch) if overlap: - target_width += self.font_dim['width'] * overlap + target_width += single_width * overlap scale_ratio_x = target_width / sym_dim['width'] # font_dim['height'] represents total line height, keep our symbols sized based upon font's em @@ -1498,9 +1505,16 @@ class font_patcher: if 'pa' in stretch: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = min(scale_ratio_x, scale_ratio_y) - if not self.args.single and not '!' in stretch and not overlap: - # non monospaced fonts just scale down on 'pa', not up - scale_ratio_x = min(scale_ratio_x, 1.0) + if scale_ratio_x > 1 and not self.args.single and not '!' in stretch and not overlap: + # on 'pa', non monospaced fonts only scale up as much as they would if monospaced + single_height = self.font_dim['height'] if '^' in stretch else self.get_iconheight(single_width=True) + single_height *= 1.0 - self.font_dim['ypadding'] + + single_ratio_x = single_width / sym_dim['width'] + single_ratio_y = single_height / sym_dim['height'] + + single_ratio_x = min(single_ratio_x, single_ratio_y) + scale_ratio_x = max(single_ratio_x, 1.0) scale_ratio_y = scale_ratio_x else: # Keep the not-stretched direction From 8763d2198555a25db55b526c3e7ee07473d8ddb3 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Mon, 6 Jul 2026 12:19:43 +0200 Subject: [PATCH 2/8] font-patcher: Add comments and mild restructuring No functional change. Also pull 'main' condition to the front of the if, i.e. 'this is a non-mono target'. Also rename function parameter that might be confused where the function is actually used and there is a variable with the same name (but different contents). Signed-off-by: Fini Jastrow --- font-patcher | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/font-patcher b/font-patcher index 98857a2e6c..11925dc80b 100755 --- a/font-patcher +++ b/font-patcher @@ -912,12 +912,12 @@ class font_patcher: # Stretch 'xz' or 'pa' (preserve aspect ratio) # Supported params: overlap | careful | xy-ratio | dont_copy | ypadding - # Overlap value is used horizontally but vertically limited to 0.01 + # Overlap value is used horizontally but vertically limited to 0.01 (implies '!', usually should have '1' when used with 'pa') # Careful does not overwrite/modify existing glyphs # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) # '1' means occupu 1 cell (default for 'xy') # '2' means occupy 2 cells (default for 'pa') - # '!' means do the 'pa' scaling even with non mono fonts (else it just scales down, never up) + # '!' means do the 'pa' scaling even with non mono fonts into a 2 cell wide space (else it just scales down, or up into a 1 cell wide space) # '^' means that scaling shall fill the whole cell and not only the icon-cap-height (for mono fonts, other always use the whole cell) # Dont_copy does not overwrite existing glyphs but rescales the preexisting ones # @@ -1461,12 +1461,13 @@ class font_patcher: if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0: self.xavgwidth[-1] = get_old_average_x_width(self.sourceFont) - def get_iconheight(self, single_width=None): - if single_width is None: - single_width = self.args.single + def get_iconheight(self, single_width_font=None): + """ Get the target height for a given target variant """ + if single_width_font is None: + single_width_font = self.args.single iconheight = self.font_dim['height'] - if single_width and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): + if single_width_font and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): # Limit the icon height on monospaced fonts because very slender and tall icons render # excessively tall otherwise. We ignore that effect for the other variants because it # does not look so much out of place there. @@ -1489,7 +1490,7 @@ class font_patcher: return (1.0, 1.0) single_width = self.font_dim['width'] - target_width = single_width * self.get_target_width(stretch) + target_width = single_width * self.get_target_width(stretch) if overlap: target_width += single_width * overlap scale_ratio_x = target_width / sym_dim['width'] @@ -1505,12 +1506,12 @@ class font_patcher: if 'pa' in stretch: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = min(scale_ratio_x, scale_ratio_y) - if scale_ratio_x > 1 and not self.args.single and not '!' in stretch and not overlap: + if not self.args.single and not '!' in stretch and not overlap and scale_ratio_x > 1.0: # on 'pa', non monospaced fonts only scale up as much as they would if monospaced - single_height = self.font_dim['height'] if '^' in stretch else self.get_iconheight(single_width=True) - single_height *= 1.0 - self.font_dim['ypadding'] - single_ratio_x = single_width / sym_dim['width'] + + single_height = self.font_dim['height'] if '^' in stretch else self.get_iconheight(single_width_font=True) + single_height *= 1.0 - self.font_dim['ypadding'] single_ratio_y = single_height / sym_dim['height'] single_ratio_x = min(single_ratio_x, single_ratio_y) From 4d83eb9f9fab4c5d4f9e419fa2abfd2683365b58 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Mon, 6 Jul 2026 12:28:56 +0200 Subject: [PATCH 3/8] font-patcher: Remove implied '!' from 'overlap' parameter We have no rule with 'pa' and 'overlap' that does not also have '!' (and '1'), but technically 'overlap' and '!' should be distinct. I.e. this change does not affect any existing case. Signed-off-by: Fini Jastrow --- font-patcher | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/font-patcher b/font-patcher index 11925dc80b..89abbc7038 100755 --- a/font-patcher +++ b/font-patcher @@ -912,7 +912,7 @@ class font_patcher: # Stretch 'xz' or 'pa' (preserve aspect ratio) # Supported params: overlap | careful | xy-ratio | dont_copy | ypadding - # Overlap value is used horizontally but vertically limited to 0.01 (implies '!', usually should have '1' when used with 'pa') + # Overlap value is used horizontally but vertically limited to 0.01 (usually should have '1' when used with 'pa') # Careful does not overwrite/modify existing glyphs # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) # '1' means occupu 1 cell (default for 'xy') @@ -1506,12 +1506,16 @@ class font_patcher: if 'pa' in stretch: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = min(scale_ratio_x, scale_ratio_y) - if not self.args.single and not '!' in stretch and not overlap and scale_ratio_x > 1.0: + if not self.args.single and not '!' in stretch and scale_ratio_x > 1.0: # on 'pa', non monospaced fonts only scale up as much as they would if monospaced single_ratio_x = single_width / sym_dim['width'] + if overlap: + single_ratio_x *= 1.0 + overlap single_height = self.font_dim['height'] if '^' in stretch else self.get_iconheight(single_width_font=True) single_height *= 1.0 - self.font_dim['ypadding'] + if overlap: + single_height *= 1.0 + min(0.01, overlap) # never aggressive vertical overlap single_ratio_y = single_height / sym_dim['height'] single_ratio_x = min(single_ratio_x, single_ratio_y) From 242959a814dcdbd68a728916db9d9cca07f30ec0 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Mon, 6 Jul 2026 13:18:51 +0200 Subject: [PATCH 4/8] font-patcher: Extract common scale code into function Signed-off-by: Fini Jastrow --- font-patcher | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/font-patcher b/font-patcher index 89abbc7038..9616e66d8c 100755 --- a/font-patcher +++ b/font-patcher @@ -1461,10 +1461,9 @@ class font_patcher: if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0: self.xavgwidth[-1] = get_old_average_x_width(self.sourceFont) - def get_iconheight(self, single_width_font=None): + def get_iconheight(self, force_single=False): """ Get the target height for a given target variant """ - if single_width_font is None: - single_width_font = self.args.single + single_width_font = self.args.single or force_single iconheight = self.font_dim['height'] if single_width_font and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): @@ -1483,41 +1482,39 @@ class font_patcher: return 1 return 2 - def get_scale_factors(self, sym_dim, stretch, overlap=None): - """ Get scale in x and y as tuple """ - # It is possible to have empty glyphs, so we need to skip those. - if not sym_dim['width'] or not sym_dim['height']: - return (1.0, 1.0) - - single_width = self.font_dim['width'] - target_width = single_width * self.get_target_width(stretch) + def get_scale_pair_raw(self, sym_dim, stretch, overlap, force_mono_metrix=False): + """ Get scale in x and y as tuple ignoring 'pa' and 'xy' flags """ + if force_mono_metrix: + stretch = '1' + stretch + cell_width_factor = self.get_target_width(stretch) if overlap: - target_width += single_width * overlap - scale_ratio_x = target_width / sym_dim['width'] + cell_width_factor += overlap + target_width = self.font_dim['width'] * cell_width_factor # font_dim['height'] represents total line height, keep our symbols sized based upon font's em # Use the font_dim['height'] only for explicit 'y' scaling (not 'pa') - target_height = self.font_dim['height'] if '^' in stretch else self.font_dim['iconheight'] + target_height = self.font_dim['height'] if '^' in stretch else self.get_iconheight(force_mono_metrix) target_height *= 1.0 - self.font_dim['ypadding'] if overlap: target_height *= 1.0 + min(0.01, overlap) # never aggressive vertical overlap - scale_ratio_y = target_height / sym_dim['height'] + + return (target_width / sym_dim['width'], target_height / sym_dim['height']) + + + def get_scale_factors(self, sym_dim, stretch, overlap=None): + """ Get scale in x and y as tuple """ + # It is possible to have empty glyphs, so we need to skip those. + if not sym_dim['width'] or not sym_dim['height']: + return (1.0, 1.0) + + (scale_ratio_x, scale_ratio_y) = self.get_scale_pair_raw(sym_dim, stretch, overlap) if 'pa' in stretch: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = min(scale_ratio_x, scale_ratio_y) if not self.args.single and not '!' in stretch and scale_ratio_x > 1.0: # on 'pa', non monospaced fonts only scale up as much as they would if monospaced - single_ratio_x = single_width / sym_dim['width'] - if overlap: - single_ratio_x *= 1.0 + overlap - - single_height = self.font_dim['height'] if '^' in stretch else self.get_iconheight(single_width_font=True) - single_height *= 1.0 - self.font_dim['ypadding'] - if overlap: - single_height *= 1.0 + min(0.01, overlap) # never aggressive vertical overlap - single_ratio_y = single_height / sym_dim['height'] - + (single_ratio_x, single_ratio_y) = self.get_scale_pair_raw(sym_dim, stretch, overlap, force_mono_metrix=True) single_ratio_x = min(single_ratio_x, single_ratio_y) scale_ratio_x = max(single_ratio_x, 1.0) scale_ratio_y = scale_ratio_x From 2ad73c8c2d6835c72d394f1637aa554ea294c751 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Tue, 7 Jul 2026 10:12:14 +0200 Subject: [PATCH 5/8] font-patcher: Remove font dimension 'iconheight' [why] The icon height is not a fixed size anymore for a given patch run, but we need to detect it depending on situation: - normally it is the old iconheight - full height in regular and propo - a bit less tall in mono - with regular and propo the icon height is now - upper limit is full height - lower limit (upscale to) is a bit less tall like mono The property is now used only for a debugging output and can leave the wrong expression that there is a fixed iconheight per patch run. Better remove it to avoid confusion. Signed-off-by: Fini Jastrow --- font-patcher | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/font-patcher b/font-patcher index 9616e66d8c..2ad35da8e1 100755 --- a/font-patcher +++ b/font-patcher @@ -1332,7 +1332,7 @@ class font_patcher: # print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname)) - self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0, 'iconheight': 0, 'ypadding': 0} + self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0, 'ypadding': 0} if metrics == Metric.HHEA: self.font_dim['ymin'] = self.sourceFont.hhea_descent - half_gap(self.sourceFont.hhea_linegap, False) @@ -1368,7 +1368,6 @@ class font_patcher: 'ymax' : self.sourceFont.ascent, 'width' : self.sourceFont.em, 'height' : self.sourceFont.descent + self.sourceFont.ascent, - 'iconheight': self.sourceFont.descent + self.sourceFont.ascent, 'ypadding' : 0, } our_btb = self.sourceFont.descent + self.sourceFont.ascent @@ -1376,8 +1375,6 @@ class font_patcher: logger.critical("Can not detect sane font height") sys.exit(1) - self.font_dim['iconheight'] = self.get_iconheight() - # Make all metrics equal self.sourceFont.os2_typolinegap = 0 self.sourceFont.os2_typoascent = self.font_dim['ymax'] @@ -1445,7 +1442,7 @@ class font_patcher: self.font_dim['ymax'] - self.font_dim['height'], self.font_dim['ymax']) logger.debug("Final font cell dimensions %d w x %d h%s", self.font_dim['width'], self.font_dim['height'], - ' (with icon cell {} h)'.format(int(self.font_dim['iconheight'])) if self.font_dim['iconheight'] != self.font_dim['height'] else '') + ' (with icon cell {} h)'.format(int(self.get_iconheight())) if self.get_iconheight() != self.font_dim['height'] else '') try: middle = lambda x, y: abs(x - y) / 2 + min(x, y) x_bb = self.sourceFont['x'].boundingBox(); From cb6a93b0383c6dd06bfe628ab468d1c949efb565 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Tue, 7 Jul 2026 22:18:11 +0200 Subject: [PATCH 6/8] font-patcher: Increase version number after changing the icon size of Regular and Propo fonts Signed-off-by: Fini Jastrow --- font-patcher | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/font-patcher b/font-patcher index 2ad35da8e1..c6597b7207 100755 --- a/font-patcher +++ b/font-patcher @@ -6,7 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals # Change the script version when you edit this script: -script_version = "4.25.1" +script_version = "4.26.0" version = "3.4.0" projectName = "Nerd Fonts" From 72ebaa76bfc782b90cb4d5226315657873d9772b Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Wed, 22 Jul 2026 14:13:26 +0200 Subject: [PATCH 7/8] font-patcher: Add possibility to add experimental flags [why] Sometimes we want to have some special feature to try out before we fully commit to it. Adding a real command line flag for that would probably be misleading. [how] Add --experimantal which takes unspecified switches that can come and go, explicitely mentioned to be an experiment. Signed-off-by: Fini Jastrow --- font-patcher | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/font-patcher b/font-patcher index c6597b7207..0fd6824ca6 100755 --- a/font-patcher +++ b/font-patcher @@ -2221,6 +2221,7 @@ def setup_arguments(): expert_group.add_argument('--configfile', dest='configfile', default=False, type=str, help='Specify a file path for configuration file (see sample: src/config.sample.cfg)') expert_group.add_argument('--custom', dest='custom', default=False, type=str, help='Specify a custom symbol font, all glyphs will be copied; absolute path suggested') expert_group.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming') + expert_group.add_argument('--experimental', dest='experimental', default=[], action='extend', nargs=1, help='Enable experimental/unstable feature, get hints with "?" or "help"') expert_group.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, help='Path to glyphs to be used for patching') expert_group.add_argument('--has-no-italic', dest='noitalic', default=False, action='store_true', help='Font family does not have Italic (but Oblique/Slanted), to help create correct RIBBI set') expert_group.add_argument('--metrics', dest='metrics', default=None, choices=get_metrics_names(), help='Select vertical metrics source (for problematic cases)') @@ -2259,6 +2260,29 @@ def setup_arguments(): extraflags += ' ' + args.font # Need to re-add the mandatory argument args = parser.parse_args(extraflags.split(), args) + experimental_features={ + # user-switch-name: ( PR number, description, variable in args ) + } + args.experimental = list(set(args.experimental)) + if '?' in args.experimental or 'help' in args.experimental: + print('\nThe following experimental features can be enabled.'); + if not len(experimental_features): + print(' No experimental feature available today') + else: + for k, v in experimental_features.items(): + print(' \'{}\': {} (PR {})'.format(k, v[1], v[0])) + print('Note that a feature might be removed without notice in the future.'); + sys.exit(0) + for k,v in experimental_features.items(): + if k in args.experimental: + setattr(args, v[2], True) + args.experimental.remove(k) + logger.warning('Enabling experimental feature %s', k) + else: + setattr(args, v[2], False) + if len(args.experimental): + logger.error('Unknown experimental flag(s): %s', ', '.join(args.experimental)) + if args.makegroups > 0 and not FontnameParserOK: logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0") sys.exit(1) From a33ff7707a0f9a2fcbbb576cd6613a7af39fc0a8 Mon Sep 17 00:00:00 2001 From: Fini Jastrow Date: Wed, 22 Jul 2026 14:33:26 +0200 Subject: [PATCH 8/8] font-patcher: Make nonmono upscaling experimental feature Signed-off-by: Fini Jastrow --- font-patcher | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/font-patcher b/font-patcher index 0fd6824ca6..d51f0bea65 100755 --- a/font-patcher +++ b/font-patcher @@ -912,15 +912,19 @@ class font_patcher: # Stretch 'xz' or 'pa' (preserve aspect ratio) # Supported params: overlap | careful | xy-ratio | dont_copy | ypadding - # Overlap value is used horizontally but vertically limited to 0.01 (usually should have '1' when used with 'pa') + # Overlap value is used horizontally but vertically limited to 0.01 (then params usually should have '1' when used with 'pa') # Careful does not overwrite/modify existing glyphs # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) # '1' means occupu 1 cell (default for 'xy') # '2' means occupy 2 cells (default for 'pa') - # '!' means do the 'pa' scaling even with non mono fonts into a 2 cell wide space (else it just scales down, or up into a 1 cell wide space) + # '!' means do the 'pa' scaling even with non mono fonts into a 2 cell wide space (else it just scales down, never up (*)) # '^' means that scaling shall fill the whole cell and not only the icon-cap-height (for mono fonts, other always use the whole cell) # Dont_copy does not overwrite existing glyphs but rescales the preexisting ones # + # (*) 'pa' scaling without '!' of a non mono font scales: + # - [normal mode] only down, never up (keeps original icon sizes unless they are too big) + # - [upscale mode] down; or up into a 1 cell wide space (at least same size as mono font icons) + # # Be careful, stretch may not change within a ScaleRule! SYM_ATTR_DEFAULT = { @@ -1510,10 +1514,15 @@ class font_patcher: # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit scale_ratio_x = min(scale_ratio_x, scale_ratio_y) if not self.args.single and not '!' in stretch and scale_ratio_x > 1.0: - # on 'pa', non monospaced fonts only scale up as much as they would if monospaced - (single_ratio_x, single_ratio_y) = self.get_scale_pair_raw(sym_dim, stretch, overlap, force_mono_metrix=True) - single_ratio_x = min(single_ratio_x, single_ratio_y) - scale_ratio_x = max(single_ratio_x, 1.0) + if not self.args.upscale: + # non monospaced fonts just scale down on 'pa', not up + if not overlap: + scale_ratio_x = 1.0 + else: + # on 'pa', non monospaced fonts only scale up as much as they would if monospaced + (single_ratio_x, single_ratio_y) = self.get_scale_pair_raw(sym_dim, stretch, overlap, force_mono_metrix=True) + single_ratio_x = min(single_ratio_x, single_ratio_y) + scale_ratio_x = max(single_ratio_x, 1.0) scale_ratio_y = scale_ratio_x else: # Keep the not-stretched direction @@ -2262,6 +2271,7 @@ def setup_arguments(): experimental_features={ # user-switch-name: ( PR number, description, variable in args ) + 'nonmono-upscale': (1926, 'Scale non-mono icons to be at least as large as mono', 'upscale'), } args.experimental = list(set(args.experimental)) if '?' in args.experimental or 'help' in args.experimental: