| 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.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 |
| |
| |
| def lib_convertCFFToCFF2(cff, otFont): |
| # This assumes a decompiled CFF table. |
| cff2GetGlyphOrder = cff.otFont.getGlyphOrder |
| topDictData = TopDictIndex(None, cff2GetGlyphOrder, None) |
| topDictData.items = cff.topDictIndex.items |
| cff.topDictIndex = topDictData |
| topDict = topDictData[0] |
| if hasattr(topDict, 'Private'): |
| privateDict = topDict.Private |
| else: |
| privateDict = None |
| opOrder = buildOrder(topDictOperators2) |
| topDict.order = opOrder |
| topDict.cff2GetGlyphOrder = cff2GetGlyphOrder |
| if not hasattr(topDict, "FDArray"): |
| fdArray = topDict.FDArray = FDArrayIndex() |
| fdArray.strings = None |
| fdArray.GlobalSubrs = topDict.GlobalSubrs |
| topDict.GlobalSubrs.fdArray = fdArray |
| charStrings = topDict.CharStrings |
| if charStrings.charStringsAreIndexed: |
| charStrings.charStringsIndex.fdArray = fdArray |
| else: |
| charStrings.fdArray = fdArray |
| fontDict = FontDict() |
| fontDict.setCFF2(True) |
| fdArray.append(fontDict) |
| fontDict.Private = privateDict |
| privateOpOrder = buildOrder(privateDictOperators2) |
| if privateDict is not None: |
| for entry in privateDictOperators: |
| key = entry[1] |
| if key not in privateOpOrder: |
| if key in privateDict.rawDict: |
| # print "Removing private dict", key |
| del privateDict.rawDict[key] |
| if hasattr(privateDict, key): |
| delattr(privateDict, key) |
| # print "Removing privateDict attr", key |
| else: |
| # clean up the PrivateDicts in the fdArray |
| fdArray = topDict.FDArray |
| privateOpOrder = buildOrder(privateDictOperators2) |
| for fontDict in fdArray: |
| fontDict.setCFF2(True) |
| for key in list(fontDict.rawDict.keys()): |
| if key not in fontDict.order: |
| del fontDict.rawDict[key] |
| if hasattr(fontDict, key): |
| delattr(fontDict, key) |
| |
| privateDict = fontDict.Private |
| for entry in privateDictOperators: |
| key = entry[1] |
| if key not in privateOpOrder: |
| if key in privateDict.rawDict: |
| # print "Removing private dict", key |
| del privateDict.rawDict[key] |
| if hasattr(privateDict, key): |
| delattr(privateDict, key) |
| # print "Removing privateDict attr", key |
| # Now delete up the decrecated topDict operators from CFF 1.0 |
| for entry in topDictOperators: |
| key = entry[1] |
| if key not in opOrder: |
| if key in topDict.rawDict: |
| del topDict.rawDict[key] |
| if hasattr(topDict, key): |
| delattr(topDict, key) |
| |
| # At this point, the Subrs and Charstrings are all still T2Charstring class |
| # easiest to fix this by compiling, then decompiling again |
| cff.major = 2 |
| file = BytesIO() |
| cff.compile(file, otFont, isCFF2=True) |
| file.seek(0) |
| cff.decompile(file, otFont, isCFF2=True) |
| |
| |
| def convertCFFtoCFF2(varFont): |
| # Convert base font to a single master CFF2 font. |
| cffTable = varFont['CFF '] |
| lib_convertCFFToCFF2(cffTable.cff, varFont) |
| newCFF2 = newTable("CFF2") |
| newCFF2.cff = cffTable.cff |
| varFont['CFF2'] = newCFF2 |
| del varFont['CFF '] |
| |
| |
| 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): |
| if glyphName not in charstrings: |
| return None |
| return charstrings[glyphName] |
| |
| 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): |
| all_cs = [ |
| _get_cs(td.CharStrings, gname) |
| for td in top_dicts] |
| if len([gs for gs in all_cs if gs is not None]) == 1: |
| continue |
| 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 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): |
| super().__init__(pen, localSubrs, |
| globalSubrs, nominalWidthX, defaultWidthX, private) |
| |
| 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.5): |
| 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 |
| 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 |