varLib. Add support for building CFF2 variable font with a call to varLib.build().
Add support functions for merging CFF tables into a CFF2 table.
This commit is contained in:
parent
08aef71458
commit
9626cfe15b
269
Lib/fontTools/cffLib/cff2_merge_funcs.py
Normal file
269
Lib/fontTools/cffLib/cff2_merge_funcs.py
Normal file
@ -0,0 +1,269 @@
|
||||
import os
|
||||
from fontTools.misc.py23 import BytesIO
|
||||
from fontTools.cffLib import (TopDictIndex,
|
||||
buildOrder,
|
||||
topDictOperators,
|
||||
topDictOperators2,
|
||||
privateDictOperators,
|
||||
privateDictOperators2,
|
||||
FDArrayIndex,
|
||||
FontDict,
|
||||
VarStoreData,)
|
||||
from fontTools.ttLib import newTable
|
||||
from fontTools import varLib
|
||||
from cff2mergePen import CFF2CharStringMergePen, MergeTypeError
|
||||
|
||||
|
||||
def addCFFVarStore(varFont, varModel):
|
||||
supports = varModel.supports[1:]
|
||||
fvarTable = varFont['fvar']
|
||||
axisKeys = [axis.axisTag for axis in fvarTable.axes]
|
||||
varTupleList = varLib.builder.buildVarRegionList(supports, axisKeys)
|
||||
varTupleIndexes = list(range(len(supports)))
|
||||
varDeltasCFFV = varLib.builder.buildVarData(varTupleIndexes, None, False)
|
||||
varStoreCFFV = varLib.builder.buildVarStore(varTupleList, [varDeltasCFFV])
|
||||
|
||||
topDict = varFont['CFF2'].cff.topDictIndex[0]
|
||||
topDict.VarStore = VarStoreData(otVarStore=varStoreCFFV)
|
||||
|
||||
|
||||
def addNamesToPost(ttFont, fontGlyphList):
|
||||
postTable = ttFont['post']
|
||||
postTable.glyphOrder = ttFont.glyphOrder = fontGlyphList
|
||||
postTable.formatType = 2.0
|
||||
postTable.extraNames = []
|
||||
postTable.mapping = {}
|
||||
postTable.compile(ttFont)
|
||||
|
||||
|
||||
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)
|
||||
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 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 pointsDiffer(pointList):
|
||||
p0 = max(pointList)
|
||||
p1 = min(pointList)
|
||||
result = False if p1 == p0 else True
|
||||
return result
|
||||
|
||||
|
||||
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 ']
|
||||
|
||||
|
||||
class MergeDictError(TypeError):
|
||||
def __init__(self, key, value, values):
|
||||
error_msg = ["For the Private Dict key ()".format(key)]
|
||||
error_msg.append("the default font value list:")
|
||||
error_msg.append("\t{}".format(value))
|
||||
error_msg.append(
|
||||
"had a different number of values than"
|
||||
"a region font:")
|
||||
for value in values:
|
||||
error_msg.append("\t{}".format(value))
|
||||
error_msg = os.linesep.join(error_msg)
|
||||
|
||||
|
||||
def conv_to_int(num):
|
||||
if round(num) == num:
|
||||
return int(num)
|
||||
else:
|
||||
return num
|
||||
|
||||
|
||||
def merge_PrivateDicts(topDict, region_top_dicts, num_masters, var_model):
|
||||
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
|
||||
pds = [private_dict] + [
|
||||
regionFDArray[fd_index].Private for regionFDArray in regionFDArrays
|
||||
]
|
||||
for key, value in private_dict.rawDict.items():
|
||||
if isinstance(value, list):
|
||||
try:
|
||||
values = [pd.rawDict[key] for pd in pds]
|
||||
except KeyError:
|
||||
del private_dict.rawDict[key]
|
||||
print(
|
||||
"Warning: {key} in default font Private dict is "
|
||||
b"missing from another font, and was "
|
||||
b"discarded.".format(key=key))
|
||||
continue
|
||||
try:
|
||||
values = zip(*values)
|
||||
except IndexError:
|
||||
raise MergeDictError(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]]
|
||||
"""
|
||||
dataList = []
|
||||
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 pointsDiffer(rel_list):
|
||||
any_points_differ = True
|
||||
prev_val_list = val_list
|
||||
deltas = var_model.getDeltas(rel_list)
|
||||
# Convert numbers with no decimal part to an int.
|
||||
deltas = [conv_to_int(delta) for delta in deltas]
|
||||
# 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 pointsDiffer(values):
|
||||
dataList = var_model.getDeltas(values)
|
||||
else:
|
||||
dataList = values[0]
|
||||
private_dict.rawDict[key] = dataList
|
||||
|
||||
|
||||
class MergeCharError(TypeError):
|
||||
def __init__(self, glyph_name, mergeError):
|
||||
self.error_msg = "{mergeError} in glyph {glyph_name}".format(
|
||||
mergeError=mergeError.error_msg,
|
||||
glyph_name=glyph_name)
|
||||
super(MergeCharError, self).__init__(self.error_msg)
|
||||
|
||||
|
||||
def merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder):
|
||||
topDict = varFont['CFF2'].cff.topDictIndex[0]
|
||||
default_charstrings = topDict.CharStrings
|
||||
region_fonts = ordered_fonts_list[1:]
|
||||
region_top_dicts = [
|
||||
ttFont['CFF '].cff.topDictIndex[0] for ttFont in region_fonts
|
||||
]
|
||||
num_masters = len(model.mapping)
|
||||
merge_PrivateDicts(topDict, region_top_dicts, num_masters, model)
|
||||
merge_charstrings(default_charstrings,
|
||||
glyphOrder,
|
||||
num_masters,
|
||||
region_top_dicts, model)
|
||||
|
||||
|
||||
def merge_charstrings(default_charstrings,
|
||||
glyphOrder,
|
||||
num_masters,
|
||||
region_top_dicts,
|
||||
var_model):
|
||||
for gname in glyphOrder:
|
||||
default_charstring = default_charstrings[gname]
|
||||
var_pen = CFF2CharStringMergePen([], num_masters, master_idx=0)
|
||||
default_charstring.draw(var_pen)
|
||||
for region_idx, region_td in enumerate(region_top_dicts):
|
||||
region_idx += 1
|
||||
region_charstrings = region_td.CharStrings
|
||||
region_charstring = region_charstrings[gname]
|
||||
var_pen.restart(region_idx)
|
||||
try:
|
||||
region_charstring.draw(var_pen)
|
||||
except MergeTypeError as err:
|
||||
err.gname = gname
|
||||
err.region_idx = region_idx
|
||||
raise MergeCharError(gname, err)
|
||||
new_charstring = var_pen.getCharString(
|
||||
private=default_charstring.private,
|
||||
globalSubrs=default_charstring.globalSubrs,
|
||||
var_model=var_model,
|
||||
optimize=True)
|
||||
default_charstrings[gname] = new_charstring
|
302
Lib/fontTools/cffLib/cff2mergePen.py
Normal file
302
Lib/fontTools/cffLib/cff2mergePen.py
Normal file
@ -0,0 +1,302 @@
|
||||
# Copyright (c) 2009 Type Supply LLC
|
||||
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.fixedTools import otRound
|
||||
from fontTools.misc.psCharStrings import CFF2Subr
|
||||
from fontTools.pens.t2CharStringPen import T2CharStringPen
|
||||
from fontTools.cffLib.specializer import (commandsToProgram,
|
||||
specializeCommands,
|
||||
)
|
||||
|
||||
|
||||
class MergeTypeError(TypeError):
|
||||
def __init__(self, point_type, pt_index, m_index, default_type):
|
||||
self.error_msg = [
|
||||
"'{point_type}' at point index {pt_index} in master "
|
||||
"index {m_index} differs from the default font point "
|
||||
"type '{default_type}'".format(
|
||||
point_type=point_type,
|
||||
pt_index=pt_index,
|
||||
m_index=m_index,
|
||||
default_type=default_type)
|
||||
][0]
|
||||
super(MergeTypeError, self).__init__(self.error_msg)
|
||||
|
||||
|
||||
class CFF2CharStringMergePen(T2CharStringPen):
|
||||
"""Pen to merge Type 2 CharStrings.
|
||||
"""
|
||||
def __init__(self, default_commands,
|
||||
num_masters, master_idx, roundTolerance=0.5):
|
||||
super(
|
||||
CFF2CharStringMergePen,
|
||||
self).__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.roundTolerance = roundTolerance
|
||||
|
||||
def _round(self, number):
|
||||
tolerance = self.roundTolerance
|
||||
if tolerance == 0:
|
||||
return number # no-op
|
||||
rounded = otRound(number)
|
||||
# return rounded integer if the tolerance >= 0.5, or if the absolute
|
||||
# difference between the original float and the rounded integer is
|
||||
# within the tolerance
|
||||
if tolerance >= .5 or abs(rounded - number) <= tolerance:
|
||||
return rounded
|
||||
else:
|
||||
# else return the value un-rounded
|
||||
return number
|
||||
|
||||
def _p(self, pt):
|
||||
""" Unlike T2CharstringPen, this class stores absolute values.
|
||||
This is to allow the logic in check_and_fix_clospath() to work,
|
||||
where the current or previous absolute point has to be compared to
|
||||
the path start-point.
|
||||
"""
|
||||
self._p0 = pt
|
||||
return list(self._p0)
|
||||
|
||||
def make_flat_curve(self, prev_coords, cur_coords):
|
||||
# Convert line coords to curve coords.
|
||||
dx = self._round((cur_coords[0] - prev_coords[0])/3.0)
|
||||
dy = self._round((cur_coords[1] - prev_coords[1])/3.0)
|
||||
new_coords = [prev_coords[0] + dx,
|
||||
prev_coords[1] + dy,
|
||||
prev_coords[0] + 2*dx,
|
||||
prev_coords[1] + 2*dy
|
||||
] + cur_coords
|
||||
return new_coords
|
||||
|
||||
def make_curve_coords(self, coords, is_default):
|
||||
# Convert line coords to curve coords.
|
||||
prev_cmd = self._commands[self.pt_index-1]
|
||||
if is_default:
|
||||
new_coords = []
|
||||
for i, cur_coords in enumerate(coords):
|
||||
prev_coords = prev_cmd[1][i]
|
||||
master_coords = self.make_flat_curve(
|
||||
prev_coords[:2], cur_coords
|
||||
)
|
||||
new_coords.append(master_coords)
|
||||
else:
|
||||
cur_coords = coords
|
||||
prev_coords = prev_cmd[1][-1]
|
||||
new_coords = self.make_flat_curve(prev_coords[:2], cur_coords)
|
||||
return new_coords
|
||||
|
||||
def check_and_fix_flat_curve(self, cmd, point_type, pt_coords):
|
||||
if (point_type == 'rlineto') and (cmd[0] == 'rrcurveto'):
|
||||
is_default = False
|
||||
pt_coords = self.make_curve_coords(pt_coords, is_default)
|
||||
success = True
|
||||
elif (point_type == 'rrcurveto') and (cmd[0] == 'rlineto'):
|
||||
is_default = True
|
||||
expanded_coords = self.make_curve_coords(cmd[1], is_default)
|
||||
cmd[1] = expanded_coords
|
||||
cmd[0] = point_type
|
||||
success = True
|
||||
else:
|
||||
success = False
|
||||
return success, pt_coords
|
||||
|
||||
def check_and_fix_clospath(self, cmd, point_type, pt_coords):
|
||||
""" Some workflows drop a lineto which closes a path.
|
||||
Also, if the last segment is a curve in one master,
|
||||
and a flat curve in another, the flat curve can get
|
||||
converted to a closing lineto, and then dropped.
|
||||
Test if:
|
||||
1) one master op is a moveto,
|
||||
2) the previous op for this master does not close the path
|
||||
3) in the other master the current op is not a moveto
|
||||
4) the current op in the otehr master closes the current path
|
||||
|
||||
If the default font is missing the closing lineto, insert it,
|
||||
then proceed with merging the current op and pt_coords.
|
||||
|
||||
If the current region is missing the closing lineto
|
||||
and therefore the current op is a moveto,
|
||||
then add closing coordinates to self._commands,
|
||||
and increment self.pt_index.
|
||||
|
||||
Note that if this may insert a point in the default font list,
|
||||
so after using it, 'cmd' needs to be reset.
|
||||
|
||||
return True if we can fix this issue.
|
||||
"""
|
||||
if point_type == 'rmoveto':
|
||||
# If this is the case, we know that cmd[0] != 'rmoveto'
|
||||
|
||||
# The previous op must not close the path for this region font.
|
||||
prev_moveto_coords = self._commands[self.prev_move_idx][1][-1]
|
||||
prv_coords = self._commands[self.pt_index-1][1][-1]
|
||||
if prev_moveto_coords == prv_coords[-2:]:
|
||||
return False
|
||||
|
||||
# The current op must close the path for the default font.
|
||||
prev_moveto_coords2 = self._commands[self.prev_move_idx][1][0]
|
||||
prv_coords = self._commands[self.pt_index][1][0]
|
||||
if prev_moveto_coords2 != prv_coords[-2:]:
|
||||
return False
|
||||
|
||||
# Add the closing line coords for this region
|
||||
# so self._commands, then increment self.pt_index
|
||||
# so that the current region op will get merged
|
||||
# with the next default font moveto.
|
||||
if cmd[0] == 'rrcurveto':
|
||||
new_coords = self.make_curve_coords(prev_moveto_coords, False)
|
||||
cmd[1].append(new_coords)
|
||||
self.pt_index += 1
|
||||
return True
|
||||
|
||||
if cmd[0] == 'rmoveto':
|
||||
# The previous op must not close the path for the default font.
|
||||
prev_moveto_coords = self._commands[self.prev_move_idx][1][0]
|
||||
prv_coords = self._commands[self.pt_index-1][1][0]
|
||||
if prev_moveto_coords == prv_coords[-2:]:
|
||||
return False
|
||||
|
||||
# The current op must close the path for this region font.
|
||||
prev_moveto_coords2 = self._commands[self.prev_move_idx][1][-1]
|
||||
if prev_moveto_coords2 != pt_coords[-2:]:
|
||||
return False
|
||||
|
||||
# Insert the close path segment in the default font.
|
||||
# We omit the last coords from the previous moveto
|
||||
# is it will be supplied by the current region point.
|
||||
# after this function returns.
|
||||
new_cmd = [point_type, None]
|
||||
prev_move_coords = self._commands[self.prev_move_idx][1][:-1]
|
||||
# Note that we omit the last region's coord from prev_move_coords,
|
||||
# as that is from the current region, and we will add the
|
||||
# current pts' coords from the current region in its place.
|
||||
if point_type == 'rlineto':
|
||||
new_cmd[1] = prev_move_coords
|
||||
else:
|
||||
# We omit the last set of coords from the
|
||||
# previous moveto, as it will be supplied by the coords
|
||||
# for the current region pt.
|
||||
new_cmd[1] = self.make_curve_coords(prev_move_coords, True)
|
||||
self._commands.insert(self.pt_index, new_cmd)
|
||||
return True
|
||||
return False
|
||||
|
||||
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:
|
||||
# Fix some issues that show up in some
|
||||
# CFF workflows, even when fonts are
|
||||
# topologically merge compatible.
|
||||
success, pt_coords = self.check_and_fix_flat_curve(
|
||||
cmd, point_type, pt_coords)
|
||||
if not success:
|
||||
success = self.check_and_fix_clospath(
|
||||
cmd, point_type, pt_coords)
|
||||
if success:
|
||||
# We may have incremented self.pt_index
|
||||
cmd = self._commands[self.pt_index]
|
||||
if cmd[0] != point_type:
|
||||
success = False
|
||||
if not success:
|
||||
raise MergeTypeError(
|
||||
point_type,
|
||||
self.pt_index,
|
||||
len(cmd[1]),
|
||||
cmd[0])
|
||||
cmd[1].append(pt_coords)
|
||||
self.pt_index += 1
|
||||
|
||||
def _moveTo(self, pt):
|
||||
pt_coords = self._p(pt)
|
||||
self.prev_move_abs_coords = self.roundPoint(self._p0)
|
||||
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):
|
||||
"""
|
||||
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]
|
||||
]
|
||||
We also make the value relative.
|
||||
"""
|
||||
for cmd in self._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] = m_args
|
||||
|
||||
# Now convert from absolute to relative
|
||||
x0 = y0 = [0]*self.num_masters
|
||||
for cmd in self._commands:
|
||||
is_x = True
|
||||
coords = cmd[1]
|
||||
rel_coords = []
|
||||
for coord in coords:
|
||||
prev_coord = x0 if is_x else y0
|
||||
rel_coord = [pt[0] - pt[1] for pt in zip(coord, prev_coord)]
|
||||
|
||||
if max(rel_coord) == min(rel_coord):
|
||||
rel_coord = rel_coord[0]
|
||||
rel_coords.append(rel_coord)
|
||||
if is_x:
|
||||
x0 = coord
|
||||
else:
|
||||
y0 = coord
|
||||
is_x = not is_x
|
||||
cmd[1] = rel_coords
|
||||
|
||||
def getCharString(
|
||||
self, private=None, globalSubrs=None,
|
||||
var_model=None, optimize=True
|
||||
):
|
||||
self.reorder_blend_args()
|
||||
commands = self._commands
|
||||
if optimize:
|
||||
maxstack = 48 if not self._CFF2 else 513
|
||||
commands = specializeCommands(commands,
|
||||
generalizeFirst=False,
|
||||
maxstack=maxstack)
|
||||
program = commandsToProgram(commands, maxstack,
|
||||
var_model, round_func=self._round)
|
||||
charString = CFF2Subr(
|
||||
program=program, private=private, globalSubrs=globalSubrs)
|
||||
return charString
|
Loading…
x
Reference in New Issue
Block a user