Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite font height calculation #1060

Merged
merged 4 commits into from
Jan 22, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 74 additions & 44 deletions font-patcher
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from __future__ import absolute_import, print_function, unicode_literals

# Change the script version when you edit this script:
script_version = "3.4.5"
script_version = "3.5.0"

version = "2.3.0"
projectName = "Nerd Fonts"
Expand Down Expand Up @@ -271,8 +271,8 @@ class font_patcher:
self.assert_monospace()
self.remove_ligatures()
self.setup_patch_set()
self.setup_line_dimensions()
self.get_sourcefont_dimensions()
self.improve_line_dimensions()
self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available
self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used

Expand Down Expand Up @@ -902,23 +902,16 @@ class font_patcher:
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR}
]

def setup_line_dimensions(self):
# win_ascent and win_descent are used to set the line height for windows fonts.
# hhead_ascent and hhead_descent are used to set the line height for mac fonts.
#
def improve_line_dimensions(self):
# Make the total line size even. This seems to make the powerline separators
# center more evenly.
if self.args.adjustLineHeight:
if (self.sourceFont.os2_winascent + self.sourceFont.os2_windescent) % 2 != 0:
# All three are equal before due to get_sourcefont_dimensions()
self.sourceFont.hhea_ascent += 1
self.sourceFont.os2_typoascent += 1
self.sourceFont.os2_winascent += 1

# Make the line size identical for windows and mac
# ! This is broken because hhea* is changed but os2_typo* is not
# ! On the other hand we need intact (i.e. original) typo values
# ! in get_sourcefont_dimensions() @TODO FIXME
self.sourceFont.hhea_ascent = self.sourceFont.os2_winascent
self.sourceFont.hhea_descent = -self.sourceFont.os2_windescent

def get_essential_references(self):
"""Find glyphs that are needed for the basic glyphs"""
# Sometimes basic glyphs are constructed from multiple other glyphs.
Expand All @@ -932,18 +925,46 @@ class font_patcher:
self.essential.add(self.sourceFont[r[0]].unicode)

def get_sourcefont_dimensions(self):
# Initial font dimensions
self.font_dim = {
'xmin' : 0,
'ymin' : -self.sourceFont.os2_windescent,
'xmax' : 0,
'ymax' : self.sourceFont.os2_winascent,
'width' : 0,
'height': 0,
}
if self.sourceFont.os2_use_typo_metrics:
self.font_dim['ymin'] = self.sourceFont.os2_typodescent
self.font_dim['ymax'] = self.sourceFont.os2_typoascent
""" This gets the font dimensions (cell width and height), and makes them equal on all platforms """
# Step 1
# There are three ways to discribe the baseline to baseline distance
# (a.k.a. line spacing) of a font. That is all a kuddelmuddel
# and we try to sort this out here
# See also https://glyphsapp.com/learn/vertical-metrics
# See also https://github.com/source-foundry/font-line
hhea_height = self.sourceFont.hhea_ascent - self.sourceFont.hhea_descent
typo_height = self.sourceFont.os2_typoascent - self.sourceFont.os2_typodescent
win_height = self.sourceFont.os2_winascent + self.sourceFont.os2_windescent
win_gap = max(0, self.sourceFont.hhea_linegap - win_height + hhea_height)
hhea_btb = hhea_height + self.sourceFont.hhea_linegap
typo_btb = typo_height + self.sourceFont.os2_typolinegap
win_btb = win_height + win_gap
use_typo = self.sourceFont.os2_use_typo_metrics != 0

# We use either TYPO (1) or WIN (2) and compare with HHEA
# and use HHEA (0) if the fonts seems broken
our_btb = typo_btb if use_typo else win_btb
if our_btb == hhea_btb:
metrics = 1 if use_typo else 2 # conforming font
else:
# We trust the WIN metric more, see experiments in #1056
print("{}: WARNING Font vertical metrics inconsistent (HHEA {} / TYPO {} / WIN {}), using WIN".format(projectName, hhea_btb, typo_btb, win_btb))
our_btb = win_btb
metrics = 1

# 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}

