Merge pull request #2789 from fonttools/varglyphset-refactor
[ttGlyphSet] Refactor/rebuild class hierarchy
This commit is contained in:
commit
55192edb0c
@ -4,12 +4,7 @@ from fontTools.misc.configTools import AbstractConfig
|
||||
from fontTools.misc.textTools import Tag, byteord, tostr
|
||||
from fontTools.misc.loggingTools import deprecateArgument
|
||||
from fontTools.ttLib import TTLibError
|
||||
from fontTools.ttLib.ttGlyphSet import (
|
||||
_TTGlyphSet, _TTGlyph,
|
||||
_TTGlyphCFF, _TTGlyphGlyf,
|
||||
_TTVarGlyphSet,
|
||||
_TTVarGlyphCFF, _TTVarGlyphGlyf,
|
||||
)
|
||||
from fontTools.ttLib.ttGlyphSet import _TTGlyph, _TTGlyphSetCFF, _TTGlyphSetGlyf
|
||||
from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
|
||||
from io import BytesIO, StringIO
|
||||
import os
|
||||
@ -684,44 +679,68 @@ class TTFont(object):
|
||||
def getGlyphSet(self, preferCFF=True, location=None, normalized=False):
|
||||
"""Return a generic GlyphSet, which is a dict-like object
|
||||
mapping glyph names to glyph objects. The returned glyph objects
|
||||
have a .draw() method that supports the Pen protocol, and will
|
||||
have a ``.draw()`` method that supports the Pen protocol, and will
|
||||
have an attribute named 'width'.
|
||||
|
||||
If the font is CFF-based, the outlines will be taken from the 'CFF ' or
|
||||
'CFF2' tables. Otherwise the outlines will be taken from the 'glyf' table.
|
||||
If the font contains both a 'CFF '/'CFF2' and a 'glyf' table, you can use
|
||||
the 'preferCFF' argument to specify which one should be taken. If the
|
||||
font contains both a 'CFF ' and a 'CFF2' table, the latter is taken.
|
||||
If the font is CFF-based, the outlines will be taken from the ``CFF ``
|
||||
or ``CFF2`` tables. Otherwise the outlines will be taken from the
|
||||
``glyf`` table.
|
||||
|
||||
If the 'location' parameter is set, it should be a dictionary mapping
|
||||
If the font contains both a ``CFF ``/``CFF2`` and a ``glyf`` table, you
|
||||
can use the ``preferCFF`` argument to specify which one should be taken.
|
||||
If the font contains both a ``CFF `` and a ``CFF2`` table, the latter is
|
||||
taken.
|
||||
|
||||
If the ``location`` parameter is set, it should be a dictionary mapping
|
||||
four-letter variation tags to their float values, and the returned
|
||||
glyph-set will represent an instance of a variable font at that location.
|
||||
If the 'normalized' variable is set to True, that location is interpretted
|
||||
as in the normalized (-1..+1) space, otherwise it is in the font's defined
|
||||
axes space.
|
||||
glyph-set will represent an instance of a variable font at that
|
||||
location.
|
||||
|
||||
If the ``normalized`` variable is set to True, that location is
|
||||
interpreted as in the normalized (-1..+1) space, otherwise it is in the
|
||||
font's defined axes space.
|
||||
"""
|
||||
glyphs = None
|
||||
if (preferCFF and any(tb in self for tb in ["CFF ", "CFF2"]) or
|
||||
("glyf" not in self and any(tb in self for tb in ["CFF ", "CFF2"]))):
|
||||
table_tag = "CFF2" if "CFF2" in self else "CFF "
|
||||
glyphs = list(self[table_tag].cff.values())[0].CharStrings
|
||||
if location and 'fvar' in self:
|
||||
glyphs = _TTVarGlyphSet(self, glyphs, _TTVarGlyphCFF,
|
||||
location, normalized)
|
||||
else:
|
||||
glyphs = _TTGlyphSet(self, glyphs, _TTGlyphCFF)
|
||||
|
||||
if glyphs is None and "glyf" in self:
|
||||
if location and 'gvar' in self:
|
||||
glyphs = _TTVarGlyphSet(self, self["glyf"], _TTVarGlyphGlyf,
|
||||
location, normalized)
|
||||
else:
|
||||
glyphs = _TTGlyphSet(self, self["glyf"], _TTGlyphGlyf)
|
||||
|
||||
if glyphs is None:
|
||||
if location and "fvar" not in self:
|
||||
location = None
|
||||
if location and not normalized:
|
||||
location = self.normalizeLocation(location)
|
||||
if ("CFF " in self or "CFF2" in self) and (preferCFF or "glyf" not in self):
|
||||
return _TTGlyphSetCFF(self, location)
|
||||
elif "glyf" in self:
|
||||
return _TTGlyphSetGlyf(self, location)
|
||||
else:
|
||||
raise TTLibError("Font contains no outlines")
|
||||
|
||||
return glyphs
|
||||
def normalizeLocation(self, location):
|
||||
"""Normalize a ``location`` from the font's defined axes space (also
|
||||
known as user space) into the normalized (-1..+1) space. It applies
|
||||
``avar`` mapping if the font contains an ``avar`` table.
|
||||
|
||||
The ``location`` parameter should be a dictionary mapping four-letter
|
||||
variation tags to their float values.
|
||||
|
||||
Raises ``TTLibError`` if the font is not a variable font.
|
||||
"""
|
||||
from fontTools.varLib.models import normalizeLocation, piecewiseLinearMap
|
||||
|
||||
if "fvar" not in self:
|
||||
raise TTLibError("Not a variable font")
|
||||
|
||||
axes = {
|
||||
a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in self["fvar"].axes
|
||||
}
|
||||
location = normalizeLocation(location, axes)
|
||||
if "avar" in self:
|
||||
avar = self["avar"]
|
||||
avarSegments = avar.segments
|
||||
mappedLocation = {}
|
||||
for axisTag, value in location.items():
|
||||
avarMapping = avarSegments.get(axisTag, None)
|
||||
if avarMapping is not None:
|
||||
value = piecewiseLinearMap(value, avarMapping)
|
||||
mappedLocation[axisTag] = value
|
||||
location = mappedLocation
|
||||
return location
|
||||
|
||||
def getBestCmap(self, cmapPreferences=((3, 10), (0, 6), (0, 4), (3, 1), (0, 3), (0, 2), (0, 1), (0, 0))):
|
||||
"""Returns the 'best' Unicode cmap dictionary available in the font
|
||||
|
@ -1,158 +1,201 @@
|
||||
"""GlyphSets returned by a TTFont."""
|
||||
|
||||
from fontTools.misc.fixedTools import otRound
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Mapping
|
||||
from copy import copy
|
||||
from fontTools.misc.fixedTools import otRound
|
||||
from fontTools.misc.loggingTools import deprecateFunction
|
||||
|
||||
|
||||
class _TTGlyphSet(object):
|
||||
class _TTGlyphSet(Mapping):
|
||||
|
||||
"""Generic dict-like GlyphSet class that pulls metrics from hmtx and
|
||||
glyph shape from TrueType or CFF.
|
||||
"""
|
||||
|
||||
def __init__(self, ttFont, glyphs, glyphType):
|
||||
"""Construct a new glyphset.
|
||||
def __init__(self, font, location, glyphsMapping):
|
||||
self.font = font
|
||||
self.location = location
|
||||
self.glyphsMapping = glyphsMapping
|
||||
self.hMetrics = font["hmtx"].metrics
|
||||
self.vMetrics = getattr(font.get("vmtx"), "metrics", None)
|
||||
if location:
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
|
||||
Args:
|
||||
font (TTFont): The font object (used to get metrics).
|
||||
glyphs (dict): A dictionary mapping glyph names to ``_TTGlyph`` objects.
|
||||
glyphType (class): Either ``_TTGlyphCFF`` or ``_TTGlyphGlyf``.
|
||||
"""
|
||||
self._glyphs = glyphs
|
||||
self._hmtx = ttFont["hmtx"]
|
||||
self._vmtx = ttFont["vmtx"] if "vmtx" in ttFont else None
|
||||
self._glyphType = glyphType
|
||||
self.hvarTable = getattr(font.get("HVAR"), "table", None)
|
||||
if self.hvarTable is not None:
|
||||
self.hvarInstancer = VarStoreInstancer(
|
||||
self.hvarTable.VarStore, font["fvar"].axes, location
|
||||
)
|
||||
# TODO VVAR, VORG
|
||||
|
||||
def keys(self):
|
||||
return list(self._glyphs.keys())
|
||||
def __contains__(self, glyphName):
|
||||
return glyphName in self.glyphsMapping
|
||||
|
||||
def has_key(self, glyphName):
|
||||
return glyphName in self._glyphs
|
||||
|
||||
__contains__ = has_key
|
||||
|
||||
def __getitem__(self, glyphName):
|
||||
horizontalMetrics = self._hmtx[glyphName]
|
||||
verticalMetrics = self._vmtx[glyphName] if self._vmtx else None
|
||||
return self._glyphType(
|
||||
self, self._glyphs[glyphName], horizontalMetrics, verticalMetrics
|
||||
)
|
||||
def __iter__(self):
|
||||
return iter(self.glyphsMapping.keys())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._glyphs)
|
||||
return len(self.glyphsMapping)
|
||||
|
||||
def get(self, glyphName, default=None):
|
||||
try:
|
||||
return self[glyphName]
|
||||
except KeyError:
|
||||
return default
|
||||
@deprecateFunction(
|
||||
"use 'glyphName in glyphSet' instead", category=DeprecationWarning
|
||||
)
|
||||
def has_key(self, glyphName):
|
||||
return glyphName in self.glyphsMapping
|
||||
|
||||
|
||||
class _TTGlyph(object):
|
||||
class _TTGlyphSetGlyf(_TTGlyphSet):
|
||||
def __init__(self, font, location):
|
||||
self.glyfTable = font["glyf"]
|
||||
super().__init__(font, location, self.glyfTable)
|
||||
if location:
|
||||
self.gvarTable = font.get("gvar")
|
||||
|
||||
"""Wrapper for a TrueType glyph that supports the Pen protocol, meaning
|
||||
that it has .draw() and .drawPoints() methods that take a pen object as
|
||||
their only argument. Additionally there are 'width' and 'lsb' attributes,
|
||||
read from the 'hmtx' table.
|
||||
def __getitem__(self, glyphName):
|
||||
return _TTGlyphGlyf(self, glyphName)
|
||||
|
||||
|
||||
class _TTGlyphSetCFF(_TTGlyphSet):
|
||||
def __init__(self, font, location):
|
||||
tableTag = "CFF2" if "CFF2" in font else "CFF "
|
||||
self.charStrings = list(font[tableTag].cff.values())[0].CharStrings
|
||||
super().__init__(font, location, self.charStrings)
|
||||
self.blender = None
|
||||
if location:
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
|
||||
varStore = getattr(self.charStrings, "varStore", None)
|
||||
if varStore is not None:
|
||||
instancer = VarStoreInstancer(
|
||||
varStore.otVarStore, font["fvar"].axes, location
|
||||
)
|
||||
self.blender = instancer.interpolateFromDeltas
|
||||
|
||||
def __getitem__(self, glyphName):
|
||||
return _TTGlyphCFF(self, glyphName)
|
||||
|
||||
|
||||
class _TTGlyph(ABC):
|
||||
|
||||
"""Glyph object that supports the Pen protocol, meaning that it has
|
||||
.draw() and .drawPoints() methods that take a pen object as their only
|
||||
argument. Additionally there are 'width' and 'lsb' attributes, read from
|
||||
the 'hmtx' table.
|
||||
|
||||
If the font contains a 'vmtx' table, there will also be 'height' and 'tsb'
|
||||
attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, glyphset, glyph, horizontalMetrics=None, verticalMetrics=None):
|
||||
"""Construct a new _TTGlyph.
|
||||
|
||||
Args:
|
||||
glyphset (_TTGlyphSet): A glyphset object used to resolve components.
|
||||
glyph (ttLib.tables._g_l_y_f.Glyph): The glyph object.
|
||||
horizontalMetrics (int, int): The glyph's width and left sidebearing.
|
||||
"""
|
||||
self._glyphset = glyphset
|
||||
self._glyph = glyph
|
||||
if horizontalMetrics:
|
||||
self.width, self.lsb = horizontalMetrics
|
||||
else:
|
||||
self.width, self.lsb = None, None
|
||||
if verticalMetrics:
|
||||
self.height, self.tsb = verticalMetrics
|
||||
def __init__(self, glyphSet, glyphName):
|
||||
self.glyphSet = glyphSet
|
||||
self.name = glyphName
|
||||
self.width, self.lsb = glyphSet.hMetrics[glyphName]
|
||||
if glyphSet.vMetrics is not None:
|
||||
self.height, self.tsb = glyphSet.vMetrics[glyphName]
|
||||
else:
|
||||
self.height, self.tsb = None, None
|
||||
if glyphSet.location and glyphSet.hvarTable is not None:
|
||||
varidx = (
|
||||
glyphSet.font.getGlyphID(glyphName)
|
||||
if glyphSet.hvarTable.AdvWidthMap is None
|
||||
else glyphSet.hvarTable.AdvWidthMap.mapping[glyphName]
|
||||
)
|
||||
self.width += glyphSet.hvarInstancer[varidx]
|
||||
# TODO: VVAR/VORG
|
||||
|
||||
@abstractmethod
|
||||
def draw(self, pen):
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
|
||||
how that works.
|
||||
"""
|
||||
self._glyph.draw(pen)
|
||||
raise NotImplementedError
|
||||
|
||||
def drawPoints(self, pen):
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details
|
||||
how that works.
|
||||
"""
|
||||
from fontTools.pens.pointPen import SegmentToPointPen
|
||||
|
||||
self.draw(SegmentToPointPen(pen))
|
||||
|
||||
|
||||
class _TTGlyphCFF(_TTGlyph):
|
||||
pass
|
||||
|
||||
|
||||
class _TTGlyphGlyf(_TTGlyph):
|
||||
def draw(self, pen):
|
||||
"""Draw the glyph onto Pen. See fontTools.pens.basePen for details
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
|
||||
how that works.
|
||||
"""
|
||||
glyfTable = self._glyphset._glyphs
|
||||
glyph = self._glyph
|
||||
offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
|
||||
glyph.draw(pen, glyfTable, offset)
|
||||
glyph, offset = self._getGlyphAndOffset()
|
||||
glyph.draw(pen, offset)
|
||||
|
||||
def drawPoints(self, pen):
|
||||
"""Draw the glyph onto PointPen. See fontTools.pens.pointPen
|
||||
for details how that works.
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details
|
||||
how that works.
|
||||
"""
|
||||
glyfTable = self._glyphset._glyphs
|
||||
glyph = self._glyph
|
||||
glyph, offset = self._getGlyphAndOffset()
|
||||
glyph.drawPoints(pen, offset)
|
||||
|
||||
def _getGlyphAndOffset(self):
|
||||
if self.glyphSet.location and self.glyphSet.gvarTable is not None:
|
||||
glyph = self._getGlyphInstance()
|
||||
else:
|
||||
glyph = self.glyphSet.glyfTable[self.name]
|
||||
|
||||
offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
|
||||
glyph.drawPoints(pen, glyfTable, offset)
|
||||
return glyph, offset
|
||||
|
||||
def _getGlyphInstance(self):
|
||||
from fontTools.varLib.iup import iup_delta
|
||||
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
||||
from fontTools.varLib.models import supportScalar
|
||||
|
||||
glyphSet = self.glyphSet
|
||||
glyfTable = glyphSet.glyfTable
|
||||
variations = glyphSet.gvarTable.variations[self.name]
|
||||
hMetrics = glyphSet.hMetrics
|
||||
vMetrics = glyphSet.vMetrics
|
||||
coordinates, _ = glyfTable._getCoordinatesAndControls(
|
||||
self.name, hMetrics, vMetrics
|
||||
)
|
||||
origCoords, endPts = None, None
|
||||
for var in variations:
|
||||
scalar = supportScalar(glyphSet.location, var.axes)
|
||||
if not scalar:
|
||||
continue
|
||||
delta = var.coordinates
|
||||
if None in delta:
|
||||
if origCoords is None:
|
||||
origCoords, control = glyfTable._getCoordinatesAndControls(
|
||||
self.name, hMetrics, vMetrics
|
||||
)
|
||||
endPts = (
|
||||
control[1] if control[0] >= 1 else list(range(len(control[1])))
|
||||
)
|
||||
delta = iup_delta(delta, origCoords, endPts)
|
||||
coordinates += GlyphCoordinates(delta) * scalar
|
||||
|
||||
glyph = copy(glyfTable[self.name]) # Shallow copy
|
||||
width, lsb, height, tsb = _setCoordinates(glyph, coordinates, glyfTable)
|
||||
if glyphSet.hvarTable is None:
|
||||
# no HVAR: let's set metrics from the phantom points
|
||||
self.width = width
|
||||
self.lsb = lsb
|
||||
self.height = height
|
||||
self.tsb = tsb
|
||||
return glyph
|
||||
|
||||
|
||||
class _TTVarGlyphSet(_TTGlyphSet):
|
||||
def __init__(self, font, glyphs, glyphType, location, normalized):
|
||||
self._ttFont = font
|
||||
self._glyphs = glyphs
|
||||
self._glyphType = glyphType
|
||||
|
||||
if not normalized:
|
||||
from fontTools.varLib.models import normalizeLocation, piecewiseLinearMap
|
||||
|
||||
axes = {
|
||||
a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
|
||||
for a in font["fvar"].axes
|
||||
}
|
||||
location = normalizeLocation(location, axes)
|
||||
if "avar" in font:
|
||||
avar = font["avar"]
|
||||
avarSegments = avar.segments
|
||||
new_location = {}
|
||||
for axis_tag, value in location.items():
|
||||
avarMapping = avarSegments.get(axis_tag, None)
|
||||
if avarMapping is not None:
|
||||
value = piecewiseLinearMap(value, avarMapping)
|
||||
new_location[axis_tag] = value
|
||||
location = new_location
|
||||
del new_location
|
||||
|
||||
self.location = location
|
||||
|
||||
def __getitem__(self, glyphName):
|
||||
if glyphName not in self._glyphs:
|
||||
raise KeyError(glyphName)
|
||||
return self._glyphType(self, glyphName, self.location)
|
||||
class _TTGlyphCFF(_TTGlyph):
|
||||
def draw(self, pen):
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
|
||||
how that works.
|
||||
"""
|
||||
self.glyphSet.charStrings[self.name].draw(pen, self.glyphSet.blender)
|
||||
|
||||
|
||||
def _setCoordinates(glyph, coord, glyfTable):
|
||||
# Handle phantom points for (left, right, top, bottom) positions.
|
||||
assert len(coord) >= 4
|
||||
if not hasattr(glyph, "xMin"):
|
||||
glyph.recalcBounds(glyfTable)
|
||||
leftSideX = coord[-4][0]
|
||||
rightSideX = coord[-3][0]
|
||||
topSideY = coord[-2][1]
|
||||
@ -163,7 +206,7 @@ def _setCoordinates(glyph, coord, glyfTable):
|
||||
|
||||
if glyph.isComposite():
|
||||
assert len(coord) == len(glyph.components)
|
||||
glyph.components = [copy(comp) for comp in glyph.components]
|
||||
glyph.components = [copy(comp) for comp in glyph.components] # Shallow copy
|
||||
for p, comp in zip(coord, glyph.components):
|
||||
if hasattr(comp, "x"):
|
||||
comp.x, comp.y = p
|
||||
@ -185,94 +228,3 @@ def _setCoordinates(glyph, coord, glyfTable):
|
||||
verticalAdvanceWidth,
|
||||
topSideBearing,
|
||||
)
|
||||
|
||||
|
||||
class _TTVarGlyph(_TTGlyph):
|
||||
def __init__(self, glyphSet, glyphName, location):
|
||||
|
||||
super().__init__(glyphSet._glyphs, glyphSet._glyphs[glyphName])
|
||||
|
||||
self._glyphSet = glyphSet
|
||||
self._ttFont = glyphSet._ttFont
|
||||
self._glyphs = glyphSet._glyphs
|
||||
self._glyphName = glyphName
|
||||
self._location = location
|
||||
|
||||
|
||||
class _TTVarGlyphCFF(_TTVarGlyph):
|
||||
def draw(self, pen):
|
||||
varStore = self._glyphs.varStore
|
||||
if varStore is None:
|
||||
blender = None
|
||||
else:
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
|
||||
vsInstancer = getattr(self._glyphSet, "vsInstancer", None)
|
||||
if vsInstancer is None:
|
||||
self._glyphSet.vsInstancer = vsInstancer = VarStoreInstancer(
|
||||
varStore.otVarStore, self._ttFont["fvar"].axes, self._location
|
||||
)
|
||||
blender = vsInstancer.interpolateFromDeltas
|
||||
self._glyph.draw(pen, blender)
|
||||
|
||||
self.width = self._ttFont["hmtx"][self._glyphName][0]
|
||||
if "HVAR" in self._ttFont:
|
||||
hvar = self._ttFont["HVAR"].table
|
||||
varidx = self._ttFont.getGlyphID(self._glyphName)
|
||||
if hvar.AdvWidthMap is not None:
|
||||
varidx = hvar.AdvWidthMap.mapping[self._glyphName]
|
||||
vsInstancer = VarStoreInstancer(
|
||||
hvar.VarStore, self._ttFont["fvar"].axes, self._location
|
||||
)
|
||||
delta = vsInstancer[varidx]
|
||||
self.width += delta
|
||||
|
||||
|
||||
class _TTVarGlyphGlyf(_TTVarGlyph):
|
||||
def draw(self, pen):
|
||||
self._drawWithPen(pen, isPointPen=False)
|
||||
|
||||
def drawPoints(self, pen):
|
||||
self._drawWithPen(pen, isPointPen=True)
|
||||
|
||||
def _drawWithPen(self, pen, isPointPen):
|
||||
from fontTools.varLib.iup import iup_delta
|
||||
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
|
||||
from fontTools.varLib.models import supportScalar
|
||||
|
||||
glyf = self._ttFont["glyf"]
|
||||
hMetrics = self._ttFont["hmtx"].metrics
|
||||
vMetrics = getattr(self._ttFont.get("vmtx"), "metrics", None)
|
||||
|
||||
variations = self._ttFont["gvar"].variations[self._glyphName]
|
||||
coordinates, _ = glyf._getCoordinatesAndControls(
|
||||
self._glyphName, hMetrics, vMetrics
|
||||
)
|
||||
origCoords, endPts = None, None
|
||||
for var in variations:
|
||||
scalar = supportScalar(self._location, var.axes)
|
||||
if not scalar:
|
||||
continue
|
||||
delta = var.coordinates
|
||||
if None in delta:
|
||||
if origCoords is None:
|
||||
origCoords, control = glyf._getCoordinatesAndControls(
|
||||
self._glyphName, hMetrics, vMetrics
|
||||
)
|
||||
endPts = (
|
||||
control[1] if control[0] >= 1 else list(range(len(control[1])))
|
||||
)
|
||||
delta = iup_delta(delta, origCoords, endPts)
|
||||
coordinates += GlyphCoordinates(delta) * scalar
|
||||
|
||||
glyph = copy(glyf[self._glyphName]) # Shallow copy
|
||||
width, lsb, height, tsb = _setCoordinates(glyph, coordinates, glyf)
|
||||
self.width = width
|
||||
self.lsb = lsb
|
||||
self.height = height
|
||||
self.tsb = tsb
|
||||
offset = lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
|
||||
if isPointPen:
|
||||
glyph.drawPoints(pen, glyf, offset)
|
||||
else:
|
||||
glyph.draw(pen, glyf, offset)
|
||||
|
@ -21,12 +21,11 @@ class TTGlyphPenTestBase:
|
||||
|
||||
glyphSet = font.getGlyphSet()
|
||||
glyfTable = font["glyf"]
|
||||
pen = self.penClass(font.getGlyphSet())
|
||||
pen = self.penClass(glyphSet)
|
||||
|
||||
for name in font.getGlyphOrder():
|
||||
oldGlyph = glyphSet[name]
|
||||
getattr(oldGlyph, self.drawMethod)(pen)
|
||||
oldGlyph = oldGlyph._glyph
|
||||
getattr(glyphSet[name], self.drawMethod)(pen)
|
||||
oldGlyph = glyfTable[name]
|
||||
newGlyph = pen.glyph()
|
||||
|
||||
if hasattr(oldGlyph, "program"):
|
||||
|
27
Tests/ttLib/data/TestTTF_normalizeLocation.ttx
Normal file
27
Tests/ttLib/data/TestTTF_normalizeLocation.ttx
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.36">
|
||||
|
||||
<avar>
|
||||
<segment axis="wght">
|
||||
<mapping from="-1.0" to="-1.0"/>
|
||||
<mapping from="-0.5" to="-0.75"/>
|
||||
<mapping from="0.0" to="0.0"/>
|
||||
<mapping from="0.5" to="0.75"/>
|
||||
<mapping from="1.0" to="1.0"/>
|
||||
</segment>
|
||||
</avar>
|
||||
|
||||
<fvar>
|
||||
|
||||
<Axis>
|
||||
<AxisTag>wght</AxisTag>
|
||||
<Flags>0x0</Flags>
|
||||
<MinValue>100.0</MinValue>
|
||||
<DefaultValue>400.0</DefaultValue>
|
||||
<MaxValue>700.0</MaxValue>
|
||||
<AxisNameID>256</AxisNameID>
|
||||
</Axis>
|
||||
|
||||
</fvar>
|
||||
|
||||
</ttFont>
|
@ -3,7 +3,7 @@ import os
|
||||
import re
|
||||
import random
|
||||
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
|
||||
from fontTools.ttLib import TTFont, newTable, registerCustomTableClass, unregisterCustomTableClass
|
||||
from fontTools.ttLib import TTFont, TTLibError, newTable, registerCustomTableClass, unregisterCustomTableClass
|
||||
from fontTools.ttLib.tables.DefaultTable import DefaultTable
|
||||
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
|
||||
import pytest
|
||||
@ -212,3 +212,36 @@ def test_ensureDecompiled(lazy):
|
||||
assert "Lookup" in font["GSUB"].table.LookupList.__dict__
|
||||
assert "reader" not in font["GPOS"].table.LookupList.__dict__
|
||||
assert "Lookup" in font["GPOS"].table.LookupList.__dict__
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def testFont_fvar_avar():
|
||||
ttxpath = os.path.join(DATA_DIR, "TestTTF_normalizeLocation.ttx")
|
||||
ttf = TTFont()
|
||||
ttf.importXML(ttxpath)
|
||||
return ttf
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"userLocation, expectedNormalizedLocation",
|
||||
[
|
||||
({}, {"wght": 0.0}),
|
||||
({"wght": 100}, {"wght": -1.0}),
|
||||
({"wght": 250}, {"wght": -0.75}),
|
||||
({"wght": 400}, {"wght": 0.0}),
|
||||
({"wght": 550}, {"wght": 0.75}),
|
||||
({"wght": 625}, {"wght": 0.875}),
|
||||
({"wght": 700}, {"wght": 1.0}),
|
||||
],
|
||||
)
|
||||
def test_font_normalizeLocation(
|
||||
testFont_fvar_avar, userLocation, expectedNormalizedLocation
|
||||
):
|
||||
normalizedLocation = testFont_fvar_avar.normalizeLocation(userLocation)
|
||||
assert expectedNormalizedLocation == normalizedLocation
|
||||
|
||||
|
||||
def test_font_normalizeLocation_no_VF():
|
||||
ttf = TTFont()
|
||||
with pytest.raises(TTLibError, match="Not a variable font"):
|
||||
ttf.normalizeLocation({})
|
||||
|
@ -119,8 +119,6 @@ class TTGlyphSetTest(object):
|
||||
glyphset = font.getGlyphSet(location=location)
|
||||
|
||||
assert isinstance(glyphset, ttGlyphSet._TTGlyphSet)
|
||||
if location:
|
||||
assert isinstance(glyphset, ttGlyphSet._TTVarGlyphSet)
|
||||
|
||||
assert list(glyphset.keys()) == [".notdef", "I"]
|
||||
|
||||
@ -136,12 +134,7 @@ class TTGlyphSetTest(object):
|
||||
|
||||
assert isinstance(glyph, ttGlyphSet._TTGlyph)
|
||||
is_glyf = fontfile.endswith(".ttf")
|
||||
if location:
|
||||
glyphType = (
|
||||
ttGlyphSet._TTVarGlyphGlyf if is_glyf else ttGlyphSet._TTVarGlyphCFF
|
||||
)
|
||||
else:
|
||||
glyphType = ttGlyphSet._TTGlyphGlyf if is_glyf else ttGlyphSet._TTGlyphCFF
|
||||
glyphType = ttGlyphSet._TTGlyphGlyf if is_glyf else ttGlyphSet._TTGlyphCFF
|
||||
assert isinstance(glyph, glyphType)
|
||||
|
||||
glyph.draw(pen)
|
||||
|
Loading…
x
Reference in New Issue
Block a user