aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools')
-rw-r--r--Lib/fontTools/__init__.py3
-rw-r--r--Lib/fontTools/afmLib.py1
-rw-r--r--Lib/fontTools/agl.py8
-rw-r--r--Lib/fontTools/cffLib/__init__.py31
-rw-r--r--Lib/fontTools/cffLib/specializer.py5
-rw-r--r--Lib/fontTools/cffLib/width.py5
-rw-r--r--Lib/fontTools/colorLib/geometry.py2
-rw-r--r--Lib/fontTools/colorLib/table_builder.py2
-rw-r--r--Lib/fontTools/designspaceLib/__init__.py17
-rw-r--r--Lib/fontTools/encodings/MacRoman.py2
-rw-r--r--Lib/fontTools/encodings/StandardEncoding.py2
-rw-r--r--Lib/fontTools/encodings/__init__.py2
-rw-r--r--Lib/fontTools/encodings/codecs.py47
-rw-r--r--Lib/fontTools/feaLib/__main__.py1
-rw-r--r--Lib/fontTools/feaLib/ast.py172
-rw-r--r--Lib/fontTools/feaLib/builder.py150
-rw-r--r--Lib/fontTools/feaLib/lexer.py1
-rw-r--r--Lib/fontTools/feaLib/parser.py260
-rw-r--r--Lib/fontTools/fontBuilder.py5
-rw-r--r--Lib/fontTools/merge.py1
-rw-r--r--Lib/fontTools/misc/__init__.py2
-rw-r--r--Lib/fontTools/misc/arrayTools.py128
-rw-r--r--Lib/fontTools/misc/bezierTools.py638
-rw-r--r--Lib/fontTools/misc/classifyTools.py1
-rw-r--r--Lib/fontTools/misc/cliTools.py1
-rw-r--r--Lib/fontTools/misc/dictTools.py1
-rw-r--r--Lib/fontTools/misc/eexec.py3
-rw-r--r--Lib/fontTools/misc/encodingTools.py1
-rw-r--r--Lib/fontTools/misc/etree.py6
-rw-r--r--Lib/fontTools/misc/filenames.py8
-rw-r--r--Lib/fontTools/misc/fixedTools.py28
-rw-r--r--Lib/fontTools/misc/intTools.py2
-rw-r--r--Lib/fontTools/misc/loggingTools.py7
-rw-r--r--Lib/fontTools/misc/macCreatorType.py3
-rw-r--r--Lib/fontTools/misc/macRes.py3
-rw-r--r--Lib/fontTools/misc/plistlib/__init__.py9
-rw-r--r--Lib/fontTools/misc/psCharStrings.py11
-rw-r--r--Lib/fontTools/misc/psLib.py19
-rw-r--r--Lib/fontTools/misc/psOperators.py2
-rw-r--r--Lib/fontTools/misc/py23.py2
-rw-r--r--Lib/fontTools/misc/roundTools.py58
-rw-r--r--Lib/fontTools/misc/sstruct.py4
-rw-r--r--Lib/fontTools/misc/symfont.py4
-rw-r--r--Lib/fontTools/misc/testTools.py5
-rw-r--r--Lib/fontTools/misc/textTools.py4
-rw-r--r--Lib/fontTools/misc/timeTools.py1
-rw-r--r--Lib/fontTools/misc/vector.py143
-rw-r--r--Lib/fontTools/misc/xmlReader.py1
-rw-r--r--Lib/fontTools/misc/xmlWriter.py8
-rw-r--r--Lib/fontTools/mtiLib/__init__.py1
-rw-r--r--Lib/fontTools/mtiLib/__main__.py1
-rw-r--r--Lib/fontTools/otlLib/builder.py61
-rw-r--r--Lib/fontTools/pens/__init__.py2
-rw-r--r--Lib/fontTools/pens/areaPen.py1
-rw-r--r--Lib/fontTools/pens/basePen.py25
-rw-r--r--Lib/fontTools/pens/boundsPen.py1
-rw-r--r--Lib/fontTools/pens/cocoaPen.py1
-rw-r--r--Lib/fontTools/pens/filterPen.py1
-rw-r--r--Lib/fontTools/pens/momentsPen.py1
-rw-r--r--Lib/fontTools/pens/perimeterPen.py1
-rw-r--r--Lib/fontTools/pens/pointInsidePen.py1
-rw-r--r--Lib/fontTools/pens/pointPen.py35
-rw-r--r--Lib/fontTools/pens/qtPen.py1
-rw-r--r--Lib/fontTools/pens/quartzPen.py1
-rw-r--r--Lib/fontTools/pens/recordingPen.py2
-rw-r--r--Lib/fontTools/pens/reportLabPen.py1
-rw-r--r--Lib/fontTools/pens/reverseContourPen.py1
-rw-r--r--Lib/fontTools/pens/roundingPen.py2
-rw-r--r--Lib/fontTools/pens/statisticsPen.py1
-rw-r--r--Lib/fontTools/pens/svgPathPen.py1
-rw-r--r--Lib/fontTools/pens/t2CharStringPen.py31
-rw-r--r--Lib/fontTools/pens/teePen.py1
-rw-r--r--Lib/fontTools/pens/transformPen.py1
-rw-r--r--Lib/fontTools/pens/ttGlyphPen.py5
-rw-r--r--Lib/fontTools/pens/wxPen.py1
-rw-r--r--Lib/fontTools/subset/__init__.py19
-rw-r--r--Lib/fontTools/subset/__main__.py1
-rw-r--r--Lib/fontTools/subset/cff.py2
-rw-r--r--Lib/fontTools/svgLib/__init__.py2
-rw-r--r--Lib/fontTools/svgLib/path/__init__.py2
-rw-r--r--Lib/fontTools/svgLib/path/arc.py4
-rw-r--r--Lib/fontTools/svgLib/path/parser.py1
-rw-r--r--Lib/fontTools/t1Lib/__init__.py2
-rw-r--r--Lib/fontTools/ttLib/__init__.py1
-rw-r--r--Lib/fontTools/ttLib/macUtils.py4
-rw-r--r--Lib/fontTools/ttLib/standardGlyphOrder.py2
-rw-r--r--Lib/fontTools/ttLib/tables/B_A_S_E_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py1
-rw-r--r--Lib/fontTools/ttLib/tables/C_B_D_T_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/C_B_L_C_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/C_F_F_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/C_F_F__2.py3
-rw-r--r--Lib/fontTools/ttLib/tables/C_O_L_R_.py5
-rw-r--r--Lib/fontTools/ttLib/tables/C_P_A_L_.py6
-rw-r--r--Lib/fontTools/ttLib/tables/D_S_I_G_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/DefaultTable.py2
-rw-r--r--Lib/fontTools/ttLib/tables/E_B_D_T_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/E_B_L_C_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/F_F_T_M_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/F__e_a_t.py2
-rw-r--r--Lib/fontTools/ttLib/tables/G_D_E_F_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/G_M_A_P_.py4
-rw-r--r--Lib/fontTools/ttLib/tables/G_P_K_G_.py6
-rw-r--r--Lib/fontTools/ttLib/tables/G_P_O_S_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/G_S_U_B_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/G__l_a_t.py5
-rw-r--r--Lib/fontTools/ttLib/tables/G__l_o_c.py1
-rw-r--r--Lib/fontTools/ttLib/tables/H_V_A_R_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/J_S_T_F_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/L_T_S_H_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/M_A_T_H_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/M_E_T_A_.py14
-rw-r--r--Lib/fontTools/ttLib/tables/M_V_A_R_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/O_S_2f_2.py1
-rw-r--r--Lib/fontTools/ttLib/tables/S_I_N_G_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/S_T_A_T_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/S_V_G_.py4
-rw-r--r--Lib/fontTools/ttLib/tables/S__i_l_f.py6
-rw-r--r--Lib/fontTools/ttLib/tables/S__i_l_l.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_B_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_C_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_D_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_J_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_P_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_S_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I_V_.py2
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__0.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__1.py4
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__2.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__3.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_S_I__5.py1
-rw-r--r--Lib/fontTools/ttLib/tables/T_T_F_A_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/TupleVariation.py2
-rw-r--r--Lib/fontTools/ttLib/tables/V_D_M_X_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/V_O_R_G_.py5
-rw-r--r--Lib/fontTools/ttLib/tables/V_V_A_R_.py1
-rw-r--r--Lib/fontTools/ttLib/tables/__init__.py3
-rw-r--r--Lib/fontTools/ttLib/tables/_a_n_k_r.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_a_v_a_r.py5
-rw-r--r--Lib/fontTools/ttLib/tables/_b_s_l_n.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_c_i_d_g.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_c_m_a_p.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_c_v_a_r.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_c_v_t.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_f_e_a_t.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_f_p_g_m.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_f_v_a_r.py4
-rw-r--r--Lib/fontTools/ttLib/tables/_g_a_s_p.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_g_c_i_d.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_g_l_y_f.py16
-rw-r--r--Lib/fontTools/ttLib/tables/_g_v_a_r.py4
-rw-r--r--Lib/fontTools/ttLib/tables/_h_d_m_x.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_h_e_a_d.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_h_h_e_a.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_h_m_t_x.py3
-rw-r--r--Lib/fontTools/ttLib/tables/_k_e_r_n.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_l_c_a_r.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_l_o_c_a.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_l_t_a_g.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_m_a_x_p.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_m_e_t_a.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_m_o_r_t.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_m_o_r_x.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_n_a_m_e.py11
-rw-r--r--Lib/fontTools/ttLib/tables/_o_p_b_d.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_p_o_s_t.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_p_r_e_p.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_p_r_o_p.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_s_b_i_x.py4
-rw-r--r--Lib/fontTools/ttLib/tables/_t_r_a_k.py2
-rw-r--r--Lib/fontTools/ttLib/tables/_v_h_e_a.py1
-rw-r--r--Lib/fontTools/ttLib/tables/_v_m_t_x.py1
-rw-r--r--Lib/fontTools/ttLib/tables/asciiTable.py2
-rw-r--r--Lib/fontTools/ttLib/tables/otBase.py11
-rw-r--r--Lib/fontTools/ttLib/tables/otConverters.py20
-rwxr-xr-xLib/fontTools/ttLib/tables/otData.py11
-rw-r--r--Lib/fontTools/ttLib/tables/otTables.py11
-rw-r--r--Lib/fontTools/ttLib/tables/sbixGlyph.py1
-rw-r--r--Lib/fontTools/ttLib/tables/sbixStrike.py5
-rw-r--r--Lib/fontTools/ttLib/tables/ttProgram.py3
-rw-r--r--Lib/fontTools/ttLib/ttCollection.py4
-rw-r--r--Lib/fontTools/ttLib/ttFont.py102
-rw-r--r--Lib/fontTools/ttLib/woff2.py3
-rw-r--r--Lib/fontTools/ttx.py2
-rw-r--r--Lib/fontTools/ufoLib/plistlib.py3
-rw-r--r--Lib/fontTools/unicode.py7
-rw-r--r--Lib/fontTools/unicodedata/__init__.py12
-rw-r--r--Lib/fontTools/varLib/__init__.py34
-rw-r--r--Lib/fontTools/varLib/cff.py33
-rw-r--r--Lib/fontTools/varLib/errors.py157
-rw-r--r--Lib/fontTools/varLib/instancer/__init__.py (renamed from Lib/fontTools/varLib/instancer.py)87
-rw-r--r--Lib/fontTools/varLib/instancer/__main__.py5
-rw-r--r--Lib/fontTools/varLib/instancer/names.py379
-rw-r--r--Lib/fontTools/varLib/merger.py158
-rw-r--r--Lib/fontTools/varLib/models.py92
-rw-r--r--Lib/fontTools/varLib/mutator.py13
-rw-r--r--Lib/fontTools/varLib/plot.py6
-rw-r--r--Lib/fontTools/varLib/varStore.py23
198 files changed, 2450 insertions, 971 deletions
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index 19040e48..82da9b70 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -1,9 +1,8 @@
-from fontTools.misc.py23 import *
import logging
from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "4.20.0"
+version = __version__ = "4.22.0"
__all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/afmLib.py b/Lib/fontTools/afmLib.py
index 67f145f4..49d99512 100644
--- a/Lib/fontTools/afmLib.py
+++ b/Lib/fontTools/afmLib.py
@@ -46,7 +46,6 @@ Here is an example of using `afmLib` to read, modify and write an AFM file:
"""
-from fontTools.misc.py23 import *
import re
# every single line starts with a "word"
diff --git a/Lib/fontTools/agl.py b/Lib/fontTools/agl.py
index b7d0bfa3..4f7ff920 100644
--- a/Lib/fontTools/agl.py
+++ b/Lib/fontTools/agl.py
@@ -26,7 +26,7 @@ This is used by fontTools when it has to construct glyph names for a font which
doesn't include any (e.g. format 3.0 post tables).
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tostr
import re
@@ -5140,7 +5140,7 @@ def _glyphComponentToUnicode(component, isZapfDingbats):
# to the corresponding character in that list.
uchars = LEGACY_AGL2UV.get(component)
if uchars:
- return "".join(map(unichr, uchars))
+ return "".join(map(chr, uchars))
# Otherwise, if the component is of the form "uni" (U+0075,
# U+006E, and U+0069) followed by a sequence of uppercase
@@ -5210,7 +5210,7 @@ def _uniToUnicode(component):
if any(c >= 0xD800 and c <= 0xDFFF for c in chars):
# The AGL specification explicitly excluded surrogate pairs.
return None
- return ''.join([unichr(c) for c in chars])
+ return ''.join([chr(c) for c in chars])
_re_u = re.compile("^u([0-9A-F]{4,6})$")
@@ -5228,5 +5228,5 @@ def _uToUnicode(component):
return None
if ((value >= 0x0000 and value <= 0xD7FF) or
(value >= 0xE000 and value <= 0x10FFFF)):
- return unichr(value)
+ return chr(value)
return None
diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py
index e97b7501..d4cd7a17 100644
--- a/Lib/fontTools/cffLib/__init__.py
+++ b/Lib/fontTools/cffLib/__init__.py
@@ -11,7 +11,7 @@ the demands of variable fonts. This module parses both original CFF and CFF2.
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc import psCharStrings
from fontTools.misc.arrayTools import unionRect, intRect
@@ -20,6 +20,7 @@ from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.otBase import OTTableWriter
from fontTools.ttLib.tables.otBase import OTTableReader
from fontTools.ttLib.tables import otTables as ot
+from io import BytesIO
import struct
import logging
import re
@@ -118,7 +119,7 @@ class CFFFontSet(object):
"""
if hasattr(nameOrIndex, "__index__"):
index = nameOrIndex.__index__()
- elif isinstance(nameOrIndex, basestring):
+ elif isinstance(nameOrIndex, str):
name = nameOrIndex
try:
index = self.fontNames.index(name)
@@ -261,7 +262,7 @@ class CFFFontSet(object):
self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
self.topDictIndex.append(topDict)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
topDict.fromXML(name, attrs, content)
@@ -277,7 +278,7 @@ class CFFFontSet(object):
if not hasattr(self, "GlobalSubrs"):
self.GlobalSubrs = GlobalSubrsIndex()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
subr = subrCharStringClass()
@@ -879,7 +880,7 @@ class FDArrayIndex(Index):
return
fontDict = FontDict()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
fontDict.fromXML(name, attrs, content)
@@ -1106,7 +1107,7 @@ class CharStrings(object):
def fromXML(self, name, attrs, content):
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
if name != "CharString":
@@ -1245,7 +1246,7 @@ class ASCIIConverter(SimpleConverter):
return tobytes(value, encoding='ascii')
def xmlWrite(self, xmlWriter, name, value):
- xmlWriter.simpletag(name, value=tounicode(value, encoding="ascii"))
+ xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
xmlWriter.newline()
def xmlRead(self, name, attrs, content, parent):
@@ -1261,7 +1262,7 @@ class Latin1Converter(SimpleConverter):
return tobytes(value, encoding='latin1')
def xmlWrite(self, xmlWriter, name, value):
- value = tounicode(value, encoding="latin1")
+ value = tostr(value, encoding="latin1")
if name in ['Notice', 'Copyright']:
value = re.sub(r"[\r\n]\s+", " ", value)
xmlWriter.simpletag(name, value=value)
@@ -1282,7 +1283,7 @@ def parseNum(s):
def parseBlendList(s):
valueList = []
for element in s:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
blendList = attrs["value"].split()
@@ -1358,7 +1359,7 @@ class TableConverter(SimpleConverter):
def xmlRead(self, name, attrs, content, parent):
ob = self.getClass()()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
ob.fromXML(name, attrs, content)
@@ -1650,7 +1651,7 @@ def parseCharset(numGlyphs, file, strings, isCID, fmt):
class EncodingCompiler(object):
def __init__(self, strings, encoding, parent):
- assert not isinstance(encoding, basestring)
+ assert not isinstance(encoding, str)
data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
if len(data0) < len(data1):
@@ -1721,7 +1722,7 @@ class EncodingConverter(SimpleConverter):
return attrs["name"]
encoding = [".notdef"] * 256
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
code = safeEval(attrs["code"])
@@ -1833,7 +1834,7 @@ class FDArrayConverter(TableConverter):
def xmlRead(self, name, attrs, content, parent):
fdArray = FDArrayIndex()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
fdArray.fromXML(name, attrs, content)
@@ -2105,6 +2106,8 @@ privateDictOperators2 = [
(11, 'StdVW', 'number', None, None),
((12, 12), 'StemSnapH', 'delta', None, None),
((12, 13), 'StemSnapV', 'delta', None, None),
+ ((12, 17), 'LanguageGroup', 'number', 0, None),
+ ((12, 18), 'ExpansionFactor', 'number', 0.06, None),
(19, 'Subrs', 'number', None, SubrsConverter()),
]
@@ -2332,7 +2335,7 @@ class TopDictCompiler(DictCompiler):
self.rawDict["charset"] = charsetCode
if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
encoding = self.dictObj.Encoding
- if not isinstance(encoding, basestring):
+ if not isinstance(encoding, str):
children.append(EncodingCompiler(strings, encoding, self))
else:
if hasattr(self.dictObj, "VarStore"):
diff --git a/Lib/fontTools/cffLib/specializer.py b/Lib/fontTools/cffLib/specializer.py
index 1d2f4b73..fbfefa92 100644
--- a/Lib/fontTools/cffLib/specializer.py
+++ b/Lib/fontTools/cffLib/specializer.py
@@ -13,12 +13,11 @@ and specific forms of the operation.
"""
-from fontTools.misc.py23 import *
from fontTools.cffLib import maxStackLimit
def stringToProgram(string):
- if isinstance(string, basestring):
+ if isinstance(string, str):
string = string.split()
program = []
for token in string:
@@ -70,7 +69,7 @@ def programToCommands(program, getNumRegions=None):
it = iter(program)
for token in it:
- if not isinstance(token, basestring):
+ if not isinstance(token, str):
stack.append(token)
continue
diff --git a/Lib/fontTools/cffLib/width.py b/Lib/fontTools/cffLib/width.py
index edce446f..00b859bb 100644
--- a/Lib/fontTools/cffLib/width.py
+++ b/Lib/fontTools/cffLib/width.py
@@ -7,11 +7,10 @@ value do not need to specify their width in their charstring, saving bytes.
This module determines the optimum ``defaultWidthX`` and ``nominalWidthX``
values for a font, when provided with a list of glyph widths."""
-from fontTools.misc.py23 import *
-from fontTools.ttLib import TTFont, getTableClass
+from fontTools.ttLib import TTFont
from collections import defaultdict
from operator import add
-from functools import partial, reduce
+from functools import reduce
class missingdict(dict):
diff --git a/Lib/fontTools/colorLib/geometry.py b/Lib/fontTools/colorLib/geometry.py
index ec647535..e62aead1 100644
--- a/Lib/fontTools/colorLib/geometry.py
+++ b/Lib/fontTools/colorLib/geometry.py
@@ -1,7 +1,7 @@
"""Helpers for manipulating 2D points and vectors in COLR table."""
from math import copysign, cos, hypot, pi
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
def _vector_between(origin, target):
diff --git a/Lib/fontTools/colorLib/table_builder.py b/Lib/fontTools/colorLib/table_builder.py
index 18e2de18..6fba6b0f 100644
--- a/Lib/fontTools/colorLib/table_builder.py
+++ b/Lib/fontTools/colorLib/table_builder.py
@@ -22,7 +22,7 @@ from fontTools.ttLib.tables.otConverters import (
IntValue,
FloatValue,
)
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
class BuildCallback(enum.Enum):
diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py
index f69c9303..9ea22fe6 100644
--- a/Lib/fontTools/designspaceLib/__init__.py
+++ b/Lib/fontTools/designspaceLib/__init__.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes, tostr
from fontTools.misc.loggingTools import LogMixin
import collections
+from io import BytesIO, StringIO
import os
import posixpath
from fontTools.misc import etree as ET
@@ -290,25 +291,25 @@ class InstanceDescriptor(SimpleDescriptor):
filename = posixpath_property("_filename")
def setStyleName(self, styleName, languageCode="en"):
- self.localisedStyleName[languageCode] = tounicode(styleName)
+ self.localisedStyleName[languageCode] = tostr(styleName)
def getStyleName(self, languageCode="en"):
return self.localisedStyleName.get(languageCode)
def setFamilyName(self, familyName, languageCode="en"):
- self.localisedFamilyName[languageCode] = tounicode(familyName)
+ self.localisedFamilyName[languageCode] = tostr(familyName)
def getFamilyName(self, languageCode="en"):
return self.localisedFamilyName.get(languageCode)
def setStyleMapStyleName(self, styleMapStyleName, languageCode="en"):
- self.localisedStyleMapStyleName[languageCode] = tounicode(styleMapStyleName)
+ self.localisedStyleMapStyleName[languageCode] = tostr(styleMapStyleName)
def getStyleMapStyleName(self, languageCode="en"):
return self.localisedStyleMapStyleName.get(languageCode)
def setStyleMapFamilyName(self, styleMapFamilyName, languageCode="en"):
- self.localisedStyleMapFamilyName[languageCode] = tounicode(styleMapFamilyName)
+ self.localisedStyleMapFamilyName[languageCode] = tostr(styleMapFamilyName)
def getStyleMapFamilyName(self, languageCode="en"):
return self.localisedStyleMapFamilyName.get(languageCode)
@@ -823,7 +824,7 @@ class BaseDocReader(LogMixin):
# '{http://www.w3.org/XML/1998/namespace}lang'
for key, lang in labelNameElement.items():
if key == XML_LANG:
- axisObject.labelNames[lang] = tounicode(labelNameElement.text)
+ axisObject.labelNames[lang] = tostr(labelNameElement.text)
self.documentObject.axes.append(axisObject)
self.axisDefaults[axisObject.name] = axisObject.default
@@ -1099,10 +1100,10 @@ class DesignSpaceDocument(LogMixin, AsDictMixin):
return self
def tostring(self, encoding=None):
- if encoding is unicode or (
+ if encoding is str or (
encoding is not None and encoding.lower() == "unicode"
):
- f = UnicodeIO()
+ f = StringIO()
xml_declaration = False
elif encoding is None or encoding == "utf-8":
f = BytesIO()
diff --git a/Lib/fontTools/encodings/MacRoman.py b/Lib/fontTools/encodings/MacRoman.py
index 9e8f2441..25232d38 100644
--- a/Lib/fontTools/encodings/MacRoman.py
+++ b/Lib/fontTools/encodings/MacRoman.py
@@ -1,5 +1,3 @@
-from fontTools.misc.py23 import *
-
MacRoman = [
'NUL', 'Eth', 'eth', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Yacute',
'yacute', 'HT', 'LF', 'Thorn', 'thorn', 'CR', 'Zcaron', 'zcaron', 'DLE', 'DC1',
diff --git a/Lib/fontTools/encodings/StandardEncoding.py b/Lib/fontTools/encodings/StandardEncoding.py
index 60b58734..810b2a09 100644
--- a/Lib/fontTools/encodings/StandardEncoding.py
+++ b/Lib/fontTools/encodings/StandardEncoding.py
@@ -1,5 +1,3 @@
-from fontTools.misc.py23 import *
-
StandardEncoding = [
'.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
'.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
diff --git a/Lib/fontTools/encodings/__init__.py b/Lib/fontTools/encodings/__init__.py
index b1760311..156cb232 100644
--- a/Lib/fontTools/encodings/__init__.py
+++ b/Lib/fontTools/encodings/__init__.py
@@ -1,3 +1 @@
"""Empty __init__.py file to signal Python this directory is a package."""
-
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/encodings/codecs.py b/Lib/fontTools/encodings/codecs.py
index c2288a77..3b1a8256 100644
--- a/Lib/fontTools/encodings/codecs.py
+++ b/Lib/fontTools/encodings/codecs.py
@@ -1,7 +1,6 @@
"""Extend the Python codecs module with a few encodings that are used in OpenType (name table)
but missing from Python. See https://github.com/fonttools/fonttools/issues/236 for details."""
-from fontTools.misc.py23 import *
import codecs
import encodings
@@ -57,35 +56,35 @@ class ExtendCodec(codecs.Codec):
_extended_encodings = {
"x_mac_japanese_ttx": ("shift_jis", {
- b"\xFC": unichr(0x007C),
- b"\x7E": unichr(0x007E),
- b"\x80": unichr(0x005C),
- b"\xA0": unichr(0x00A0),
- b"\xFD": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\xFC": chr(0x007C),
+ b"\x7E": chr(0x007E),
+ b"\x80": chr(0x005C),
+ b"\xA0": chr(0x00A0),
+ b"\xFD": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
"x_mac_trad_chinese_ttx": ("big5", {
- b"\x80": unichr(0x005C),
- b"\xA0": unichr(0x00A0),
- b"\xFD": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\x80": chr(0x005C),
+ b"\xA0": chr(0x00A0),
+ b"\xFD": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
"x_mac_korean_ttx": ("euc_kr", {
- b"\x80": unichr(0x00A0),
- b"\x81": unichr(0x20A9),
- b"\x82": unichr(0x2014),
- b"\x83": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\x80": chr(0x00A0),
+ b"\x81": chr(0x20A9),
+ b"\x82": chr(0x2014),
+ b"\x83": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
"x_mac_simp_chinese_ttx": ("gb2312", {
- b"\x80": unichr(0x00FC),
- b"\xA0": unichr(0x00A0),
- b"\xFD": unichr(0x00A9),
- b"\xFE": unichr(0x2122),
- b"\xFF": unichr(0x2026),
+ b"\x80": chr(0x00FC),
+ b"\xA0": chr(0x00A0),
+ b"\xFD": chr(0x00A9),
+ b"\xFE": chr(0x2122),
+ b"\xFF": chr(0x2026),
}),
}
diff --git a/Lib/fontTools/feaLib/__main__.py b/Lib/fontTools/feaLib/__main__.py
index 348cf0a9..99c64231 100644
--- a/Lib/fontTools/feaLib/__main__.py
+++ b/Lib/fontTools/feaLib/__main__.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.feaLib.builder import addOpenTypeFeatures, Builder
from fontTools.feaLib.error import FeatureLibError
diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py
index 6c2bfce8..763d0d2c 100644
--- a/Lib/fontTools/feaLib/ast.py
+++ b/Lib/fontTools/feaLib/ast.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord, tobytes
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.location import FeatureLibLocation
from fontTools.misc.encodingTools import getEncoding
@@ -28,12 +28,15 @@ __all__ = [
"Anchor",
"AnchorDefinition",
"AttachStatement",
+ "AxisValueLocationStatement",
"BaseAxis",
"CVParametersNameStatement",
"ChainContextPosStatement",
"ChainContextSubstStatement",
"CharacterStatement",
"CursivePosStatement",
+ "ElidedFallbackName",
+ "ElidedFallbackNameID",
"Expression",
"FeatureNameStatement",
"FeatureReferenceStatement",
@@ -62,6 +65,9 @@ __all__ = [
"SingleSubstStatement",
"SizeParameters",
"Statement",
+ "STATAxisValueStatement",
+ "STATDesignAxisStatement",
+ "STATNameStatement",
"SubtableStatement",
"TableBlock",
"ValueRecord",
@@ -252,7 +258,7 @@ class GlyphClass(Expression):
def add_range(self, start, end, glyphs):
"""Add a range (e.g. ``A-Z``) to the class. ``start`` and ``end``
- are either :class:`GlyphName` objects or strings representing the
+ are either :class:`GlyphName` objects or strings representing the
start and end glyphs in the class, and ``glyphs`` is the full list of
:class:`GlyphName` objects in the range."""
if self.curr < len(self.glyphs):
@@ -547,7 +553,7 @@ class MarkClass(object):
class MarkClassDefinition(Statement):
- """A single ``markClass`` statement. The ``markClass`` should be a
+ """A single ``markClass`` statement. The ``markClass`` should be a
:class:`MarkClass` object, the ``anchor`` an :class:`Anchor` object,
and the ``glyphs`` parameter should be a `glyph-containing object`_ .
@@ -849,7 +855,7 @@ class IgnorePosStatement(Statement):
"""An ``ignore pos`` statement, containing `one or more` contexts to ignore.
``chainContexts`` should be a list of ``(prefix, glyphs, suffix)`` tuples,
- with each of ``prefix``, ``glyphs`` and ``suffix`` being
+ with each of ``prefix``, ``glyphs`` and ``suffix`` being
`glyph-containing objects`_ ."""
def __init__(self, chainContexts, location=None):
@@ -1146,7 +1152,7 @@ class MarkBasePosStatement(Statement):
def asFea(self, indent=""):
res = "pos base {}".format(self.base.asFea())
for a, m in self.marks:
- res += " {} mark @{}".format(a.asFea(), m.name)
+ res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
res += ";"
return res
@@ -1165,7 +1171,7 @@ class MarkLigPosStatement(Statement):
# ... add definitions to mark classes...
glyph = GlyphName("lam_meem_jeem")
- marks = [
+ marks = [
[ (Anchor(625,1800), m1) ], # Attachments on 1st component (lam)
[ (Anchor(376,-378), m2) ], # Attachments on 2nd component (meem)
[ ] # No attachments on the jeem
@@ -1192,10 +1198,15 @@ class MarkLigPosStatement(Statement):
for l in self.marks:
temp = ""
if l is None or not len(l):
- temp = " <anchor NULL>"
+ temp = "\n" + indent + SHIFT * 2 + "<anchor NULL>"
else:
for a, m in l:
- temp += " {} mark @{}".format(a.asFea(), m.name)
+ temp += (
+ "\n"
+ + indent
+ + SHIFT * 2
+ + "{} mark @{}".format(a.asFea(), m.name)
+ )
ligs.append(temp)
res += ("\n" + indent + SHIFT + "ligComponent").join(ligs)
res += ";"
@@ -1218,7 +1229,7 @@ class MarkMarkPosStatement(Statement):
def asFea(self, indent=""):
res = "pos mark {}".format(self.baseMarks.asFea())
for a, m in self.marks:
- res += " {} mark @{}".format(a.asFea(), m.name)
+ res += "\n" + indent + SHIFT + "{} mark @{}".format(a.asFea(), m.name)
res += ";"
return res
@@ -1261,7 +1272,7 @@ class MultipleSubstStatement(Statement):
res += " " + " ".join(map(asFea, self.suffix))
else:
res += asFea(self.glyph)
- replacement = self.replacement or [ NullGlyph() ]
+ replacement = self.replacement or [NullGlyph()]
res += " by "
res += " ".join(map(asFea, replacement))
res += ";"
@@ -1657,7 +1668,7 @@ class NameRecord(Statement):
def escape(c, escape_pattern):
# Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
- return unichr(c)
+ return chr(c)
else:
return escape_pattern % c
@@ -1699,6 +1710,16 @@ class FeatureNameStatement(NameRecord):
return '{} {}"{}";'.format(tag, plat, self.string)
+class STATNameStatement(NameRecord):
+ """Represents a STAT table ``name`` statement."""
+
+ def asFea(self, indent=""):
+ plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
+ if plat != "":
+ plat += " "
+ return 'name {}"{}";'.format(plat, self.string)
+
+
class SizeParameters(Statement):
"""A ``parameters`` statement."""
@@ -1877,3 +1898,132 @@ class VheaField(Statement):
fields = ("VertTypoAscender", "VertTypoDescender", "VertTypoLineGap")
keywords = dict([(x.lower(), x) for x in fields])
return "{} {};".format(keywords[self.key], self.value)
+
+
+class STATDesignAxisStatement(Statement):
+ """A STAT table Design Axis
+
+ Args:
+ tag (str): a 4 letter axis tag
+ axisOrder (int): an int
+ names (list): a list of :class:`STATNameStatement` objects
+ """
+
+ def __init__(self, tag, axisOrder, names, location=None):
+ Statement.__init__(self, location)
+ self.tag = tag
+ self.axisOrder = axisOrder
+ self.names = names
+ self.location = location
+
+ def build(self, builder):
+ builder.addDesignAxis(self, self.location)
+
+ def asFea(self, indent=""):
+ indent += SHIFT
+ res = f"DesignAxis {self.tag} {self.axisOrder} {{ \n"
+ res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
+ res += "};"
+ return res
+
+
+class ElidedFallbackName(Statement):
+ """STAT table ElidedFallbackName
+
+ Args:
+ names: a list of :class:`STATNameStatement` objects
+ """
+
+ def __init__(self, names, location=None):
+ Statement.__init__(self, location)
+ self.names = names
+ self.location = location
+
+ def build(self, builder):
+ builder.setElidedFallbackName(self.names, self.location)
+
+ def asFea(self, indent=""):
+ indent += SHIFT
+ res = "ElidedFallbackName { \n"
+ res += ("\n" + indent).join([s.asFea(indent=indent) for s in self.names]) + "\n"
+ res += "};"
+ return res
+
+
+class ElidedFallbackNameID(Statement):
+ """STAT table ElidedFallbackNameID
+
+ Args:
+ value: an int pointing to an existing name table name ID
+ """
+
+ def __init__(self, value, location=None):
+ Statement.__init__(self, location)
+ self.value = value
+ self.location = location
+
+ def build(self, builder):
+ builder.setElidedFallbackName(self.value, self.location)
+
+ def asFea(self, indent=""):
+ return f"ElidedFallbackNameID {self.value};"
+
+
+class STATAxisValueStatement(Statement):
+ """A STAT table Axis Value Record
+
+ Args:
+ names (list): a list of :class:`STATNameStatement` objects
+ locations (list): a list of :class:`AxisValueLocationStatement` objects
+ flags (int): an int
+ """
+
+ def __init__(self, names, locations, flags, location=None):
+ Statement.__init__(self, location)
+ self.names = names
+ self.locations = locations
+ self.flags = flags
+
+ def build(self, builder):
+ builder.addAxisValueRecord(self, self.location)
+
+ def asFea(self, indent=""):
+ res = "AxisValue {\n"
+ for location in self.locations:
+ res += location.asFea()
+
+ for nameRecord in self.names:
+ res += nameRecord.asFea()
+ res += "\n"
+
+ if self.flags:
+ flags = ["OlderSiblingFontAttribute", "ElidableAxisValueName"]
+ flagStrings = []
+ curr = 1
+ for i in range(len(flags)):
+ if self.flags & curr != 0:
+ flagStrings.append(flags[i])
+ curr = curr << 1
+ res += f"flag {' '.join(flagStrings)};\n"
+ res += "};"
+ return res
+
+
+class AxisValueLocationStatement(Statement):
+ """
+ A STAT table Axis Value Location
+
+ Args:
+ tag (str): a 4 letter axis tag
+ values (list): a list of ints and/or floats
+ """
+
+ def __init__(self, tag, values, location=None):
+ Statement.__init__(self, location)
+ self.tag = tag
+ self.values = values
+
+ def asFea(self, res=""):
+ res += f"location {self.tag} "
+ res += f"{' '.join(str(i) for i in self.values)};\n"
+ return res
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
index 30046bda..4a7d9575 100644
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import binary2num, safeEval
from fontTools.feaLib.error import FeatureLibError
@@ -33,6 +33,7 @@ from fontTools.otlLib.builder import (
from fontTools.otlLib.error import OpenTypeLibError
from collections import defaultdict
import itertools
+from io import StringIO
import logging
import warnings
import os
@@ -78,7 +79,7 @@ def addOpenTypeFeaturesFromString(
"""
- featurefile = UnicodeIO(tounicode(features))
+ featurefile = StringIO(tostr(features))
if filename:
featurefile.name = filename
addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug)
@@ -98,6 +99,7 @@ class Builder(object):
"hhea",
"name",
"vhea",
+ "STAT",
]
)
@@ -159,6 +161,8 @@ class Builder(object):
self.hhea_ = {}
# for table 'vhea'
self.vhea_ = {}
+ # for table 'STAT'
+ self.stat_ = {}
def build(self, tables=None, debug=False):
if self.parseTree is None:
@@ -188,6 +192,8 @@ class Builder(object):
self.build_name()
if "OS/2" in tables:
self.build_OS_2()
+ if "STAT" in tables:
+ self.build_STAT()
for tag in ("GPOS", "GSUB"):
if tag not in tables:
continue
@@ -510,6 +516,140 @@ class Builder(object):
if version >= 5:
checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize"))
+ def setElidedFallbackName(self, value, location):
+ # ElidedFallbackName is a convenience method for setting
+ # ElidedFallbackNameID so only one can be allowed
+ for token in ("ElidedFallbackName", "ElidedFallbackNameID"):
+ if token in self.stat_:
+ raise FeatureLibError(
+ f"{token} is already set.",
+ location,
+ )
+ if isinstance(value, int):
+ self.stat_["ElidedFallbackNameID"] = value
+ elif isinstance(value, list):
+ self.stat_["ElidedFallbackName"] = value
+ else:
+ raise AssertionError(value)
+
+ def addDesignAxis(self, designAxis, location):
+ if "DesignAxes" not in self.stat_:
+ self.stat_["DesignAxes"] = []
+ if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]):
+ raise FeatureLibError(
+ f'DesignAxis already defined for tag "{designAxis.tag}".',
+ location,
+ )
+ if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]):
+ raise FeatureLibError(
+ f"DesignAxis already defined for axis number {designAxis.axisOrder}.",
+ location,
+ )
+ self.stat_["DesignAxes"].append(designAxis)
+
+ def addAxisValueRecord(self, axisValueRecord, location):
+ if "AxisValueRecords" not in self.stat_:
+ self.stat_["AxisValueRecords"] = []
+ # Check for duplicate AxisValueRecords
+ for record_ in self.stat_["AxisValueRecords"]:
+ if (
+ {n.asFea() for n in record_.names}
+ == {n.asFea() for n in axisValueRecord.names}
+ and {n.asFea() for n in record_.locations}
+ == {n.asFea() for n in axisValueRecord.locations}
+ and record_.flags == axisValueRecord.flags
+ ):
+ raise FeatureLibError(
+ "An AxisValueRecord with these values is already defined.",
+ location,
+ )
+ self.stat_["AxisValueRecords"].append(axisValueRecord)
+
+ def build_STAT(self):
+ if not self.stat_:
+ return
+
+ axes = self.stat_.get("DesignAxes")
+ if not axes:
+ raise FeatureLibError("DesignAxes not defined", None)
+ axisValueRecords = self.stat_.get("AxisValueRecords")
+ axisValues = {}
+ format4_locations = []
+ for tag in axes:
+ axisValues[tag.tag] = []
+ if axisValueRecords is not None:
+ for avr in axisValueRecords:
+ valuesDict = {}
+ if avr.flags > 0:
+ valuesDict["flags"] = avr.flags
+ if len(avr.locations) == 1:
+ location = avr.locations[0]
+ values = location.values
+ if len(values) == 1: # format1
+ valuesDict.update({"value": values[0], "name": avr.names})
+ if len(values) == 2: # format3
+ valuesDict.update(
+ {
+ "value": values[0],
+ "linkedValue": values[1],
+ "name": avr.names,
+ }
+ )
+ if len(values) == 3: # format2
+ nominal, minVal, maxVal = values
+ valuesDict.update(
+ {
+ "nominalValue": nominal,
+ "rangeMinValue": minVal,
+ "rangeMaxValue": maxVal,
+ "name": avr.names,
+ }
+ )
+ axisValues[location.tag].append(valuesDict)
+ else:
+ valuesDict.update(
+ {
+ "location": {i.tag: i.values[0] for i in avr.locations},
+ "name": avr.names,
+ }
+ )
+ format4_locations.append(valuesDict)
+
+ designAxes = [
+ {
+ "ordering": a.axisOrder,
+ "tag": a.tag,
+ "name": a.names,
+ "values": axisValues[a.tag],
+ }
+ for a in axes
+ ]
+
+ nameTable = self.font.get("name")
+ if not nameTable: # this only happens for unit tests
+ nameTable = self.font["name"] = newTable("name")
+ nameTable.names = []
+
+ if "ElidedFallbackNameID" in self.stat_:
+ nameID = self.stat_["ElidedFallbackNameID"]
+ name = nameTable.getDebugName(nameID)
+ if not name:
+ raise FeatureLibError(
+ f"ElidedFallbackNameID {nameID} points "
+ "to a nameID that does not exist in the "
+ '"name" table',
+ None,
+ )
+ elif "ElidedFallbackName" in self.stat_:
+ nameID = self.stat_["ElidedFallbackName"]
+
+ otl.buildStatTable(
+ self.font,
+ designAxes,
+ locations=format4_locations,
+ elidedFallbackName=nameID,
+ )
+
def build_codepages_(self, pages):
pages2bits = {
1252: 0,
@@ -718,8 +858,10 @@ class Builder(object):
str(ix)
]._replace(feature=key)
except KeyError:
- warnings.warn("feaLib.Builder subclass needs upgrading to "
- "stash debug information. See fonttools#2065.")
+ warnings.warn(
+ "feaLib.Builder subclass needs upgrading to "
+ "stash debug information. See fonttools#2065."
+ )
feature_key = (feature_tag, lookup_indices)
feature_index = feature_indices.get(feature_key)
diff --git a/Lib/fontTools/feaLib/lexer.py b/Lib/fontTools/feaLib/lexer.py
index 3caf3dc5..140fbd82 100644
--- a/Lib/fontTools/feaLib/lexer.py
+++ b/Lib/fontTools/feaLib/lexer.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.feaLib.error import FeatureLibError, IncludedFeaNotFound
from fontTools.feaLib.location import FeatureLibLocation
import re
diff --git a/Lib/fontTools/feaLib/parser.py b/Lib/fontTools/feaLib/parser.py
index 23a49618..804cba9f 100644
--- a/Lib/fontTools/feaLib/parser.py
+++ b/Lib/fontTools/feaLib/parser.py
@@ -1,7 +1,7 @@
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lexer import Lexer, IncludingLexer, NonIncludingLexer
from fontTools.misc.encodingTools import getEncoding
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, tobytes, tostr
import fontTools.feaLib.ast as ast
import logging
import os
@@ -971,8 +971,8 @@ class Parser(object):
location = self.cur_token_location_
DesignSize = self.expect_decipoint_()
SubfamilyID = self.expect_number_()
- RangeStart = 0
- RangeEnd = 0
+ RangeStart = 0.
+ RangeEnd = 0.
if self.next_token_type_ in (Lexer.NUMBER, Lexer.FLOAT) or SubfamilyID != 0:
RangeStart = self.expect_decipoint_()
RangeEnd = self.expect_decipoint_()
@@ -1003,6 +1003,7 @@ class Parser(object):
"name": self.parse_table_name_,
"BASE": self.parse_table_BASE_,
"OS/2": self.parse_table_OS_2_,
+ "STAT": self.parse_table_STAT_,
}.get(name)
if handler:
handler(table)
@@ -1162,6 +1163,35 @@ class Parser(object):
unescaped = self.unescape_string_(string, encoding)
return platformID, platEncID, langID, unescaped
+ def parse_stat_name_(self):
+ platEncID = None
+ langID = None
+ if self.next_token_type_ in Lexer.NUMBERS:
+ platformID = self.expect_any_number_()
+ location = self.cur_token_location_
+ if platformID not in (1, 3):
+ raise FeatureLibError("Expected platform id 1 or 3", location)
+ if self.next_token_type_ in Lexer.NUMBERS:
+ platEncID = self.expect_any_number_()
+ langID = self.expect_any_number_()
+ else:
+ platformID = 3
+ location = self.cur_token_location_
+
+ if platformID == 1: # Macintosh
+ platEncID = platEncID or 0 # Roman
+ langID = langID or 0 # English
+ else: # 3, Windows
+ platEncID = platEncID or 1 # Unicode
+ langID = langID or 0x0409 # English
+
+ string = self.expect_string_()
+ encoding = getEncoding(platformID, platEncID, langID)
+ if encoding is None:
+ raise FeatureLibError("Unsupported encoding", location)
+ unescaped = self.unescape_string_(string, encoding)
+ return platformID, platEncID, langID, unescaped
+
def parse_nameid_(self):
assert self.cur_token_ == "nameid", self.cur_token_
location, nameID = self.cur_token_location_, self.expect_any_number_()
@@ -1192,12 +1222,12 @@ class Parser(object):
# We convert surrogates to actual Unicode by round-tripping through
# Python's UTF-16 codec in a special mode.
utf16 = tobytes(s, "utf_16_be", "surrogatepass")
- return tounicode(utf16, "utf_16_be")
+ return tostr(utf16, "utf_16_be")
@staticmethod
def unescape_unichr_(match):
n = match.group(0)[1:]
- return unichr(int(n, 16))
+ return chr(int(n, 16))
@staticmethod
def unescape_byte_(match, encoding):
@@ -1283,6 +1313,198 @@ class Parser(object):
elif self.cur_token_ == ";":
continue
+ def parse_STAT_ElidedFallbackName(self):
+ assert self.is_cur_keyword_("ElidedFallbackName")
+ self.expect_symbol_("{")
+ names = []
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_()
+ if self.is_cur_keyword_("name"):
+ platformID, platEncID, langID, string = self.parse_stat_name_()
+ nameRecord = self.ast.STATNameStatement(
+ "stat",
+ platformID,
+ platEncID,
+ langID,
+ string,
+ location=self.cur_token_location_,
+ )
+ names.append(nameRecord)
+ else:
+ if self.cur_token_ != ";":
+ raise FeatureLibError(
+ f"Unexpected token {self.cur_token_} " f"in ElidedFallbackName",
+ self.cur_token_location_,
+ )
+ self.expect_symbol_("}")
+ if not names:
+ raise FeatureLibError('Expected "name"', self.cur_token_location_)
+ return names
+
+ def parse_STAT_design_axis(self):
+ assert self.is_cur_keyword_("DesignAxis")
+ names = []
+ axisTag = self.expect_tag_()
+ if (
+ axisTag not in ("ital", "opsz", "slnt", "wdth", "wght")
+ and not axisTag.isupper()
+ ):
+ log.warning(f"Unregistered axis tag {axisTag} should be uppercase.")
+ axisOrder = self.expect_number_()
+ self.expect_symbol_("{")
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_()
+ if self.cur_token_type_ is Lexer.COMMENT:
+ continue
+ elif self.is_cur_keyword_("name"):
+ location = self.cur_token_location_
+ platformID, platEncID, langID, string = self.parse_stat_name_()
+ name = self.ast.STATNameStatement(
+ "stat", platformID, platEncID, langID, string, location=location
+ )
+ names.append(name)
+ elif self.cur_token_ == ";":
+ continue
+ else:
+ raise FeatureLibError(
+ f'Expected "name", got {self.cur_token_}', self.cur_token_location_
+ )
+
+ self.expect_symbol_("}")
+ return self.ast.STATDesignAxisStatement(
+ axisTag, axisOrder, names, self.cur_token_location_
+ )
+
+ def parse_STAT_axis_value_(self):
+ assert self.is_cur_keyword_("AxisValue")
+ self.expect_symbol_("{")
+ locations = []
+ names = []
+ flags = 0
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_(comments=True)
+ if self.cur_token_type_ is Lexer.COMMENT:
+ continue
+ elif self.is_cur_keyword_("name"):
+ location = self.cur_token_location_
+ platformID, platEncID, langID, string = self.parse_stat_name_()
+ name = self.ast.STATNameStatement(
+ "stat", platformID, platEncID, langID, string, location=location
+ )
+ names.append(name)
+ elif self.is_cur_keyword_("location"):
+ location = self.parse_STAT_location()
+ locations.append(location)
+ elif self.is_cur_keyword_("flag"):
+ flags = self.expect_stat_flags()
+ elif self.cur_token_ == ";":
+ continue
+ else:
+ raise FeatureLibError(
+ f"Unexpected token {self.cur_token_} " f"in AxisValue",
+ self.cur_token_location_,
+ )
+ self.expect_symbol_("}")
+ if not names:
+ raise FeatureLibError('Expected "Axis Name"', self.cur_token_location_)
+ if not locations:
+ raise FeatureLibError('Expected "Axis location"', self.cur_token_location_)
+ if len(locations) > 1:
+ for location in locations:
+ if len(location.values) > 1:
+ raise FeatureLibError(
+ "Only one value is allowed in a "
+ "Format 4 Axis Value Record, but "
+ f"{len(location.values)} were found.",
+ self.cur_token_location_,
+ )
+ format4_tags = []
+ for location in locations:
+ tag = location.tag
+ if tag in format4_tags:
+ raise FeatureLibError(
+ f"Axis tag {tag} already " "defined.", self.cur_token_location_
+ )
+ format4_tags.append(tag)
+
+ return self.ast.STATAxisValueStatement(
+ names, locations, flags, self.cur_token_location_
+ )
+
+ def parse_STAT_location(self):
+ values = []
+ tag = self.expect_tag_()
+ if len(tag.strip()) != 4:
+ raise FeatureLibError(
+ f"Axis tag {self.cur_token_} must be 4 " "characters",
+ self.cur_token_location_,
+ )
+
+ while self.next_token_ != ";":
+ if self.next_token_type_ is Lexer.FLOAT:
+ value = self.expect_float_()
+ values.append(value)
+ elif self.next_token_type_ is Lexer.NUMBER:
+ value = self.expect_number_()
+ values.append(value)
+ else:
+ raise FeatureLibError(
+ f'Unexpected value "{self.next_token_}". '
+ "Expected integer or float.",
+ self.next_token_location_,
+ )
+ if len(values) == 3:
+ nominal, min_val, max_val = values
+ if nominal < min_val or nominal > max_val:
+ raise FeatureLibError(
+ f"Default value {nominal} is outside "
+ f"of specified range "
+ f"{min_val}-{max_val}.",
+ self.next_token_location_,
+ )
+ return self.ast.AxisValueLocationStatement(tag, values)
+
+ def parse_table_STAT_(self, table):
+ statements = table.statements
+ design_axes = []
+ while self.next_token_ != "}" or self.cur_comments_:
+ self.advance_lexer_(comments=True)
+ if self.cur_token_type_ is Lexer.COMMENT:
+ statements.append(
+ self.ast.Comment(self.cur_token_, location=self.cur_token_location_)
+ )
+ elif self.cur_token_type_ is Lexer.NAME:
+ if self.is_cur_keyword_("ElidedFallbackName"):
+ names = self.parse_STAT_ElidedFallbackName()
+ statements.append(self.ast.ElidedFallbackName(names))
+ elif self.is_cur_keyword_("ElidedFallbackNameID"):
+ value = self.expect_number_()
+ statements.append(self.ast.ElidedFallbackNameID(value))
+ self.expect_symbol_(";")
+ elif self.is_cur_keyword_("DesignAxis"):
+ designAxis = self.parse_STAT_design_axis()
+ design_axes.append(designAxis.tag)
+ statements.append(designAxis)
+ self.expect_symbol_(";")
+ elif self.is_cur_keyword_("AxisValue"):
+ axisValueRecord = self.parse_STAT_axis_value_()
+ for location in axisValueRecord.locations:
+ if location.tag not in design_axes:
+ # Tag must be defined in a DesignAxis before it
+ # can be referenced
+ raise FeatureLibError(
+ "DesignAxis not defined for " f"{location.tag}.",
+ self.cur_token_location_,
+ )
+ statements.append(axisValueRecord)
+ self.expect_symbol_(";")
+ else:
+ raise FeatureLibError(
+ f"Unexpected token {self.cur_token_}", self.cur_token_location_
+ )
+ elif self.cur_token_ == ";":
+ continue
+
def parse_base_tag_list_(self):
# Parses BASE table entries. (See `section 9.a <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#9.a>`_)
assert self.cur_token_ in (
@@ -1784,7 +2006,7 @@ class Parser(object):
raise FeatureLibError("Expected a tag", self.cur_token_location_)
if len(self.cur_token_) > 4:
raise FeatureLibError(
- "Tags can not be longer than 4 characters", self.cur_token_location_
+ "Tags cannot be longer than 4 characters", self.cur_token_location_
)
return (self.cur_token_ + " ")[:4]
@@ -1856,6 +2078,32 @@ class Parser(object):
"Expected an integer or floating-point number", self.cur_token_location_
)
+ def expect_stat_flags(self):
+ value = 0
+ flags = {
+ "OlderSiblingFontAttribute": 1,
+ "ElidableAxisValueName": 2,
+ }
+ while self.next_token_ != ";":
+ if self.next_token_ in flags:
+ name = self.expect_name_()
+ value = value | flags[name]
+ else:
+ raise FeatureLibError(
+ f"Unexpected STAT flag {self.cur_token_}", self.cur_token_location_
+ )
+ return value
+
+ def expect_stat_values_(self):
+ if self.next_token_type_ == Lexer.FLOAT:
+ return self.expect_float_()
+ elif self.next_token_type_ is Lexer.NUMBER:
+ return self.expect_number_()
+ else:
+ raise FeatureLibError(
+ "Expected an integer or floating-point number", self.cur_token_location_
+ )
+
def expect_string_(self):
self.advance_lexer_()
if self.cur_token_type_ is Lexer.STRING:
diff --git a/Lib/fontTools/fontBuilder.py b/Lib/fontTools/fontBuilder.py
index f4c943f3..e2824084 100644
--- a/Lib/fontTools/fontBuilder.py
+++ b/Lib/fontTools/fontBuilder.py
@@ -129,10 +129,8 @@ fb.save("test.otf")
```
"""
-from .misc.py23 import *
from .ttLib import TTFont, newTable
from .ttLib.tables._c_m_a_p import cmap_classes
-from .ttLib.tables._n_a_m_e import NameRecord, makeName
from .misc.timeTools import timestampNow
import struct
from collections import OrderedDict
@@ -478,7 +476,7 @@ class FontBuilder(object):
nameID = nameName
else:
nameID = _nameIDs[nameName]
- if isinstance(nameValue, basestring):
+ if isinstance(nameValue, str):
nameValue = dict(en=nameValue)
nameTable.addMultilingualName(
nameValue, ttFont=self.font, nameID=nameID, windows=windows, mac=mac
@@ -894,7 +892,6 @@ def buildCmapSubTable(cmapping, format, platformID, platEncID):
def addFvar(font, axes, instances):
from .ttLib.tables._f_v_a_r import Axis, NamedInstance
- from .designspaceLib import AxisDescriptor
assert axes
diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py
index 96b80236..2df22a8d 100644
--- a/Lib/fontTools/merge.py
+++ b/Lib/fontTools/merge.py
@@ -2,7 +2,6 @@
#
# Google Author(s): Behdad Esfahbod, Roozbeh Pournader
-from fontTools.misc.py23 import *
from fontTools.misc.timeTools import timestampNow
from fontTools import ttLib, cffLib
from fontTools.ttLib.tables import otTables, _h_e_a_d
diff --git a/Lib/fontTools/misc/__init__.py b/Lib/fontTools/misc/__init__.py
index b1760311..156cb232 100644
--- a/Lib/fontTools/misc/__init__.py
+++ b/Lib/fontTools/misc/__init__.py
@@ -1,3 +1 @@
"""Empty __init__.py file to signal Python this directory is a package."""
-
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/misc/arrayTools.py b/Lib/fontTools/misc/arrayTools.py
index e76ced7f..c20a9eda 100644
--- a/Lib/fontTools/misc/arrayTools.py
+++ b/Lib/fontTools/misc/arrayTools.py
@@ -2,11 +2,11 @@
so on.
"""
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
-from numbers import Number
+from fontTools.misc.roundTools import otRound
+from fontTools.misc.vector import Vector as _Vector
import math
-import operator
+import warnings
+
def calcBounds(array):
"""Calculate the bounding rectangle of a 2D points array.
@@ -228,6 +228,19 @@ def rectCenter(rect):
(xMin, yMin, xMax, yMax) = rect
return (xMin+xMax)/2, (yMin+yMax)/2
+def rectArea(rect):
+ """Determine rectangle area.
+
+ Args:
+ rect: Bounding rectangle, expressed as tuples
+ ``(xMin, yMin, xMax, yMax)``.
+
+ Returns:
+ The area of the rectangle.
+ """
+ (xMin, yMin, xMax, yMax) = rect
+ return (yMax - yMin) * (xMax - xMin)
+
def intRect(rect):
"""Round a rectangle to integer values.
@@ -248,107 +261,14 @@ def intRect(rect):
return (xMin, yMin, xMax, yMax)
-class Vector(object):
- """A math-like vector.
-
- Represents an n-dimensional numeric vector. ``Vector`` objects support
- vector addition and subtraction, scalar multiplication and division,
- negation, rounding, and comparison tests.
-
- Attributes:
- values: Sequence of values stored in the vector.
- """
+class Vector(_Vector):
- def __init__(self, values, keep=False):
- """Initialize a vector. If ``keep`` is true, values will be copied."""
- self.values = values if keep else list(values)
-
- def __getitem__(self, index):
- return self.values[index]
-
- def __len__(self):
- return len(self.values)
-
- def __repr__(self):
- return "Vector(%s)" % self.values
-
- def _vectorOp(self, other, op):
- if isinstance(other, Vector):
- assert len(self.values) == len(other.values)
- a = self.values
- b = other.values
- return [op(a[i], b[i]) for i in range(len(self.values))]
- if isinstance(other, Number):
- return [op(v, other) for v in self.values]
- raise NotImplementedError
-
- def _scalarOp(self, other, op):
- if isinstance(other, Number):
- return [op(v, other) for v in self.values]
- raise NotImplementedError
-
- def _unaryOp(self, op):
- return [op(v) for v in self.values]
-
- def __add__(self, other):
- return Vector(self._vectorOp(other, operator.add), keep=True)
- def __iadd__(self, other):
- self.values = self._vectorOp(other, operator.add)
- return self
- __radd__ = __add__
-
- def __sub__(self, other):
- return Vector(self._vectorOp(other, operator.sub), keep=True)
- def __isub__(self, other):
- self.values = self._vectorOp(other, operator.sub)
- return self
- def __rsub__(self, other):
- return other + (-self)
-
- def __mul__(self, other):
- return Vector(self._scalarOp(other, operator.mul), keep=True)
- def __imul__(self, other):
- self.values = self._scalarOp(other, operator.mul)
- return self
- __rmul__ = __mul__
-
- def __truediv__(self, other):
- return Vector(self._scalarOp(other, operator.truediv), keep=True)
- def __itruediv__(self, other):
- self.values = self._scalarOp(other, operator.truediv)
- return self
-
- def __pos__(self):
- return Vector(self._unaryOp(operator.pos), keep=True)
- def __neg__(self):
- return Vector(self._unaryOp(operator.neg), keep=True)
- def __round__(self):
- return Vector(self._unaryOp(round), keep=True)
- def toInt(self):
- """Synonym for ``round``."""
- return self.__round__()
-
- def __eq__(self, other):
- if type(other) == Vector:
- return self.values == other.values
- else:
- return self.values == other
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __bool__(self):
- return any(self.values)
- __nonzero__ = __bool__
-
- def __abs__(self):
- return math.sqrt(sum([x*x for x in self.values]))
- def dot(self, other):
- """Performs vector dot product, returning sum of
- ``a[0] * b[0], a[1] * b[1], ...``"""
- a = self.values
- b = other.values if type(other) == Vector else b
- assert len(a) == len(b)
- return sum([a[i] * b[i] for i in range(len(a))])
+ def __init__(self, *args, **kwargs):
+ warnings.warn(
+ "fontTools.misc.arrayTools.Vector has been deprecated, please use "
+ "fontTools.misc.vector.Vector instead.",
+ DeprecationWarning,
+ )
def pairwise(iterable, reverse=False):
diff --git a/Lib/fontTools/misc/bezierTools.py b/Lib/fontTools/misc/bezierTools.py
index 659de34e..2cf2640c 100644
--- a/Lib/fontTools/misc/bezierTools.py
+++ b/Lib/fontTools/misc/bezierTools.py
@@ -2,9 +2,12 @@
"""fontTools.misc.bezierTools.py -- tools for working with Bezier path segments.
"""
-from fontTools.misc.arrayTools import calcBounds
-from fontTools.misc.py23 import *
+from fontTools.misc.arrayTools import calcBounds, sectRect, rectArea
+from fontTools.misc.transform import Identity
import math
+from collections import namedtuple
+
+Intersection = namedtuple("Intersection", ["pt", "t1", "t2"])
__all__ = [
@@ -25,6 +28,14 @@ __all__ = [
"splitCubicAtT",
"solveQuadratic",
"solveCubic",
+ "quadraticPointAtT",
+ "cubicPointAtT",
+ "linePointAtT",
+ "segmentPointAtT",
+ "lineLineIntersections",
+ "curveLineIntersections",
+ "curveCurveIntersections",
+ "segmentSegmentIntersections",
]
@@ -42,23 +53,31 @@ def calcCubicArcLength(pt1, pt2, pt3, pt4, tolerance=0.005):
Returns:
Arc length value.
"""
- return calcCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance)
+ return calcCubicArcLengthC(
+ complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4), tolerance
+ )
def _split_cubic_into_two(p0, p1, p2, p3):
- mid = (p0 + 3 * (p1 + p2) + p3) * .125
- deriv3 = (p3 + p2 - p1 - p0) * .125
- return ((p0, (p0 + p1) * .5, mid - deriv3, mid),
- (mid, mid + deriv3, (p2 + p3) * .5, p3))
+ mid = (p0 + 3 * (p1 + p2) + p3) * 0.125
+ deriv3 = (p3 + p2 - p1 - p0) * 0.125
+ return (
+ (p0, (p0 + p1) * 0.5, mid - deriv3, mid),
+ (mid, mid + deriv3, (p2 + p3) * 0.5, p3),
+ )
+
def _calcCubicArcLengthCRecurse(mult, p0, p1, p2, p3):
- arch = abs(p0-p3)
- box = abs(p0-p1) + abs(p1-p2) + abs(p2-p3)
- if arch * mult >= box:
- return (arch + box) * .5
- else:
- one,two = _split_cubic_into_two(p0,p1,p2,p3)
- return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(mult, *two)
+ arch = abs(p0 - p3)
+ box = abs(p0 - p1) + abs(p1 - p2) + abs(p2 - p3)
+ if arch * mult >= box:
+ return (arch + box) * 0.5
+ else:
+ one, two = _split_cubic_into_two(p0, p1, p2, p3)
+ return _calcCubicArcLengthCRecurse(mult, *one) + _calcCubicArcLengthCRecurse(
+ mult, *two
+ )
+
def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005):
"""Calculates the arc length for a cubic Bezier segment.
@@ -70,7 +89,7 @@ def calcCubicArcLengthC(pt1, pt2, pt3, pt4, tolerance=0.005):
Returns:
Arc length value.
"""
- mult = 1. + 1.5 * tolerance # The 1.5 is a empirical hack; no math
+ mult = 1.0 + 1.5 * tolerance # The 1.5 is a empirical hack; no math
return _calcCubicArcLengthCRecurse(mult, pt1, pt2, pt3, pt4)
@@ -85,7 +104,7 @@ def _dot(v1, v2):
def _intSecAtan(x):
# In : sympy.integrate(sp.sec(sp.atan(x)))
# Out: x*sqrt(x**2 + 1)/2 + asinh(x)/2
- return x * math.sqrt(x**2 + 1)/2 + math.asinh(x)/2
+ return x * math.sqrt(x ** 2 + 1) / 2 + math.asinh(x) / 2
def calcQuadraticArcLength(pt1, pt2, pt3):
@@ -141,16 +160,16 @@ def calcQuadraticArcLengthC(pt1, pt2, pt3):
d = d1 - d0
n = d * 1j
scale = abs(n)
- if scale == 0.:
- return abs(pt3-pt1)
- origDist = _dot(n,d0)
+ if scale == 0.0:
+ return abs(pt3 - pt1)
+ origDist = _dot(n, d0)
if abs(origDist) < epsilon:
- if _dot(d0,d1) >= 0:
- return abs(pt3-pt1)
+ if _dot(d0, d1) >= 0:
+ return abs(pt3 - pt1)
a, b = abs(d0), abs(d1)
- return (a*a + b*b) / (a+b)
- x0 = _dot(d,d0) / origDist
- x1 = _dot(d,d1) / origDist
+ return (a * a + b * b) / (a + b)
+ x0 = _dot(d, d0) / origDist
+ x1 = _dot(d, d1) / origDist
Len = abs(2 * (_intSecAtan(x1) - _intSecAtan(x0)) * origDist / (scale * (x1 - x0)))
return Len
@@ -190,13 +209,17 @@ def approximateQuadraticArcLengthC(pt1, pt2, pt3):
# to be integrated with the best-matching fifth-degree polynomial
# approximation of it.
#
- #https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Legendre_quadrature
+ # https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Legendre_quadrature
# abs(BezierCurveC[2].diff(t).subs({t:T})) for T in sorted(.5, .5±sqrt(3/5)/2),
# weighted 5/18, 8/18, 5/18 respectively.
- v0 = abs(-0.492943519233745*pt1 + 0.430331482911935*pt2 + 0.0626120363218102*pt3)
- v1 = abs(pt3-pt1)*0.4444444444444444
- v2 = abs(-0.0626120363218102*pt1 - 0.430331482911935*pt2 + 0.492943519233745*pt3)
+ v0 = abs(
+ -0.492943519233745 * pt1 + 0.430331482911935 * pt2 + 0.0626120363218102 * pt3
+ )
+ v1 = abs(pt3 - pt1) * 0.4444444444444444
+ v2 = abs(
+ -0.0626120363218102 * pt1 - 0.430331482911935 * pt2 + 0.492943519233745 * pt3
+ )
return v0 + v1 + v2
@@ -220,14 +243,18 @@ def calcQuadraticBounds(pt1, pt2, pt3):
(0.0, 0.0, 100, 100)
"""
(ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3)
- ax2 = ax*2.0
- ay2 = ay*2.0
+ ax2 = ax * 2.0
+ ay2 = ay * 2.0
roots = []
if ax2 != 0:
- roots.append(-bx/ax2)
+ roots.append(-bx / ax2)
if ay2 != 0:
- roots.append(-by/ay2)
- points = [(ax*t*t + bx*t + cx, ay*t*t + by*t + cy) for t in roots if 0 <= t < 1] + [pt1, pt3]
+ roots.append(-by / ay2)
+ points = [
+ (ax * t * t + bx * t + cx, ay * t * t + by * t + cy)
+ for t in roots
+ if 0 <= t < 1
+ ] + [pt1, pt3]
return calcBounds(points)
@@ -256,7 +283,9 @@ def approximateCubicArcLength(pt1, pt2, pt3, pt4):
>>> approximateCubicArcLength((0, 0), (50, 0), (100, -50), (-50, 0)) # cusp
154.80848416537057
"""
- return approximateCubicArcLengthC(complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4))
+ return approximateCubicArcLengthC(
+ complex(*pt1), complex(*pt2), complex(*pt3), complex(*pt4)
+ )
def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
@@ -276,11 +305,21 @@ def approximateCubicArcLengthC(pt1, pt2, pt3, pt4):
# abs(BezierCurveC[3].diff(t).subs({t:T})) for T in sorted(0, .5±(3/7)**.5/2, .5, 1),
# weighted 1/20, 49/180, 32/90, 49/180, 1/20 respectively.
- v0 = abs(pt2-pt1)*.15
- v1 = abs(-0.558983582205757*pt1 + 0.325650248872424*pt2 + 0.208983582205757*pt3 + 0.024349751127576*pt4)
- v2 = abs(pt4-pt1+pt3-pt2)*0.26666666666666666
- v3 = abs(-0.024349751127576*pt1 - 0.208983582205757*pt2 - 0.325650248872424*pt3 + 0.558983582205757*pt4)
- v4 = abs(pt4-pt3)*.15
+ v0 = abs(pt2 - pt1) * 0.15
+ v1 = abs(
+ -0.558983582205757 * pt1
+ + 0.325650248872424 * pt2
+ + 0.208983582205757 * pt3
+ + 0.024349751127576 * pt4
+ )
+ v2 = abs(pt4 - pt1 + pt3 - pt2) * 0.26666666666666666
+ v3 = abs(
+ -0.024349751127576 * pt1
+ - 0.208983582205757 * pt2
+ - 0.325650248872424 * pt3
+ + 0.558983582205757 * pt4
+ )
+ v4 = abs(pt4 - pt3) * 0.15
return v0 + v1 + v2 + v3 + v4
@@ -313,7 +352,13 @@ def calcCubicBounds(pt1, pt2, pt3, pt4):
yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
roots = xRoots + yRoots
- points = [(ax*t*t*t + bx*t*t + cx * t + dx, ay*t*t*t + by*t*t + cy * t + dy) for t in roots] + [pt1, pt4]
+ points = [
+ (
+ ax * t * t * t + bx * t * t + cx * t + dx,
+ ay * t * t * t + by * t * t + cy * t + dy,
+ )
+ for t in roots
+ ] + [pt1, pt4]
return calcBounds(points)
@@ -356,8 +401,8 @@ def splitLine(pt1, pt2, where, isHorizontal):
pt1x, pt1y = pt1
pt2x, pt2y = pt2
- ax = (pt2x - pt1x)
- ay = (pt2y - pt1y)
+ ax = pt2x - pt1x
+ ay = pt2y - pt1y
bx = pt1x
by = pt1y
@@ -410,9 +455,10 @@ def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
((50, 50), (75, 50), (100, 0))
"""
a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- solutions = solveQuadratic(a[isHorizontal], b[isHorizontal],
- c[isHorizontal] - where)
- solutions = sorted([t for t in solutions if 0 <= t < 1])
+ solutions = solveQuadratic(
+ a[isHorizontal], b[isHorizontal], c[isHorizontal] - where
+ )
+ solutions = sorted(t for t in solutions if 0 <= t < 1)
if not solutions:
return [(pt1, pt2, pt3)]
return _splitQuadraticAtT(a, b, c, *solutions)
@@ -446,9 +492,10 @@ def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15))
"""
a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
- d[isHorizontal] - where)
- solutions = sorted([t for t in solutions if 0 <= t < 1])
+ solutions = solveCubic(
+ a[isHorizontal], b[isHorizontal], c[isHorizontal], d[isHorizontal] - where
+ )
+ solutions = sorted(t for t in solutions if 0 <= t < 1)
if not solutions:
return [(pt1, pt2, pt3, pt4)]
return _splitCubicAtT(a, b, c, d, *solutions)
@@ -512,17 +559,17 @@ def _splitQuadraticAtT(a, b, c, *ts):
cx, cy = c
for i in range(len(ts) - 1):
t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
+ t2 = ts[i + 1]
+ delta = t2 - t1
# calc new a, b and c
- delta_2 = delta*delta
+ delta_2 = delta * delta
a1x = ax * delta_2
a1y = ay * delta_2
- b1x = (2*ax*t1 + bx) * delta
- b1y = (2*ay*t1 + by) * delta
- t1_2 = t1*t1
- c1x = ax*t1_2 + bx*t1 + cx
- c1y = ay*t1_2 + by*t1 + cy
+ b1x = (2 * ax * t1 + bx) * delta
+ b1y = (2 * ay * t1 + by) * delta
+ t1_2 = t1 * t1
+ c1x = ax * t1_2 + bx * t1 + cx
+ c1y = ay * t1_2 + by * t1 + cy
pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y))
segments.append((pt1, pt2, pt3))
@@ -540,24 +587,26 @@ def _splitCubicAtT(a, b, c, d, *ts):
dx, dy = d
for i in range(len(ts) - 1):
t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
+ t2 = ts[i + 1]
+ delta = t2 - t1
- delta_2 = delta*delta
- delta_3 = delta*delta_2
- t1_2 = t1*t1
- t1_3 = t1*t1_2
+ delta_2 = delta * delta
+ delta_3 = delta * delta_2
+ t1_2 = t1 * t1
+ t1_3 = t1 * t1_2
# calc new a, b, c and d
a1x = ax * delta_3
a1y = ay * delta_3
- b1x = (3*ax*t1 + bx) * delta_2
- b1y = (3*ay*t1 + by) * delta_2
- c1x = (2*bx*t1 + cx + 3*ax*t1_2) * delta
- c1y = (2*by*t1 + cy + 3*ay*t1_2) * delta
- d1x = ax*t1_3 + bx*t1_2 + cx*t1 + dx
- d1y = ay*t1_3 + by*t1_2 + cy*t1 + dy
- pt1, pt2, pt3, pt4 = calcCubicPoints((a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y))
+ b1x = (3 * ax * t1 + bx) * delta_2
+ b1y = (3 * ay * t1 + by) * delta_2
+ c1x = (2 * bx * t1 + cx + 3 * ax * t1_2) * delta
+ c1y = (2 * by * t1 + cy + 3 * ay * t1_2) * delta
+ d1x = ax * t1_3 + bx * t1_2 + cx * t1 + dx
+ d1y = ay * t1_3 + by * t1_2 + cy * t1 + dy
+ pt1, pt2, pt3, pt4 = calcCubicPoints(
+ (a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y)
+ )
segments.append((pt1, pt2, pt3, pt4))
return segments
@@ -569,8 +618,7 @@ def _splitCubicAtT(a, b, c, d, *ts):
from math import sqrt, acos, cos, pi
-def solveQuadratic(a, b, c,
- sqrt=sqrt):
+def solveQuadratic(a, b, c, sqrt=sqrt):
"""Solve a quadratic equation.
Solves *a*x*x + b*x + c = 0* where a, b and c are real.
@@ -590,13 +638,13 @@ def solveQuadratic(a, b, c,
roots = []
else:
# We have a linear equation with 1 root.
- roots = [-c/b]
+ roots = [-c / b]
else:
# We have a true quadratic equation. Apply the quadratic formula to find two roots.
- DD = b*b - 4.0*a*c
+ DD = b * b - 4.0 * a * c
if DD >= 0.0:
rDD = sqrt(DD)
- roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a]
+ roots = [(-b + rDD) / 2.0 / a, (-b - rDD) / 2.0 / a]
else:
# complex roots, ignore
roots = []
@@ -646,52 +694,52 @@ def solveCubic(a, b, c, d):
# returns unreliable results, so we fall back to quad.
return solveQuadratic(b, c, d)
a = float(a)
- a1 = b/a
- a2 = c/a
- a3 = d/a
+ a1 = b / a
+ a2 = c / a
+ a3 = d / a
- Q = (a1*a1 - 3.0*a2)/9.0
- R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0
+ Q = (a1 * a1 - 3.0 * a2) / 9.0
+ R = (2.0 * a1 * a1 * a1 - 9.0 * a1 * a2 + 27.0 * a3) / 54.0
- R2 = R*R
- Q3 = Q*Q*Q
+ R2 = R * R
+ Q3 = Q * Q * Q
R2 = 0 if R2 < epsilon else R2
Q3 = 0 if abs(Q3) < epsilon else Q3
R2_Q3 = R2 - Q3
- if R2 == 0. and Q3 == 0.:
- x = round(-a1/3.0, epsilonDigits)
+ if R2 == 0.0 and Q3 == 0.0:
+ x = round(-a1 / 3.0, epsilonDigits)
return [x, x, x]
- elif R2_Q3 <= epsilon * .5:
+ elif R2_Q3 <= epsilon * 0.5:
# The epsilon * .5 above ensures that Q3 is not zero.
- theta = acos(max(min(R/sqrt(Q3), 1.0), -1.0))
- rQ2 = -2.0*sqrt(Q)
- a1_3 = a1/3.0
- x0 = rQ2*cos(theta/3.0) - a1_3
- x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1_3
- x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1_3
+ theta = acos(max(min(R / sqrt(Q3), 1.0), -1.0))
+ rQ2 = -2.0 * sqrt(Q)
+ a1_3 = a1 / 3.0
+ x0 = rQ2 * cos(theta / 3.0) - a1_3
+ x1 = rQ2 * cos((theta + 2.0 * pi) / 3.0) - a1_3
+ x2 = rQ2 * cos((theta + 4.0 * pi) / 3.0) - a1_3
x0, x1, x2 = sorted([x0, x1, x2])
# Merge roots that are close-enough
if x1 - x0 < epsilon and x2 - x1 < epsilon:
- x0 = x1 = x2 = round((x0 + x1 + x2) / 3., epsilonDigits)
+ x0 = x1 = x2 = round((x0 + x1 + x2) / 3.0, epsilonDigits)
elif x1 - x0 < epsilon:
- x0 = x1 = round((x0 + x1) / 2., epsilonDigits)
+ x0 = x1 = round((x0 + x1) / 2.0, epsilonDigits)
x2 = round(x2, epsilonDigits)
elif x2 - x1 < epsilon:
x0 = round(x0, epsilonDigits)
- x1 = x2 = round((x1 + x2) / 2., epsilonDigits)
+ x1 = x2 = round((x1 + x2) / 2.0, epsilonDigits)
else:
x0 = round(x0, epsilonDigits)
x1 = round(x1, epsilonDigits)
x2 = round(x2, epsilonDigits)
return [x0, x1, x2]
else:
- x = pow(sqrt(R2_Q3)+abs(R), 1/3.0)
- x = x + Q/x
+ x = pow(sqrt(R2_Q3) + abs(R), 1 / 3.0)
+ x = x + Q / x
if R >= 0.0:
x = -x
- x = round(x - a1/3.0, epsilonDigits)
+ x = round(x - a1 / 3.0, epsilonDigits)
return [x]
@@ -699,6 +747,7 @@ def solveCubic(a, b, c, d):
# Conversion routines for points to parameters and vice versa
#
+
def calcQuadraticParameters(pt1, pt2, pt3):
x2, y2 = pt2
x3, y3 = pt3
@@ -753,17 +802,406 @@ def calcCubicPoints(a, b, c, d):
return (x1, y1), (x2, y2), (x3, y3), (x4, y4)
+#
+# Point at time
+#
+
+
+def linePointAtT(pt1, pt2, t):
+ """Finds the point at time `t` on a line.
+
+ Args:
+ pt1, pt2: Coordinates of the line as 2D tuples.
+ t: The time along the line.
+
+ Returns:
+ A 2D tuple with the coordinates of the point.
+ """
+ return ((pt1[0] * (1 - t) + pt2[0] * t), (pt1[1] * (1 - t) + pt2[1] * t))
+
+
+def quadraticPointAtT(pt1, pt2, pt3, t):
+ """Finds the point at time `t` on a quadratic curve.
+
+ Args:
+ pt1, pt2, pt3: Coordinates of the curve as 2D tuples.
+ t: The time along the curve.
+
+ Returns:
+ A 2D tuple with the coordinates of the point.
+ """
+ x = (1 - t) * (1 - t) * pt1[0] + 2 * (1 - t) * t * pt2[0] + t * t * pt3[0]
+ y = (1 - t) * (1 - t) * pt1[1] + 2 * (1 - t) * t * pt2[1] + t * t * pt3[1]
+ return (x, y)
+
+
+def cubicPointAtT(pt1, pt2, pt3, pt4, t):
+ """Finds the point at time `t` on a cubic curve.
+
+ Args:
+ pt1, pt2, pt3, pt4: Coordinates of the curve as 2D tuples.
+ t: The time along the curve.
+
+ Returns:
+ A 2D tuple with the coordinates of the point.
+ """
+ x = (
+ (1 - t) * (1 - t) * (1 - t) * pt1[0]
+ + 3 * (1 - t) * (1 - t) * t * pt2[0]
+ + 3 * (1 - t) * t * t * pt3[0]
+ + t * t * t * pt4[0]
+ )
+ y = (
+ (1 - t) * (1 - t) * (1 - t) * pt1[1]
+ + 3 * (1 - t) * (1 - t) * t * pt2[1]
+ + 3 * (1 - t) * t * t * pt3[1]
+ + t * t * t * pt4[1]
+ )
+ return (x, y)
+
+
+def segmentPointAtT(seg, t):
+ if len(seg) == 2:
+ return linePointAtT(*seg, t)
+ elif len(seg) == 3:
+ return quadraticPointAtT(*seg, t)
+ elif len(seg) == 4:
+ return cubicPointAtT(*seg, t)
+ raise ValueError("Unknown curve degree")
+
+
+#
+# Intersection finders
+#
+
+
+def _line_t_of_pt(s, e, pt):
+ sx, sy = s
+ ex, ey = e
+ px, py = pt
+ if not math.isclose(sx, ex):
+ return (px - sx) / (ex - sx)
+ if not math.isclose(sy, ey):
+ return (py - sy) / (ey - sy)
+ # Line is a point!
+ return -1
+
+
+def _both_points_are_on_same_side_of_origin(a, b, origin):
+ xDiff = (a[0] - origin[0]) * (b[0] - origin[0])
+ yDiff = (a[1] - origin[1]) * (b[1] - origin[1])
+ return not (xDiff <= 0.0 and yDiff <= 0.0)
+
+
+def lineLineIntersections(s1, e1, s2, e2):
+ """Finds intersections between two line segments.
+
+ Args:
+ s1, e1: Coordinates of the first line as 2D tuples.
+ s2, e2: Coordinates of the second line as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+
+ >>> a = lineLineIntersections( (310,389), (453, 222), (289, 251), (447, 367))
+ >>> len(a)
+ 1
+ >>> intersection = a[0]
+ >>> intersection.pt
+ (374.44882952482897, 313.73458370177315)
+ >>> (intersection.t1, intersection.t2)
+ (0.45069111555824454, 0.5408153767394238)
+ """
+ s1x, s1y = s1
+ e1x, e1y = e1
+ s2x, s2y = s2
+ e2x, e2y = e2
+ if (
+ math.isclose(s2x, e2x) and math.isclose(s1x, e1x) and not math.isclose(s1x, s2x)
+ ): # Parallel vertical
+ return []
+ if (
+ math.isclose(s2y, e2y) and math.isclose(s1y, e1y) and not math.isclose(s1y, s2y)
+ ): # Parallel horizontal
+ return []
+ if math.isclose(s2x, e2x) and math.isclose(s2y, e2y): # Line segment is tiny
+ return []
+ if math.isclose(s1x, e1x) and math.isclose(s1y, e1y): # Line segment is tiny
+ return []
+ if math.isclose(e1x, s1x):
+ x = s1x
+ slope34 = (e2y - s2y) / (e2x - s2x)
+ y = slope34 * (x - s2x) + s2y
+ pt = (x, y)
+ return [
+ Intersection(
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
+ )
+ ]
+ if math.isclose(s2x, e2x):
+ x = s2x
+ slope12 = (e1y - s1y) / (e1x - s1x)
+ y = slope12 * (x - s1x) + s1y
+ pt = (x, y)
+ return [
+ Intersection(
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
+ )
+ ]
+
+ slope12 = (e1y - s1y) / (e1x - s1x)
+ slope34 = (e2y - s2y) / (e2x - s2x)
+ if math.isclose(slope12, slope34):
+ return []
+ x = (slope12 * s1x - s1y - slope34 * s2x + s2y) / (slope12 - slope34)
+ y = slope12 * (x - s1x) + s1y
+ pt = (x, y)
+ if _both_points_are_on_same_side_of_origin(
+ pt, e1, s1
+ ) and _both_points_are_on_same_side_of_origin(pt, s2, e2):
+ return [
+ Intersection(
+ pt=pt, t1=_line_t_of_pt(s1, e1, pt), t2=_line_t_of_pt(s2, e2, pt)
+ )
+ ]
+ return []
+
+
+def _alignment_transformation(segment):
+ # Returns a transformation which aligns a segment horizontally at the
+ # origin. Apply this transformation to curves and root-find to find
+ # intersections with the segment.
+ start = segment[0]
+ end = segment[-1]
+ angle = math.atan2(end[1] - start[1], end[0] - start[0])
+ return Identity.rotate(-angle).translate(-start[0], -start[1])
+
+
+def _curve_line_intersections_t(curve, line):
+ aligned_curve = _alignment_transformation(line).transformPoints(curve)
+ if len(curve) == 3:
+ a, b, c = calcQuadraticParameters(*aligned_curve)
+ intersections = solveQuadratic(a[1], b[1], c[1])
+ elif len(curve) == 4:
+ a, b, c, d = calcCubicParameters(*aligned_curve)
+ intersections = solveCubic(a[1], b[1], c[1], d[1])
+ else:
+ raise ValueError("Unknown curve degree")
+ return sorted(i for i in intersections if 0.0 <= i <= 1)
+
+
+def curveLineIntersections(curve, line):
+ """Finds intersections between a curve and a line.
+
+ Args:
+ curve: List of coordinates of the curve segment as 2D tuples.
+ line: List of coordinates of the line segment as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+ >>> curve = [ (100, 240), (30, 60), (210, 230), (160, 30) ]
+ >>> line = [ (25, 260), (230, 20) ]
+ >>> intersections = curveLineIntersections(curve, line)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (84.90010344084885, 189.87306176459828)
+ """
+ if len(curve) == 3:
+ pointFinder = quadraticPointAtT
+ elif len(curve) == 4:
+ pointFinder = cubicPointAtT
+ else:
+ raise ValueError("Unknown curve degree")
+ intersections = []
+ for t in _curve_line_intersections_t(curve, line):
+ pt = pointFinder(*curve, t)
+ intersections.append(Intersection(pt=pt, t1=t, t2=_line_t_of_pt(*line, pt)))
+ return intersections
+
+
+def _curve_bounds(c):
+ if len(c) == 3:
+ return calcQuadraticBounds(*c)
+ elif len(c) == 4:
+ return calcCubicBounds(*c)
+ raise ValueError("Unknown curve degree")
+
+
+def _split_segment_at_t(c, t):
+ if len(c) == 2:
+ s, e = c
+ midpoint = linePointAtT(s, e, t)
+ return [(s, midpoint), (midpoint, e)]
+ if len(c) == 3:
+ return splitQuadraticAtT(*c, t)
+ elif len(c) == 4:
+ return splitCubicAtT(*c, t)
+ raise ValueError("Unknown curve degree")
+
+
+def _curve_curve_intersections_t(
+ curve1, curve2, precision=1e-3, range1=None, range2=None
+):
+ bounds1 = _curve_bounds(curve1)
+ bounds2 = _curve_bounds(curve2)
+
+ if not range1:
+ range1 = (0.0, 1.0)
+ if not range2:
+ range2 = (0.0, 1.0)
+
+ # If bounds don't intersect, go home
+ intersects, _ = sectRect(bounds1, bounds2)
+ if not intersects:
+ return []
+
+ def midpoint(r):
+ return 0.5 * (r[0] + r[1])
+
+ # If they do overlap but they're tiny, approximate
+ if rectArea(bounds1) < precision and rectArea(bounds2) < precision:
+ return [(midpoint(range1), midpoint(range2))]
+
+ c11, c12 = _split_segment_at_t(curve1, 0.5)
+ c11_range = (range1[0], midpoint(range1))
+ c12_range = (midpoint(range1), range1[1])
+
+ c21, c22 = _split_segment_at_t(curve2, 0.5)
+ c21_range = (range2[0], midpoint(range2))
+ c22_range = (midpoint(range2), range2[1])
+
+ found = []
+ found.extend(
+ _curve_curve_intersections_t(
+ c11, c21, precision, range1=c11_range, range2=c21_range
+ )
+ )
+ found.extend(
+ _curve_curve_intersections_t(
+ c12, c21, precision, range1=c12_range, range2=c21_range
+ )
+ )
+ found.extend(
+ _curve_curve_intersections_t(
+ c11, c22, precision, range1=c11_range, range2=c22_range
+ )
+ )
+ found.extend(
+ _curve_curve_intersections_t(
+ c12, c22, precision, range1=c12_range, range2=c22_range
+ )
+ )
+
+ unique_key = lambda ts: (int(ts[0] / precision), int(ts[1] / precision))
+ seen = set()
+ unique_values = []
+
+ for ts in found:
+ key = unique_key(ts)
+ if key in seen:
+ continue
+ seen.add(key)
+ unique_values.append(ts)
+
+ return unique_values
+
+
+def curveCurveIntersections(curve1, curve2):
+ """Finds intersections between a curve and a curve.
+
+ Args:
+ curve1: List of coordinates of the first curve segment as 2D tuples.
+ curve2: List of coordinates of the second curve segment as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+ >>> curve1 = [ (10,100), (90,30), (40,140), (220,220) ]
+ >>> curve2 = [ (5,150), (180,20), (80,250), (210,190) ]
+ >>> intersections = curveCurveIntersections(curve1, curve2)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (81.7831487395506, 109.88904552375288)
+ """
+ intersection_ts = _curve_curve_intersections_t(curve1, curve2)
+ return [
+ Intersection(pt=segmentPointAtT(curve1, ts[0]), t1=ts[0], t2=ts[1])
+ for ts in intersection_ts
+ ]
+
+
+def segmentSegmentIntersections(seg1, seg2):
+ """Finds intersections between two segments.
+
+ Args:
+ seg1: List of coordinates of the first segment as 2D tuples.
+ seg2: List of coordinates of the second segment as 2D tuples.
+
+ Returns:
+ A list of ``Intersection`` objects, each object having ``pt``, ``t1``
+ and ``t2`` attributes containing the intersection point, time on first
+ segment and time on second segment respectively.
+
+ Examples::
+ >>> curve1 = [ (10,100), (90,30), (40,140), (220,220) ]
+ >>> curve2 = [ (5,150), (180,20), (80,250), (210,190) ]
+ >>> intersections = segmentSegmentIntersections(curve1, curve2)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (81.7831487395506, 109.88904552375288)
+ >>> curve3 = [ (100, 240), (30, 60), (210, 230), (160, 30) ]
+ >>> line = [ (25, 260), (230, 20) ]
+ >>> intersections = segmentSegmentIntersections(curve3, line)
+ >>> len(intersections)
+ 3
+ >>> intersections[0].pt
+ (84.90010344084885, 189.87306176459828)
+
+ """
+ # Arrange by degree
+ swapped = False
+ if len(seg2) > len(seg1):
+ seg2, seg1 = seg1, seg2
+ swapped = True
+ if len(seg1) > 2:
+ if len(seg2) > 2:
+ intersections = curveCurveIntersections(seg1, seg2)
+ else:
+ intersections = curveLineIntersections(seg1, seg2)
+ elif len(seg1) == 2 and len(seg2) == 2:
+ intersections = lineLineIntersections(*seg1, *seg2)
+ else:
+ raise ValueError("Couldn't work out which intersection function to use")
+ if not swapped:
+ return intersections
+ return [Intersection(pt=i.pt, t1=i.t2, t2=i.t1) for i in intersections]
+
+
def _segmentrepr(obj):
"""
- >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
- '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
+ >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
+ '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
"""
try:
it = iter(obj)
except TypeError:
return "%g" % obj
else:
- return "(%s)" % ", ".join([_segmentrepr(x) for x in it])
+ return "(%s)" % ", ".join(_segmentrepr(x) for x in it)
def printSegments(segments):
@@ -773,7 +1211,9 @@ def printSegments(segments):
for segment in segments:
print(_segmentrepr(segment))
+
if __name__ == "__main__":
import sys
import doctest
+
sys.exit(doctest.testmod().failed)
diff --git a/Lib/fontTools/misc/classifyTools.py b/Lib/fontTools/misc/classifyTools.py
index 73101186..ae88a8f7 100644
--- a/Lib/fontTools/misc/classifyTools.py
+++ b/Lib/fontTools/misc/classifyTools.py
@@ -1,7 +1,6 @@
""" fontTools.misc.classifyTools.py -- tools for classifying things.
"""
-from fontTools.misc.py23 import *
class Classifier(object):
diff --git a/Lib/fontTools/misc/cliTools.py b/Lib/fontTools/misc/cliTools.py
index 4e5353b9..e8c17677 100644
--- a/Lib/fontTools/misc/cliTools.py
+++ b/Lib/fontTools/misc/cliTools.py
@@ -1,5 +1,4 @@
"""Collection of utilities for command-line interfaces and console scripts."""
-from fontTools.misc.py23 import *
import os
import re
diff --git a/Lib/fontTools/misc/dictTools.py b/Lib/fontTools/misc/dictTools.py
index 47528684..ae7932c9 100644
--- a/Lib/fontTools/misc/dictTools.py
+++ b/Lib/fontTools/misc/dictTools.py
@@ -1,6 +1,5 @@
"""Misc dict tools."""
-from fontTools.misc.py23 import *
__all__ = ['hashdict']
diff --git a/Lib/fontTools/misc/eexec.py b/Lib/fontTools/misc/eexec.py
index 36719a1c..71f733c1 100644
--- a/Lib/fontTools/misc/eexec.py
+++ b/Lib/fontTools/misc/eexec.py
@@ -12,7 +12,8 @@ the new key at the end of the operation.
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, bytesjoin, byteord
+
def _decryptChar(cipher, R):
cipher = byteord(cipher)
diff --git a/Lib/fontTools/misc/encodingTools.py b/Lib/fontTools/misc/encodingTools.py
index 438e484d..eccf951d 100644
--- a/Lib/fontTools/misc/encodingTools.py
+++ b/Lib/fontTools/misc/encodingTools.py
@@ -1,7 +1,6 @@
"""fontTools.misc.encodingTools.py -- tools for working with OpenType encodings.
"""
-from fontTools.misc.py23 import *
import fontTools.encodings.codecs
# Map keyed by platformID, then platEncID, then possibly langID
diff --git a/Lib/fontTools/misc/etree.py b/Lib/fontTools/misc/etree.py
index 2338f099..6e943e4b 100644
--- a/Lib/fontTools/misc/etree.py
+++ b/Lib/fontTools/misc/etree.py
@@ -11,7 +11,7 @@ or subclasses built-in ElementTree classes to add features that are
only availble in lxml, like OrderedDict for attributes, pretty_print and
iterwalk.
"""
-from fontTools.misc.py23 import basestring, unicode, tounicode, open
+from fontTools.misc.py23 import unicode, tostr
XML_DECLARATION = """<?xml version='1.0' encoding='%s'?>"""
@@ -242,7 +242,7 @@ except ImportError:
Reject all bytes input that contains non-ASCII characters.
"""
try:
- s = tounicode(s, encoding="ascii", errors="strict")
+ s = tostr(s, encoding="ascii", errors="strict")
except UnicodeDecodeError:
raise ValueError(
"Bytes strings can only contain ASCII characters. "
@@ -356,7 +356,7 @@ except ImportError:
if isinstance(tag, QName):
if tag.text not in qnames:
add_qname(tag.text)
- elif isinstance(tag, basestring):
+ elif isinstance(tag, str):
if tag not in qnames:
add_qname(tag)
elif tag is not None and tag is not Comment and tag is not PI:
diff --git a/Lib/fontTools/misc/filenames.py b/Lib/fontTools/misc/filenames.py
index f7eb247a..0f010008 100644
--- a/Lib/fontTools/misc/filenames.py
+++ b/Lib/fontTools/misc/filenames.py
@@ -16,7 +16,7 @@ by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
- Tal Leming
- Just van Rossum
"""
-from fontTools.misc.py23 import basestring, unicode
+
illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ")
illegalCharacters += [chr(i) for i in range(1, 32)]
@@ -95,9 +95,9 @@ def userNameToFileName(userName, existing=[], prefix="", suffix=""):
>>> userNameToFileName("alt.con") == "alt._con"
True
"""
- # the incoming name must be a unicode string
- if not isinstance(userName, unicode):
- raise ValueError("The value for userName must be a unicode string.")
+ # the incoming name must be a str
+ if not isinstance(userName, str):
+ raise ValueError("The value for userName must be a string.")
# establish the prefix and suffix lengths
prefixLength = len(prefix)
suffixLength = len(suffix)
diff --git a/Lib/fontTools/misc/fixedTools.py b/Lib/fontTools/misc/fixedTools.py
index 931b665e..f0474abf 100644
--- a/Lib/fontTools/misc/fixedTools.py
+++ b/Lib/fontTools/misc/fixedTools.py
@@ -17,15 +17,13 @@ functions for converting between fixed-point, float and string representations.
The maximum value that can still fit in an F2Dot14. (1.99993896484375)
"""
-from fontTools.misc.py23 import *
-import math
+from .roundTools import otRound
import logging
log = logging.getLogger(__name__)
__all__ = [
"MAX_F2DOT14",
- "otRound",
"fixedToFloat",
"floatToFixed",
"floatToFixedToFloat",
@@ -41,30 +39,6 @@ __all__ = [
MAX_F2DOT14 = 0x7FFF / (1 << 14)
-def otRound(value):
- """Round float value to nearest integer towards ``+Infinity``.
-
- The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
- defines the required method for converting floating point values to
- fixed-point. In particular it specifies the following rounding strategy:
-
- for fractional values of 0.5 and higher, take the next higher integer;
- for other fractional values, truncate.
-
- This function rounds the floating-point value according to this strategy
- in preparation for conversion to fixed-point.
-
- Args:
- value (float): The input floating-point value.
-
- Returns
- float: The rounded value.
- """
- # See this thread for how we ended up with this implementation:
- # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
- return int(math.floor(value + 0.5))
-
-
def fixedToFloat(value, precisionBits):
"""Converts a fixed-point number to a float given the number of
precision bits.
diff --git a/Lib/fontTools/misc/intTools.py b/Lib/fontTools/misc/intTools.py
index 9f4497ba..448e1627 100644
--- a/Lib/fontTools/misc/intTools.py
+++ b/Lib/fontTools/misc/intTools.py
@@ -1,5 +1,3 @@
-from fontTools.misc.py23 import *
-
__all__ = ['popCount']
diff --git a/Lib/fontTools/misc/loggingTools.py b/Lib/fontTools/misc/loggingTools.py
index 3281d429..d1baa839 100644
--- a/Lib/fontTools/misc/loggingTools.py
+++ b/Lib/fontTools/misc/loggingTools.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
import sys
import logging
import timeit
@@ -60,7 +59,7 @@ class LevelFormatter(logging.Formatter):
"only '%' percent style is supported in both python 2 and 3")
if fmt is None:
fmt = DEFAULT_FORMATS
- if isinstance(fmt, basestring):
+ if isinstance(fmt, str):
default_format = fmt
custom_formats = {}
elif isinstance(fmt, Mapping):
@@ -151,7 +150,7 @@ def configLogger(**kwargs):
handlers = [h]
# By default, the top-level library logger is configured.
logger = kwargs.pop("logger", "fontTools")
- if not logger or isinstance(logger, basestring):
+ if not logger or isinstance(logger, str):
# empty "" or None means the 'root' logger
logger = logging.getLogger(logger)
# before (re)configuring, reset named logger and its children (if exist)
@@ -436,7 +435,7 @@ class CapturingLogHandler(logging.Handler):
def __init__(self, logger, level):
super(CapturingLogHandler, self).__init__(level=level)
self.records = []
- if isinstance(logger, basestring):
+ if isinstance(logger, str):
self.logger = logging.getLogger(logger)
else:
self.logger = logger
diff --git a/Lib/fontTools/misc/macCreatorType.py b/Lib/fontTools/misc/macCreatorType.py
index c28ceb98..fb237200 100644
--- a/Lib/fontTools/misc/macCreatorType.py
+++ b/Lib/fontTools/misc/macCreatorType.py
@@ -1,5 +1,4 @@
-from fontTools.misc.py23 import *
-import sys
+from fontTools.misc.py23 import Tag, bytesjoin, strjoin
try:
import xattr
except ImportError:
diff --git a/Lib/fontTools/misc/macRes.py b/Lib/fontTools/misc/macRes.py
index 0053ee3a..2c15b347 100644
--- a/Lib/fontTools/misc/macRes.py
+++ b/Lib/fontTools/misc/macRes.py
@@ -1,4 +1,5 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tostr
+from io import BytesIO
import struct
from fontTools.misc import sstruct
from collections import OrderedDict
diff --git a/Lib/fontTools/misc/plistlib/__init__.py b/Lib/fontTools/misc/plistlib/__init__.py
index d8391041..84dc4183 100644
--- a/Lib/fontTools/misc/plistlib/__init__.py
+++ b/Lib/fontTools/misc/plistlib/__init__.py
@@ -1,5 +1,4 @@
import collections.abc
-import sys
import re
from typing import (
Any,
@@ -24,10 +23,8 @@ from functools import singledispatch
from fontTools.misc import etree
-from fontTools.misc.py23 import (
- tounicode,
- tobytes,
-)
+from fontTools.misc.py23 import tostr
+
# By default, we
# - deserialize <data> elements as bytes and
@@ -368,7 +365,7 @@ def _dict_element(d: Mapping[str, PlistEncodable], ctx: SimpleNamespace) -> etre
continue
raise TypeError("keys must be strings")
k = etree.SubElement(el, "key")
- k.text = tounicode(key, "utf-8")
+ k.text = tostr(key, "utf-8")
el.append(_make_element(value, ctx))
ctx.indent_level -= 1
return el
diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py
index 5f1427d0..cb675050 100644
--- a/Lib/fontTools/misc/psCharStrings.py
+++ b/Lib/fontTools/misc/psCharStrings.py
@@ -2,7 +2,7 @@
CFF dictionary data and Type1/Type2 CharStrings.
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin
from fontTools.misc.fixedTools import (
fixedToFloat, floatToFixed, floatToFixedToStr, strToFixedToFloat,
)
@@ -997,7 +997,7 @@ class T2CharString(object):
# If present, remove return and endchar operators.
if program and program[-1] in ("return", "endchar"):
program = program[:-1]
- elif program and not isinstance(program[-1], basestring):
+ elif program and not isinstance(program[-1], str):
raise CharStringCompileError(
"T2CharString or Subr has items on the stack after last operator."
)
@@ -1010,7 +1010,7 @@ class T2CharString(object):
while i < end:
token = program[i]
i = i + 1
- if isinstance(token, basestring):
+ if isinstance(token, str):
try:
bytecode.extend(bytechr(b) for b in opcodes[token])
except KeyError:
@@ -1043,8 +1043,7 @@ class T2CharString(object):
self.program = None
def getToken(self, index,
- len=len, byteord=byteord, basestring=basestring,
- isinstance=isinstance):
+ len=len, byteord=byteord, isinstance=isinstance):
if self.bytecode is not None:
if index >= len(self.bytecode):
return None, 0, 0
@@ -1057,7 +1056,7 @@ class T2CharString(object):
return None, 0, 0
token = self.program[index]
index = index + 1
- isOperator = isinstance(token, basestring)
+ isOperator = isinstance(token, str)
return token, isOperator, index
def getBytes(self, index, nBytes):
diff --git a/Lib/fontTools/misc/psLib.py b/Lib/fontTools/misc/psLib.py
index e4748302..916755ce 100644
--- a/Lib/fontTools/misc/psLib.py
+++ b/Lib/fontTools/misc/psLib.py
@@ -1,6 +1,21 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tobytes, tostr
from fontTools.misc import eexec
-from .psOperators import *
+from .psOperators import (
+ PSOperators,
+ ps_StandardEncoding,
+ ps_array,
+ ps_boolean,
+ ps_dict,
+ ps_integer,
+ ps_literal,
+ ps_mark,
+ ps_name,
+ ps_operator,
+ ps_procedure,
+ ps_procmark,
+ ps_real,
+ ps_string,
+)
import re
from collections.abc import Callable
from string import whitespace
diff --git a/Lib/fontTools/misc/psOperators.py b/Lib/fontTools/misc/psOperators.py
index de278fd6..3b378f59 100644
--- a/Lib/fontTools/misc/psOperators.py
+++ b/Lib/fontTools/misc/psOperators.py
@@ -1,5 +1,3 @@
-from fontTools.misc.py23 import *
-
_accessstrings = {0: "", 1: "readonly", 2: "executeonly", 3: "noaccess"}
diff --git a/Lib/fontTools/misc/py23.py b/Lib/fontTools/misc/py23.py
index bced8007..9096e2ef 100644
--- a/Lib/fontTools/misc/py23.py
+++ b/Lib/fontTools/misc/py23.py
@@ -9,7 +9,7 @@ from io import StringIO as UnicodeIO
from types import SimpleNamespace
warnings.warn(
- "The py23 module has been deprecated and will be removed in the next release. "
+ "The py23 module has been deprecated and will be removed in a future release. "
"Please update your code.",
DeprecationWarning,
)
diff --git a/Lib/fontTools/misc/roundTools.py b/Lib/fontTools/misc/roundTools.py
new file mode 100644
index 00000000..c1d546f1
--- /dev/null
+++ b/Lib/fontTools/misc/roundTools.py
@@ -0,0 +1,58 @@
+"""
+Various round-to-integer helpers.
+"""
+
+import math
+import functools
+import logging
+
+log = logging.getLogger(__name__)
+
+__all__ = [
+ "noRound",
+ "otRound",
+ "maybeRound",
+ "roundFunc",
+]
+
+def noRound(value):
+ return value
+
+def otRound(value):
+ """Round float value to nearest integer towards ``+Infinity``.
+
+ The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_)
+ defines the required method for converting floating point values to
+ fixed-point. In particular it specifies the following rounding strategy:
+
+ for fractional values of 0.5 and higher, take the next higher integer;
+ for other fractional values, truncate.
+
+ This function rounds the floating-point value according to this strategy
+ in preparation for conversion to fixed-point.
+
+ Args:
+ value (float): The input floating-point value.
+
+ Returns
+ float: The rounded value.
+ """
+ # See this thread for how we ended up with this implementation:
+ # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
+ return int(math.floor(value + 0.5))
+
+def maybeRound(v, tolerance, round=otRound):
+ rounded = round(v)
+ return rounded if abs(rounded - v) <= tolerance else v
+
+def roundFunc(tolerance, round=otRound):
+ if tolerance < 0:
+ raise ValueError("Rounding tolerance must be positive")
+
+ if tolerance == 0:
+ return noRound
+
+ if tolerance >= .5:
+ return round
+
+ return functools.partial(maybeRound, tolerance=tolerance, round=round)
diff --git a/Lib/fontTools/misc/sstruct.py b/Lib/fontTools/misc/sstruct.py
index 8b69a429..ba1f8788 100644
--- a/Lib/fontTools/misc/sstruct.py
+++ b/Lib/fontTools/misc/sstruct.py
@@ -46,7 +46,7 @@ calcsize(fmt)
it returns the size of the data in bytes.
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes, tostr
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
import struct
import re
@@ -68,7 +68,7 @@ def pack(fmt, obj):
if name in fixes:
# fixed point conversion
value = fl2fi(value, fixes[name])
- elif isinstance(value, basestring):
+ elif isinstance(value, str):
value = tobytes(value)
elements.append(value)
data = struct.pack(*(formatstring,) + tuple(elements))
diff --git a/Lib/fontTools/misc/symfont.py b/Lib/fontTools/misc/symfont.py
index d3e5ce23..a1a87300 100644
--- a/Lib/fontTools/misc/symfont.py
+++ b/Lib/fontTools/misc/symfont.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from functools import partial
from itertools import count
@@ -112,8 +111,7 @@ MomentXYPen = partial(GreenPen, func=x*y)
def printGreenPen(penName, funcs, file=sys.stdout):
print(
-'''from fontTools.misc.py23 import *
-from fontTools.pens.basePen import BasePen
+'''from fontTools.pens.basePen import BasePen
class %s(BasePen):
diff --git a/Lib/fontTools/misc/testTools.py b/Lib/fontTools/misc/testTools.py
index be9bc851..1b258e37 100644
--- a/Lib/fontTools/misc/testTools.py
+++ b/Lib/fontTools/misc/testTools.py
@@ -1,12 +1,13 @@
"""Helpers for writing unit tests."""
from collections.abc import Iterable
+from io import BytesIO
import os
import shutil
import sys
import tempfile
from unittest import TestCase as _TestCase
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes
from fontTools.misc.xmlWriter import XMLWriter
@@ -25,7 +26,7 @@ def parseXML(xmlSnippet):
xml = b"<root>"
if isinstance(xmlSnippet, bytes):
xml += xmlSnippet
- elif isinstance(xmlSnippet, unicode):
+ elif isinstance(xmlSnippet, str):
xml += tobytes(xmlSnippet, 'utf-8')
elif isinstance(xmlSnippet, Iterable):
xml += b"".join(tobytes(s, 'utf-8') for s in xmlSnippet)
diff --git a/Lib/fontTools/misc/textTools.py b/Lib/fontTools/misc/textTools.py
index 6a047ae6..072976af 100644
--- a/Lib/fontTools/misc/textTools.py
+++ b/Lib/fontTools/misc/textTools.py
@@ -1,7 +1,7 @@
"""fontTools.misc.textTools.py -- miscellaneous routines."""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin, tobytes
import ast
import string
@@ -12,7 +12,7 @@ safeEval = ast.literal_eval
def readHex(content):
"""Convert a list of hex strings to binary data."""
- return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, basestring)))
+ return deHexStr(strjoin(chunk for chunk in content if isinstance(chunk, str)))
def deHexStr(hexdata):
diff --git a/Lib/fontTools/misc/timeTools.py b/Lib/fontTools/misc/timeTools.py
index 13613827..f4b84f6e 100644
--- a/Lib/fontTools/misc/timeTools.py
+++ b/Lib/fontTools/misc/timeTools.py
@@ -1,7 +1,6 @@
"""fontTools.misc.timeTools.py -- tools for working with OpenType timestamps.
"""
-from fontTools.misc.py23 import *
import os
import time
from datetime import datetime, timezone
diff --git a/Lib/fontTools/misc/vector.py b/Lib/fontTools/misc/vector.py
new file mode 100644
index 00000000..81c14841
--- /dev/null
+++ b/Lib/fontTools/misc/vector.py
@@ -0,0 +1,143 @@
+from numbers import Number
+import math
+import operator
+import warnings
+
+
+__all__ = ["Vector"]
+
+
+class Vector(tuple):
+
+ """A math-like vector.
+
+ Represents an n-dimensional numeric vector. ``Vector`` objects support
+ vector addition and subtraction, scalar multiplication and division,
+ negation, rounding, and comparison tests.
+ """
+
+ __slots__ = ()
+
+ def __new__(cls, values, keep=False):
+ if keep is not False:
+ warnings.warn(
+ "the 'keep' argument has been deprecated",
+ DeprecationWarning,
+ )
+ if type(values) == Vector:
+ # No need to create a new object
+ return values
+ return super().__new__(cls, values)
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}({super().__repr__()})"
+
+ def _vectorOp(self, other, op):
+ if isinstance(other, Vector):
+ assert len(self) == len(other)
+ return self.__class__(op(a, b) for a, b in zip(self, other))
+ if isinstance(other, Number):
+ return self.__class__(op(v, other) for v in self)
+ raise NotImplementedError()
+
+ def _scalarOp(self, other, op):
+ if isinstance(other, Number):
+ return self.__class__(op(v, other) for v in self)
+ raise NotImplementedError()
+
+ def _unaryOp(self, op):
+ return self.__class__(op(v) for v in self)
+
+ def __add__(self, other):
+ return self._vectorOp(other, operator.add)
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ return self._vectorOp(other, operator.sub)
+
+ def __rsub__(self, other):
+ return self._vectorOp(other, _operator_rsub)
+
+ def __mul__(self, other):
+ return self._scalarOp(other, operator.mul)
+
+ __rmul__ = __mul__
+
+ def __truediv__(self, other):
+ return self._scalarOp(other, operator.truediv)
+
+ def __rtruediv__(self, other):
+ return self._scalarOp(other, _operator_rtruediv)
+
+ def __pos__(self):
+ return self._unaryOp(operator.pos)
+
+ def __neg__(self):
+ return self._unaryOp(operator.neg)
+
+ def __round__(self, *, round=round):
+ return self._unaryOp(round)
+
+ def __eq__(self, other):
+ if isinstance(other, list):
+ # bw compat Vector([1, 2, 3]) == [1, 2, 3]
+ other = tuple(other)
+ return super().__eq__(other)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __bool__(self):
+ return any(self)
+
+ __nonzero__ = __bool__
+
+ def __abs__(self):
+ return math.sqrt(sum(x * x for x in self))
+
+ def length(self):
+ """Return the length of the vector. Equivalent to abs(vector)."""
+ return abs(self)
+
+ def normalized(self):
+ """Return the normalized vector of the vector."""
+ return self / abs(self)
+
+ def dot(self, other):
+ """Performs vector dot product, returning the sum of
+ ``a[0] * b[0], a[1] * b[1], ...``"""
+ assert len(self) == len(other)
+ return sum(a * b for a, b in zip(self, other))
+
+ # Deprecated methods/properties
+
+ def toInt(self):
+ warnings.warn(
+ "the 'toInt' method has been deprecated, use round(vector) instead",
+ DeprecationWarning,
+ )
+ return self.__round__()
+
+ @property
+ def values(self):
+ warnings.warn(
+ "the 'values' attribute has been deprecated, use "
+ "the vector object itself instead",
+ DeprecationWarning,
+ )
+ return list(self)
+
+ @values.setter
+ def values(self, values):
+ raise AttributeError(
+ "can't set attribute, the 'values' attribute has been deprecated",
+ )
+
+
+def _operator_rsub(a, b):
+ return operator.sub(b, a)
+
+
+def _operator_rtruediv(a, b):
+ return operator.truediv(b, a)
diff --git a/Lib/fontTools/misc/xmlReader.py b/Lib/fontTools/misc/xmlReader.py
index 406bba27..b2707e99 100644
--- a/Lib/fontTools/misc/xmlReader.py
+++ b/Lib/fontTools/misc/xmlReader.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from fontTools.ttLib.tables.DefaultTable import DefaultTable
diff --git a/Lib/fontTools/misc/xmlWriter.py b/Lib/fontTools/misc/xmlWriter.py
index 6ab8469e..fec127a9 100644
--- a/Lib/fontTools/misc/xmlWriter.py
+++ b/Lib/fontTools/misc/xmlWriter.py
@@ -1,6 +1,6 @@
"""xmlWriter.py -- Simple XML authoring class"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord, strjoin, tobytes, tostr
import sys
import os
import string
@@ -34,8 +34,8 @@ class XMLWriter(object):
self.totype = tobytes
except TypeError:
# This better not fail.
- self.file.write(tounicode(''))
- self.totype = tounicode
+ self.file.write('')
+ self.totype = tostr
self.indentwhite = self.totype(indentwhite)
if newlinestr is None:
self.newlinestr = self.totype(os.linesep)
@@ -156,7 +156,7 @@ class XMLWriter(object):
return ""
data = ""
for attr, value in attributes:
- if not isinstance(value, (bytes, unicode)):
+ if not isinstance(value, (bytes, str)):
value = str(value)
data = data + ' %s="%s"' % (attr, escapeattr(value))
return data
diff --git a/Lib/fontTools/mtiLib/__init__.py b/Lib/fontTools/mtiLib/__init__.py
index 8525754f..667a216d 100644
--- a/Lib/fontTools/mtiLib/__init__.py
+++ b/Lib/fontTools/mtiLib/__init__.py
@@ -6,7 +6,6 @@
# http://monotype.github.io/OpenType_Table_Source/otl_source.html
# https://github.com/Monotype/OpenType_Table_Source/
-from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.ttLib.tables._c_m_a_p import cmap_classes
from fontTools.ttLib.tables import otTables as ot
diff --git a/Lib/fontTools/mtiLib/__main__.py b/Lib/fontTools/mtiLib/__main__.py
index eacfefd5..fe6b638b 100644
--- a/Lib/fontTools/mtiLib/__main__.py
+++ b/Lib/fontTools/mtiLib/__main__.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
import sys
from fontTools.mtiLib import main
diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py
index 029aa3fc..182f7da6 100644
--- a/Lib/fontTools/otlLib/builder.py
+++ b/Lib/fontTools/otlLib/builder.py
@@ -9,7 +9,9 @@ from fontTools.ttLib.tables.otBase import (
CountReference,
)
from fontTools.ttLib.tables import otBase
+from fontTools.feaLib.ast import STATNameStatement
from fontTools.otlLib.error import OpenTypeLibError
+from functools import reduce
import logging
import copy
@@ -94,9 +96,10 @@ def buildLookup(subtables, flags=0, markFilterSet=None):
subtables = [st for st in subtables if st is not None]
if not subtables:
return None
- assert all(t.LookupType == subtables[0].LookupType for t in subtables), (
- "all subtables must have the same LookupType; got %s"
- % repr([t.LookupType for t in subtables])
+ assert all(
+ t.LookupType == subtables[0].LookupType for t in subtables
+ ), "all subtables must have the same LookupType; got %s" % repr(
+ [t.LookupType for t in subtables]
)
self = ot.Lookup()
self.LookupType = subtables[0].LookupType
@@ -1027,7 +1030,7 @@ class MarkMarkPosBuilder(LookupBuilder):
builder.marks["acute"] = (0, a1)
builder.marks["grave"] = (0, a1)
builder.marks["cedilla"] = (1, a2)
- builder.baseMarks["acute"] = (0, a3)
+ builder.baseMarks["acute"] = {0: a3}
Attributes:
font (``fontTools.TTLib.TTFont``): A font object.
@@ -1035,8 +1038,8 @@ class MarkMarkPosBuilder(LookupBuilder):
source which produced this lookup.
marks: An dictionary mapping a glyph name to a two-element
tuple containing a mark class ID and ``otTables.Anchor`` object.
- baseMarks: An dictionary mapping a glyph name to a two-element
- tuple containing a mark class ID and ``otTables.Anchor`` object.
+ baseMarks: An dictionary mapping a glyph name to a dictionary
+ containing one item: a mark class ID and a ``otTables.Anchor`` object.
lookupflag (int): The lookup's flag
markFilterSet: Either ``None`` if no mark filtering set is used, or
an integer representing the filtering set to be used for this
@@ -2073,8 +2076,8 @@ def buildPairPosClassesSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2
classDef2.add(gc2)
self = ot.PairPos()
self.Format = 2
- self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
- self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
+ valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
+ valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
self.Coverage = buildCoverage(coverage, glyphMap)
self.ClassDef1 = classDef1.build()
self.ClassDef2 = classDef2.build()
@@ -2087,7 +2090,9 @@ def buildPairPosClassesSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2
self.Class1Record.append(rec1)
for c2 in classes2:
rec2 = ot.Class2Record()
- rec2.Value1, rec2.Value2 = pairs.get((c1, c2), (None, None))
+ val1, val2 = pairs.get((c1, c2), (None, None))
+ rec2.Value1 = ValueRecord(src=val1, valueFormat=valueFormat1) if valueFormat1 else None
+ rec2.Value2 = ValueRecord(src=val2, valueFormat=valueFormat2) if valueFormat2 else None
rec1.Class2Record.append(rec2)
self.Class1Count = len(self.Class1Record)
self.Class2Count = len(classes2)
@@ -2172,8 +2177,8 @@ def buildPairPosGlyphsSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=
"""
self = ot.PairPos()
self.Format = 1
- self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
- self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
+ valueFormat1 = self.ValueFormat1 = _getValueFormat(valueFormat1, pairs.values(), 0)
+ valueFormat2 = self.ValueFormat2 = _getValueFormat(valueFormat2, pairs.values(), 1)
p = {}
for (glyphA, glyphB), (valA, valB) in pairs.items():
p.setdefault(glyphA, []).append((glyphB, valA, valB))
@@ -2186,8 +2191,8 @@ def buildPairPosGlyphsSubtable(pairs, glyphMap, valueFormat1=None, valueFormat2=
for glyph2, val1, val2 in sorted(p[glyph], key=lambda x: glyphMap[x[0]]):
pvr = ot.PairValueRecord()
pvr.SecondGlyph = glyph2
- pvr.Value1 = val1 if val1 and val1.getFormat() != 0 else None
- pvr.Value2 = val2 if val2 and val2.getFormat() != 0 else None
+ pvr.Value1 = ValueRecord(src=val1, valueFormat=valueFormat1) if valueFormat1 else None
+ pvr.Value2 = ValueRecord(src=val2, valueFormat=valueFormat2) if valueFormat2 else None
ps.PairValueRecord.append(pvr)
ps.PairValueCount = len(ps.PairValueRecord)
self.PairSetCount = len(self.PairSet)
@@ -2308,10 +2313,8 @@ def buildSinglePosSubtable(values, glyphMap):
"""
self = ot.SinglePos()
self.Coverage = buildCoverage(values.keys(), glyphMap)
- valueRecords = [values[g] for g in self.Coverage.glyphs]
- self.ValueFormat = 0
- for v in valueRecords:
- self.ValueFormat |= v.getFormat()
+ valueFormat = self.ValueFormat = reduce(int.__or__, [v.getFormat() for v in values.values()], 0)
+ valueRecords = [ValueRecord(src=values[g], valueFormat=valueFormat) for g in self.Coverage.glyphs]
if all(v == valueRecords[0] for v in valueRecords):
self.Format = 1
if self.ValueFormat != 0:
@@ -2575,7 +2578,9 @@ class ClassDefBuilder(object):
self.classes_.add(glyphs)
for glyph in glyphs:
if glyph in self.glyphs_:
- raise OpenTypeLibError(f"Glyph {glyph} is already present in class.", None)
+ raise OpenTypeLibError(
+ f"Glyph {glyph} is already present in class.", None
+ )
self.glyphs_[glyph] = glyphs
def classes(self):
@@ -2687,8 +2692,8 @@ def buildStatTable(ttFont, axes, locations=None, elidedFallbackName=2):
]
The optional 'elidedFallbackName' argument can be a name ID (int),
- a string, or a dictionary containing multilingual names. It
- translates to the ElidedFallbackNameID field.
+ a string, a dictionary containing multilingual names, or a list of
+ STATNameStatements. It translates to the ElidedFallbackNameID field.
The 'ttFont' argument must be a TTFont instance that already has a
'name' table. If a 'STAT' table already exists, it will be
@@ -2797,6 +2802,20 @@ def _addName(nameTable, value, minNameID=0):
names = dict(en=value)
elif isinstance(value, dict):
names = value
+ elif isinstance(value, list):
+ nameID = nameTable._findUnusedNameID()
+ for nameRecord in value:
+ if isinstance(nameRecord, STATNameStatement):
+ nameTable.setName(
+ nameRecord.string,
+ nameID,
+ nameRecord.platformID,
+ nameRecord.platEncID,
+ nameRecord.langID,
+ )
+ else:
+ raise TypeError("value must be a list of STATNameStatements")
+ return nameID
else:
- raise TypeError("value must be int, str or dict")
+ raise TypeError("value must be int, str, dict or list")
return nameTable.addMultilingualName(names, minNameID=minNameID)
diff --git a/Lib/fontTools/pens/__init__.py b/Lib/fontTools/pens/__init__.py
index b1760311..156cb232 100644
--- a/Lib/fontTools/pens/__init__.py
+++ b/Lib/fontTools/pens/__init__.py
@@ -1,3 +1 @@
"""Empty __init__.py file to signal Python this directory is a package."""
-
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/pens/areaPen.py b/Lib/fontTools/pens/areaPen.py
index c9301542..403afe7b 100644
--- a/Lib/fontTools/pens/areaPen.py
+++ b/Lib/fontTools/pens/areaPen.py
@@ -1,6 +1,5 @@
"""Calculate the area of a glyph."""
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/basePen.py b/Lib/fontTools/pens/basePen.py
index c8c4c551..2161e021 100644
--- a/Lib/fontTools/pens/basePen.py
+++ b/Lib/fontTools/pens/basePen.py
@@ -36,26 +36,27 @@ Coordinates are usually expressed as (x, y) tuples, but generally any
sequence of length 2 will do.
"""
-from fontTools.misc.py23 import *
+from typing import Tuple
+
from fontTools.misc.loggingTools import LogMixin
__all__ = ["AbstractPen", "NullPen", "BasePen",
"decomposeSuperBezierSegment", "decomposeQuadraticSegment"]
-class AbstractPen(object):
+class AbstractPen:
- def moveTo(self, pt):
+ def moveTo(self, pt: Tuple[float, float]) -> None:
"""Begin a new sub path, set the current point to 'pt'. You must
end each sub path with a call to pen.closePath() or pen.endPath().
"""
raise NotImplementedError
- def lineTo(self, pt):
+ def lineTo(self, pt: Tuple[float, float]) -> None:
"""Draw a straight line from the current point to 'pt'."""
raise NotImplementedError
- def curveTo(self, *points):
+ def curveTo(self, *points: Tuple[float, float]) -> None:
"""Draw a cubic bezier with an arbitrary number of control points.
The last point specified is on-curve, all others are off-curve
@@ -76,7 +77,7 @@ class AbstractPen(object):
"""
raise NotImplementedError
- def qCurveTo(self, *points):
+ def qCurveTo(self, *points: Tuple[float, float]) -> None:
"""Draw a whole string of quadratic curve segments.
The last point specified is on-curve, all others are off-curve
@@ -93,19 +94,23 @@ class AbstractPen(object):
"""
raise NotImplementedError
- def closePath(self):
+ def closePath(self) -> None:
"""Close the current sub path. You must call either pen.closePath()
or pen.endPath() after each sub path.
"""
pass
- def endPath(self):
+ def endPath(self) -> None:
"""End the current sub path, but don't close it. You must call
either pen.closePath() or pen.endPath() after each sub path.
"""
pass
- def addComponent(self, glyphName, transformation):
+ def addComponent(
+ self,
+ glyphName: str,
+ transformation: Tuple[float, float, float, float, float, float]
+ ) -> None:
"""Add a sub glyph. The 'transformation' argument must be a 6-tuple
containing an affine transformation, or a Transform object from the
fontTools.misc.transform module. More precisely: it should be a
@@ -114,7 +119,7 @@ class AbstractPen(object):
raise NotImplementedError
-class NullPen(object):
+class NullPen(AbstractPen):
"""A pen that does nothing.
"""
diff --git a/Lib/fontTools/pens/boundsPen.py b/Lib/fontTools/pens/boundsPen.py
index c76efdfb..810715ca 100644
--- a/Lib/fontTools/pens/boundsPen.py
+++ b/Lib/fontTools/pens/boundsPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect
from fontTools.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/cocoaPen.py b/Lib/fontTools/pens/cocoaPen.py
index 9ca6f3bb..67482b4d 100644
--- a/Lib/fontTools/pens/cocoaPen.py
+++ b/Lib/fontTools/pens/cocoaPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/filterPen.py b/Lib/fontTools/pens/filterPen.py
index 7539efb5..4355ba41 100644
--- a/Lib/fontTools/pens/filterPen.py
+++ b/Lib/fontTools/pens/filterPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen
from fontTools.pens.pointPen import AbstractPointPen
from fontTools.pens.recordingPen import RecordingPen
diff --git a/Lib/fontTools/pens/momentsPen.py b/Lib/fontTools/pens/momentsPen.py
index 694d6b02..8c90f70a 100644
--- a/Lib/fontTools/pens/momentsPen.py
+++ b/Lib/fontTools/pens/momentsPen.py
@@ -1,6 +1,5 @@
"""Pen calculating 0th, 1st, and 2nd moments of area of glyph shapes.
This is low-level, autogenerated pen. Use statisticsPen instead."""
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/perimeterPen.py b/Lib/fontTools/pens/perimeterPen.py
index 36c7edb4..9a09cb8f 100644
--- a/Lib/fontTools/pens/perimeterPen.py
+++ b/Lib/fontTools/pens/perimeterPen.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Calculate the perimeter of a glyph."""
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import approximateQuadraticArcLengthC, calcQuadraticArcLengthC, approximateCubicArcLengthC, calcCubicArcLengthC
import math
diff --git a/Lib/fontTools/pens/pointInsidePen.py b/Lib/fontTools/pens/pointInsidePen.py
index 8de077c9..34597f40 100644
--- a/Lib/fontTools/pens/pointInsidePen.py
+++ b/Lib/fontTools/pens/pointInsidePen.py
@@ -2,7 +2,6 @@
for shapes.
"""
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import solveQuadratic, solveCubic
diff --git a/Lib/fontTools/pens/pointPen.py b/Lib/fontTools/pens/pointPen.py
index 55832181..26f99d41 100644
--- a/Lib/fontTools/pens/pointPen.py
+++ b/Lib/fontTools/pens/pointPen.py
@@ -11,8 +11,11 @@ steps through all the points in a call from glyph.drawPoints().
This allows the caller to provide more data for each point.
For instance, whether or not a point is smooth, and its name.
"""
-from fontTools.pens.basePen import AbstractPen
+
import math
+from typing import Any, Optional, Tuple
+
+from fontTools.pens.basePen import AbstractPen
__all__ = [
"AbstractPointPen",
@@ -24,26 +27,36 @@ __all__ = [
]
-class AbstractPointPen(object):
- """
- Baseclass for all PointPens.
- """
+class AbstractPointPen:
+ """Baseclass for all PointPens."""
- def beginPath(self, identifier=None, **kwargs):
+ def beginPath(self, identifier: Optional[str] = None, **kwargs: Any) -> None:
"""Start a new sub path."""
raise NotImplementedError
- def endPath(self):
+ def endPath(self) -> None:
"""End the current sub path."""
raise NotImplementedError
- def addPoint(self, pt, segmentType=None, smooth=False, name=None,
- identifier=None, **kwargs):
+ def addPoint(
+ self,
+ pt: Tuple[float, float],
+ segmentType: Optional[str] = None,
+ smooth: bool = False,
+ name: Optional[str] = None,
+ identifier: Optional[str] = None,
+ **kwargs: Any
+ ) -> None:
"""Add a point to the current sub path."""
raise NotImplementedError
- def addComponent(self, baseGlyphName, transformation, identifier=None,
- **kwargs):
+ def addComponent(
+ self,
+ baseGlyphName: str,
+ transformation: Tuple[float, float, float, float, float, float],
+ identifier: Optional[str] = None,
+ **kwargs: Any
+ ) -> None:
"""Add a sub glyph."""
raise NotImplementedError
diff --git a/Lib/fontTools/pens/qtPen.py b/Lib/fontTools/pens/qtPen.py
index 20d7e23a..34736453 100644
--- a/Lib/fontTools/pens/qtPen.py
+++ b/Lib/fontTools/pens/qtPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/quartzPen.py b/Lib/fontTools/pens/quartzPen.py
index d35a993b..16b9c2d8 100644
--- a/Lib/fontTools/pens/quartzPen.py
+++ b/Lib/fontTools/pens/quartzPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from Quartz.CoreGraphics import CGPathCreateMutable, CGPathMoveToPoint
diff --git a/Lib/fontTools/pens/recordingPen.py b/Lib/fontTools/pens/recordingPen.py
index b25011d6..99e87e5a 100644
--- a/Lib/fontTools/pens/recordingPen.py
+++ b/Lib/fontTools/pens/recordingPen.py
@@ -1,5 +1,4 @@
"""Pen recording operations that can be accessed or replayed."""
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen, DecomposingPen
from fontTools.pens.pointPen import AbstractPointPen
@@ -141,7 +140,6 @@ class RecordingPointPen(AbstractPointPen):
if __name__ == "__main__":
- from fontTools.pens.basePen import _TestPen
pen = RecordingPen()
pen.moveTo((0, 0))
pen.lineTo((0, 100))
diff --git a/Lib/fontTools/pens/reportLabPen.py b/Lib/fontTools/pens/reportLabPen.py
index 51d213f7..c0a4610b 100644
--- a/Lib/fontTools/pens/reportLabPen.py
+++ b/Lib/fontTools/pens/reportLabPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
from reportlab.graphics.shapes import Path
diff --git a/Lib/fontTools/pens/reverseContourPen.py b/Lib/fontTools/pens/reverseContourPen.py
index abc0fa29..9b3241b6 100644
--- a/Lib/fontTools/pens/reverseContourPen.py
+++ b/Lib/fontTools/pens/reverseContourPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc.arrayTools import pairwise
from fontTools.pens.filterPen import ContourFilterPen
diff --git a/Lib/fontTools/pens/roundingPen.py b/Lib/fontTools/pens/roundingPen.py
index c032cad1..2a7c476c 100644
--- a/Lib/fontTools/pens/roundingPen.py
+++ b/Lib/fontTools/pens/roundingPen.py
@@ -1,4 +1,4 @@
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools.misc.transform import Transform
from fontTools.pens.filterPen import FilterPen, FilterPointPen
diff --git a/Lib/fontTools/pens/statisticsPen.py b/Lib/fontTools/pens/statisticsPen.py
index 7d602067..abd6ff5e 100644
--- a/Lib/fontTools/pens/statisticsPen.py
+++ b/Lib/fontTools/pens/statisticsPen.py
@@ -1,6 +1,5 @@
"""Pen calculating area, center of mass, variance and standard-deviation,
covariance and correlation, and slant, of glyph shapes."""
-from fontTools.misc.py23 import *
import math
from fontTools.pens.momentsPen import MomentsPen
diff --git a/Lib/fontTools/pens/svgPathPen.py b/Lib/fontTools/pens/svgPathPen.py
index 803f3935..4352ba47 100644
--- a/Lib/fontTools/pens/svgPathPen.py
+++ b/Lib/fontTools/pens/svgPathPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/pens/t2CharStringPen.py b/Lib/fontTools/pens/t2CharStringPen.py
index 89340d1e..0fddec1a 100644
--- a/Lib/fontTools/pens/t2CharStringPen.py
+++ b/Lib/fontTools/pens/t2CharStringPen.py
@@ -1,37 +1,12 @@
# Copyright (c) 2009 Type Supply LLC
# Author: Tal Leming
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound, roundFunc
from fontTools.misc.psCharStrings import T2CharString
from fontTools.pens.basePen import BasePen
from fontTools.cffLib.specializer import specializeCommands, commandsToProgram
-def t2c_round(number, tolerance=0.5):
- if tolerance == 0:
- return number # no-op
- rounded = otRound(number)
- # return rounded integer if the tolerance >= 0.5, or if the absolute
- # difference between the original float and the rounded integer is
- # within the tolerance
- if tolerance >= .5 or abs(rounded - number) <= tolerance:
- return rounded
- else:
- # else return the value un-rounded
- return number
-
-def makeRoundFunc(tolerance):
- if tolerance < 0:
- raise ValueError("Rounding tolerance must be positive")
-
- def roundPoint(point):
- x, y = point
- return t2c_round(x, tolerance), t2c_round(y, tolerance)
-
- return roundPoint
-
-
class T2CharStringPen(BasePen):
"""Pen to draw Type 2 CharStrings.
@@ -45,7 +20,7 @@ class T2CharStringPen(BasePen):
def __init__(self, width, glyphSet, roundTolerance=0.5, CFF2=False):
super(T2CharStringPen, self).__init__(glyphSet)
- self.roundPoint = makeRoundFunc(roundTolerance)
+ self.round = roundFunc(roundTolerance)
self._CFF2 = CFF2
self._width = width
self._commands = []
@@ -53,7 +28,7 @@ class T2CharStringPen(BasePen):
def _p(self, pt):
p0 = self._p0
- pt = self._p0 = self.roundPoint(pt)
+ pt = self._p0 = (self.round(pt[0]), self.round(pt[1]))
return [pt[0]-p0[0], pt[1]-p0[1]]
def _moveTo(self, pt):
diff --git a/Lib/fontTools/pens/teePen.py b/Lib/fontTools/pens/teePen.py
index 49420dca..2f30e922 100644
--- a/Lib/fontTools/pens/teePen.py
+++ b/Lib/fontTools/pens/teePen.py
@@ -1,5 +1,4 @@
"""Pen multiplexing drawing to one or more pens."""
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import AbstractPen
diff --git a/Lib/fontTools/pens/transformPen.py b/Lib/fontTools/pens/transformPen.py
index 6619ba73..2dcf83b1 100644
--- a/Lib/fontTools/pens/transformPen.py
+++ b/Lib/fontTools/pens/transformPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.filterPen import FilterPen, FilterPointPen
diff --git a/Lib/fontTools/pens/ttGlyphPen.py b/Lib/fontTools/pens/ttGlyphPen.py
index 866298be..e7841efc 100644
--- a/Lib/fontTools/pens/ttGlyphPen.py
+++ b/Lib/fontTools/pens/ttGlyphPen.py
@@ -1,6 +1,6 @@
-from fontTools.misc.py23 import *
from array import array
from fontTools.misc.fixedTools import MAX_F2DOT14, otRound, floatToFixedToFloat
+from fontTools.misc.roundTools import otRound
from fontTools.pens.basePen import LoggingPen
from fontTools.pens.transformPen import TransformPen
from fontTools.ttLib.tables import ttProgram
@@ -73,6 +73,9 @@ class TTGlyphPen(LoggingPen):
assert self._isClosed(), '"move"-type point must begin a new contour.'
self._addPoint(pt, 1)
+ def curveTo(self, *points):
+ raise NotImplementedError
+
def qCurveTo(self, *points):
assert len(points) >= 1
for pt in points[:-1]:
diff --git a/Lib/fontTools/pens/wxPen.py b/Lib/fontTools/pens/wxPen.py
index 5ff6c471..1504f089 100644
--- a/Lib/fontTools/pens/wxPen.py
+++ b/Lib/fontTools/pens/wxPen.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.pens.basePen import BasePen
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index 8162c09c..f687b056 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -2,14 +2,13 @@
#
# Google Author(s): Behdad Esfahbod
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.ttLib.tables import otTables
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.pens.basePen import NullPen
from fontTools.misc.loggingTools import Timer
from fontTools.subset.cff import *
-from fontTools.varLib import varStore
import sys
import struct
import array
@@ -428,13 +427,14 @@ def intersect_class(self, glyphs, klass):
if v == klass and g in glyphs)
@_add_method(otTables.ClassDef)
-def subset(self, glyphs, remap=False):
+def subset(self, glyphs, remap=False, useClass0=True):
"""Returns ascending list of remaining classes."""
self.classDefs = {g:v for g,v in self.classDefs.items() if g in glyphs}
# Note: while class 0 has the special meaning of "not matched",
# if no glyph will ever /not match/, we can optimize class 0 out too.
+ # Only do this if allowed.
indices = _uniq_sort(
- ([0] if any(g not in self.classDefs for g in glyphs) else []) +
+ ([0] if ((not useClass0) or any(g not in self.classDefs for g in glyphs)) else []) +
list(self.classDefs.values()))
if remap:
self.remap(indices)
@@ -570,15 +570,16 @@ def subset_glyphs(self, s):
self.PairSetCount = len(self.PairSet)
return bool(self.PairSetCount)
elif self.Format == 2:
- class1_map = [c for c in self.ClassDef1.subset(s.glyphs, remap=True) if c < self.Class1Count]
- class2_map = [c for c in self.ClassDef2.subset(s.glyphs, remap=True) if c < self.Class2Count]
+ class1_map = [c for c in self.ClassDef1.subset(s.glyphs.intersection(self.Coverage.glyphs), remap=True) if c < self.Class1Count]
+ class2_map = [c for c in self.ClassDef2.subset(s.glyphs, remap=True, useClass0=False) if c < self.Class2Count]
self.Class1Record = [self.Class1Record[i] for i in class1_map]
for c in self.Class1Record:
c.Class2Record = [c.Class2Record[i] for i in class2_map]
self.Class1Count = len(class1_map)
self.Class2Count = len(class2_map)
+ # If only Class2 0 left, no need to keep anything.
return bool(self.Class1Count and
- self.Class2Count and
+ (self.Class2Count > 1) and
self.Coverage.subset(s.glyphs))
else:
assert 0, "unknown format: %s" % self.Format
@@ -1877,7 +1878,7 @@ def subset_glyphs(self, s):
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
used.update(table.RsbMap.mapping.values())
- varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
+ varidx_map = table.VarStore.subset_varidxes(used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
if table.AdvWidthMap:
table.AdvWidthMap.mapping = _remap_index_map(s, varidx_map, table.AdvWidthMap)
@@ -1915,7 +1916,7 @@ def subset_glyphs(self, s):
table.VOrgMap.mapping = _dict_subset(table.VOrgMap.mapping, s.glyphs)
used.update(table.VOrgMap.mapping.values())
- varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
+ varidx_map = table.VarStore.subset_varidxes(used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
if table.AdvHeightMap:
table.AdvHeightMap.mapping = _remap_index_map(s, varidx_map, table.AdvHeightMap)
diff --git a/Lib/fontTools/subset/__main__.py b/Lib/fontTools/subset/__main__.py
index 93549d5d..22038473 100644
--- a/Lib/fontTools/subset/__main__.py
+++ b/Lib/fontTools/subset/__main__.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
import sys
from fontTools.subset import main
diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py
index 7db6d880..b59c6b96 100644
--- a/Lib/fontTools/subset/cff.py
+++ b/Lib/fontTools/subset/cff.py
@@ -1,7 +1,7 @@
from fontTools.misc import psCharStrings
from fontTools import ttLib
from fontTools.pens.basePen import NullPen
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools.varLib.varStore import VarStoreInstancer
def _add_method(*clazzes):
diff --git a/Lib/fontTools/svgLib/__init__.py b/Lib/fontTools/svgLib/__init__.py
index ca5af1cf..c049006b 100644
--- a/Lib/fontTools/svgLib/__init__.py
+++ b/Lib/fontTools/svgLib/__init__.py
@@ -1,5 +1,3 @@
-from fontTools.misc.py23 import *
-
from .path import SVGPath, parse_path
__all__ = ["SVGPath", "parse_path"]
diff --git a/Lib/fontTools/svgLib/path/__init__.py b/Lib/fontTools/svgLib/path/__init__.py
index 9dc5ac61..9440429b 100644
--- a/Lib/fontTools/svgLib/path/__init__.py
+++ b/Lib/fontTools/svgLib/path/__init__.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tostr
from fontTools.pens.transformPen import TransformPen
from fontTools.misc import etree
diff --git a/Lib/fontTools/svgLib/path/arc.py b/Lib/fontTools/svgLib/path/arc.py
index 25b9aa5e..31810712 100644
--- a/Lib/fontTools/svgLib/path/arc.py
+++ b/Lib/fontTools/svgLib/path/arc.py
@@ -4,10 +4,8 @@ The code is mostly adapted from Blink's SVGPathNormalizer::DecomposeArcToCubic
https://github.com/chromium/chromium/blob/93831f2/third_party/
blink/renderer/core/svg/svg_path_parser.cc#L169-L278
"""
-from fontTools.misc.py23 import *
-from fontTools.misc.py23 import isfinite
from fontTools.misc.transform import Identity, Scale
-from math import atan2, ceil, cos, fabs, pi, radians, sin, sqrt, tan
+from math import atan2, ceil, cos, fabs, isfinite, pi, radians, sin, sqrt, tan
TWO_PI = 2 * pi
diff --git a/Lib/fontTools/svgLib/path/parser.py b/Lib/fontTools/svgLib/path/parser.py
index 3d8d539d..1fcf8998 100644
--- a/Lib/fontTools/svgLib/path/parser.py
+++ b/Lib/fontTools/svgLib/path/parser.py
@@ -7,7 +7,6 @@
# Copyright (c) 2013-2014 Lennart Regebro
# License: MIT
-from fontTools.misc.py23 import *
from .arc import EllipticalArc
import re
diff --git a/Lib/fontTools/t1Lib/__init__.py b/Lib/fontTools/t1Lib/__init__.py
index 88729aec..e1d94d35 100644
--- a/Lib/fontTools/t1Lib/__init__.py
+++ b/Lib/fontTools/t1Lib/__init__.py
@@ -15,7 +15,7 @@ write(path, data, kind='OTHER', dohex=False)
part should be written as hexadecimal or binary, but only if kind
is 'OTHER'.
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin
from fontTools.misc import eexec
from fontTools.misc.macCreatorType import getMacCreatorAndType
import os
diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py
index 7b01ddd4..16417e73 100644
--- a/Lib/fontTools/ttLib/__init__.py
+++ b/Lib/fontTools/ttLib/__init__.py
@@ -41,7 +41,6 @@ Dumping 'prep' table...
"""
-from fontTools.misc.py23 import *
from fontTools.misc.loggingTools import deprecateFunction
import logging
diff --git a/Lib/fontTools/ttLib/macUtils.py b/Lib/fontTools/ttLib/macUtils.py
index 6a7cb2e8..496fb672 100644
--- a/Lib/fontTools/ttLib/macUtils.py
+++ b/Lib/fontTools/ttLib/macUtils.py
@@ -1,5 +1,5 @@
"""ttLib.macUtils.py -- Various Mac-specific stuff."""
-from fontTools.misc.py23 import *
+from io import BytesIO
from fontTools.misc.macRes import ResourceReader, ResourceError
@@ -40,7 +40,7 @@ class SFNTResourceReader(BytesIO):
def __init__(self, path, res_name_or_index):
from fontTools import ttLib
reader = ResourceReader(path)
- if isinstance(res_name_or_index, basestring):
+ if isinstance(res_name_or_index, str):
rsrc = reader.getNamedResource('sfnt', res_name_or_index)
else:
rsrc = reader.getIndResource('sfnt', res_name_or_index)
diff --git a/Lib/fontTools/ttLib/standardGlyphOrder.py b/Lib/fontTools/ttLib/standardGlyphOrder.py
index 9697fcc6..1f980e45 100644
--- a/Lib/fontTools/ttLib/standardGlyphOrder.py
+++ b/Lib/fontTools/ttLib/standardGlyphOrder.py
@@ -1,5 +1,3 @@
-from fontTools.misc.py23 import *
-
#
# 'post' table formats 1.0 and 2.0 rely on this list of "standard"
# glyphs.
diff --git a/Lib/fontTools/ttLib/tables/B_A_S_E_.py b/Lib/fontTools/ttLib/tables/B_A_S_E_.py
index 6c4863ad..9551e2c6 100644
--- a/Lib/fontTools/ttLib/tables/B_A_S_E_.py
+++ b/Lib/fontTools/ttLib/tables/B_A_S_E_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py b/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
index 7e19b011..9197923d 100644
--- a/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
+++ b/Lib/fontTools/ttLib/tables/BitmapGlyphMetrics.py
@@ -1,6 +1,5 @@
# Since bitmap glyph metrics are shared between EBLC and EBDT
# this class gets its own python file.
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
import logging
diff --git a/Lib/fontTools/ttLib/tables/C_B_D_T_.py b/Lib/fontTools/ttLib/tables/C_B_D_T_.py
index 6fd4143b..11bb60b8 100644
--- a/Lib/fontTools/ttLib/tables/C_B_D_T_.py
+++ b/Lib/fontTools/ttLib/tables/C_B_D_T_.py
@@ -3,7 +3,7 @@
# Google Author(s): Matt Fontaine
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from . import E_B_D_T_
from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
diff --git a/Lib/fontTools/ttLib/tables/C_B_L_C_.py b/Lib/fontTools/ttLib/tables/C_B_L_C_.py
index ed7e3aed..2f785710 100644
--- a/Lib/fontTools/ttLib/tables/C_B_L_C_.py
+++ b/Lib/fontTools/ttLib/tables/C_B_L_C_.py
@@ -2,7 +2,6 @@
#
# Google Author(s): Matt Fontaine
-from fontTools.misc.py23 import *
from . import E_B_L_C_
class table_C_B_L_C_(E_B_L_C_.table_E_B_L_C_):
diff --git a/Lib/fontTools/ttLib/tables/C_F_F_.py b/Lib/fontTools/ttLib/tables/C_F_F_.py
index 1d3860c9..d12b89d2 100644
--- a/Lib/fontTools/ttLib/tables/C_F_F_.py
+++ b/Lib/fontTools/ttLib/tables/C_F_F_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from io import BytesIO
from fontTools import cffLib
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/C_F_F__2.py b/Lib/fontTools/ttLib/tables/C_F_F__2.py
index 7aaf4ce6..6217ebba 100644
--- a/Lib/fontTools/ttLib/tables/C_F_F__2.py
+++ b/Lib/fontTools/ttLib/tables/C_F_F__2.py
@@ -1,5 +1,4 @@
-from fontTools.misc.py23 import *
-from fontTools import cffLib
+from io import BytesIO
from fontTools.ttLib.tables.C_F_F_ import table_C_F_F_
diff --git a/Lib/fontTools/ttLib/tables/C_O_L_R_.py b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
index db490520..4004d417 100644
--- a/Lib/fontTools/ttLib/tables/C_O_L_R_.py
+++ b/Lib/fontTools/ttLib/tables/C_O_L_R_.py
@@ -2,7 +2,6 @@
#
# Google Author(s): Behdad Esfahbod
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -105,11 +104,11 @@ class table_C_O_L_R_(DefaultTable.DefaultTable):
self.ColorLayers = {}
glyphName = attrs["name"]
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
layers = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
layer = LayerRecord()
layer.fromXML(element[0], element[1], element[2], ttFont)
diff --git a/Lib/fontTools/ttLib/tables/C_P_A_L_.py b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
index 22ef0623..c095095e 100644
--- a/Lib/fontTools/ttLib/tables/C_P_A_L_.py
+++ b/Lib/fontTools/ttLib/tables/C_P_A_L_.py
@@ -2,7 +2,7 @@
#
# Google Author(s): Behdad Esfahbod
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import array
@@ -208,7 +208,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
self.paletteTypes.append(int(attrs.get("type", self.DEFAULT_PALETTE_TYPE)))
palette = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
attrs = element[1]
color = Color.fromHex(attrs["value"])
@@ -217,7 +217,7 @@ class table_C_P_A_L_(DefaultTable.DefaultTable):
elif name == "paletteEntryLabels":
colorLabels = {}
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
elementName, elementAttr, _ = element
if elementName == "label":
diff --git a/Lib/fontTools/ttLib/tables/D_S_I_G_.py b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
index d9a02ba2..1a520cab 100644
--- a/Lib/fontTools/ttLib/tables/D_S_I_G_.py
+++ b/Lib/fontTools/ttLib/tables/D_S_I_G_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, strjoin, tobytes, tostr
from fontTools.misc.textTools import safeEval
from fontTools.misc import sstruct
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/DefaultTable.py b/Lib/fontTools/ttLib/tables/DefaultTable.py
index 16f5da07..c70480a3 100644
--- a/Lib/fontTools/ttLib/tables/DefaultTable.py
+++ b/Lib/fontTools/ttLib/tables/DefaultTable.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag
from fontTools.ttLib import getClassTag
class DefaultTable(object):
diff --git a/Lib/fontTools/ttLib/tables/E_B_D_T_.py b/Lib/fontTools/ttLib/tables/E_B_D_T_.py
index e85a4cd1..5d9e7244 100644
--- a/Lib/fontTools/ttLib/tables/E_B_D_T_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_D_T_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, readHex, hexStr, deHexStr
from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGlyphMetrics, smallGlyphMetricsFormat
diff --git a/Lib/fontTools/ttLib/tables/E_B_L_C_.py b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
index b065df07..94d40d96 100644
--- a/Lib/fontTools/ttLib/tables/E_B_L_C_.py
+++ b/Lib/fontTools/ttLib/tables/E_B_L_C_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from . import DefaultTable
from fontTools.misc.textTools import safeEval
diff --git a/Lib/fontTools/ttLib/tables/F_F_T_M_.py b/Lib/fontTools/ttLib/tables/F_F_T_M_.py
index 20a12eca..2376f2db 100644
--- a/Lib/fontTools/ttLib/tables/F_F_T_M_.py
+++ b/Lib/fontTools/ttLib/tables/F_F_T_M_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.timeTools import timestampFromString, timestampToString
diff --git a/Lib/fontTools/ttLib/tables/F__e_a_t.py b/Lib/fontTools/ttLib/tables/F__e_a_t.py
index d7f0ba3d..7e510614 100644
--- a/Lib/fontTools/ttLib/tables/F__e_a_t.py
+++ b/Lib/fontTools/ttLib/tables/F__e_a_t.py
@@ -1,8 +1,6 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
-from .otBase import BaseTTXConverter
from . import DefaultTable
from . import grUtils
import struct
diff --git a/Lib/fontTools/ttLib/tables/G_D_E_F_.py b/Lib/fontTools/ttLib/tables/G_D_E_F_.py
index 5fb65d54..d4a57414 100644
--- a/Lib/fontTools/ttLib/tables/G_D_E_F_.py
+++ b/Lib/fontTools/ttLib/tables/G_D_E_F_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/G_M_A_P_.py b/Lib/fontTools/ttLib/tables/G_M_A_P_.py
index ce3ac41f..5b30dcfe 100644
--- a/Lib/fontTools/ttLib/tables/G_M_A_P_.py
+++ b/Lib/fontTools/ttLib/tables/G_M_A_P_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -115,7 +115,7 @@ class table_G_M_A_P_(DefaultTable.DefaultTable):
gmapRecord = GMAPRecord()
self.gmapRecords.append(gmapRecord)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
gmapRecord.fromXML(name, attrs, content, ttFont)
diff --git a/Lib/fontTools/ttLib/tables/G_P_K_G_.py b/Lib/fontTools/ttLib/tables/G_P_K_G_.py
index 601b3c26..7598a62a 100644
--- a/Lib/fontTools/ttLib/tables/G_P_K_G_.py
+++ b/Lib/fontTools/ttLib/tables/G_P_K_G_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, readHex
from . import DefaultTable
@@ -106,7 +106,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
if not hasattr(self, "GMAPs"):
self.GMAPs = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
itemName, itemAttrs, itemContent = element
if itemName == "hexdata":
@@ -115,7 +115,7 @@ class table_G_P_K_G_(DefaultTable.DefaultTable):
if not hasattr(self, "glyphlets"):
self.glyphlets = []
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
itemName, itemAttrs, itemContent = element
if itemName == "hexdata":
diff --git a/Lib/fontTools/ttLib/tables/G_P_O_S_.py b/Lib/fontTools/ttLib/tables/G_P_O_S_.py
index 34d1ce14..013c8209 100644
--- a/Lib/fontTools/ttLib/tables/G_P_O_S_.py
+++ b/Lib/fontTools/ttLib/tables/G_P_O_S_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/G_S_U_B_.py b/Lib/fontTools/ttLib/tables/G_S_U_B_.py
index 5ba87785..44036490 100644
--- a/Lib/fontTools/ttLib/tables/G_S_U_B_.py
+++ b/Lib/fontTools/ttLib/tables/G_S_U_B_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/G__l_a_t.py b/Lib/fontTools/ttLib/tables/G__l_a_t.py
index 8fdb9671..a4e8e38f 100644
--- a/Lib/fontTools/ttLib/tables/G__l_a_t.py
+++ b/Lib/fontTools/ttLib/tables/G__l_a_t.py
@@ -1,12 +1,11 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
-from itertools import *
+# from itertools import *
from functools import partial
from . import DefaultTable
from . import grUtils
-import struct, operator, warnings
+import struct
Glat_format_0 = """
diff --git a/Lib/fontTools/ttLib/tables/G__l_o_c.py b/Lib/fontTools/ttLib/tables/G__l_o_c.py
index 4ff8a47b..fa114a31 100644
--- a/Lib/fontTools/ttLib/tables/G__l_o_c.py
+++ b/Lib/fontTools/ttLib/tables/G__l_o_c.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/H_V_A_R_.py b/Lib/fontTools/ttLib/tables/H_V_A_R_.py
index 67d5a7bb..56992ad0 100644
--- a/Lib/fontTools/ttLib/tables/H_V_A_R_.py
+++ b/Lib/fontTools/ttLib/tables/H_V_A_R_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/J_S_T_F_.py b/Lib/fontTools/ttLib/tables/J_S_T_F_.py
index 704f0304..ddf54055 100644
--- a/Lib/fontTools/ttLib/tables/J_S_T_F_.py
+++ b/Lib/fontTools/ttLib/tables/J_S_T_F_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/L_T_S_H_.py b/Lib/fontTools/ttLib/tables/L_T_S_H_.py
index 689548b9..94c2c22a 100644
--- a/Lib/fontTools/ttLib/tables/L_T_S_H_.py
+++ b/Lib/fontTools/ttLib/tables/L_T_S_H_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/M_A_T_H_.py b/Lib/fontTools/ttLib/tables/M_A_T_H_.py
index 7bf9098e..d894c082 100644
--- a/Lib/fontTools/ttLib/tables/M_A_T_H_.py
+++ b/Lib/fontTools/ttLib/tables/M_A_T_H_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/M_E_T_A_.py b/Lib/fontTools/ttLib/tables/M_E_T_A_.py
index 862487f7..d4f6bc8c 100644
--- a/Lib/fontTools/ttLib/tables/M_E_T_A_.py
+++ b/Lib/fontTools/ttLib/tables/M_E_T_A_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
@@ -173,7 +173,7 @@ class table_M_E_T_A_(DefaultTable.DefaultTable):
glyphRec = GlyphRecord()
self.glyphRecords.append(glyphRec)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
glyphRec.fromXML(name, attrs, content, ttFont)
@@ -207,7 +207,7 @@ class GlyphRecord(object):
stringRec = StringRecord()
self.stringRecs.append(stringRec)
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
stringRec.fromXML(name, attrs, content, ttFont)
stringRec.stringLen = len(stringRec.string)
@@ -229,7 +229,7 @@ class GlyphRecord(object):
# XXX The following two functions are really broken around UTF-8 vs Unicode
def mapXMLToUTF8(string):
- uString = unicode()
+ uString = str()
strLen = len(string)
i = 0
while i < strLen:
@@ -245,9 +245,9 @@ def mapXMLToUTF8(string):
i = i+1
valStr = string[j:i]
- uString = uString + unichr(eval('0x' + valStr))
+ uString = uString + chr(eval('0x' + valStr))
else:
- uString = uString + unichr(byteord(string[i]))
+ uString = uString + chr(byteord(string[i]))
i = i +1
return uString.encode('utf_8')
@@ -281,7 +281,7 @@ class StringRecord(object):
def fromXML(self, name, attrs, content, ttFont):
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
value = attrs["value"]
diff --git a/Lib/fontTools/ttLib/tables/M_V_A_R_.py b/Lib/fontTools/ttLib/tables/M_V_A_R_.py
index 4bae5afe..34ab20f7 100644
--- a/Lib/fontTools/ttLib/tables/M_V_A_R_.py
+++ b/Lib/fontTools/ttLib/tables/M_V_A_R_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/O_S_2f_2.py b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
index 9d833d96..a5765224 100644
--- a/Lib/fontTools/ttLib/tables/O_S_2f_2.py
+++ b/Lib/fontTools/ttLib/tables/O_S_2f_2.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from fontTools.ttLib.tables import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/S_I_N_G_.py b/Lib/fontTools/ttLib/tables/S_I_N_G_.py
index 61992423..dd9b63c4 100644
--- a/Lib/fontTools/ttLib/tables/S_I_N_G_.py
+++ b/Lib/fontTools/ttLib/tables/S_I_N_G_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/S_T_A_T_.py b/Lib/fontTools/ttLib/tables/S_T_A_T_.py
index c8278e0c..1769de91 100644
--- a/Lib/fontTools/ttLib/tables/S_T_A_T_.py
+++ b/Lib/fontTools/ttLib/tables/S_T_A_T_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/S_V_G_.py b/Lib/fontTools/ttLib/tables/S_V_G_.py
index 89551cda..135f2718 100644
--- a/Lib/fontTools/ttLib/tables/S_V_G_.py
+++ b/Lib/fontTools/ttLib/tables/S_V_G_.py
@@ -1,12 +1,12 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, strjoin, tobytes, tostr
from fontTools.misc import sstruct
from . import DefaultTable
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
+from io import BytesIO
import struct
-import re
import logging
diff --git a/Lib/fontTools/ttLib/tables/S__i_l_f.py b/Lib/fontTools/ttLib/tables/S__i_l_f.py
index 9953d156..95880b07 100644
--- a/Lib/fontTools/ttLib/tables/S__i_l_f.py
+++ b/Lib/fontTools/ttLib/tables/S__i_l_f.py
@@ -1,13 +1,13 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
-from itertools import *
+# from itertools import *
from . import DefaultTable
from . import grUtils
from array import array
from functools import reduce
-import struct, operator, warnings, re, sys
+import struct, re, sys
Silf_hdr_format = '''
>
diff --git a/Lib/fontTools/ttLib/tables/S__i_l_l.py b/Lib/fontTools/ttLib/tables/S__i_l_l.py
index 4d134418..5ab9ee34 100644
--- a/Lib/fontTools/ttLib/tables/S__i_l_l.py
+++ b/Lib/fontTools/ttLib/tables/S__i_l_l.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr
from fontTools.misc.textTools import safeEval
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_B_.py b/Lib/fontTools/ttLib/tables/T_S_I_B_.py
index 88b00159..25d43104 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_B_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_B_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_B_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_C_.py b/Lib/fontTools/ttLib/tables/T_S_I_C_.py
index 4f4763f3..573b3f9c 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_C_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_C_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_D_.py b/Lib/fontTools/ttLib/tables/T_S_I_D_.py
index 1fd3cb12..310eb174 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_D_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_D_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_D_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_J_.py b/Lib/fontTools/ttLib/tables/T_S_I_J_.py
index 462ad89e..c1a46ba6 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_J_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_J_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_J_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_P_.py b/Lib/fontTools/ttLib/tables/T_S_I_P_.py
index 06d57354..778974c8 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_P_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_P_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_P_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_S_.py b/Lib/fontTools/ttLib/tables/T_S_I_S_.py
index 27fcac76..61c9f76f 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_S_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_S_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .T_S_I_V_ import table_T_S_I_V_
class table_T_S_I_S_(table_T_S_I_V_):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I_V_.py b/Lib/fontTools/ttLib/tables/T_S_I_V_.py
index 1939cdc7..80214452 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I_V_.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I_V_.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin, tobytes, tostr
from . import asciiTable
class table_T_S_I_V_(asciiTable.asciiTable):
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__0.py b/Lib/fontTools/ttLib/tables/T_S_I__0.py
index 04f58cdd..b187f425 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__0.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__0.py
@@ -5,7 +5,6 @@ TSI0 is the index table containing the lengths and offsets for the glyph
programs and 'extra' programs ('fpgm', 'prep', and 'cvt') that are contained
in the TSI1 table.
"""
-from fontTools.misc.py23 import *
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__1.py b/Lib/fontTools/ttLib/tables/T_S_I__1.py
index b014cb4b..9ae7acd6 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__1.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__1.py
@@ -4,7 +4,7 @@ tool to store its hinting source data.
TSI1 contains the text of the glyph programs in the form of low-level assembly
code, as well as the 'extra' programs 'fpgm', 'ppgm' (i.e. 'prep'), and 'cvt'.
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin, tobytes, tostr
from . import DefaultTable
from fontTools.misc.loggingTools import LogMixin
@@ -68,7 +68,7 @@ class table_T_S_I__1(LogMixin, DefaultTable.DefaultTable):
"%r textLength (%d) must not be > 32768" % (name, textLength))
text = data[textOffset:textOffset+textLength]
assert len(text) == textLength
- text = tounicode(text, encoding='utf-8')
+ text = tostr(text, encoding='utf-8')
if text:
programs[name] = text
if isExtra:
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__2.py b/Lib/fontTools/ttLib/tables/T_S_I__2.py
index f7206d08..036c9815 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__2.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__2.py
@@ -5,7 +5,6 @@ TSI2 is the index table containing the lengths and offsets for the glyph
programs that are contained in the TSI3 table. It uses the same format as
the TSI0 table.
"""
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("TSI0")
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__3.py b/Lib/fontTools/ttLib/tables/T_S_I__3.py
index 92e0e53e..a2490142 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__3.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__3.py
@@ -3,7 +3,6 @@ tool to store its hinting source data.
TSI3 contains the text of the glyph programs in the form of 'VTTTalk' code.
"""
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("TSI1")
diff --git a/Lib/fontTools/ttLib/tables/T_S_I__5.py b/Lib/fontTools/ttLib/tables/T_S_I__5.py
index 5c7da1cb..7be09f9a 100644
--- a/Lib/fontTools/ttLib/tables/T_S_I__5.py
+++ b/Lib/fontTools/ttLib/tables/T_S_I__5.py
@@ -3,7 +3,6 @@ tool to store its hinting source data.
TSI5 contains the VTT character groups.
"""
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import sys
diff --git a/Lib/fontTools/ttLib/tables/T_T_F_A_.py b/Lib/fontTools/ttLib/tables/T_T_F_A_.py
index 3fdbd070..8446dfc5 100644
--- a/Lib/fontTools/ttLib/tables/T_T_F_A_.py
+++ b/Lib/fontTools/ttLib/tables/T_T_F_A_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from . import asciiTable
class table_T_T_F_A_(asciiTable.asciiTable):
diff --git a/Lib/fontTools/ttLib/tables/TupleVariation.py b/Lib/fontTools/ttLib/tables/TupleVariation.py
index 7f5c3a66..a63fb6c6 100644
--- a/Lib/fontTools/ttLib/tables/TupleVariation.py
+++ b/Lib/fontTools/ttLib/tables/TupleVariation.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
floatToFixed as fl2fi,
diff --git a/Lib/fontTools/ttLib/tables/V_D_M_X_.py b/Lib/fontTools/ttLib/tables/V_D_M_X_.py
index b4028c35..ba8593f1 100644
--- a/Lib/fontTools/ttLib/tables/V_D_M_X_.py
+++ b/Lib/fontTools/ttLib/tables/V_D_M_X_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from . import DefaultTable
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
diff --git a/Lib/fontTools/ttLib/tables/V_O_R_G_.py b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
index bbc6d022..0b7fe959 100644
--- a/Lib/fontTools/ttLib/tables/V_O_R_G_.py
+++ b/Lib/fontTools/ttLib/tables/V_O_R_G_.py
@@ -1,7 +1,6 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc.textTools import safeEval
from . import DefaultTable
-import operator
import struct
@@ -84,7 +83,7 @@ class table_V_O_R_G_(DefaultTable.DefaultTable):
if name == "VOriginRecord":
vOriginRec = VOriginRecord()
for element in content:
- if isinstance(element, basestring):
+ if isinstance(element, str):
continue
name, attrs, content = element
vOriginRec.fromXML(name, attrs, content, ttFont)
diff --git a/Lib/fontTools/ttLib/tables/V_V_A_R_.py b/Lib/fontTools/ttLib/tables/V_V_A_R_.py
index b835fc16..88f30552 100644
--- a/Lib/fontTools/ttLib/tables/V_V_A_R_.py
+++ b/Lib/fontTools/ttLib/tables/V_V_A_R_.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/__init__.py b/Lib/fontTools/ttLib/tables/__init__.py
index 0c99c22e..bbfb8b70 100644
--- a/Lib/fontTools/ttLib/tables/__init__.py
+++ b/Lib/fontTools/ttLib/tables/__init__.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
# DON'T EDIT! This file is generated by MetaTools/buildTableList.py.
def _moduleFinderHint():
@@ -15,6 +14,7 @@ def _moduleFinderHint():
from . import C_O_L_R_
from . import C_P_A_L_
from . import D_S_I_G_
+ from . import D__e_b_g
from . import E_B_D_T_
from . import E_B_L_C_
from . import F_F_T_M_
@@ -39,6 +39,7 @@ def _moduleFinderHint():
from . import S__i_l_f
from . import S__i_l_l
from . import T_S_I_B_
+ from . import T_S_I_C_
from . import T_S_I_D_
from . import T_S_I_J_
from . import T_S_I_P_
diff --git a/Lib/fontTools/ttLib/tables/_a_n_k_r.py b/Lib/fontTools/ttLib/tables/_a_n_k_r.py
index 9372168d..1f2946c2 100644
--- a/Lib/fontTools/ttLib/tables/_a_n_k_r.py
+++ b/Lib/fontTools/ttLib/tables/_a_n_k_r.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_a_v_a_r.py b/Lib/fontTools/ttLib/tables/_a_v_a_r.py
index 02c4c887..2b6a40ed 100644
--- a/Lib/fontTools/ttLib/tables/_a_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_a_v_a_r.py
@@ -1,5 +1,4 @@
-from fontTools.misc.py23 import *
-from fontTools import ttLib
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
@@ -7,10 +6,8 @@ from fontTools.misc.fixedTools import (
floatToFixedToStr as fl2str,
strToFixedToFloat as str2fl,
)
-from fontTools.misc.textTools import safeEval
from fontTools.ttLib import TTLibError
from . import DefaultTable
-import array
import struct
import logging
diff --git a/Lib/fontTools/ttLib/tables/_b_s_l_n.py b/Lib/fontTools/ttLib/tables/_b_s_l_n.py
index 8c0e39ea..8e266fa5 100644
--- a/Lib/fontTools/ttLib/tables/_b_s_l_n.py
+++ b/Lib/fontTools/ttLib/tables/_b_s_l_n.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_c_i_d_g.py b/Lib/fontTools/ttLib/tables/_c_i_d_g.py
index b9722563..de83d4d6 100644
--- a/Lib/fontTools/ttLib/tables/_c_i_d_g.py
+++ b/Lib/fontTools/ttLib/tables/_c_i_d_g.py
@@ -1,5 +1,4 @@
# coding: utf-8
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_c_m_a_p.py b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
index f7f92669..a65a0c25 100644
--- a/Lib/fontTools/ttLib/tables/_c_m_a_p.py
+++ b/Lib/fontTools/ttLib/tables/_c_m_a_p.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc.textTools import safeEval, readHex
from fontTools.misc.encodingTools import getEncoding
from fontTools.ttLib import getSearchRange
diff --git a/Lib/fontTools/ttLib/tables/_c_v_a_r.py b/Lib/fontTools/ttLib/tables/_c_v_a_r.py
index e336d248..09b2c16c 100644
--- a/Lib/fontTools/ttLib/tables/_c_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_c_v_a_r.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from . import DefaultTable
from fontTools.misc import sstruct
from fontTools.ttLib.tables.TupleVariation import \
diff --git a/Lib/fontTools/ttLib/tables/_c_v_t.py b/Lib/fontTools/ttLib/tables/_c_v_t.py
index 0016e3bb..26395c93 100644
--- a/Lib/fontTools/ttLib/tables/_c_v_t.py
+++ b/Lib/fontTools/ttLib/tables/_c_v_t.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import sys
diff --git a/Lib/fontTools/ttLib/tables/_f_e_a_t.py b/Lib/fontTools/ttLib/tables/_f_e_a_t.py
index 35344fdd..eb03f8ba 100644
--- a/Lib/fontTools/ttLib/tables/_f_e_a_t.py
+++ b/Lib/fontTools/ttLib/tables/_f_e_a_t.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_f_p_g_m.py b/Lib/fontTools/ttLib/tables/_f_p_g_m.py
index 77fdc6d0..ec3576ce 100644
--- a/Lib/fontTools/ttLib/tables/_f_p_g_m.py
+++ b/Lib/fontTools/ttLib/tables/_f_p_g_m.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from . import DefaultTable
from . import ttProgram
diff --git a/Lib/fontTools/ttLib/tables/_f_v_a_r.py b/Lib/fontTools/ttLib/tables/_f_v_a_r.py
index 933b25eb..7487da62 100644
--- a/Lib/fontTools/ttLib/tables/_f_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_f_v_a_r.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
@@ -6,7 +6,7 @@ from fontTools.misc.fixedTools import (
floatToFixedToStr as fl2str,
strToFixedToFloat as str2fl,
)
-from fontTools.misc.textTools import safeEval, num2binary, binary2num
+from fontTools.misc.textTools import safeEval
from fontTools.ttLib import TTLibError
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/_g_a_s_p.py b/Lib/fontTools/ttLib/tables/_g_a_s_p.py
index b49ccb59..2c80913c 100644
--- a/Lib/fontTools/ttLib/tables/_g_a_s_p.py
+++ b/Lib/fontTools/ttLib/tables/_g_a_s_p.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/_g_c_i_d.py b/Lib/fontTools/ttLib/tables/_g_c_i_d.py
index b2da40af..2e746c84 100644
--- a/Lib/fontTools/ttLib/tables/_g_c_i_d.py
+++ b/Lib/fontTools/ttLib/tables/_g_c_i_d.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
index e12969e4..4680ddbf 100644
--- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py
@@ -1,7 +1,7 @@
"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
from collections import namedtuple
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tostr
from fontTools.misc import sstruct
from fontTools import ttLib
from fontTools import version
@@ -152,7 +152,7 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
if glyph.numberOfContours:
if splitGlyphs:
glyphPath = userNameToFileName(
- tounicode(glyphName, 'utf-8'),
+ tostr(glyphName, 'utf-8'),
existingGlyphFiles,
prefix=path + ".",
suffix=ext)
@@ -1506,12 +1506,12 @@ class GlyphCoordinates(object):
p = self._checkFloat(p)
self._a.extend(p)
- def toInt(self):
+ def toInt(self, *, round=otRound):
if not self.isFloat():
return
a = array.array("h")
for n in self._a:
- a.append(otRound(n))
+ a.append(round(n))
self._a = a
def relativeToAbsolute(self):
@@ -1626,13 +1626,9 @@ class GlyphCoordinates(object):
for i in range(len(a)):
a[i] = -a[i]
return r
- def __round__(self):
- """
- Note: This is Python 3 only. Python 2 does not call __round__.
- As such, we cannot test this method either. :(
- """
+ def __round__(self, *, round=otRound):
r = self.copy()
- r.toInt()
+ r.toInt(round=round)
return r
def __add__(self, other): return self.copy().__iadd__(other)
diff --git a/Lib/fontTools/ttLib/tables/_g_v_a_r.py b/Lib/fontTools/ttLib/tables/_g_v_a_r.py
index 94417071..8c9b530e 100644
--- a/Lib/fontTools/ttLib/tables/_g_v_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_g_v_a_r.py
@@ -1,8 +1,6 @@
-from fontTools.misc.py23 import *
-from fontTools import ttLib
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
-from fontTools.ttLib import TTLibError
from . import DefaultTable
import array
import itertools
diff --git a/Lib/fontTools/ttLib/tables/_h_d_m_x.py b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
index db5d9d8a..954d1bc1 100644
--- a/Lib/fontTools/ttLib/tables/_h_d_m_x.py
+++ b/Lib/fontTools/ttLib/tables/_h_d_m_x.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, strjoin
from fontTools.misc import sstruct
from . import DefaultTable
import array
diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
index 154669ae..4d19da03 100644
--- a/Lib/fontTools/ttLib/tables/_h_e_a_d.py
+++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import floatToFixedToStr, strToFixedToFloat
from fontTools.misc.textTools import safeEval, num2binary, binary2num
diff --git a/Lib/fontTools/ttLib/tables/_h_h_e_a.py b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
index 4d93b28a..9b8baaad 100644
--- a/Lib/fontTools/ttLib/tables/_h_h_e_a.py
+++ b/Lib/fontTools/ttLib/tables/_h_h_e_a.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.fixedTools import (
diff --git a/Lib/fontTools/ttLib/tables/_h_m_t_x.py b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
index a690a6e6..6980b8d8 100644
--- a/Lib/fontTools/ttLib/tables/_h_m_t_x.py
+++ b/Lib/fontTools/ttLib/tables/_h_m_t_x.py
@@ -1,5 +1,4 @@
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/_k_e_r_n.py b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
index 39e65923..f3f714b2 100644
--- a/Lib/fontTools/ttLib/tables/_k_e_r_n.py
+++ b/Lib/fontTools/ttLib/tables/_k_e_r_n.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.ttLib import getSearchRange
from fontTools.misc.textTools import safeEval, readHex
from fontTools.misc.fixedTools import (
diff --git a/Lib/fontTools/ttLib/tables/_l_c_a_r.py b/Lib/fontTools/ttLib/tables/_l_c_a_r.py
index a3ee0dd2..e63310ef 100644
--- a/Lib/fontTools/ttLib/tables/_l_c_a_r.py
+++ b/Lib/fontTools/ttLib/tables/_l_c_a_r.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_l_o_c_a.py b/Lib/fontTools/ttLib/tables/_l_o_c_a.py
index df61fafd..6a8693ed 100644
--- a/Lib/fontTools/ttLib/tables/_l_o_c_a.py
+++ b/Lib/fontTools/ttLib/tables/_l_o_c_a.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from . import DefaultTable
import sys
import array
diff --git a/Lib/fontTools/ttLib/tables/_l_t_a_g.py b/Lib/fontTools/ttLib/tables/_l_t_a_g.py
index 011c46be..caec72a3 100644
--- a/Lib/fontTools/ttLib/tables/_l_t_a_g.py
+++ b/Lib/fontTools/ttLib/tables/_l_t_a_g.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tobytes
from fontTools.misc.textTools import safeEval
from . import DefaultTable
import struct
diff --git a/Lib/fontTools/ttLib/tables/_m_a_x_p.py b/Lib/fontTools/ttLib/tables/_m_a_x_p.py
index fc1e69dd..e810806d 100644
--- a/Lib/fontTools/ttLib/tables/_m_a_x_p.py
+++ b/Lib/fontTools/ttLib/tables/_m_a_x_p.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/_m_e_t_a.py b/Lib/fontTools/ttLib/tables/_m_e_t_a.py
index 2cd479c9..1a125f82 100644
--- a/Lib/fontTools/ttLib/tables/_m_e_t_a.py
+++ b/Lib/fontTools/ttLib/tables/_m_e_t_a.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, strjoin
from fontTools.misc import sstruct
from fontTools.misc.textTools import readHex
from fontTools.ttLib import TTLibError
diff --git a/Lib/fontTools/ttLib/tables/_m_o_r_t.py b/Lib/fontTools/ttLib/tables/_m_o_r_t.py
index ea86ba8d..261e593e 100644
--- a/Lib/fontTools/ttLib/tables/_m_o_r_t.py
+++ b/Lib/fontTools/ttLib/tables/_m_o_r_t.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_m_o_r_x.py b/Lib/fontTools/ttLib/tables/_m_o_r_x.py
index e64e2b81..da299c6d 100644
--- a/Lib/fontTools/ttLib/tables/_m_o_r_x.py
+++ b/Lib/fontTools/ttLib/tables/_m_o_r_x.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_n_a_m_e.py b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
index 4bb9024b..206469de 100644
--- a/Lib/fontTools/ttLib/tables/_n_a_m_e.py
+++ b/Lib/fontTools/ttLib/tables/_n_a_m_e.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, strjoin, tobytes, tostr
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.encodingTools import getEncoding
@@ -133,7 +133,7 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
"""
if not hasattr(self, 'names'):
self.names = []
- if not isinstance(string, unicode):
+ if not isinstance(string, str):
if isinstance(string, bytes):
log.warning(
"name string is bytes, ensure it's correctly encoded: %r", string)
@@ -310,10 +310,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
"'platforms' must contain at least one (platformID, platEncID, langID) tuple"
if not hasattr(self, 'names'):
self.names = []
- if not isinstance(string, unicode):
+ if not isinstance(string, str):
raise TypeError(
- "expected %s, found %s: %r" % (
- unicode.__name__, type(string).__name__,string ))
+ "expected str, found %s: %r" % (type(string).__name__, string))
nameID = self._findUnusedNameID(minNameID + 1)
for platformID, platEncID, langID in platforms:
self.names.append(makeName(string, nameID, platformID, platEncID, langID))
@@ -457,7 +456,7 @@ class NameRecord(object):
elif byteord(string[0]) == 0 and all(isascii(byteord(b)) for b in string[1:]):
string = bytesjoin(b'\0'+bytechr(byteord(b)) for b in string[1:])
- string = tounicode(string, encoding=encoding, errors=errors)
+ string = tostr(string, encoding=encoding, errors=errors)
# If decoded strings still looks like UTF-16BE, it suggests a double-encoding.
# Fix it up.
diff --git a/Lib/fontTools/ttLib/tables/_o_p_b_d.py b/Lib/fontTools/ttLib/tables/_o_p_b_d.py
index a8768968..b22af216 100644
--- a/Lib/fontTools/ttLib/tables/_o_p_b_d.py
+++ b/Lib/fontTools/ttLib/tables/_o_p_b_d.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_p_o_s_t.py b/Lib/fontTools/ttLib/tables/_p_o_s_t.py
index 3dd3b77c..e26e81f8 100644
--- a/Lib/fontTools/ttLib/tables/_p_o_s_t.py
+++ b/Lib/fontTools/ttLib/tables/_p_o_s_t.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytechr, byteord, tobytes, tostr
from fontTools import ttLib
from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
from fontTools.misc import sstruct
diff --git a/Lib/fontTools/ttLib/tables/_p_r_e_p.py b/Lib/fontTools/ttLib/tables/_p_r_e_p.py
index b66ad1bf..7f517fb8 100644
--- a/Lib/fontTools/ttLib/tables/_p_r_e_p.py
+++ b/Lib/fontTools/ttLib/tables/_p_r_e_p.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("fpgm")
diff --git a/Lib/fontTools/ttLib/tables/_p_r_o_p.py b/Lib/fontTools/ttLib/tables/_p_r_o_p.py
index 7b4738a3..aead9d72 100644
--- a/Lib/fontTools/ttLib/tables/_p_r_o_p.py
+++ b/Lib/fontTools/ttLib/tables/_p_r_o_p.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
diff --git a/Lib/fontTools/ttLib/tables/_s_b_i_x.py b/Lib/fontTools/ttLib/tables/_s_b_i_x.py
index 0bfabca4..c4b2ad38 100644
--- a/Lib/fontTools/ttLib/tables/_s_b_i_x.py
+++ b/Lib/fontTools/ttLib/tables/_s_b_i_x.py
@@ -1,9 +1,7 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval, num2binary, binary2num
from . import DefaultTable
-from .sbixGlyph import *
-from .sbixStrike import *
+from .sbixStrike import Strike
sbixHeaderFormat = """
diff --git a/Lib/fontTools/ttLib/tables/_t_r_a_k.py b/Lib/fontTools/ttLib/tables/_t_r_a_k.py
index 7448916c..7f3227dc 100644
--- a/Lib/fontTools/ttLib/tables/_t_r_a_k.py
+++ b/Lib/fontTools/ttLib/tables/_t_r_a_k.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
diff --git a/Lib/fontTools/ttLib/tables/_v_h_e_a.py b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
index 55ba45a1..2bb24667 100644
--- a/Lib/fontTools/ttLib/tables/_v_h_e_a.py
+++ b/Lib/fontTools/ttLib/tables/_v_h_e_a.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from fontTools.misc.fixedTools import (
diff --git a/Lib/fontTools/ttLib/tables/_v_m_t_x.py b/Lib/fontTools/ttLib/tables/_v_m_t_x.py
index 761a4c9a..fc818d83 100644
--- a/Lib/fontTools/ttLib/tables/_v_m_t_x.py
+++ b/Lib/fontTools/ttLib/tables/_v_m_t_x.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools import ttLib
superclass = ttLib.getTableClass("hmtx")
diff --git a/Lib/fontTools/ttLib/tables/asciiTable.py b/Lib/fontTools/ttLib/tables/asciiTable.py
index 0ad83ebe..7b036c8e 100644
--- a/Lib/fontTools/ttLib/tables/asciiTable.py
+++ b/Lib/fontTools/ttLib/tables/asciiTable.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin, tobytes, tostr
from . import DefaultTable
diff --git a/Lib/fontTools/ttLib/tables/otBase.py b/Lib/fontTools/ttLib/tables/otBase.py
index 5ce32c17..3c07f9e1 100644
--- a/Lib/fontTools/ttLib/tables/otBase.py
+++ b/Lib/fontTools/ttLib/tables/otBase.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytesjoin
from .DefaultTable import DefaultTable
import sys
import array
@@ -654,9 +654,15 @@ class BaseTable(object):
def compile(self, writer, font):
self.ensureDecompiled()
+ # TODO Following hack to be removed by rewriting how FormatSwitching tables
+ # are handled.
+ # https://github.com/fonttools/fonttools/pull/2238#issuecomment-805192631
if hasattr(self, 'preWrite'):
+ deleteFormat = not hasattr(self, 'Format')
table = self.preWrite(font)
+ deleteFormat = deleteFormat and hasattr(self, 'Format')
else:
+ deleteFormat = False
table = self.__dict__.copy()
# some count references may have been initialized in a custom preWrite; we set
@@ -740,6 +746,9 @@ class BaseTable(object):
if conv.isPropagated:
writer[conv.name] = value
+ if deleteFormat:
+ del self.Format
+
def readFormat(self, reader):
pass
diff --git a/Lib/fontTools/ttLib/tables/otConverters.py b/Lib/fontTools/ttLib/tables/otConverters.py
index 96d461a3..4af38acd 100644
--- a/Lib/fontTools/ttLib/tables/otConverters.py
+++ b/Lib/fontTools/ttLib/tables/otConverters.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import bytesjoin, tobytes, tostr
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
floatToFixed as fl2fi,
@@ -338,6 +338,18 @@ class NameID(UShort):
log.warning("name id %d missing from name table" % value)
xmlWriter.newline()
+class STATFlags(UShort):
+ def xmlWrite(self, xmlWriter, font, value, name, attrs):
+ xmlWriter.simpletag(name, attrs + [("value", value)])
+ flags = []
+ if value & 0x01:
+ flags.append("OlderSiblingFontAttribute")
+ if value & 0x02:
+ flags.append("ElidableAxisValueName")
+ if flags:
+ xmlWriter.write(" ")
+ xmlWriter.comment(" ".join(flags))
+ xmlWriter.newline()
class FloatValue(SimpleValue):
@staticmethod
@@ -412,8 +424,8 @@ class Char64(SimpleValue):
zeroPos = data.find(b"\0")
if zeroPos >= 0:
data = data[:zeroPos]
- s = tounicode(data, encoding="ascii", errors="replace")
- if s != tounicode(data, encoding="ascii", errors="ignore"):
+ s = tostr(data, encoding="ascii", errors="replace")
+ if s != tostr(data, encoding="ascii", errors="ignore"):
log.warning('replaced non-ASCII characters in "%s"' %
s)
return s
@@ -1745,7 +1757,6 @@ converterMapping = {
"int8": Int8,
"int16": Short,
"uint8": UInt8,
- "uint8": UInt8,
"uint16": UShort,
"uint24": UInt24,
"uint32": ULong,
@@ -1770,6 +1781,7 @@ converterMapping = {
"LookupFlag": LookupFlag,
"ExtendMode": ExtendMode,
"CompositeMode": CompositeMode,
+ "STATFlags": STATFlags,
# AAT
"CIDGlyphMap": CIDGlyphMap,
diff --git a/Lib/fontTools/ttLib/tables/otData.py b/Lib/fontTools/ttLib/tables/otData.py
index 28b40c47..c4294169 100755
--- a/Lib/fontTools/ttLib/tables/otData.py
+++ b/Lib/fontTools/ttLib/tables/otData.py
@@ -1,6 +1,3 @@
-# coding: utf-8
-from fontTools.misc.py23 import *
-
otData = [
#
@@ -872,7 +869,7 @@ otData = [
('AxisValueFormat1', [
('uint16', 'Format', None, None, 'Format, = 1'),
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('Fixed', 'Value', None, None, ''),
]),
@@ -880,7 +877,7 @@ otData = [
('AxisValueFormat2', [
('uint16', 'Format', None, None, 'Format, = 2'),
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('Fixed', 'NominalValue', None, None, ''),
('Fixed', 'RangeMinValue', None, None, ''),
@@ -890,7 +887,7 @@ otData = [
('AxisValueFormat3', [
('uint16', 'Format', None, None, 'Format, = 3'),
('uint16', 'AxisIndex', None, None, 'Index into the axis record array identifying the axis of design variation to which the axis value record applies.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('Fixed', 'Value', None, None, ''),
('Fixed', 'LinkedValue', None, None, ''),
@@ -899,7 +896,7 @@ otData = [
('AxisValueFormat4', [
('uint16', 'Format', None, None, 'Format, = 4'),
('uint16', 'AxisCount', None, None, 'The total number of axes contributing to this axis-values combination.'),
- ('uint16', 'Flags', None, None, 'Flags.'),
+ ('STATFlags', 'Flags', None, None, 'Flags.'),
('NameID', 'ValueNameID', None, None, ''),
('struct', 'AxisValueRecord', 'AxisCount', 0, 'Array of AxisValue records that provide the combination of axis values, one for each contributing axis. '),
]),
diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index 008909bd..85befb3b 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -8,8 +8,8 @@ converter objects from otConverters.py.
from enum import IntEnum
import itertools
from collections import namedtuple
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.py23 import bytesjoin
+from fontTools.misc.roundTools import otRound
from fontTools.misc.textTools import pad, safeEval
from .otBase import (
BaseTable, FormatSwitchingBaseTable, ValueRecord, CountReference,
@@ -558,6 +558,7 @@ class Coverage(FormatSwitchingBaseTable):
else:
self.glyphs = []
log.warning("Unknown Coverage format: %s", self.Format)
+ del self.Format # Don't need this anymore
def preWrite(self, font):
glyphs = getattr(self, "glyphs", None)
@@ -739,6 +740,7 @@ class SingleSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.mapping = mapping
+ del self.Format # Don't need this anymore
def preWrite(self, font):
mapping = getattr(self, "mapping", None)
@@ -809,6 +811,7 @@ class MultipleSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.mapping = mapping
+ del self.Format # Don't need this anymore
def preWrite(self, font):
mapping = getattr(self, "mapping", None)
@@ -927,6 +930,7 @@ class ClassDef(FormatSwitchingBaseTable):
else:
log.warning("Unknown ClassDef format: %s", self.Format)
self.classDefs = classDefs
+ del self.Format # Don't need this anymore
def _getClassRanges(self, font):
classDefs = getattr(self, "classDefs", None)
@@ -1015,6 +1019,7 @@ class AlternateSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.alternates = alternates
+ del self.Format # Don't need this anymore
def preWrite(self, font):
self.Format = 1
@@ -1085,6 +1090,7 @@ class LigatureSubst(FormatSwitchingBaseTable):
else:
assert 0, "unknown format: %s" % self.Format
self.ligatures = ligatures
+ del self.Format # Don't need this anymore
def preWrite(self, font):
self.Format = 1
@@ -1681,7 +1687,6 @@ def splitMarkBasePos(oldSubTable, newSubTable, overflowRecord):
oldSubTable.MarkCoverage.glyphs = oldMarkCoverage
newSubTable.MarkCoverage = oldSubTable.MarkCoverage.__class__()
- newSubTable.MarkCoverage.Format = oldSubTable.MarkCoverage.Format
newSubTable.MarkCoverage.glyphs = newMarkCoverage
# share the same BaseCoverage in both halves
diff --git a/Lib/fontTools/ttLib/tables/sbixGlyph.py b/Lib/fontTools/ttLib/tables/sbixGlyph.py
index c27ecfe2..fe29c090 100644
--- a/Lib/fontTools/ttLib/tables/sbixGlyph.py
+++ b/Lib/fontTools/ttLib/tables/sbixGlyph.py
@@ -1,4 +1,3 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
from fontTools.misc.textTools import readHex, safeEval
import struct
diff --git a/Lib/fontTools/ttLib/tables/sbixStrike.py b/Lib/fontTools/ttLib/tables/sbixStrike.py
index 7e3e17e8..b367a99f 100644
--- a/Lib/fontTools/ttLib/tables/sbixStrike.py
+++ b/Lib/fontTools/ttLib/tables/sbixStrike.py
@@ -1,7 +1,6 @@
-from fontTools.misc.py23 import *
from fontTools.misc import sstruct
-from fontTools.misc.textTools import readHex
-from .sbixGlyph import *
+from fontTools.misc.textTools import safeEval
+from .sbixGlyph import Glyph
import struct
sbixStrikeHeaderFormat = """
diff --git a/Lib/fontTools/ttLib/tables/ttProgram.py b/Lib/fontTools/ttLib/tables/ttProgram.py
index 3094d62b..a1dfa3c5 100644
--- a/Lib/fontTools/ttLib/tables/ttProgram.py
+++ b/Lib/fontTools/ttLib/tables/ttProgram.py
@@ -1,8 +1,9 @@
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import strjoin
from fontTools.misc.textTools import num2binary, binary2num, readHex
import array
+from io import StringIO
import re
import logging
diff --git a/Lib/fontTools/ttLib/ttCollection.py b/Lib/fontTools/ttLib/ttCollection.py
index eb22ba76..3db4c8cd 100644
--- a/Lib/fontTools/ttLib/ttCollection.py
+++ b/Lib/fontTools/ttLib/ttCollection.py
@@ -1,6 +1,6 @@
-from fontTools.misc.py23 import *
from fontTools.ttLib.ttFont import TTFont
from fontTools.ttLib.sfnt import readTTCHeader, writeTTCHeader
+from io import BytesIO
import struct
import logging
@@ -104,7 +104,7 @@ class TTCollection(object):
return self.fonts[item]
def __setitem__(self, item, value):
- self.fonts[item] = values
+ self.fonts[item] = value
def __delitem__(self, item):
return self.fonts[item]
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py
index 811cf003..41a48751 100644
--- a/Lib/fontTools/ttLib/ttFont.py
+++ b/Lib/fontTools/ttLib/ttFont.py
@@ -1,11 +1,12 @@
from fontTools.misc import xmlWriter
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, byteord, tostr
from fontTools.misc.loggingTools import deprecateArgument
from fontTools.ttLib import TTLibError
from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
+from io import BytesIO, StringIO
import os
import logging
-import itertools
+import traceback
log = logging.getLogger(__name__)
@@ -161,22 +162,17 @@ class TTFont(object):
if self.lazy and self.reader.file.name == file:
raise TTLibError(
"Can't overwrite TTFont when 'lazy' attribute is True")
- closeStream = True
- file = open(file, "wb")
+ createStream = True
else:
# assume "file" is a writable file object
- closeStream = False
+ createStream = False
tmp = BytesIO()
writer_reordersTables = self._save(tmp)
- if (reorderTables is None or writer_reordersTables or
+ if not (reorderTables is None or writer_reordersTables or
(reorderTables is False and self.reader is None)):
- # don't reorder tables and save as is
- file.write(tmp.getvalue())
- tmp.close()
- else:
if reorderTables is False:
# sort tables using the original font's order
tableOrder = list(self.reader.keys())
@@ -186,12 +182,17 @@ class TTFont(object):
tmp.flush()
tmp2 = BytesIO()
reorderFontTables(tmp, tmp2, tableOrder)
- file.write(tmp2.getvalue())
tmp.close()
- tmp2.close()
+ tmp = tmp2
- if closeStream:
- file.close()
+ if createStream:
+ # "file" is a path
+ with open(file, "wb") as file:
+ file.write(tmp.getvalue())
+ else:
+ file.write(tmp.getvalue())
+
+ tmp.close()
def _save(self, file, tableCache=None):
"""Internal function, to be shared by save() and TTCollection.save()"""
@@ -368,45 +369,46 @@ class TTFont(object):
def __getitem__(self, tag):
tag = Tag(tag)
- try:
- return self.tables[tag]
- except KeyError:
+ table = self.tables.get(tag)
+ if table is None:
if tag == "GlyphOrder":
table = GlyphOrder(tag)
self.tables[tag] = table
- return table
- if self.reader is not None:
- import traceback
- log.debug("Reading '%s' table from disk", tag)
- data = self.reader[tag]
- if self._tableCache is not None:
- table = self._tableCache.get((Tag(tag), data))
- if table is not None:
- return table
- tableClass = getTableClass(tag)
- table = tableClass(tag)
- self.tables[tag] = table
- log.debug("Decompiling '%s' table", tag)
- try:
- table.decompile(data, self)
- except:
- if not self.ignoreDecompileErrors:
- raise
- # fall back to DefaultTable, retaining the binary table data
- log.exception(
- "An exception occurred during the decompilation of the '%s' table", tag)
- from .tables.DefaultTable import DefaultTable
- file = StringIO()
- traceback.print_exc(file=file)
- table = DefaultTable(tag)
- table.ERROR = file.getvalue()
- self.tables[tag] = table
- table.decompile(data, self)
- if self._tableCache is not None:
- self._tableCache[(Tag(tag), data)] = table
- return table
+ elif self.reader is not None:
+ table = self._readTable(tag)
else:
raise KeyError("'%s' table not found" % tag)
+ return table
+
+ def _readTable(self, tag):
+ log.debug("Reading '%s' table from disk", tag)
+ data = self.reader[tag]
+ if self._tableCache is not None:
+ table = self._tableCache.get((tag, data))
+ if table is not None:
+ return table
+ tableClass = getTableClass(tag)
+ table = tableClass(tag)
+ self.tables[tag] = table
+ log.debug("Decompiling '%s' table", tag)
+ try:
+ table.decompile(data, self)
+ except Exception:
+ if not self.ignoreDecompileErrors:
+ raise
+ # fall back to DefaultTable, retaining the binary table data
+ log.exception(
+ "An exception occurred during the decompilation of the '%s' table", tag)
+ from .tables.DefaultTable import DefaultTable
+ file = StringIO()
+ traceback.print_exc(file=file)
+ table = DefaultTable(tag)
+ table.ERROR = file.getvalue()
+ self.tables[tag] = table
+ table.decompile(data, self)
+ if self._tableCache is not None:
+ self._tableCache[(tag, data)] = table
+ return table
def __setitem__(self, tag, table):
self.tables[Tag(tag)] = table
@@ -636,7 +638,7 @@ class TTFont(object):
log.debug("reusing '%s' table", tag)
writer.setEntry(tag, entry)
return
- log.debug("writing '%s' table to disk", tag)
+ log.debug("Writing '%s' table to disk", tag)
writer[tag] = tabledata
if tableCache is not None:
tableCache[(Tag(tag), tabledata)] = writer[tag]
@@ -646,7 +648,7 @@ class TTFont(object):
"""
tag = Tag(tag)
if self.isLoaded(tag):
- log.debug("compiling '%s' table", tag)
+ log.debug("Compiling '%s' table", tag)
return self.tables[tag].compile(self)
elif self.reader and tag in self.reader:
log.debug("Reading '%s' table from disk", tag)
diff --git a/Lib/fontTools/ttLib/woff2.py b/Lib/fontTools/ttLib/woff2.py
index d088b70f..cc58afa5 100644
--- a/Lib/fontTools/ttLib/woff2.py
+++ b/Lib/fontTools/ttLib/woff2.py
@@ -1,4 +1,5 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, bytechr, byteord, bytesjoin
+from io import BytesIO
import sys
import array
import struct
diff --git a/Lib/fontTools/ttx.py b/Lib/fontTools/ttx.py
index 9522c625..2eed0c5c 100644
--- a/Lib/fontTools/ttx.py
+++ b/Lib/fontTools/ttx.py
@@ -86,7 +86,7 @@ usage: ttx [options] inputfile1 [... inputfileN]
"""
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import Tag, tostr
from fontTools.ttLib import TTFont, TTLibError
from fontTools.misc.macCreatorType import getMacCreatorAndType
from fontTools.unicode import setUnicodeData
diff --git a/Lib/fontTools/ufoLib/plistlib.py b/Lib/fontTools/ufoLib/plistlib.py
index cfea9a5e..76381687 100644
--- a/Lib/fontTools/ufoLib/plistlib.py
+++ b/Lib/fontTools/ufoLib/plistlib.py
@@ -2,7 +2,8 @@
for the old ufoLib.plistlib module, which was moved to fontTools.misc.plistlib.
Please use the latter instead.
"""
-from fontTools.misc.plistlib import dump, dumps, load, loads, tobytes
+from fontTools.misc.plistlib import dump, dumps, load, loads
+from fontTools.misc.py23 import tobytes
# The following functions were part of the old py2-like ufoLib.plistlib API.
# They are kept only for backward compatiblity.
diff --git a/Lib/fontTools/unicode.py b/Lib/fontTools/unicode.py
index 23589db7..e0867aa1 100644
--- a/Lib/fontTools/unicode.py
+++ b/Lib/fontTools/unicode.py
@@ -1,7 +1,4 @@
-from fontTools.misc.py23 import *
-
def _makeunicodes(f):
- import re
lines = iter(f.readlines())
unicodes = {}
for line in lines:
@@ -16,7 +13,7 @@ def _makeunicodes(f):
class _UnicodeCustom(object):
def __init__(self, f):
- if isinstance(f, basestring):
+ if isinstance(f, str):
with open(f) as fd:
codes = _makeunicodes(fd)
else:
@@ -39,7 +36,7 @@ class _UnicodeBuiltin(object):
except ImportError:
import unicodedata
try:
- return unicodedata.name(unichr(charCode))
+ return unicodedata.name(chr(charCode))
except ValueError:
return "????"
diff --git a/Lib/fontTools/unicodedata/__init__.py b/Lib/fontTools/unicodedata/__init__.py
index 404ad8b8..8845b829 100644
--- a/Lib/fontTools/unicodedata/__init__.py
+++ b/Lib/fontTools/unicodedata/__init__.py
@@ -1,4 +1,4 @@
-from fontTools.misc.py23 import *
+from fontTools.misc.py23 import byteord, tostr
import re
from bisect import bisect_right
@@ -50,7 +50,7 @@ def script(char):
'Latn'
>>> script(",")
'Zyyy'
- >>> script(unichr(0x10FFFF))
+ >>> script(chr(0x10FFFF))
'Zzzz'
"""
code = byteord(char)
@@ -73,9 +73,9 @@ def script_extension(char):
>>> script_extension("a") == {'Latn'}
True
- >>> script_extension(unichr(0x060C)) == {'Rohg', 'Syrc', 'Yezi', 'Arab', 'Thaa'}
+ >>> script_extension(chr(0x060C)) == {'Rohg', 'Syrc', 'Yezi', 'Arab', 'Thaa'}
True
- >>> script_extension(unichr(0x10FFFF)) == {'Zzzz'}
+ >>> script_extension(chr(0x10FFFF)) == {'Zzzz'}
True
"""
code = byteord(char)
@@ -219,9 +219,9 @@ def block(char):
>>> block("a")
'Basic Latin'
- >>> block(unichr(0x060C))
+ >>> block(chr(0x060C))
'Arabic'
- >>> block(unichr(0xEFFFF))
+ >>> block(chr(0xEFFFF))
'No_Block'
"""
code = byteord(char)
diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py
index 605fda2a..36ff0d97 100644
--- a/Lib/fontTools/varLib/__init__.py
+++ b/Lib/fontTools/varLib/__init__.py
@@ -18,9 +18,9 @@ Then you can make a variable-font this way:
API *will* change in near future.
"""
-from fontTools.misc.py23 import *
-from fontTools.misc.fixedTools import otRound
-from fontTools.misc.arrayTools import Vector
+from fontTools.misc.py23 import Tag, tostr
+from fontTools.misc.roundTools import noRound, otRound
+from fontTools.misc.vector import Vector
from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
@@ -34,6 +34,7 @@ from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.iup import iup_delta_optimize
from fontTools.varLib.featureVars import addFeatureVariations
from fontTools.designspaceLib import DesignSpaceDocument
+from functools import partial
from collections import OrderedDict, namedtuple
import os.path
import logging
@@ -90,7 +91,7 @@ def _add_fvar(font, axes, instances):
"stylename element with an 'xml:lang=\"en\"' attribute)."
)
localisedStyleName = dict(instance.localisedStyleName)
- localisedStyleName["en"] = tounicode(instance.styleName)
+ localisedStyleName["en"] = tostr(instance.styleName)
else:
localisedStyleName = instance.localisedStyleName
@@ -99,7 +100,7 @@ def _add_fvar(font, axes, instances):
inst = NamedInstance()
inst.subfamilyNameID = nameTable.addMultilingualName(localisedStyleName)
if psname is not None:
- psname = tounicode(psname)
+ psname = tostr(psname)
inst.postscriptNameID = nameTable.addName(psname)
inst.coordinates = {axes[k].tag:axes[k].map_backward(v) for k,v in coordinates.items()}
#inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()}
@@ -253,7 +254,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
# Update gvar
gvar.variations[glyph] = []
- deltas = model.getDeltas(allCoords)
+ deltas = model.getDeltas(allCoords, round=partial(GlyphCoordinates.__round__, round=round))
supports = model.supports
assert len(deltas) == len(supports)
@@ -262,7 +263,7 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
endPts = control.endPts
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
- if all(abs(v) <= tolerance for v in delta.array) and not isComposite:
+ if all(v == 0 for v in delta.array) and not isComposite:
continue
var = TupleVariation(support, delta)
if optimize:
@@ -304,7 +305,7 @@ def _remove_TTHinting(font):
font["glyf"].removeHinting()
# TODO: Modify gasp table to deactivate gridfitting for all ranges?
-def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
+def _merge_TTHinting(font, masterModel, master_ttfs):
log.info("Merging TT hinting")
assert "cvar" not in font
@@ -363,10 +364,9 @@ def _merge_TTHinting(font, masterModel, master_ttfs, tolerance=0.5):
return
variations = []
- deltas, supports = masterModel.getDeltasAndSupports(all_cvs)
+ deltas, supports = masterModel.getDeltasAndSupports(all_cvs, round=round) # builtin round calls into Vector.__round__, which uses builtin round as we like
for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
- delta = [otRound(d) for d in delta]
- if all(abs(v) <= tolerance for v in delta):
+ if all(v == 0 for v in delta):
continue
var = TupleVariation(support, delta)
variations.append(var)
@@ -441,7 +441,7 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
vOrigDeltasAndSupports = {}
for glyph in glyphOrder:
vhAdvances = [metrics[glyph][0] if glyph in metrics else None for metrics in advMetricses]
- vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances)
+ vhAdvanceDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vhAdvances, round=round)
singleModel = models.allEqual(id(v[1]) for v in vhAdvanceDeltasAndSupports.values())
@@ -453,7 +453,7 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
# glyphs which have a non-default vOrig.
vOrigs = [metrics[glyph] if glyph in metrics else defaultVOrig
for metrics, defaultVOrig in vOrigMetricses]
- vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs)
+ vOrigDeltasAndSupports[glyph] = masterModel.getDeltasAndSupports(vOrigs, round=round)
directStore = None
if singleModel:
@@ -463,7 +463,7 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
varTupleIndexes = list(range(len(supports)))
varData = builder.buildVarData(varTupleIndexes, [], optimize=False)
for glyphName in glyphOrder:
- varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0])
+ varData.addItem(vhAdvanceDeltasAndSupports[glyphName][0], round=noRound)
varData.optimize()
directStore = builder.buildVarStore(varTupleList, [varData])
@@ -473,14 +473,14 @@ def _get_advance_metrics(font, masterModel, master_ttfs,
for glyphName in glyphOrder:
deltas, supports = vhAdvanceDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports)
- advMapping[glyphName] = storeBuilder.storeDeltas(deltas)
+ advMapping[glyphName] = storeBuilder.storeDeltas(deltas, round=noRound)
if vOrigMetricses:
vOrigMap = {}
for glyphName in glyphOrder:
deltas, supports = vOrigDeltasAndSupports[glyphName]
storeBuilder.setSupports(supports)
- vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas)
+ vOrigMap[glyphName] = storeBuilder.storeDeltas(deltas, round=noRound)
indirectStore = storeBuilder.finish()
mapping2 = indirectStore.optimize()
@@ -751,7 +751,7 @@ def load_designspace(designspace):
if not axis.tag:
raise VarLibValidationError(f"Axis at index {axis_index} needs a tag.")
if not axis.labelNames:
- axis.labelNames["en"] = tounicode(axis_name)
+ axis.labelNames["en"] = tostr(axis_name)
axes[axis_name] = axis
log.info("Axes:\n%s", pformat([axis.asdict() for axis in axes.values()]))
diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py
index 0a6ba220..4eed8b33 100644
--- a/Lib/fontTools/varLib/cff.py
+++ b/Lib/fontTools/varLib/cff.py
@@ -17,10 +17,14 @@ from fontTools.cffLib.specializer import (
from fontTools.ttLib import newTable
from fontTools import varLib
from fontTools.varLib.models import allEqual
+from fontTools.misc.roundTools import roundFunc
from fontTools.misc.psCharStrings import T2CharString, T2OutlineExtractor
-from fontTools.pens.t2CharStringPen import T2CharStringPen, t2c_round
+from fontTools.pens.t2CharStringPen import T2CharStringPen
+from functools import partial
-from .errors import VarLibCFFDictMergeError, VarLibCFFPointTypeMergeError, VarLibMergeError
+from .errors import (
+ VarLibCFFDictMergeError, VarLibCFFPointTypeMergeError,
+ VarLibCFFHintTypeMergeError,VarLibMergeError)
# Backwards compatibility
@@ -422,16 +426,6 @@ def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel):
return cvData
-def makeRoundNumberFunc(tolerance):
- if tolerance < 0:
- raise ValueError("Rounding tolerance must be positive")
-
- def roundNumber(val):
- return t2c_round(val, tolerance)
-
- return roundNumber
-
-
class CFFToCFF2OutlineExtractor(T2OutlineExtractor):
""" This class is used to remove the initial width from the CFF
charstring without trying to add the width to self.nominalWidthX,
@@ -518,7 +512,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
self.prev_move_idx = 0
self.seen_moveto = False
self.glyphName = glyphName
- self.roundNumber = makeRoundNumberFunc(roundTolerance)
+ self.round = roundFunc(roundTolerance, round=round)
def add_point(self, point_type, pt_coords):
if self.m_index == 0:
@@ -539,7 +533,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
else:
cmd = self._commands[self.pt_index]
if cmd[0] != hint_type:
- raise VarLibCFFPointTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
+ raise VarLibCFFHintTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
cmd[1].append(args)
self.pt_index += 1
@@ -548,14 +542,14 @@ class CFF2CharStringMergePen(T2CharStringPen):
# For hintmask, fonttools.cffLib.specializer.py expects
# each of these to be represented by two sequential commands:
# first holding only the operator name, with an empty arg list,
- # second with an empty string as the op name, and the mask arg list.
+ # second with an empty string as the op name, and the mask arg list.
if self.m_index == 0:
self._commands.append([hint_type, []])
self._commands.append(["", [abs_args]])
else:
cmd = self._commands[self.pt_index]
if cmd[0] != hint_type:
- raise VarLibCFFPointTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
+ raise VarLibCFFHintTypeMergeError(hint_type, self.pt_index, len(cmd[1]),
cmd[0], self.glyphName)
self.pt_index += 1
cmd = self._commands[self.pt_index]
@@ -594,7 +588,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
def getCommands(self):
return self._commands
- def reorder_blend_args(self, commands, get_delta_func, round_func):
+ def reorder_blend_args(self, commands, get_delta_func):
"""
We first re-order the master coordinate values.
For a moveto to lineto, the args are now arranged as:
@@ -637,8 +631,6 @@ class CFF2CharStringMergePen(T2CharStringPen):
else:
# convert to deltas
deltas = get_delta_func(coord)[1:]
- if round_func:
- deltas = [round_func(delta) for delta in deltas]
coord = [coord[0]] + deltas
new_coords.append(coord)
cmd[1] = new_coords
@@ -649,8 +641,7 @@ class CFF2CharStringMergePen(T2CharStringPen):
self, private=None, globalSubrs=None,
var_model=None, optimize=True):
commands = self._commands
- commands = self.reorder_blend_args(commands, var_model.getDeltas,
- self.roundNumber)
+ commands = self.reorder_blend_args(commands, partial (var_model.getDeltas, round=self.round))
if optimize:
commands = specializeCommands(
commands, generalizeFirst=False,
diff --git a/Lib/fontTools/varLib/errors.py b/Lib/fontTools/varLib/errors.py
index b73f1886..5840070f 100644
--- a/Lib/fontTools/varLib/errors.py
+++ b/Lib/fontTools/varLib/errors.py
@@ -1,3 +1,6 @@
+import textwrap
+
+
class VarLibError(Exception):
"""Base exception for the varLib module."""
@@ -9,8 +12,144 @@ class VarLibValidationError(VarLibError):
class VarLibMergeError(VarLibError):
"""Raised when input data cannot be merged into a variable font."""
+ def __init__(self, merger, **kwargs):
+ self.merger = merger
+ if not kwargs:
+ kwargs = {}
+ if "stack" in kwargs:
+ self.stack = kwargs["stack"]
+ del kwargs["stack"]
+ else:
+ self.stack = []
+ self.cause = kwargs
+
+ @property
+ def reason(self):
+ return self.__doc__
+
+ def _master_name(self, ix):
+ ttf = self.merger.ttfs[ix]
+ if (
+ "name" in ttf
+ and ttf["name"].getDebugName(1)
+ and ttf["name"].getDebugName(2)
+ ):
+ return ttf["name"].getDebugName(1) + " " + ttf["name"].getDebugName(2)
+ elif hasattr(ttf.reader, "file") and hasattr(ttf.reader.file, "name"):
+ return ttf.reader.file.name
+ else:
+ return "master number %i" % ix
+
+ @property
+ def offender(self):
+ if "expected" in self.cause and "got" in self.cause:
+ index = [x == self.cause["expected"] for x in self.cause["got"]].index(
+ False
+ )
+ return index, self._master_name(index)
+ return None, None
+
+ @property
+ def details(self):
+ if "expected" in self.cause and "got" in self.cause:
+ offender_index, offender = self.offender
+ got = self.cause["got"][offender_index]
+ return f"Expected to see {self.stack[0]}=={self.cause['expected']}, instead saw {got}\n"
+ return ""
+
+ def __str__(self):
+ offender_index, offender = self.offender
+ location = ""
+ if offender:
+ location = f"\n\nThe problem is likely to be in {offender}:\n"
+ context = "".join(reversed(self.stack))
+ basic = textwrap.fill(
+ f"Couldn't merge the fonts, because {self.reason}. "
+ f"This happened while performing the following operation: {context}",
+ width=78,
+ )
+ return "\n\n" + basic + location + self.details
+
+
+class ShouldBeConstant(VarLibMergeError):
+ """some values were different, but should have been the same"""
+
+ @property
+ def details(self):
+ if self.stack[0] != ".FeatureCount":
+ return super().details
+ offender_index, offender = self.offender
+ bad_ttf = self.merger.ttfs[offender_index]
+ good_ttf = self.merger.ttfs[offender_index - 1]
+
+ good_features = [
+ x.FeatureTag
+ for x in good_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
+ ]
+ bad_features = [
+ x.FeatureTag
+ for x in bad_ttf[self.stack[-1]].table.FeatureList.FeatureRecord
+ ]
+ return (
+ "\nIncompatible features between masters.\n"
+ f"Expected: {', '.join(good_features)}.\n"
+ f"Got: {', '.join(bad_features)}.\n"
+ )
+
+
+class FoundANone(VarLibMergeError):
+ """one of the values in a list was empty when it shouldn't have been"""
+
+ @property
+ def offender(self):
+ cause = self.argv[0]
+ index = [x is None for x in cause["got"]].index(True)
+ return index, self._master_name(index)
+
+ @property
+ def details(self):
+ cause, stack = self.args[0], self.args[1:]
+ return f"{stack[0]}=={cause['got']}\n"
+
+
+class MismatchedTypes(VarLibMergeError):
+ """data had inconsistent types"""
+
+
+class LengthsDiffer(VarLibMergeError):
+ """a list of objects had inconsistent lengths"""
-class VarLibCFFDictMergeError(VarLibMergeError):
+
+class KeysDiffer(VarLibMergeError):
+ """a list of objects had different keys"""
+
+
+class InconsistentGlyphOrder(VarLibMergeError):
+ """the glyph order was inconsistent between masters"""
+
+
+class InconsistentExtensions(VarLibMergeError):
+ """the masters use extension lookups in inconsistent ways"""
+
+
+class UnsupportedFormat(VarLibMergeError):
+ """an OpenType subtable (%s) had a format I didn't expect"""
+
+ @property
+ def reason(self):
+ cause, stack = self.args[0], self.args[1:]
+ return self.__doc__ % cause["subtable"]
+
+
+class UnsupportedFormat(UnsupportedFormat):
+ """an OpenType subtable (%s) had inconsistent formats between masters"""
+
+
+class VarLibCFFMergeError(VarLibError):
+ pass
+
+
+class VarLibCFFDictMergeError(VarLibCFFMergeError):
"""Raised when a CFF PrivateDict cannot be merged."""
def __init__(self, key, value, values):
@@ -23,8 +162,8 @@ class VarLibCFFDictMergeError(VarLibMergeError):
self.args = (error_msg,)
-class VarLibCFFPointTypeMergeError(VarLibMergeError):
- """Raised when a CFF glyph cannot be merged."""
+class VarLibCFFPointTypeMergeError(VarLibCFFMergeError):
+ """Raised when a CFF glyph cannot be merged because of point type differences."""
def __init__(self, point_type, pt_index, m_index, default_type, glyph_name):
error_msg = (
@@ -35,5 +174,17 @@ class VarLibCFFPointTypeMergeError(VarLibMergeError):
self.args = (error_msg,)
+class VarLibCFFHintTypeMergeError(VarLibCFFMergeError):
+ """Raised when a CFF glyph cannot be merged because of hint type differences."""
+
+ def __init__(self, hint_type, cmd_index, m_index, default_type, glyph_name):
+ error_msg = (
+ f"Glyph '{glyph_name}': '{hint_type}' at index {cmd_index} in "
+ f"master index {m_index} differs from the default font hint type "
+ f"'{default_type}'"
+ )
+ self.args = (error_msg,)
+
+
class VariationModelError(VarLibError):
"""Raised when a variation model is faulty."""
diff --git a/Lib/fontTools/varLib/instancer.py b/Lib/fontTools/varLib/instancer/__init__.py
index fba17842..9bd30f19 100644
--- a/Lib/fontTools/varLib/instancer.py
+++ b/Lib/fontTools/varLib/instancer/__init__.py
@@ -84,6 +84,7 @@ from fontTools import subset # noqa: F401
from fontTools.varLib import builder
from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.merger import MutatorMerger
+from fontTools.varLib.instancer import names
from contextlib import contextmanager
import collections
from copy import deepcopy
@@ -1008,6 +1009,13 @@ def instantiateSTAT(varfont, axisLimits):
):
return # STAT table empty, nothing to do
+ log.info("Instantiating STAT table")
+ newAxisValueTables = axisValuesFromAxisLimits(stat, axisLimits)
+ stat.AxisValueArray.AxisValue = newAxisValueTables
+ stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
+
+
+def axisValuesFromAxisLimits(stat, axisLimits):
location, axisRanges = splitAxisLocationAndRanges(axisLimits, rangeType=AxisRange)
def isAxisValueOutsideLimits(axisTag, axisValue):
@@ -1019,8 +1027,6 @@ def instantiateSTAT(varfont, axisLimits):
return True
return False
- log.info("Instantiating STAT table")
-
# only keep AxisValues whose axis is not pinned nor restricted, or is pinned at the
# exact (nominal) value, or is restricted but the value is within the new range
designAxes = stat.DesignAxisRecord.Axis
@@ -1048,55 +1054,9 @@ def instantiateSTAT(varfont, axisLimits):
if dropAxisValueTable:
continue
else:
- log.warn("Unknown AxisValue table format (%s); ignored", axisValueFormat)
+ log.warning("Unknown AxisValue table format (%s); ignored", axisValueFormat)
newAxisValueTables.append(axisValueTable)
-
- stat.AxisValueArray.AxisValue = newAxisValueTables
- stat.AxisValueCount = len(stat.AxisValueArray.AxisValue)
-
-
-def getVariationNameIDs(varfont):
- used = []
- if "fvar" in varfont:
- fvar = varfont["fvar"]
- for axis in fvar.axes:
- used.append(axis.axisNameID)
- for instance in fvar.instances:
- used.append(instance.subfamilyNameID)
- if instance.postscriptNameID != 0xFFFF:
- used.append(instance.postscriptNameID)
- if "STAT" in varfont:
- stat = varfont["STAT"].table
- for axis in stat.DesignAxisRecord.Axis if stat.DesignAxisRecord else ():
- used.append(axis.AxisNameID)
- for value in stat.AxisValueArray.AxisValue if stat.AxisValueArray else ():
- used.append(value.ValueNameID)
- # nameIDs <= 255 are reserved by OT spec so we don't touch them
- return {nameID for nameID in used if nameID > 255}
-
-
-@contextmanager
-def pruningUnusedNames(varfont):
- origNameIDs = getVariationNameIDs(varfont)
-
- yield
-
- log.info("Pruning name table")
- exclude = origNameIDs - getVariationNameIDs(varfont)
- varfont["name"].names[:] = [
- record for record in varfont["name"].names if record.nameID not in exclude
- ]
- if "ltag" in varfont:
- # Drop the whole 'ltag' table if all the language-dependent Unicode name
- # records that reference it have been dropped.
- # TODO: Only prune unused ltag tags, renumerating langIDs accordingly.
- # Note ltag can also be used by feat or morx tables, so check those too.
- if not any(
- record
- for record in varfont["name"].names
- if record.platformID == 0 and record.langID != 0xFFFF
- ):
- del varfont["ltag"]
+ return newAxisValueTables
def setMacOverlapFlags(glyfTable):
@@ -1187,6 +1147,7 @@ def instantiateVariableFont(
inplace=False,
optimize=True,
overlap=OverlapMode.KEEP_AND_SET_FLAGS,
+ updateFontNames=False,
):
"""Instantiate variable font, either fully or partially.
@@ -1219,6 +1180,11 @@ def instantiateVariableFont(
contours and components, you can pass OverlapMode.REMOVE. Note that this
requires the skia-pathops package (available to pip install).
The overlap parameter only has effect when generating full static instances.
+ updateFontNames (bool): if True, update the instantiated font's name table using
+ the Axis Value Tables from the STAT table. The name table will be updated so
+ it conforms to the R/I/B/BI model. If the STAT table is missing or
+ an Axis Value table is missing for a given axis coordinate, a ValueError will
+ be raised.
"""
# 'overlap' used to be bool and is now enum; for backward compat keep accepting bool
overlap = OverlapMode(int(overlap))
@@ -1234,6 +1200,10 @@ def instantiateVariableFont(
if not inplace:
varfont = deepcopy(varfont)
+ if updateFontNames:
+ log.info("Updating name table")
+ names.updateNameTable(varfont, axisLimits)
+
if "gvar" in varfont:
instantiateGvar(varfont, normalizedLimits, optimize=optimize)
@@ -1256,7 +1226,7 @@ def instantiateVariableFont(
if "avar" in varfont:
instantiateAvar(varfont, axisLimits)
- with pruningUnusedNames(varfont):
+ with names.pruningUnusedNames(varfont):
if "STAT" in varfont:
instantiateSTAT(varfont, axisLimits)
@@ -1345,7 +1315,7 @@ def parseArgs(args):
"locargs",
metavar="AXIS=LOC",
nargs="*",
- help="List of space separated locations. A location consist in "
+ help="List of space separated locations. A location consists of "
"the tag of a variation axis, followed by '=' and one of number, "
"number:number or the literal string 'drop'. "
"E.g.: wdth=100 or wght=75.0:125.0 or wght=drop",
@@ -1377,6 +1347,12 @@ def parseArgs(args):
help="Merge overlapping contours and components (only applicable "
"when generating a full instance). Requires skia-pathops",
)
+ parser.add_argument(
+ "--update-name-table",
+ action="store_true",
+ help="Update the instantiated font's `name` table. Input font must have "
+ "a STAT table with Axis Value Tables",
+ )
loggingGroup = parser.add_mutually_exclusive_group(required=False)
loggingGroup.add_argument(
"-v", "--verbose", action="store_true", help="Run more verbosely."
@@ -1428,6 +1404,7 @@ def main(args=None):
inplace=True,
optimize=options.optimize,
overlap=options.overlap,
+ updateFontNames=options.update_name_table,
)
outfile = (
@@ -1443,9 +1420,3 @@ def main(args=None):
outfile,
)
varfont.save(outfile)
-
-
-if __name__ == "__main__":
- import sys
-
- sys.exit(main())
diff --git a/Lib/fontTools/varLib/instancer/__main__.py b/Lib/fontTools/varLib/instancer/__main__.py
new file mode 100644
index 00000000..64ffff2b
--- /dev/null
+++ b/Lib/fontTools/varLib/instancer/__main__.py
@@ -0,0 +1,5 @@
+import sys
+from fontTools.varLib.instancer import main
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/Lib/fontTools/varLib/instancer/names.py b/Lib/fontTools/varLib/instancer/names.py
new file mode 100644
index 00000000..cfe12a94
--- /dev/null
+++ b/Lib/fontTools/varLib/instancer/names.py
@@ -0,0 +1,379 @@
+"""Helpers for instantiating name table records."""
+
+from contextlib import contextmanager
+from copy import deepcopy
+from enum import IntEnum
+import re
+
+
+class NameID(IntEnum):
+ FAMILY_NAME = 1
+ SUBFAMILY_NAME = 2
+ UNIQUE_FONT_IDENTIFIER = 3
+ FULL_FONT_NAME = 4
+ VERSION_STRING = 5
+ POSTSCRIPT_NAME = 6
+ TYPOGRAPHIC_FAMILY_NAME = 16
+ TYPOGRAPHIC_SUBFAMILY_NAME = 17
+ VARIATIONS_POSTSCRIPT_NAME_PREFIX = 25
+
+
+ELIDABLE_AXIS_VALUE_NAME = 2
+
+
+def getVariationNameIDs(varfont):
+ used = []
+ if "fvar" in varfont:
+ fvar = varfont["fvar"]
+ for axis in fvar.axes:
+ used.append(axis.axisNameID)
+ for instance in fvar.instances:
+ used.append(instance.subfamilyNameID)
+ if instance.postscriptNameID != 0xFFFF:
+ used.append(instance.postscriptNameID)
+ if "STAT" in varfont:
+ stat = varfont["STAT"].table
+ for axis in stat.DesignAxisRecord.Axis if stat.DesignAxisRecord else ():
+ used.append(axis.AxisNameID)
+ for value in stat.AxisValueArray.AxisValue if stat.AxisValueArray else ():
+ used.append(value.ValueNameID)
+ # nameIDs <= 255 are reserved by OT spec so we don't touch them
+ return {nameID for nameID in used if nameID > 255}
+
+
+@contextmanager
+def pruningUnusedNames(varfont):
+ from . import log
+
+ origNameIDs = getVariationNameIDs(varfont)
+
+ yield
+
+ log.info("Pruning name table")
+ exclude = origNameIDs - getVariationNameIDs(varfont)
+ varfont["name"].names[:] = [
+ record for record in varfont["name"].names if record.nameID not in exclude
+ ]
+ if "ltag" in varfont:
+ # Drop the whole 'ltag' table if all the language-dependent Unicode name
+ # records that reference it have been dropped.
+ # TODO: Only prune unused ltag tags, renumerating langIDs accordingly.
+ # Note ltag can also be used by feat or morx tables, so check those too.
+ if not any(
+ record
+ for record in varfont["name"].names
+ if record.platformID == 0 and record.langID != 0xFFFF
+ ):
+ del varfont["ltag"]
+
+
+def updateNameTable(varfont, axisLimits):
+ """Update instatiated variable font's name table using STAT AxisValues.
+
+ Raises ValueError if the STAT table is missing or an Axis Value table is
+ missing for requested axis locations.
+
+ First, collect all STAT AxisValues that match the new default axis locations
+ (excluding "elided" ones); concatenate the strings in design axis order,
+ while giving priority to "synthetic" values (Format 4), to form the
+ typographic subfamily name associated with the new default instance.
+ Finally, update all related records in the name table, making sure that
+ legacy family/sub-family names conform to the the R/I/B/BI (Regular, Italic,
+ Bold, Bold Italic) naming model.
+
+ Example: Updating a partial variable font:
+ | >>> ttFont = TTFont("OpenSans[wdth,wght].ttf")
+ | >>> updateNameTable(ttFont, {"wght": AxisRange(400, 900), "wdth": 75})
+
+ The name table records will be updated in the following manner:
+ NameID 1 familyName: "Open Sans" --> "Open Sans Condensed"
+ NameID 2 subFamilyName: "Regular" --> "Regular"
+ NameID 3 Unique font identifier: "3.000;GOOG;OpenSans-Regular" --> \
+ "3.000;GOOG;OpenSans-Condensed"
+ NameID 4 Full font name: "Open Sans Regular" --> "Open Sans Condensed"
+ NameID 6 PostScript name: "OpenSans-Regular" --> "OpenSans-Condensed"
+ NameID 16 Typographic Family name: None --> "Open Sans"
+ NameID 17 Typographic Subfamily name: None --> "Condensed"
+
+ References:
+ https://docs.microsoft.com/en-us/typography/opentype/spec/stat
+ https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
+ """
+ from . import AxisRange, axisValuesFromAxisLimits
+
+ if "STAT" not in varfont:
+ raise ValueError("Cannot update name table since there is no STAT table.")
+ stat = varfont["STAT"].table
+ if not stat.AxisValueArray:
+ raise ValueError("Cannot update name table since there are no STAT Axis Values")
+ fvar = varfont["fvar"]
+
+ # The updated name table will reflect the new 'zero origin' of the font.
+ # If we're instantiating a partial font, we will populate the unpinned
+ # axes with their default axis values.
+ fvarDefaults = {a.axisTag: a.defaultValue for a in fvar.axes}
+ defaultAxisCoords = deepcopy(axisLimits)
+ for axisTag, val in fvarDefaults.items():
+ if axisTag not in defaultAxisCoords or isinstance(
+ defaultAxisCoords[axisTag], AxisRange
+ ):
+ defaultAxisCoords[axisTag] = val
+
+ axisValueTables = axisValuesFromAxisLimits(stat, defaultAxisCoords)
+ checkAxisValuesExist(stat, axisValueTables, defaultAxisCoords)
+
+ # ignore "elidable" axis values, should be omitted in application font menus.
+ axisValueTables = [
+ v for v in axisValueTables if not v.Flags & ELIDABLE_AXIS_VALUE_NAME
+ ]
+ axisValueTables = _sortAxisValues(axisValueTables)
+ _updateNameRecords(varfont, axisValueTables)
+
+
+def checkAxisValuesExist(stat, axisValues, axisCoords):
+ seen = set()
+ designAxes = stat.DesignAxisRecord.Axis
+ for axisValueTable in axisValues:
+ axisValueFormat = axisValueTable.Format
+ if axisValueTable.Format in (1, 2, 3):
+ axisTag = designAxes[axisValueTable.AxisIndex].AxisTag
+ if axisValueFormat == 2:
+ axisValue = axisValueTable.NominalValue
+ else:
+ axisValue = axisValueTable.Value
+ if axisTag in axisCoords and axisValue == axisCoords[axisTag]:
+ seen.add(axisTag)
+ elif axisValueTable.Format == 4:
+ for rec in axisValueTable.AxisValueRecord:
+ axisTag = designAxes[rec.AxisIndex].AxisTag
+ if axisTag in axisCoords and rec.Value == axisCoords[axisTag]:
+ seen.add(axisTag)
+
+ missingAxes = set(axisCoords) - seen
+ if missingAxes:
+ missing = ", ".join(f"'{i}={axisCoords[i]}'" for i in missingAxes)
+ raise ValueError(f"Cannot find Axis Values [{missing}]")
+
+
+def _sortAxisValues(axisValues):
+ # Sort by axis index, remove duplicates and ensure that format 4 AxisValues
+ # are dominant.
+ # The MS Spec states: "if a format 1, format 2 or format 3 table has a
+ # (nominal) value used in a format 4 table that also has values for
+ # other axes, the format 4 table, being the more specific match, is used",
+ # https://docs.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4
+ results = []
+ seenAxes = set()
+ # Sort format 4 axes so the tables with the most AxisValueRecords are first
+ format4 = sorted(
+ [v for v in axisValues if v.Format == 4],
+ key=lambda v: len(v.AxisValueRecord),
+ reverse=True,
+ )
+
+ for val in format4:
+ axisIndexes = set(r.AxisIndex for r in val.AxisValueRecord)
+ minIndex = min(axisIndexes)
+ if not seenAxes & axisIndexes:
+ seenAxes |= axisIndexes
+ results.append((minIndex, val))
+
+ for val in axisValues:
+ if val in format4:
+ continue
+ axisIndex = val.AxisIndex
+ if axisIndex not in seenAxes:
+ seenAxes.add(axisIndex)
+ results.append((axisIndex, val))
+
+ return [axisValue for _, axisValue in sorted(results)]
+
+
+def _updateNameRecords(varfont, axisValues):
+ # Update nametable based on the axisValues using the R/I/B/BI model.
+ nametable = varfont["name"]
+ stat = varfont["STAT"].table
+
+ axisValueNameIDs = [a.ValueNameID for a in axisValues]
+ ribbiNameIDs = [n for n in axisValueNameIDs if _isRibbi(nametable, n)]
+ nonRibbiNameIDs = [n for n in axisValueNameIDs if n not in ribbiNameIDs]
+ elidedNameID = stat.ElidedFallbackNameID
+ elidedNameIsRibbi = _isRibbi(nametable, elidedNameID)
+
+ getName = nametable.getName
+ platforms = set((r.platformID, r.platEncID, r.langID) for r in nametable.names)
+ for platform in platforms:
+ if not all(getName(i, *platform) for i in (1, 2, elidedNameID)):
+ # Since no family name and subfamily name records were found,
+ # we cannot update this set of name Records.
+ continue
+
+ subFamilyName = " ".join(
+ getName(n, *platform).toUnicode() for n in ribbiNameIDs
+ )
+ if nonRibbiNameIDs:
+ typoSubFamilyName = " ".join(
+ getName(n, *platform).toUnicode() for n in axisValueNameIDs
+ )
+ else:
+ typoSubFamilyName = None
+
+ # If neither subFamilyName and typographic SubFamilyName exist,
+ # we will use the STAT's elidedFallbackName
+ if not typoSubFamilyName and not subFamilyName:
+ if elidedNameIsRibbi:
+ subFamilyName = getName(elidedNameID, *platform).toUnicode()
+ else:
+ typoSubFamilyName = getName(elidedNameID, *platform).toUnicode()
+
+ familyNameSuffix = " ".join(
+ getName(n, *platform).toUnicode() for n in nonRibbiNameIDs
+ )
+
+ _updateNameTableStyleRecords(
+ varfont,
+ familyNameSuffix,
+ subFamilyName,
+ typoSubFamilyName,
+ *platform,
+ )
+
+
+def _isRibbi(nametable, nameID):
+ englishRecord = nametable.getName(nameID, 3, 1, 0x409)
+ return (
+ True
+ if englishRecord is not None
+ and englishRecord.toUnicode() in ("Regular", "Italic", "Bold", "Bold Italic")
+ else False
+ )
+
+
+def _updateNameTableStyleRecords(
+ varfont,
+ familyNameSuffix,
+ subFamilyName,
+ typoSubFamilyName,
+ platformID=3,
+ platEncID=1,
+ langID=0x409,
+):
+ # TODO (Marc F) It may be nice to make this part a standalone
+ # font renamer in the future.
+ nametable = varfont["name"]
+ platform = (platformID, platEncID, langID)
+
+ currentFamilyName = nametable.getName(
+ NameID.TYPOGRAPHIC_FAMILY_NAME, *platform
+ ) or nametable.getName(NameID.FAMILY_NAME, *platform)
+
+ currentStyleName = nametable.getName(
+ NameID.TYPOGRAPHIC_SUBFAMILY_NAME, *platform
+ ) or nametable.getName(NameID.SUBFAMILY_NAME, *platform)
+
+ if not all([currentFamilyName, currentStyleName]):
+ raise ValueError(f"Missing required NameIDs 1 and 2 for platform {platform}")
+
+ currentFamilyName = currentFamilyName.toUnicode()
+ currentStyleName = currentStyleName.toUnicode()
+
+ nameIDs = {
+ NameID.FAMILY_NAME: currentFamilyName,
+ NameID.SUBFAMILY_NAME: subFamilyName or "Regular",
+ }
+ if typoSubFamilyName:
+ nameIDs[NameID.FAMILY_NAME] = f"{currentFamilyName} {familyNameSuffix}".strip()
+ nameIDs[NameID.TYPOGRAPHIC_FAMILY_NAME] = currentFamilyName
+ nameIDs[NameID.TYPOGRAPHIC_SUBFAMILY_NAME] = typoSubFamilyName
+ else:
+ # Remove previous Typographic Family and SubFamily names since they're
+ # no longer required
+ for nameID in (
+ NameID.TYPOGRAPHIC_FAMILY_NAME,
+ NameID.TYPOGRAPHIC_SUBFAMILY_NAME,
+ ):
+ nametable.removeNames(nameID=nameID)
+
+ newFamilyName = (
+ nameIDs.get(NameID.TYPOGRAPHIC_FAMILY_NAME) or nameIDs[NameID.FAMILY_NAME]
+ )
+ newStyleName = (
+ nameIDs.get(NameID.TYPOGRAPHIC_SUBFAMILY_NAME) or nameIDs[NameID.SUBFAMILY_NAME]
+ )
+
+ nameIDs[NameID.FULL_FONT_NAME] = f"{newFamilyName} {newStyleName}"
+ nameIDs[NameID.POSTSCRIPT_NAME] = _updatePSNameRecord(
+ varfont, newFamilyName, newStyleName, platform
+ )
+
+ uniqueID = _updateUniqueIdNameRecord(varfont, nameIDs, platform)
+ if uniqueID:
+ nameIDs[NameID.UNIQUE_FONT_IDENTIFIER] = uniqueID
+
+ for nameID, string in nameIDs.items():
+ assert string, nameID
+ nametable.setName(string, nameID, *platform)
+
+ if "fvar" not in varfont:
+ nametable.removeNames(NameID.VARIATIONS_POSTSCRIPT_NAME_PREFIX)
+
+
+def _updatePSNameRecord(varfont, familyName, styleName, platform):
+ # Implementation based on Adobe Technical Note #5902 :
+ # https://wwwimages2.adobe.com/content/dam/acom/en/devnet/font/pdfs/5902.AdobePSNameGeneration.pdf
+ nametable = varfont["name"]
+
+ family_prefix = nametable.getName(
+ NameID.VARIATIONS_POSTSCRIPT_NAME_PREFIX, *platform
+ )
+ if family_prefix:
+ family_prefix = family_prefix.toUnicode()
+ else:
+ family_prefix = familyName
+
+ psName = f"{family_prefix}-{styleName}"
+ # Remove any characters other than uppercase Latin letters, lowercase
+ # Latin letters, digits and hyphens.
+ psName = re.sub(r"[^A-Za-z0-9-]", r"", psName)
+
+ if len(psName) > 127:
+ # Abbreviating the stylename so it fits within 127 characters whilst
+ # conforming to every vendor's specification is too complex. Instead
+ # we simply truncate the psname and add the required "..."
+ return f"{psName[:124]}..."
+ return psName
+
+
+def _updateUniqueIdNameRecord(varfont, nameIDs, platform):
+ nametable = varfont["name"]
+ currentRecord = nametable.getName(NameID.UNIQUE_FONT_IDENTIFIER, *platform)
+ if not currentRecord:
+ return None
+
+ # Check if full name and postscript name are a substring of currentRecord
+ for nameID in (NameID.FULL_FONT_NAME, NameID.POSTSCRIPT_NAME):
+ nameRecord = nametable.getName(nameID, *platform)
+ if not nameRecord:
+ continue
+ if nameRecord.toUnicode() in currentRecord.toUnicode():
+ return currentRecord.toUnicode().replace(
+ nameRecord.toUnicode(), nameIDs[nameRecord.nameID]
+ )
+
+ # Create a new string since we couldn't find any substrings.
+ fontVersion = _fontVersion(varfont, platform)
+ achVendID = varfont["OS/2"].achVendID
+ # Remove non-ASCII characers and trailing spaces
+ vendor = re.sub(r"[^\x00-\x7F]", "", achVendID).strip()
+ psName = nameIDs[NameID.POSTSCRIPT_NAME]
+ return f"{fontVersion};{vendor};{psName}"
+
+
+def _fontVersion(font, platform=(3, 1, 0x409)):
+ nameRecord = font["name"].getName(NameID.VERSION_STRING, *platform)
+ if nameRecord is None:
+ return f'{font["head"].fontRevision:.3f}'
+ # "Version 1.101; ttfautohint (v1.8.1.43-b0c9)" --> "1.101"
+ # Also works fine with inputs "Version 1.101" or "1.101" etc
+ versionNumber = nameRecord.toUnicode().split(";")[0]
+ return versionNumber.lstrip("Version ").strip()
diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py
index 071942b8..c9d14381 100644
--- a/Lib/fontTools/varLib/merger.py
+++ b/Lib/fontTools/varLib/merger.py
@@ -3,8 +3,8 @@ Merge OpenType Layout tables (GDEF / GPOS / GSUB).
"""
import copy
from operator import ior
-from fontTools.misc.fixedTools import otRound
from fontTools.misc import classifyTools
+from fontTools.misc.roundTools import otRound
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables import otBase as otBase
from fontTools.ttLib.tables.DefaultTable import DefaultTable
@@ -14,8 +14,18 @@ from fontTools.varLib.varStore import VarStoreInstancer
from functools import reduce
from fontTools.otlLib.builder import buildSinglePos
-from .errors import VarLibMergeError
-
+from .errors import (
+ ShouldBeConstant,
+ FoundANone,
+ MismatchedTypes,
+ LengthsDiffer,
+ KeysDiffer,
+ InconsistentGlyphOrder,
+ InconsistentExtensions,
+ UnsupportedFormat,
+ UnsupportedFormat,
+ VarLibMergeError,
+)
class Merger(object):
@@ -69,7 +79,9 @@ class Merger(object):
item.ensureDecompiled()
keys = sorted(vars(out).keys())
if not all(keys == sorted(vars(v).keys()) for v in lst):
- raise VarLibMergeError((keys, [sorted(vars(v).keys()) for v in lst]))
+ raise KeysDiffer(self, expected=keys,
+ got=[sorted(vars(v).keys()) for v in lst]
+ )
mergers = self.mergersFor(out)
defaultMerger = mergers.get('*', self.__class__.mergeThings)
try:
@@ -79,44 +91,47 @@ class Merger(object):
values = [getattr(table, key) for table in lst]
mergerFunc = mergers.get(key, defaultMerger)
mergerFunc(self, value, values)
- except Exception as e:
- e.args = e.args + ('.'+key,)
+ except VarLibMergeError as e:
+ e.stack.append('.'+key)
raise
def mergeLists(self, out, lst):
if not allEqualTo(out, lst, len):
- raise VarLibMergeError((len(out), [len(v) for v in lst]))
+ raise LengthsDiffer(self, expected=len(out), got=[len(x) for x in lst])
for i,(value,values) in enumerate(zip(out, zip(*lst))):
try:
self.mergeThings(value, values)
- except Exception as e:
- e.args = e.args + ('[%d]' % i,)
+ except VarLibMergeError as e:
+ e.stack.append('[%d]' % i)
raise
def mergeThings(self, out, lst):
- try:
- if not allEqualTo(out, lst, type):
- raise VarLibMergeError((out, lst))
- mergerFunc = self.mergersFor(out).get(None, None)
- if mergerFunc is not None:
- mergerFunc(self, out, lst)
- elif hasattr(out, '__dict__'):
- self.mergeObjects(out, lst)
- elif isinstance(out, list):
- self.mergeLists(out, lst)
- else:
- if not allEqualTo(out, lst):
- raise VarLibMergeError((out, lst))
- except Exception as e:
- e.args = e.args + (type(out).__name__,)
- raise
+ if not allEqualTo(out, lst, type):
+ raise MismatchedTypes(self,
+ expected=type(out).__name__,
+ got=[type(x).__name__ for x in lst]
+ )
+ mergerFunc = self.mergersFor(out).get(None, None)
+ if mergerFunc is not None:
+ mergerFunc(self, out, lst)
+ elif hasattr(out, '__dict__'):
+ self.mergeObjects(out, lst)
+ elif isinstance(out, list):
+ self.mergeLists(out, lst)
+ else:
+ if not allEqualTo(out, lst):
+ raise ShouldBeConstant(self, expected=out, got=lst)
def mergeTables(self, font, master_ttfs, tableTags):
-
for tag in tableTags:
if tag not in font: continue
- self.mergeThings(font[tag], [m[tag] if tag in m else None
- for m in master_ttfs])
+ try:
+ self.ttfs = [m for m in master_ttfs if tag in m]
+ self.mergeThings(font[tag], [m[tag] if tag in m else None
+ for m in master_ttfs])
+ except VarLibMergeError as e:
+ e.stack.append(tag)
+ raise
#
# Aligning merger
@@ -128,7 +143,7 @@ class AligningMerger(Merger):
def merge(merger, self, lst):
if self is None:
if not allNone(lst):
- raise VarLibMergeError(lst)
+ raise NotANone(self, expected=None, got=lst)
return
lst = [l.classDefs for l in lst]
@@ -141,7 +156,7 @@ def merge(merger, self, lst):
for k in allKeys:
allValues = nonNone(l.get(k) for l in lst)
if not allEqual(allValues):
- raise VarLibMergeError(allValues)
+ raise ShouldBeConstant(self, expected=allValues[0], got=lst, stack="."+k)
if not allValues:
self[k] = None
else:
@@ -178,7 +193,7 @@ def _merge_GlyphOrders(font, lst, values_lst=None, default=None):
order = sorted(combined, key=sortKey)
# Make sure all input glyphsets were in proper order
if not all(sorted(vs, key=sortKey) == vs for vs in lst):
- raise VarLibMergeError("Glyph order inconsistent across masters.")
+ raise InconsistentGlyphOrder(self)
del combined
paddedValues = None
@@ -205,10 +220,7 @@ def _Lookup_SinglePos_get_effective_value(subtables, glyph):
elif self.Format == 2:
return self.Value[self.Coverage.glyphs.index(glyph)]
else:
- raise VarLibMergeError(
- "Cannot retrieve effective value for SinglePos lookup, unsupported "
- f"format {self.Format}."
- )
+ raise UnsupportedFormat(self, subtable="single positioning lookup")
return None
def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph):
@@ -230,17 +242,14 @@ def _Lookup_PairPos_get_effective_value_pair(subtables, firstGlyph, secondGlyph)
klass2 = self.ClassDef2.classDefs.get(secondGlyph, 0)
return self.Class1Record[klass1].Class2Record[klass2]
else:
- raise VarLibMergeError(
- "Cannot retrieve effective value pair for PairPos lookup, unsupported "
- f"format {self.Format}."
- )
+ raise UnsupportedFormat(self, subtable="pair positioning lookup")
return None
@AligningMerger.merger(ot.SinglePos)
def merge(merger, self, lst):
self.ValueFormat = valueFormat = reduce(int.__or__, [l.ValueFormat for l in lst], 0)
if not (len(lst) == 1 or (valueFormat & ~0xF == 0)):
- raise VarLibMergeError(f"SinglePos format {valueFormat} is unsupported.")
+ raise UnsupportedFormat(self, subtable="single positioning lookup")
# If all have same coverage table and all are format 1,
coverageGlyphs = self.Coverage.glyphs
@@ -400,28 +409,12 @@ def _ClassDef_merge_classify(lst, allGlyphses=None):
return self, classes
-# It's stupid that we need to do this here. Just need to, to match test
-# expecatation results, since ttx prints out format of ClassDef (and Coverage)
-# even though it should not.
-def _ClassDef_calculate_Format(self, font):
- fmt = 2
- ranges = self._getClassRanges(font)
- if ranges:
- startGlyph = ranges[0][1]
- endGlyph = ranges[-1][3]
- glyphCount = endGlyph - startGlyph + 1
- if len(ranges) * 3 >= glyphCount + 1:
- # Format 1 is more compact
- fmt = 1
- self.Format = fmt
-
def _PairPosFormat2_align_matrices(self, lst, font, transparent=False):
matrices = [l.Class1Record for l in lst]
# Align first classes
self.ClassDef1, classes = _ClassDef_merge_classify([l.ClassDef1 for l in lst], [l.Coverage.glyphs for l in lst])
- _ClassDef_calculate_Format(self.ClassDef1, font)
self.Class1Count = len(classes)
new_matrices = []
for l,matrix in zip(lst, matrices):
@@ -460,7 +453,6 @@ def _PairPosFormat2_align_matrices(self, lst, font, transparent=False):
# Align second classes
self.ClassDef2, classes = _ClassDef_merge_classify([l.ClassDef2 for l in lst])
- _ClassDef_calculate_Format(self.ClassDef2, font)
self.Class2Count = len(classes)
new_matrices = []
for l,matrix in zip(lst, matrices):
@@ -526,9 +518,7 @@ def merge(merger, self, lst):
elif self.Format == 2:
_PairPosFormat2_merge(self, lst, merger)
else:
- raise VarLibMergeError(
- f"Cannot merge PairPos lookup, unsupported format {self.Format}."
- )
+ raise UnsupportedFormat(self, subtable="pair positioning lookup")
del merger.valueFormat1, merger.valueFormat2
@@ -594,8 +584,7 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
# input masters.
if not allEqual(allClasses):
- raise VarLibMergeError(allClasses)
- if not allClasses:
+ raise allClasses(self, allClasses)
rec = None
else:
rec = ot.MarkRecord()
@@ -644,36 +633,32 @@ def _MarkBasePosFormat1_merge(self, lst, merger, Mark='Mark', Base='Base'):
@AligningMerger.merger(ot.MarkBasePos)
def merge(merger, self, lst):
if not allEqualTo(self.Format, (l.Format for l in lst)):
- raise VarLibMergeError(
- f"MarkBasePos formats inconsistent across masters, "
- f"expected {self.Format} but got {[l.Format for l in lst]}."
+ raise InconsistentFormats(self,
+ subtable="mark-to-base positioning lookup",
+ expected=self.Format,
+ got=[l.Format for l in lst]
)
if self.Format == 1:
_MarkBasePosFormat1_merge(self, lst, merger)
else:
- raise VarLibMergeError(
- f"Cannot merge MarkBasePos lookup, unsupported format {self.Format}."
- )
+ raise UnsupportedFormat(self, subtable="mark-to-base positioning lookup")
@AligningMerger.merger(ot.MarkMarkPos)
def merge(merger, self, lst):
if not allEqualTo(self.Format, (l.Format for l in lst)):
- raise VarLibMergeError(
- f"MarkMarkPos formats inconsistent across masters, "
- f"expected {self.Format} but got {[l.Format for l in lst]}."
+ raise InconsistentFormats(self,
+ subtable="mark-to-mark positioning lookup",
+ expected=self.Format,
+ got=[l.Format for l in lst]
)
if self.Format == 1:
_MarkBasePosFormat1_merge(self, lst, merger, 'Mark1', 'Mark2')
else:
- raise VarLibMergeError(
- f"Cannot merge MarkMarkPos lookup, unsupported format {self.Format}."
- )
-
+ raise UnsupportedFormat(self, subtable="mark-to-mark positioning lookup")
def _PairSet_flatten(lst, font):
self = ot.PairSet()
self.Coverage = ot.Coverage()
- self.Coverage.Format = 1
# Align them
glyphs, padded = _merge_GlyphOrders(font,
@@ -699,7 +684,6 @@ def _Lookup_PairPosFormat1_subtables_flatten(lst, font):
self = ot.PairPos()
self.Format = 1
self.Coverage = ot.Coverage()
- self.Coverage.Format = 1
self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)
@@ -720,7 +704,6 @@ def _Lookup_PairPosFormat2_subtables_flatten(lst, font):
self = ot.PairPos()
self.Format = 2
self.Coverage = ot.Coverage()
- self.Coverage.Format = 1
self.ValueFormat1 = reduce(int.__or__, [l.ValueFormat1 for l in lst], 0)
self.ValueFormat2 = reduce(int.__or__, [l.ValueFormat2 for l in lst], 0)
@@ -797,15 +780,12 @@ def merge(merger, self, lst):
continue
if sts[0].__class__.__name__.startswith('Extension'):
if not allEqual([st.__class__ for st in sts]):
- raise VarLibMergeError(
- "Use of extensions inconsistent between masters: "
- f"{[st.__class__.__name__ for st in sts]}."
+ raise InconsistentExtensions(self,
+ expected="Extension",
+ got=[st.__class__.__name__ for st in sts]
)
if not allEqual([st.ExtensionLookupType for st in sts]):
- raise VarLibMergeError(
- "Extension lookup type differs between masters: "
- f"{[st.ExtensionLookupType for st in sts]}."
- )
+ raise InconsistentExtensions(self)
l.LookupType = sts[0].ExtensionLookupType
new_sts = [st.ExtSubTable for st in sts]
del sts[:]
@@ -1034,7 +1014,7 @@ class VariationMerger(AligningMerger):
if None in lst:
if allNone(lst):
if out is not None:
- raise VarLibMergeError((out, lst))
+ raise FoundANone(self, got=lst)
return
masterModel = self.model
model, lst = masterModel.getSubModel(lst)
@@ -1055,7 +1035,7 @@ def buildVarDevTable(store_builder, master_values):
@VariationMerger.merger(ot.BaseCoord)
def merge(merger, self, lst):
if self.Format != 1:
- raise VarLibMergeError(f"BaseCoord format {self.Format} unsupported.")
+ raise UnsupportedFormat(self, subtable="a baseline coordinate")
self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst])
if DeviceTable:
self.Format = 3
@@ -1064,7 +1044,7 @@ def merge(merger, self, lst):
@VariationMerger.merger(ot.CaretValue)
def merge(merger, self, lst):
if self.Format != 1:
- raise VarLibMergeError(f"CaretValue format {self.Format} unsupported.")
+ raise UnsupportedFormat(self, subtable="a caret")
self.Coordinate, DeviceTable = buildVarDevTable(merger.store_builder, [a.Coordinate for a in lst])
if DeviceTable:
self.Format = 3
@@ -1073,7 +1053,7 @@ def merge(merger, self, lst):
@VariationMerger.merger(ot.Anchor)
def merge(merger, self, lst):
if self.Format != 1:
- raise VarLibMergeError(f"Anchor format {self.Format} unsupported.")
+ raise UnsupportedFormat(self, subtable="an anchor")
self.XCoordinate, XDeviceTable = buildVarDevTable(merger.store_builder, [a.XCoordinate for a in lst])
self.YCoordinate, YDeviceTable = buildVarDevTable(merger.store_builder, [a.YCoordinate for a in lst])
if XDeviceTable or YDeviceTable:
diff --git a/Lib/fontTools/varLib/models.py b/Lib/fontTools/varLib/models.py
index 9cc40b1c..9296deda 100644
--- a/Lib/fontTools/varLib/models.py
+++ b/Lib/fontTools/varLib/models.py
@@ -5,6 +5,7 @@ __all__ = ['nonNone', 'allNone', 'allEqual', 'allEqualTo', 'subList',
'supportScalar',
'VariationModel']
+from fontTools.misc.roundTools import noRound
from .errors import VariationModelError
@@ -281,34 +282,18 @@ class VariationModel(object):
def _computeMasterSupports(self, axisPoints):
supports = []
- deltaWeights = []
- locations = self.locations
- # Compute min/max across each axis, use it as total range.
- # TODO Take this as input from outside?
- minV = {}
- maxV = {}
- for l in locations:
- for k,v in l.items():
- minV[k] = min(v, minV.get(k, v))
- maxV[k] = max(v, maxV.get(k, v))
- for i,loc in enumerate(locations):
- box = {}
- for axis,locV in loc.items():
- if locV > 0:
- box[axis] = (0, locV, maxV[axis])
- else:
- box[axis] = (minV[axis], locV, 0)
-
- locAxes = set(loc.keys())
+ regions = self._locationsToRegions()
+ for i,region in enumerate(regions):
+ locAxes = set(region.keys())
# Walk over previous masters now
- for j,m in enumerate(locations[:i]):
+ for j,prev_region in enumerate(regions[:i]):
# Master with extra axes do not participte
- if not set(m.keys()).issubset(locAxes):
+ if not set(prev_region.keys()).issubset(locAxes):
continue
# If it's NOT in the current box, it does not participate
relevant = True
- for axis, (lower,peak,upper) in box.items():
- if axis not in m or not (m[axis] == peak or lower < m[axis] < upper):
+ for axis, (lower,peak,upper) in region.items():
+ if axis not in prev_region or not (prev_region[axis][1] == peak or lower < prev_region[axis][1] < upper):
relevant = False
break
if not relevant:
@@ -323,10 +308,10 @@ class VariationModel(object):
bestAxes = {}
bestRatio = -1
- for axis in m.keys():
- val = m[axis]
- assert axis in box
- lower,locV,upper = box[axis]
+ for axis in prev_region.keys():
+ val = prev_region[axis][1]
+ assert axis in region
+ lower,locV,upper = region[axis]
newLower, newUpper = lower, upper
if val < locV:
newLower = val
@@ -344,21 +329,46 @@ class VariationModel(object):
bestAxes[axis] = (newLower, locV, newUpper)
for axis,triple in bestAxes.items ():
- box[axis] = triple
- supports.append(box)
+ region[axis] = triple
+ supports.append(region)
+ self.supports = supports
+ self._computeDeltaWeights()
+
+ def _locationsToRegions(self):
+ locations = self.locations
+ # Compute min/max across each axis, use it as total range.
+ # TODO Take this as input from outside?
+ minV = {}
+ maxV = {}
+ for l in locations:
+ for k,v in l.items():
+ minV[k] = min(v, minV.get(k, v))
+ maxV[k] = max(v, maxV.get(k, v))
+ regions = []
+ for i,loc in enumerate(locations):
+ region = {}
+ for axis,locV in loc.items():
+ if locV > 0:
+ region[axis] = (0, locV, maxV[axis])
+ else:
+ region[axis] = (minV[axis], locV, 0)
+ regions.append(region)
+ return regions
+
+ def _computeDeltaWeights(self):
+ deltaWeights = []
+ for i,loc in enumerate(self.locations):
deltaWeight = {}
# Walk over previous masters now, populate deltaWeight
- for j,m in enumerate(locations[:i]):
- scalar = supportScalar(loc, supports[j])
+ for j,m in enumerate(self.locations[:i]):
+ scalar = supportScalar(loc, self.supports[j])
if scalar:
deltaWeight[j] = scalar
deltaWeights.append(deltaWeight)
-
- self.supports = supports
self.deltaWeights = deltaWeights
- def getDeltas(self, masterValues):
+ def getDeltas(self, masterValues, *, round=noRound):
assert len(masterValues) == len(self.deltaWeights)
mapping = self.reverseMapping
out = []
@@ -366,12 +376,12 @@ class VariationModel(object):
delta = masterValues[mapping[i]]
for j,weight in weights.items():
delta -= out[j] * weight
- out.append(delta)
+ out.append(round(delta))
return out
- def getDeltasAndSupports(self, items):
+ def getDeltasAndSupports(self, items, *, round=noRound):
model, items = self.getSubModel(items)
- return model.getDeltas(items), model.supports
+ return model.getDeltas(items, round=round), model.supports
def getScalars(self, loc):
return [supportScalar(loc, support) for support in self.supports]
@@ -393,12 +403,12 @@ class VariationModel(object):
scalars = self.getScalars(loc)
return self.interpolateFromDeltasAndScalars(deltas, scalars)
- def interpolateFromMasters(self, loc, masterValues):
- deltas = self.getDeltas(masterValues)
+ def interpolateFromMasters(self, loc, masterValues, *, round=noRound):
+ deltas = self.getDeltas(masterValues, round=round)
return self.interpolateFromDeltas(loc, deltas)
- def interpolateFromMastersAndScalars(self, masterValues, scalars):
- deltas = self.getDeltas(masterValues)
+ def interpolateFromMastersAndScalars(self, masterValues, scalars, *, round=noRound):
+ deltas = self.getDeltas(masterValues, round=round)
return self.interpolateFromDeltasAndScalars(deltas, scalars)
diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py
index ad76420a..02ce4422 100644
--- a/Lib/fontTools/varLib/mutator.py
+++ b/Lib/fontTools/varLib/mutator.py
@@ -3,7 +3,8 @@ Instantiate a variation font. Run, eg:
$ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85
"""
-from fontTools.misc.fixedTools import floatToFixedToFloat, otRound, floatToFixed
+from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed
+from fontTools.misc.roundTools import otRound
from fontTools.pens.boundsPen import BoundsPen
from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables import ttProgram
@@ -345,14 +346,8 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
# Change maxp attributes as IDEF is added
if 'maxp' in varfont:
maxp = varfont['maxp']
- if hasattr(maxp, "maxInstructionDefs"):
- maxp.maxInstructionDefs += 1
- else:
- setattr(maxp, "maxInstructionDefs", 1)
- if hasattr(maxp, "maxStackElements"):
- maxp.maxStackElements = max(len(loc), maxp.maxStackElements)
- else:
- setattr(maxp, "maxInstructionDefs", len(loc))
+ setattr(maxp, "maxInstructionDefs", 1 + getattr(maxp, "maxInstructionDefs", 0))
+ setattr(maxp, "maxStackElements", max(len(loc), getattr(maxp, "maxStackElements", 0)))
if 'name' in varfont:
log.info("Pruning name table")
diff --git a/Lib/fontTools/varLib/plot.py b/Lib/fontTools/varLib/plot.py
index b6561dc6..811559fa 100644
--- a/Lib/fontTools/varLib/plot.py
+++ b/Lib/fontTools/varLib/plot.py
@@ -2,8 +2,8 @@
from fontTools.varLib.models import VariationModel, supportScalar
from fontTools.designspaceLib import DesignSpaceDocument
-from mpl_toolkits.mplot3d import axes3d
from matplotlib import pyplot
+from mpl_toolkits.mplot3d import axes3d
from itertools import cycle
import math
import logging
@@ -68,10 +68,10 @@ def plotLocations(locations, fig, names=None, **kwargs):
def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
+ subplot = fig.add_subplot(111)
for i, (support, color, name) in enumerate(
zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
):
- subplot = fig.add_subplot(rows, cols, i + 1)
if name is not None:
subplot.set_title(name)
subplot.set_xlabel(axis)
@@ -91,10 +91,10 @@ def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs):
ax1, ax2 = axes
+ axis3D = fig.add_subplot(111, projection='3d')
for i, (support, color, name) in enumerate(
zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
):
- axis3D = fig.add_subplot(rows, cols, i + 1, projection='3d')
if name is not None:
axis3D.set_title(name)
axis3D.set_xlabel(ax1)
diff --git a/Lib/fontTools/varLib/varStore.py b/Lib/fontTools/varLib/varStore.py
index b28d2a65..8a382df0 100644
--- a/Lib/fontTools/varLib/varStore.py
+++ b/Lib/fontTools/varLib/varStore.py
@@ -1,4 +1,4 @@
-from fontTools.misc.fixedTools import otRound
+from fontTools.misc.roundTools import noRound, otRound
from fontTools.ttLib.tables import otTables as ot
from fontTools.varLib.models import supportScalar
from fontTools.varLib.builder import (buildVarRegionList, buildVarStore,
@@ -83,15 +83,12 @@ class OnlineVarStoreBuilder(object):
def storeMasters(self, master_values):
- deltas = self._model.getDeltas(master_values)
- base = otRound(deltas.pop(0))
- return base, self.storeDeltas(deltas)
-
- def storeDeltas(self, deltas):
- # Pity that this exists here, since VarData_addItem
- # does the same. But to look into our cache, it's
- # good to adjust deltas here as well...
- deltas = [otRound(d) for d in deltas]
+ deltas = self._model.getDeltas(master_values, round=round)
+ base = deltas.pop(0)
+ return base, self.storeDeltas(deltas, round=noRound)
+
+ def storeDeltas(self, deltas, *, round=round):
+ deltas = [round(d) for d in deltas]
if len(deltas) == len(self._supports) + 1:
deltas = tuple(deltas[1:])
else:
@@ -109,14 +106,14 @@ class OnlineVarStoreBuilder(object):
# Full array. Start new one.
self._add_VarData()
return self.storeDeltas(deltas)
- self._data.addItem(deltas)
+ self._data.addItem(deltas, round=noRound)
varIdx = (self._outer << 16) + inner
self._cache[deltas] = varIdx
return varIdx
-def VarData_addItem(self, deltas):
- deltas = [otRound(d) for d in deltas]
+def VarData_addItem(self, deltas, *, round=round):
+ deltas = [round(d) for d in deltas]
countUs = self.VarRegionCount
countThem = len(deltas)