Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b2de9cf
Fix typo
simoncozens Oct 18, 2023
1448a92
Wait, this whole method is dead
simoncozens Oct 18, 2023
fb4e2c5
Typo
simoncozens Oct 18, 2023
fc4a454
Assertions to avoid unbound/None access
simoncozens Oct 18, 2023
57fbdce
ABC annotations only make sense if we're an ABC
simoncozens Oct 18, 2023
b7e51f6
More assertions to calm type checkers
simoncozens Oct 18, 2023
10700c2
Dead code?
simoncozens Oct 18, 2023
bf643ad
Hopefully uncontroversial typings
simoncozens Oct 18, 2023
d078bfd
Import the types
simoncozens Oct 18, 2023
b7d0ece
Make point into an ordinary class, not a tuple, because reasons
simoncozens Oct 18, 2023
1e37190
Typing requires types.
simoncozens Oct 18, 2023
b729bb1
Uncontroversial typings
simoncozens Oct 18, 2023
49f35c2
Some more typings
simoncozens Oct 18, 2023
f8ca0b5
Really void functions
simoncozens Oct 18, 2023
c46b555
Better typings
simoncozens Oct 18, 2023
dac1ad0
Mainly assertions
simoncozens Oct 18, 2023
48551a5
More type hints
simoncozens Oct 18, 2023
2c24afa
Move number upstairs, others
simoncozens Oct 18, 2023
cb04597
Complex numbers would be a mistake here
simoncozens Oct 18, 2023
e338e32
More type hinting
simoncozens Oct 18, 2023
40badd5
This was a true optional
simoncozens Oct 18, 2023
db2a6dc
More fixups
simoncozens Oct 18, 2023
c8687bc
Oops, they reused a variable
simoncozens Oct 18, 2023
916aa85
Fix assertion
simoncozens Oct 18, 2023
7d3d512
Make mypy happy without check-untyped-defs
simoncozens Oct 18, 2023
84fe6b0
As close to done as I'm getting
simoncozens Oct 18, 2023
6ac132e
Fix failing test
simoncozens Oct 18, 2023
ef05a40
Use typing_extensions to support <3.11
simoncozens Oct 18, 2023
5e5a0e3
Restore formatting
simoncozens Oct 18, 2023
60f2ea3
Ignore mypy error
simoncozens Oct 20, 2023
f4128b9
Use lo/hi, not tuple
simoncozens Oct 20, 2023
f11236d
pathElement is optional in hintSegment
simoncozens Oct 20, 2023
68a6f63
Good catch
simoncozens Oct 20, 2023
f4c5c6b
Check all methods
simoncozens Oct 20, 2023
e8375c9
Add mypy to requirements-dev
simoncozens Oct 20, 2023
4c286d0
Run mypy on build
simoncozens Oct 20, 2023
859f98d
Restore code style
simoncozens Oct 20, 2023
2e7f3a2
Slacken off the weakref type to make 3.8 happier
simoncozens Oct 20, 2023
9046243
Remove dead import
simoncozens Oct 20, 2023
40522b1
pytype does not support Self yet
simoncozens Oct 20, 2023
d034b45
Explain Protocol class
simoncozens Oct 20, 2023
0945357
List can be subscripted, list can't
simoncozens Oct 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/testpythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ jobs:
run: |
python -m pytest -n auto --dist loadfile --no-cov tests --color=yes

- name: Test type hints (otfautohint only)
run: |
mypy python/afdko/otfautohint

- name: Test uninstall AFDKO
run: |
python -m pip uninstall afdko -y
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[mypy]
ignore_missing_imports = True
check_untyped_defs = True
1 change: 1 addition & 0 deletions python/afdko/fdkutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def runShellCmdLogging(cmd, shell=True):
proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
while 1:
assert proc.stdout
output = proc.stdout.readline().rstrip()
if output:
print(output.decode('utf-8', 'backslashreplace'))
Expand Down
4 changes: 4 additions & 0 deletions python/afdko/otfautohint/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import os
from typing import Union


Number = Union[int, float]


class FontParseError(Exception):
Expand Down
12 changes: 7 additions & 5 deletions python/afdko/otfautohint/autohint.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from collections import namedtuple
from threading import Thread
from multiprocessing import Pool, Manager, current_process
from typing import Iterator, Optional, Union

from .otfFont import CFFFontData
from .ufoFont import UFOFontData
Expand Down Expand Up @@ -295,6 +296,7 @@ def hint(self):
pcount = self.options.process_count
if pcount is None:
pcount = os.cpu_count()
assert pcount is not None
if pcount < 0:
pcount = os.cpu_count() - pcount
if pcount < 0:
Expand All @@ -310,6 +312,7 @@ def hint(self):

