font-patcher: Add option to manipulate xAvgCharWidth

[why]
Some old applications seem to depend on obsolete xAvgCharWidth values to
show two-cell glyphs correctly. Fontforge can only generate OS/2 tables
version 4, but these applications need 2 or less. In fact they seem to
not look up the version number, but rely on the value being like it
always has been ;-)

One example is Windows notepad, that takes the xAvgCharWidth as base for
the cell size and draws the two-cell chars in a cell twice that size -
without any regard to glyph width.

[how]
These issue seems to be encountered rather seldom and only with some
obscure (grin) applications. There is also no good way to handle this
automatically. So we add a command line option that allows the user to
tweak the value after patched-font generation.

The option is called `--xavgcharwidth`:
* If not specified the behavior of the patcher does not change
* If just given the xAvgCharWidth is copied over from the source
* If a number is added that number is used as xAvgCharWidth
* If the number added is zero we will calculate the old style xAvgCharWidth

Fixes: #522

Signed-off-by: Fini Jastrow <ulf.fini.jastrow@desy.de>
This commit is contained in:
Fini Jastrow 2023-04-12 15:19:52 +02:00 committed by Fini
parent cf59464e52
commit 156f9bf694

View file

@ -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.6.1"
script_version = "3.7.0"
version = "2.3.3"
projectName = "Nerd Fonts"
@ -135,6 +135,7 @@ class TableHEADWriter:
positions = {'checksumAdjustment': 2+2+4,
'flags': 2+2+4+4+4,
'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2,
'avgWidth': 2,
}
where = self.tab_offset + positions[where]
self.f.seek(where)
@ -282,6 +283,23 @@ def get_btb_metrics(font):
win_btb = win_height + win_gap
return (hhea_btb, typo_btb, win_btb, win_gap)
def get_old_average_x_width(font):
""" Determine xAvgCharWidth of the OS/2 table """
# Fontforge can not create fonts with old (i.e. prior to OS/2 version 3)
# table values, but some very old applications do need them sometimes
# https://learn.microsoft.com/en-us/typography/opentype/spec/os2#xavgcharwidth
s = 0
weights = {
'a': 64, 'b': 14, 'c': 27, 'd': 35, 'e': 100, 'f': 20, 'g': 14, 'h': 42, 'i': 63,
'j': 3, 'k': 6, 'l': 35, 'm': 20, 'n': 56, 'o': 56, 'p': 17, 'q': 4, 'r': 49,
's': 56, 't': 71, 'u': 31, 'v': 10, 'w': 18, 'x': 3, 'y': 18, 'z': 2, 32: 166,
}
for g in weights:
if g not in font:
sys.exit("{}: Can not determine ancient style xAvgCharWidth".format(projectName))
s += font[g].width * weights[g]
return int(s / 1000)
class font_patcher:
def __init__(self, args):
@ -296,6 +314,7 @@ class font_patcher:
self.onlybitmaps = 0
self.essential = set()
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
self.xavgwidth = [] # list of ints
def patch(self, font):
self.sourceFont = font
@ -409,6 +428,20 @@ class font_patcher:
for idx in range(source_font.num_fonts):
if not self.args.quiet:
print("{}: Tweaking {}/{}".format(projectName, idx + 1, source_font.num_fonts))
xwidth_s = ''
xwidth = self.xavgwidth[idx]
if isinstance(xwidth, int):
if isinstance(xwidth, bool) and xwidth:
source_font.find_table([b'OS/2'], idx)
xwidth = source_font.getshort('avgWidth')
xwidth_s = ' (copied from source)'
dest_font.find_table([b'OS/2'], idx)
d_xwidth = dest_font.getshort('avgWidth')
if d_xwidth != xwidth:
if not self.args.quiet:
print("Changing xAvgCharWidth from {} to {}{}".format(d_xwidth, xwidth, xwidth_s))
dest_font.putshort(xwidth, 'avgWidth')
dest_font.reset_table_checksum()
source_font.find_head_table(idx)
dest_font.find_head_table(idx)
if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0:
@ -1158,6 +1191,10 @@ class font_patcher:
self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used
# print("FINAL", self.font_dim)
self.xavgwidth.append(self.args.xavgwidth)
if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0:
self.xavgwidth[-1] = get_old_average_x_width(self.sourceFont)
def get_target_width(self, stretch):
""" Get the target width (1 or 2 'cell') for a given stretch parameter """
@ -1758,6 +1795,12 @@ def setup_arguments():
progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set')
parser.set_defaults(progressbars=True)
parser.add_argument('--also-windows', dest='alsowindows', default=False, action='store_true', help='Create two fonts, the normal and the --windows version')
parser.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True)
# --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts
# Possible values with examples:
# <none> - copy from sourcefont (default)
# 0 - calculate from font according to OS/2-version-2
# 500 - set to 500
# symbol fonts to include arguments
sym_font_group = parser.add_argument_group('Symbol Fonts')
@ -1846,6 +1889,12 @@ def setup_arguments():
if is_ttc:
sys.exit(projectName + ": Can not create single font files from True Type Collections")
if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool):
if args.xavgwidth < 0:
sys.exit(projectName + ": --xavgcharwidth takes no negative numbers")
if args.xavgwidth > 16384:
sys.exit(projectName + ": --xavgcharwidth takes only numbers up to 16384")
return args