diff --git a/Lib/fontTools/pens/freetypePen.py b/Lib/fontTools/pens/freetypePen.py index 870776bc7..065da932a 100644 --- a/Lib/fontTools/pens/freetypePen.py +++ b/Lib/fontTools/pens/freetypePen.py @@ -46,7 +46,7 @@ class FreeTypePen(BasePen): glyphSet: a dictionary of drawable glyph objects keyed by name used to resolve component references in composite glyphs. - :Examples: + Examples: If `numpy` and `matplotlib` is available, the following code will show the glyph image of `fi` in a new window:: @@ -178,7 +178,7 @@ class FreeTypePen(BasePen): object of the resulted bitmap and ``size`` is a 2-tuple of its dimension. - :Notes: + Notes: The image size should always be given explicitly if you need to get a proper glyph image. When ``width`` and ``height`` are omitted, it forcifully fits to the bounding box and the side bearings get @@ -188,15 +188,15 @@ class FreeTypePen(BasePen): maintained but RSB won’t. The difference between the two becomes more obvious when rotate or skew transformation is applied. - :Example: - .. code-block:: + Example: + .. code-block:: pycon + >>> >> pen = FreeTypePen(None) >> glyph.draw(pen) >> buf, size = pen.buffer(width=500, height=1000) >> type(buf), len(buf), size (, 500000, (500, 1000)) - """ transform = transform or Transform() if not hasattr(transform, "transformPoint"): @@ -269,7 +269,7 @@ class FreeTypePen(BasePen): A ``numpy.ndarray`` object with a shape of ``(height, width)``. Each element takes a value in the range of ``[0.0, 1.0]``. - :Notes: + Notes: The image size should always be given explicitly if you need to get a proper glyph image. When ``width`` and ``height`` are omitted, it forcifully fits to the bounding box and the side bearings get @@ -279,15 +279,17 @@ class FreeTypePen(BasePen): maintained but RSB won’t. The difference between the two becomes more obvious when rotate or skew transformation is applied. - :Example: - .. code-block:: + Example: + .. code-block:: pycon + >>> >> pen = FreeTypePen(None) >> glyph.draw(pen) >> arr = pen.array(width=500, height=1000) >> type(a), a.shape (, (1000, 500)) """ + import numpy as np buf, size = self.buffer( @@ -318,7 +320,7 @@ class FreeTypePen(BasePen): rendering glyphs with negative sidebearings without clipping. evenOdd: Pass ``True`` for even-odd fill instead of non-zero. - :Notes: + Notes: The image size should always be given explicitly if you need to get a proper glyph image. When ``width`` and ``height`` are omitted, it forcifully fits to the bounding box and the side bearings get @@ -328,9 +330,10 @@ class FreeTypePen(BasePen): maintained but RSB won’t. The difference between the two becomes more obvious when rotate or skew transformation is applied. - :Example: - .. code-block:: + Example: + .. code-block:: pycon + >>> >> pen = FreeTypePen(None) >> glyph.draw(pen) >> pen.show(width=500, height=1000) @@ -370,7 +373,7 @@ class FreeTypePen(BasePen): A ``PIL.image`` object. The image is filled in black with alpha channel obtained from the rendered bitmap. - :Notes: + Notes: The image size should always be given explicitly if you need to get a proper glyph image. When ``width`` and ``height`` are omitted, it forcifully fits to the bounding box and the side bearings get @@ -380,9 +383,10 @@ class FreeTypePen(BasePen): maintained but RSB won’t. The difference between the two becomes more obvious when rotate or skew transformation is applied. - :Example: - .. code-block:: + Example: + .. code-block:: pycon + >>> >> pen = FreeTypePen(None) >> glyph.draw(pen) >> img = pen.image(width=500, height=1000) diff --git a/Lib/fontTools/pens/pointInsidePen.py b/Lib/fontTools/pens/pointInsidePen.py index e1fbbbcb1..0c022d31b 100644 --- a/Lib/fontTools/pens/pointInsidePen.py +++ b/Lib/fontTools/pens/pointInsidePen.py @@ -15,7 +15,8 @@ class PointInsidePen(BasePen): Instances of this class can be recycled, as long as the setTestPoint() method is used to set the new point to test. - Typical usage: + :Example: + .. code-block:: pen = PointInsidePen(glyphSet, (100, 200)) outline.draw(pen) diff --git a/Lib/fontTools/pens/recordingPen.py b/Lib/fontTools/pens/recordingPen.py index ba165e195..b8a817ccf 100644 --- a/Lib/fontTools/pens/recordingPen.py +++ b/Lib/fontTools/pens/recordingPen.py @@ -33,6 +33,7 @@ class RecordingPen(AbstractPen): pen.replay(otherPen). :Example: + .. code-block:: from fontTools.ttLib import TTFont from fontTools.pens.recordingPen import RecordingPen @@ -91,47 +92,48 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen): by thir name; other arguments are forwarded to the DecomposingPen's constructor:: - >>> class SimpleGlyph(object): - ... def draw(self, pen): - ... pen.moveTo((0, 0)) - ... pen.curveTo((1, 1), (2, 2), (3, 3)) - ... pen.closePath() - >>> class CompositeGlyph(object): - ... def draw(self, pen): - ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) - >>> class MissingComponent(object): - ... def draw(self, pen): - ... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0)) - >>> class FlippedComponent(object): - ... def draw(self, pen): - ... pen.addComponent('a', (-1, 0, 0, 1, 0, 0)) - >>> glyphSet = { - ... 'a': SimpleGlyph(), - ... 'b': CompositeGlyph(), - ... 'c': MissingComponent(), - ... 'd': FlippedComponent(), - ... } - >>> for name, glyph in sorted(glyphSet.items()): - ... pen = DecomposingRecordingPen(glyphSet) - ... try: - ... glyph.draw(pen) - ... except pen.MissingComponentError: - ... pass - ... print("{}: {}".format(name, pen.value)) - a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] - b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] - c: [] - d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())] - >>> for name, glyph in sorted(glyphSet.items()): - ... pen = DecomposingRecordingPen( - ... glyphSet, skipMissingComponents=True, reverseFlipped=True, - ... ) - ... glyph.draw(pen) - ... print("{}: {}".format(name, pen.value)) - a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] - b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] - c: [] - d: [('moveTo', ((0, 0),)), ('lineTo', ((-3, 3),)), ('curveTo', ((-2, 2), (-1, 1), (0, 0))), ('closePath', ())] + >>> class SimpleGlyph(object): + ... def draw(self, pen): + ... pen.moveTo((0, 0)) + ... pen.curveTo((1, 1), (2, 2), (3, 3)) + ... pen.closePath() + >>> class CompositeGlyph(object): + ... def draw(self, pen): + ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) + >>> class MissingComponent(object): + ... def draw(self, pen): + ... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0)) + >>> class FlippedComponent(object): + ... def draw(self, pen): + ... pen.addComponent('a', (-1, 0, 0, 1, 0, 0)) + >>> glyphSet = { + ... 'a': SimpleGlyph(), + ... 'b': CompositeGlyph(), + ... 'c': MissingComponent(), + ... 'd': FlippedComponent(), + ... } + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPen(glyphSet) + ... try: + ... glyph.draw(pen) + ... except pen.MissingComponentError: + ... pass + ... print("{}: {}".format(name, pen.value)) + a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] + b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] + c: [] + d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())] + + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPen( + ... glyphSet, skipMissingComponents=True, reverseFlipped=True, + ... ) + ... glyph.draw(pen) + ... print("{}: {}".format(name, pen.value)) + a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] + b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] + c: [] + d: [('moveTo', ((0, 0),)), ('lineTo', ((-3, 3),)), ('curveTo', ((-2, 2), (-1, 1), (0, 0))), ('closePath', ())] """ # raises MissingComponentError(KeyError) if base glyph is not found in glyphSet @@ -145,6 +147,7 @@ class RecordingPointPen(AbstractPointPen): pointPen.replay(otherPointPen). :Example: + .. code-block:: from defcon import Font from fontTools.pens.recordingPen import RecordingPointPen @@ -211,81 +214,82 @@ class DecomposingRecordingPointPen(DecomposingPointPen, RecordingPointPen): keyed by thir name; other arguments are forwarded to the DecomposingPointPen's constructor:: - >>> from pprint import pprint - >>> class SimpleGlyph(object): - ... def drawPoints(self, pen): - ... pen.beginPath() - ... pen.addPoint((0, 0), "line") - ... pen.addPoint((1, 1)) - ... pen.addPoint((2, 2)) - ... pen.addPoint((3, 3), "curve") - ... pen.endPath() - >>> class CompositeGlyph(object): - ... def drawPoints(self, pen): - ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) - >>> class MissingComponent(object): - ... def drawPoints(self, pen): - ... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0)) - >>> class FlippedComponent(object): - ... def drawPoints(self, pen): - ... pen.addComponent('a', (-1, 0, 0, 1, 0, 0)) - >>> glyphSet = { - ... 'a': SimpleGlyph(), - ... 'b': CompositeGlyph(), - ... 'c': MissingComponent(), - ... 'd': FlippedComponent(), - ... } - >>> for name, glyph in sorted(glyphSet.items()): - ... pen = DecomposingRecordingPointPen(glyphSet) - ... try: - ... glyph.drawPoints(pen) - ... except pen.MissingComponentError: - ... pass - ... pprint({name: pen.value}) - {'a': [('beginPath', (), {}), - ('addPoint', ((0, 0), 'line', False, None), {}), - ('addPoint', ((1, 1), None, False, None), {}), - ('addPoint', ((2, 2), None, False, None), {}), - ('addPoint', ((3, 3), 'curve', False, None), {}), - ('endPath', (), {})]} - {'b': [('beginPath', (), {}), - ('addPoint', ((-1, 1), 'line', False, None), {}), - ('addPoint', ((0, 2), None, False, None), {}), - ('addPoint', ((1, 3), None, False, None), {}), - ('addPoint', ((2, 4), 'curve', False, None), {}), - ('endPath', (), {})]} - {'c': []} - {'d': [('beginPath', (), {}), - ('addPoint', ((0, 0), 'line', False, None), {}), - ('addPoint', ((-1, 1), None, False, None), {}), - ('addPoint', ((-2, 2), None, False, None), {}), - ('addPoint', ((-3, 3), 'curve', False, None), {}), - ('endPath', (), {})]} - >>> for name, glyph in sorted(glyphSet.items()): - ... pen = DecomposingRecordingPointPen( - ... glyphSet, skipMissingComponents=True, reverseFlipped=True, - ... ) - ... glyph.drawPoints(pen) - ... pprint({name: pen.value}) - {'a': [('beginPath', (), {}), - ('addPoint', ((0, 0), 'line', False, None), {}), - ('addPoint', ((1, 1), None, False, None), {}), - ('addPoint', ((2, 2), None, False, None), {}), - ('addPoint', ((3, 3), 'curve', False, None), {}), - ('endPath', (), {})]} - {'b': [('beginPath', (), {}), - ('addPoint', ((-1, 1), 'line', False, None), {}), - ('addPoint', ((0, 2), None, False, None), {}), - ('addPoint', ((1, 3), None, False, None), {}), - ('addPoint', ((2, 4), 'curve', False, None), {}), - ('endPath', (), {})]} - {'c': []} - {'d': [('beginPath', (), {}), - ('addPoint', ((0, 0), 'curve', False, None), {}), - ('addPoint', ((-3, 3), 'line', False, None), {}), - ('addPoint', ((-2, 2), None, False, None), {}), - ('addPoint', ((-1, 1), None, False, None), {}), - ('endPath', (), {})]} + >>> from pprint import pprint + >>> class SimpleGlyph(object): + ... def drawPoints(self, pen): + ... pen.beginPath() + ... pen.addPoint((0, 0), "line") + ... pen.addPoint((1, 1)) + ... pen.addPoint((2, 2)) + ... pen.addPoint((3, 3), "curve") + ... pen.endPath() + >>> class CompositeGlyph(object): + ... def drawPoints(self, pen): + ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) + >>> class MissingComponent(object): + ... def drawPoints(self, pen): + ... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0)) + >>> class FlippedComponent(object): + ... def drawPoints(self, pen): + ... pen.addComponent('a', (-1, 0, 0, 1, 0, 0)) + >>> glyphSet = { + ... 'a': SimpleGlyph(), + ... 'b': CompositeGlyph(), + ... 'c': MissingComponent(), + ... 'd': FlippedComponent(), + ... } + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPointPen(glyphSet) + ... try: + ... glyph.drawPoints(pen) + ... except pen.MissingComponentError: + ... pass + ... pprint({name: pen.value}) + {'a': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'line', False, None), {}), + ('addPoint', ((1, 1), None, False, None), {}), + ('addPoint', ((2, 2), None, False, None), {}), + ('addPoint', ((3, 3), 'curve', False, None), {}), + ('endPath', (), {})]} + {'b': [('beginPath', (), {}), + ('addPoint', ((-1, 1), 'line', False, None), {}), + ('addPoint', ((0, 2), None, False, None), {}), + ('addPoint', ((1, 3), None, False, None), {}), + ('addPoint', ((2, 4), 'curve', False, None), {}), + ('endPath', (), {})]} + {'c': []} + {'d': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'line', False, None), {}), + ('addPoint', ((-1, 1), None, False, None), {}), + ('addPoint', ((-2, 2), None, False, None), {}), + ('addPoint', ((-3, 3), 'curve', False, None), {}), + ('endPath', (), {})]} + + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPointPen( + ... glyphSet, skipMissingComponents=True, reverseFlipped=True, + ... ) + ... glyph.drawPoints(pen) + ... pprint({name: pen.value}) + {'a': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'line', False, None), {}), + ('addPoint', ((1, 1), None, False, None), {}), + ('addPoint', ((2, 2), None, False, None), {}), + ('addPoint', ((3, 3), 'curve', False, None), {}), + ('endPath', (), {})]} + {'b': [('beginPath', (), {}), + ('addPoint', ((-1, 1), 'line', False, None), {}), + ('addPoint', ((0, 2), None, False, None), {}), + ('addPoint', ((1, 3), None, False, None), {}), + ('addPoint', ((2, 4), 'curve', False, None), {}), + ('endPath', (), {})]} + {'c': []} + {'d': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'curve', False, None), {}), + ('addPoint', ((-3, 3), 'line', False, None), {}), + ('addPoint', ((-2, 2), None, False, None), {}), + ('addPoint', ((-1, 1), None, False, None), {}), + ('endPath', (), {})]} """ # raises MissingComponentError(KeyError) if base glyph is not found in glyphSet diff --git a/Lib/fontTools/pens/svgPathPen.py b/Lib/fontTools/pens/svgPathPen.py index 29d41a802..29d128da3 100644 --- a/Lib/fontTools/pens/svgPathPen.py +++ b/Lib/fontTools/pens/svgPathPen.py @@ -9,27 +9,30 @@ def pointToString(pt, ntos=str): class SVGPathPen(BasePen): """Pen to draw SVG path d commands. - Example:: - >>> pen = SVGPathPen(None) - >>> pen.moveTo((0, 0)) - >>> pen.lineTo((1, 1)) - >>> pen.curveTo((2, 2), (3, 3), (4, 4)) - >>> pen.closePath() - >>> pen.getCommands() - 'M0 0 1 1C2 2 3 3 4 4Z' - Args: glyphSet: a dictionary of drawable glyph objects keyed by name used to resolve component references in composite glyphs. ntos: a callable that takes a number and returns a string, to customize how numbers are formatted (default: str). + :Example: + .. code-block:: + + >>> pen = SVGPathPen(None) + >>> pen.moveTo((0, 0)) + >>> pen.lineTo((1, 1)) + >>> pen.curveTo((2, 2), (3, 3), (4, 4)) + >>> pen.closePath() + >>> pen.getCommands() + 'M0 0 1 1C2 2 3 3 4 4Z' + Note: Fonts have a coordinate system where Y grows up, whereas in SVG, Y grows down. As such, rendering path data from this pen in SVG typically results in upside-down glyphs. You can fix this by wrapping the data from this pen in an SVG group element with transform, or wrap this pen in a transform pen. For example: + .. code-block:: python spen = svgPathPen.SVGPathPen(glyphset) pen= TransformPen(spen , (1, 0, 0, -1, 0, 0)) diff --git a/Lib/fontTools/pens/transformPen.py b/Lib/fontTools/pens/transformPen.py index ff98dbddb..3db6efdf2 100644 --- a/Lib/fontTools/pens/transformPen.py +++ b/Lib/fontTools/pens/transformPen.py @@ -58,22 +58,27 @@ class TransformPointPen(FilterPointPen): """PointPen that transforms all coordinates using a Affine transformation, and passes them to another PointPen. - >>> from fontTools.pens.recordingPen import RecordingPointPen - >>> rec = RecordingPointPen() - >>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5)) - >>> v = iter(rec.value) - >>> pen.beginPath(identifier="contour-0") - >>> next(v) - ('beginPath', (), {'identifier': 'contour-0'}) - >>> pen.addPoint((100, 100), "line") - >>> next(v) - ('addPoint', ((190, 205), 'line', False, None), {}) - >>> pen.endPath() - >>> next(v) - ('endPath', (), {}) - >>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0") - >>> next(v) - ('addComponent', ('a', ), {'identifier': 'component-0'}) + For example:: + + >>> from fontTools.pens.recordingPen import RecordingPointPen + >>> rec = RecordingPointPen() + >>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5)) + >>> v = iter(rec.value) + >>> pen.beginPath(identifier="contour-0") + >>> next(v) + ('beginPath', (), {'identifier': 'contour-0'}) + + >>> pen.addPoint((100, 100), "line") + >>> next(v) + ('addPoint', ((190, 205), 'line', False, None), {}) + + >>> pen.endPath() + >>> next(v) + ('endPath', (), {}) + + >>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0") + >>> next(v) + ('addComponent', ('a', ), {'identifier': 'component-0'}) """ def __init__(self, outPointPen, transformation): diff --git a/Lib/fontTools/svgLib/path/__init__.py b/Lib/fontTools/svgLib/path/__init__.py index 742bc64ce..043b4dbe1 100644 --- a/Lib/fontTools/svgLib/path/__init__.py +++ b/Lib/fontTools/svgLib/path/__init__.py @@ -14,6 +14,8 @@ class SVGPath(object): For example, reading from an SVG file and drawing to a Defcon Glyph: + .. code-block:: + import defcon glyph = defcon.Glyph() pen = glyph.getPen() @@ -23,6 +25,8 @@ class SVGPath(object): Or reading from a string containing SVG data, using the alternative 'fromstring' (a class method): + .. code-block:: + data = '> 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 + >>> + >> 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:: + To add a table to the font, use the :py:func:`newTable` function: + .. code-block:: pycon - >> os2 = newTable("OS/2") - >> os2.version = 4 - >> # set other attributes - >> font["OS/2"] = os2 + >>> + >> 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):: + :ref:`ttx` binary): + .. code-block:: pycon - >> tt.saveXML("afont.ttx") - Dumping 'LTSH' table... - Dumping 'OS/2' table... - [...] + >> + >> 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 + >> 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:: @@ -981,14 +987,16 @@ 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:: + extra underscore is prepended. Examples: + .. code-block:: pycon - >>> tagToIdentifier('glyf') - '_g_l_y_f' - >>> tagToIdentifier('cvt ') - '_c_v_t' - >>> tagToIdentifier('OS/2') - 'O_S_2f_2' + >>> + >> tagToIdentifier('glyf') + '_g_l_y_f' + >> tagToIdentifier('cvt ') + '_c_v_t' + >> tagToIdentifier('OS/2') + 'O_S_2f_2' """ import re diff --git a/Lib/fontTools/ufoLib/__init__.py b/Lib/fontTools/ufoLib/__init__.py index a014c9317..aa57beede 100755 --- a/Lib/fontTools/ufoLib/__init__.py +++ b/Lib/fontTools/ufoLib/__init__.py @@ -7,25 +7,29 @@ of the specification. Sets that list the font info attribute names for the fontinfo.plist formats are available for external use. These are: - fontInfoAttributesVersion1 - fontInfoAttributesVersion2 - fontInfoAttributesVersion3 + +- fontInfoAttributesVersion1 +- fontInfoAttributesVersion2 +- fontInfoAttributesVersion3 A set listing the fontinfo.plist attributes that were deprecated in version 2 is available for external use: - deprecatedFontInfoAttributesVersion2 + +- deprecatedFontInfoAttributesVersion2 Functions that do basic validation on values for fontinfo.plist are available for external use. These are - validateFontInfoVersion2ValueForAttribute - validateFontInfoVersion3ValueForAttribute + +- validateFontInfoVersion2ValueForAttribute +- validateFontInfoVersion3ValueForAttribute Value conversion functions are available for converting fontinfo.plist values between the possible format versions. - convertFontInfoValueForAttributeFromVersion1ToVersion2 - convertFontInfoValueForAttributeFromVersion2ToVersion1 - convertFontInfoValueForAttributeFromVersion2ToVersion3 - convertFontInfoValueForAttributeFromVersion3ToVersion2 + +- convertFontInfoValueForAttributeFromVersion1ToVersion2 +- convertFontInfoValueForAttributeFromVersion2ToVersion1 +- convertFontInfoValueForAttributeFromVersion2ToVersion3 +- convertFontInfoValueForAttributeFromVersion3ToVersion2 """ import os diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 5c96fd01c..36b1851cb 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -10,10 +10,14 @@ ttf-interpolatable files for the masters and build a variable-font from them. Such ttf-interpolatable and designspace files can be generated from a Glyphs source, eg., using noto-source as an example: + .. code-block:: sh + $ fontmake -o ttf-interpolatable -g NotoSansArabic-MM.glyphs Then you can make a variable-font this way: + .. code-block:: sh + $ fonttools varLib master_ufo/NotoSansArabic.designspace API *will* change in near future. diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py index 59ac5c6f7..155429ab5 100644 --- a/Lib/fontTools/varLib/cff.py +++ b/Lib/fontTools/varLib/cff.py @@ -96,7 +96,7 @@ def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map): * step through each key in FontDict.Private. * For each key, step through each relevant source font Private dict, and - build a list of values to blend. + build a list of values to blend. The 'relevant' source fonts are selected by first getting the right submodel using ``vsindex_dict[vsindex]``. The indices of the diff --git a/Lib/fontTools/varLib/instancer/__init__.py b/Lib/fontTools/varLib/instancer/__init__.py index 82676d419..f6df65e96 100644 --- a/Lib/fontTools/varLib/instancer/__init__.py +++ b/Lib/fontTools/varLib/instancer/__init__.py @@ -5,7 +5,9 @@ create full instances (i.e. static fonts) from variable fonts, as well as "parti variable fonts that only contain a subset of the original variation space. For example, if you wish to pin the width axis to a given location while also -restricting the weight axis to 400..700 range, you can do:: +restricting the weight axis to 400..700 range, you can do: + +.. code-block:: sh $ fonttools varLib.instancer ./NotoSans-VF.ttf wdth=85 wght=400:700 @@ -17,32 +19,38 @@ and returns a new TTFont representing either a partial VF, or full instance if a the VF axes were given an explicit coordinate. E.g. here's how to pin the wght axis at a given location in a wght+wdth variable -font, keeping only the deltas associated with the wdth axis:: +font, keeping only the deltas associated with the wdth axis: +.. code-block:: pycon -| >>> from fontTools import ttLib -| >>> from fontTools.varLib import instancer -| >>> varfont = ttLib.TTFont("path/to/MyVariableFont.ttf") -| >>> [a.axisTag for a in varfont["fvar"].axes] # the varfont's current axes -| ['wght', 'wdth'] -| >>> partial = instancer.instantiateVariableFont(varfont, {"wght": 300}) -| >>> [a.axisTag for a in partial["fvar"].axes] # axes left after pinning 'wght' -| ['wdth'] + >>> + >> from fontTools import ttLib + >> from fontTools.varLib import instancer + >> varfont = ttLib.TTFont("path/to/MyVariableFont.ttf") + >> [a.axisTag for a in varfont["fvar"].axes] # the varfont's current axes + ['wght', 'wdth'] + >> partial = instancer.instantiateVariableFont(varfont, {"wght": 300}) + >> [a.axisTag for a in partial["fvar"].axes] # axes left after pinning 'wght' + ['wdth'] If the input location specifies all the axes, the resulting instance is no longer 'variable' (same as using fontools varLib.mutator): +.. code-block:: pycon -| >>> instance = instancer.instantiateVariableFont( -| ... varfont, {"wght": 700, "wdth": 67.5} -| ... ) -| >>> "fvar" not in instance -| True + >>> + >> instance = instancer.instantiateVariableFont( + ... varfont, {"wght": 700, "wdth": 67.5} + ... ) + >> "fvar" not in instance + True If one just want to drop an axis at the default location, without knowing in advance what the default value for that axis is, one can pass a `None` value: +.. code-block:: pycon -| >>> instance = instancer.instantiateVariableFont(varfont, {"wght": None}) -| >>> len(varfont["fvar"].axes) -| 1 + >>> + >> instance = instancer.instantiateVariableFont(varfont, {"wght": None}) + >> len(varfont["fvar"].axes) + 1 From the console script, this is equivalent to passing `wght=drop` as input. @@ -56,25 +64,33 @@ course be combined: L1 dropping one or more axes while leaving the default tables unmodified; + .. code-block:: pycon - | >>> font = instancer.instantiateVariableFont(varfont, {"wght": None}) + >>> + >> font = instancer.instantiateVariableFont(varfont, {"wght": None}) L2 dropping one or more axes while pinning them at non-default locations; - - | >>> font = instancer.instantiateVariableFont(varfont, {"wght": 700}) + .. code-block:: pycon + + >>> + >> font = instancer.instantiateVariableFont(varfont, {"wght": 700}) L3 restricting the range of variation of one or more axes, by setting either a new minimum or maximum, potentially -- though not necessarily -- dropping entire regions of variations that fall completely outside this new range. - - | >>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300)}) + .. code-block:: pycon + + >>> + >> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300)}) L4 moving the default location of an axis, by specifying (min,defalt,max) values: - - | >>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300, 700)}) + .. code-block:: pycon + + >>> + >> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300, 700)}) Currently only TrueType-flavored variable fonts (i.e. containing 'glyf' table) are supported, but support for CFF2 variable fonts will be added soon. @@ -897,7 +913,18 @@ def _instantiateGvarGlyph( return if optimize: + # IUP semantics depend on point equality, and so round prior to + # optimization to ensure that comparisons that happen now will be the + # same as those that happen at render time. This is especially needed + # when floating point deltas have been applied to the default position. + # See https://github.com/fonttools/fonttools/issues/3634 + # Rounding must happen only after calculating glyf metrics above, to + # preserve backwards compatibility. + # See 0010a3cd9aa25f84a3a6250dafb119743d32aa40 + coordinates.toInt() + isComposite = glyf[glyphname].isComposite() + for var in tupleVarStore: var.optimize(coordinates, endPts, isComposite=isComposite) diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py index 6c327f945..80e46bb24 100644 --- a/Lib/fontTools/varLib/mutator.py +++ b/Lib/fontTools/varLib/mutator.py @@ -1,7 +1,9 @@ """ Instantiate a variation font. Run, eg: -$ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85 +.. code-block:: sh + + $ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85 """ from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed @@ -162,7 +164,9 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True): defining the desired location along the variable font's axes. The location values must be specified as user-space coordinates, e.g.: - {'wght': 400, 'wdth': 100} + .. code-block:: + + {'wght': 400, 'wdth': 100} By default, a new TTFont object is returned. If ``inplace`` is True, the input varfont is modified and reduced to a static font. diff --git a/Lib/fontTools/voltLib/voltToFea.py b/Lib/fontTools/voltLib/voltToFea.py index 2265d5029..c77d5ad11 100644 --- a/Lib/fontTools/voltLib/voltToFea.py +++ b/Lib/fontTools/voltLib/voltToFea.py @@ -7,12 +7,16 @@ Usage To convert a VTP project file: +.. code-block:: sh + $ fonttools voltLib.voltToFea input.vtp output.fea It is also possible convert font files with `TSIV` table (as saved from Volt), in this case the glyph names used in the Volt project will be mapped to the actual glyph names in the font files when written to the feature file: +.. code-block:: sh + $ fonttools voltLib.voltToFea input.ttf output.fea The ``--quiet`` option can be used to suppress warnings. diff --git a/Tests/varLib/instancer/data/3634-VF.ttx b/Tests/varLib/instancer/data/3634-VF.ttx new file mode 100644 index 000000000..1ef750b33 --- /dev/null +++ b/Tests/varLib/instancer/data/3634-VF.ttx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Weight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 400.0 + 400.0 + 900.0 + 256 + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/instancer/data/test_results/3634-VF-partial.ttx b/Tests/varLib/instancer/data/test_results/3634-VF-partial.ttx new file mode 100644 index 000000000..b639a3d6e --- /dev/null +++ b/Tests/varLib/instancer/data/test_results/3634-VF-partial.ttx @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Weight + + + New Font + + + Regular + + + 0.000;NONE;NewFont-Regular + + + New Font Regular + + + Version 0.000 + + + NewFont-Regular + + + Weight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wght + 0x0 + 401.0 + 401.0 + 900.0 + 256 + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/instancer/instancer_test.py b/Tests/varLib/instancer/instancer_test.py index b9a45058b..1ac9f35fa 100644 --- a/Tests/varLib/instancer/instancer_test.py +++ b/Tests/varLib/instancer/instancer_test.py @@ -2390,3 +2390,41 @@ def test_set_ribbi_bits(): assert name_id_2 == "Italic", location assert mac_style == 0b10, location assert fs_selection == 0b0000001, location + + +def test_rounds_before_iup(): + """Regression test for fonttools/fonttools#3634, with TTX based on + reproduction process there.""" + + varfont = ttLib.TTFont() + varfont.importXML(os.path.join(TESTDATA, "3634-VF.ttx")) + + # Instantiate at a new default position, sufficient to cause differences + # when unrounded but not when rounded. + partial = instancer.instantiateVariableFont(varfont, {"wght": (401, 401, 900)}) + + # Save and reload actual result to recalculate bounding box values, etc. + bytes_out = BytesIO() + partial.save(bytes_out) + bytes_out.seek(0) + partial = ttLib.TTFont(bytes_out) + + # Load expected result, then save and reload to normalise TTX output. + expected = ttLib.TTFont() + expected.importXML(os.path.join(TESTDATA, "test_results", "3634-VF-partial.ttx")) + + bytes_out = BytesIO() + expected.save(bytes_out) + bytes_out.seek(0) + expected = ttLib.TTFont(bytes_out) + + # Serialise actual and expected to TTX strings, and compare. + string_out = StringIO() + partial.saveXML(string_out) + partial_ttx = stripVariableItemsFromTTX(string_out.getvalue()) + + string_out = StringIO() + expected.saveXML(string_out) + expected_ttx = stripVariableItemsFromTTX(string_out.getvalue()) + + assert partial_ttx == expected_ttx