pool = None
logThread = None
gmap: Optional[Iterator] = None
try:
dictRecord = self.dictManager.getDictRecord()
if pcount == 1:
Expand Down Expand Up @@ -362,6 +365,7 @@ def hint(self):
pool.close()
pool.join()
logQueue.put(None)
assert logThread is not None
logThread.join()
finally:
if pool is not None:
Expand All @@ -384,17 +388,15 @@ def close(self):
f.font.close()


def openFont(path, options):
def openFont(path, options) -> Union[UFOFontData, CFFFontData]:
font_format = get_font_format(path)
if font_format is None:
raise FontParseError(f"{path} is not a supported font format")

if font_format == "UFO":
font = UFOFontData(path, options.logOnly, options.writeToDefaultLayer)
return UFOFontData(path, options.logOnly, options.writeToDefaultLayer)
else:
font = CFFFontData(path, font_format)

return font
return CFFFontData(path, font_format)


def get_outpath(options, font_path, i):
Expand Down
66 changes: 44 additions & 22 deletions python/afdko/otfautohint/fdTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import numbers
import sys
import os
from typing import Any, Optional, Tuple, List, Dict

from . import Number


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -110,12 +113,27 @@
["StemSnapH", "StemSnapV"])


BlueValue = Tuple[int, int, str, str, int]
BlueZoneDict = Dict[Tuple[int, int], Tuple[int, str, str]]


class FontInfoParseError(ValueError):
pass


class FDDict:
def __init__(self, fdIndex, dictName=None, fontName=None):
BlueValuesPairs: List[BlueValue]
OtherBlueValuesPairs: List[BlueValue]
BlueFuzz: int
DominantH: List[int]
DominantV: List[int]
BaselineOvershoot: Number
BaselineYCoord: Number
FamilyBaselineYCoord: Number
OrigEmSqUnits: Number
FontName: Optional[str]

def __init__(self, fdIndex, dictName=None, fontName: Optional[str] = None):
self.fdIndex = fdIndex
for key in kFDDictKeys:
setattr(self, key, None)
Expand Down Expand Up @@ -313,6 +331,7 @@ def parseFontInfoFile(fdArrayMap, data, glyphList, maxY, minY, fontName):
maxfdIndex = max(fdArrayMap.keys())
dictValueList = []
dictKeyWord = ''
fdDict: Optional[FDDict] = None

state = baseState

Expand Down Expand Up @@ -377,6 +396,7 @@ def parseFontInfoFile(fdArrayMap, data, glyphList, maxY, minY, fontName):
elif state == inDictValue:
if token[-1] in ["]", ")"]:
dictValueList.append(token[:-1])
assert fdDict is not None
fdDict.setInfo(dictKeyWord, dictValueList)
state = dictState # found the last token in the list value.
else:
Expand Down Expand Up @@ -415,6 +435,7 @@ def parseFontInfoFile(fdArrayMap, data, glyphList, maxY, minY, fontName):
dictName = None
fdDict = None
else:
assert fdDict is not None
if token in kFDDictKeys:
value = tokenList[i]
i += 1
Expand All @@ -434,6 +455,7 @@ def parseFontInfoFile(fdArrayMap, data, glyphList, maxY, minY, fontName):

for dictName, fdIndex in fdIndexDict.items():
fdDict = fdArrayMap[fdIndex]
assert fdDict is not None
if fdDict.DominantH is None:
log.warning("The FDDict '%s' in fontinfo has no "
"DominantH value", dictName)
Expand Down Expand Up @@ -474,13 +496,13 @@ def parseFontInfoFile(fdArrayMap, data, glyphList, maxY, minY, fontName):
return fdSelectMap, finalFDict


def mergeFDDicts(prevDictList):
def mergeFDDicts(prevDictList: List[FDDict]) -> Dict[str, Any]:
# Extract the union of the stem widths and zones from the list
# of FDDicts, and replace the current values in the topDict.
blueZoneDict = {}
otherBlueZoneDict = {}
dominantHDict = {}
dominantVDict = {}
blueZoneDict: BlueZoneDict = {}
otherBlueZoneDict: BlueZoneDict = {}
dominantHDict: Dict[int, str] = {}
dominantVDict: Dict[int, str] = {}
bluePairListNames = [kFontDictBluePairsName, kFontDictOtherBluePairsName]
zoneDictList = [blueZoneDict, otherBlueZoneDict]
for prefDDict in prevDictList:
Expand All @@ -500,18 +522,18 @@ def mergeFDDicts(prevDictList):
stemDictList = [dominantHDict, dominantVDict]
for i in (0, 1):
stemFieldName = stemNameList[i]
dList = getattr(prefDDict, stemFieldName)
dList: List[int] = getattr(prefDDict, stemFieldName)
stemDict = stemDictList[i]
if dList is not None:
for width in dList:
stemDict[width] = prefDDict.DictName

