Merge branch 'fonttools:main' into ttf2otf

This commit is contained in:
ftCLI 2024-09-23 09:14:08 +02:00 committed by GitHub
commit 09df5fcc02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 927 additions and 226 deletions

View File

@ -46,7 +46,7 @@ class FreeTypePen(BasePen):
glyphSet: a dictionary of drawable glyph objects keyed by name glyphSet: a dictionary of drawable glyph objects keyed by name
used to resolve component references in composite glyphs. used to resolve component references in composite glyphs.
:Examples: Examples:
If `numpy` and `matplotlib` is available, the following code will If `numpy` and `matplotlib` is available, the following code will
show the glyph image of `fi` in a new window:: 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 object of the resulted bitmap and ``size`` is a 2-tuple of its
dimension. dimension.
:Notes: Notes:
The image size should always be given explicitly if you need to get The image size should always be given explicitly if you need to get
a proper glyph image. When ``width`` and ``height`` are omitted, it a proper glyph image. When ``width`` and ``height`` are omitted, it
forcifully fits to the bounding box and the side bearings get forcifully fits to the bounding box and the side bearings get
@ -188,15 +188,15 @@ class FreeTypePen(BasePen):
maintained but RSB wont. The difference between the two becomes maintained but RSB wont. The difference between the two becomes
more obvious when rotate or skew transformation is applied. more obvious when rotate or skew transformation is applied.
:Example: Example:
.. code-block:: .. code-block:: pycon
>>>
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
>> buf, size = pen.buffer(width=500, height=1000) >> buf, size = pen.buffer(width=500, height=1000)
>> type(buf), len(buf), size >> type(buf), len(buf), size
(<class 'bytes'>, 500000, (500, 1000)) (<class 'bytes'>, 500000, (500, 1000))
""" """
transform = transform or Transform() transform = transform or Transform()
if not hasattr(transform, "transformPoint"): if not hasattr(transform, "transformPoint"):
@ -269,7 +269,7 @@ class FreeTypePen(BasePen):
A ``numpy.ndarray`` object with a shape of ``(height, width)``. A ``numpy.ndarray`` object with a shape of ``(height, width)``.
Each element takes a value in the range of ``[0.0, 1.0]``. 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 The image size should always be given explicitly if you need to get
a proper glyph image. When ``width`` and ``height`` are omitted, it a proper glyph image. When ``width`` and ``height`` are omitted, it
forcifully fits to the bounding box and the side bearings get forcifully fits to the bounding box and the side bearings get
@ -279,15 +279,17 @@ class FreeTypePen(BasePen):
maintained but RSB wont. The difference between the two becomes maintained but RSB wont. The difference between the two becomes
more obvious when rotate or skew transformation is applied. more obvious when rotate or skew transformation is applied.
:Example: Example:
.. code-block:: .. code-block:: pycon
>>>
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
>> arr = pen.array(width=500, height=1000) >> arr = pen.array(width=500, height=1000)
>> type(a), a.shape >> type(a), a.shape
(<class 'numpy.ndarray'>, (1000, 500)) (<class 'numpy.ndarray'>, (1000, 500))
""" """
import numpy as np import numpy as np
buf, size = self.buffer( buf, size = self.buffer(
@ -318,7 +320,7 @@ class FreeTypePen(BasePen):
rendering glyphs with negative sidebearings without clipping. rendering glyphs with negative sidebearings without clipping.
evenOdd: Pass ``True`` for even-odd fill instead of non-zero. 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 The image size should always be given explicitly if you need to get
a proper glyph image. When ``width`` and ``height`` are omitted, it a proper glyph image. When ``width`` and ``height`` are omitted, it
forcifully fits to the bounding box and the side bearings get forcifully fits to the bounding box and the side bearings get
@ -328,9 +330,10 @@ class FreeTypePen(BasePen):
maintained but RSB wont. The difference between the two becomes maintained but RSB wont. The difference between the two becomes
more obvious when rotate or skew transformation is applied. more obvious when rotate or skew transformation is applied.
:Example: Example:
.. code-block:: .. code-block:: pycon
>>>
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
>> pen.show(width=500, height=1000) >> 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 A ``PIL.image`` object. The image is filled in black with alpha
channel obtained from the rendered bitmap. channel obtained from the rendered bitmap.
:Notes: Notes:
The image size should always be given explicitly if you need to get The image size should always be given explicitly if you need to get
a proper glyph image. When ``width`` and ``height`` are omitted, it a proper glyph image. When ``width`` and ``height`` are omitted, it
forcifully fits to the bounding box and the side bearings get forcifully fits to the bounding box and the side bearings get
@ -380,9 +383,10 @@ class FreeTypePen(BasePen):
maintained but RSB wont. The difference between the two becomes maintained but RSB wont. The difference between the two becomes
more obvious when rotate or skew transformation is applied. more obvious when rotate or skew transformation is applied.
:Example: Example:
.. code-block:: .. code-block:: pycon
>>>
>> pen = FreeTypePen(None) >> pen = FreeTypePen(None)
>> glyph.draw(pen) >> glyph.draw(pen)
>> img = pen.image(width=500, height=1000) >> img = pen.image(width=500, height=1000)

View File

@ -15,7 +15,8 @@ class PointInsidePen(BasePen):
Instances of this class can be recycled, as long as the Instances of this class can be recycled, as long as the
setTestPoint() method is used to set the new point to test. setTestPoint() method is used to set the new point to test.
Typical usage: :Example:
.. code-block::
pen = PointInsidePen(glyphSet, (100, 200)) pen = PointInsidePen(glyphSet, (100, 200))
outline.draw(pen) outline.draw(pen)

View File

@ -33,6 +33,7 @@ class RecordingPen(AbstractPen):
pen.replay(otherPen). pen.replay(otherPen).
:Example: :Example:
.. code-block::
from fontTools.ttLib import TTFont from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import RecordingPen from fontTools.pens.recordingPen import RecordingPen
@ -122,6 +123,7 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen):
b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())]
c: [] c: []
d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())] d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())]
>>> for name, glyph in sorted(glyphSet.items()): >>> for name, glyph in sorted(glyphSet.items()):
... pen = DecomposingRecordingPen( ... pen = DecomposingRecordingPen(
... glyphSet, skipMissingComponents=True, reverseFlipped=True, ... glyphSet, skipMissingComponents=True, reverseFlipped=True,
@ -145,6 +147,7 @@ class RecordingPointPen(AbstractPointPen):
pointPen.replay(otherPointPen). pointPen.replay(otherPointPen).
:Example: :Example:
.. code-block::
from defcon import Font from defcon import Font
from fontTools.pens.recordingPen import RecordingPointPen from fontTools.pens.recordingPen import RecordingPointPen
@ -261,6 +264,7 @@ class DecomposingRecordingPointPen(DecomposingPointPen, RecordingPointPen):
('addPoint', ((-2, 2), None, False, None), {}), ('addPoint', ((-2, 2), None, False, None), {}),
('addPoint', ((-3, 3), 'curve', False, None), {}), ('addPoint', ((-3, 3), 'curve', False, None), {}),
('endPath', (), {})]} ('endPath', (), {})]}
>>> for name, glyph in sorted(glyphSet.items()): >>> for name, glyph in sorted(glyphSet.items()):
... pen = DecomposingRecordingPointPen( ... pen = DecomposingRecordingPointPen(
... glyphSet, skipMissingComponents=True, reverseFlipped=True, ... glyphSet, skipMissingComponents=True, reverseFlipped=True,

View File

@ -9,7 +9,15 @@ def pointToString(pt, ntos=str):
class SVGPathPen(BasePen): class SVGPathPen(BasePen):
"""Pen to draw SVG path d commands. """Pen to draw SVG path d commands.
Example:: 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 = SVGPathPen(None)
>>> pen.moveTo((0, 0)) >>> pen.moveTo((0, 0))
>>> pen.lineTo((1, 1)) >>> pen.lineTo((1, 1))
@ -18,18 +26,13 @@ class SVGPathPen(BasePen):
>>> pen.getCommands() >>> pen.getCommands()
'M0 0 1 1C2 2 3 3 4 4Z' '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).
Note: Note:
Fonts have a coordinate system where Y grows up, whereas in SVG, Fonts have a coordinate system where Y grows up, whereas in SVG,
Y grows down. As such, rendering path data from this pen in Y grows down. As such, rendering path data from this pen in
SVG typically results in upside-down glyphs. You can fix this SVG typically results in upside-down glyphs. You can fix this
by wrapping the data from this pen in an SVG group element with by wrapping the data from this pen in an SVG group element with
transform, or wrap this pen in a transform pen. For example: transform, or wrap this pen in a transform pen. For example:
.. code-block:: python
spen = svgPathPen.SVGPathPen(glyphset) spen = svgPathPen.SVGPathPen(glyphset)
pen= TransformPen(spen , (1, 0, 0, -1, 0, 0)) pen= TransformPen(spen , (1, 0, 0, -1, 0, 0))

View File

@ -58,6 +58,8 @@ class TransformPointPen(FilterPointPen):
"""PointPen that transforms all coordinates using a Affine transformation, """PointPen that transforms all coordinates using a Affine transformation,
and passes them to another PointPen. and passes them to another PointPen.
For example::
>>> from fontTools.pens.recordingPen import RecordingPointPen >>> from fontTools.pens.recordingPen import RecordingPointPen
>>> rec = RecordingPointPen() >>> rec = RecordingPointPen()
>>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5)) >>> pen = TransformPointPen(rec, (2, 0, 0, 2, -10, 5))
@ -65,12 +67,15 @@ class TransformPointPen(FilterPointPen):
>>> pen.beginPath(identifier="contour-0") >>> pen.beginPath(identifier="contour-0")
>>> next(v) >>> next(v)
('beginPath', (), {'identifier': 'contour-0'}) ('beginPath', (), {'identifier': 'contour-0'})
>>> pen.addPoint((100, 100), "line") >>> pen.addPoint((100, 100), "line")
>>> next(v) >>> next(v)
('addPoint', ((190, 205), 'line', False, None), {}) ('addPoint', ((190, 205), 'line', False, None), {})
>>> pen.endPath() >>> pen.endPath()
>>> next(v) >>> next(v)
('endPath', (), {}) ('endPath', (), {})
>>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0") >>> pen.addComponent("a", (1, 0, 0, 1, -10, 5), identifier="component-0")
>>> next(v) >>> next(v)
('addComponent', ('a', <Transform [2 0 0 2 -30 15]>), {'identifier': 'component-0'}) ('addComponent', ('a', <Transform [2 0 0 2 -30 15]>), {'identifier': 'component-0'})

