|
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 |
|
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] |
|
self._cache = None |
|
self._data = None |
|
|
|
def finish(self, optimize=True): |
|
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) |
|
|
|
|
|
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: |
|
|
|
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: |
|
|
|
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 |
|
|