remove venv
This commit is contained in:
parent
056387013d
commit
0680c7594e
13999 changed files with 0 additions and 2895688 deletions
|
|
@ -1,490 +0,0 @@
|
|||
"""GlyphSets returned by a TTFont."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Mapping
|
||||
from contextlib import contextmanager
|
||||
from copy import copy, deepcopy
|
||||
from types import SimpleNamespace
|
||||
from fontTools.misc.vector import Vector
|
||||
from fontTools.misc.fixedTools import otRound, fixedToFloat as fi2fl
|
||||
from fontTools.misc.loggingTools import deprecateFunction
|
||||
from fontTools.misc.transform import Transform, DecomposedTransform
|
||||
from fontTools.pens.transformPen import TransformPen, TransformPointPen
|
||||
from fontTools.pens.recordingPen import (
|
||||
DecomposingRecordingPen,
|
||||
lerpRecordings,
|
||||
replayRecording,
|
||||
)
|
||||
|
||||
|
||||
class _TTGlyphSet(Mapping):
|
||||
"""Generic dict-like GlyphSet class that pulls metrics from hmtx and
|
||||
glyph shape from TrueType or CFF.
|
||||
"""
|
||||
|
||||
def __init__(self, font, location, glyphsMapping, *, recalcBounds=True):
|
||||
self.recalcBounds = recalcBounds
|
||||
self.font = font
|
||||
self.defaultLocationNormalized = (
|
||||
{axis.axisTag: 0 for axis in self.font["fvar"].axes}
|
||||
if "fvar" in self.font
|
||||
else {}
|
||||
)
|
||||
self.location = location if location is not None else {}
|
||||
self.rawLocation = {} # VarComponent-only location
|
||||
self.originalLocation = location if location is not None else {}
|
||||
self.depth = 0
|
||||
self.locationStack = []
|
||||
self.rawLocationStack = []
|
||||
self.glyphsMapping = glyphsMapping
|
||||
self.hMetrics = font["hmtx"].metrics
|
||||
self.vMetrics = getattr(font.get("vmtx"), "metrics", None)
|
||||
self.hvarTable = None
|
||||
if location:
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
|
||||
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
|
||||
|
||||
@contextmanager
|
||||
def pushLocation(self, location, reset: bool):
|
||||
self.locationStack.append(self.location)
|
||||
self.rawLocationStack.append(self.rawLocation)
|
||||
if reset:
|
||||
self.location = self.originalLocation.copy()
|
||||
self.rawLocation = self.defaultLocationNormalized.copy()
|
||||
else:
|
||||
self.location = self.location.copy()
|
||||
self.rawLocation = {}
|
||||
self.location.update(location)
|
||||
self.rawLocation.update(location)
|
||||
|
||||
try:
|
||||
yield None
|
||||
finally:
|
||||
self.location = self.locationStack.pop()
|
||||
self.rawLocation = self.rawLocationStack.pop()
|
||||
|
||||
@contextmanager
|
||||
def pushDepth(self):
|
||||
try:
|
||||
depth = self.depth
|
||||
self.depth += 1
|
||||
yield depth
|
||||
finally:
|
||||
self.depth -= 1
|
||||
|
||||
def __contains__(self, glyphName):
|
||||
return glyphName in self.glyphsMapping
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.glyphsMapping.keys())
|
||||
|
||||
def __len__(self):
|
||||
return len(self.glyphsMapping)
|
||||
|
||||
@deprecateFunction(
|
||||
"use 'glyphName in glyphSet' instead", category=DeprecationWarning
|
||||
)
|
||||
def has_key(self, glyphName):
|
||||
return glyphName in self.glyphsMapping
|
||||
|
||||
|
||||
class _TTGlyphSetGlyf(_TTGlyphSet):
|
||||
def __init__(self, font, location, recalcBounds=True):
|
||||
self.glyfTable = font["glyf"]
|
||||
super().__init__(font, location, self.glyfTable, recalcBounds=recalcBounds)
|
||||
self.gvarTable = font.get("gvar")
|
||||
|
||||
def __getitem__(self, glyphName):
|
||||
return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds)
|
||||
|
||||
|
||||
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.setLocation(location)
|
||||
|
||||
def __getitem__(self, glyphName):
|
||||
return _TTGlyphCFF(self, glyphName)
|
||||
|
||||
def setLocation(self, location):
|
||||
self.blender = None
|
||||
if location:
|
||||
# TODO Optimize by using instancer.setLocation()
|
||||
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
|
||||
varStore = getattr(self.charStrings, "varStore", None)
|
||||
if varStore is not None:
|
||||
instancer = VarStoreInstancer(
|
||||
varStore.otVarStore, self.font["fvar"].axes, location
|
||||
)
|
||||
self.blender = instancer.interpolateFromDeltas
|
||||
else:
|
||||
self.blender = None
|
||||
|
||||
@contextmanager
|
||||
def pushLocation(self, location, reset: bool):
|
||||
self.setLocation(location)
|
||||
with _TTGlyphSet.pushLocation(self, location, reset) as value:
|
||||
try:
|
||||
yield value
|
||||
finally:
|
||||
self.setLocation(self.location)
|
||||
|
||||
|
||||
class _TTGlyphSetVARC(_TTGlyphSet):
|
||||
def __init__(self, font, location, glyphSet):
|
||||
self.glyphSet = glyphSet
|
||||
super().__init__(font, location, glyphSet)
|
||||
self.varcTable = font["VARC"].table
|
||||
|
||||
def __getitem__(self, glyphName):
|
||||
varc = self.varcTable
|
||||
if glyphName not in varc.Coverage.glyphs:
|
||||
return self.glyphSet[glyphName]
|
||||
return _TTGlyphVARC(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, glyphName, *, recalcBounds=True):
|
||||
self.glyphSet = glyphSet
|
||||
self.name = glyphName
|
||||
self.recalcBounds = recalcBounds
|
||||
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.
|
||||
"""
|
||||
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 _TTGlyphGlyf(_TTGlyph):
|
||||
def draw(self, pen):
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
|
||||
how that works.
|
||||
"""
|
||||
glyph, offset = self._getGlyphAndOffset()
|
||||
|
||||
with self.glyphSet.pushDepth() as depth:
|
||||
if depth:
|
||||
offset = 0 # Offset should only apply at top-level
|
||||
|
||||
glyph.draw(pen, self.glyphSet.glyfTable, offset)
|
||||
|
||||
def drawPoints(self, pen):
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details
|
||||
how that works.
|
||||
"""
|
||||
glyph, offset = self._getGlyphAndOffset()
|
||||
|
||||
with self.glyphSet.pushDepth() as depth:
|
||||
if depth:
|
||||
offset = 0 # Offset should only apply at top-level
|
||||
|
||||
glyph.drawPoints(pen, self.glyphSet.glyfTable, 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
|
||||
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, recalcBounds=self.recalcBounds
|
||||
)
|
||||
self.lsb = lsb
|
||||
self.tsb = tsb
|
||||
if glyphSet.hvarTable is None:
|
||||
# no HVAR: let's set metrics from the phantom points
|
||||
self.width = width
|
||||
self.height = height
|
||||
return glyph
|
||||
|
||||
|
||||
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 _evaluateCondition(condition, fvarAxes, location, instancer):
|
||||
if condition.Format == 1:
|
||||
# ConditionAxisRange
|
||||
axisIndex = condition.AxisIndex
|
||||
axisTag = fvarAxes[axisIndex].axisTag
|
||||
axisValue = location.get(axisTag, 0)
|
||||
minValue = condition.FilterRangeMinValue
|
||||
maxValue = condition.FilterRangeMaxValue
|
||||
return minValue <= axisValue <= maxValue
|
||||
elif condition.Format == 2:
|
||||
# ConditionValue
|
||||
value = condition.DefaultValue
|
||||
value += instancer[condition.VarIdx][0]
|
||||
return value > 0
|
||||
elif condition.Format == 3:
|
||||
# ConditionAnd
|
||||
for subcondition in condition.ConditionTable:
|
||||
if not _evaluateCondition(subcondition, fvarAxes, location, instancer):
|
||||
return False
|
||||
return True
|
||||
elif condition.Format == 4:
|
||||
# ConditionOr
|
||||
for subcondition in condition.ConditionTable:
|
||||
if _evaluateCondition(subcondition, fvarAxes, location, instancer):
|
||||
return True
|
||||
return False
|
||||
elif condition.Format == 5:
|
||||
# ConditionNegate
|
||||
return not _evaluateCondition(
|
||||
condition.conditionTable, fvarAxes, location, instancer
|
||||
)
|
||||
else:
|
||||
return False # Unkonwn condition format
|
||||
|
||||
|
||||
class _TTGlyphVARC(_TTGlyph):
|
||||
def _draw(self, pen, isPointPen):
|
||||
"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
|
||||
how that works.
|
||||
"""
|
||||
from fontTools.ttLib.tables.otTables import (
|
||||
VarComponentFlags,
|
||||
NO_VARIATION_INDEX,
|
||||
)
|
||||
|
||||
glyphSet = self.glyphSet
|
||||
varc = glyphSet.varcTable
|
||||
idx = varc.Coverage.glyphs.index(self.name)
|
||||
glyph = varc.VarCompositeGlyphs.VarCompositeGlyph[idx]
|
||||
|
||||
from fontTools.varLib.multiVarStore import MultiVarStoreInstancer
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
|
||||
fvarAxes = glyphSet.font["fvar"].axes
|
||||
instancer = MultiVarStoreInstancer(
|
||||
varc.MultiVarStore, fvarAxes, self.glyphSet.location
|
||||
)
|
||||
|
||||
for comp in glyph.components:
|
||||
if comp.flags & VarComponentFlags.HAVE_CONDITION:
|
||||
condition = varc.ConditionList.ConditionTable[comp.conditionIndex]
|
||||
if not _evaluateCondition(
|
||||
condition, fvarAxes, self.glyphSet.location, instancer
|
||||
):
|
||||
continue
|
||||
|
||||
location = {}
|
||||
if comp.axisIndicesIndex is not None:
|
||||
axisIndices = varc.AxisIndicesList.Item[comp.axisIndicesIndex]
|
||||
axisValues = Vector(comp.axisValues)
|
||||
if comp.axisValuesVarIndex != NO_VARIATION_INDEX:
|
||||
axisValues += fi2fl(instancer[comp.axisValuesVarIndex], 14)
|
||||
assert len(axisIndices) == len(axisValues), (
|
||||
len(axisIndices),
|
||||
len(axisValues),
|
||||
)
|
||||
location = {
|
||||
fvarAxes[i].axisTag: v for i, v in zip(axisIndices, axisValues)
|
||||
}
|
||||
|
||||
if comp.transformVarIndex != NO_VARIATION_INDEX:
|
||||
deltas = instancer[comp.transformVarIndex]
|
||||
comp = deepcopy(comp)
|
||||
comp.applyTransformDeltas(deltas)
|
||||
transform = comp.transform
|
||||
|
||||
reset = comp.flags & VarComponentFlags.RESET_UNSPECIFIED_AXES
|
||||
with self.glyphSet.glyphSet.pushLocation(location, reset):
|
||||
with self.glyphSet.pushLocation(location, reset):
|
||||
shouldDecompose = self.name == comp.glyphName
|
||||
|
||||
if not shouldDecompose:
|
||||
try:
|
||||
pen.addVarComponent(
|
||||
comp.glyphName, transform, self.glyphSet.rawLocation
|
||||
)
|
||||
except AttributeError:
|
||||
shouldDecompose = True
|
||||
|
||||
if shouldDecompose:
|
||||
t = transform.toTransform()
|
||||
compGlyphSet = (
|
||||
self.glyphSet
|
||||
if comp.glyphName != self.name
|
||||
else glyphSet.glyphSet
|
||||
)
|
||||
g = compGlyphSet[comp.glyphName]
|
||||
if isPointPen:
|
||||
tPen = TransformPointPen(pen, t)
|
||||
g.drawPoints(tPen)
|
||||
else:
|
||||
tPen = TransformPen(pen, t)
|
||||
g.draw(tPen)
|
||||
|
||||
def draw(self, pen):
|
||||
self._draw(pen, False)
|
||||
|
||||
def drawPoints(self, pen):
|
||||
self._draw(pen, True)
|
||||
|
||||
|
||||
def _setCoordinates(glyph, coord, glyfTable, *, recalcBounds=True):
|
||||
# Handle phantom points for (left, right, top, bottom) positions.
|
||||
assert len(coord) >= 4
|
||||
leftSideX = coord[-4][0]
|
||||
rightSideX = coord[-3][0]
|
||||
topSideY = coord[-2][1]
|
||||
bottomSideY = coord[-1][1]
|
||||
|
||||
for _ in range(4):
|
||||
del coord[-1]
|
||||
|
||||
if glyph.isComposite():
|
||||
assert len(coord) == len(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
|
||||
elif glyph.numberOfContours == 0:
|
||||
assert len(coord) == 0
|
||||
else:
|
||||
assert len(coord) == len(glyph.coordinates)
|
||||
glyph.coordinates = coord
|
||||
|
||||
if recalcBounds:
|
||||
glyph.recalcBounds(glyfTable)
|
||||
|
||||
horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
|
||||
verticalAdvanceWidth = otRound(topSideY - bottomSideY)
|
||||
leftSideBearing = otRound(glyph.xMin - leftSideX)
|
||||
topSideBearing = otRound(topSideY - glyph.yMax)
|
||||
return (
|
||||
horizontalAdvanceWidth,
|
||||
leftSideBearing,
|
||||
verticalAdvanceWidth,
|
||||
topSideBearing,
|
||||
)
|
||||
|
||||
|
||||
class LerpGlyphSet(Mapping):
|
||||
"""A glyphset that interpolates between two other glyphsets.
|
||||
|
||||
Factor is typically between 0 and 1. 0 means the first glyphset,
|
||||
1 means the second glyphset, and 0.5 means the average of the
|
||||
two glyphsets. Other values are possible, and can be useful to
|
||||
extrapolate. Defaults to 0.5.
|
||||
"""
|
||||
|
||||
def __init__(self, glyphset1, glyphset2, factor=0.5):
|
||||
self.glyphset1 = glyphset1
|
||||
self.glyphset2 = glyphset2
|
||||
self.factor = factor
|
||||
|
||||
def __getitem__(self, glyphname):
|
||||
if glyphname in self.glyphset1 and glyphname in self.glyphset2:
|
||||
return LerpGlyph(glyphname, self)
|
||||
raise KeyError(glyphname)
|
||||
|
||||
def __contains__(self, glyphname):
|
||||
return glyphname in self.glyphset1 and glyphname in self.glyphset2
|
||||
|
||||
def __iter__(self):
|
||||
set1 = set(self.glyphset1)
|
||||
set2 = set(self.glyphset2)
|
||||
return iter(set1.intersection(set2))
|
||||
|
||||
def __len__(self):
|
||||
set1 = set(self.glyphset1)
|
||||
set2 = set(self.glyphset2)
|
||||
return len(set1.intersection(set2))
|
||||
|
||||
|
||||
class LerpGlyph:
|
||||
def __init__(self, glyphname, glyphset):
|
||||
self.glyphset = glyphset
|
||||
self.glyphname = glyphname
|
||||
|
||||
def draw(self, pen):
|
||||
recording1 = DecomposingRecordingPen(self.glyphset.glyphset1)
|
||||
self.glyphset.glyphset1[self.glyphname].draw(recording1)
|
||||
recording2 = DecomposingRecordingPen(self.glyphset.glyphset2)
|
||||
self.glyphset.glyphset2[self.glyphname].draw(recording2)
|
||||
|
||||
factor = self.glyphset.factor
|
||||
|
||||
replayRecording(lerpRecordings(recording1.value, recording2.value, factor), pen)
|
||||
Loading…
Add table
Add a link
Reference in a new issue