View File

@ -14,6 +14,8 @@ class SVGPath(object):
For example, reading from an SVG file and drawing to a Defcon Glyph: For example, reading from an SVG file and drawing to a Defcon Glyph:
.. code-block::
import defcon import defcon
glyph = defcon.Glyph() glyph = defcon.Glyph()
pen = glyph.getPen() pen = glyph.getPen()
@ -23,6 +25,8 @@ class SVGPath(object):
Or reading from a string containing SVG data, using the alternative Or reading from a string containing SVG data, using the alternative
'fromstring' (a class method): 'fromstring' (a class method):
.. code-block::
data = '<?xml version="1.0" ...' data = '<?xml version="1.0" ...'
svg = SVGPath.fromstring(data) svg = SVGPath.fromstring(data)
svg.draw(pen) svg.draw(pen)

View File

@ -103,6 +103,8 @@ def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc):
If the pen has an "arcTo" method, it is called with the original values If the pen has an "arcTo" method, it is called with the original values
of the elliptical arc curve commands: of the elliptical arc curve commands:
.. code-block::
pen.arcTo(rx, ry, rotation, arc_large, arc_sweep, (x, y)) pen.arcTo(rx, ry, rotation, arc_large, arc_sweep, (x, y))
Otherwise, the arcs are approximated by series of cubic Bezier segments Otherwise, the arcs are approximated by series of cubic Bezier segments