if metrics == 0:
self.font_dim['ymin'] = self.sourceFont.hhea_descent + half_gap(self.sourceFont.hhea_linegap, False)
self.font_dim['ymax'] = self.sourceFont.hhea_ascent + half_gap(self.sourceFont.hhea_linegap, True)
elif metrics == 1:
self.font_dim['ymin'] = self.sourceFont.os2_typodescent + half_gap(self.sourceFont.os2_typolinegap, False)
self.font_dim['ymax'] = self.sourceFont.os2_typoascent + half_gap(self.sourceFont.os2_typolinegap, True)
else:
self.font_dim['ymin'] = -self.sourceFont.os2_windescent + half_gap(win_gap, False)
self.font_dim['ymax'] = self.sourceFont.os2_winascent + half_gap(win_gap, True)

# Calculate font height
self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax']
Expand All @@ -958,26 +979,21 @@ class font_patcher:
'width' : self.sourceFont.em,
'height': self.sourceFont.descent + self.sourceFont.ascent,
}
elif self.font_dim['height'] < 0:
sys.exit("{}: Can not detect sane font height".format(projectName))

# Line gap add extra space on the bottom of the line which
# doesn't allow the powerline glyphs to fill the entire line.
# Put half of the gap into the 'cell', each top and bottom
gap = max(self.sourceFont.hhea_linegap, self.sourceFont.os2_typolinegap) # TODO probably wrong
if self.sourceFont.os2_use_typo_metrics:
gap = self.sourceFont.os2_typolinegap
self.sourceFont.hhea_linegap = 0
# Make all metrics equal
self.sourceFont.os2_typolinegap = 0
if gap > 0:
gap_top = int(gap / 2)
gap_bottom = gap - gap_top
print("Redistributing line gap of {} ({} top and {} bottom)".format(gap, gap_top, gap_bottom))
self.font_dim['ymin'] -= gap_bottom
self.font_dim['ymax'] += gap_top
self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax']
self.sourceFont.os2_typoascent = self.sourceFont.os2_typoascent + gap_top
self.sourceFont.os2_typodescent = self.sourceFont.os2_typodescent - gap_bottom
# TODO Check what to do with win and hhea values

self.sourceFont.os2_typoascent = self.font_dim['ymax']
self.sourceFont.os2_typodescent = self.font_dim['ymin']
self.sourceFont.os2_winascent = self.sourceFont.os2_typoascent
self.sourceFont.os2_windescent = -self.sourceFont.os2_typodescent
self.sourceFont.hhea_ascent = self.sourceFont.os2_typoascent
self.sourceFont.hhea_descent = self.sourceFont.os2_typodescent
self.sourceFont.hhea_linegap = self.sourceFont.os2_typolinegap
self.sourceFont.os2_use_typo_metrics = 1

# Step 2
# Find the biggest char width and advance width
# 0x00-0x17f is the Latin Extended-A range
warned = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target
Expand Down Expand Up @@ -1387,6 +1403,20 @@ class font_patcher:
return None


def half_gap(gap, top):
""" Divides integer value into two new integers """
# Line gap add extra space on the bottom of the line which
# doesn't allow the powerline glyphs to fill the entire line.
# Put half of the gap into the 'cell', each top and bottom
if gap <= 0:
return 0
gap_top = int(gap / 2)
gap_bottom = gap - gap_top
if top:
print("Redistributing line gap of {} ({} top and {} bottom)".format(gap, gap_top, gap_bottom))
return gap_top
return gap_bottom

def replace_font_name(font_name, replacement_dict):
""" Replaces all keys with vals from replacement_dict in font_name. """
for key, val in replacement_dict.items():
Expand Down Expand Up @@ -1566,7 +1596,7 @@ def setup_arguments():
args = parser.parse_args()

if args.makegroups and not FontnameParserOK:
sys.exit(projectName + ": FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))
sys.exit("{}: FontnameParser module missing (bin/scripts/name_parser/Fontname*), can not --makegroups".format(projectName))

# if you add a new font, set it to True here inside the if condition
if args.complete:
Expand Down