up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
631
venv/lib/python3.13/site-packages/fontTools/varLib/cff.py
Normal file
631
venv/lib/python3.13/site-packages/fontTools/varLib/cff.py
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
from collections import namedtuple
|
||||
from fontTools.cffLib import (
|
||||
maxStackLimit,
|
||||
TopDictIndex,
|
||||
buildOrder,
|
||||
topDictOperators,
|
||||
topDictOperators2,
|
||||
privateDictOperators,
|
||||
privateDictOperators2,
|
||||
FDArrayIndex,
|
||||
FontDict,
|
||||
VarStoreData,
|
||||
)
|
||||
from io import BytesIO
|
||||
from fontTools.cffLib.specializer import specializeCommands, commandsToProgram
|
||||
from fontTools.ttLib import newTable
|
||||
from fontTools import varLib
|
||||
from fontTools.varLib.models import allEqual
|
||||
from fontTools.misc.loggingTools import deprecateFunction
|
||||
from fontTools.misc.roundTools import roundFunc
|
||||
from fontTools.misc.psCharStrings import T2CharString, T2OutlineExtractor
|
||||
from fontTools.pens.t2CharStringPen import T2CharStringPen
|
||||
from functools import partial
|
||||
|
||||
from .errors import (
|
||||
VarLibCFFDictMergeError,
|
||||
VarLibCFFPointTypeMergeError,
|
||||
VarLibCFFHintTypeMergeError,
|
||||
VarLibMergeError,
|
||||
)
|
||||
|
||||
|
||||
# Backwards compatibility
|
||||
MergeDictError = VarLibCFFDictMergeError
|
||||
MergeTypeError = VarLibCFFPointTypeMergeError
|
||||
|
||||
|
||||
def addCFFVarStore(varFont, varModel, varDataList, masterSupports):
|
||||
fvarTable = varFont["fvar"]
|
||||
axisKeys = [axis.axisTag for axis in fvarTable.axes]
|
||||
varTupleList = varLib.builder.buildVarRegionList(masterSupports, axisKeys)
|
||||
varStoreCFFV = varLib.builder.buildVarStore(varTupleList, varDataList)
|
||||
|
||||
topDict = varFont["CFF2"].cff.topDictIndex[0]
|
||||
topDict.VarStore = VarStoreData(otVarStore=varStoreCFFV)
|
||||
if topDict.FDArray[0].vstore is None:
|
||||
fdArray = topDict.FDArray
|
||||
for fontDict in fdArray:
|
||||
if hasattr(fontDict, "Private"):
|
||||
fontDict.Private.vstore = topDict.VarStore
|
||||
|
||||
|
||||
@deprecateFunction("Use fontTools.cffLib.CFFToCFF2.convertCFFToCFF2 instead.")
|
||||
def convertCFFtoCFF2(varFont):
|
||||
from fontTools.cffLib.CFFToCFF2 import convertCFFToCFF2
|
||||
|
||||
return convertCFFToCFF2(varFont)
|
||||
|
||||
|
||||
def conv_to_int(num):
|
||||
if isinstance(num, float) and num.is_integer():
|
||||
return int(num)
|
||||
return num
|
||||
|
||||
|
||||
pd_blend_fields = (
|
||||
"BlueValues",
|
||||
"OtherBlues",
|
||||
"FamilyBlues",
|
||||
"FamilyOtherBlues",
|
||||
"BlueScale",
|
||||
"BlueShift",
|
||||
"BlueFuzz",
|
||||
"StdHW",
|
||||
"StdVW",
|
||||
"StemSnapH",
|
||||
"StemSnapV",
|
||||
)
|
||||
|
||||
|
||||
def get_private(regionFDArrays, fd_index, ri, fd_map):
|
||||
region_fdArray = regionFDArrays[ri]
|
||||
region_fd_map = fd_map[fd_index]
|
||||
if ri in region_fd_map:
|
||||
region_fdIndex = region_fd_map[ri]
|
||||
private = region_fdArray[region_fdIndex].Private
|
||||
else:
|
||||
private = None
|
||||
return private
|
||||
|
||||
|
||||
def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map):
|
||||
"""
|
||||
I step through the FontDicts in the FDArray of the varfont TopDict.
|
||||
For each varfont FontDict:
|
||||
|
||||
* step through each key in FontDict.Private.
|
||||
* For each key, step through each relevant source font Private dict, and
|
||||
build a list of values to blend.
|
||||
|
||||
The 'relevant' source fonts are selected by first getting the right
|
||||
submodel using ``vsindex_dict[vsindex]``. The indices of the
|
||||
``subModel.locations`` are mapped to source font list indices by
|
||||
assuming the latter order is the same as the order of the
|
||||
``var_model.locations``. I can then get the index of each subModel
|
||||
location in the list of ``var_model.locations``.
|
||||
"""
|
||||
|
||||
topDict = top_dicts[0]
|
||||
region_top_dicts = top_dicts[1:]
|
||||
if hasattr(region_top_dicts[0], "FDArray"):
|
||||
regionFDArrays = [fdTopDict.FDArray for fdTopDict in region_top_dicts]
|
||||
else:
|
||||
regionFDArrays = [[fdTopDict] for fdTopDict in region_top_dicts]
|
||||
for fd_index, font_dict in enumerate(topDict.FDArray):
|
||||
private_dict = font_dict.Private
|
||||
vsindex = getattr(private_dict, "vsindex", 0)
|
||||
# At the moment, no PrivateDict has a vsindex key, but let's support
|
||||
# how it should work. See comment at end of
|
||||
# merge_charstrings() - still need to optimize use of vsindex.
|
||||
sub_model, _ = vsindex_dict[vsindex]
|
||||
master_indices = []
|
||||
for loc in sub_model.locations[1:]:
|
||||
i = var_model.locations.index(loc) - 1
|
||||
master_indices.append(i)
|
||||
pds = [private_dict]
|
||||
last_pd = private_dict
|
||||
for ri in master_indices:
|
||||
pd = get_private(regionFDArrays, fd_index, ri, fd_map)
|
||||
# If the region font doesn't have this FontDict, just reference
|
||||
# the last one used.
|
||||
if pd is None:
|
||||
pd = last_pd
|
||||
else:
|
||||
last_pd = pd
|
||||
pds.append(pd)
|
||||
num_masters = len(pds)
|
||||
for key, value in private_dict.rawDict.items():
|
||||
dataList = []
|
||||
if key not in pd_blend_fields:
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
try:
|
||||
values = [pd.rawDict[key] for pd in pds]
|
||||
except KeyError:
|
||||
print(
|
||||
"Warning: {key} in default font Private dict is "
|
||||
"missing from another font, and was "
|
||||
"discarded.".format(key=key)
|
||||
)
|
||||
continue
|
||||
try:
|
||||
values = zip(*values)
|
||||
except IndexError:
|
||||
raise VarLibCFFDictMergeError(key, value, values)
|
||||
"""
|
||||
Row 0 contains the first value from each master.
|
||||
Convert each row from absolute values to relative
|
||||
values from the previous row.
|
||||
e.g for three masters, a list of values was:
|
||||
master 0 OtherBlues = [-217,-205]
|
||||
master 1 OtherBlues = [-234,-222]
|
||||
master 1 OtherBlues = [-188,-176]
|
||||
The call to zip() converts this to:
|
||||
[(-217, -234, -188), (-205, -222, -176)]
|
||||
and is converted finally to:
|
||||
OtherBlues = [[-217, 17.0, 46.0], [-205, 0.0, 0.0]]
|
||||
"""
|
||||
prev_val_list = [0] * num_masters
|
||||
any_points_differ = False
|
||||
for val_list in values:
|
||||
rel_list = [
|
||||
(val - prev_val_list[i]) for (i, val) in enumerate(val_list)
|
||||
]
|
||||
if (not any_points_differ) and not allEqual(rel_list):
|
||||
any_points_differ = True
|
||||
prev_val_list = val_list
|
||||
deltas = sub_model.getDeltas(rel_list)
|
||||
# For PrivateDict BlueValues, the default font
|
||||
# values are absolute, not relative to the prior value.
|
||||
deltas[0] = val_list[0]
|
||||
dataList.append(deltas)
|
||||
# If there are no blend values,then
|
||||
# we can collapse the blend lists.
|
||||
if not any_points_differ:
|
||||
dataList = [data[0] for data in dataList]
|
||||
else:
|
||||
values = [pd.rawDict[key] for pd in pds]
|
||||
if not allEqual(values):
|
||||
dataList = sub_model.getDeltas(values)
|
||||
else:
|
||||
dataList = values[0]
|
||||
|
||||
# Convert numbers with no decimal part to an int
|
||||
if isinstance(dataList, list):
|
||||
for i, item in enumerate(dataList):
|
||||
if isinstance(item, list):
|
||||
for j, jtem in enumerate(item):
|
||||
dataList[i][j] = conv_to_int(jtem)
|
||||
else:
|
||||
dataList[i] = conv_to_int(item)
|
||||
else:
|
||||
dataList = conv_to_int(dataList)
|
||||
|
||||
private_dict.rawDict[key] = dataList
|
||||
|
||||
|
||||
def _cff_or_cff2(font):
|
||||
if "CFF " in font:
|
||||
return font["CFF "]
|
||||
return font["CFF2"]
|
||||
|
||||
|
||||
def getfd_map(varFont, fonts_list):
|
||||
"""Since a subset source font may have fewer FontDicts in their
|
||||
FDArray than the default font, we have to match up the FontDicts in
|
||||
the different fonts . We do this with the FDSelect array, and by
|
||||
assuming that the same glyph will reference matching FontDicts in
|
||||
each source font. We return a mapping from fdIndex in the default
|
||||
font to a dictionary which maps each master list index of each
|
||||
region font to the equivalent fdIndex in the region font."""
|
||||
fd_map = {}
|
||||
default_font = fonts_list[0]
|
||||
region_fonts = fonts_list[1:]
|
||||
num_regions = len(region_fonts)
|
||||
topDict = _cff_or_cff2(default_font).cff.topDictIndex[0]
|
||||
if not hasattr(topDict, "FDSelect"):
|
||||
# All glyphs reference only one FontDict.
|
||||
# Map the FD index for regions to index 0.
|
||||
fd_map[0] = {ri: 0 for ri in range(num_regions)}
|
||||
return fd_map
|
||||
|
||||
gname_mapping = {}
|
||||
default_fdSelect = topDict.FDSelect
|
||||
glyphOrder = default_font.getGlyphOrder()
|
||||
for gid, fdIndex in enumerate(default_fdSelect):
|
||||
gname_mapping[glyphOrder[gid]] = fdIndex
|
||||
if fdIndex not in fd_map:
|
||||
fd_map[fdIndex] = {}
|
||||
for ri, region_font in enumerate(region_fonts):
|
||||
region_glyphOrder = region_font.getGlyphOrder()
|
||||
region_topDict = _cff_or_cff2(region_font).cff.topDictIndex[0]
|
||||
if not hasattr(region_topDict, "FDSelect"):
|
||||
# All the glyphs share the same FontDict. Pick any glyph.
|
||||
default_fdIndex = gname_mapping[region_glyphOrder[0]]
|
||||
fd_map[default_fdIndex][ri] = 0
|
||||
else:
|
||||
region_fdSelect = region_topDict.FDSelect
|
||||
for gid, fdIndex in enumerate(region_fdSelect):
|
||||
default_fdIndex = gname_mapping[region_glyphOrder[gid]]
|
||||
region_map = fd_map[default_fdIndex]
|
||||
if ri not in region_map:
|
||||
region_map[ri] = fdIndex
|
||||
return fd_map
|
||||
|
||||
|
||||
CVarData = namedtuple("CVarData", "varDataList masterSupports vsindex_dict")
|
||||
|
||||
|
||||
def merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder):
|
||||
topDict = varFont["CFF2"].cff.topDictIndex[0]
|
||||
top_dicts = [topDict] + [
|
||||
_cff_or_cff2(ttFont).cff.topDictIndex[0] for ttFont in ordered_fonts_list[1:]
|
||||
]
|
||||
num_masters = len(model.mapping)
|
||||
cvData = merge_charstrings(glyphOrder, num_masters, top_dicts, model)
|
||||
fd_map = getfd_map(varFont, ordered_fonts_list)
|
||||
merge_PrivateDicts(top_dicts, cvData.vsindex_dict, model, fd_map)
|
||||
addCFFVarStore(varFont, model, cvData.varDataList, cvData.masterSupports)
|
||||
|
||||
|
||||
def _get_cs(charstrings, glyphName, filterEmpty=False):
|
||||
if glyphName not in charstrings:
|
||||
return None
|
||||
cs = charstrings[glyphName]
|
||||
|
||||
if filterEmpty:
|
||||
cs.decompile()
|
||||
if cs.program == []: # CFF2 empty charstring
|
||||
return None
|
||||
elif (
|
||||
len(cs.program) <= 2
|
||||
and cs.program[-1] == "endchar"
|
||||
and (len(cs.program) == 1 or type(cs.program[0]) in (int, float))
|
||||
): # CFF1 empty charstring
|
||||
return None
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def _add_new_vsindex(
|
||||
model, key, masterSupports, vsindex_dict, vsindex_by_key, varDataList
|
||||
):
|
||||
varTupleIndexes = []
|
||||
for support in model.supports[1:]:
|
||||
if support not in masterSupports:
|
||||
masterSupports.append(support)
|
||||
varTupleIndexes.append(masterSupports.index(support))
|
||||
var_data = varLib.builder.buildVarData(varTupleIndexes, None, False)
|
||||
vsindex = len(vsindex_dict)
|
||||
vsindex_by_key[key] = vsindex
|
||||
vsindex_dict[vsindex] = (model, [key])
|
||||
varDataList.append(var_data)
|
||||
return vsindex
|
||||
|
||||
|
||||
def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel):
|
||||
vsindex_dict = {}
|
||||
vsindex_by_key = {}
|
||||
varDataList = []
|
||||
masterSupports = []
|
||||
default_charstrings = top_dicts[0].CharStrings
|
||||
for gid, gname in enumerate(glyphOrder):
|
||||
# interpret empty non-default masters as missing glyphs from a sparse master
|
||||
all_cs = [
|
||||
_get_cs(td.CharStrings, gname, i != 0) for i, td in enumerate(top_dicts)
|
||||
]
|
||||
model, model_cs = masterModel.getSubModel(all_cs)
|
||||
# create the first pass CFF2 charstring, from
|
||||
# the default charstring.
|
||||
default_charstring = model_cs[0]
|
||||
var_pen = CFF2CharStringMergePen([], gname, num_masters, 0)
|
||||
# We need to override outlineExtractor because these
|
||||
# charstrings do have widths in the 'program'; we need to drop these
|
||||
# values rather than post assertion error for them.
|
||||
default_charstring.outlineExtractor = MergeOutlineExtractor
|
||||
default_charstring.draw(var_pen)
|
||||
|
||||
# Add the coordinates from all the other regions to the
|
||||
# blend lists in the CFF2 charstring.
|
||||
region_cs = model_cs[1:]
|
||||
for region_idx, region_charstring in enumerate(region_cs, start=1):
|
||||
var_pen.restart(region_idx)
|
||||
region_charstring.outlineExtractor = MergeOutlineExtractor
|
||||
region_charstring.draw(var_pen)
|
||||
|
||||
# Collapse each coordinate list to a blend operator and its args.
|
||||
new_cs = var_pen.getCharString(
|
||||
private=default_charstring.private,
|
||||
globalSubrs=default_charstring.globalSubrs,
|
||||
var_model=model,
|
||||
optimize=True,
|
||||
)
|
||||
default_charstrings[gname] = new_cs
|
||||
|
||||
if not region_cs:
|
||||
continue
|
||||
|
||||
if (not var_pen.seen_moveto) or ("blend" not in new_cs.program):
|
||||
# If this is not a marking glyph, or if there are no blend
|
||||
# arguments, then we can use vsindex 0. No need to
|
||||
# check if we need a new vsindex.
|
||||
continue
|
||||
|
||||
# If the charstring required a new model, create
|
||||
# a VarData table to go with, and set vsindex.
|
||||
key = tuple(v is not None for v in all_cs)
|
||||
try:
|
||||
vsindex = vsindex_by_key[key]
|
||||
except KeyError:
|
||||
vsindex = _add_new_vsindex(
|
||||
model, key, masterSupports, vsindex_dict, vsindex_by_key, varDataList
|
||||
)
|
||||
# We do not need to check for an existing new_cs.private.vsindex,
|
||||
# as we know it doesn't exist yet.
|
||||
if vsindex != 0:
|
||||
new_cs.program[:0] = [vsindex, "vsindex"]
|
||||
|
||||
# If there is no variation in any of the charstrings, then vsindex_dict
|
||||
# never gets built. This could still be needed if there is variation
|
||||
# in the PrivatDict, so we will build the default data for vsindex = 0.
|
||||
if not vsindex_dict:
|
||||
key = (True,) * num_masters
|
||||
_add_new_vsindex(
|
||||
masterModel, key, masterSupports, vsindex_dict, vsindex_by_key, varDataList
|
||||
)
|
||||
cvData = CVarData(
|
||||
varDataList=varDataList,
|
||||
masterSupports=masterSupports,
|
||||
vsindex_dict=vsindex_dict,
|
||||
)
|
||||
# XXX To do: optimize use of vsindex between the PrivateDicts and
|
||||
# charstrings
|
||||
return cvData
|
||||
|
||||
|
||||
class CFFToCFF2OutlineExtractor(T2OutlineExtractor):
|
||||
"""This class is used to remove the initial width from the CFF
|
||||
charstring without trying to add the width to self.nominalWidthX,
|
||||
which is None."""
|
||||
|
||||
def popallWidth(self, evenOdd=0):
|
||||
args = self.popall()
|
||||
if not self.gotWidth:
|
||||
if evenOdd ^ (len(args) % 2):
|
||||
args = args[1:]
|
||||
self.width = self.defaultWidthX
|
||||
self.gotWidth = 1
|
||||
return args
|
||||
|
||||
|
||||
class MergeOutlineExtractor(CFFToCFF2OutlineExtractor):
|
||||
"""Used to extract the charstring commands - including hints - from a
|
||||
CFF charstring in order to merge it as another set of region data
|
||||
into a CFF2 variable font charstring."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pen,
|
||||
localSubrs,
|
||||
globalSubrs,
|
||||
nominalWidthX,
|
||||
defaultWidthX,
|
||||
private=None,
|
||||
blender=None,
|
||||
):
|
||||
super().__init__(
|
||||
pen, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private, blender
|
||||
)
|
||||
|
||||
def countHints(self):
|
||||
args = self.popallWidth()
|
||||
self.hintCount = self.hintCount + len(args) // 2
|
||||
return args
|
||||
|
||||
def _hint_op(self, type, args):
|
||||
self.pen.add_hint(type, args)
|
||||
|
||||
def op_hstem(self, index):
|
||||
args = self.countHints()
|
||||
self._hint_op("hstem", args)
|
||||
|
||||
def op_vstem(self, index):
|
||||
args = self.countHints()
|
||||
self._hint_op("vstem", args)
|
||||
|
||||
def op_hstemhm(self, index):
|
||||
args = self.countHints()
|
||||
self._hint_op("hstemhm", args)
|
||||
|
||||
def op_vstemhm(self, index):
|
||||
args = self.countHints()
|
||||
self._hint_op("vstemhm", args)
|
||||
|
||||
def _get_hintmask(self, index):
|
||||
if not self.hintMaskBytes:
|
||||
args = self.countHints()
|
||||
if args:
|
||||
self._hint_op("vstemhm", args)
|
||||
self.hintMaskBytes = (self.hintCount + 7) // 8
|
||||
hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)
|
||||
return index, hintMaskBytes
|
||||
|
||||
def op_hintmask(self, index):
|
||||
index, hintMaskBytes = self._get_hintmask(index)
|
||||
self.pen.add_hintmask("hintmask", [hintMaskBytes])
|
||||
return hintMaskBytes, index
|
||||
|
||||
def op_cntrmask(self, index):
|
||||
index, hintMaskBytes = self._get_hintmask(index)
|
||||
self.pen.add_hintmask("cntrmask", [hintMaskBytes])
|
||||
return hintMaskBytes, index
|
||||
|
||||
|
||||
class CFF2CharStringMergePen(T2CharStringPen):
|
||||
"""Pen to merge Type 2 CharStrings."""
|
||||
|
||||
def __init__(
|
||||
self, default_commands, glyphName, num_masters, master_idx, roundTolerance=0.01
|
||||
):
|
||||
# For roundTolerance see https://github.com/fonttools/fonttools/issues/2838
|
||||
super().__init__(
|
||||
width=None, glyphSet=None, CFF2=True, roundTolerance=roundTolerance
|
||||
)
|
||||
self.pt_index = 0
|
||||
self._commands = default_commands
|
||||
self.m_index = master_idx
|
||||
self.num_masters = num_masters
|
||||
self.prev_move_idx = 0
|
||||
self.seen_moveto = False
|
||||
self.glyphName = glyphName
|
||||
self.round = roundFunc(roundTolerance, round=round)
|
||||
|
||||
def add_point(self, point_type, pt_coords):
|
||||
if self.m_index == 0:
|
||||
self._commands.append([point_type, [pt_coords]])
|
||||
else:
|
||||
cmd = self._commands[self.pt_index]
|
||||
if cmd[0] != point_type:
|
||||
raise VarLibCFFPointTypeMergeError(
|
||||
point_type, self.pt_index, len(cmd[1]), cmd[0], self.glyphName
|
||||
)
|
||||
cmd[1].append(pt_coords)
|
||||
self.pt_index += 1
|
||||
|
||||
def add_hint(self, hint_type, args):
|
||||
if self.m_index == 0:
|
||||
self._commands.append([hint_type, [args]])
|
||||
else:
|
||||
cmd = self._commands[self.pt_index]
|
||||
if cmd[0] != hint_type:
|
||||
raise VarLibCFFHintTypeMergeError(
|
||||
hint_type, self.pt_index, len(cmd[1]), cmd[0], self.glyphName
|
||||
)
|
||||
cmd[1].append(args)
|
||||
self.pt_index += 1
|
||||
|
||||
def add_hintmask(self, hint_type, abs_args):
|
||||
# For hintmask, fonttools.cffLib.specializer.py expects
|
||||
# each of these to be represented by two sequential commands:
|
||||
# first holding only the operator name, with an empty arg list,
|
||||
# second with an empty string as the op name, and the mask arg list.
|
||||
if self.m_index == 0:
|
||||
self._commands.append([hint_type, []])
|
||||
self._commands.append(["", [abs_args]])
|
||||
else:
|
||||
cmd = self._commands[self.pt_index]
|
||||
if cmd[0] != hint_type:
|
||||
raise VarLibCFFHintTypeMergeError(
|
||||
hint_type, self.pt_index, len(cmd[1]), cmd[0], self.glyphName
|
||||
)
|
||||
self.pt_index += 1
|
||||
cmd = self._commands[self.pt_index]
|
||||
cmd[1].append(abs_args)
|
||||
self.pt_index += 1
|
||||
|
||||
def _moveTo(self, pt):
|
||||
if not self.seen_moveto:
|
||||
self.seen_moveto = True
|
||||
pt_coords = self._p(pt)
|
||||
self.add_point("rmoveto", pt_coords)
|
||||
# I set prev_move_idx here because add_point()
|
||||
# can change self.pt_index.
|
||||
self.prev_move_idx = self.pt_index - 1
|
||||
|
||||
def _lineTo(self, pt):
|
||||
pt_coords = self._p(pt)
|
||||
self.add_point("rlineto", pt_coords)
|
||||
|
||||
def _curveToOne(self, pt1, pt2, pt3):
|
||||
_p = self._p
|
||||
pt_coords = _p(pt1) + _p(pt2) + _p(pt3)
|
||||
self.add_point("rrcurveto", pt_coords)
|
||||
|
||||
def _closePath(self):
|
||||
pass
|
||||
|
||||
def _endPath(self):
|
||||
pass
|
||||
|
||||
def restart(self, region_idx):
|
||||
self.pt_index = 0
|
||||
self.m_index = region_idx
|
||||
self._p0 = (0, 0)
|
||||
|
||||
def getCommands(self):
|
||||
return self._commands
|
||||
|
||||
def reorder_blend_args(self, commands, get_delta_func):
|
||||
"""
|
||||
We first re-order the master coordinate values.
|
||||
For a moveto to lineto, the args are now arranged as::
|
||||
|
||||
[ [master_0 x,y], [master_1 x,y], [master_2 x,y] ]
|
||||
|
||||
We re-arrange this to::
|
||||
|
||||
[ [master_0 x, master_1 x, master_2 x],
|
||||
[master_0 y, master_1 y, master_2 y]
|
||||
]
|
||||
|
||||
If the master values are all the same, we collapse the list to
|
||||
as single value instead of a list.
|
||||
|
||||
We then convert this to::
|
||||
|
||||
[ [master_0 x] + [x delta tuple] + [numBlends=1]
|
||||
[master_0 y] + [y delta tuple] + [numBlends=1]
|
||||
]
|
||||
"""
|
||||
for cmd in commands:
|
||||
# arg[i] is the set of arguments for this operator from master i.
|
||||
args = cmd[1]
|
||||
m_args = zip(*args)
|
||||
# m_args[n] is now all num_master args for the i'th argument
|
||||
# for this operation.
|
||||
cmd[1] = list(m_args)
|
||||
lastOp = None
|
||||
for cmd in commands:
|
||||
op = cmd[0]
|
||||
# masks are represented by two cmd's: first has only op names,
|
||||
# second has only args.
|
||||
if lastOp in ["hintmask", "cntrmask"]:
|
||||
coord = list(cmd[1])
|
||||
if not allEqual(coord):
|
||||
raise VarLibMergeError(
|
||||
"Hintmask values cannot differ between source fonts."
|
||||
)
|
||||
cmd[1] = [coord[0][0]]
|
||||
else:
|
||||
coords = cmd[1]
|
||||
new_coords = []
|
||||
for coord in coords:
|
||||
if allEqual(coord):
|
||||
new_coords.append(coord[0])
|
||||
else:
|
||||
# convert to deltas
|
||||
deltas = get_delta_func(coord)[1:]
|
||||
coord = [coord[0]] + deltas
|
||||
coord.append(1)
|
||||
new_coords.append(coord)
|
||||
cmd[1] = new_coords
|
||||
lastOp = op
|
||||
return commands
|
||||
|
||||
def getCharString(
|
||||
self, private=None, globalSubrs=None, var_model=None, optimize=True
|
||||
):
|
||||
commands = self._commands
|
||||
commands = self.reorder_blend_args(
|
||||
commands, partial(var_model.getDeltas, round=self.round)
|
||||
)
|
||||
if optimize:
|
||||
commands = specializeCommands(
|
||||
commands, generalizeFirst=False, maxstack=maxStackLimit
|
||||
)
|
||||
program = commandsToProgram(commands)
|
||||
charString = T2CharString(
|
||||
program=program, private=private, globalSubrs=globalSubrs
|
||||
)
|
||||
return charString
|
||||
Loading…
Add table
Add a link
Reference in a new issue