View File

@ -1,8 +1,9 @@
"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format. """ttLib/sfnt.py -- low-level module to deal with the sfnt file format.
Defines two public classes: Defines two public classes:
SFNTReader
SFNTWriter - SFNTReader
- SFNTWriter
(Normally you don't have to use these classes explicitly; they are (Normally you don't have to use these classes explicitly; they are
used automatically by ttLib.TTFont.) used automatically by ttLib.TTFont.)

View File

@ -26,8 +26,10 @@ class TTFont(object):
accessing tables. Tables will be only decompiled when necessary, ie. when accessing tables. Tables will be only decompiled when necessary, ie. when
they're actually accessed. This means that simple operations can be extremely fast. they're actually accessed. This means that simple operations can be extremely fast.
Example usage:: Example usage:
.. code-block:: pycon
>>>
>> from fontTools import ttLib >> from fontTools import ttLib
>> tt = ttLib.TTFont("afont.ttf") # Load an existing font file >> tt = ttLib.TTFont("afont.ttf") # Load an existing font file
>> tt['maxp'].numGlyphs >> tt['maxp'].numGlyphs
@ -38,16 +40,20 @@ class TTFont(object):
2048 2048
For details of the objects returned when accessing each table, see :ref:`tables`. 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 = newTable("OS/2")
>> os2.version = 4 >> os2.version = 4
>> # set other attributes >> # set other attributes
>> font["OS/2"] = os2 >> font["OS/2"] = os2
TrueType fonts can also be serialized to and from XML format (see also the 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") >> tt.saveXML("afont.ttx")
Dumping 'LTSH' table... Dumping 'LTSH' table...
Dumping 'OS/2' table... Dumping 'OS/2' table...
@ -981,13 +987,15 @@ def tagToIdentifier(tag):
letters get an underscore after the letter. Trailing spaces are letters get an underscore after the letter. Trailing spaces are
trimmed. Illegal characters are escaped as two hex bytes. If the trimmed. Illegal characters are escaped as two hex bytes. If the
result starts with a number (as the result of a hex escape), an 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') >>>
>> tagToIdentifier('glyf')
'_g_l_y_f' '_g_l_y_f'
>>> tagToIdentifier('cvt ') >> tagToIdentifier('cvt ')
'_c_v_t' '_c_v_t'
>>> tagToIdentifier('OS/2') >> tagToIdentifier('OS/2')
'O_S_2f_2' 'O_S_2f_2'
""" """
import re import re

View File

@ -7,25 +7,29 @@ of the specification.
Sets that list the font info attribute names for the fontinfo.plist Sets that list the font info attribute names for the fontinfo.plist
formats are available for external use. These are: formats are available for external use. These are:
fontInfoAttributesVersion1
fontInfoAttributesVersion2 - fontInfoAttributesVersion1
fontInfoAttributesVersion3 - fontInfoAttributesVersion2
- fontInfoAttributesVersion3
A set listing the fontinfo.plist attributes that were deprecated A set listing the fontinfo.plist attributes that were deprecated
in version 2 is available for external use: in version 2 is available for external use:
deprecatedFontInfoAttributesVersion2
- deprecatedFontInfoAttributesVersion2
Functions that do basic validation on values for fontinfo.plist Functions that do basic validation on values for fontinfo.plist
are available for external use. These are are available for external use. These are
validateFontInfoVersion2ValueForAttribute
validateFontInfoVersion3ValueForAttribute - validateFontInfoVersion2ValueForAttribute
- validateFontInfoVersion3ValueForAttribute
Value conversion functions are available for converting Value conversion functions are available for converting
fontinfo.plist values between the possible format versions. fontinfo.plist values between the possible format versions.
convertFontInfoValueForAttributeFromVersion1ToVersion2
convertFontInfoValueForAttributeFromVersion2ToVersion1 - convertFontInfoValueForAttributeFromVersion1ToVersion2
convertFontInfoValueForAttributeFromVersion2ToVersion3 - convertFontInfoValueForAttributeFromVersion2ToVersion1
convertFontInfoValueForAttributeFromVersion3ToVersion2 - convertFontInfoValueForAttributeFromVersion2ToVersion3
- convertFontInfoValueForAttributeFromVersion3ToVersion2
""" """
import os import os

View File

@ -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 them. Such ttf-interpolatable and designspace files can be generated from
a Glyphs source, eg., using noto-source as an example: a Glyphs source, eg., using noto-source as an example:
.. code-block:: sh
$ fontmake -o ttf-interpolatable -g NotoSansArabic-MM.glyphs $ fontmake -o ttf-interpolatable -g NotoSansArabic-MM.glyphs
Then you can make a variable-font this way: Then you can make a variable-font this way:
.. code-block:: sh
$ fonttools varLib master_ufo/NotoSansArabic.designspace $ fonttools varLib master_ufo/NotoSansArabic.designspace
API *will* change in near future. API *will* change in near future.

View File

@ -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. 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 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 $ 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. 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 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 >> from fontTools import ttLib
| >>> varfont = ttLib.TTFont("path/to/MyVariableFont.ttf") >> from fontTools.varLib import instancer
| >>> [a.axisTag for a in varfont["fvar"].axes] # the varfont's current axes >> varfont = ttLib.TTFont("path/to/MyVariableFont.ttf")
| ['wght', 'wdth'] >> [a.axisTag for a in varfont["fvar"].axes] # the varfont's current axes
| >>> partial = instancer.instantiateVariableFont(varfont, {"wght": 300}) ['wght', 'wdth']
| >>> [a.axisTag for a in partial["fvar"].axes] # axes left after pinning 'wght' >> partial = instancer.instantiateVariableFont(varfont, {"wght": 300})
| ['wdth'] >> [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 If the input location specifies all the axes, the resulting instance is no longer
'variable' (same as using fontools varLib.mutator): 'variable' (same as using fontools varLib.mutator):
.. code-block:: pycon
| >>> instance = instancer.instantiateVariableFont( >>>
| ... varfont, {"wght": 700, "wdth": 67.5} >> instance = instancer.instantiateVariableFont(
| ... ) ... varfont, {"wght": 700, "wdth": 67.5}
| >>> "fvar" not in instance ... )
| True >> "fvar" not in instance
True
If one just want to drop an axis at the default location, without knowing in 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: 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) >> instance = instancer.instantiateVariableFont(varfont, {"wght": None})
| 1 >> len(varfont["fvar"].axes)
1
From the console script, this is equivalent to passing `wght=drop` as input. From the console script, this is equivalent to passing `wght=drop` as input.
@ -56,25 +64,33 @@ course be combined:
L1 L1
dropping one or more axes while leaving the default tables unmodified; 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 L2
dropping one or more axes while pinning them at non-default locations; dropping one or more axes while pinning them at non-default locations;
.. code-block:: pycon
| >>> font = instancer.instantiateVariableFont(varfont, {"wght": 700}) >>>
>> font = instancer.instantiateVariableFont(varfont, {"wght": 700})
L3 L3
restricting the range of variation of one or more axes, by setting either restricting the range of variation of one or more axes, by setting either
a new minimum or maximum, potentially -- though not necessarily -- dropping a new minimum or maximum, potentially -- though not necessarily -- dropping
entire regions of variations that fall completely outside this new range. entire regions of variations that fall completely outside this new range.
.. code-block:: pycon
| >>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300)}) >>>
>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300)})
L4 L4
moving the default location of an axis, by specifying (min,defalt,max) values: moving the default location of an axis, by specifying (min,defalt,max) values:
.. code-block:: pycon
| >>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300, 700)}) >>>
>> font = instancer.instantiateVariableFont(varfont, {"wght": (100, 300, 700)})
Currently only TrueType-flavored variable fonts (i.e. containing 'glyf' table) Currently only TrueType-flavored variable fonts (i.e. containing 'glyf' table)
are supported, but support for CFF2 variable fonts will be added soon. are supported, but support for CFF2 variable fonts will be added soon.
@ -897,7 +913,18 @@ def _instantiateGvarGlyph(
return return
if optimize: 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() isComposite = glyf[glyphname].isComposite()
for var in tupleVarStore: for var in tupleVarStore:
var.optimize(coordinates, endPts, isComposite=isComposite) var.optimize(coordinates, endPts, isComposite=isComposite)

View File

@ -1,7 +1,9 @@
""" """
Instantiate a variation font. Run, eg: 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 from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed
@ -162,6 +164,8 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
defining the desired location along the variable font's axes. defining the desired location along the variable font's axes.
The location values must be specified as user-space coordinates, e.g.: The location values must be specified as user-space coordinates, e.g.:
.. code-block::
{'wght': 400, 'wdth': 100} {'wght': 400, 'wdth': 100}
By default, a new TTFont object is returned. If ``inplace`` is True, the By default, a new TTFont object is returned. If ``inplace`` is True, the

View File

@ -7,12 +7,16 @@ Usage
To convert a VTP project file: To convert a VTP project file:
.. code-block:: sh
$ fonttools voltLib.voltToFea input.vtp output.fea $ fonttools voltLib.voltToFea input.vtp output.fea
It is also possible convert font files with `TSIV` table (as saved from Volt), 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 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: actual glyph names in the font files when written to the feature file:
.. code-block:: sh
$ fonttools voltLib.voltToFea input.ttf output.fea $ fonttools voltLib.voltToFea input.ttf output.fea
The ``--quiet`` option can be used to suppress warnings. The ``--quiet`` option can be used to suppress warnings.

View File

@ -0,0 +1,294 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.53">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="A"/>
</GlyphOrder>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="0.0"/>
<checkSumAdjustment value="0x139cb6b8"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Wed Sep 11 11:44:03 2024"/>
<modified value="Wed Sep 11 11:44:03 2024"/>
<xMin value="0"/>
<yMin value="-200"/>
<xMax value="1000"/>
<yMax value="1000"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="6"/>
<fontDirectionHint value="2"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="1000"/>
<descent value="-200"/>
<lineGap value="0"/>
<advanceWidthMax value="1000"/>
<minLeftSideBearing value="0"/>
<minRightSideBearing value="0"/>
<xMaxExtent value="1000"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="2"/>
</hhea>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="2"/>
<maxPoints value="8"/>
<maxContours value="2"/>
<maxCompositePoints value="0"/>
<maxCompositeContours value="0"/>
<maxZones value="1"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
<maxComponentDepth value="0"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
<xAvgCharWidth value="750"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="600"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="75"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="600"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="350"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="300"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="NONE"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="65"/>
<usLastCharIndex value="65"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="200"/>
<usWinAscent value="1000"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="500"/>
<sCapHeight value="700"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="500" lsb="50"/>
<mtx name="A" width="1000" lsb="0"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
</cmap_format_4>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<!-- The xMin, yMin, xMax and yMax values
will be recalculated by the compiler. -->
<TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
<contour>
<pt x="50" y="-200" on="1"/>
<pt x="50" y="800" on="1"/>
<pt x="450" y="800" on="1"/>
<pt x="450" y="-200" on="1"/>
</contour>
<contour>
<pt x="100" y="-150" on="1"/>
<pt x="400" y="-150" on="1"/>
<pt x="400" y="750" on="1"/>
<pt x="100" y="750" on="1"/>
</contour>
<instructions/>
</TTGlyph>
<TTGlyph name="A" xMin="0" yMin="0" xMax="1000" yMax="1000">
<contour>
<pt x="0" y="1000" on="1"/>
<pt x="1000" y="1000" on="1"/>
<pt x="1000" y="0" on="1"/>
<pt x="0" y="0" on="1"/>
</contour>
<instructions/>
</TTGlyph>
</glyf>
<name>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Weight
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
New Font
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
0.000;NONE;NewFont-Regular
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
New Font Regular
</namerecord>
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
Version 0.000
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
NewFont-Regular
</namerecord>
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
Weight
</namerecord>
</name>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
</extraNames>
</post>
<HVAR>
<Version value="0x00010000"/>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=1 -->
<!-- RegionCount=0 -->
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=1 -->
<NumShorts value="0"/>
<!-- VarRegionCount=0 -->
<Item index="0" value="[]"/>
</VarData>
</VarStore>
<AdvWidthMap>
<Map glyph=".notdef" outer="0" inner="0"/>
<Map glyph="A" outer="0" inner="0"/>
</AdvWidthMap>
</HVAR>
<STAT>
<Version value="0x00010001"/>
<DesignAxisRecordSize value="8"/>
<!-- DesignAxisCount=1 -->
<DesignAxisRecord>
<Axis index="0">
<AxisTag value="wght"/>
<AxisNameID value="256"/> <!-- Weight -->
<AxisOrdering value="0"/>
</Axis>
</DesignAxisRecord>
<!-- AxisValueCount=0 -->
<ElidedFallbackNameID value="2"/> <!-- Regular -->
</STAT>
<fvar>
<!-- Weight -->
<Axis>
<AxisTag>wght</AxisTag>
<Flags>0x0</Flags>
<MinValue>400.0</MinValue>
<DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue>
<AxisNameID>256</AxisNameID>
</Axis>
</fvar>
<gvar>
<version value="1"/>
<reserved value="0"/>
<glyphVariations glyph="A">
<tuple>
<coord axis="wght" value="1.0"/>
<delta pt="0" x="499" y="0"/>
<delta pt="1" x="500" y="0"/>
<delta pt="2" x="500" y="0"/>
<delta pt="3" x="500" y="0"/>
<delta pt="4" x="0" y="0"/>
<delta pt="5" x="0" y="0"/>
<delta pt="6" x="0" y="0"/>
<delta pt="7" x="0" y="0"/>
</tuple>
</glyphVariations>
</gvar>
</ttFont>

View File

@ -0,0 +1,294 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.53">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="A"/>
</GlyphOrder>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="0.0"/>
<checkSumAdjustment value="0xbd1ef780"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Wed Sep 11 11:44:03 2024"/>
<modified value="Wed Sep 11 12:08:44 2024"/>
<xMin value="1"/>
<yMin value="-200"/>
<xMax value="1001"/>
<yMax value="1000"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="6"/>
<fontDirectionHint value="2"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="1000"/>
<descent value="-200"/>
<lineGap value="0"/>
<advanceWidthMax value="1000"/>
<minLeftSideBearing value="1"/>
<minRightSideBearing value="-1"/>
<xMaxExtent value="1001"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="2"/>
</hhea>
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
<numGlyphs value="2"/>
<maxPoints value="8"/>
<maxContours value="2"/>
<maxCompositePoints value="0"/>
<maxCompositeContours value="0"/>
<maxZones value="1"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
<maxComponentDepth value="0"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
<xAvgCharWidth value="750"/>
<usWeightClass value="401"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="600"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="75"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="600"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="350"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="300"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="NONE"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="65"/>
<usLastCharIndex value="65"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="200"/>
<usWinAscent value="1000"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="500"/>
<sCapHeight value="700"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="500" lsb="50"/>
<mtx name="A" width="1000" lsb="1"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
</cmap_format_4>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<!-- The xMin, yMin, xMax and yMax values
will be recalculated by the compiler. -->
<TTGlyph name=".notdef" xMin="50" yMin="-200" xMax="450" yMax="800">
<contour>
<pt x="50" y="-200" on="1"/>
<pt x="50" y="800" on="1"/>
<pt x="450" y="800" on="1"/>
<pt x="450" y="-200" on="1"/>
</contour>
<contour>
<pt x="100" y="-150" on="1"/>
<pt x="400" y="-150" on="1"/>
<pt x="400" y="750" on="1"/>
<pt x="100" y="750" on="1"/>
</contour>
<instructions/>
</TTGlyph>
<TTGlyph name="A" xMin="1" yMin="0" xMax="1001" yMax="1000">
<contour>
<pt x="1" y="1000" on="1"/>
<pt x="1001" y="1000" on="1"/>
<pt x="1001" y="0" on="1"/>
<pt x="1" y="0" on="1"/>
</contour>
<instructions/>
</TTGlyph>
</glyf>
<name>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Weight
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
New Font
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
0.000;NONE;NewFont-Regular
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
New Font Regular
</namerecord>
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
Version 0.000
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
NewFont-Regular
</namerecord>
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
Weight
</namerecord>
</name>
<post>
<formatType value="2.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
<psNames>
<!-- This file uses unique glyph names based on the information
found in the 'post' table. Since these names might not be unique,
we have to invent artificial names in case of clashes. In order to
be able to retain the original information, we need a name to
ps name mapping for those cases where they differ. That's what
you see below.
-->
</psNames>
<extraNames>
<!-- following are the name that are not taken from the standard Mac glyph order -->
</extraNames>
</post>
<HVAR>
<Version value="0x00010000"/>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=1 -->
<!-- RegionCount=0 -->
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=1 -->
<NumShorts value="0"/>
<!-- VarRegionCount=0 -->
<Item index="0" value="[]"/>
</VarData>
</VarStore>
<AdvWidthMap>
<Map glyph=".notdef" outer="0" inner="0"/>
<Map glyph="A" outer="0" inner="0"/>
</AdvWidthMap>
</HVAR>
<STAT>
<Version value="0x00010001"/>
<DesignAxisRecordSize value="8"/>
<!-- DesignAxisCount=1 -->
<DesignAxisRecord>
<Axis index="0">
<AxisTag value="wght"/>
<AxisNameID value="256"/> <!-- Weight -->
<AxisOrdering value="0"/>
</Axis>
</DesignAxisRecord>
<!-- AxisValueCount=0 -->
<ElidedFallbackNameID value="2"/> <!-- Regular -->
</STAT>
<fvar>
<!-- Weight -->
<Axis>
<AxisTag>wght</AxisTag>
<Flags>0x0</Flags>
<MinValue>401.0</MinValue>
<DefaultValue>401.0</DefaultValue>
<MaxValue>900.0</MaxValue>
<AxisNameID>256</AxisNameID>
</Axis>
</fvar>
<gvar>
<version value="1"/>
<reserved value="0"/>
<glyphVariations glyph="A">
<tuple>
<coord axis="wght" value="1.0"/>
<delta pt="0" x="498" y="0"/>
<delta pt="1" x="499" y="0"/>
<delta pt="2" x="499" y="0"/>
<delta pt="3" x="499" y="0"/>
<delta pt="4" x="0" y="0"/>
<delta pt="5" x="0" y="0"/>
<delta pt="6" x="0" y="0"/>
<delta pt="7" x="0" y="0"/>
</tuple>
</glyphVariations>
</gvar>
</ttFont>

View File

@ -2390,3 +2390,41 @@ def test_set_ribbi_bits():
assert name_id_2 == "Italic", location assert name_id_2 == "Italic", location
assert mac_style == 0b10, location assert mac_style == 0b10, location
assert fs_selection == 0b0000001, 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