Change vertical metrics to make Apple ATS work properly. New ascender value: 2728, new descender value: -680
Includes alternate fix to @thundernixon's PR #146
This commit is contained in:
parent
e1d8712ecd
commit
1dbc8fd053
3 changed files with 1073 additions and 1239 deletions
110
misc/glyphs-scripts/fixup-vertical-metrics.py
Normal file
110
misc/glyphs-scripts/fixup-vertical-metrics.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#MenuTitle: Fixup Vertical Metrics
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
font = Glyphs.font
|
||||||
|
caps = set(font.classes['Uppercase'].code.strip().split(' '))
|
||||||
|
lowercase = set(font.classes['Lowercase'].code.strip().split(' '))
|
||||||
|
|
||||||
|
print(font)
|
||||||
|
|
||||||
|
mainMaxDescent = 0
|
||||||
|
mainMaxDescentGlyph = ""
|
||||||
|
maxDescent = 0
|
||||||
|
mainMaxAscent = 0
|
||||||
|
mainMaxAscentGlyph = ""
|
||||||
|
maxAscent = 0
|
||||||
|
typoAscender = 0
|
||||||
|
typoDescender = 0
|
||||||
|
|
||||||
|
for master in font.masters:
|
||||||
|
ta = max(master.ascender, master.capHeight)
|
||||||
|
if typoAscender == 0:
|
||||||
|
typoAscender = ta
|
||||||
|
elif typoAscender != ta:
|
||||||
|
raise Error('ascender or capHeight varies with masters; vertical metrics must be same in all masters')
|
||||||
|
|
||||||
|
td = master.descender
|
||||||
|
if typoDescender == 0:
|
||||||
|
typoDescender = td
|
||||||
|
elif typoDescender != td:
|
||||||
|
raise Error('descender or capHeight varies with masters; vertical metrics must be same in all masters')
|
||||||
|
|
||||||
|
for glyph in font.glyphs:
|
||||||
|
if not glyph.export:
|
||||||
|
continue
|
||||||
|
|
||||||
|
layer = glyph.layers[master.id]
|
||||||
|
|
||||||
|
# get descender of current layer
|
||||||
|
descent = layer.bounds.origin.y
|
||||||
|
|
||||||
|
# get ascender of current layer
|
||||||
|
ascent = layer.bounds.size.height + descent
|
||||||
|
|
||||||
|
# if descent/ascent of current layer is greater than previous max descents/ascents, update the max descent/ascent
|
||||||
|
if descent <= maxDescent:
|
||||||
|
maxDescent = descent
|
||||||
|
maxDescentGlyph = glyph.name
|
||||||
|
|
||||||
|
if ascent >= maxAscent:
|
||||||
|
maxAscent = ascent
|
||||||
|
maxAscentGlyph = glyph.name
|
||||||
|
|
||||||
|
# get descender of current layer
|
||||||
|
descent = layer.bounds.origin.y
|
||||||
|
|
||||||
|
# get ascender of current layer (total height of layer, subtracting value of descender)
|
||||||
|
ascent = layer.bounds.size.height + descent
|
||||||
|
|
||||||
|
# get maximums of only letters in list vars, for typo and hhea values
|
||||||
|
if glyph.name in caps and ascent >= mainMaxAscent:
|
||||||
|
mainMaxAscent = ascent
|
||||||
|
mainMaxAscentGlyph = glyph.name
|
||||||
|
|
||||||
|
if glyph.name in lowercase and descent <= mainMaxDescent:
|
||||||
|
# if descent/ascent of current layer is greater than previous max descents/ascents, update the max descent/ascent
|
||||||
|
mainMaxDescent = descent
|
||||||
|
mainMaxDescentGlyph = glyph.name
|
||||||
|
|
||||||
|
|
||||||
|
# check values for sanity
|
||||||
|
# print(maxDescentGlyph, maxDescent, maxAscentGlyph, maxAscent)
|
||||||
|
|
||||||
|
# make lineGap so that the total of `ascent + descent + lineGap` equals 120% of UPM size
|
||||||
|
# UPM = font.upm
|
||||||
|
# totalSize = maxAscent + abs(maxDescent)
|
||||||
|
# lineGap = int((UPM * 1.2)) - totalSize
|
||||||
|
# print(UPM, UPM * 1.2, totalSize, lineGap)
|
||||||
|
|
||||||
|
## use highest/lowest points to set custom parameters for winAscent and winDescent
|
||||||
|
## following vertical metric schema from https://github.com/googlefonts/gf-docs/tree/master/VerticalMetrics
|
||||||
|
|
||||||
|
font.customParameters["Use Typo Metrics"] = True
|
||||||
|
|
||||||
|
# ascenderDelta = max(abs(typoAscender), abs(mainMaxAscent)) - min(abs(typoAscender), abs(mainMaxAscent))
|
||||||
|
descenderDelta = max(typoDescender, mainMaxDescent) - min(typoDescender, mainMaxDescent)
|
||||||
|
|
||||||
|
if descenderDelta == 0:
|
||||||
|
print('descenderDelta is zero -- no change')
|
||||||
|
else:
|
||||||
|
print('descenderDelta:', descenderDelta)
|
||||||
|
|
||||||
|
for master in font.masters:
|
||||||
|
|
||||||
|
# Win Ascent/Descent = Font bbox yMax/yMin
|
||||||
|
master.customParameters["winAscent"] = maxAscent
|
||||||
|
master.customParameters["winDescent"] = abs(maxDescent)
|
||||||
|
|
||||||
|
# no/zero line gap
|
||||||
|
# if "typoLineGap" in master.customParameters:
|
||||||
|
# del master.customParameters["typoLineGap"]
|
||||||
|
# if "hheaLineGap" in master.customParameters:
|
||||||
|
# del master.customParameters["hheaLineGap"]
|
||||||
|
master.customParameters["typoLineGap"] = 0
|
||||||
|
master.customParameters["hheaLineGap"] = 0
|
||||||
|
|
||||||
|
master.customParameters["typoDescender"] = typoDescender - descenderDelta
|
||||||
|
master.customParameters["hheaDescender"] = typoDescender - descenderDelta
|
||||||
|
|
||||||
|
master.customParameters["typoAscender"] = typoAscender + descenderDelta
|
||||||
|
master.customParameters["hheaAscender"] = typoAscender + descenderDelta
|
||||||
183
misc/glyphs-scripts/preflight.py
Normal file
183
misc/glyphs-scripts/preflight.py
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
#MenuTitle: Preflight
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
__doc__="""
|
||||||
|
Checks for bad paths and anchors
|
||||||
|
"""
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
Glyphs.clearLog()
|
||||||
|
Glyphs.showMacroWindow()
|
||||||
|
|
||||||
|
mainRunLoop = AppKit.NSRunLoop.mainRunLoop()
|
||||||
|
|
||||||
|
_lowerCaseGlyphNames = None
|
||||||
|
|
||||||
|
def getLowerCaseGlyphNames():
|
||||||
|
global _lowerCaseGlyphNames
|
||||||
|
if _lowerCaseGlyphNames is None:
|
||||||
|
# TODO: split with regexp to allow more than one space as a separator
|
||||||
|
_lowerCaseGlyphNames = set(font.classes['Lowercase'].code.strip().split(' '))
|
||||||
|
return _lowerCaseGlyphNames
|
||||||
|
|
||||||
|
def yieldAppMain():
|
||||||
|
mainRunLoop.runMode_beforeDate_(AppKit.NSRunLoopCommonModes, AppKit.NSDate.new())
|
||||||
|
|
||||||
|
def headline(titleString):
|
||||||
|
print("\n------ %s ------" % titleString.upper())
|
||||||
|
|
||||||
|
def log(glyphName, layerName, msg):
|
||||||
|
if layerName != "":
|
||||||
|
print("[glyph] %s \t Layer %s: %s." % ( glyphName, layerName, msg ))
|
||||||
|
elif glyphName != "":
|
||||||
|
print("[glyph] %s \t - \t %s." % ( glyphName, msg ))
|
||||||
|
else:
|
||||||
|
print("[info] %s." % ( msg ))
|
||||||
|
|
||||||
|
def masterLayersIterator(font):
|
||||||
|
for g in font.glyphs:
|
||||||
|
for master in font.masters:
|
||||||
|
yield g.layers[master.id], g
|
||||||
|
yieldAppMain()
|
||||||
|
|
||||||
|
def checkForOpenPaths(font):
|
||||||
|
headline("Checking for open paths")
|
||||||
|
ok = True
|
||||||
|
for layer, g in masterLayersIterator(font):
|
||||||
|
openPathsFound = 0
|
||||||
|
for path in layer.paths:
|
||||||
|
if not path.closed:
|
||||||
|
openPathsFound += 1
|
||||||
|
if openPathsFound > 0:
|
||||||
|
ok = False
|
||||||
|
log(g.name, layer.name, "%d open path(s) found" % openPathsFound)
|
||||||
|
if ok:
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
def checkForPathDirections(font):
|
||||||
|
headline("Checking for path directions")
|
||||||
|
ok = True
|
||||||
|
for layer, g in masterLayersIterator(font):
|
||||||
|
firstPath = layer.paths[0]
|
||||||
|
if firstPath and firstPath.direction != -1:
|
||||||
|
ok = False
|
||||||
|
if len(layer.paths) > 1:
|
||||||
|
msg = "Bad path order or direction."
|
||||||
|
else:
|
||||||
|
msg = "Bad path direction."
|
||||||
|
log(g.name, layer.name, msg)
|
||||||
|
if ok:
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
def checkForPointsOutOfBounds(font):
|
||||||
|
headline("Checking for nodes out of bounds")
|
||||||
|
ok = True
|
||||||
|
for layer, g in masterLayersIterator(font):
|
||||||
|
nodesOutOfBounds = 0
|
||||||
|
anchorsOutOfBounds = []
|
||||||
|
|
||||||
|
for path in layer.paths:
|
||||||
|
for n in path.nodes:
|
||||||
|
if abs(n.x) > 32766 or abs(n.y) > 32766:
|
||||||
|
nodesOutOfBounds += 1
|
||||||
|
for a in layer.anchors:
|
||||||
|
if abs(a.x) > 32766 or abs(a.y) > 32766:
|
||||||
|
anchorsOutOfBounds.append(a.name)
|
||||||
|
|
||||||
|
if nodesOutOfBounds:
|
||||||
|
ok = False
|
||||||
|
log(g.name, layer.name, "%d node(s) out of bounds" % nodesOutOfBounds)
|
||||||
|
|
||||||
|
if anchorsOutOfBounds:
|
||||||
|
ok = False
|
||||||
|
log(g.name, layer.name, "%d anchor(s) out of bounds (%r)" % (
|
||||||
|
len(anchorsOutOfBounds),
|
||||||
|
anchorsOutOfBounds
|
||||||
|
))
|
||||||
|
if ok:
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
def checkUnicode(font):
|
||||||
|
headline("Checking Unicodes")
|
||||||
|
ok = True
|
||||||
|
|
||||||
|
listOfUnicodes = [ (g.name, g.unicode) for g in font.glyphs if g.unicode != None ]
|
||||||
|
numberOfGlyphs = len(listOfUnicodes)
|
||||||
|
|
||||||
|
# glyphsWithoutUnicodes = [ g.name for g in allGlyphs if g.unicode == None ]
|
||||||
|
# for gName in glyphsWithoutUnicodes:
|
||||||
|
# log( gName, "", "Warning: No Unicode value set" )
|
||||||
|
|
||||||
|
for i in range(numberOfGlyphs - 1):
|
||||||
|
firstGlyph = listOfUnicodes[i]
|
||||||
|
for j in range(i+1, numberOfGlyphs):
|
||||||
|
secondGlyph = listOfUnicodes[j]
|
||||||
|
if firstGlyph[1] == secondGlyph[1]:
|
||||||
|
ok = False
|
||||||
|
log(
|
||||||
|
"%s & %s" % (firstGlyph[0], secondGlyph[0]),
|
||||||
|
"-",
|
||||||
|
"Both glyphs carry same Unicode value %s" % (firstGlyph[1])
|
||||||
|
)
|
||||||
|
if ok:
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
def checkVerticalMetrics(font):
|
||||||
|
headline("Checking vertical metrics")
|
||||||
|
ascender = 0
|
||||||
|
descender = 0
|
||||||
|
capHeight = 0
|
||||||
|
lowerCase = getLowerCaseGlyphNames()
|
||||||
|
ok = True
|
||||||
|
|
||||||
|
for master in font.masters:
|
||||||
|
if ascender == 0:
|
||||||
|
ascender = master.ascender
|
||||||
|
elif ascender != master.ascender:
|
||||||
|
print('ascender varies with masters; vertical metrics must be same in all masters')
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if capHeight == 0:
|
||||||
|
capHeight = master.capHeight
|
||||||
|
elif capHeight != master.capHeight:
|
||||||
|
print('capHeight varies with masters; vertical metrics must be same in all masters')
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if descender == 0:
|
||||||
|
descender = master.descender
|
||||||
|
elif descender != master.descender:
|
||||||
|
print('descender varies with masters; vertical metrics must be same in all masters')
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
for master in font.masters:
|
||||||
|
for glyph in font.glyphs:
|
||||||
|
if not glyph.export or glyph.name not in lowerCase:
|
||||||
|
continue
|
||||||
|
|
||||||
|
layer = glyph.layers[master.id]
|
||||||
|
|
||||||
|
# get ymin of current layer
|
||||||
|
ymin = layer.bounds.origin.y
|
||||||
|
if ymin < descender:
|
||||||
|
ok = False
|
||||||
|
log(glyph.name, layer.name,
|
||||||
|
'Warning: lower than descender (ymin=%r, descender=%r)' % (
|
||||||
|
ymin, descender))
|
||||||
|
if ok:
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
font = Glyphs.font
|
||||||
|
font.disableUpdateInterface()
|
||||||
|
try:
|
||||||
|
checkForOpenPaths(font)
|
||||||
|
checkForPathDirections(font)
|
||||||
|
checkForPointsOutOfBounds(font)
|
||||||
|
checkUnicode(font)
|
||||||
|
checkVerticalMetrics(font)
|
||||||
|
finally:
|
||||||
|
font.enableUpdateInterface()
|
||||||
2019
src/Inter.glyphs
2019
src/Inter.glyphs
File diff suppressed because one or more lines are too long
Reference in a new issue