From e0dc30aa2d658ba227909b0af402229e465be027 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 18 Nov 2021 13:06:02 +0000 Subject: [PATCH] [docs] Improve documentation for fontTools.ttLib.ttFont (#2442) --- Doc/source/conf.py | 2 +- Doc/source/index.rst | 3 +- Doc/source/ttLib/index.rst | 25 +++-- Doc/source/ttLib/ttFont.rst | 16 ++- Lib/fontTools/ttLib/__init__.py | 43 +------- Lib/fontTools/ttLib/sfnt.py | 4 +- Lib/fontTools/ttLib/ttFont.py | 179 +++++++++++++++++++++----------- 7 files changed, 152 insertions(+), 120 deletions(-) diff --git a/Doc/source/conf.py b/Doc/source/conf.py index 82a5d5799..2fdc4ebed 100644 --- a/Doc/source/conf.py +++ b/Doc/source/conf.py @@ -30,7 +30,7 @@ needs_sphinx = "1.3" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.coverage"] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.coverage", "sphinx.ext.autosectionlabel"] autodoc_mock_imports = ["gtk"] diff --git a/Doc/source/index.rst b/Doc/source/index.rst index 83bb8504f..784834d83 100644 --- a/Doc/source/index.rst +++ b/Doc/source/index.rst @@ -61,7 +61,7 @@ Libraries --------- The main library you will want to access when using fontTools for font -engineering is likely to be :py:mod:`fontTools.ttLib`, which is the package +engineering is likely to be :py:mod:`fontTools.ttLib.ttFont`, which is the module for handling TrueType/OpenType fonts. However, there are many other libraries in the fontTools suite: @@ -80,6 +80,7 @@ libraries in the fontTools suite: - :py:mod:`fontTools.svgLib.path`: Library for drawing SVG paths onto glyphs - :py:mod:`fontTools.t1Lib`: Tools for PostScript Type 1 fonts (Python2 only) - :py:mod:`fontTools.tfmLib`: Module for reading TFM files +- :py:mod:`fontTools.ttLib`: Module for reading/writing OpenType and Truetype fonts - :py:mod:`fontTools.ttx`: Module for converting between OTF and XML representation - :py:mod:`fontTools.ufoLib`: Module for reading and writing UFO files - :py:mod:`fontTools.unicodedata`: Convert between Unicode and OpenType script information diff --git a/Doc/source/ttLib/index.rst b/Doc/source/ttLib/index.rst index dffb9a309..7798238da 100644 --- a/Doc/source/ttLib/index.rst +++ b/Doc/source/ttLib/index.rst @@ -1,19 +1,24 @@ -##### -ttLib -##### +############################################# +ttLib: Read/write OpenType and TrueType fonts +############################################# + +Most users of the fontTools library will be using it to generate or manipulate +OpenType and TrueType fonts. (FontTools initially only supported TrueType fonts, +gaining OpenType support in version 2.0, and so uses the ``tt`` prefix to refer to +both kinds of font. Because of this we will refer to both as "TrueType fonts" +unless we need to make a distinction.) + +The main entry point for such operations is the :py:mod:`fontTools.ttLib.ttFont` +module, but other modules also provide useful functionality for handling OpenType +fonts. .. toctree:: :maxdepth: 2 + ttFont + ttCollection macUtils sfnt standardGlyphOrder tables - ttCollection - ttFont woff2 - -.. automodule:: fontTools.ttLib - :inherited-members: - :members: - :undoc-members: diff --git a/Doc/source/ttLib/ttFont.rst b/Doc/source/ttLib/ttFont.rst index a571050c8..a3b4c9d0e 100644 --- a/Doc/source/ttLib/ttFont.rst +++ b/Doc/source/ttLib/ttFont.rst @@ -1,9 +1,17 @@ -###### -ttFont -###### +############################################## +ttFont: Read/write OpenType and TrueType fonts +############################################## -.. automodule:: fontTools.ttLib.ttFont +.. autoclass:: fontTools.ttLib.ttFont.TTFont + :inherited-members: + :members: + +.. autoclass:: fontTools.ttLib.ttFont.GlyphOrder :inherited-members: :members: :undoc-members: :private-members: + +.. automodule:: fontTools.ttLib.ttFont + :members: getTableModule, registerCustomTableClass, unregisterCustomTableClass, getCustomTableClass, getClassTag, newTable, tagToIdentifier, identifierToTag, tagToXML, xmlToTag, sortedTagList, reorderFontTables + diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py index 16417e73b..dadd7f20c 100644 --- a/Lib/fontTools/ttLib/__init__.py +++ b/Lib/fontTools/ttLib/__init__.py @@ -1,45 +1,4 @@ -"""fontTools.ttLib -- a package for dealing with TrueType fonts. - -This package offers translators to convert TrueType fonts to Python -objects and vice versa, and additionally from Python to TTX (an XML-based -text format) and vice versa. - -Example interactive session: - -Python 1.5.2c1 (#43, Mar 9 1999, 13:06:43) [CW PPC w/GUSI w/MSL] -Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam ->> from fontTools import ttLib ->> tt = ttLib.TTFont("afont.ttf") ->> tt['maxp'].numGlyphs -242 ->> tt['OS/2'].achVendID -'B&H\000' ->> tt['head'].unitsPerEm -2048 ->> tt.saveXML("afont.ttx") -Dumping 'LTSH' table... -Dumping 'OS/2' table... -Dumping 'VDMX' table... -Dumping 'cmap' table... -Dumping 'cvt ' table... -Dumping 'fpgm' table... -Dumping 'glyf' table... -Dumping 'hdmx' table... -Dumping 'head' table... -Dumping 'hhea' table... -Dumping 'hmtx' table... -Dumping 'loca' table... -Dumping 'maxp' table... -Dumping 'name' table... -Dumping 'post' table... -Dumping 'prep' table... ->> tt2 = ttLib.TTFont() ->> tt2.importXML("afont.ttx") ->> tt2['maxp'].numGlyphs -242 ->> - -""" +"""fontTools.ttLib -- a package for dealing with TrueType fonts.""" from fontTools.misc.loggingTools import deprecateFunction import logging diff --git a/Lib/fontTools/ttLib/sfnt.py b/Lib/fontTools/ttLib/sfnt.py index 8fb9e0aa0..e7c06337f 100644 --- a/Lib/fontTools/ttLib/sfnt.py +++ b/Lib/fontTools/ttLib/sfnt.py @@ -8,8 +8,8 @@ Defines two public classes: used automatically by ttLib.TTFont.) The reading and writing of sfnt files is separated in two distinct -classes, since whenever to number of tables changes or whenever -a table's length chages you need to rewrite the whole file anyway. +classes, since whenever the number of tables changes or whenever +a table's length changes you need to rewrite the whole file anyway. """ from io import BytesIO diff --git a/Lib/fontTools/ttLib/ttFont.py b/Lib/fontTools/ttLib/ttFont.py index cd0b843be..28b857d7f 100644 --- a/Lib/fontTools/ttLib/ttFont.py +++ b/Lib/fontTools/ttLib/ttFont.py @@ -12,10 +12,77 @@ 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, @@ -23,54 +90,6 @@ class TTFont(object): 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 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: @@ -138,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: @@ -320,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: @@ -337,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()): @@ -406,17 +436,24 @@ 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 def getGlyphOrder(self): + """Returns a list of glyph names ordered by their position in the font.""" try: return self.glyphOrder except AttributeError: @@ -531,18 +568,25 @@ class TTFont(object): return textTools.caselessSort(self.getGlyphOrder()) 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: 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: @@ -553,6 +597,7 @@ class TTFont(object): raise KeyError(glyphName) 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] @@ -561,6 +606,7 @@ class TTFont(object): 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 @@ -598,7 +644,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): @@ -642,9 +692,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)