mirror of
https://github.com/daylinmorgan/monolisa-nerdfont-patch.git
synced 2024-12-23 07:20:44 -06:00
Compare commits
3 commits
56a06a32c9
...
e577167bdc
Author | SHA1 | Date | |
---|---|---|---|
|
e577167bdc | ||
|
a148894512 | ||
|
5006509e0a |
3 changed files with 265 additions and 110 deletions
375
bin/font-patcher
375
bin/font-patcher
|
@ -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
BIN
src/glyphs/extraglyphs.sfd
Normal file
Binary file not shown.
Loading…
Reference in a new issue