some new features
This commit is contained in:
@ -0,0 +1,253 @@
|
||||
from fontTools.misc.roundTools import noRound, otRound
|
||||
from fontTools.misc.intTools import bit_count
|
||||
from fontTools.misc.vector import Vector
|
||||
from fontTools.ttLib.tables import otTables as ot
|
||||
from fontTools.varLib.models import supportScalar
|
||||
import fontTools.varLib.varStore # For monkey-patching
|
||||
from fontTools.varLib.builder import (
|
||||
buildVarRegionList,
|
||||
buildSparseVarRegionList,
|
||||
buildSparseVarRegion,
|
||||
buildMultiVarStore,
|
||||
buildMultiVarData,
|
||||
)
|
||||
from fontTools.misc.iterTools import batched
|
||||
from functools import partial
|
||||
from collections import defaultdict
|
||||
from heapq import heappush, heappop
|
||||
|
||||
|
||||
NO_VARIATION_INDEX = ot.NO_VARIATION_INDEX
|
||||
ot.MultiVarStore.NO_VARIATION_INDEX = NO_VARIATION_INDEX
|
||||
|
||||
|
||||
def _getLocationKey(loc):
|
||||
return tuple(sorted(loc.items(), key=lambda kv: kv[0]))
|
||||
|
||||
|
||||
class OnlineMultiVarStoreBuilder(object):
|
||||
def __init__(self, axisTags):
|
||||
self._axisTags = axisTags
|
||||
self._regionMap = {}
|
||||
self._regionList = buildSparseVarRegionList([], axisTags)
|
||||
self._store = buildMultiVarStore(self._regionList, [])
|
||||
self._data = None
|
||||
self._model = None
|
||||
self._supports = None
|
||||
self._varDataIndices = {}
|
||||
self._varDataCaches = {}
|
||||
self._cache = None
|
||||
|
||||
def setModel(self, model):
|
||||
self.setSupports(model.supports)
|
||||
self._model = model
|
||||
|
||||
def setSupports(self, supports):
|
||||
self._model = None
|
||||
self._supports = list(supports)
|
||||
if not self._supports[0]:
|
||||
del self._supports[0] # Drop base master support
|
||||
self._cache = None
|
||||
self._data = None
|
||||
|
||||
def finish(self):
|
||||
self._regionList.RegionCount = len(self._regionList.Region)
|
||||
self._store.MultiVarDataCount = len(self._store.MultiVarData)
|
||||
return self._store
|
||||
|
||||
def _add_MultiVarData(self):
|
||||
regionMap = self._regionMap
|
||||
regionList = self._regionList
|
||||
|
||||
regions = self._supports
|
||||
regionIndices = []
|
||||
for region in regions:
|
||||
key = _getLocationKey(region)
|
||||
idx = regionMap.get(key)
|
||||
if idx is None:
|
||||
varRegion = buildSparseVarRegion(region, self._axisTags)
|
||||
idx = regionMap[key] = len(regionList.Region)
|
||||
regionList.Region.append(varRegion)
|
||||
regionIndices.append(idx)
|
||||
|
||||
# Check if we have one already...
|
||||
key = tuple(regionIndices)
|
||||
varDataIdx = self._varDataIndices.get(key)
|
||||
if varDataIdx is not None:
|
||||
self._outer = varDataIdx
|
||||
self._data = self._store.MultiVarData[varDataIdx]
|
||||
self._cache = self._varDataCaches[key]
|
||||
if len(self._data.Item) == 0xFFFF:
|
||||
# This is full. Need new one.
|
||||
varDataIdx = None
|
||||
|
||||
if varDataIdx is None:
|
||||
self._data = buildMultiVarData(regionIndices, [])
|
||||
self._outer = len(self._store.MultiVarData)
|
||||
self._store.MultiVarData.append(self._data)
|
||||
self._varDataIndices[key] = self._outer
|
||||
if key not in self._varDataCaches:
|
||||
self._varDataCaches[key] = {}
|
||||
self._cache = self._varDataCaches[key]
|
||||
|
||||
def storeMasters(self, master_values, *, round=round):
|
||||
deltas = self._model.getDeltas(master_values, round=round)
|
||||
base = deltas.pop(0)
|
||||
return base, self.storeDeltas(deltas, round=noRound)
|
||||
|
||||
def storeDeltas(self, deltas, *, round=round):
|
||||
deltas = tuple(round(d) for d in deltas)
|
||||
|
||||
if not any(deltas):
|
||||
return NO_VARIATION_INDEX
|
||||
|
||||
deltas_tuple = tuple(tuple(d) for d in deltas)
|
||||
|
||||
if not self._data:
|
||||
self._add_MultiVarData()
|
||||
|
||||
varIdx = self._cache.get(deltas_tuple)
|
||||
if varIdx is not None:
|
||||
return varIdx
|
||||
|
||||
inner = len(self._data.Item)
|
||||
if inner == 0xFFFF:
|
||||
# Full array. Start new one.
|
||||
self._add_MultiVarData()
|
||||
return self.storeDeltas(deltas, round=noRound)
|
||||
self._data.addItem(deltas, round=noRound)
|
||||
|
||||
varIdx = (self._outer << 16) + inner
|
||||
self._cache[deltas_tuple] = varIdx
|
||||
return varIdx
|
||||
|
||||
|
||||
def MultiVarData_addItem(self, deltas, *, round=round):
|
||||
deltas = tuple(round(d) for d in deltas)
|
||||
|
||||
assert len(deltas) == self.VarRegionCount
|
||||
|
||||
values = []
|
||||
for d in deltas:
|
||||
values.extend(d)
|
||||
|
||||
self.Item.append(values)
|
||||
self.ItemCount = len(self.Item)
|
||||
|
||||
|
||||
ot.MultiVarData.addItem = MultiVarData_addItem
|
||||
|
||||
|
||||
def SparseVarRegion_get_support(self, fvar_axes):
|
||||
return {
|
||||
fvar_axes[reg.AxisIndex].axisTag: (reg.StartCoord, reg.PeakCoord, reg.EndCoord)
|
||||
for reg in self.SparseVarRegionAxis
|
||||
}
|
||||
|
||||
|
||||
ot.SparseVarRegion.get_support = SparseVarRegion_get_support
|
||||
|
||||
|
||||
def MultiVarStore___bool__(self):
|
||||
return bool(self.MultiVarData)
|
||||
|
||||
|
||||
ot.MultiVarStore.__bool__ = MultiVarStore___bool__
|
||||
|
||||
|
||||
class MultiVarStoreInstancer(object):
|
||||
def __init__(self, multivarstore, fvar_axes, location={}):
|
||||
self.fvar_axes = fvar_axes
|
||||
assert multivarstore is None or multivarstore.Format == 1
|
||||
self._varData = multivarstore.MultiVarData if multivarstore else []
|
||||
self._regions = (
|
||||
multivarstore.SparseVarRegionList.Region if multivarstore else []
|
||||
)
|
||||
self.setLocation(location)
|
||||
|
||||
def setLocation(self, location):
|
||||
self.location = dict(location)
|
||||
self._clearCaches()
|
||||
|
||||
def _clearCaches(self):
|
||||
self._scalars = {}
|
||||
|
||||
def _getScalar(self, regionIdx):
|
||||
scalar = self._scalars.get(regionIdx)
|
||||
if scalar is None:
|
||||
support = self._regions[regionIdx].get_support(self.fvar_axes)
|
||||
scalar = supportScalar(self.location, support)
|
||||
self._scalars[regionIdx] = scalar
|
||||
return scalar
|
||||
|
||||
@staticmethod
|
||||
def interpolateFromDeltasAndScalars(deltas, scalars):
|
||||
if not deltas:
|
||||
return Vector([])
|
||||
assert len(deltas) % len(scalars) == 0, (len(deltas), len(scalars))
|
||||
m = len(deltas) // len(scalars)
|
||||
delta = Vector([0] * m)
|
||||
for d, s in zip(batched(deltas, m), scalars):
|
||||
if not s:
|
||||
continue
|
||||
delta += Vector(d) * s
|
||||
return delta
|
||||
|
||||
def __getitem__(self, varidx):
|
||||
major, minor = varidx >> 16, varidx & 0xFFFF
|
||||
if varidx == NO_VARIATION_INDEX:
|
||||
return Vector([])
|
||||
varData = self._varData
|
||||
scalars = [self._getScalar(ri) for ri in varData[major].VarRegionIndex]
|
||||
deltas = varData[major].Item[minor]
|
||||
return self.interpolateFromDeltasAndScalars(deltas, scalars)
|
||||
|
||||
def interpolateFromDeltas(self, varDataIndex, deltas):
|
||||
varData = self._varData
|
||||
scalars = [self._getScalar(ri) for ri in varData[varDataIndex].VarRegionIndex]
|
||||
return self.interpolateFromDeltasAndScalars(deltas, scalars)
|
||||
|
||||
|
||||
def MultiVarStore_subset_varidxes(self, varIdxes):
|
||||
return ot.VarStore.subset_varidxes(self, varIdxes, VarData="MultiVarData")
|
||||
|
||||
|
||||
def MultiVarStore_prune_regions(self):
|
||||
return ot.VarStore.prune_regions(
|
||||
self, VarData="MultiVarData", VarRegionList="SparseVarRegionList"
|
||||
)
|
||||
|
||||
|
||||
ot.MultiVarStore.prune_regions = MultiVarStore_prune_regions
|
||||
ot.MultiVarStore.subset_varidxes = MultiVarStore_subset_varidxes
|
||||
|
||||
|
||||
def MultiVarStore_get_supports(self, major, fvarAxes):
|
||||
supports = []
|
||||
varData = self.MultiVarData[major]
|
||||
for regionIdx in varData.VarRegionIndex:
|
||||
region = self.SparseVarRegionList.Region[regionIdx]
|
||||
support = region.get_support(fvarAxes)
|
||||
supports.append(support)
|
||||
return supports
|
||||
|
||||
|
||||
ot.MultiVarStore.get_supports = MultiVarStore_get_supports
|
||||
|
||||
|
||||
def VARC_collect_varidxes(self, varidxes):
|
||||
for glyph in self.VarCompositeGlyphs.VarCompositeGlyph:
|
||||
for component in glyph.components:
|
||||
varidxes.add(component.axisValuesVarIndex)
|
||||
varidxes.add(component.transformVarIndex)
|
||||
|
||||
|
||||
def VARC_remap_varidxes(self, varidxes_map):
|
||||
for glyph in self.VarCompositeGlyphs.VarCompositeGlyph:
|
||||
for component in glyph.components:
|
||||
component.axisValuesVarIndex = varidxes_map[component.axisValuesVarIndex]
|
||||
component.transformVarIndex = varidxes_map[component.transformVarIndex]
|
||||
|
||||
|
||||
ot.VARC.collect_varidxes = VARC_collect_varidxes
|
||||
ot.VARC.remap_varidxes = VARC_remap_varidxes
|
||||
Reference in New Issue
Block a user