From 8d32a247109d1511279741f4595f736212d2a66f Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Thu, 14 May 2020 17:04:34 +0100 Subject: [PATCH] [docs] Source documentation for cffLib (#1935) * [docs] Source documentation for cffLib * Address feedback --- Doc/source/cffLib/index.rst | 49 +++++- Doc/source/cffLib/specializer.rst | 6 +- Doc/source/cffLib/width.rst | 10 +- Lib/fontTools/cffLib/__init__.py | 236 ++++++++++++++++++++++++---- Lib/fontTools/cffLib/specializer.py | 13 +- Lib/fontTools/cffLib/width.py | 7 +- 6 files changed, 277 insertions(+), 44 deletions(-) diff --git a/Doc/source/cffLib/index.rst b/Doc/source/cffLib/index.rst index be8073e39..281a0b128 100644 --- a/Doc/source/cffLib/index.rst +++ b/Doc/source/cffLib/index.rst @@ -1,6 +1,10 @@ -###### -cffLib -###### +################################## +cffLib: read/write Adobe CFF fonts +################################## + +.. automodule:: fontTools.cffLib + +This package also contains two modules for manipulating CFF format glyphs: .. toctree:: :maxdepth: 1 @@ -8,7 +12,42 @@ cffLib specializer width -.. automodule:: fontTools.cffLib +.. autoclass:: fontTools.cffLib.CFFFontSet :inherited-members: :members: - :undoc-members: + +.. autoclass:: fontTools.cffLib.TopDict + :members: + +.. autoclass:: fontTools.cffLib.CharStrings + :members: + +.. autoclass:: fontTools.cffLib.Index + :members: + +.. autoclass:: fontTools.cffLib.GlobalSubrsIndex + :members: + +.. autoclass:: fontTools.cffLib.TopDictIndex + :members: + +.. autoclass:: fontTools.cffLib.CFFWriter + :members: + +.. autoclass:: fontTools.cffLib.IndexCompiler + :members: + +.. autoclass:: fontTools.cffLib.TopDictIndexCompiler + :members: + +.. autoclass:: fontTools.cffLib.FDArrayIndexCompiler + :members: + +.. autoclass:: fontTools.cffLib.GlobalSubrsCompiler + :members: + +.. autoclass:: fontTools.cffLib.SubrsCompiler + :members: + +.. autoclass:: fontTools.cffLib.CharStringsCompiler + :members: diff --git a/Doc/source/cffLib/specializer.rst b/Doc/source/cffLib/specializer.rst index 1d6645c71..016a89621 100644 --- a/Doc/source/cffLib/specializer.rst +++ b/Doc/source/cffLib/specializer.rst @@ -1,6 +1,6 @@ -########### -specializer -########### +############################################################## +specializer: T2CharString operator specializer and generalizer +############################################################## .. automodule:: fontTools.cffLib.specializer :inherited-members: diff --git a/Doc/source/cffLib/width.rst b/Doc/source/cffLib/width.rst index 704a9aa4e..68944da8a 100644 --- a/Doc/source/cffLib/width.rst +++ b/Doc/source/cffLib/width.rst @@ -1,8 +1,6 @@ -##### -width -##### +######################################### +width: T2CharString glyph width optimizer +######################################### .. automodule:: fontTools.cffLib.width - :inherited-members: - :members: - :undoc-members: + :members: optimizeWidths, optimizeWidthsBruteforce diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py index d67bc13e8..e97b75012 100644 --- a/Lib/fontTools/cffLib/__init__.py +++ b/Lib/fontTools/cffLib/__init__.py @@ -1,4 +1,15 @@ -"""cffLib.py -- read/write tools for Adobe CFF fonts.""" +"""cffLib: read/write Adobe CFF fonts + +OpenType fonts with PostScript outlines contain a completely independent +font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts +requires also dealing with CFF. This module allows you to read and write +fonts written in the CFF format. + +In 2016, OpenType 1.8 introduced the `CFF2 `_ +format which, along with other changes, extended the CFF format to deal with +the demands of variable fonts. This module parses both original CFF and CFF2. + +""" from fontTools.misc.py23 import * from fontTools.misc import sstruct @@ -28,8 +39,37 @@ maxStackLimit = 513 class CFFFontSet(object): + """A CFF font "file" can contain more than one font, although this is + extremely rare (and not allowed within OpenType fonts). + + This class is the entry point for parsing a CFF table. To actually + manipulate the data inside the CFF font, you will want to access the + ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet`` + object can either be treated as a dictionary (with appropriate + ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict` + objects, or as a list. + + .. code:: python + + from fontTools import ttLib + tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf") + tt["CFF "].cff + # + tt["CFF "].cff[0] # Here's your actual font data + # + + """ def decompile(self, file, otFont, isCFF2=None): + """Parse a binary CFF file into an internal representation. ``file`` + should be a file handle object. ``otFont`` is the top-level + :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. + + If ``isCFF2`` is passed and set to ``True`` or ``False``, then the + library makes an assertion that the CFF header is of the appropriate + version. + """ + self.otFont = otFont sstruct.unpack(cffHeaderFormat, file.read(3), self) if isCFF2 is not None: @@ -89,6 +129,14 @@ class CFFFontSet(object): return self.topDictIndex[index] def compile(self, file, otFont, isCFF2=None): + """Write the object back into binary representation onto the given file. + ``file`` should be a file handle object. ``otFont`` is the top-level + :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. + + If ``isCFF2`` is passed and set to ``True`` or ``False``, then the + library makes an assertion that the CFF header is of the appropriate + version. + """ self.otFont = otFont if isCFF2 is not None: # called from ttLib: assert 'major' value matches expected version @@ -144,6 +192,16 @@ class CFFFontSet(object): writer.toFile(file) def toXML(self, xmlWriter): + """Write the object into XML representation onto the given + :class:`fontTools.misc.xmlWriter.XMLWriter`. + + .. code:: python + + writer = xmlWriter.XMLWriter(sys.stdout) + tt["CFF "].cff.toXML(writer) + + """ + xmlWriter.simpletag("major", value=self.major) xmlWriter.newline() xmlWriter.simpletag("minor", value=self.minor) @@ -163,6 +221,7 @@ class CFFFontSet(object): xmlWriter.newline() def fromXML(self, name, attrs, content, otFont=None): + """Reads data from the XML element into the ``CFFFontSet`` object.""" self.otFont = otFont # set defaults. These will be replaced if there are entries for them @@ -230,7 +289,11 @@ class CFFFontSet(object): self.minor = int(attrs['value']) def convertCFFToCFF2(self, otFont): - # This assumes a decompiled CFF table. + """Converts this object from CFF format to CFF2 format. This conversion + is done 'in-place'. The conversion cannot be reversed. + + This assumes a decompiled CFF table. (i.e. that the object has been + filled via :meth:`decompile`.)""" self.major = 2 cff2GetGlyphOrder = self.otFont.getGlyphOrder topDictData = TopDictIndex(None, cff2GetGlyphOrder, None) @@ -307,7 +370,8 @@ class CFFFontSet(object): class CFFWriter(object): - + """Helper class for serializing CFF data to binary. Used by + :meth:`CFFFontSet.compile`.""" def __init__(self, isCFF2): self.data = [] self.isCFF2 = isCFF2 @@ -367,6 +431,8 @@ def calcOffSize(largestOffset): class IndexCompiler(object): + """Base class for writing CFF `INDEX data `_ + to binary.""" def __init__(self, items, strings, parent, isCFF2=None): if isCFF2 is None and hasattr(parent, "isCFF2"): @@ -446,6 +512,7 @@ class IndexedStringsCompiler(IndexCompiler): class TopDictIndexCompiler(IndexCompiler): + """Helper class for writing the TopDict to binary.""" def getItems(self, items, strings): out = [] @@ -481,6 +548,9 @@ class TopDictIndexCompiler(IndexCompiler): class FDArrayIndexCompiler(IndexCompiler): + """Helper class for writing the + `Font DICT INDEX `_ + to binary.""" def getItems(self, items, strings): out = [] @@ -519,6 +589,8 @@ class FDArrayIndexCompiler(IndexCompiler): class GlobalSubrsCompiler(IndexCompiler): + """Helper class for writing the `global subroutine INDEX `_ + to binary.""" def getItems(self, items, strings): out = [] @@ -529,14 +601,17 @@ class GlobalSubrsCompiler(IndexCompiler): class SubrsCompiler(GlobalSubrsCompiler): - + """Helper class for writing the `local subroutine INDEX `_ + to binary.""" + def setPos(self, pos, endPos): offset = pos - self.parent.pos self.parent.rawDict["Subrs"] = offset class CharStringsCompiler(GlobalSubrsCompiler): - + """Helper class for writing the `CharStrings INDEX `_ + to binary.""" def getItems(self, items, strings): out = [] for cs in items: @@ -549,8 +624,9 @@ class CharStringsCompiler(GlobalSubrsCompiler): class Index(object): - - """This class represents what the CFF spec calls an INDEX.""" + """This class represents what the CFF spec calls an INDEX (an array of + variable-sized objects). `Index` items can be addressed and set using + Python list indexing.""" compilerClass = IndexCompiler @@ -608,16 +684,50 @@ class Index(object): return data def append(self, item): + """Add an item to an INDEX.""" self.items.append(item) def getCompiler(self, strings, parent, isCFF2=None): return self.compilerClass(self, strings, parent, isCFF2=isCFF2) def clear(self): + """Empty the INDEX.""" del self.items[:] class GlobalSubrsIndex(Index): + """This index contains all the global subroutines in the font. A global + subroutine is a set of ``CharString`` data which is accessible to any + glyph in the font, and are used to store repeated instructions - for + example, components may be encoded as global subroutines, but so could + hinting instructions. + + Remember that when interpreting a ``callgsubr`` instruction (or indeed + a ``callsubr`` instruction) that you will need to add the "subroutine + number bias" to number given: + + .. code:: python + + tt = ttLib.TTFont("Almendra-Bold.otf") + u = tt["CFF "].cff[0].CharStrings["udieresis"] + u.decompile() + + u.toXML(XMLWriter(sys.stdout)) + # + # -64 callgsubr <-- Subroutine which implements the dieresis mark + # + + tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG + # + + tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT + # + + ("The bias applied depends on the number of subrs (gsubrs). If the number of + subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less + than 33900, it is 1131; otherwise it is 32768.", + `Subroutine Operators `) + """ compilerClass = GlobalSubrsCompiler subrClass = psCharStrings.T2CharString @@ -647,6 +757,15 @@ class GlobalSubrsIndex(Index): return self.subrClass(data, private=private, globalSubrs=self.globalSubrs) def toXML(self, xmlWriter): + """Write the subroutines index into XML representation onto the given + :class:`fontTools.misc.xmlWriter.XMLWriter`. + + .. code:: python + + writer = xmlWriter.XMLWriter(sys.stdout) + tt["CFF "].cff[0].GlobalSubrs.toXML(writer) + + """ xmlWriter.comment( "The 'index' attribute is only for humans; " "it is ignored when parsed.") @@ -677,10 +796,26 @@ class GlobalSubrsIndex(Index): class SubrsIndex(GlobalSubrsIndex): + """This index contains a glyph's local subroutines. A local subroutine is a + private set of ``CharString`` data which is accessible only to the glyph to + which the index is attached.""" + compilerClass = SubrsCompiler class TopDictIndex(Index): + """This index represents the array of ``TopDict`` structures in the font + (again, usually only one entry is present). Hence the following calls are + equivalent: + + .. code:: python + + tt["CFF "].cff[0] + # + tt["CFF "].cff.topDictIndex[0] + # + + """ compilerClass = TopDictIndexCompiler @@ -869,6 +1004,20 @@ class FDSelect(object): class CharStrings(object): + """The ``CharStrings`` in the font represent the instructions for drawing + each glyph. This object presents a dictionary interface to the font's + CharStrings, indexed by glyph name: + + .. code:: python + + tt["CFF "].cff[0].CharStrings["a"] + # + + See :class:`fontTools.misc.psCharStrings.T1CharString` and + :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile, + compile and interpret the glyph drawing instructions in the returned objects. + + """ def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=None): @@ -2087,27 +2236,33 @@ class DictCompiler(object): def arg_delta_blend(self, value): - """ A delta list with blend lists has to be *all* blend lists. - The value is a list is arranged as follows. - [ - [V0, d0..dn] - [V1, d0..dn] - ... - [Vm, d0..dn] - ] - V is the absolute coordinate value from the default font, and d0-dn are - the delta values from the n regions. Each V is an absolute coordinate - from the default font. - We want to return a list: - [ - [v0, v1..vm] - [d0..dn] - ... - [d0..dn] - numBlends - blendOp - ] - where each v is relative to the previous default font value. + """A delta list with blend lists has to be *all* blend lists. + + The value is a list is arranged as follows:: + + [ + [V0, d0..dn] + [V1, d0..dn] + ... + [Vm, d0..dn] + ] + + ``V`` is the absolute coordinate value from the default font, and ``d0-dn`` + are the delta values from the *n* regions. Each ``V`` is an absolute + coordinate from the default font. + + We want to return a list:: + + [ + [v0, v1..vm] + [d0..dn] + ... + [d0..dn] + numBlends + blendOp + ] + + where each ``v`` is relative to the previous default font value. """ numMasters = len(value[0]) numBlends = len(value) @@ -2356,6 +2511,30 @@ class BaseDict(object): class TopDict(BaseDict): + """The ``TopDict`` represents the top-level dictionary holding font + information. CFF2 tables contain a restricted set of top-level entries + as described `here `_, + but CFF tables may contain a wider range of information. This information + can be accessed through attributes or through the dictionary returned + through the ``rawDict`` property: + + .. code:: python + + font = tt["CFF "].cff[0] + font.FamilyName + # 'Linux Libertine O' + font.rawDict["FamilyName"] + # 'Linux Libertine O' + + More information is available in the CFF file's private dictionary, accessed + via the ``Private`` property: + + .. code:: python + + tt["CFF "].cff[0].Private.BlueValues + # [-15, 0, 515, 515, 666, 666] + + """ defaults = buildDefaults(topDictOperators) converters = buildConverters(topDictOperators) @@ -2377,6 +2556,7 @@ class TopDict(BaseDict): self.order = buildOrder(topDictOperators) def getGlyphOrder(self): + """Returns a list of glyph names in the CFF font.""" return self.charset def postDecompile(self): diff --git a/Lib/fontTools/cffLib/specializer.py b/Lib/fontTools/cffLib/specializer.py index ff2c9ae8c..1d2f4b737 100644 --- a/Lib/fontTools/cffLib/specializer.py +++ b/Lib/fontTools/cffLib/specializer.py @@ -1,6 +1,17 @@ # -*- coding: utf-8 -*- -"""T2CharString operator specializer and generalizer.""" +"""T2CharString operator specializer and generalizer. + +PostScript glyph drawing operations can be expressed in multiple different +ways. For example, as well as the ``lineto`` operator, there is also a +``hlineto`` operator which draws a horizontal line, removing the need to +specify a ``dx`` coordinate, and a ``vlineto`` operator which draws a +vertical line, removing the need to specify a ``dy`` coordinate. As well +as decompiling :class:`fontTools.misc.psCharStrings.T2CharString` objects +into lists of operations, this module allows for conversion between general +and specific forms of the operation. + +""" from fontTools.misc.py23 import * from fontTools.cffLib import maxStackLimit diff --git a/Lib/fontTools/cffLib/width.py b/Lib/fontTools/cffLib/width.py index fefae3aa8..edce446fa 100644 --- a/Lib/fontTools/cffLib/width.py +++ b/Lib/fontTools/cffLib/width.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- -"""T2CharString glyph width optimizer.""" +"""T2CharString glyph width optimizer. + +CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX`` +value do not need to specify their width in their charstring, saving bytes. +This module determines the optimum ``defaultWidthX`` and ``nominalWidthX`` +values for a font, when provided with a list of glyph widths.""" from fontTools.misc.py23 import * from fontTools.ttLib import TTFont, getTableClass