diff options
Diffstat (limited to 'Lib/fontTools/ttLib/ttFont.py')
-rw-r--r-- | Lib/fontTools/ttLib/ttFont.py | 318 |
1 files changed, 173 insertions, 145 deletions
diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index 41a48751..3929e2f3 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -1,5 +1,5 @@ from fontTools.misc import xmlWriter -from fontTools.misc.py23 import Tag, byteord, tostr +from fontTools.misc.textTools import Tag, byteord, tostr from fontTools.misc.loggingTools import deprecateArgument from fontTools.ttLib import TTLibError from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter @@ -12,75 +12,84 @@ log = logging.getLogger(__name__) class TTFont(object): - """The main font object. It manages file input and output, and offers - a convenient way of accessing tables. - Tables will be only decompiled when necessary, ie. when they're actually - accessed. This means that simple operations can be extremely fast. + """Represents a TrueType font. + + The object manages file input and output, and offers a convenient way of + accessing tables. Tables will be only decompiled when necessary, ie. when + they're actually accessed. This means that simple operations can be extremely fast. + + Example usage:: + + >> from fontTools import ttLib + >> tt = ttLib.TTFont("afont.ttf") # Load an existing font file + >> tt['maxp'].numGlyphs + 242 + >> tt['OS/2'].achVendID + 'B&H\000' + >> tt['head'].unitsPerEm + 2048 + + For details of the objects returned when accessing each table, see :ref:`tables`. + To add a table to the font, use the :py:func:`newTable` function:: + + >> os2 = newTable("OS/2") + >> os2.version = 4 + >> # set other attributes + >> font["OS/2"] = os2 + + TrueType fonts can also be serialized to and from XML format (see also the + :ref:`ttx` binary):: + + >> tt.saveXML("afont.ttx") + Dumping 'LTSH' table... + Dumping 'OS/2' table... + [...] + + >> tt2 = ttLib.TTFont() # Create a new font object + >> tt2.importXML("afont.ttx") + >> tt2['maxp'].numGlyphs + 242 + + The TTFont object may be used as a context manager; this will cause the file + reader to be closed after the context ``with`` block is exited:: + + with TTFont(filename) as f: + # Do stuff + + Args: + file: When reading a font from disk, either a pathname pointing to a file, + or a readable file object. + res_name_or_index: If running on a Macintosh, either a sfnt resource name or + an sfnt resource index number. If the index number is zero, TTLib will + autodetect whether the file is a flat file or a suitcase. (If it is a suitcase, + only the first 'sfnt' resource will be read.) + sfntVersion (str): When constructing a font object from scratch, sets the four-byte + sfnt magic number to be used. Defaults to ``\0\1\0\0`` (TrueType). To create + an OpenType file, use ``OTTO``. + flavor (str): Set this to ``woff`` when creating a WOFF file or ``woff2`` for a WOFF2 + file. + checkChecksums (int): How checksum data should be treated. Default is 0 + (no checking). Set to 1 to check and warn on wrong checksums; set to 2 to + raise an exception if any wrong checksums are found. + recalcBBoxes (bool): If true (the default), recalculates ``glyf``, ``CFF ``, + ``head`` bounding box values and ``hhea``/``vhea`` min/max values on save. + Also compiles the glyphs on importing, which saves memory consumption and + time. + ignoreDecompileErrors (bool): If true, exceptions raised during table decompilation + will be ignored, and the binary data will be returned for those tables instead. + recalcTimestamp (bool): If true (the default), sets the ``modified`` timestamp in + the ``head`` table on save. + fontNumber (int): The index of the font in a TrueType Collection file. + lazy (bool): If lazy is set to True, many data structures are loaded lazily, upon + access only. If it is set to False, many data structures are loaded immediately. + The default is ``lazy=None`` which is somewhere in between. """ def __init__(self, file=None, res_name_or_index=None, sfntVersion="\000\001\000\000", flavor=None, checkChecksums=0, - verbose=None, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False, + verbose=None, recalcBBoxes=True, allowVID=NotImplemented, ignoreDecompileErrors=False, recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=None, _tableCache=None): - - """The constructor can be called with a few different arguments. - When reading a font from disk, 'file' should be either a pathname - pointing to a file, or a readable file object. - - It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt - resource name or an sfnt resource index number or zero. The latter - case will cause TTLib to autodetect whether the file is a flat file - or a suitcase. (If it's a suitcase, only the first 'sfnt' resource - will be read!) - - The 'checkChecksums' argument is used to specify how sfnt - checksums are treated upon reading a file from disk: - 0: don't check (default) - 1: check, print warnings if a wrong checksum is found - 2: check, raise an exception if a wrong checksum is found. - - The TTFont constructor can also be called without a 'file' - argument: this is the way to create a new empty font. - In this case you can optionally supply the 'sfntVersion' argument, - and a 'flavor' which can be None, 'woff', or 'woff2'. - - If the recalcBBoxes argument is false, a number of things will *not* - be recalculated upon save/compile: - 1) 'glyf' glyph bounding boxes - 2) 'CFF ' font bounding box - 3) 'head' font bounding box - 4) 'hhea' min/max values - 5) 'vhea' min/max values - (1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-). - Additionally, upon importing an TTX file, this option cause glyphs - to be compiled right away. This should reduce memory consumption - greatly, and therefore should have some impact on the time needed - to parse/compile large fonts. - - If the recalcTimestamp argument is false, the modified timestamp in the - 'head' table will *not* be recalculated upon save/compile. - - If the allowVID argument is set to true, then virtual GID's are - supported. Asking for a glyph ID with a glyph name or GID that is not in - the font will return a virtual GID. This is valid for GSUB and cmap - tables. For SING glyphlets, the cmap table is used to specify Unicode - values for virtual GI's used in GSUB/GPOS rules. If the gid N is requested - and does not exist in the font, or the glyphname has the form glyphN - and does not exist in the font, then N is used as the virtual GID. - Else, the first virtual GID is assigned as 0x1000 -1; for subsequent new - virtual GIDs, the next is one less than the previous. - - If ignoreDecompileErrors is set to True, exceptions raised in - individual tables during decompilation will be ignored, falling - back to the DefaultTable implementation, which simply keeps the - binary data. - - If lazy is set to True, many data structures are loaded lazily, upon - access only. If it is set to False, many data structures are loaded - immediately. The default is lazy=None which is somewhere in between. - """ - for name in ("verbose", "quiet"): val = locals().get(name) if val is not None: @@ -92,12 +101,6 @@ class TTFont(object): self.recalcTimestamp = recalcTimestamp self.tables = {} self.reader = None - - # Permit the user to reference glyphs that are not int the font. - self.last_vid = 0xFFFE # Can't make it be 0xFFFF, as the world is full unsigned short integer counters that get incremented after the last seen GID value. - self.reverseVIDDict = {} - self.VIDDict = {} - self.allowVID = allowVID self.ignoreDecompileErrors = ignoreDecompileErrors if not file: @@ -154,9 +157,15 @@ class TTFont(object): self.reader.close() def save(self, file, reorderTables=True): - """Save the font to disk. Similarly to the constructor, - the 'file' argument can be either a pathname or a writable - file object. + """Save the font to disk. + + Args: + file: Similarly to the constructor, can be either a pathname or a writable + file object. + reorderTables (Option[bool]): If true (the default), reorder the tables, + sorting them by tag (recommended by the OpenType specification). If + false, retain the original font order. If None, reorder by table + dependency (fastest). """ if not hasattr(file, "write"): if self.lazy and self.reader.file.name == file: @@ -215,7 +224,7 @@ class TTFont(object): return writer.reordersTables() - def saveXML(self, fileOrPath, newlinestr=None, **kwargs): + def saveXML(self, fileOrPath, newlinestr="\n", **kwargs): """Export the font as TTX (an XML-based text file), or as a series of text files when splitTables is true. In the latter case, the 'fileOrPath' argument should be a path to a directory. @@ -336,11 +345,15 @@ class TTFont(object): reader.read() def isLoaded(self, tag): - """Return true if the table identified by 'tag' has been + """Return true if the table identified by ``tag`` has been decompiled and loaded into memory.""" return tag in self.tables def has_key(self, tag): + """Test if the table identified by ``tag`` is present in the font. + + As well as this method, ``tag in font`` can also be used to determine the + presence of the table.""" if self.isLoaded(tag): return True elif self.reader and tag in self.reader: @@ -353,6 +366,7 @@ class TTFont(object): __contains__ = has_key def keys(self): + """Returns the list of tables in the font, along with the ``GlyphOrder`` pseudo-table.""" keys = list(self.tables.keys()) if self.reader: for key in list(self.reader.keys()): @@ -364,6 +378,14 @@ class TTFont(object): keys = sortedTagList(keys) return ["GlyphOrder"] + keys + def ensureDecompiled(self): + """Decompile all the tables, even if a TTFont was opened in 'lazy' mode.""" + for tag in self.keys(): + table = self[tag] + if self.lazy is not False and hasattr(table, "ensureDecompiled"): + table.ensureDecompiled() + self.lazy = False + def __len__(self): return len(list(self.keys())) @@ -422,15 +444,26 @@ class TTFont(object): del self.reader[tag] def get(self, tag, default=None): + """Returns the table if it exists or (optionally) a default if it doesn't.""" try: return self[tag] except KeyError: return default def setGlyphOrder(self, glyphOrder): + """Set the glyph order + + Args: + glyphOrder ([str]): List of glyph names in order. + """ self.glyphOrder = glyphOrder + if hasattr(self, '_reverseGlyphOrderDict'): + del self._reverseGlyphOrderDict + if self.isLoaded("glyf"): + self["glyf"].setGlyphOrder(glyphOrder) def getGlyphOrder(self): + """Returns a list of glyph names ordered by their position in the font.""" try: return self.glyphOrder except AttributeError: @@ -544,78 +577,55 @@ class TTFont(object): from fontTools.misc import textTools return textTools.caselessSort(self.getGlyphOrder()) - def getGlyphName(self, glyphID, requireReal=False): + def getGlyphName(self, glyphID): + """Returns the name for the glyph with the given ID. + + If no name is available, synthesises one with the form ``glyphXXXXX``` where + ```XXXXX`` is the zero-padded glyph ID. + """ try: return self.getGlyphOrder()[glyphID] except IndexError: - if requireReal or not self.allowVID: - # XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in - # the cmap table than there are glyphs. I don't think it's legal... - return "glyph%.5d" % glyphID - else: - # user intends virtual GID support + return "glyph%.5d" % glyphID + + def getGlyphNameMany(self, lst): + """Converts a list of glyph IDs into a list of glyph names.""" + glyphOrder = self.getGlyphOrder(); + cnt = len(glyphOrder) + return [glyphOrder[gid] if gid < cnt else "glyph%.5d" % gid + for gid in lst] + + def getGlyphID(self, glyphName): + """Returns the ID of the glyph with the given name.""" + try: + return self.getReverseGlyphMap()[glyphName] + except KeyError: + if glyphName[:5] == "glyph": try: - glyphName = self.VIDDict[glyphID] - except KeyError: - glyphName ="glyph%.5d" % glyphID - self.last_vid = min(glyphID, self.last_vid ) - self.reverseVIDDict[glyphName] = glyphID - self.VIDDict[glyphID] = glyphName - return glyphName - - def getGlyphID(self, glyphName, requireReal=False): - if not hasattr(self, "_reverseGlyphOrderDict"): - self._buildReverseGlyphOrderDict() - glyphOrder = self.getGlyphOrder() - d = self._reverseGlyphOrderDict - if glyphName not in d: - if glyphName in glyphOrder: - self._buildReverseGlyphOrderDict() - return self.getGlyphID(glyphName) - else: - if requireReal: + return int(glyphName[5:]) + except (NameError, ValueError): raise KeyError(glyphName) - elif not self.allowVID: - # Handle glyphXXX only - if glyphName[:5] == "glyph": - try: - return int(glyphName[5:]) - except (NameError, ValueError): - raise KeyError(glyphName) - else: - # user intends virtual GID support - try: - glyphID = self.reverseVIDDict[glyphName] - except KeyError: - # if name is in glyphXXX format, use the specified name. - if glyphName[:5] == "glyph": - try: - glyphID = int(glyphName[5:]) - except (NameError, ValueError): - glyphID = None - if glyphID is None: - glyphID = self.last_vid -1 - self.last_vid = glyphID - self.reverseVIDDict[glyphName] = glyphID - self.VIDDict[glyphID] = glyphName - return glyphID - - glyphID = d[glyphName] - if glyphName != glyphOrder[glyphID]: - self._buildReverseGlyphOrderDict() - return self.getGlyphID(glyphName) - return glyphID + + def getGlyphIDMany(self, lst): + """Converts a list of glyph names into a list of glyph IDs.""" + d = self.getReverseGlyphMap() + try: + return [d[glyphName] for glyphName in lst] + except KeyError: + getGlyphID = self.getGlyphID + return [getGlyphID(glyphName) for glyphName in lst] def getReverseGlyphMap(self, rebuild=False): + """Returns a mapping of glyph names to glyph IDs.""" if rebuild or not hasattr(self, "_reverseGlyphOrderDict"): self._buildReverseGlyphOrderDict() return self._reverseGlyphOrderDict def _buildReverseGlyphOrderDict(self): self._reverseGlyphOrderDict = d = {} - glyphOrder = self.getGlyphOrder() - for glyphID in range(len(glyphOrder)): - d[glyphOrder[glyphID]] = glyphID + for glyphID,glyphName in enumerate(self.getGlyphOrder()): + d[glyphName] = glyphID + return d def _writeTable(self, tag, writer, done, tableCache=None): """Internal helper function for self.save(). Keeps track of @@ -644,7 +654,11 @@ class TTFont(object): tableCache[(Tag(tag), tabledata)] = writer[tag] def getTableData(self, tag): - """Returns raw table data, whether compiled or directly read from disk. + """Returns the binary representation of a table. + + If the table is currently loaded and in memory, the data is compiled to + binary and returned; if it is not currently loaded, the binary data is + read from the font file and returned. """ tag = Tag(tag) if self.isLoaded(tag): @@ -688,9 +702,18 @@ class TTFont(object): or None, if no unicode cmap subtable is available. By default it will search for the following (platformID, platEncID) - pairs: - (3, 10), (0, 6), (0, 4), (3, 1), (0, 3), (0, 2), (0, 1), (0, 0) - This can be customized via the cmapPreferences argument. + pairs:: + + (3, 10), + (0, 6), + (0, 4), + (3, 1), + (0, 3), + (0, 2), + (0, 1), + (0, 0) + + This can be customized via the ``cmapPreferences`` argument. """ return self["cmap"].getBestCmap(cmapPreferences=cmapPreferences) @@ -820,9 +843,9 @@ class GlyphOrder(object): def fromXML(self, name, attrs, content, ttFont): if not hasattr(self, "glyphOrder"): self.glyphOrder = [] - ttFont.setGlyphOrder(self.glyphOrder) if name == "GlyphID": self.glyphOrder.append(attrs["name"]) + ttFont.setGlyphOrder(self.glyphOrder) def getTableModule(tag): @@ -854,12 +877,13 @@ _customTableRegistry = {} def registerCustomTableClass(tag, moduleName, className=None): """Register a custom packer/unpacker class for a table. + The 'moduleName' must be an importable module. If no 'className' is given, it is derived from the tag, for example it will be - table_C_U_S_T_ for a 'CUST' tag. + ``table_C_U_S_T_`` for a 'CUST' tag. The registered table class should be a subclass of - fontTools.ttLib.tables.DefaultTable.DefaultTable + :py:class:`fontTools.ttLib.tables.DefaultTable.DefaultTable` """ if className is None: className = "table_" + tagToIdentifier(tag) @@ -930,10 +954,14 @@ def tagToIdentifier(tag): letters get an underscore after the letter. Trailing spaces are trimmed. Illegal characters are escaped as two hex bytes. If the result starts with a number (as the result of a hex escape), an - extra underscore is prepended. Examples: - 'glyf' -> '_g_l_y_f' - 'cvt ' -> '_c_v_t' - 'OS/2' -> 'O_S_2f_2' + extra underscore is prepended. Examples:: + + >>> tagToIdentifier('glyf') + '_g_l_y_f' + >>> tagToIdentifier('cvt ') + '_c_v_t' + >>> tagToIdentifier('OS/2') + 'O_S_2f_2' """ import re tag = Tag(tag) |