assert prefDDict
# Now we have collected all the stem widths and zones
# from all the dicts. See if we can merge them.
goodBlueZoneList = []
goodOtherBlueZoneList = []
goodHStemList = []
goodVStemList = []
goodBlueZoneList: List[int] = []
goodOtherBlueZoneList: List[int] = []
goodHStemList: List[int] = []
goodVStemList: List[int] = []

zoneDictList = [blueZoneDict, otherBlueZoneDict]
goodZoneLists = [goodBlueZoneList, goodOtherBlueZoneList]
Expand All @@ -525,10 +547,9 @@ def mergeFDDicts(prevDictList):
goodStemList = goodStemLists[ki]

# Zones first.
zoneList = zoneDict.keys()
zoneList = sorted(zoneDict.keys())
if not zoneList:
continue
zoneList = sorted(zoneList)
# Now check for conflicts.
prevZone = zoneList[0]
goodZoneList.append(prevZone[1])
Expand Down Expand Up @@ -558,10 +579,9 @@ def mergeFDDicts(prevDictList):

prevZone = zone

stemList = stemDict.keys()
stemList = sorted(stemDict.keys())
if not stemList:
continue
stemList = sorted(stemList)
# Now check for conflicts.
prevStem = stemList[0]
goodStemList.append(prevStem)
Expand All @@ -574,7 +594,7 @@ def mergeFDDicts(prevDictList):
else:
goodStemList.append(stem)
prevStem = stem
privateMap = {}
privateMap: Dict[str, Any] = {}
if goodBlueZoneList:
privateMap['BlueValues'] = goodBlueZoneList
if goodOtherBlueZoneList:
Expand Down Expand Up @@ -642,7 +662,7 @@ def getFDInfo(font, desc, options, glyphList, isVF):
options.noFlex,
options.vCounterGlyphs,
options.hCounterGlyphs, desc)
fdArrayMap = {0: fdDict}
fdArrayMap: Dict[int, FDDict] = {0: fdDict}
minY, maxY = font.get_min_max(fdDict.OrigEmSqUnits)
fdSelectMap, finalFDict = parseFontInfoFile(
fdArrayMap, srcFontinfoData, glyphList, maxY, minY, desc)
Expand All @@ -655,7 +675,7 @@ def getFDInfo(font, desc, options, glyphList, isVF):
privateMap = mergeFDDicts([finalFDict])
elif isVF:
fdSelectMap = {}
dictRecord = {}
dictRecord: Dict[int, Dict[int, FDDict]] = {}
fdArraySet = set()
for name in glyphList:
fdIndex = font.getfdIndex(name)
Expand Down Expand Up @@ -698,7 +718,7 @@ def __init__(self, options, fontInstances, glyphList, isVF=False):
self.fontInstances = fontInstances
self.glyphList = glyphList
self.isVF = isVF
self.fdSelectMap = None
self.fdSelectMap: Optional[Dict] = None
self.auxRecord = {}
refI = fontInstances[0]
fdArrayCompat = True
Expand Down Expand Up @@ -736,6 +756,7 @@ def __init__(self, options, fontInstances, glyphList, isVF=False):
log.error("Cannot continue")
sys.exit()

assert self.fdSelectMap
if options.printFDDictList or options.printAllFDDict:
# Print the user defined FontDicts, and exit.
print("Private Dictionaries:\n")
Expand Down Expand Up @@ -766,6 +787,7 @@ def getDictRecord(self):
return self.dictRecord

def getRecKey(self, gname, vsindex):
assert self.fdSelectMap is not None
fdIndex = self.fdSelectMap[gname]
if vsindex in self.dictRecord and fdIndex in self.dictRecord[vsindex]:
return vsindex, fdIndex
Expand All @@ -783,7 +805,7 @@ def getRecKey(self, gname, vsindex):
self.auxRecord[vsindex][fdIndex] = fddict
return fddict

def checkGlyphList(self):
def checkGlyphList(self) -> None:
options = self.options
glyphSet = set(self.glyphList)
# Check for missing glyphs explicitly added via fontinfo or cmd line
Expand All @@ -797,7 +819,7 @@ def checkGlyphList(self):
log.warning("%s glyph named in fontinfo is " % label +
"not in font: %s" % name)

def addDict(self, dict1, dict2):
def addDict(self, dict1: Dict, dict2: Dict) -> bool:
# This allows for sparse masters, just verifying that if a glyph name
# is in both dictionaries it maps to the same index.
good = True
Expand Down
Loading