commit
090fd5f5f0
@ -1,4 +1,16 @@
|
|||||||
from __future__ import print_function, division, absolute_import
|
from __future__ import print_function, division, absolute_import
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
|
import logging
|
||||||
|
from fontTools.misc.loggingTools import Logger, configLogger
|
||||||
|
|
||||||
|
# set the logging.Logger class to one which supports the "last resort" handler,
|
||||||
|
# to be used when the client doesn't explicitly configure logging.
|
||||||
|
# It prints the bare message to sys.stderr, only for events of severity WARNING
|
||||||
|
# or greater.
|
||||||
|
logging.setLoggerClass(Logger)
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
version = "3.0"
|
version = "3.0"
|
||||||
|
|
||||||
|
__all__ = ["version", "log", "configLogger"]
|
||||||
|
@ -6,8 +6,12 @@ from fontTools.misc import sstruct
|
|||||||
from fontTools.misc import psCharStrings
|
from fontTools.misc import psCharStrings
|
||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
import struct
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
DEBUG = 0
|
|
||||||
|
# mute cffLib debug messages when running ttx in verbose mode
|
||||||
|
DEBUG = logging.DEBUG - 1
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
cffHeaderFormat = """
|
cffHeaderFormat = """
|
||||||
@ -132,8 +136,7 @@ class CFFWriter(object):
|
|||||||
lastPosList = None
|
lastPosList = None
|
||||||
count = 1
|
count = 1
|
||||||
while True:
|
while True:
|
||||||
if DEBUG:
|
log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
|
||||||
print("CFFWriter.toFile() iteration:", count)
|
|
||||||
count = count + 1
|
count = count + 1
|
||||||
pos = 0
|
pos = 0
|
||||||
posList = [pos]
|
posList = [pos]
|
||||||
@ -149,8 +152,7 @@ class CFFWriter(object):
|
|||||||
if posList == lastPosList:
|
if posList == lastPosList:
|
||||||
break
|
break
|
||||||
lastPosList = posList
|
lastPosList = posList
|
||||||
if DEBUG:
|
log.log(DEBUG, "CFFWriter.toFile() writing to file.")
|
||||||
print("CFFWriter.toFile() writing to file.")
|
|
||||||
begin = file.tell()
|
begin = file.tell()
|
||||||
posList = [0]
|
posList = [0]
|
||||||
for item in self.data:
|
for item in self.data:
|
||||||
@ -308,16 +310,14 @@ class Index(object):
|
|||||||
name = self.__class__.__name__
|
name = self.__class__.__name__
|
||||||
if file is None:
|
if file is None:
|
||||||
return
|
return
|
||||||
if DEBUG:
|
log.log(DEBUG, "loading %s at %s", name, file.tell())
|
||||||
print("loading %s at %s" % (name, file.tell()))
|
|
||||||
self.file = file
|
self.file = file
|
||||||
count = readCard16(file)
|
count = readCard16(file)
|
||||||
if count == 0:
|
if count == 0:
|
||||||
return
|
return
|
||||||
self.items = [None] * count
|
self.items = [None] * count
|
||||||
offSize = readCard8(file)
|
offSize = readCard8(file)
|
||||||
if DEBUG:
|
log.log(DEBUG, " index count: %s offSize: %s", count, offSize)
|
||||||
print(" index count: %s offSize: %s" % (count, offSize))
|
|
||||||
assert offSize <= 4, "offSize too large: %s" % offSize
|
assert offSize <= 4, "offSize too large: %s" % offSize
|
||||||
self.offsets = offsets = []
|
self.offsets = offsets = []
|
||||||
pad = b'\0' * (4 - offSize)
|
pad = b'\0' * (4 - offSize)
|
||||||
@ -328,8 +328,7 @@ class Index(object):
|
|||||||
offsets.append(int(offset))
|
offsets.append(int(offset))
|
||||||
self.offsetBase = file.tell() - 1
|
self.offsetBase = file.tell() - 1
|
||||||
file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
|
file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
|
||||||
if DEBUG:
|
log.log(DEBUG, " end of %s at %s", name, file.tell())
|
||||||
print(" end of %s at %s" % (name, file.tell()))
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.items)
|
return len(self.items)
|
||||||
@ -784,8 +783,7 @@ class CharsetConverter(object):
|
|||||||
numGlyphs = parent.numGlyphs
|
numGlyphs = parent.numGlyphs
|
||||||
file = parent.file
|
file = parent.file
|
||||||
file.seek(value)
|
file.seek(value)
|
||||||
if DEBUG:
|
log.log(DEBUG, "loading charset at %s", value)
|
||||||
print("loading charset at %s" % value)
|
|
||||||
format = readCard8(file)
|
format = readCard8(file)
|
||||||
if format == 0:
|
if format == 0:
|
||||||
charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
|
charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
|
||||||
@ -794,8 +792,7 @@ class CharsetConverter(object):
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
assert len(charset) == numGlyphs
|
assert len(charset) == numGlyphs
|
||||||
if DEBUG:
|
log.log(DEBUG, " charset end at %s", file.tell())
|
||||||
print(" charset end at %s" % file.tell())
|
|
||||||
else: # offset == 0 -> no charset data.
|
else: # offset == 0 -> no charset data.
|
||||||
if isCID or "CharStrings" not in parent.rawDict:
|
if isCID or "CharStrings" not in parent.rawDict:
|
||||||
assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset.
|
assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset.
|
||||||
@ -964,8 +961,7 @@ class EncodingConverter(SimpleConverter):
|
|||||||
assert value > 1
|
assert value > 1
|
||||||
file = parent.file
|
file = parent.file
|
||||||
file.seek(value)
|
file.seek(value)
|
||||||
if DEBUG:
|
log.log(DEBUG, "loading Encoding at %s", value)
|
||||||
print("loading Encoding at %s" % value)
|
|
||||||
fmt = readCard8(file)
|
fmt = readCard8(file)
|
||||||
haveSupplement = fmt & 0x80
|
haveSupplement = fmt & 0x80
|
||||||
if haveSupplement:
|
if haveSupplement:
|
||||||
@ -1335,9 +1331,7 @@ class DictCompiler(object):
|
|||||||
return len(self.compile("getDataLength"))
|
return len(self.compile("getDataLength"))
|
||||||
|
|
||||||
def compile(self, reason):
|
def compile(self, reason):
|
||||||
if DEBUG:
|
log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
|
||||||
print("-- compiling %s for %s" % (self.__class__.__name__, reason))
|
|
||||||
print("in baseDict: ", self)
|
|
||||||
rawDict = self.rawDict
|
rawDict = self.rawDict
|
||||||
data = []
|
data = []
|
||||||
for name in self.dictObj.order:
|
for name in self.dictObj.order:
|
||||||
@ -1468,16 +1462,15 @@ class BaseDict(object):
|
|||||||
|
|
||||||
def __init__(self, strings=None, file=None, offset=None):
|
def __init__(self, strings=None, file=None, offset=None):
|
||||||
self.rawDict = {}
|
self.rawDict = {}
|
||||||
if DEBUG:
|
if offset is not None:
|
||||||
print("loading %s at %s" % (self.__class__.__name__, offset))
|
log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
|
||||||
self.file = file
|
self.file = file
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.skipNames = []
|
self.skipNames = []
|
||||||
|
|
||||||
def decompile(self, data):
|
def decompile(self, data):
|
||||||
if DEBUG:
|
log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data))
|
||||||
print(" length %s is %s" % (self.__class__.__name__, len(data)))
|
|
||||||
dec = self.decompilerClass(self.strings)
|
dec = self.decompilerClass(self.strings)
|
||||||
dec.decompile(data)
|
dec.decompile(data)
|
||||||
self.rawDict = dec.getDict()
|
self.rawDict = dec.getDict()
|
||||||
@ -1558,7 +1551,7 @@ class TopDict(BaseDict):
|
|||||||
try:
|
try:
|
||||||
charString.decompile()
|
charString.decompile()
|
||||||
except:
|
except:
|
||||||
print("Error in charstring ", i)
|
log.error("Error in charstring %s", i)
|
||||||
import sys
|
import sys
|
||||||
typ, value = sys.exc_info()[0:2]
|
typ, value = sys.exc_info()[0:2]
|
||||||
raise typ(value)
|
raise typ(value)
|
||||||
|
@ -11,10 +11,16 @@ from fontTools.misc.timeTools import timestampNow
|
|||||||
from fontTools import ttLib, cffLib
|
from fontTools import ttLib, cffLib
|
||||||
from fontTools.ttLib.tables import otTables, _h_e_a_d
|
from fontTools.ttLib.tables import otTables, _h_e_a_d
|
||||||
from fontTools.ttLib.tables.DefaultTable import DefaultTable
|
from fontTools.ttLib.tables.DefaultTable import DefaultTable
|
||||||
|
from fontTools.misc.loggingTools import Timer
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import operator
|
import operator
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
timer = Timer(logger=logging.getLogger(__name__+".timer"), level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def _add_method(*clazzes, **kwargs):
|
def _add_method(*clazzes, **kwargs):
|
||||||
@ -144,7 +150,7 @@ def mergeBits(bitmap):
|
|||||||
@_add_method(DefaultTable, allowDefaultTable=True)
|
@_add_method(DefaultTable, allowDefaultTable=True)
|
||||||
def merge(self, m, tables):
|
def merge(self, m, tables):
|
||||||
if not hasattr(self, 'mergeMap'):
|
if not hasattr(self, 'mergeMap'):
|
||||||
m.log("Don't know how to merge '%s'." % self.tableTag)
|
log.info("Don't know how to merge '%s'.", self.tableTag)
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
logic = self.mergeMap
|
logic = self.mergeMap
|
||||||
@ -650,6 +656,9 @@ class Options(object):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
|
self.verbose = False
|
||||||
|
self.timing = False
|
||||||
|
|
||||||
self.set(**kwargs)
|
self.set(**kwargs)
|
||||||
|
|
||||||
def set(self, **kwargs):
|
def set(self, **kwargs):
|
||||||
@ -721,15 +730,12 @@ class Options(object):
|
|||||||
|
|
||||||
class Merger(object):
|
class Merger(object):
|
||||||
|
|
||||||
def __init__(self, options=None, log=None):
|
def __init__(self, options=None):
|
||||||
|
|
||||||
if not log:
|
|
||||||
log = Logger()
|
|
||||||
if not options:
|
if not options:
|
||||||
options = Options()
|
options = Options()
|
||||||
|
|
||||||
self.options = options
|
self.options = options
|
||||||
self.log = log
|
|
||||||
|
|
||||||
def merge(self, fontfiles):
|
def merge(self, fontfiles):
|
||||||
|
|
||||||
@ -766,7 +772,7 @@ class Merger(object):
|
|||||||
allTags = ['cmap'] + list(allTags)
|
allTags = ['cmap'] + list(allTags)
|
||||||
|
|
||||||
for tag in allTags:
|
for tag in allTags:
|
||||||
|
with timer("merge '%s'" % tag):
|
||||||
tables = [font.get(tag, NotImplemented) for font in fonts]
|
tables = [font.get(tag, NotImplemented) for font in fonts]
|
||||||
|
|
||||||
clazz = ttLib.getTableClass(tag)
|
clazz = ttLib.getTableClass(tag)
|
||||||
@ -775,10 +781,9 @@ class Merger(object):
|
|||||||
|
|
||||||
if table is not NotImplemented and table is not False:
|
if table is not NotImplemented and table is not False:
|
||||||
mega[tag] = table
|
mega[tag] = table
|
||||||
self.log("Merged '%s'." % tag)
|
log.info("Merged '%s'.", tag)
|
||||||
else:
|
else:
|
||||||
self.log("Dropped '%s'." % tag)
|
log.info("Dropped '%s'.", tag)
|
||||||
self.log.lapse("merge '%s'" % tag)
|
|
||||||
|
|
||||||
del self.duplicateGlyphsPerFont
|
del self.duplicateGlyphsPerFont
|
||||||
|
|
||||||
@ -874,64 +879,19 @@ class Merger(object):
|
|||||||
# TODO FeatureParams nameIDs
|
# TODO FeatureParams nameIDs
|
||||||
|
|
||||||
|
|
||||||
class Logger(object):
|
|
||||||
|
|
||||||
def __init__(self, verbose=False, xml=False, timing=False):
|
|
||||||
self.verbose = verbose
|
|
||||||
self.xml = xml
|
|
||||||
self.timing = timing
|
|
||||||
self.last_time = self.start_time = time.time()
|
|
||||||
|
|
||||||
def parse_opts(self, argv):
|
|
||||||
argv = argv[:]
|
|
||||||
for v in ['verbose', 'xml', 'timing']:
|
|
||||||
if "--"+v in argv:
|
|
||||||
setattr(self, v, True)
|
|
||||||
argv.remove("--"+v)
|
|
||||||
return argv
|
|
||||||
|
|
||||||
def __call__(self, *things):
|
|
||||||
if not self.verbose:
|
|
||||||
return
|
|
||||||
print(' '.join(str(x) for x in things))
|
|
||||||
|
|
||||||
def lapse(self, *things):
|
|
||||||
if not self.timing:
|
|
||||||
return
|
|
||||||
new_time = time.time()
|
|
||||||
print("Took %0.3fs to %s" %(new_time - self.last_time,
|
|
||||||
' '.join(str(x) for x in things)))
|
|
||||||
self.last_time = new_time
|
|
||||||
|
|
||||||
def font(self, font, file=sys.stdout):
|
|
||||||
if not self.xml:
|
|
||||||
return
|
|
||||||
from fontTools.misc import xmlWriter
|
|
||||||
writer = xmlWriter.XMLWriter(file)
|
|
||||||
font.disassembleInstructions = False # Work around ttLib bug
|
|
||||||
for tag in font.keys():
|
|
||||||
writer.begintag(tag)
|
|
||||||
writer.newline()
|
|
||||||
font[tag].toXML(writer, font)
|
|
||||||
writer.endtag(tag)
|
|
||||||
writer.newline()
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Options',
|
'Options',
|
||||||
'Merger',
|
'Merger',
|
||||||
'Logger',
|
|
||||||
'main'
|
'main'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@timer("make one with everything (TOTAL TIME)")
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
|
from fontTools import configLogger
|
||||||
|
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
log = Logger()
|
|
||||||
args = log.parse_opts(args)
|
|
||||||
|
|
||||||
options = Options()
|
options = Options()
|
||||||
args = options.parse_opts(args)
|
args = options.parse_opts(args)
|
||||||
|
|
||||||
@ -939,14 +899,18 @@ def main(args=None):
|
|||||||
print("usage: pyftmerge font...", file=sys.stderr)
|
print("usage: pyftmerge font...", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
merger = Merger(options=options, log=log)
|
configLogger(level=logging.INFO if options.verbose else logging.WARNING)
|
||||||
|
if options.timing:
|
||||||
|
timer.logger.setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
timer.logger.disabled = True
|
||||||
|
|
||||||
|
merger = Merger(options=options)
|
||||||
font = merger.merge(args)
|
font = merger.merge(args)
|
||||||
outfile = 'merged.ttf'
|
outfile = 'merged.ttf'
|
||||||
|
with timer("compile and save font"):
|
||||||
font.save(outfile)
|
font.save(outfile)
|
||||||
log.lapse("compile and save font")
|
|
||||||
|
|
||||||
log.last_time = log.start_time
|
|
||||||
log.lapse("make one with everything(TOTAL TIME)")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
502
Lib/fontTools/misc/loggingTools.py
Normal file
502
Lib/fontTools/misc/loggingTools.py
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
""" fontTools.misc.loggingTools.py -- tools for interfacing with the Python
|
||||||
|
logging package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function, absolute_import
|
||||||
|
from fontTools.misc.py23 import basestring
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import timeit
|
||||||
|
from functools import wraps
|
||||||
|
import collections
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
try:
|
||||||
|
from logging import PercentStyle
|
||||||
|
except ImportError:
|
||||||
|
PercentStyle = None
|
||||||
|
|
||||||
|
|
||||||
|
# default logging level used by Timer class
|
||||||
|
TIME_LEVEL = logging.DEBUG
|
||||||
|
|
||||||
|
# per-level format strings used by the default formatter
|
||||||
|
# (the level name is not printed for INFO and DEBUG messages)
|
||||||
|
DEFAULT_FORMATS = {
|
||||||
|
"*": "%(levelname)s: %(message)s",
|
||||||
|
"INFO": "%(message)s",
|
||||||
|
"DEBUG": "%(message)s",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LevelFormatter(logging.Formatter):
|
||||||
|
""" Formatter class which optionally takes a dict of logging levels to
|
||||||
|
format strings, allowing to customise the log records appearance for
|
||||||
|
specific levels.
|
||||||
|
The '*' key identifies the default format string.
|
||||||
|
|
||||||
|
>>> import sys
|
||||||
|
>>> handler = logging.StreamHandler(sys.stdout)
|
||||||
|
>>> formatter = LevelFormatter(
|
||||||
|
... fmt={
|
||||||
|
... '*': '[%(levelname)s] %(message)s',
|
||||||
|
... 'DEBUG': '%(name)s [%(levelname)s] %(message)s',
|
||||||
|
... 'INFO': '%(message)s',
|
||||||
|
... })
|
||||||
|
>>> handler.setFormatter(formatter)
|
||||||
|
>>> log = logging.getLogger('test')
|
||||||
|
>>> log.setLevel(logging.DEBUG)
|
||||||
|
>>> log.addHandler(handler)
|
||||||
|
>>> log.debug('this uses a custom format string')
|
||||||
|
test [DEBUG] this uses a custom format string
|
||||||
|
>>> log.info('this also uses a custom format string')
|
||||||
|
this also uses a custom format string
|
||||||
|
>>> log.warning("this one uses the default format string")
|
||||||
|
[WARNING] this one uses the default format string
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fmt=None, datefmt=None, style="%"):
|
||||||
|
if style != '%':
|
||||||
|
raise ValueError(
|
||||||
|
"only '%' percent style is supported in both python 2 and 3")
|
||||||
|
if fmt is None:
|
||||||
|
fmt = DEFAULT_FORMATS
|
||||||
|
if isinstance(fmt, basestring):
|
||||||
|
default_format = fmt
|
||||||
|
custom_formats = {}
|
||||||
|
elif isinstance(fmt, collections.Mapping):
|
||||||
|
custom_formats = dict(fmt)
|
||||||
|
default_format = custom_formats.pop("*", None)
|
||||||
|
else:
|
||||||
|
raise TypeError('fmt must be a str or a dict of str: %r' % fmt)
|
||||||
|
super(LevelFormatter, self).__init__(default_format, datefmt)
|
||||||
|
self.default_format = self._fmt
|
||||||
|
self.custom_formats = {}
|
||||||
|
for level, fmt in custom_formats.items():
|
||||||
|
level = logging._checkLevel(level)
|
||||||
|
self.custom_formats[level] = fmt
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
if self.custom_formats:
|
||||||
|
fmt = self.custom_formats.get(record.levelno, self.default_format)
|
||||||
|
if self._fmt != fmt:
|
||||||
|
self._fmt = fmt
|
||||||
|
# for python >= 3.2, _style needs to be set if _fmt changes
|
||||||
|
if PercentStyle:
|
||||||
|
self._style = PercentStyle(fmt)
|
||||||
|
return super(LevelFormatter, self).format(record)
|
||||||
|
|
||||||
|
|
||||||
|
class _StderrHandler(logging.StreamHandler):
|
||||||
|
""" This class is like a StreamHandler using sys.stderr, but always uses
|
||||||
|
whatever sys.stderr is currently set to rather than the value of
|
||||||
|
sys.stderr at handler construction time.
|
||||||
|
"""
|
||||||
|
def __init__(self, level=logging.NOTSET):
|
||||||
|
"""
|
||||||
|
Initialize the handler.
|
||||||
|
"""
|
||||||
|
logging.Handler.__init__(self, level)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stream(self):
|
||||||
|
return sys.stderr
|
||||||
|
|
||||||
|
|
||||||
|
if not hasattr(logging, 'lastResort'):
|
||||||
|
# for Python pre-3.2, set a "last resort" handler used when clients don't
|
||||||
|
# explicitly configure logging
|
||||||
|
logging.lastResort = _StderrHandler(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
|
class Logger(logging.Logger):
|
||||||
|
""" Add support for 'lastResort' handler introduced in Python 3.2.
|
||||||
|
You can set logging.lastResort to None, if you wish to obtain the pre-3.2
|
||||||
|
behaviour. Also see:
|
||||||
|
https://docs.python.org/3.5/howto/logging.html#what-happens-if-no-configuration-is-provided
|
||||||
|
"""
|
||||||
|
|
||||||
|
def callHandlers(self, record):
|
||||||
|
# this is the same as Python 3.5's logging.Logger.callHandlers
|
||||||
|
c = self
|
||||||
|
found = 0
|
||||||
|
while c:
|
||||||
|
for hdlr in c.handlers:
|
||||||
|
found = found + 1
|
||||||
|
if record.levelno >= hdlr.level:
|
||||||
|
hdlr.handle(record)
|
||||||
|
if not c.propagate:
|
||||||
|
c = None # break out
|
||||||
|
else:
|
||||||
|
c = c.parent
|
||||||
|
if (found == 0):
|
||||||
|
if logging.lastResort:
|
||||||
|
if record.levelno >= logging.lastResort.level:
|
||||||
|
logging.lastResort.handle(record)
|
||||||
|
elif logging.raiseExceptions and not self.manager.emittedNoHandlerWarning:
|
||||||
|
sys.stderr.write("No handlers could be found for logger"
|
||||||
|
" \"%s\"\n" % self.name)
|
||||||
|
self.manager.emittedNoHandlerWarning = True
|
||||||
|
|
||||||
|
|
||||||
|
def configLogger(**kwargs):
|
||||||
|
""" Do basic configuration for the logging system. This is more or less
|
||||||
|
the same as logging.basicConfig with some additional options and defaults.
|
||||||
|
|
||||||
|
The default behaviour is to create a StreamHandler which writes to
|
||||||
|
sys.stderr, set a formatter using the DEFAULT_FORMATS strings, and add
|
||||||
|
the handler to the top-level library logger ("fontTools").
|
||||||
|
|
||||||
|
A number of optional keyword arguments may be specified, which can alter
|
||||||
|
the default behaviour.
|
||||||
|
|
||||||
|
logger Specifies the logger name or a Logger instance to be configured.
|
||||||
|
(it defaults to "fontTools" logger). Unlike basicConfig, this
|
||||||
|
function can be called multiple times to reconfigure a logger.
|
||||||
|
If the logger or any of its children already exists before the
|
||||||
|
call is made, they will be reset before the new configuration
|
||||||
|
is applied.
|
||||||
|
filename Specifies that a FileHandler be created, using the specified
|
||||||
|
filename, rather than a StreamHandler.
|
||||||
|
filemode Specifies the mode to open the file, if filename is specified
|
||||||
|
(if filemode is unspecified, it defaults to 'a').
|
||||||
|
format Use the specified format string for the handler. This argument
|
||||||
|
also accepts a dictionary of format strings keyed by level name,
|
||||||
|
to allow customising the records appearance for specific levels.
|
||||||
|
The special '*' key is for 'any other' level.
|
||||||
|
datefmt Use the specified date/time format.
|
||||||
|
level Set the logger level to the specified level.
|
||||||
|
stream Use the specified stream to initialize the StreamHandler. Note
|
||||||
|
that this argument is incompatible with 'filename' - if both
|
||||||
|
are present, 'stream' is ignored.
|
||||||
|
handlers If specified, this should be an iterable of already created
|
||||||
|
handlers, which will be added to the logger. Any handler
|
||||||
|
in the list which does not have a formatter assigned will be
|
||||||
|
assigned the formatter created in this function.
|
||||||
|
filters If specified, this should be an iterable of already created
|
||||||
|
filters, which will be added to the handler(s), if the latter
|
||||||
|
do(es) not already have filters assigned.
|
||||||
|
propagate All loggers have a "propagate" attribute initially set to True,
|
||||||
|
which determines whether to continue searching for handlers up
|
||||||
|
the logging hierarchy. By default, this arguments sets the
|
||||||
|
"propagate" attribute to False.
|
||||||
|
"""
|
||||||
|
# using kwargs to enforce keyword-only arguments in py2.
|
||||||
|
handlers = kwargs.pop("handlers", None)
|
||||||
|
if handlers is None:
|
||||||
|
if "stream" in kwargs and "filename" in kwargs:
|
||||||
|
raise ValueError("'stream' and 'filename' should not be "
|
||||||
|
"specified together")
|
||||||
|
else:
|
||||||
|
if "stream" in kwargs or "filename" in kwargs:
|
||||||
|
raise ValueError("'stream' or 'filename' should not be "
|
||||||
|
"specified together with 'handlers'")
|
||||||
|
if handlers is None:
|
||||||
|
filename = kwargs.pop("filename", None)
|
||||||
|
mode = kwargs.pop("filemode", 'a')
|
||||||
|
if filename:
|
||||||
|
h = logging.FileHandler(filename, mode)
|
||||||
|
else:
|
||||||
|
stream = kwargs.pop("stream", None)
|
||||||
|
h = logging.StreamHandler(stream)
|
||||||
|
handlers = [h]
|
||||||
|
# By default, the top-level library logger is configured.
|
||||||
|
logger = kwargs.pop("logger", "fontTools")
|
||||||
|
if not logger or isinstance(logger, basestring):
|
||||||
|
# empty "" or None means the 'root' logger
|
||||||
|
logger = logging.getLogger(logger)
|
||||||
|
# before (re)configuring, reset named logger and its children (if exist)
|
||||||
|
_resetExistingLoggers(parent=logger.name)
|
||||||
|
# use DEFAULT_FORMATS if 'format' is None
|
||||||
|
fs = kwargs.pop("format", None)
|
||||||
|
dfs = kwargs.pop("datefmt", None)
|
||||||
|
# XXX: '%' is the only format style supported on both py2 and 3
|
||||||
|
style = kwargs.pop("style", '%')
|
||||||
|
fmt = LevelFormatter(fs, dfs, style)
|
||||||
|
filters = kwargs.pop("filters", [])
|
||||||
|
for h in handlers:
|
||||||
|
if h.formatter is None:
|
||||||
|
h.setFormatter(fmt)
|
||||||
|
if not h.filters:
|
||||||
|
for f in filters:
|
||||||
|
h.addFilter(f)
|
||||||
|
logger.addHandler(h)
|
||||||
|
if logger.name != "root":
|
||||||
|
# stop searching up the hierarchy for handlers
|
||||||
|
logger.propagate = kwargs.pop("propagate", False)
|
||||||
|
# set a custom severity level
|
||||||
|
level = kwargs.pop("level", None)
|
||||||
|
if level is not None:
|
||||||
|
logger.setLevel(level)
|
||||||
|
if kwargs:
|
||||||
|
keys = ', '.join(kwargs.keys())
|
||||||
|
raise ValueError('Unrecognised argument(s): %s' % keys)
|
||||||
|
|
||||||
|
|
||||||
|
def _resetExistingLoggers(parent="root"):
|
||||||
|
""" Reset the logger named 'parent' and all its children to their initial
|
||||||
|
state, if they already exist in the current configuration.
|
||||||
|
"""
|
||||||
|
root = logging.root
|
||||||
|
# get sorted list of all existing loggers
|
||||||
|
existing = sorted(root.manager.loggerDict.keys())
|
||||||
|
if parent == "root":
|
||||||
|
# all the existing loggers are children of 'root'
|
||||||
|
loggers_to_reset = [parent] + existing
|
||||||
|
elif parent not in existing:
|
||||||
|
# nothing to do
|
||||||
|
return
|
||||||
|
elif parent in existing:
|
||||||
|
loggers_to_reset = [parent]
|
||||||
|
# collect children, starting with the entry after parent name
|
||||||
|
i = existing.index(parent) + 1
|
||||||
|
prefixed = parent + "."
|
||||||
|
pflen = len(prefixed)
|
||||||
|
num_existing = len(existing)
|
||||||
|
while i < num_existing:
|
||||||
|
if existing[i][:pflen] == prefixed:
|
||||||
|
loggers_to_reset.append(existing[i])
|
||||||
|
i += 1
|
||||||
|
for name in loggers_to_reset:
|
||||||
|
if name == "root":
|
||||||
|
root.setLevel(logging.WARNING)
|
||||||
|
for h in root.handlers[:]:
|
||||||
|
root.removeHandler(h)
|
||||||
|
for f in root.filters[:]:
|
||||||
|
root.removeFilters(f)
|
||||||
|
root.disabled = False
|
||||||
|
else:
|
||||||
|
logger = root.manager.loggerDict[name]
|
||||||
|
logger.level = logging.NOTSET
|
||||||
|
logger.handlers = []
|
||||||
|
logger.filters = []
|
||||||
|
logger.propagate = True
|
||||||
|
logger.disabled = False
|
||||||
|
|
||||||
|
|
||||||
|
class Timer(object):
|
||||||
|
""" Keeps track of overall time and split/lap times.
|
||||||
|
|
||||||
|
>>> import time
|
||||||
|
>>> timer = Timer()
|
||||||
|
>>> time.sleep(0.01)
|
||||||
|
>>> print("First lap:", timer.split())
|
||||||
|
First lap: 0.0...
|
||||||
|
>>> time.sleep(0.02)
|
||||||
|
>>> print("Second lap:", timer.split())
|
||||||
|
Second lap: 0.0...
|
||||||
|
>>> print("Overall time:", timer.time())
|
||||||
|
Overall time: 0.0...
|
||||||
|
|
||||||
|
Can be used as a context manager inside with-statements.
|
||||||
|
|
||||||
|
>>> with Timer() as t:
|
||||||
|
... time.sleep(0.01)
|
||||||
|
>>> print("%0.3f seconds" % t.elapsed)
|
||||||
|
0.0... seconds
|
||||||
|
|
||||||
|
If initialised with a logger, it can log the elapsed time automatically
|
||||||
|
upon exiting the with-statement.
|
||||||
|
|
||||||
|
>>> import logging
|
||||||
|
>>> log = logging.getLogger("fontTools")
|
||||||
|
>>> configLogger(level="DEBUG", format="%(message)s", stream=sys.stdout)
|
||||||
|
>>> with Timer(log, 'do something'):
|
||||||
|
... time.sleep(0.01)
|
||||||
|
Took ... to do something
|
||||||
|
|
||||||
|
The same Timer instance, holding a reference to a logger, can be reused
|
||||||
|
in multiple with-statements, optionally with different messages or levels.
|
||||||
|
|
||||||
|
>>> timer = Timer(log)
|
||||||
|
>>> with timer():
|
||||||
|
... time.sleep(0.01)
|
||||||
|
elapsed time: 0.01...s
|
||||||
|
>>> with timer('redo it', level=logging.INFO):
|
||||||
|
... time.sleep(0.02)
|
||||||
|
Took ... to redo it
|
||||||
|
|
||||||
|
It can also be used as a function decorator to log the time elapsed to run
|
||||||
|
the decorated function.
|
||||||
|
|
||||||
|
>>> @timer()
|
||||||
|
... def test1():
|
||||||
|
... time.sleep(0.01)
|
||||||
|
>>> @timer('run test 2', level=logging.INFO)
|
||||||
|
... def test2():
|
||||||
|
... time.sleep(0.02)
|
||||||
|
>>> test1()
|
||||||
|
Took 0.01... to run 'test1'
|
||||||
|
>>> test2()
|
||||||
|
Took ... to run test 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
# timeit.default_timer choses the most accurate clock for each platform
|
||||||
|
_time = timeit.default_timer
|
||||||
|
default_msg = "elapsed time: %(time).3fs"
|
||||||
|
default_format = "Took %(time).3fs to %(msg)s"
|
||||||
|
|
||||||
|
def __init__(self, logger=None, msg=None, level=None, start=None):
|
||||||
|
self.reset(start)
|
||||||
|
if logger is None:
|
||||||
|
for arg in ('msg', 'level'):
|
||||||
|
if locals().get(arg) is not None:
|
||||||
|
raise ValueError(
|
||||||
|
"'%s' can't be specified without a 'logger'" % arg)
|
||||||
|
self.logger = logger
|
||||||
|
self.level = level if level is not None else TIME_LEVEL
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
def reset(self, start=None):
|
||||||
|
""" Reset timer to 'start_time' or the current time. """
|
||||||
|
if start is None:
|
||||||
|
self.start = self._time()
|
||||||
|
else:
|
||||||
|
self.start = start
|
||||||
|
self.last = self.start
|
||||||
|
self.elapsed = 0.0
|
||||||
|
|
||||||
|
def time(self):
|
||||||
|
""" Return the overall time (in seconds) since the timer started. """
|
||||||
|
return self._time() - self.start
|
||||||
|
|
||||||
|
def split(self):
|
||||||
|
""" Split and return the lap time (in seconds) in between splits. """
|
||||||
|
current = self._time()
|
||||||
|
self.elapsed = current - self.last
|
||||||
|
self.last = current
|
||||||
|
return self.elapsed
|
||||||
|
|
||||||
|
def formatTime(self, msg, time):
|
||||||
|
""" Format 'time' value in 'msg' and return formatted string.
|
||||||
|
If 'msg' contains a '%(time)' format string, try to use that.
|
||||||
|
Otherwise, use the predefined 'default_format'.
|
||||||
|
If 'msg' is empty or None, fall back to 'default_msg'.
|
||||||
|
"""
|
||||||
|
if not msg:
|
||||||
|
msg = self.default_msg
|
||||||
|
if msg.find("%(time)") < 0:
|
||||||
|
msg = self.default_format % {"msg": msg, "time": time}
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
msg = msg % {"time": time}
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass # skip if the format string is malformed
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
""" Start a new lap """
|
||||||
|
self.last = self._time()
|
||||||
|
self.elapsed = 0.0
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
""" End the current lap. If timer has a logger, log the time elapsed,
|
||||||
|
using the format string in self.msg (or the default one).
|
||||||
|
"""
|
||||||
|
time = self.split()
|
||||||
|
if self.logger is None or exc_type:
|
||||||
|
# if there's no logger attached, or if any exception occurred in
|
||||||
|
# the with-statement, exit without logging the time
|
||||||
|
return
|
||||||
|
message = self.formatTime(self.msg, time)
|
||||||
|
self.logger.log(self.level, message)
|
||||||
|
|
||||||
|
def __call__(self, func_or_msg=None, **kwargs):
|
||||||
|
""" If the first argument is a function, return a decorator which runs
|
||||||
|
the wrapped function inside Timer's context manager.
|
||||||
|
Otherwise, treat the first argument as a 'msg' string and return an updated
|
||||||
|
Timer instance, referencing the same logger.
|
||||||
|
A 'level' keyword can also be passed to override self.level.
|
||||||
|
"""
|
||||||
|
if isinstance(func_or_msg, collections.Callable):
|
||||||
|
func = func_or_msg
|
||||||
|
# use the function name when no explicit 'msg' is provided
|
||||||
|
if not self.msg:
|
||||||
|
self.msg = "run '%s'" % func.__name__
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwds):
|
||||||
|
with self:
|
||||||
|
return func(*args, **kwds)
|
||||||
|
return wrapper
|
||||||
|
else:
|
||||||
|
msg = func_or_msg or kwargs.get("msg")
|
||||||
|
level = kwargs.get("level", self.level)
|
||||||
|
return self.__class__(self.logger, msg, level)
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return self.elapsed
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return int(self.elapsed)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%.3f" % self.elapsed
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelsFilter(logging.Filter):
|
||||||
|
""" Filter out records emitted from a list of enabled channel names,
|
||||||
|
including their children. It works the same as the logging.Filter class,
|
||||||
|
but allows to specify multiple channel names.
|
||||||
|
|
||||||
|
>>> import sys
|
||||||
|
>>> handler = logging.StreamHandler(sys.stdout)
|
||||||
|
>>> handler.setFormatter(logging.Formatter("%(message)s"))
|
||||||
|
>>> filter = ChannelsFilter("A.B", "C.D")
|
||||||
|
>>> handler.addFilter(filter)
|
||||||
|
>>> root = logging.getLogger()
|
||||||
|
>>> root.addHandler(handler)
|
||||||
|
>>> root.setLevel(level=logging.DEBUG)
|
||||||
|
>>> logging.getLogger('A.B').debug('this record passes through')
|
||||||
|
this record passes through
|
||||||
|
>>> logging.getLogger('A.B.C').debug('records from children also pass')
|
||||||
|
records from children also pass
|
||||||
|
>>> logging.getLogger('C.D').debug('this one as well')
|
||||||
|
this one as well
|
||||||
|
>>> logging.getLogger('A.B.').debug('also this one')
|
||||||
|
also this one
|
||||||
|
>>> logging.getLogger('A.F').debug('but this one does not!')
|
||||||
|
>>> logging.getLogger('C.DE').debug('neither this one!')
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *names):
|
||||||
|
self.names = names
|
||||||
|
self.num = len(names)
|
||||||
|
self.lenghts = {n: len(n) for n in names}
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
if self.num == 0:
|
||||||
|
return True
|
||||||
|
for name in self.names:
|
||||||
|
nlen = self.lenghts[name]
|
||||||
|
if name == record.name:
|
||||||
|
return True
|
||||||
|
elif (record.name.find(name, 0, nlen) == 0
|
||||||
|
and record.name[nlen] == "."):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def deprecateArgument(name, msg, category=UserWarning):
|
||||||
|
""" Raise a warning about deprecated function argument 'name'. """
|
||||||
|
warnings.warn(
|
||||||
|
"%r is deprecated; %s" % (name, msg), category=category, stacklevel=3)
|
||||||
|
|
||||||
|
|
||||||
|
def deprecateFunction(msg, category=UserWarning):
|
||||||
|
""" Decorator to raise a warning when a deprecated function is called. """
|
||||||
|
def decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
warnings.warn(
|
||||||
|
"%r is deprecated; %s" % (func.__name__, msg),
|
||||||
|
category=category, stacklevel=2)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)
|
@ -5,9 +5,10 @@ CFF dictionary data and Type1/Type2 CharStrings.
|
|||||||
from __future__ import print_function, division, absolute_import
|
from __future__ import print_function, division, absolute_import
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
import struct
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
DEBUG = 0
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def read_operator(self, b0, data, index):
|
def read_operator(self, b0, data, index):
|
||||||
@ -315,7 +316,7 @@ class T2CharString(ByteCodeBase):
|
|||||||
try:
|
try:
|
||||||
bytecode = bytesjoin(bytecode)
|
bytecode = bytesjoin(bytecode)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
print(bytecode)
|
log.error(bytecode)
|
||||||
raise
|
raise
|
||||||
self.setBytecode(bytecode)
|
self.setBytecode(bytecode)
|
||||||
|
|
||||||
|
@ -5,8 +5,11 @@ from .psOperators import *
|
|||||||
import re
|
import re
|
||||||
import collections
|
import collections
|
||||||
from string import whitespace
|
from string import whitespace
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
ps_special = b'()<>[]{}%' # / is one too, but we take care of that one differently
|
ps_special = b'()<>[]{}%' # / is one too, but we take care of that one differently
|
||||||
|
|
||||||
skipwhiteRE = re.compile(bytesjoin([b"[", whitespace, b"]*"]))
|
skipwhiteRE = re.compile(bytesjoin([b"[", whitespace, b"]*"]))
|
||||||
@ -189,14 +192,18 @@ class PSInterpreter(PSOperators):
|
|||||||
handle_object(object)
|
handle_object(object)
|
||||||
tokenizer.close()
|
tokenizer.close()
|
||||||
self.tokenizer = None
|
self.tokenizer = None
|
||||||
finally:
|
except:
|
||||||
if self.tokenizer is not None:
|
if self.tokenizer is not None:
|
||||||
if 0:
|
log.debug(
|
||||||
print('ps error:\n- - - - - - -')
|
'ps error:\n'
|
||||||
print(self.tokenizer.buf[self.tokenizer.pos-50:self.tokenizer.pos])
|
'- - - - - - -\n'
|
||||||
print('>>>')
|
'%s\n'
|
||||||
print(self.tokenizer.buf[self.tokenizer.pos:self.tokenizer.pos+50])
|
'>>>\n'
|
||||||
print('- - - - - - -')
|
'%s\n'
|
||||||
|
'- - - - - - -',
|
||||||
|
self.tokenizer.buf[self.tokenizer.pos-50:self.tokenizer.pos],
|
||||||
|
self.tokenizer.buf[self.tokenizer.pos:self.tokenizer.pos+50])
|
||||||
|
raise
|
||||||
|
|
||||||
def handle_object(self, object):
|
def handle_object(self, object):
|
||||||
if not (self.proclevel or object.literal or object.type == 'proceduretype'):
|
if not (self.proclevel or object.literal or object.type == 'proceduretype'):
|
||||||
|
@ -4,8 +4,11 @@ from fontTools import ttLib
|
|||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
from fontTools.ttLib.tables.DefaultTable import DefaultTable
|
from fontTools.ttLib.tables.DefaultTable import DefaultTable
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class TTXParseError(Exception): pass
|
class TTXParseError(Exception): pass
|
||||||
|
|
||||||
BUFSIZE = 0x4000
|
BUFSIZE = 0x4000
|
||||||
@ -13,7 +16,7 @@ BUFSIZE = 0x4000
|
|||||||
|
|
||||||
class XMLReader(object):
|
class XMLReader(object):
|
||||||
|
|
||||||
def __init__(self, fileOrPath, ttFont, progress=None, quiet=False):
|
def __init__(self, fileOrPath, ttFont, progress=None, quiet=None):
|
||||||
if fileOrPath == '-':
|
if fileOrPath == '-':
|
||||||
fileOrPath = sys.stdin
|
fileOrPath = sys.stdin
|
||||||
if not hasattr(fileOrPath, "read"):
|
if not hasattr(fileOrPath, "read"):
|
||||||
@ -25,6 +28,9 @@ class XMLReader(object):
|
|||||||
self._closeStream = False
|
self._closeStream = False
|
||||||
self.ttFont = ttFont
|
self.ttFont = ttFont
|
||||||
self.progress = progress
|
self.progress = progress
|
||||||
|
if quiet is not None:
|
||||||
|
from fontTools.misc.loggingTools import deprecateArgument
|
||||||
|
deprecateArgument("quiet", "configure logging instead")
|
||||||
self.quiet = quiet
|
self.quiet = quiet
|
||||||
self.root = None
|
self.root = None
|
||||||
self.contentStack = []
|
self.contentStack = []
|
||||||
@ -83,7 +89,7 @@ class XMLReader(object):
|
|||||||
# else fall back to using the current working directory
|
# else fall back to using the current working directory
|
||||||
dirname = os.getcwd()
|
dirname = os.getcwd()
|
||||||
subFile = os.path.join(dirname, subFile)
|
subFile = os.path.join(dirname, subFile)
|
||||||
subReader = XMLReader(subFile, self.ttFont, self.progress, self.quiet)
|
subReader = XMLReader(subFile, self.ttFont, self.progress)
|
||||||
subReader.read()
|
subReader.read()
|
||||||
self.contentStack.append([])
|
self.contentStack.append([])
|
||||||
return
|
return
|
||||||
@ -91,11 +97,7 @@ class XMLReader(object):
|
|||||||
msg = "Parsing '%s' table..." % tag
|
msg = "Parsing '%s' table..." % tag
|
||||||
if self.progress:
|
if self.progress:
|
||||||
self.progress.setLabel(msg)
|
self.progress.setLabel(msg)
|
||||||
elif self.ttFont.verbose:
|
log.info(msg)
|
||||||
ttLib.debugmsg(msg)
|
|
||||||
else:
|
|
||||||
if not self.quiet:
|
|
||||||
print(msg)
|
|
||||||
if tag == "GlyphOrder":
|
if tag == "GlyphOrder":
|
||||||
tableClass = ttLib.GlyphOrder
|
tableClass = ttLib.GlyphOrder
|
||||||
elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])):
|
elif "ERROR" in attrs or ('raw' in attrs and safeEval(attrs['raw'])):
|
||||||
|
@ -15,15 +15,16 @@ from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
|
|||||||
from fontTools.otlLib import builder as otl
|
from fontTools.otlLib import builder as otl
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from operator import setitem
|
from operator import setitem
|
||||||
|
import logging
|
||||||
|
|
||||||
class MtiLibError(Exception): pass
|
class MtiLibError(Exception): pass
|
||||||
class ReferenceNotFoundError(MtiLibError): pass
|
class ReferenceNotFoundError(MtiLibError): pass
|
||||||
class FeatureNotFoundError(ReferenceNotFoundError): pass
|
class FeatureNotFoundError(ReferenceNotFoundError): pass
|
||||||
class LookupNotFoundError(ReferenceNotFoundError): pass
|
class LookupNotFoundError(ReferenceNotFoundError): pass
|
||||||
|
|
||||||
def debug(*args):
|
|
||||||
#print(*args)
|
log = logging.getLogger(__name__)
|
||||||
pass
|
|
||||||
|
|
||||||
def makeGlyph(s):
|
def makeGlyph(s):
|
||||||
if s[:2] == 'U ':
|
if s[:2] == 'U ':
|
||||||
@ -79,18 +80,18 @@ class DeferredMapping(dict):
|
|||||||
self._deferredMappings = []
|
self._deferredMappings = []
|
||||||
|
|
||||||
def addDeferredMapping(self, setter, sym, e):
|
def addDeferredMapping(self, setter, sym, e):
|
||||||
debug("Adding deferred mapping for symbol '%s'" % sym, type(e).__name__)
|
log.debug("Adding deferred mapping for symbol '%s' %s", sym, type(e).__name__)
|
||||||
self._deferredMappings.append((setter,sym, e))
|
self._deferredMappings.append((setter,sym, e))
|
||||||
|
|
||||||
def applyDeferredMappings(self):
|
def applyDeferredMappings(self):
|
||||||
for setter,sym,e in self._deferredMappings:
|
for setter,sym,e in self._deferredMappings:
|
||||||
debug("Applying deferred mapping for symbol '%s'" % sym, type(e).__name__)
|
log.debug("Applying deferred mapping for symbol '%s' %s", sym, type(e).__name__)
|
||||||
try:
|
try:
|
||||||
mapped = self[sym]
|
mapped = self[sym]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise e
|
raise e
|
||||||
setter(mapped)
|
setter(mapped)
|
||||||
debug("Set to %s" % mapped)
|
log.debug("Set to %s", mapped)
|
||||||
self._deferredMappings = []
|
self._deferredMappings = []
|
||||||
|
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ def parseScriptList(lines, featureMap=None):
|
|||||||
with lines.between('script table'):
|
with lines.between('script table'):
|
||||||
for line in lines:
|
for line in lines:
|
||||||
scriptTag, langSysTag, defaultFeature, features = line
|
scriptTag, langSysTag, defaultFeature, features = line
|
||||||
debug("Adding script", scriptTag, "language-system", langSysTag)
|
log.debug("Adding script %s language-system %s", scriptTag, langSysTag)
|
||||||
|
|
||||||
langSys = ot.LangSys()
|
langSys = ot.LangSys()
|
||||||
langSys.LookupOrder = None
|
langSys.LookupOrder = None
|
||||||
@ -676,7 +677,7 @@ def parseContext(self, lines, font, Type, lookupMap=None):
|
|||||||
typ = lines.peek()[0].split()[0].lower()
|
typ = lines.peek()[0].split()[0].lower()
|
||||||
if typ == 'glyph':
|
if typ == 'glyph':
|
||||||
self.Format = 1
|
self.Format = 1
|
||||||
debug("Parsing %s format %s" % (Type, self.Format))
|
log.debug("Parsing %s format %s", Type, self.Format)
|
||||||
c = ContextHelper(Type, self.Format)
|
c = ContextHelper(Type, self.Format)
|
||||||
rules = []
|
rules = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@ -690,7 +691,7 @@ def parseContext(self, lines, font, Type, lookupMap=None):
|
|||||||
bucketizeRules(self, c, rules, self.Coverage.glyphs)
|
bucketizeRules(self, c, rules, self.Coverage.glyphs)
|
||||||
elif typ.endswith('class'):
|
elif typ.endswith('class'):
|
||||||
self.Format = 2
|
self.Format = 2
|
||||||
debug("Parsing %s format %s" % (Type, self.Format))
|
log.debug("Parsing %s format %s", Type, self.Format)
|
||||||
c = ContextHelper(Type, self.Format)
|
c = ContextHelper(Type, self.Format)
|
||||||
classDefs = [None] * c.DataLen
|
classDefs = [None] * c.DataLen
|
||||||
while lines.peek()[0].endswith("class definition begin"):
|
while lines.peek()[0].endswith("class definition begin"):
|
||||||
@ -720,7 +721,7 @@ def parseContext(self, lines, font, Type, lookupMap=None):
|
|||||||
bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
|
bucketizeRules(self, c, rules, range(max(firstClasses) + 1))
|
||||||
elif typ.endswith('coverage'):
|
elif typ.endswith('coverage'):
|
||||||
self.Format = 3
|
self.Format = 3
|
||||||
debug("Parsing %s format %s" % (Type, self.Format))
|
log.debug("Parsing %s format %s", Type, self.Format)
|
||||||
c = ContextHelper(Type, self.Format)
|
c = ContextHelper(Type, self.Format)
|
||||||
coverages = tuple([] for i in range(c.DataLen))
|
coverages = tuple([] for i in range(c.DataLen))
|
||||||
while lines.peek()[0].endswith("coverage definition begin"):
|
while lines.peek()[0].endswith("coverage definition begin"):
|
||||||
@ -782,7 +783,7 @@ def parseReverseChainedSubst(self, lines, font, _lookupMap=None):
|
|||||||
def parseLookup(lines, tableTag, font, lookupMap=None):
|
def parseLookup(lines, tableTag, font, lookupMap=None):
|
||||||
line = lines.expect('lookup')
|
line = lines.expect('lookup')
|
||||||
_, name, typ = line
|
_, name, typ = line
|
||||||
debug("Parsing lookup type %s %s" % (typ, name))
|
log.debug("Parsing lookup type %s %s", typ, name)
|
||||||
lookup = ot.Lookup()
|
lookup = ot.Lookup()
|
||||||
with lines.until('lookup end'):
|
with lines.until('lookup end'):
|
||||||
|
|
||||||
@ -838,7 +839,7 @@ def parseGSUBGPOS(lines, font, tableTag):
|
|||||||
lookupMap = DeferredMapping()
|
lookupMap = DeferredMapping()
|
||||||
featureMap = DeferredMapping()
|
featureMap = DeferredMapping()
|
||||||
assert tableTag in ('GSUB', 'GPOS')
|
assert tableTag in ('GSUB', 'GPOS')
|
||||||
debug("Parsing", tableTag)
|
log.debug("Parsing %s", tableTag)
|
||||||
self = getattr(ot, tableTag)()
|
self = getattr(ot, tableTag)()
|
||||||
self.Version = 1.0
|
self.Version = 1.0
|
||||||
fields = {
|
fields = {
|
||||||
@ -933,7 +934,7 @@ def parseMarkFilteringSets(lines, font):
|
|||||||
return makeMarkFilteringSets(sets, font)
|
return makeMarkFilteringSets(sets, font)
|
||||||
|
|
||||||
def parseGDEF(lines, font):
|
def parseGDEF(lines, font):
|
||||||
debug("Parsing GDEF")
|
log.debug("Parsing GDEF")
|
||||||
self = ot.GDEF()
|
self = ot.GDEF()
|
||||||
fields = {
|
fields = {
|
||||||
'class definition begin':
|
'class definition begin':
|
||||||
@ -954,7 +955,7 @@ def parseGDEF(lines, font):
|
|||||||
while lines.peek() is not None:
|
while lines.peek() is not None:
|
||||||
typ = lines.peek()[0].lower()
|
typ = lines.peek()[0].lower()
|
||||||
if typ not in fields:
|
if typ not in fields:
|
||||||
debug ('Skipping', line)
|
log.debug('Skipping %s', typ)
|
||||||
next(lines)
|
next(lines)
|
||||||
continue
|
continue
|
||||||
attr,parser = fields[typ]
|
attr,parser = fields[typ]
|
||||||
@ -964,7 +965,7 @@ def parseGDEF(lines, font):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def parseTable(lines, font, tableTag=None):
|
def parseTable(lines, font, tableTag=None):
|
||||||
debug("Parsing table")
|
log.debug("Parsing table")
|
||||||
line = lines.peek()
|
line = lines.peek()
|
||||||
if line[0].split()[0] == 'FontDame':
|
if line[0].split()[0] == 'FontDame':
|
||||||
next(lines)
|
next(lines)
|
||||||
@ -1103,13 +1104,18 @@ class MockFont(object):
|
|||||||
return self._glyphOrder
|
return self._glyphOrder
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
|
from fontTools import configLogger
|
||||||
|
# configure the library logger (for >= WARNING)
|
||||||
|
configLogger()
|
||||||
|
# comment this out to enable debug messages from mtiLib's logger
|
||||||
|
# log.setLevel(logging.DEBUG)
|
||||||
font = MockFont()
|
font = MockFont()
|
||||||
tableTag = None
|
tableTag = None
|
||||||
if args[0].startswith('-t'):
|
if args[0].startswith('-t'):
|
||||||
tableTag = args[0][2:]
|
tableTag = args[0][2:]
|
||||||
del args[0]
|
del args[0]
|
||||||
for f in args:
|
for f in args:
|
||||||
debug("Processing", f)
|
log.debug("Processing %s", f)
|
||||||
table = build(open(f, 'rt', encoding="utf-8"), font, tableTag=tableTag)
|
table = build(open(f, 'rt', encoding="utf-8"), font, tableTag=tableTag)
|
||||||
blob = table.compile(font) # Make sure it compiles
|
blob = table.compile(font) # Make sure it compiles
|
||||||
decompiled = table.__class__()
|
decompiled = table.__class__()
|
||||||
|
@ -8,10 +8,12 @@ from fontTools import ttLib
|
|||||||
from fontTools.ttLib.tables import otTables
|
from fontTools.ttLib.tables import otTables
|
||||||
from fontTools.misc import psCharStrings
|
from fontTools.misc import psCharStrings
|
||||||
from fontTools.pens.boundsPen import BoundsPen
|
from fontTools.pens.boundsPen import BoundsPen
|
||||||
|
from fontTools.misc.loggingTools import Timer
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
import time
|
|
||||||
import array
|
import array
|
||||||
|
import logging
|
||||||
|
from types import MethodType
|
||||||
|
|
||||||
__usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
|
__usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
|
||||||
|
|
||||||
@ -316,6 +318,22 @@ Example:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def _log_glyphs(self, glyphs, font=None):
|
||||||
|
self.info("Glyph names: %s", sorted(glyphs))
|
||||||
|
if font:
|
||||||
|
reverseGlyphMap = font.getReverseGlyphMap()
|
||||||
|
self.info("Glyph IDs: %s", sorted(reverseGlyphMap[g] for g in glyphs))
|
||||||
|
|
||||||
|
# bind "glyphs" function to 'log' object
|
||||||
|
log.glyphs = MethodType(_log_glyphs, log)
|
||||||
|
|
||||||
|
# I use a different timing channel so I can configure it separately from the
|
||||||
|
# main module's logger
|
||||||
|
timer = Timer(logger=logging.getLogger(__name__+".timer"))
|
||||||
|
|
||||||
|
|
||||||
def _add_method(*clazzes):
|
def _add_method(*clazzes):
|
||||||
"""Returns a decorator function that adds a new method to one or
|
"""Returns a decorator function that adds a new method to one or
|
||||||
more classes."""
|
more classes."""
|
||||||
@ -2409,6 +2427,9 @@ class Options(object):
|
|||||||
self.canonical_order = False # Order tables as recommended
|
self.canonical_order = False # Order tables as recommended
|
||||||
self.flavor = None # May be 'woff' or 'woff2'
|
self.flavor = None # May be 'woff' or 'woff2'
|
||||||
self.desubroutinize = False # Desubroutinize CFF CharStrings
|
self.desubroutinize = False # Desubroutinize CFF CharStrings
|
||||||
|
self.verbose = False
|
||||||
|
self.timing = False
|
||||||
|
self.xml = False
|
||||||
|
|
||||||
self.set(**kwargs)
|
self.set(**kwargs)
|
||||||
|
|
||||||
@ -2494,15 +2515,12 @@ class Subsetter(object):
|
|||||||
class MissingGlyphsSubsettingError(SubsettingError): pass
|
class MissingGlyphsSubsettingError(SubsettingError): pass
|
||||||
class MissingUnicodesSubsettingError(SubsettingError): pass
|
class MissingUnicodesSubsettingError(SubsettingError): pass
|
||||||
|
|
||||||
def __init__(self, options=None, log=None):
|
def __init__(self, options=None):
|
||||||
|
|
||||||
if not log:
|
|
||||||
log = Logger()
|
|
||||||
if not options:
|
if not options:
|
||||||
options = Options()
|
options = Options()
|
||||||
|
|
||||||
self.options = options
|
self.options = options
|
||||||
self.log = log
|
|
||||||
self.unicodes_requested = set()
|
self.unicodes_requested = set()
|
||||||
self.glyph_names_requested = set()
|
self.glyph_names_requested = set()
|
||||||
self.glyph_ids_requested = set()
|
self.glyph_ids_requested = set()
|
||||||
@ -2524,23 +2542,23 @@ class Subsetter(object):
|
|||||||
if(tag.strip() in self.options.drop_tables or
|
if(tag.strip() in self.options.drop_tables or
|
||||||
(tag.strip() in self.options.hinting_tables and not self.options.hinting) or
|
(tag.strip() in self.options.hinting_tables and not self.options.hinting) or
|
||||||
(tag == 'kern' and (not self.options.legacy_kern and 'GPOS' in font))):
|
(tag == 'kern' and (not self.options.legacy_kern and 'GPOS' in font))):
|
||||||
self.log(tag, "dropped")
|
log.info("%s dropped", tag)
|
||||||
del font[tag]
|
del font[tag]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
clazz = ttLib.getTableClass(tag)
|
clazz = ttLib.getTableClass(tag)
|
||||||
|
|
||||||
if hasattr(clazz, 'prune_pre_subset'):
|
if hasattr(clazz, 'prune_pre_subset'):
|
||||||
|
with timer("load '%s'" % tag):
|
||||||
table = font[tag]
|
table = font[tag]
|
||||||
self.log.lapse("load '%s'" % tag)
|
with timer("prune '%s'" % tag):
|
||||||
retain = table.prune_pre_subset(self.options)
|
retain = table.prune_pre_subset(self.options)
|
||||||
self.log.lapse("prune '%s'" % tag)
|
|
||||||
if not retain:
|
if not retain:
|
||||||
self.log(tag, "pruned to empty; dropped")
|
log.info("%s pruned to empty; dropped", tag)
|
||||||
del font[tag]
|
del font[tag]
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.log(tag, "pruned")
|
log.info("%s pruned", tag)
|
||||||
|
|
||||||
def _closure_glyphs(self, font):
|
def _closure_glyphs(self, font):
|
||||||
|
|
||||||
@ -2558,7 +2576,7 @@ class Subsetter(object):
|
|||||||
self.glyphs_missing.update(i for i in self.glyph_ids_requested
|
self.glyphs_missing.update(i for i in self.glyph_ids_requested
|
||||||
if i >= len(glyph_order))
|
if i >= len(glyph_order))
|
||||||
if self.glyphs_missing:
|
if self.glyphs_missing:
|
||||||
self.log("Missing requested glyphs: %s" % self.glyphs_missing)
|
log.info("Missing requested glyphs: %s", self.glyphs_missing)
|
||||||
if not self.options.ignore_missing_glyphs:
|
if not self.options.ignore_missing_glyphs:
|
||||||
raise self.MissingGlyphsSubsettingError(self.glyphs_missing)
|
raise self.MissingGlyphsSubsettingError(self.glyphs_missing)
|
||||||
|
|
||||||
@ -2566,13 +2584,13 @@ class Subsetter(object):
|
|||||||
|
|
||||||
self.unicodes_missing = set()
|
self.unicodes_missing = set()
|
||||||
if 'cmap' in font:
|
if 'cmap' in font:
|
||||||
|
with timer("close glyph list over 'cmap'"):
|
||||||
font['cmap'].closure_glyphs(self)
|
font['cmap'].closure_glyphs(self)
|
||||||
self.glyphs.intersection_update(realGlyphs)
|
self.glyphs.intersection_update(realGlyphs)
|
||||||
self.log.lapse("close glyph list over 'cmap'")
|
|
||||||
self.glyphs_cmaped = frozenset(self.glyphs)
|
self.glyphs_cmaped = frozenset(self.glyphs)
|
||||||
if self.unicodes_missing:
|
if self.unicodes_missing:
|
||||||
missing = ["U+%04X" % u for u in self.unicodes_missing]
|
missing = ["U+%04X" % u for u in self.unicodes_missing]
|
||||||
self.log("Missing glyphs for requested Unicodes: %s" % missing)
|
log.info("Missing glyphs for requested Unicodes: %s", missing)
|
||||||
if not self.options.ignore_missing_unicodes:
|
if not self.options.ignore_missing_unicodes:
|
||||||
raise self.MissingUnicodesSubsettingError(missing)
|
raise self.MissingUnicodesSubsettingError(missing)
|
||||||
del missing
|
del missing
|
||||||
@ -2580,67 +2598,67 @@ class Subsetter(object):
|
|||||||
if self.options.notdef_glyph:
|
if self.options.notdef_glyph:
|
||||||
if 'glyf' in font:
|
if 'glyf' in font:
|
||||||
self.glyphs.add(font.getGlyphName(0))
|
self.glyphs.add(font.getGlyphName(0))
|
||||||
self.log("Added gid0 to subset")
|
log.info("Added gid0 to subset")
|
||||||
else:
|
else:
|
||||||
self.glyphs.add('.notdef')
|
self.glyphs.add('.notdef')
|
||||||
self.log("Added .notdef to subset")
|
log.info("Added .notdef to subset")
|
||||||
if self.options.recommended_glyphs:
|
if self.options.recommended_glyphs:
|
||||||
if 'glyf' in font:
|
if 'glyf' in font:
|
||||||
for i in range(min(4, len(font.getGlyphOrder()))):
|
for i in range(min(4, len(font.getGlyphOrder()))):
|
||||||
self.glyphs.add(font.getGlyphName(i))
|
self.glyphs.add(font.getGlyphName(i))
|
||||||
self.log("Added first four glyphs to subset")
|
log.info("Added first four glyphs to subset")
|
||||||
|
|
||||||
if 'GSUB' in font:
|
if 'GSUB' in font:
|
||||||
self.log("Closing glyph list over 'GSUB': %d glyphs before" %
|
with timer("close glyph list over 'GSUB'"):
|
||||||
|
log.info("Closing glyph list over 'GSUB': %d glyphs before",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
font['GSUB'].closure_glyphs(self)
|
font['GSUB'].closure_glyphs(self)
|
||||||
self.glyphs.intersection_update(realGlyphs)
|
self.glyphs.intersection_update(realGlyphs)
|
||||||
self.log("Closed glyph list over 'GSUB': %d glyphs after" %
|
log.info("Closed glyph list over 'GSUB': %d glyphs after",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
self.log.lapse("close glyph list over 'GSUB'")
|
|
||||||
self.glyphs_gsubed = frozenset(self.glyphs)
|
self.glyphs_gsubed = frozenset(self.glyphs)
|
||||||
|
|
||||||
if 'MATH' in font:
|
if 'MATH' in font:
|
||||||
self.log("Closing glyph list over 'MATH': %d glyphs before" %
|
with timer("close glyph list over 'MATH'"):
|
||||||
|
log.info("Closing glyph list over 'MATH': %d glyphs before",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
font['MATH'].closure_glyphs(self)
|
font['MATH'].closure_glyphs(self)
|
||||||
self.glyphs.intersection_update(realGlyphs)
|
self.glyphs.intersection_update(realGlyphs)
|
||||||
self.log("Closed glyph list over 'MATH': %d glyphs after" %
|
log.info("Closed glyph list over 'MATH': %d glyphs after",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
self.log.lapse("close glyph list over 'MATH'")
|
|
||||||
self.glyphs_mathed = frozenset(self.glyphs)
|
self.glyphs_mathed = frozenset(self.glyphs)
|
||||||
|
|
||||||
if 'COLR' in font:
|
if 'COLR' in font:
|
||||||
self.log("Closing glyph list over 'COLR': %d glyphs before" %
|
with timer("close glyph list over 'COLR'"):
|
||||||
|
log.info("Closing glyph list over 'COLR': %d glyphs before",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
font['COLR'].closure_glyphs(self)
|
font['COLR'].closure_glyphs(self)
|
||||||
self.glyphs.intersection_update(realGlyphs)
|
self.glyphs.intersection_update(realGlyphs)
|
||||||
self.log("Closed glyph list over 'COLR': %d glyphs after" %
|
log.info("Closed glyph list over 'COLR': %d glyphs after",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
self.log.lapse("close glyph list over 'COLR'")
|
|
||||||
self.glyphs_colred = frozenset(self.glyphs)
|
self.glyphs_colred = frozenset(self.glyphs)
|
||||||
|
|
||||||
if 'glyf' in font:
|
if 'glyf' in font:
|
||||||
self.log("Closing glyph list over 'glyf': %d glyphs before" %
|
with timer("close glyph list over 'glyf'"):
|
||||||
|
log.info("Closing glyph list over 'glyf': %d glyphs before",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
font['glyf'].closure_glyphs(self)
|
font['glyf'].closure_glyphs(self)
|
||||||
self.glyphs.intersection_update(realGlyphs)
|
self.glyphs.intersection_update(realGlyphs)
|
||||||
self.log("Closed glyph list over 'glyf': %d glyphs after" %
|
log.info("Closed glyph list over 'glyf': %d glyphs after",
|
||||||
len(self.glyphs))
|
len(self.glyphs))
|
||||||
self.log.glyphs(self.glyphs, font=font)
|
log.glyphs(self.glyphs, font=font)
|
||||||
self.log.lapse("close glyph list over 'glyf'")
|
|
||||||
self.glyphs_glyfed = frozenset(self.glyphs)
|
self.glyphs_glyfed = frozenset(self.glyphs)
|
||||||
|
|
||||||
self.glyphs_all = frozenset(self.glyphs)
|
self.glyphs_all = frozenset(self.glyphs)
|
||||||
|
|
||||||
self.log("Retaining %d glyphs: " % len(self.glyphs_all))
|
log.info("Retaining %d glyphs", len(self.glyphs_all))
|
||||||
|
|
||||||
del self.glyphs
|
del self.glyphs
|
||||||
|
|
||||||
@ -2650,27 +2668,27 @@ class Subsetter(object):
|
|||||||
clazz = ttLib.getTableClass(tag)
|
clazz = ttLib.getTableClass(tag)
|
||||||
|
|
||||||
if tag.strip() in self.options.no_subset_tables:
|
if tag.strip() in self.options.no_subset_tables:
|
||||||
self.log(tag, "subsetting not needed")
|
log.info("%s subsetting not needed", tag)
|
||||||
elif hasattr(clazz, 'subset_glyphs'):
|
elif hasattr(clazz, 'subset_glyphs'):
|
||||||
|
with timer("subset '%s'" % tag):
|
||||||
table = font[tag]
|
table = font[tag]
|
||||||
self.glyphs = self.glyphs_all
|
self.glyphs = self.glyphs_all
|
||||||
retain = table.subset_glyphs(self)
|
retain = table.subset_glyphs(self)
|
||||||
del self.glyphs
|
del self.glyphs
|
||||||
self.log.lapse("subset '%s'" % tag)
|
|
||||||
if not retain:
|
if not retain:
|
||||||
self.log(tag, "subsetted to empty; dropped")
|
log.info("%s subsetted to empty; dropped", tag)
|
||||||
del font[tag]
|
del font[tag]
|
||||||
else:
|
else:
|
||||||
self.log(tag, "subsetted")
|
log.info("%s subsetted", tag)
|
||||||
else:
|
else:
|
||||||
self.log(tag, "NOT subset; don't know how to subset; dropped")
|
log.info("%s NOT subset; don't know how to subset; dropped", tag)
|
||||||
del font[tag]
|
del font[tag]
|
||||||
|
|
||||||
|
with timer("subset GlyphOrder"):
|
||||||
glyphOrder = font.getGlyphOrder()
|
glyphOrder = font.getGlyphOrder()
|
||||||
glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
|
glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
|
||||||
font.setGlyphOrder(glyphOrder)
|
font.setGlyphOrder(glyphOrder)
|
||||||
font._buildReverseGlyphOrderDict()
|
font._buildReverseGlyphOrderDict()
|
||||||
self.log.lapse("subset GlyphOrder")
|
|
||||||
|
|
||||||
def _prune_post_subset(self, font):
|
def _prune_post_subset(self, font):
|
||||||
for tag in font.keys():
|
for tag in font.keys():
|
||||||
@ -2679,17 +2697,17 @@ class Subsetter(object):
|
|||||||
old_uniranges = font[tag].getUnicodeRanges()
|
old_uniranges = font[tag].getUnicodeRanges()
|
||||||
new_uniranges = font[tag].recalcUnicodeRanges(font, pruneOnly=True)
|
new_uniranges = font[tag].recalcUnicodeRanges(font, pruneOnly=True)
|
||||||
if old_uniranges != new_uniranges:
|
if old_uniranges != new_uniranges:
|
||||||
self.log(tag, "Unicode ranges pruned: %s" % sorted(new_uniranges))
|
log.info("%s Unicode ranges pruned: %s", tag, sorted(new_uniranges))
|
||||||
clazz = ttLib.getTableClass(tag)
|
clazz = ttLib.getTableClass(tag)
|
||||||
if hasattr(clazz, 'prune_post_subset'):
|
if hasattr(clazz, 'prune_post_subset'):
|
||||||
|
with timer("prune '%s'" % tag):
|
||||||
table = font[tag]
|
table = font[tag]
|
||||||
retain = table.prune_post_subset(self.options)
|
retain = table.prune_post_subset(self.options)
|
||||||
self.log.lapse("prune '%s'" % tag)
|
|
||||||
if not retain:
|
if not retain:
|
||||||
self.log(tag, "pruned to empty; dropped")
|
log.info("%s pruned to empty; dropped", tag)
|
||||||
del font[tag]
|
del font[tag]
|
||||||
else:
|
else:
|
||||||
self.log(tag, "pruned")
|
log.info("%s pruned", tag)
|
||||||
|
|
||||||
def subset(self, font):
|
def subset(self, font):
|
||||||
|
|
||||||
@ -2699,56 +2717,7 @@ class Subsetter(object):
|
|||||||
self._prune_post_subset(font)
|
self._prune_post_subset(font)
|
||||||
|
|
||||||
|
|
||||||
class Logger(object):
|
@timer("load font")
|
||||||
|
|
||||||
def __init__(self, verbose=False, xml=False, timing=False):
|
|
||||||
self.verbose = verbose
|
|
||||||
self.xml = xml
|
|
||||||
self.timing = timing
|
|
||||||
self.last_time = self.start_time = time.time()
|
|
||||||
|
|
||||||
def parse_opts(self, argv):
|
|
||||||
argv = argv[:]
|
|
||||||
for v in ['verbose', 'xml', 'timing']:
|
|
||||||
if "--"+v in argv:
|
|
||||||
setattr(self, v, True)
|
|
||||||
argv.remove("--"+v)
|
|
||||||
return argv
|
|
||||||
|
|
||||||
def __call__(self, *things):
|
|
||||||
if not self.verbose:
|
|
||||||
return
|
|
||||||
print(' '.join(str(x) for x in things))
|
|
||||||
|
|
||||||
def lapse(self, *things):
|
|
||||||
if not self.timing:
|
|
||||||
return
|
|
||||||
new_time = time.time()
|
|
||||||
print("Took %0.3fs to %s" %(new_time - self.last_time,
|
|
||||||
' '.join(str(x) for x in things)))
|
|
||||||
self.last_time = new_time
|
|
||||||
|
|
||||||
def glyphs(self, glyphs, font=None):
|
|
||||||
if not self.verbose:
|
|
||||||
return
|
|
||||||
self("Glyph names:", sorted(glyphs))
|
|
||||||
if font:
|
|
||||||
reverseGlyphMap = font.getReverseGlyphMap()
|
|
||||||
self("Glyph IDs: ", sorted(reverseGlyphMap[g] for g in glyphs))
|
|
||||||
|
|
||||||
def font(self, font, file=sys.stdout):
|
|
||||||
if not self.xml:
|
|
||||||
return
|
|
||||||
from fontTools.misc import xmlWriter
|
|
||||||
writer = xmlWriter.XMLWriter(file)
|
|
||||||
for tag in font.keys():
|
|
||||||
writer.begintag(tag)
|
|
||||||
writer.newline()
|
|
||||||
font[tag].toXML(writer, font)
|
|
||||||
writer.endtag(tag)
|
|
||||||
writer.newline()
|
|
||||||
|
|
||||||
|
|
||||||
def load_font(fontFile,
|
def load_font(fontFile,
|
||||||
options,
|
options,
|
||||||
allowVID=False,
|
allowVID=False,
|
||||||
@ -2783,6 +2752,7 @@ def load_font(fontFile,
|
|||||||
|
|
||||||
return font
|
return font
|
||||||
|
|
||||||
|
@timer("compile and save font")
|
||||||
def save_font(font, outfile, options):
|
def save_font(font, outfile, options):
|
||||||
if options.flavor and not hasattr(font, 'flavor'):
|
if options.flavor and not hasattr(font, 'flavor'):
|
||||||
raise Exception("fonttools version does not support flavors.")
|
raise Exception("fonttools version does not support flavors.")
|
||||||
@ -2816,7 +2786,9 @@ def parse_gids(s):
|
|||||||
def parse_glyphs(s):
|
def parse_glyphs(s):
|
||||||
return s.replace(',', ' ').split()
|
return s.replace(',', ' ').split()
|
||||||
|
|
||||||
|
@timer("make one with everything (TOTAL TIME)")
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
|
from fontTools import configLogger
|
||||||
|
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
@ -2825,9 +2797,6 @@ def main(args=None):
|
|||||||
print(__doc__)
|
print(__doc__)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
log = Logger()
|
|
||||||
args = log.parse_opts(args)
|
|
||||||
|
|
||||||
options = Options()
|
options = Options()
|
||||||
args = options.parse_opts(args,
|
args = options.parse_opts(args,
|
||||||
ignore_unknown=['gids', 'gids-file',
|
ignore_unknown=['gids', 'gids-file',
|
||||||
@ -2841,10 +2810,16 @@ def main(args=None):
|
|||||||
print("Try pyftsubset --help for more information.", file=sys.stderr)
|
print("Try pyftsubset --help for more information.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
configLogger(level=logging.INFO if options.verbose else logging.WARNING)
|
||||||
|
if options.timing:
|
||||||
|
timer.logger.setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
timer.logger.disabled = True
|
||||||
|
|
||||||
fontfile = args[0]
|
fontfile = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
|
|
||||||
subsetter = Subsetter(options=options, log=log)
|
subsetter = Subsetter(options=options)
|
||||||
outfile = fontfile + '.subset'
|
outfile = fontfile + '.subset'
|
||||||
glyphs = []
|
glyphs = []
|
||||||
gids = []
|
gids = []
|
||||||
@ -2896,7 +2871,8 @@ def main(args=None):
|
|||||||
|
|
||||||
dontLoadGlyphNames = not options.glyph_names and not glyphs
|
dontLoadGlyphNames = not options.glyph_names and not glyphs
|
||||||
font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
|
font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
|
||||||
log.lapse("load font")
|
|
||||||
|
with timer("compile glyph list"):
|
||||||
if wildcard_glyphs:
|
if wildcard_glyphs:
|
||||||
glyphs.extend(font.getGlyphOrder())
|
glyphs.extend(font.getGlyphOrder())
|
||||||
if wildcard_unicodes:
|
if wildcard_unicodes:
|
||||||
@ -2905,27 +2881,23 @@ def main(args=None):
|
|||||||
unicodes.extend(t.cmap.keys())
|
unicodes.extend(t.cmap.keys())
|
||||||
assert '' not in glyphs
|
assert '' not in glyphs
|
||||||
|
|
||||||
log.lapse("compile glyph list")
|
log.info("Text: '%s'" % text)
|
||||||
log("Text: '%s'" % text)
|
log.info("Unicodes: %s", unicodes)
|
||||||
log("Unicodes:", unicodes)
|
log.info("Glyphs: %s", glyphs)
|
||||||
log("Glyphs:", glyphs)
|
log.info("Gids: %s", gids)
|
||||||
log("Gids:", gids)
|
|
||||||
|
|
||||||
subsetter.populate(glyphs=glyphs, gids=gids, unicodes=unicodes, text=text)
|
subsetter.populate(glyphs=glyphs, gids=gids, unicodes=unicodes, text=text)
|
||||||
subsetter.subset(font)
|
subsetter.subset(font)
|
||||||
|
|
||||||
save_font(font, outfile, options)
|
save_font(font, outfile, options)
|
||||||
log.lapse("compile and save font")
|
|
||||||
|
|
||||||
log.last_time = log.start_time
|
if options.verbose:
|
||||||
log.lapse("make one with everything(TOTAL TIME)")
|
|
||||||
|
|
||||||
if log.verbose:
|
|
||||||
import os
|
import os
|
||||||
log("Input font:% 7d bytes: %s" % (os.path.getsize(fontfile), fontfile))
|
log.info("Input font:% 7d bytes: %s" % (os.path.getsize(fontfile), fontfile))
|
||||||
log("Subset font:% 7d bytes: %s" % (os.path.getsize(outfile), outfile))
|
log.info("Subset font:% 7d bytes: %s" % (os.path.getsize(outfile), outfile))
|
||||||
|
|
||||||
log.font(font)
|
if options.xml:
|
||||||
|
font.saveXML(sys.stdout)
|
||||||
|
|
||||||
font.close()
|
font.close()
|
||||||
|
|
||||||
@ -2933,7 +2905,6 @@ def main(args=None):
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'Options',
|
'Options',
|
||||||
'Subsetter',
|
'Subsetter',
|
||||||
'Logger',
|
|
||||||
'load_font',
|
'load_font',
|
||||||
'save_font',
|
'save_font',
|
||||||
'parse_gids',
|
'parse_gids',
|
||||||
|
@ -43,10 +43,14 @@ Dumping 'prep' table...
|
|||||||
|
|
||||||
from __future__ import print_function, division, absolute_import
|
from __future__ import print_function, division, absolute_import
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
|
from fontTools.misc.loggingTools import deprecateArgument, deprecateFunction
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class TTLibError(Exception): pass
|
class TTLibError(Exception): pass
|
||||||
|
|
||||||
|
|
||||||
@ -60,8 +64,8 @@ class TTFont(object):
|
|||||||
|
|
||||||
def __init__(self, file=None, res_name_or_index=None,
|
def __init__(self, file=None, res_name_or_index=None,
|
||||||
sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False,
|
sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False,
|
||||||
verbose=False, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False,
|
verbose=None, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False,
|
||||||
recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=False):
|
recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=None):
|
||||||
|
|
||||||
"""The constructor can be called with a few different arguments.
|
"""The constructor can be called with a few different arguments.
|
||||||
When reading a font from disk, 'file' should be either a pathname
|
When reading a font from disk, 'file' should be either a pathname
|
||||||
@ -119,8 +123,13 @@ class TTFont(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from fontTools.ttLib import sfnt
|
from fontTools.ttLib import sfnt
|
||||||
self.verbose = verbose
|
|
||||||
self.quiet = quiet
|
for name in ("verbose", "quiet"):
|
||||||
|
val = locals().get(name)
|
||||||
|
if val is not None:
|
||||||
|
deprecateArgument(name, "configure logging instead")
|
||||||
|
setattr(self, name, val)
|
||||||
|
|
||||||
self.lazy = lazy
|
self.lazy = lazy
|
||||||
self.recalcBBoxes = recalcBBoxes
|
self.recalcBBoxes = recalcBBoxes
|
||||||
self.recalcTimestamp = recalcTimestamp
|
self.recalcTimestamp = recalcTimestamp
|
||||||
@ -231,7 +240,7 @@ class TTFont(object):
|
|||||||
if closeStream:
|
if closeStream:
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
def saveXML(self, fileOrPath, progress=None, quiet=False,
|
def saveXML(self, fileOrPath, progress=None, quiet=None,
|
||||||
tables=None, skipTables=None, splitTables=False, disassembleInstructions=True,
|
tables=None, skipTables=None, splitTables=False, disassembleInstructions=True,
|
||||||
bitmapGlyphDataFormat='raw'):
|
bitmapGlyphDataFormat='raw'):
|
||||||
"""Export the font as TTX (an XML-based text file), or as a series of text
|
"""Export the font as TTX (an XML-based text file), or as a series of text
|
||||||
@ -244,6 +253,9 @@ class TTFont(object):
|
|||||||
from fontTools import version
|
from fontTools import version
|
||||||
from fontTools.misc import xmlWriter
|
from fontTools.misc import xmlWriter
|
||||||
|
|
||||||
|
if quiet is not None:
|
||||||
|
deprecateArgument("quiet", "configure logging instead")
|
||||||
|
|
||||||
self.disassembleInstructions = disassembleInstructions
|
self.disassembleInstructions = disassembleInstructions
|
||||||
self.bitmapGlyphDataFormat = bitmapGlyphDataFormat
|
self.bitmapGlyphDataFormat = bitmapGlyphDataFormat
|
||||||
if not tables:
|
if not tables:
|
||||||
@ -287,7 +299,7 @@ class TTFont(object):
|
|||||||
writer.newline()
|
writer.newline()
|
||||||
else:
|
else:
|
||||||
tableWriter = writer
|
tableWriter = writer
|
||||||
self._tableToXML(tableWriter, tag, progress, quiet)
|
self._tableToXML(tableWriter, tag, progress)
|
||||||
if splitTables:
|
if splitTables:
|
||||||
tableWriter.endtag("ttFont")
|
tableWriter.endtag("ttFont")
|
||||||
tableWriter.newline()
|
tableWriter.newline()
|
||||||
@ -297,10 +309,10 @@ class TTFont(object):
|
|||||||
writer.endtag("ttFont")
|
writer.endtag("ttFont")
|
||||||
writer.newline()
|
writer.newline()
|
||||||
writer.close()
|
writer.close()
|
||||||
if self.verbose:
|
|
||||||
debugmsg("Done dumping TTX")
|
|
||||||
|
|
||||||
def _tableToXML(self, writer, tag, progress, quiet):
|
def _tableToXML(self, writer, tag, progress, quiet=None):
|
||||||
|
if quiet is not None:
|
||||||
|
deprecateArgument("quiet", "configure logging instead")
|
||||||
if tag in self:
|
if tag in self:
|
||||||
table = self[tag]
|
table = self[tag]
|
||||||
report = "Dumping '%s' table..." % tag
|
report = "Dumping '%s' table..." % tag
|
||||||
@ -308,11 +320,7 @@ class TTFont(object):
|
|||||||
report = "No '%s' table found." % tag
|
report = "No '%s' table found." % tag
|
||||||
if progress:
|
if progress:
|
||||||
progress.setLabel(report)
|
progress.setLabel(report)
|
||||||
elif self.verbose:
|
log.info(report)
|
||||||
debugmsg(report)
|
|
||||||
else:
|
|
||||||
if not quiet:
|
|
||||||
print(report)
|
|
||||||
if tag not in self:
|
if tag not in self:
|
||||||
return
|
return
|
||||||
xmlTag = tagToXML(tag)
|
xmlTag = tagToXML(tag)
|
||||||
@ -332,10 +340,13 @@ class TTFont(object):
|
|||||||
writer.newline()
|
writer.newline()
|
||||||
writer.newline()
|
writer.newline()
|
||||||
|
|
||||||
def importXML(self, fileOrPath, progress=None, quiet=False):
|
def importXML(self, fileOrPath, progress=None, quiet=None):
|
||||||
"""Import a TTX file (an XML-based text format), so as to recreate
|
"""Import a TTX file (an XML-based text format), so as to recreate
|
||||||
a font object.
|
a font object.
|
||||||
"""
|
"""
|
||||||
|
if quiet is not None:
|
||||||
|
deprecateArgument("quiet", "configure logging instead")
|
||||||
|
|
||||||
if "maxp" in self and "post" in self:
|
if "maxp" in self and "post" in self:
|
||||||
# Make sure the glyph order is loaded, as it otherwise gets
|
# Make sure the glyph order is loaded, as it otherwise gets
|
||||||
# lost if the XML doesn't contain the glyph order, yet does
|
# lost if the XML doesn't contain the glyph order, yet does
|
||||||
@ -345,7 +356,7 @@ class TTFont(object):
|
|||||||
|
|
||||||
from fontTools.misc import xmlReader
|
from fontTools.misc import xmlReader
|
||||||
|
|
||||||
reader = xmlReader.XMLReader(fileOrPath, self, progress, quiet)
|
reader = xmlReader.XMLReader(fileOrPath, self, progress)
|
||||||
reader.read()
|
reader.read()
|
||||||
|
|
||||||
def isLoaded(self, tag):
|
def isLoaded(self, tag):
|
||||||
@ -391,21 +402,20 @@ class TTFont(object):
|
|||||||
return table
|
return table
|
||||||
if self.reader is not None:
|
if self.reader is not None:
|
||||||
import traceback
|
import traceback
|
||||||
if self.verbose:
|
log.debug("Reading '%s' table from disk", tag)
|
||||||
debugmsg("Reading '%s' table from disk" % tag)
|
|
||||||
data = self.reader[tag]
|
data = self.reader[tag]
|
||||||
tableClass = getTableClass(tag)
|
tableClass = getTableClass(tag)
|
||||||
table = tableClass(tag)
|
table = tableClass(tag)
|
||||||
self.tables[tag] = table
|
self.tables[tag] = table
|
||||||
if self.verbose:
|
log.debug("Decompiling '%s' table", tag)
|
||||||
debugmsg("Decompiling '%s' table" % tag)
|
|
||||||
try:
|
try:
|
||||||
table.decompile(data, self)
|
table.decompile(data, self)
|
||||||
except:
|
except:
|
||||||
if not self.ignoreDecompileErrors:
|
if not self.ignoreDecompileErrors:
|
||||||
raise
|
raise
|
||||||
# fall back to DefaultTable, retaining the binary table data
|
# fall back to DefaultTable, retaining the binary table data
|
||||||
print("An exception occurred during the decompilation of the '%s' table" % tag)
|
log.exception(
|
||||||
|
"An exception occurred during the decompilation of the '%s' table", tag)
|
||||||
from .tables.DefaultTable import DefaultTable
|
from .tables.DefaultTable import DefaultTable
|
||||||
file = StringIO()
|
file = StringIO()
|
||||||
traceback.print_exc(file=file)
|
traceback.print_exc(file=file)
|
||||||
@ -634,8 +644,7 @@ class TTFont(object):
|
|||||||
else:
|
else:
|
||||||
done.append(masterTable)
|
done.append(masterTable)
|
||||||
tabledata = self.getTableData(tag)
|
tabledata = self.getTableData(tag)
|
||||||
if self.verbose:
|
log.debug("writing '%s' table to disk", tag)
|
||||||
debugmsg("writing '%s' table to disk" % tag)
|
|
||||||
writer[tag] = tabledata
|
writer[tag] = tabledata
|
||||||
done.append(tag)
|
done.append(tag)
|
||||||
|
|
||||||
@ -644,12 +653,10 @@ class TTFont(object):
|
|||||||
"""
|
"""
|
||||||
tag = Tag(tag)
|
tag = Tag(tag)
|
||||||
if self.isLoaded(tag):
|
if self.isLoaded(tag):
|
||||||
if self.verbose:
|
log.debug("compiling '%s' table", tag)
|
||||||
debugmsg("compiling '%s' table" % tag)
|
|
||||||
return self.tables[tag].compile(self)
|
return self.tables[tag].compile(self)
|
||||||
elif self.reader and tag in self.reader:
|
elif self.reader and tag in self.reader:
|
||||||
if self.verbose:
|
log.debug("Reading '%s' table from disk", tag)
|
||||||
debugmsg("Reading '%s' table from disk" % tag)
|
|
||||||
return self.reader[tag]
|
return self.reader[tag]
|
||||||
else:
|
else:
|
||||||
raise KeyError(tag)
|
raise KeyError(tag)
|
||||||
@ -901,6 +908,7 @@ def xmlToTag(tag):
|
|||||||
return Tag(tag + " " * (4 - len(tag)))
|
return Tag(tag + " " * (4 - len(tag)))
|
||||||
|
|
||||||
|
|
||||||
|
@deprecateFunction("use logging instead", category=DeprecationWarning)
|
||||||
def debugmsg(msg):
|
def debugmsg(msg):
|
||||||
import time
|
import time
|
||||||
print(msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time())))
|
print(msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time())))
|
||||||
|
@ -18,6 +18,10 @@ from fontTools.misc import sstruct
|
|||||||
from fontTools.ttLib import getSearchRange
|
from fontTools.ttLib import getSearchRange
|
||||||
import struct
|
import struct
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SFNTReader(object):
|
class SFNTReader(object):
|
||||||
@ -118,8 +122,8 @@ class SFNTReader(object):
|
|||||||
# Be obnoxious, and barf when it's wrong
|
# Be obnoxious, and barf when it's wrong
|
||||||
assert checksum == entry.checkSum, "bad checksum for '%s' table" % tag
|
assert checksum == entry.checkSum, "bad checksum for '%s' table" % tag
|
||||||
elif checksum != entry.checkSum:
|
elif checksum != entry.checkSum:
|
||||||
# Be friendly, and just print a warning.
|
# Be friendly, and just log a warning.
|
||||||
print("bad checksum for '%s' table" % tag)
|
log.warning("bad checksum for '%s' table", tag)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def __delitem__(self, tag):
|
def __delitem__(self, tag):
|
||||||
|
@ -4,8 +4,11 @@ from __future__ import print_function, division, absolute_import
|
|||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
from fontTools.misc import sstruct
|
from fontTools.misc import sstruct
|
||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
bigGlyphMetricsFormat = """
|
bigGlyphMetricsFormat = """
|
||||||
> # big endian
|
> # big endian
|
||||||
height: B
|
height: B
|
||||||
@ -48,7 +51,7 @@ class BitmapGlyphMetrics(object):
|
|||||||
if name in metricNames:
|
if name in metricNames:
|
||||||
vars(self)[name] = safeEval(attrs['value'])
|
vars(self)[name] = safeEval(attrs['value'])
|
||||||
else:
|
else:
|
||||||
print("Warning: unknown name '%s' being ignored in %s." % name, self.__class__.__name__)
|
log.warning("unknown name '%s' being ignored in %s.", name, self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
class BigGlyphMetrics(BitmapGlyphMetrics):
|
class BigGlyphMetrics(BitmapGlyphMetrics):
|
||||||
|
@ -7,6 +7,10 @@ from . import DefaultTable
|
|||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
ebdtTableVersionFormat = """
|
ebdtTableVersionFormat = """
|
||||||
> # big endian
|
> # big endian
|
||||||
@ -166,7 +170,7 @@ class table_E_B_D_T_(DefaultTable.DefaultTable):
|
|||||||
assert glyphName not in bitmapGlyphDict, "Duplicate glyphs with the same name '%s' in the same strike." % glyphName
|
assert glyphName not in bitmapGlyphDict, "Duplicate glyphs with the same name '%s' in the same strike." % glyphName
|
||||||
bitmapGlyphDict[glyphName] = curGlyph
|
bitmapGlyphDict[glyphName] = curGlyph
|
||||||
else:
|
else:
|
||||||
print("Warning: %s being ignored by %s", name, self.__class__.__name__)
|
log.warning("%s being ignored by %s", name, self.__class__.__name__)
|
||||||
|
|
||||||
# Grow the strike data array to the appropriate size. The XML
|
# Grow the strike data array to the appropriate size. The XML
|
||||||
# format allows the strike index value to be out of order.
|
# format allows the strike index value to be out of order.
|
||||||
@ -196,7 +200,7 @@ class EbdtComponent(object):
|
|||||||
if name in componentNames:
|
if name in componentNames:
|
||||||
vars(self)[name] = safeEval(attrs['value'])
|
vars(self)[name] = safeEval(attrs['value'])
|
||||||
else:
|
else:
|
||||||
print("Warning: unknown name '%s' being ignored by EbdtComponent." % name)
|
log.warning("unknown name '%s' being ignored by EbdtComponent.", name)
|
||||||
|
|
||||||
# Helper functions for dealing with binary.
|
# Helper functions for dealing with binary.
|
||||||
|
|
||||||
@ -478,7 +482,7 @@ def _createBitmapPlusMetricsMixin(metricsClass):
|
|||||||
self.metrics = metricsClass()
|
self.metrics = metricsClass()
|
||||||
self.metrics.fromXML(name, attrs, content, ttFont)
|
self.metrics.fromXML(name, attrs, content, ttFont)
|
||||||
elif name == oppositeMetricsName:
|
elif name == oppositeMetricsName:
|
||||||
print("Warning: %s being ignored in format %d." % oppositeMetricsName, self.getFormat())
|
log.warning("Warning: %s being ignored in format %d.", oppositeMetricsName, self.getFormat())
|
||||||
|
|
||||||
return BitmapPlusMetricsMixin
|
return BitmapPlusMetricsMixin
|
||||||
|
|
||||||
@ -692,7 +696,7 @@ class ComponentBitmapGlyph(BitmapGlyph):
|
|||||||
curComponent.fromXML(name, attrs, content, ttFont)
|
curComponent.fromXML(name, attrs, content, ttFont)
|
||||||
self.componentArray.append(curComponent)
|
self.componentArray.append(curComponent)
|
||||||
else:
|
else:
|
||||||
print("Warning: '%s' being ignored in component array." % name)
|
log.warning("'%s' being ignored in component array.", name)
|
||||||
|
|
||||||
|
|
||||||
class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph):
|
class ebdt_bitmap_format_8(BitmapPlusSmallMetricsMixin, ComponentBitmapGlyph):
|
||||||
|
@ -7,6 +7,10 @@ from .BitmapGlyphMetrics import BigGlyphMetrics, bigGlyphMetricsFormat, SmallGly
|
|||||||
import struct
|
import struct
|
||||||
import itertools
|
import itertools
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
eblcHeaderFormat = """
|
eblcHeaderFormat = """
|
||||||
> # big endian
|
> # big endian
|
||||||
@ -293,7 +297,7 @@ class BitmapSizeTable(object):
|
|||||||
elif name in dataNames:
|
elif name in dataNames:
|
||||||
vars(self)[name] = safeEval(attrs['value'])
|
vars(self)[name] = safeEval(attrs['value'])
|
||||||
else:
|
else:
|
||||||
print("Warning: unknown name '%s' being ignored in BitmapSizeTable." % name)
|
log.warning("unknown name '%s' being ignored in BitmapSizeTable.", name)
|
||||||
|
|
||||||
|
|
||||||
class SbitLineMetrics(object):
|
class SbitLineMetrics(object):
|
||||||
@ -503,7 +507,7 @@ class FixedSizeIndexSubTableMixin(object):
|
|||||||
self.metrics = BigGlyphMetrics()
|
self.metrics = BigGlyphMetrics()
|
||||||
self.metrics.fromXML(name, attrs, content, ttFont)
|
self.metrics.fromXML(name, attrs, content, ttFont)
|
||||||
elif name == SmallGlyphMetrics.__name__:
|
elif name == SmallGlyphMetrics.__name__:
|
||||||
print("Warning: SmallGlyphMetrics being ignored in format %d." % self.indexFormat)
|
log.warning("SmallGlyphMetrics being ignored in format %d.", self.indexFormat)
|
||||||
|
|
||||||
def padBitmapData(self, data):
|
def padBitmapData(self, data):
|
||||||
# Make sure that the data isn't bigger than the fixed size.
|
# Make sure that the data isn't bigger than the fixed size.
|
||||||
|
@ -3,9 +3,11 @@ from fontTools.misc.py23 import *
|
|||||||
from fontTools.misc import sstruct
|
from fontTools.misc import sstruct
|
||||||
from fontTools.misc.textTools import safeEval, num2binary, binary2num
|
from fontTools.misc.textTools import safeEval, num2binary, binary2num
|
||||||
from fontTools.ttLib.tables import DefaultTable
|
from fontTools.ttLib.tables import DefaultTable
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# panose classification
|
# panose classification
|
||||||
|
|
||||||
panoseFormat = """
|
panoseFormat = """
|
||||||
@ -116,7 +118,7 @@ class table_O_S_2f_2(DefaultTable.DefaultTable):
|
|||||||
from fontTools import ttLib
|
from fontTools import ttLib
|
||||||
raise ttLib.TTLibError("unknown format for OS/2 table: version %s" % self.version)
|
raise ttLib.TTLibError("unknown format for OS/2 table: version %s" % self.version)
|
||||||
if len(data):
|
if len(data):
|
||||||
warnings.warn("too much 'OS/2' table data")
|
log.warning("too much 'OS/2' table data")
|
||||||
|
|
||||||
self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
|
self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
|
||||||
|
|
||||||
|
@ -8,6 +8,11 @@ except ImportError:
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import struct
|
import struct
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
__doc__="""
|
__doc__="""
|
||||||
Compiles/decompiles version 0 and 1 SVG tables from/to XML.
|
Compiles/decompiles version 0 and 1 SVG tables from/to XML.
|
||||||
@ -104,7 +109,8 @@ class table_S_V_G_(DefaultTable.DefaultTable):
|
|||||||
self.decompile_format_1(data, ttFont)
|
self.decompile_format_1(data, ttFont)
|
||||||
else:
|
else:
|
||||||
if self.version != 0:
|
if self.version != 0:
|
||||||
print("Unknown SVG table version '%s'. Decompiling as version 0." % (self.version))
|
log.warning(
|
||||||
|
"Unknown SVG table version '%s'. Decompiling as version 0.", self.version)
|
||||||
self.decompile_format_0(data, ttFont)
|
self.decompile_format_0(data, ttFont)
|
||||||
|
|
||||||
def decompile_format_0(self, data, ttFont):
|
def decompile_format_0(self, data, ttFont):
|
||||||
@ -314,7 +320,7 @@ class table_S_V_G_(DefaultTable.DefaultTable):
|
|||||||
if self.colorPalettes.numColorParams == 0:
|
if self.colorPalettes.numColorParams == 0:
|
||||||
self.colorPalettes = None
|
self.colorPalettes = None
|
||||||
else:
|
else:
|
||||||
print("Unknown", name, content)
|
log.warning("Unknown %s %s", name, content)
|
||||||
|
|
||||||
class DocumentIndexEntry(object):
|
class DocumentIndexEntry(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -8,9 +8,11 @@ from fontTools.ttLib import TTLibError
|
|||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
import array
|
import array
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Apple's documentation of 'avar':
|
# Apple's documentation of 'avar':
|
||||||
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html
|
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html
|
||||||
|
|
||||||
@ -22,6 +24,7 @@ AVAR_HEADER_FORMAT = """
|
|||||||
|
|
||||||
|
|
||||||
class table__a_v_a_r(DefaultTable.DefaultTable):
|
class table__a_v_a_r(DefaultTable.DefaultTable):
|
||||||
|
|
||||||
dependencies = ["fvar"]
|
dependencies = ["fvar"]
|
||||||
|
|
||||||
def __init__(self, tag=None):
|
def __init__(self, tag=None):
|
||||||
@ -57,7 +60,7 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
|
|||||||
fromValue, toValue = struct.unpack(">hh", data[pos:pos+4])
|
fromValue, toValue = struct.unpack(">hh", data[pos:pos+4])
|
||||||
segments[fixedToFloat(fromValue, 14)] = fixedToFloat(toValue, 14)
|
segments[fixedToFloat(fromValue, 14)] = fixedToFloat(toValue, 14)
|
||||||
pos = pos + 4
|
pos = pos + 4
|
||||||
self.fixupSegments_(warn=warnings.warn)
|
self.fixupSegments_()
|
||||||
|
|
||||||
def toXML(self, writer, ttFont, progress=None):
|
def toXML(self, writer, ttFont, progress=None):
|
||||||
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
|
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
|
||||||
@ -81,14 +84,14 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
|
|||||||
fromValue = safeEval(elementAttrs["from"])
|
fromValue = safeEval(elementAttrs["from"])
|
||||||
toValue = safeEval(elementAttrs["to"])
|
toValue = safeEval(elementAttrs["to"])
|
||||||
if fromValue in segment:
|
if fromValue in segment:
|
||||||
warnings.warn("duplicate entry for %s in axis '%s'" %
|
log.warning("duplicate entry for %s in axis '%s'",
|
||||||
(fromValue, axis))
|
fromValue, axis)
|
||||||
segment[fromValue] = toValue
|
segment[fromValue] = toValue
|
||||||
self.fixupSegments_(warn=warnings.warn)
|
self.fixupSegments_()
|
||||||
|
|
||||||
def fixupSegments_(self, warn):
|
def fixupSegments_(self):
|
||||||
for axis, mappings in self.segments.items():
|
for axis, mappings in self.segments.items():
|
||||||
for k in [-1.0, 0.0, 1.0]:
|
for k in [-1.0, 0.0, 1.0]:
|
||||||
if mappings.get(k) != k:
|
if mappings.get(k) != k:
|
||||||
warn("avar axis '%s' should map %s to %s" % (axis, k, k))
|
log.warning("avar axis '%s' should map %s to %s", axis, k, k)
|
||||||
mappings[k] = k
|
mappings[k] = k
|
||||||
|
@ -7,6 +7,7 @@ from fontTools.ttLib import TTLibError
|
|||||||
from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r
|
from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r
|
||||||
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis
|
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis
|
||||||
import collections
|
import collections
|
||||||
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
@ -65,15 +66,17 @@ class AxisVariationTableTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_fixupSegments(self):
|
def test_fixupSegments(self):
|
||||||
avar = table__a_v_a_r()
|
avar = table__a_v_a_r()
|
||||||
|
logger = logging.getLogger(table__a_v_a_r.__module__)
|
||||||
|
sio = StringIO()
|
||||||
|
logger.addHandler(logging.StreamHandler(stream=sio))
|
||||||
avar.segments = {"wdth": {0.3: 0.8, 1.0: 0.7}}
|
avar.segments = {"wdth": {0.3: 0.8, 1.0: 0.7}}
|
||||||
warnings = []
|
avar.fixupSegments_()
|
||||||
avar.fixupSegments_(lambda w: warnings.append(w))
|
|
||||||
self.assertEqual({"wdth": {-1.0: -1.0, 0.0: 0.0, 0.3: 0.8, 1.0: 1.0}}, avar.segments)
|
self.assertEqual({"wdth": {-1.0: -1.0, 0.0: 0.0, 0.3: 0.8, 1.0: 1.0}}, avar.segments)
|
||||||
self.assertEqual([
|
self.assertEqual([
|
||||||
"avar axis 'wdth' should map -1.0 to -1.0",
|
"avar axis 'wdth' should map -1.0 to -1.0",
|
||||||
"avar axis 'wdth' should map 0.0 to 0.0",
|
"avar axis 'wdth' should map 0.0 to 0.0",
|
||||||
"avar axis 'wdth' should map 1.0 to 1.0"
|
"avar axis 'wdth' should map 1.0 to 1.0"
|
||||||
], warnings)
|
], sio.getvalue().splitlines())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def makeFont(axisTags):
|
def makeFont(axisTags):
|
||||||
|
@ -9,6 +9,10 @@ import sys
|
|||||||
import struct
|
import struct
|
||||||
import array
|
import array
|
||||||
import operator
|
import operator
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class table__c_m_a_p(DefaultTable.DefaultTable):
|
class table__c_m_a_p(DefaultTable.DefaultTable):
|
||||||
@ -51,7 +55,10 @@ class table__c_m_a_p(DefaultTable.DefaultTable):
|
|||||||
format, length = struct.unpack(">HL", data[offset:offset+6])
|
format, length = struct.unpack(">HL", data[offset:offset+6])
|
||||||
|
|
||||||
if not length:
|
if not length:
|
||||||
print("Error: cmap subtable is reported as having zero length: platformID %s, platEncID %s, format %s offset %s. Skipping table." % (platformID, platEncID,format, offset))
|
log.error(
|
||||||
|
"cmap subtable is reported as having zero length: platformID %s, "
|
||||||
|
"platEncID %s, format %s offset %s. Skipping table.",
|
||||||
|
platformID, platEncID, format, offset)
|
||||||
continue
|
continue
|
||||||
table = CmapSubtable.newSubtable(format)
|
table = CmapSubtable.newSubtable(format)
|
||||||
table.platformID = platformID
|
table.platformID = platformID
|
||||||
|
@ -13,7 +13,10 @@ from . import ttProgram
|
|||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
import array
|
import array
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
#
|
#
|
||||||
# The Apple and MS rasterizers behave differently for
|
# The Apple and MS rasterizers behave differently for
|
||||||
@ -56,10 +59,11 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|||||||
self.glyphs[glyphName] = glyph
|
self.glyphs[glyphName] = glyph
|
||||||
last = next
|
last = next
|
||||||
if len(data) - next >= 4:
|
if len(data) - next >= 4:
|
||||||
warnings.warn("too much 'glyf' table data: expected %d, received %d bytes" %
|
log.warning(
|
||||||
(next, len(data)))
|
"too much 'glyf' table data: expected %d, received %d bytes",
|
||||||
|
next, len(data))
|
||||||
if noname:
|
if noname:
|
||||||
warnings.warn('%s glyphs have no name' % noname)
|
log.warning('%s glyphs have no name', noname)
|
||||||
if ttFont.lazy is False: # Be lazy for None and True
|
if ttFont.lazy is False: # Be lazy for None and True
|
||||||
for glyph in self.glyphs.values():
|
for glyph in self.glyphs.values():
|
||||||
glyph.expand(self)
|
glyph.expand(self)
|
||||||
@ -145,8 +149,7 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|||||||
if not hasattr(self, "glyphOrder"):
|
if not hasattr(self, "glyphOrder"):
|
||||||
self.glyphOrder = ttFont.getGlyphOrder()
|
self.glyphOrder = ttFont.getGlyphOrder()
|
||||||
glyphName = attrs["name"]
|
glyphName = attrs["name"]
|
||||||
if ttFont.verbose:
|
log.debug("unpacking glyph '%s'", glyphName)
|
||||||
ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
|
|
||||||
glyph = Glyph()
|
glyph = Glyph()
|
||||||
for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
|
for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
|
||||||
setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
|
setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
|
||||||
@ -453,7 +456,9 @@ class Glyph(object):
|
|||||||
self.program.fromBytecode(data[:numInstructions])
|
self.program.fromBytecode(data[:numInstructions])
|
||||||
data = data[numInstructions:]
|
data = data[numInstructions:]
|
||||||
if len(data) >= 4:
|
if len(data) >= 4:
|
||||||
warnings.warn("too much glyph data at the end of composite glyph: %d excess bytes" % len(data))
|
log.warning(
|
||||||
|
"too much glyph data at the end of composite glyph: %d excess bytes",
|
||||||
|
len(data))
|
||||||
|
|
||||||
def decompileCoordinates(self, data):
|
def decompileCoordinates(self, data):
|
||||||
endPtsOfContours = array.array("h")
|
endPtsOfContours = array.array("h")
|
||||||
@ -545,7 +550,8 @@ class Glyph(object):
|
|||||||
xDataLen = struct.calcsize(xFormat)
|
xDataLen = struct.calcsize(xFormat)
|
||||||
yDataLen = struct.calcsize(yFormat)
|
yDataLen = struct.calcsize(yFormat)
|
||||||
if len(data) - (xDataLen + yDataLen) >= 4:
|
if len(data) - (xDataLen + yDataLen) >= 4:
|
||||||
warnings.warn("too much glyph data: %d excess bytes" % (len(data) - (xDataLen + yDataLen)))
|
log.warning(
|
||||||
|
"too much glyph data: %d excess bytes", len(data) - (xDataLen + yDataLen))
|
||||||
xCoordinates = struct.unpack(xFormat, data[:xDataLen])
|
xCoordinates = struct.unpack(xFormat, data[:xDataLen])
|
||||||
yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
|
yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
|
||||||
return flags, xCoordinates, yCoordinates
|
return flags, xCoordinates, yCoordinates
|
||||||
@ -734,7 +740,7 @@ class Glyph(object):
|
|||||||
bbox = calcBounds([coords[last], coords[next]])
|
bbox = calcBounds([coords[last], coords[next]])
|
||||||
if not pointInRect(coords[j], bbox):
|
if not pointInRect(coords[j], bbox):
|
||||||
# Ouch!
|
# Ouch!
|
||||||
warnings.warn("Outline has curve with implicit extrema.")
|
log.warning("Outline has curve with implicit extrema.")
|
||||||
# Ouch! Find analytical curve bounds.
|
# Ouch! Find analytical curve bounds.
|
||||||
pthis = coords[j]
|
pthis = coords[j]
|
||||||
plast = coords[last]
|
plast = coords[last]
|
||||||
@ -1005,7 +1011,6 @@ class GlyphComponent(object):
|
|||||||
self.flags = int(flags)
|
self.flags = int(flags)
|
||||||
glyphID = int(glyphID)
|
glyphID = int(glyphID)
|
||||||
self.glyphName = glyfTable.getGlyphName(int(glyphID))
|
self.glyphName = glyfTable.getGlyphName(int(glyphID))
|
||||||
#print ">>", reprflag(self.flags)
|
|
||||||
data = data[4:]
|
data = data[4:]
|
||||||
|
|
||||||
if self.flags & ARG_1_AND_2_ARE_WORDS:
|
if self.flags & ARG_1_AND_2_ARE_WORDS:
|
||||||
|
@ -5,9 +5,11 @@ from fontTools.misc.textTools import safeEval, num2binary, binary2num
|
|||||||
from fontTools.misc.timeTools import timestampFromString, timestampToString, timestampNow
|
from fontTools.misc.timeTools import timestampFromString, timestampToString, timestampNow
|
||||||
from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff # For backward compat
|
from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff # For backward compat
|
||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
headFormat = """
|
headFormat = """
|
||||||
> # big endian
|
> # big endian
|
||||||
tableVersion: 16.16F
|
tableVersion: 16.16F
|
||||||
@ -37,7 +39,7 @@ class table__h_e_a_d(DefaultTable.DefaultTable):
|
|||||||
dummy, rest = sstruct.unpack2(headFormat, data, self)
|
dummy, rest = sstruct.unpack2(headFormat, data, self)
|
||||||
if rest:
|
if rest:
|
||||||
# this is quite illegal, but there seem to be fonts out there that do this
|
# this is quite illegal, but there seem to be fonts out there that do this
|
||||||
warnings.warn("extra bytes at the end of 'head' table")
|
log.warning("extra bytes at the end of 'head' table")
|
||||||
assert rest == "\0\0"
|
assert rest == "\0\0"
|
||||||
|
|
||||||
# For timestamp fields, ignore the top four bytes. Some fonts have
|
# For timestamp fields, ignore the top four bytes. Some fonts have
|
||||||
@ -48,11 +50,11 @@ class table__h_e_a_d(DefaultTable.DefaultTable):
|
|||||||
for stamp in 'created', 'modified':
|
for stamp in 'created', 'modified':
|
||||||
value = getattr(self, stamp)
|
value = getattr(self, stamp)
|
||||||
if value > 0xFFFFFFFF:
|
if value > 0xFFFFFFFF:
|
||||||
warnings.warn("'%s' timestamp out of range; ignoring top bytes" % stamp)
|
log.warning("'%s' timestamp out of range; ignoring top bytes", stamp)
|
||||||
value &= 0xFFFFFFFF
|
value &= 0xFFFFFFFF
|
||||||
setattr(self, stamp, value)
|
setattr(self, stamp, value)
|
||||||
if value < 0x7C259DC0: # January 1, 1970 00:00:00
|
if value < 0x7C259DC0: # January 1, 1970 00:00:00
|
||||||
warnings.warn("'%s' timestamp seems very low; regarding as unix timestamp" % stamp)
|
log.warning("'%s' timestamp seems very low; regarding as unix timestamp", stamp)
|
||||||
value += 0x7C259DC0
|
value += 0x7C259DC0
|
||||||
setattr(self, stamp, value)
|
setattr(self, stamp, value)
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@ from fontTools.misc.textTools import safeEval
|
|||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
import sys
|
import sys
|
||||||
import array
|
import array
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class table__h_m_t_x(DefaultTable.DefaultTable):
|
class table__h_m_t_x(DefaultTable.DefaultTable):
|
||||||
@ -31,7 +34,7 @@ class table__h_m_t_x(DefaultTable.DefaultTable):
|
|||||||
if sys.byteorder != "big":
|
if sys.byteorder != "big":
|
||||||
sideBearings.byteswap()
|
sideBearings.byteswap()
|
||||||
if data:
|
if data:
|
||||||
warnings.warn("too much 'hmtx'/'vmtx' table data")
|
log.warning("too much 'hmtx'/'vmtx' table data")
|
||||||
self.metrics = {}
|
self.metrics = {}
|
||||||
glyphOrder = ttFont.getGlyphOrder()
|
glyphOrder = ttFont.getGlyphOrder()
|
||||||
for i in range(numberOfMetrics):
|
for i in range(numberOfMetrics):
|
||||||
|
@ -6,7 +6,10 @@ from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2
|
|||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
import struct
|
import struct
|
||||||
import array
|
import array
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class table__k_e_r_n(DefaultTable.DefaultTable):
|
class table__k_e_r_n(DefaultTable.DefaultTable):
|
||||||
@ -118,7 +121,7 @@ class KernTable_format_0(object):
|
|||||||
# Slower, but will not throw an IndexError on an invalid glyph id.
|
# Slower, but will not throw an IndexError on an invalid glyph id.
|
||||||
kernTable[(ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value
|
kernTable[(ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value
|
||||||
if len(data) > 6 * nPairs:
|
if len(data) > 6 * nPairs:
|
||||||
warnings.warn("excess data in 'kern' subtable: %d bytes" % len(data))
|
log.warning("excess data in 'kern' subtable: %d bytes", len(data))
|
||||||
|
|
||||||
def compile(self, ttFont):
|
def compile(self, ttFont):
|
||||||
nPairs = len(self.kernTable)
|
nPairs = len(self.kernTable)
|
||||||
|
@ -3,7 +3,11 @@ from fontTools.misc.py23 import *
|
|||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
import sys
|
import sys
|
||||||
import array
|
import array
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class table__l_o_c_a(DefaultTable.DefaultTable):
|
class table__l_o_c_a(DefaultTable.DefaultTable):
|
||||||
|
|
||||||
@ -25,7 +29,8 @@ class table__l_o_c_a(DefaultTable.DefaultTable):
|
|||||||
l.append(locations[i] * 2)
|
l.append(locations[i] * 2)
|
||||||
locations = l
|
locations = l
|
||||||
if len(locations) < (ttFont['maxp'].numGlyphs + 1):
|
if len(locations) < (ttFont['maxp'].numGlyphs + 1):
|
||||||
warnings.warn("corrupt 'loca' table, or wrong numGlyphs in 'maxp': %d %d" % (len(locations) - 1, ttFont['maxp'].numGlyphs))
|
log.warning("corrupt 'loca' table, or wrong numGlyphs in 'maxp': %d %d",
|
||||||
|
len(locations) - 1, ttFont['maxp'].numGlyphs)
|
||||||
self.locations = locations
|
self.locations = locations
|
||||||
|
|
||||||
def compile(self, ttFont):
|
def compile(self, ttFont):
|
||||||
|
@ -5,6 +5,10 @@ from fontTools.misc.textTools import safeEval
|
|||||||
from fontTools.misc.encodingTools import getEncoding
|
from fontTools.misc.encodingTools import getEncoding
|
||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
import struct
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
nameRecordFormat = """
|
nameRecordFormat = """
|
||||||
> # big endian
|
> # big endian
|
||||||
@ -25,8 +29,9 @@ class table__n_a_m_e(DefaultTable.DefaultTable):
|
|||||||
format, n, stringOffset = struct.unpack(">HHH", data[:6])
|
format, n, stringOffset = struct.unpack(">HHH", data[:6])
|
||||||
expectedStringOffset = 6 + n * nameRecordSize
|
expectedStringOffset = 6 + n * nameRecordSize
|
||||||
if stringOffset != expectedStringOffset:
|
if stringOffset != expectedStringOffset:
|
||||||
# XXX we need a warn function
|
log.error(
|
||||||
print("Warning: 'name' table stringOffset incorrect. Expected: %s; Actual: %s" % (expectedStringOffset, stringOffset))
|
"'name' table stringOffset incorrect. Expected: %s; Actual: %s",
|
||||||
|
expectedStringOffset, stringOffset)
|
||||||
stringData = data[stringOffset:]
|
stringData = data[stringOffset:]
|
||||||
data = data[6:]
|
data = data[6:]
|
||||||
self.names = []
|
self.names = []
|
||||||
|
@ -3,6 +3,9 @@ from fontTools.misc.py23 import *
|
|||||||
from .DefaultTable import DefaultTable
|
from .DefaultTable import DefaultTable
|
||||||
import array
|
import array
|
||||||
import struct
|
import struct
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class OverflowErrorRecord(object):
|
class OverflowErrorRecord(object):
|
||||||
def __init__(self, overflowTuple):
|
def __init__(self, overflowTuple):
|
||||||
@ -46,12 +49,12 @@ class BaseTTXConverter(DefaultTable):
|
|||||||
if cachingStats:
|
if cachingStats:
|
||||||
stats = sorted([(v, k) for k, v in cachingStats.items()])
|
stats = sorted([(v, k) for k, v in cachingStats.items()])
|
||||||
stats.reverse()
|
stats.reverse()
|
||||||
print("cachingsstats for ", self.tableTag)
|
log.debug("cachingStats for %s", self.tableTag)
|
||||||
for v, k in stats:
|
for v, k in stats:
|
||||||
if v < 2:
|
if v < 2:
|
||||||
break
|
break
|
||||||
print(v, k)
|
log.debug("%s %s", v, k)
|
||||||
print("---", len(stats))
|
log.debug("--- %s", len(stats))
|
||||||
|
|
||||||
def compile(self, font):
|
def compile(self, font):
|
||||||
""" Create a top-level OTFWriter for the GPOS/GSUB table.
|
""" Create a top-level OTFWriter for the GPOS/GSUB table.
|
||||||
@ -92,7 +95,7 @@ class BaseTTXConverter(DefaultTable):
|
|||||||
raise # Oh well...
|
raise # Oh well...
|
||||||
|
|
||||||
overflowRecord = e.value
|
overflowRecord = e.value
|
||||||
print("Attempting to fix OTLOffsetOverflowError", e)
|
log.warning("Attempting to fix OTLOffsetOverflowError %s", e)
|
||||||
lastItem = overflowRecord
|
lastItem = overflowRecord
|
||||||
|
|
||||||
ok = 0
|
ok = 0
|
||||||
|
@ -3,6 +3,10 @@ from fontTools.misc.py23 import *
|
|||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
|
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
|
||||||
from .otBase import ValueRecordFactory
|
from .otBase import ValueRecordFactory
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def buildConverters(tableSpec, tableNamespace):
|
def buildConverters(tableSpec, tableNamespace):
|
||||||
@ -329,8 +333,8 @@ class Table(Struct):
|
|||||||
return None
|
return None
|
||||||
if offset <= 3:
|
if offset <= 3:
|
||||||
# XXX hack to work around buggy pala.ttf
|
# XXX hack to work around buggy pala.ttf
|
||||||
print("*** Warning: offset is not 0, yet suspiciously low (%s). table: %s" \
|
log.warning("offset is not 0, yet suspiciously low (%d). table: %s",
|
||||||
% (offset, self.tableClass.__name__))
|
offset, self.tableClass.__name__)
|
||||||
return None
|
return None
|
||||||
table = self.tableClass()
|
table = self.tableClass()
|
||||||
reader = reader.getSubReader(offset)
|
reader = reader.getSubReader(offset)
|
||||||
|
@ -8,7 +8,10 @@ from __future__ import print_function, division, absolute_import
|
|||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
from .otBase import BaseTable, FormatSwitchingBaseTable
|
from .otBase import BaseTable, FormatSwitchingBaseTable
|
||||||
import operator
|
import operator
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FeatureParams(BaseTable):
|
class FeatureParams(BaseTable):
|
||||||
@ -46,7 +49,7 @@ class Coverage(FormatSwitchingBaseTable):
|
|||||||
# this when writing font out.
|
# this when writing font out.
|
||||||
sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
|
sorted_ranges = sorted(ranges, key=lambda a: a.StartCoverageIndex)
|
||||||
if ranges != sorted_ranges:
|
if ranges != sorted_ranges:
|
||||||
warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
|
log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
|
||||||
ranges = sorted_ranges
|
ranges = sorted_ranges
|
||||||
del sorted_ranges
|
del sorted_ranges
|
||||||
for r in ranges:
|
for r in ranges:
|
||||||
@ -57,14 +60,14 @@ class Coverage(FormatSwitchingBaseTable):
|
|||||||
try:
|
try:
|
||||||
startID = font.getGlyphID(start, requireReal=True)
|
startID = font.getGlyphID(start, requireReal=True)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
warnings.warn("Coverage table has start glyph ID out of range: %s." % start)
|
log.warning("Coverage table has start glyph ID out of range: %s.", start)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
endID = font.getGlyphID(end, requireReal=True) + 1
|
endID = font.getGlyphID(end, requireReal=True) + 1
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Apparently some tools use 65535 to "match all" the range
|
# Apparently some tools use 65535 to "match all" the range
|
||||||
if end != 'glyph65535':
|
if end != 'glyph65535':
|
||||||
warnings.warn("Coverage table has end glyph ID out of range: %s." % end)
|
log.warning("Coverage table has end glyph ID out of range: %s.", end)
|
||||||
# NOTE: We clobber out-of-range things here. There are legit uses for those,
|
# NOTE: We clobber out-of-range things here. There are legit uses for those,
|
||||||
# but none that we have seen in the wild.
|
# but none that we have seen in the wild.
|
||||||
endID = len(glyphOrder)
|
endID = len(glyphOrder)
|
||||||
@ -107,7 +110,7 @@ class Coverage(FormatSwitchingBaseTable):
|
|||||||
ranges[i] = r
|
ranges[i] = r
|
||||||
index = index + end - start + 1
|
index = index + end - start + 1
|
||||||
if brokenOrder:
|
if brokenOrder:
|
||||||
warnings.warn("GSUB/GPOS Coverage is not sorted by glyph ids.")
|
log.warning("GSUB/GPOS Coverage is not sorted by glyph ids.")
|
||||||
ranges.sort(key=lambda a: a.StartID)
|
ranges.sort(key=lambda a: a.StartID)
|
||||||
for r in ranges:
|
for r in ranges:
|
||||||
del r.StartID
|
del r.StartID
|
||||||
@ -288,11 +291,12 @@ class ClassDef(FormatSwitchingBaseTable):
|
|||||||
try:
|
try:
|
||||||
startID = font.getGlyphID(start, requireReal=True)
|
startID = font.getGlyphID(start, requireReal=True)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
warnings.warn("ClassDef table has start glyph ID out of range: %s." % start)
|
log.warning("ClassDef table has start glyph ID out of range: %s.", start)
|
||||||
startID = len(glyphOrder)
|
startID = len(glyphOrder)
|
||||||
endID = startID + len(classList)
|
endID = startID + len(classList)
|
||||||
if endID > len(glyphOrder):
|
if endID > len(glyphOrder):
|
||||||
warnings.warn("ClassDef table has entries for out of range glyph IDs: %s,%s." % (start, len(classList)))
|
log.warning("ClassDef table has entries for out of range glyph IDs: %s,%s.",
|
||||||
|
start, len(classList))
|
||||||
# NOTE: We clobber out-of-range things here. There are legit uses for those,
|
# NOTE: We clobber out-of-range things here. There are legit uses for those,
|
||||||
# but none that we have seen in the wild.
|
# but none that we have seen in the wild.
|
||||||
endID = len(glyphOrder)
|
endID = len(glyphOrder)
|
||||||
@ -309,14 +313,14 @@ class ClassDef(FormatSwitchingBaseTable):
|
|||||||
try:
|
try:
|
||||||
startID = font.getGlyphID(start, requireReal=True)
|
startID = font.getGlyphID(start, requireReal=True)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
warnings.warn("ClassDef table has start glyph ID out of range: %s." % start)
|
log.warning("ClassDef table has start glyph ID out of range: %s.", start)
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
endID = font.getGlyphID(end, requireReal=True) + 1
|
endID = font.getGlyphID(end, requireReal=True) + 1
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Apparently some tools use 65535 to "match all" the range
|
# Apparently some tools use 65535 to "match all" the range
|
||||||
if end != 'glyph65535':
|
if end != 'glyph65535':
|
||||||
warnings.warn("ClassDef table has end glyph ID out of range: %s." % end)
|
log.warning("ClassDef table has end glyph ID out of range: %s.", end)
|
||||||
# NOTE: We clobber out-of-range things here. There are legit uses for those,
|
# NOTE: We clobber out-of-range things here. There are legit uses for those,
|
||||||
# but none that we have seen in the wild.
|
# but none that we have seen in the wild.
|
||||||
endID = len(glyphOrder)
|
endID = len(glyphOrder)
|
||||||
|
@ -5,6 +5,10 @@ from fontTools.misc.py23 import *
|
|||||||
from fontTools.misc.textTools import num2binary, binary2num, readHex
|
from fontTools.misc.textTools import num2binary, binary2num, readHex
|
||||||
import array
|
import array
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# first, the list of instructions that eat bytes or words from the instruction stream
|
# first, the list of instructions that eat bytes or words from the instruction stream
|
||||||
|
|
||||||
@ -233,7 +237,7 @@ class Program(object):
|
|||||||
traceback.print_exc(file=tmp)
|
traceback.print_exc(file=tmp)
|
||||||
msg = "An exception occurred during the decompilation of glyph program:\n\n"
|
msg = "An exception occurred during the decompilation of glyph program:\n\n"
|
||||||
msg += tmp.getvalue()
|
msg += tmp.getvalue()
|
||||||
print(msg, file=sys.stderr)
|
log.error(msg)
|
||||||
writer.begintag("bytecode")
|
writer.begintag("bytecode")
|
||||||
writer.newline()
|
writer.newline()
|
||||||
writer.comment(msg.strip())
|
writer.comment(msg.strip())
|
||||||
|
@ -13,6 +13,10 @@ from fontTools.ttLib.sfnt import (SFNTReader, SFNTWriter, DirectoryEntry,
|
|||||||
WOFFFlavorData, sfntDirectoryFormat, sfntDirectorySize, SFNTDirectoryEntry,
|
WOFFFlavorData, sfntDirectoryFormat, sfntDirectorySize, SFNTDirectoryEntry,
|
||||||
sfntDirectoryEntrySize, calcChecksum)
|
sfntDirectoryEntrySize, calcChecksum)
|
||||||
from fontTools.ttLib.tables import ttProgram
|
from fontTools.ttLib.tables import ttProgram
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
haveBrotli = False
|
haveBrotli = False
|
||||||
try:
|
try:
|
||||||
@ -28,8 +32,9 @@ class WOFF2Reader(SFNTReader):
|
|||||||
|
|
||||||
def __init__(self, file, checkChecksums=1, fontNumber=-1):
|
def __init__(self, file, checkChecksums=1, fontNumber=-1):
|
||||||
if not haveBrotli:
|
if not haveBrotli:
|
||||||
print('The WOFF2 decoder requires the Brotli Python extension, available at:\n'
|
log.error(
|
||||||
'https://github.com/google/brotli', file=sys.stderr)
|
'The WOFF2 decoder requires the Brotli Python extension, available at: '
|
||||||
|
'https://github.com/google/brotli')
|
||||||
raise ImportError("No module named brotli")
|
raise ImportError("No module named brotli")
|
||||||
|
|
||||||
self.file = file
|
self.file = file
|
||||||
@ -133,8 +138,9 @@ class WOFF2Writer(SFNTWriter):
|
|||||||
def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
|
def __init__(self, file, numTables, sfntVersion="\000\001\000\000",
|
||||||
flavor=None, flavorData=None):
|
flavor=None, flavorData=None):
|
||||||
if not haveBrotli:
|
if not haveBrotli:
|
||||||
print('The WOFF2 encoder requires the Brotli Python extension, available at:\n'
|
log.error(
|
||||||
'https://github.com/google/brotli', file=sys.stderr)
|
'The WOFF2 encoder requires the Brotli Python extension, available at: '
|
||||||
|
'https://github.com/google/brotli')
|
||||||
raise ImportError("No module named brotli")
|
raise ImportError("No module named brotli")
|
||||||
|
|
||||||
self.file = file
|
self.file = file
|
||||||
|
@ -81,15 +81,20 @@ from fontTools.ttLib import TTFont, TTLibError
|
|||||||
from fontTools.misc.macCreatorType import getMacCreatorAndType
|
from fontTools.misc.macCreatorType import getMacCreatorAndType
|
||||||
from fontTools.unicode import setUnicodeData
|
from fontTools.unicode import setUnicodeData
|
||||||
from fontTools.misc.timeTools import timestampSinceEpoch
|
from fontTools.misc.timeTools import timestampSinceEpoch
|
||||||
|
from fontTools.misc.loggingTools import Timer
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import getopt
|
import getopt
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
from fontTools import version
|
from fontTools import version
|
||||||
print(__doc__ % version)
|
print(__doc__ % version)
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
|
|
||||||
numberAddedRE = re.compile("#\d+$")
|
numberAddedRE = re.compile("#\d+$")
|
||||||
@ -136,8 +141,7 @@ class Options(object):
|
|||||||
for option, value in rawOptions:
|
for option, value in rawOptions:
|
||||||
# general options
|
# general options
|
||||||
if option == "-h":
|
if option == "-h":
|
||||||
from fontTools import version
|
usage()
|
||||||
print(__doc__ % version)
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif option == "-d":
|
elif option == "-d":
|
||||||
if not os.path.isdir(value):
|
if not os.path.isdir(value):
|
||||||
@ -187,8 +191,16 @@ class Options(object):
|
|||||||
self.recalcTimestamp = True
|
self.recalcTimestamp = True
|
||||||
elif option == "--flavor":
|
elif option == "--flavor":
|
||||||
self.flavor = value
|
self.flavor = value
|
||||||
|
if self.verbose and self.quiet:
|
||||||
|
raise getopt.GetoptError("-q and -v options are mutually exclusive")
|
||||||
|
if self.verbose:
|
||||||
|
self.logLevel = logging.DEBUG
|
||||||
|
elif self.quiet:
|
||||||
|
self.logLevel = logging.WARNING
|
||||||
|
else:
|
||||||
|
self.logLevel = logging.INFO
|
||||||
if self.mergeFile and self.flavor:
|
if self.mergeFile and self.flavor:
|
||||||
print("-m and --flavor options are mutually exclusive")
|
raise getopt.GetoptError("-m and --flavor options are mutually exclusive")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
if self.onlyTables and self.skipTables:
|
if self.onlyTables and self.skipTables:
|
||||||
raise getopt.GetoptError("-t and -x options are mutually exclusive")
|
raise getopt.GetoptError("-t and -x options are mutually exclusive")
|
||||||
@ -221,17 +233,15 @@ def ttList(input, output, options):
|
|||||||
ttf.close()
|
ttf.close()
|
||||||
|
|
||||||
|
|
||||||
|
@Timer(log, 'Done dumping TTX in %(time).3f seconds')
|
||||||
def ttDump(input, output, options):
|
def ttDump(input, output, options):
|
||||||
if not options.quiet:
|
log.info('Dumping "%s" to "%s"...', input, output)
|
||||||
print('Dumping "%s" to "%s"...' % (input, output))
|
|
||||||
if options.unicodedata:
|
if options.unicodedata:
|
||||||
setUnicodeData(options.unicodedata)
|
setUnicodeData(options.unicodedata)
|
||||||
ttf = TTFont(input, 0, verbose=options.verbose, allowVID=options.allowVID,
|
ttf = TTFont(input, 0, allowVID=options.allowVID,
|
||||||
quiet=options.quiet,
|
|
||||||
ignoreDecompileErrors=options.ignoreDecompileErrors,
|
ignoreDecompileErrors=options.ignoreDecompileErrors,
|
||||||
fontNumber=options.fontNumber)
|
fontNumber=options.fontNumber)
|
||||||
ttf.saveXML(output,
|
ttf.saveXML(output,
|
||||||
quiet=options.quiet,
|
|
||||||
tables=options.onlyTables,
|
tables=options.onlyTables,
|
||||||
skipTables=options.skipTables,
|
skipTables=options.skipTables,
|
||||||
splitTables=options.splitTables,
|
splitTables=options.splitTables,
|
||||||
@ -240,14 +250,14 @@ def ttDump(input, output, options):
|
|||||||
ttf.close()
|
ttf.close()
|
||||||
|
|
||||||
|
|
||||||
|
@Timer(log, 'Done compiling TTX in %(time).3f seconds')
|
||||||
def ttCompile(input, output, options):
|
def ttCompile(input, output, options):
|
||||||
if not options.quiet:
|
log.info('Compiling "%s" to "%s"...' % (input, output))
|
||||||
print('Compiling "%s" to "%s"...' % (input, output))
|
|
||||||
ttf = TTFont(options.mergeFile, flavor=options.flavor,
|
ttf = TTFont(options.mergeFile, flavor=options.flavor,
|
||||||
recalcBBoxes=options.recalcBBoxes,
|
recalcBBoxes=options.recalcBBoxes,
|
||||||
recalcTimestamp=options.recalcTimestamp,
|
recalcTimestamp=options.recalcTimestamp,
|
||||||
verbose=options.verbose, allowVID=options.allowVID)
|
allowVID=options.allowVID)
|
||||||
ttf.importXML(input, quiet=options.quiet)
|
ttf.importXML(input)
|
||||||
|
|
||||||
if not options.recalcTimestamp:
|
if not options.recalcTimestamp:
|
||||||
# use TTX file modification time for head "modified" timestamp
|
# use TTX file modification time for head "modified" timestamp
|
||||||
@ -256,10 +266,6 @@ def ttCompile(input, output, options):
|
|||||||
|
|
||||||
ttf.save(output)
|
ttf.save(output)
|
||||||
|
|
||||||
if options.verbose:
|
|
||||||
import time
|
|
||||||
print("finished at", time.strftime("%H:%M:%S", time.localtime(time.time())))
|
|
||||||
|
|
||||||
|
|
||||||
def guessFileType(fileName):
|
def guessFileType(fileName):
|
||||||
base, ext = os.path.splitext(fileName)
|
base, ext = os.path.splitext(fileName)
|
||||||
@ -305,6 +311,9 @@ def parseOptions(args):
|
|||||||
raise getopt.GetoptError('Must specify at least one input file')
|
raise getopt.GetoptError('Must specify at least one input file')
|
||||||
|
|
||||||
for input in files:
|
for input in files:
|
||||||
|
if not os.path.isfile(input):
|
||||||
|
raise getopt.GetoptError('File not found: "%s"' % input)
|
||||||
|
continue
|
||||||
tp = guessFileType(input)
|
tp = guessFileType(input)
|
||||||
if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"):
|
if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"):
|
||||||
extension = ".ttx"
|
extension = ".ttx"
|
||||||
@ -319,7 +328,7 @@ def parseOptions(args):
|
|||||||
extension = "."+options.flavor if options.flavor else ".otf"
|
extension = "."+options.flavor if options.flavor else ".otf"
|
||||||
action = ttCompile
|
action = ttCompile
|
||||||
else:
|
else:
|
||||||
print('Unknown file type: "%s"' % input)
|
raise getopt.GetoptError('Unknown file type: "%s"' % input)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if options.outputFile:
|
if options.outputFile:
|
||||||
@ -342,37 +351,43 @@ def waitForKeyPress():
|
|||||||
"""Force the DOS Prompt window to stay open so the user gets
|
"""Force the DOS Prompt window to stay open so the user gets
|
||||||
a chance to see what's wrong."""
|
a chance to see what's wrong."""
|
||||||
import msvcrt
|
import msvcrt
|
||||||
print('(Hit any key to exit)')
|
print('(Hit any key to exit)', file=sys.stderr)
|
||||||
while not msvcrt.kbhit():
|
while not msvcrt.kbhit():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
|
from fontTools import configLogger
|
||||||
|
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
try:
|
try:
|
||||||
jobs, options = parseOptions(args)
|
jobs, options = parseOptions(args)
|
||||||
except getopt.GetoptError as e:
|
except getopt.GetoptError as e:
|
||||||
print('error:', e, file=sys.stderr)
|
|
||||||
usage()
|
usage()
|
||||||
|
print("ERROR:", e, file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
configLogger(level=options.logLevel)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process(jobs, options)
|
process(jobs, options)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("(Cancelled.)")
|
log.error("(Cancelled.)")
|
||||||
|
sys.exit(1)
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
waitForKeyPress()
|
waitForKeyPress()
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
except TTLibError as e:
|
except TTLibError as e:
|
||||||
print("Error:",e)
|
log.error(e)
|
||||||
|
sys.exit(1)
|
||||||
except:
|
except:
|
||||||
|
log.exception('Unhandled exception has occurred')
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
waitForKeyPress()
|
waitForKeyPress()
|
||||||
else:
|
sys.exit(1)
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -27,7 +27,7 @@ from fontTools.ttLib import TTFont
|
|||||||
from fontTools.ttLib.tables._n_a_m_e import NameRecord
|
from fontTools.ttLib.tables._n_a_m_e import NameRecord
|
||||||
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
|
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
|
||||||
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, GlyphVariation
|
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, GlyphVariation
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
|
|
||||||
def AddFontVariations(font):
|
def AddFontVariations(font):
|
||||||
@ -75,13 +75,13 @@ def AddGlyphVariations(font, thin, regular, black):
|
|||||||
thinCoord = GetCoordinates(thin, glyphName)
|
thinCoord = GetCoordinates(thin, glyphName)
|
||||||
blackCoord = GetCoordinates(black, glyphName)
|
blackCoord = GetCoordinates(black, glyphName)
|
||||||
if not regularCoord or not blackCoord or not thinCoord:
|
if not regularCoord or not blackCoord or not thinCoord:
|
||||||
warnings.warn("glyph %s not present in all input fonts" %
|
logging.warning("glyph %s not present in all input fonts",
|
||||||
glyphName)
|
glyphName)
|
||||||
continue
|
continue
|
||||||
if (len(regularCoord) != len(blackCoord) or
|
if (len(regularCoord) != len(blackCoord) or
|
||||||
len(regularCoord) != len(thinCoord)):
|
len(regularCoord) != len(thinCoord)):
|
||||||
warnings.warn("glyph %s has not the same number of "
|
logging.warning("glyph %s has not the same number of "
|
||||||
"control points in all input fonts" % glyphName)
|
"control points in all input fonts", glyphName)
|
||||||
continue
|
continue
|
||||||
thinDelta = []
|
thinDelta = []
|
||||||
blackDelta = []
|
blackDelta = []
|
||||||
@ -129,6 +129,7 @@ def GetCoordinates(font, glyphName):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
logging.basicConfig(format="%(levelname)s: %(message)s")
|
||||||
thin = TTFont("/tmp/Roboto/Roboto-Thin.ttf")
|
thin = TTFont("/tmp/Roboto/Roboto-Thin.ttf")
|
||||||
regular = TTFont("/tmp/Roboto/Roboto-Regular.ttf")
|
regular = TTFont("/tmp/Roboto/Roboto-Regular.ttf")
|
||||||
black = TTFont("/tmp/Roboto/Roboto-Black.ttf")
|
black = TTFont("/tmp/Roboto/Roboto-Black.ttf")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user