Compare commits

...

3 commits

Author SHA1 Message Date
github-actions
e577167bdc chore: change batteries 2023-03-15 01:26:02 +00:00
github-actions
a148894512 chore: change batteries 2023-03-01 01:47:47 +00:00
github-actions
5006509e0a chore: change batteries 2023-02-15 01:39:45 +00:00
3 changed files with 265 additions and 110 deletions

View file

@ -6,7 +6,7 @@
from __future__ import absolute_import, print_function, unicode_literals from __future__ import absolute_import, print_function, unicode_literals
# Change the script version when you edit this script: # Change the script version when you edit this script:
script_version = "3.5.2" script_version = "3.6.1"
version = "2.3.3" version = "2.3.3"
projectName = "Nerd Fonts" projectName = "Nerd Fonts"
@ -21,6 +21,7 @@ from argparse import RawTextHelpFormatter
import errno import errno
import subprocess import subprocess
import json import json
from enum import Enum
try: try:
import configparser import configparser
except ImportError: except ImportError:
@ -130,7 +131,7 @@ class TableHEADWriter:
def goto(self, where): def goto(self, where):
""" Go to a named location in the file or to the specified index """ """ Go to a named location in the file or to the specified index """
if type(where) is str: if isinstance(where, str):
positions = {'checksumAdjustment': 2+2+4, positions = {'checksumAdjustment': 2+2+4,
'flags': 2+2+4+4+4, 'flags': 2+2+4+4+4,
'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2, 'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2,
@ -194,6 +195,14 @@ def panose_check_to_text(value, panose = False):
return "Panose says \"monospaced\"" return "Panose says \"monospaced\""
return "Panose is invalid" + (" ({})".format(list(panose)) if panose else "") return "Panose is invalid" + (" ({})".format(list(panose)) if panose else "")
def panose_proportion_to_text(value):
""" Interpret a Panose proportion value (4th value) for family 2 (latin text) """
proportion = {
0: "Any", 1: "No Fit", 2: "Old Style", 3: "Modern", 4: "Even Width",
5: "Extended", 6: "Condensed", 7: "Very Extended", 8: "Very Condensed",
9: "Monospaced" }
return proportion.get(value, "??? {}".format(value))
def is_monospaced(font): def is_monospaced(font):
""" Check if a font is probably monospaced """ """ Check if a font is probably monospaced """
# Some fonts lie (or have not any Panose flag set), spot check monospaced: # Some fonts lie (or have not any Panose flag set), spot check monospaced:
@ -222,6 +231,20 @@ def is_monospaced(font):
# We believe our own check more then Panose ;-D # We believe our own check more then Panose ;-D
return (width_mono, None if width_mono else glyph) return (width_mono, None if width_mono else glyph)
def force_panose_monospaced(font):
""" Forces the Panose flag to monospaced if they are unset or halfway ok already """
# For some Windows applications (e.g. 'cmd'), they seem to honour the Panose table
# https://forum.high-logic.com/postedfiles/Panose.pdf
panose = list(font.os2_panose)
if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default)
panose[0] = 2 # make kind latin text and display
print(" Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')")
font.os2_panose = tuple(panose)
if panose[0] == 2 and panose[3] != 9:
print(" Setting Panose 'Proportion' to 'Monospaced' (was '{}')".format(panose_proportion_to_text(panose[3])))
panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced
font.os2_panose = tuple(panose)
def get_advance_width(font, extended, minimum): def get_advance_width(font, extended, minimum):
""" Get the maximum/minimum advance width in the extended(?) range """ """ Get the maximum/minimum advance width in the extended(?) range """
width = 0 width = 0
@ -248,6 +271,17 @@ def report_advance_widths(font):
get_advance_width(font, True, True), get_advance_width(font, False, True), get_advance_width(font, True, True), get_advance_width(font, False, True),
get_advance_width(font, False, False), get_advance_width(font, True, False)) get_advance_width(font, False, False), get_advance_width(font, True, False))
def get_btb_metrics(font):
""" Get the baseline to baseline distance for all three metrics """
hhea_height = font.hhea_ascent - font.hhea_descent
typo_height = font.os2_typoascent - font.os2_typodescent
win_height = font.os2_winascent + font.os2_windescent
win_gap = max(0, font.hhea_linegap - win_height + hhea_height)
hhea_btb = hhea_height + font.hhea_linegap
typo_btb = typo_height + font.os2_typolinegap
win_btb = win_height + win_gap
return (hhea_btb, typo_btb, win_btb, win_gap)
class font_patcher: class font_patcher:
def __init__(self, args): def __init__(self, args):
@ -258,6 +292,7 @@ class font_patcher:
self.patch_set = None # class 'list' self.patch_set = None # class 'list'
self.font_dim = None # class 'dict' self.font_dim = None # class 'dict'
self.font_extrawide = False self.font_extrawide = False
self.source_monospaced = None # Later True or False
self.onlybitmaps = 0 self.onlybitmaps = 0
self.essential = set() self.essential = set()
self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) self.config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True)
@ -267,8 +302,7 @@ class font_patcher:
self.setup_version() self.setup_version()
self.get_essential_references() self.get_essential_references()
self.setup_name_backup(font) self.setup_name_backup(font)
if self.args.single: self.assert_monospace()
self.assert_monospace()
self.remove_ligatures() self.remove_ligatures()
self.setup_patch_set() self.setup_patch_set()
self.get_sourcefont_dimensions() self.get_sourcefont_dimensions()
@ -280,13 +314,6 @@ class font_patcher:
# Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows. # Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows.
# This needs to be done on all characters, as some information seems to be lost from the original font file. # This needs to be done on all characters, as some information seems to be lost from the original font file.
self.set_sourcefont_glyph_widths() self.set_sourcefont_glyph_widths()
# For some Windows applications (e.g. 'cmd') that is not enough. But they seem to honour the Panose table
# https://forum.high-logic.com/postedfiles/Panose.pdf
panose = list(self.sourceFont.os2_panose)
if panose[0] == 0 or panose[0] == 2: # 0 (1st value) = family kind; 0 = any (default); 2 = latin text and display
panose[0] = 2 # Assert kind
panose[3] = 9 # 3 (4th value) = propotion; 9 = monospaced
self.sourceFont.os2_panose = tuple(panose)
# For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs # For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs
if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2: if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2:
@ -316,6 +343,7 @@ class font_patcher:
sys.exit("{}: Can not open symbol source for '{}'\n{:>{}} (i.e. {})".format( sys.exit("{}: Can not open symbol source for '{}'\n{:>{}} (i.e. {})".format(
projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename'])) projectName, patch['Name'], '', len(projectName), self.args.glyphdir + patch['Filename']))
symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename'])) symfont = fontforge.open(os.path.join(self.args.glyphdir, patch['Filename']))
symfont.encoding = 'UnicodeFull'
# Match the symbol font size to the source font size # Match the symbol font size to the source font size
symfont.em = self.sourceFont.em symfont.em = self.sourceFont.em
@ -422,8 +450,10 @@ class font_patcher:
def setup_font_names(self, font): def setup_font_names(self, font):
font.fontname = font.persistent["fontname"] font.fontname = font.persistent["fontname"]
font.fullname = font.persistent["fullname"] if isinstance(font.persistent["fullname"], str):
font.familyname = font.persistent["familyname"] font.fullname = font.persistent["fullname"]
if isinstance(font.persistent["familyname"], str):
font.familyname = font.persistent["familyname"]
verboseAdditionalFontNameSuffix = " " + projectNameSingular verboseAdditionalFontNameSuffix = " " + projectNameSingular
if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later if self.args.windows: # attempt to shorten here on the additional name BEFORE trimming later
additionalFontNameSuffix = " " + projectNameAbbreviation additionalFontNameSuffix = " " + projectNameAbbreviation
@ -470,7 +500,7 @@ class font_patcher:
verboseAdditionalFontNameSuffix += " Mono" verboseAdditionalFontNameSuffix += " Mono"
if FontnameParserOK and self.args.makegroups: if FontnameParserOK and self.args.makegroups:
use_fullname = type(font.fullname) == str # Usually the fullname is better to parse use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse
# Use fullname if it is 'equal' to the fontname # Use fullname if it is 'equal' to the fontname
if font.fullname: if font.fullname:
use_fullname |= font.fontname.lower() == FontnameTools.postscript_char_filter(font.fullname).lower() use_fullname |= font.fontname.lower() == FontnameTools.postscript_char_filter(font.fullname).lower()
@ -686,6 +716,9 @@ class font_patcher:
def assert_monospace(self): def assert_monospace(self):
# Check if the sourcefont is monospaced # Check if the sourcefont is monospaced
width_mono, offending_char = is_monospaced(self.sourceFont) width_mono, offending_char = is_monospaced(self.sourceFont)
self.source_monospaced = width_mono
if self.args.nonmono:
return
panose_mono = check_panose_monospaced(self.sourceFont) panose_mono = check_panose_monospaced(self.sourceFont)
# The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown' # The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown'
if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1): if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1):
@ -693,19 +726,48 @@ class font_patcher:
print(" {} and {}".format( print(" {} and {}".format(
report_advance_widths(self.sourceFont), report_advance_widths(self.sourceFont),
panose_check_to_text(panose_mono, self.sourceFont.os2_panose))) panose_check_to_text(panose_mono, self.sourceFont.os2_panose)))
if not width_mono: if self.args.single and not width_mono:
print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless") print(" Warning: Sourcefont is not monospaced - forcing to monospace not advisable, results might be useless")
if offending_char is not None: if offending_char is not None:
print(" Offending char: 0x{:X}".format(offending_char)) print(" Offending char: 0x{:X}".format(offending_char))
if self.args.single <= 1: if self.args.single <= 1:
sys.exit(projectName + ": Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching") sys.exit(projectName + ": Font will not be patched! Give --mono (or -s, or --use-single-width-glyphs) twice to force patching")
if width_mono:
force_panose_monospaced(self.sourceFont)
def setup_patch_set(self): def setup_patch_set(self):
""" Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """ """ Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """
# Supported params: overlap | careful
box_enabled = self.source_monospaced # Box glyph only for monospaced
if box_enabled:
self.sourceFont.selection.select(("ranges",), 0x2500, 0x259f)
box_glyphs_target = len(list(self.sourceFont.selection))
box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs))
if box_glyphs_target > box_glyphs_current:
# Sourcefont does not have all of these glyphs, do not mix sets
if not self.args.quiet and box_glyphs_current > 0:
print("INFO: {}/{} box drawing glyphs will be replaced".format(
box_glyphs_current, box_glyphs_target))
box_keep = False
box_enabled = True
else:
box_keep = True # just scale do not copy
box_enabled = False # Cowardly not scaling existing glyphs, although the code would allow this
# Stretch 'xz' or 'pa' (preserve aspect ratio)
# Supported params: overlap | careful | xy-ratio | dont_copy
# Overlap value is used horizontally but vertically limited to 0.01 # Overlap value is used horizontally but vertically limited to 0.01
# Powerline dividers # 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)
# Dont_copy does not overwrite existing glyphs but rescales the preexisting ones
SYM_ATTR_DEFAULT = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}
}
SYM_ATTR_POWERLINE = { SYM_ATTR_POWERLINE = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}},
@ -722,16 +784,16 @@ class font_patcher:
0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.5}}, 0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'xy-ratio': 0.5}},
# Bottom Triangles # Bottom Triangles
0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}}, 0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {}}, 0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}},
0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}}, 0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}}, 0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}},
# Top Triangles # Top Triangles
0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}}, 0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {}}, 0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}},
0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02}}, 0xe0be: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}},
0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}}, 0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}},
# Flames # Flames
0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}}, 0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
@ -740,36 +802,34 @@ class font_patcher:
0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}}, 0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {}},
# Small squares # Small squares
0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.86}},
0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.86}},
# Bigger squares # Bigger squares
0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.78}},
0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.78}},
# Waveform # Waveform
0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}}, 0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
0xe0ca: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}}, 0xe0ca: {'align': 'r', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.01}},
# Hexagons # Hexagons
0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'overlap': 0.02, 'xy-ratio': 0.85}},
0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': 'xy2', 'params': {'xy-ratio': 0.865}},
# Legos # Legos
0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': 'pa', 'params': {}},
0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {}}, 0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}},
0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02}}, 0xe0d0: {'align': 'l', 'valign': 'c', 'stretch': 'pa', 'params': {}},
0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': 'pa', 'params': {}},
# Top and bottom trapezoid # Top and bottom trapezoid
0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}}, 0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}},
0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}} 0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}}
} }
SYM_ATTR_TRIGRAPH = {
SYM_ATTR_DEFAULT = { 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa1!', 'params': {'overlap': -0.10, 'careful': True}}
# 'pa' == preserve aspect ratio
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}
} }
SYM_ATTR_FONTA = { SYM_ATTR_FONTA = {
# 'pa' == preserve aspect ratio # 'pa' == preserve aspect ratio
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}},
@ -779,7 +839,16 @@ class font_patcher:
0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, 0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}},
0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}} 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}
} }
SYM_ATTR_HEAVYBRACKETS = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {'careful': True}}
}
SYM_ATTR_BOX = {
'default': {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'overlap': 0.02, 'dont_copy': box_keep}},
# No overlap with checkered greys (commented out because that raises problems on rescaling clients)
# 0x2591: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}},
# 0x2592: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}},
# 0x2593: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}},
}
CUSTOM_ATTR = { CUSTOM_ATTR = {
# 'pa' == preserve aspect ratio # 'pa' == preserve aspect ratio
'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}} 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}}
@ -828,10 +897,24 @@ class font_patcher:
# For historic reasons ScaleGroups is sometimes called 'new method' and ScaleGlyph 'old'. # For historic reasons ScaleGroups is sometimes called 'new method' and ScaleGlyph 'old'.
# The codepoints mentioned here are symbol-font-codepoints. # The codepoints mentioned here are symbol-font-codepoints.
DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo BOX_SCALE_LIST = {'ScaleGroups': [
[*range(0x2500, 0x2570 + 1), *range(0x2574, 0x257f + 1)], # box drawing
range(0x2571, 0x2573 + 1), # diagonals
[*range(0x2580, 0x2590 + 1), 0x2594, 0x2595], # blocks
range(0x2591, 0x2593 + 1), # greys
range(0x2594, 0x259f + 1), # quards (Note: quard 2597 in Hack is wrong, scales like block!)
]}
CODI_SCALE_LIST = {'ScaleGroups': [
range(0xea99, 0xeaa1 + 1), # arrows
range(0xeb6e, 0xeb71 + 1), # triangles
range(0xeab4, 0xeab7 + 1), # chevrons
[0xea71, *range(0xeaa6, 0xeaab + 1), 0xeabc, 0xeb18, 0xeb87, 0xeb88, 0xeb8a, 0xeb8c, 0xebb4], # cicles
[0xeacc, 0xeaba], # dash
]}
DEVI_SCALE_LIST = {'ScaleGlyph': 0xE60E, # Android logo
'GlyphsToScale': [ 'GlyphsToScale': [
(0xe6bd, 0xe6c3) # very small things (0xe6bd, 0xe6c3) # very small things
]} ]}
FONTA_SCALE_LIST = {'ScaleGroups': [ FONTA_SCALE_LIST = {'ScaleGroups': [
[0xf005, 0xf006, 0xf089], # star, star empty, half star [0xf005, 0xf006, 0xf089], # star, star empty, half star
range(0xf026, 0xf028 + 1), # volume off, down, up range(0xf026, 0xf028 + 1), # volume off, down, up
@ -853,7 +936,7 @@ class font_patcher:
range(0xf221, 0xf22d + 1), # gender or so range(0xf221, 0xf22d + 1), # gender or so
range(0xf255, 0xf25b + 1), # hand symbols range(0xf255, 0xf25b + 1), # hand symbols
]} ]}
OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, # looking glass (probably biggest glyph?) OCTI_SCALE_LIST = {'ScaleGlyph': 0xF02E, # looking glass (probably biggest glyph?)
'GlyphsToScale': [ 'GlyphsToScale': [
(0xf03d, 0xf040), # arrows (0xf03d, 0xf040), # arrows
0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles 0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles
@ -861,23 +944,34 @@ class font_patcher:
0xf071, 0xf09f, 0xf0a0, 0xf0a1, # small arrows 0xf071, 0xf09f, 0xf0a0, 0xf0a1, # small arrows
0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons 0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons
0xf0ca, # dash 0xf0ca, # dash
]} ]}
WEATH_SCALE_LIST = {'ScaleGroups': [ WEATH_SCALE_LIST = {'ScaleGroups': [
[0xf03c, 0xf042, 0xf045 ], # degree signs
[0xf043, 0xf044, 0xf048, 0xf04b, 0xf04c, 0xf04d, 0xf057, 0xf058, 0xf087, 0xf088], # arrows
range(0xf053, 0xf055 + 1), # thermometers
[*range(0xf059, 0xf061 + 1), 0xf0b1], # wind directions
range(0xf089, 0xf094 + 1), # clocks
range(0xf095, 0xf0b0 + 1), # moon phases range(0xf095, 0xf0b0 + 1), # moon phases
range(0xf0b7, 0xf0c3 + 1), # wind strengths range(0xf0b7, 0xf0c3 + 1), # wind strengths
range(0xf053, 0xf055 + 1), # thermometer [0xf06e, 0xf070 ], # solar/lunar eclipse
[0xf06e, 0xf070 ], # solar eclipse # Note: Codepoints listed before that are also in the following range
[0xf042, 0xf045 ], # degree sign # will take the scaling of the previous group (the ScaleGroups are
]} # searched through in definition order).
MDI_SCALE_LIST = {'ScaleGlyph': 0xf068d, # 'solid' fills complete design space # But be careful, the combined bounding box for the following group
'GlyphsToScale+': [ # _will_ include all glyphs in its definition: Make sure the exempt
(0xf0000, 0xfffff) # all because they are very well scaled already # glyphs from above are smaller (do not extend) the combined bounding
# box of this range:
range(0xf000, 0xf0cb + 1), # lots of clouds and other (Please read note above!)
]} ]}
MDI_SCALE_LIST = None # Maybe later add some selected ScaleGroups
# Define the character ranges # Define the character ranges
# Symbol font ranges # Symbol font ranges
self.patch_set = [ self.patch_set = [
{'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5AA, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x276C, 'SymEnd': 0x2771, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_HEAVYBRACKETS},
{'Enabled': box_enabled, 'Name': "Box Drawing", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x2500, 'SymEnd': 0x259F, 'SrcStart': None, 'ScaleRules': BOX_SCALE_LIST, 'Attributes': SYM_ATTR_BOX},
{'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleRules': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons.ttf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE6C5, 'SrcStart': 0xE700, 'ScaleRules': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
@ -885,6 +979,7 @@ class font_patcher:
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D4, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE},
{'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0x2630, 'SymEnd': 0x2630, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_TRIGRAPH},
{'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'ScaleRules': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xF000, 'SymEnd': 0xF2E0, 'SrcStart': None, 'ScaleRules': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA},
{'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize
@ -898,7 +993,7 @@ class font_patcher:
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap
{'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons.ttf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF27C, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Desktop
{'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEBEB, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT},
{'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR} {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR}
] ]
@ -928,9 +1023,10 @@ class font_patcher:
self.add_glyphrefs_to_essential(altcode) self.add_glyphrefs_to_essential(altcode)
# From fontforge documentation: # From fontforge documentation:
# glyph.references return a tuple of tuples containing, for each reference in foreground, # glyph.references return a tuple of tuples containing, for each reference in foreground,
# a glyph name, a transformation matrix, and whether the reference is currently selected. # a glyph name, a transformation matrix, and (depending on ff version) whether the
# reference is currently selected.
references = self.sourceFont[unicode].references references = self.sourceFont[unicode].references
for refcode in [ self.sourceFont[n].unicode for n, m, s in references ]: for refcode in [ self.sourceFont[n].unicode for n, *_ in references ]: # tuple of 2 or 3 depending on ff version
if refcode not in self.essential and refcode >= 0: if refcode not in self.essential and refcode >= 0:
self.add_glyphrefs_to_essential(refcode) self.add_glyphrefs_to_essential(refcode)
@ -961,39 +1057,37 @@ class font_patcher:
# and we try to sort this out here # and we try to sort this out here
# See also https://glyphsapp.com/learn/vertical-metrics # See also https://glyphsapp.com/learn/vertical-metrics
# See also https://github.com/source-foundry/font-line # See also https://github.com/source-foundry/font-line
hhea_height = self.sourceFont.hhea_ascent - self.sourceFont.hhea_descent (hhea_btb, typo_btb, win_btb, win_gap) = get_btb_metrics(self.sourceFont)
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 use_typo = self.sourceFont.os2_use_typo_metrics != 0
Metric = Enum('Metric', ['HHEA', 'TYPO', 'WIN'])
# We use either TYPO (1) or WIN (2) and compare with HHEA # We use either TYPO (1) or WIN (2) and compare with HHEA
# and use HHEA (0) if the fonts seems broken # and use HHEA (0) if the fonts seems broken - no WIN, see #1056
our_btb = typo_btb if use_typo else win_btb our_btb = typo_btb if use_typo else win_btb
if our_btb == hhea_btb: if our_btb == hhea_btb:
metrics = 1 if use_typo else 2 # conforming font metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font
else: else:
# We trust the WIN metric more, see experiments in #1056 # 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)) print("{}: WARNING Font vertical metrics inconsistent (HHEA {} / TYPO {} / WIN {}), using WIN".format(projectName, hhea_btb, typo_btb, win_btb))
our_btb = win_btb our_btb = win_btb
metrics = 1 metrics = Metric.WIN
# print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname)) # 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} self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0}
if metrics == 0: if metrics == Metric.HHEA:
self.font_dim['ymin'] = self.sourceFont.hhea_descent + half_gap(self.sourceFont.hhea_linegap, False) 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) self.font_dim['ymax'] = self.sourceFont.hhea_ascent + half_gap(self.sourceFont.hhea_linegap, True)
elif metrics == 1: elif metrics == Metric.TYPO:
self.font_dim['ymin'] = self.sourceFont.os2_typodescent + half_gap(self.sourceFont.os2_typolinegap, False) 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) self.font_dim['ymax'] = self.sourceFont.os2_typoascent + half_gap(self.sourceFont.os2_typolinegap, True)
else: elif metrics == Metric.WIN:
self.font_dim['ymin'] = -self.sourceFont.os2_windescent + half_gap(win_gap, False) 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) self.font_dim['ymax'] = self.sourceFont.os2_winascent + half_gap(win_gap, True)
else:
pass # Will fail the metrics check some line later
# Calculate font height # Calculate font height
self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax'] self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax']
@ -1008,6 +1102,7 @@ class font_patcher:
'width' : self.sourceFont.em, 'width' : self.sourceFont.em,
'height': self.sourceFont.descent + self.sourceFont.ascent, 'height': self.sourceFont.descent + self.sourceFont.ascent,
} }
our_btb = self.sourceFont.descent + self.sourceFont.ascent
elif self.font_dim['height'] < 0: elif self.font_dim['height'] < 0:
sys.exit("{}: Can not detect sane font height".format(projectName)) sys.exit("{}: Can not detect sane font height".format(projectName))
@ -1021,11 +1116,15 @@ class font_patcher:
self.sourceFont.hhea_descent = self.sourceFont.os2_typodescent self.sourceFont.hhea_descent = self.sourceFont.os2_typodescent
self.sourceFont.hhea_linegap = self.sourceFont.os2_typolinegap self.sourceFont.hhea_linegap = self.sourceFont.os2_typolinegap
self.sourceFont.os2_use_typo_metrics = 1 self.sourceFont.os2_use_typo_metrics = 1
(check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont)
if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb:
sys.exit("{}: Error in baseline to baseline code detected".format(projectName))
# Step 2 # Step 2
# Find the biggest char width and advance width # Find the biggest char width and advance width
# 0x00-0x17f is the Latin Extended-A range # 0x00-0x17f is the Latin Extended-A range
warned = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target warned1 = self.args.quiet or self.args.nonmono # Do not warn if quiet or proportional target
warned2 = warned1
for glyph in range(0x21, 0x17f): for glyph in range(0x21, 0x17f):
if glyph in range(0x7F, 0xBF) or glyph in [ if glyph in range(0x7F, 0xBF) or glyph in [
0x132, 0x133, # IJ, ij (in Overpass Mono) 0x132, 0x133, # IJ, ij (in Overpass Mono)
@ -1041,30 +1140,39 @@ class font_patcher:
# print("WIDTH {:X} {} ({} {})".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) # print("WIDTH {:X} {} ({} {})".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if self.font_dim['width'] < self.sourceFont[glyph].width: if self.font_dim['width'] < self.sourceFont[glyph].width:
self.font_dim['width'] = self.sourceFont[glyph].width self.font_dim['width'] = self.sourceFont[glyph].width
if not warned and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
print("Warning: Extended glyphs wider than basic glyphs, results might be useless\n {}".format( print("Warning: Extended glyphs wider than basic glyphs, results might be useless\n {}".format(
report_advance_widths(self.sourceFont))) report_advance_widths(self.sourceFont)))
warned = True warned1 = True
# print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) # print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if xmax > self.font_dim['xmax']: if xmax > self.font_dim['xmax']:
self.font_dim['xmax'] = xmax self.font_dim['xmax'] = xmax
if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z
print("Info: Extended glyphs wider bounding box than basic glyphs")
warned2 = True
# print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) # print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax))
if self.font_dim['width'] < self.font_dim['xmax']:
if not self.args.quiet:
print("Warning: Font has negative right side bearing in extended glyphs")
self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used
# print("FINAL", self.font_dim) # print("FINAL", self.font_dim)
def get_target_width(self, stretch):
""" Get the target width (1 or 2 'cell') for a given stretch parameter """
# For monospaced fonts all chars need to be maximum 'one' space wide
# other fonts allows double width glyphs for 'pa' or if requested with '2'
if self.args.single or ('pa' not in stretch and '2' not in stretch) or '1' in stretch:
return 1
return 2
def get_scale_factors(self, sym_dim, stretch): def get_scale_factors(self, sym_dim, stretch):
""" Get scale in x and y as tuple """ """ Get scale in x and y as tuple """
# It is possible to have empty glyphs, so we need to skip those. # It is possible to have empty glyphs, so we need to skip those.
if not sym_dim['width'] or not sym_dim['height']: if not sym_dim['width'] or not sym_dim['height']:
return (1.0, 1.0) return (1.0, 1.0)
# For monospaced fonts all chars need to be maximum 'one' space wide target_width = self.font_dim['width'] * self.get_target_width(stretch)
# other fonts allows double width glyphs for 'pa' or if requested with '2'
if self.args.single or (stretch != 'pa' and '2' not in stretch):
relative_width = 1.0
else:
relative_width = 2.0
target_width = self.font_dim['width'] * relative_width
scale_ratio_x = target_width / sym_dim['width'] scale_ratio_x = target_width / sym_dim['width']
# font_dim['height'] represents total line height, keep our symbols sized based upon font's em # font_dim['height'] represents total line height, keep our symbols sized based upon font's em
@ -1072,10 +1180,10 @@ class font_patcher:
target_height = self.font_dim['height'] target_height = self.font_dim['height']
scale_ratio_y = target_height / sym_dim['height'] scale_ratio_y = target_height / sym_dim['height']
if stretch == 'pa': if 'pa' in stretch:
# We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit # 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) scale_ratio_x = min(scale_ratio_x, scale_ratio_y)
if not self.args.single: if not self.args.single and not '!' in stretch:
# non monospaced fonts just scale down on 'pa', not up # non monospaced fonts just scale down on 'pa', not up
scale_ratio_x = min(scale_ratio_x, 1.0) scale_ratio_x = min(scale_ratio_x, 1.0)
scale_ratio_y = scale_ratio_x scale_ratio_y = scale_ratio_x
@ -1093,7 +1201,6 @@ class font_patcher:
""" Copies symbol glyphs into self.sourceFont """ """ Copies symbol glyphs into self.sourceFont """
progressText = '' progressText = ''
careful = False careful = False
glyphSetLength = 0
sourceFontCounter = 0 sourceFontCounter = 0
if self.args.careful: if self.args.careful:
@ -1114,14 +1221,14 @@ class font_patcher:
glyphSetLength = len(symbolFontSelection) glyphSetLength = len(symbolFontSelection)
if not self.args.quiet: if not self.args.quiet:
sys.stdout.write("Adding " + str(max(1, glyphSetLength)) + " Glyphs from " + setName + " Set \n") modify = attributes['default']['params'].get('dont_copy')
sys.stdout.write("{} {} Glyphs from {} Set\n".format(
"Adding" if not modify else "Rescaling", glyphSetLength, setName))
currentSourceFontGlyph = -1 # initialize for the exactEncoding case currentSourceFontGlyph = -1 # initialize for the exactEncoding case
width_warning = False width_warning = False
for index, sym_glyph in enumerate(symbolFontSelection): for index, sym_glyph in enumerate(symbolFontSelection):
index = max(1, index)
sym_attr = attributes.get(sym_glyph.unicode) sym_attr = attributes.get(sym_glyph.unicode)
if sym_attr is None: if sym_attr is None:
sym_attr = attributes['default'] sym_attr = attributes['default']
@ -1176,29 +1283,37 @@ class font_patcher:
if currentSourceFontGlyph in self.sourceFont: if currentSourceFontGlyph in self.sourceFont:
self.sourceFont[currentSourceFontGlyph].removePosSub("*") self.sourceFont[currentSourceFontGlyph].removePosSub("*")
# This will destroy any content currently in currentSourceFontGlyph, so do it first dont_copy = sym_attr['params'].get('dont_copy')
glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None
# Select and copy symbol from its encoding point if dont_copy:
# We need to do this select after the careful check, this way we don't # Just prepare scaling of existing glyphs
# reset our selection before starting the next loop glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None
symbolFont.selection.select(sym_glyph.encoding) else:
symbolFont.copy() # This will destroy any content currently in currentSourceFontGlyph, so do it first
glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None
# Paste it # Select and copy symbol from its encoding point
self.sourceFont.selection.select(currentSourceFontGlyph) # We need to do this select after the careful check, this way we don't
self.sourceFont.paste() # reset our selection before starting the next loop
self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname symbolFont.selection.select(sym_glyph.encoding)
self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols symbolFont.copy()
# Paste it
self.sourceFont.selection.select(currentSourceFontGlyph)
self.sourceFont.paste()
self.sourceFont[currentSourceFontGlyph].glyphname = sym_glyph.glyphname
self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols
# Prepare symbol glyph dimensions # Prepare symbol glyph dimensions
sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph])
if glyph_scale_data is not None: if glyph_scale_data is not None:
if glyph_scale_data[1] is not None: if glyph_scale_data[1] is not None:
sym_dim = glyph_scale_data[1] # Use combined bounding box sym_dim = glyph_scale_data[1] # Use combined bounding box
# This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa') (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch'])
# Except we do not have glyph_scale_data[1] always... else:
(scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0]) # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa')
# Except we do not have glyph_scale_data[1] always...
(scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0])
else: else:
(scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch']) (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, sym_attr['stretch'])
@ -1252,17 +1367,24 @@ class font_patcher:
x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2) x_align_distance += (self.font_dim['width'] / 2) - (sym_dim['width'] / 2)
elif sym_attr['align'] == 'r': elif sym_attr['align'] == 'r':
# Right align # Right align
x_align_distance += self.font_dim['width'] - sym_dim['width'] x_align_distance += self.font_dim['width'] * self.get_target_width(sym_attr['stretch']) - sym_dim['width']
if not self.args.single and '2' in sym_attr['stretch']: # If symbol glyph is wider than target font cell, just left-align
x_align_distance += self.font_dim['width'] x_align_distance = max(self.font_dim['xmin'] - sym_dim['xmin'], x_align_distance)
if overlap: if overlap:
overlap_width = self.font_dim['width'] * overlap overlap_width = self.font_dim['width'] * overlap
if sym_attr['align'] == 'l': if sym_attr['align'] == 'l':
x_align_distance -= overlap_width x_align_distance -= overlap_width
if sym_attr['align'] == 'r' and not self.args.nonmono: elif sym_attr['align'] == 'c':
# Nonmono is 'left aligned' per definition, translation does not help here if overlap_width > 0:
x_align_distance += overlap_width x_align_distance -= overlap_width / 2
elif sym_attr['align'] == 'r':
# Check and correct overlap; it can go wrong if we have a xy-ratio limit
target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(sym_attr['stretch'])
target_xmax += overlap_width
glyph_xmax = sym_dim['xmax'] + x_align_distance
correction = target_xmax - glyph_xmax
x_align_distance += correction
align_matrix = psMat.translate(x_align_distance, y_align_distance) align_matrix = psMat.translate(x_align_distance, y_align_distance)
self.sourceFont[currentSourceFontGlyph].transform(align_matrix) self.sourceFont[currentSourceFontGlyph].transform(align_matrix)
@ -1485,7 +1607,7 @@ def get_multiglyph_boundingBox(glyphs, destGlyph = None):
if glyph is None: if glyph is None:
# Glyph has been in defining range but is not in the actual font # Glyph has been in defining range but is not in the actual font
continue continue
if destGlyph: if destGlyph and glyph.font != destGlyph.font:
glyph.font.selection.select(glyph) glyph.font.selection.select(glyph)
glyph.font.copy() glyph.font.copy()
destGlyph.font.selection.select(destGlyph) destGlyph.font.selection.select(destGlyph)
@ -1571,6 +1693,34 @@ def check_fontforge_min_version():
sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion)) sys.stderr.write("{}: Please use at least version: {}\n".format(projectName, minimumVersion))
sys.exit(1) sys.exit(1)
def check_version_with_git(version):
""" Upgraded the version to the current git tag version (starting with 'v') """
git = subprocess.run("git describe --tags",
cwd=os.path.dirname(__file__),
shell=True,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
).stdout.decode('utf-8')
if len(git) == 0:
return False
tag = git.strip()
if len(tag) == 0 or not tag.startswith('v'):
return False
tag = tag[1:]
r = re.search('(.*?)(-[0-9]+)-g[0-9a-fA-F]+$', tag)
if r:
tag = r.group(1)
patchlevel = r.group(2)
else:
patchlevel = ""
# Inspired by Phaxmohdem's versiontuple https://stackoverflow.com/a/28568003
versiontuple = lambda v: tuple( p.zfill(8) for p in v.split(".") )
if versiontuple(tag) > versiontuple(version):
return tag + patchlevel
if versiontuple(tag) == versiontuple(version) and len(patchlevel) > 0:
return tag + patchlevel
return False
def setup_arguments(): def setup_arguments():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=( description=(
@ -1699,7 +1849,12 @@ def setup_arguments():
def main(): def main():
print("{} Patcher v{} ({}) executing".format(projectName, version, script_version)) global version
git_version = check_version_with_git(version)
print("{} Patcher v{} ({}) (ff {}) executing".format(
projectName, git_version if git_version else version, script_version, fontforge.version()))
if git_version:
version = git_version
check_fontforge_min_version() check_fontforge_min_version()
args = setup_arguments() args = setup_arguments()
patcher = font_patcher(args) patcher = font_patcher(args)

Binary file not shown.

BIN
src/glyphs/extraglyphs.sfd Normal file

Binary file not shown.