Fixed issue with reading/writing PrivateDict BlueValues to ttx file. To date, BlueValue arguments have been written as absolute coordinate values, reflecting the history of the CFF ttx table format. However, Behdad Esfahbod pointed out that it is not always possible to roundtrip between the absolute values in the ttx file and the CFF2 default font value and the delta list. This update changes the ttx format so that in PrivateDict BlueValues, the default font values are written as absolute coordinates, preserving continuity with how CFF tables are written, but the region values are written as the deltas from the VariationStore delta list.
This also fixes fonttools/fonttools/issues/1030. Although the roundtrip is generally possible when a VariationStore is built from source font data using the Superpolator model, it is possible to build region definitions that do not follow this model. Behdad cited the Skia "Q" example, where the tail of the Q is affected by two regions defined as: min=0 peak=0.5 max=0.51 delta=+10 min=0.49 peak=0.5 max=0.51 delta=-10
This commit is contained in:
parent
045287aa25
commit
8b02b5a294
@ -1972,12 +1972,7 @@ class DictCompiler(object):
|
|||||||
|
|
||||||
def arg_number(self, num):
|
def arg_number(self, num):
|
||||||
if isinstance(num, list):
|
if isinstance(num, list):
|
||||||
blendList = num
|
data = [encodeNumber(val) for val in num]
|
||||||
firstNum = blendList[0]
|
|
||||||
data = [firstNum]
|
|
||||||
for blendNum in blendList[1:]:
|
|
||||||
data.append(blendNum - firstNum)
|
|
||||||
data = [encodeNumber(val) for val in data]
|
|
||||||
data.append(encodeNumber(1))
|
data.append(encodeNumber(1))
|
||||||
data.append(bytechr(blendOp))
|
data.append(bytechr(blendOp))
|
||||||
datum = bytesjoin(data)
|
datum = bytesjoin(data)
|
||||||
@ -2011,15 +2006,33 @@ class DictCompiler(object):
|
|||||||
data.append(encodeNumber(num))
|
data.append(encodeNumber(num))
|
||||||
return bytesjoin(data)
|
return bytesjoin(data)
|
||||||
|
|
||||||
|
|
||||||
def arg_delta_blend(self, value):
|
def arg_delta_blend(self, value):
|
||||||
# A delta list with blend lists has to be *all* blend lists.
|
""" A delta list with blend lists has to be *all* blend lists.
|
||||||
# We have a list of master value lists, where the nth master value list
|
The value is a list is arranged as follows.
|
||||||
# contains the absolute values from each master for the nth entry in the
|
[
|
||||||
# current array.
|
[V0, d0..dn]
|
||||||
# We first convert these to relative values from the previous entry.
|
[V1, d0..dn]
|
||||||
|
...
|
||||||
|
[Vm, d0..dn]
|
||||||
|
]
|
||||||
|
V is the absolute coordinate value from the default font, and d0-dn are
|
||||||
|
the delta values from the n regions. Each V is an absolute coordinate
|
||||||
|
from the default font.
|
||||||
|
We want to return a list:
|
||||||
|
[
|
||||||
|
[v0, v1..vm]
|
||||||
|
[d0..dn]
|
||||||
|
...
|
||||||
|
[d0..dn]
|
||||||
|
numBlends
|
||||||
|
blendOp
|
||||||
|
]
|
||||||
|
where each v is relative to the previous default font value.
|
||||||
|
"""
|
||||||
numMasters = len(value[0])
|
numMasters = len(value[0])
|
||||||
numValues = len(value)
|
numBlends = len(value)
|
||||||
numStack = (numValues * numMasters) + 1
|
numStack = (numBlends * numMasters) + 1
|
||||||
if numStack > self.maxBlendStack:
|
if numStack > self.maxBlendStack:
|
||||||
# Figure out the max number of value we can blend
|
# Figure out the max number of value we can blend
|
||||||
# and divide this list up into chunks of that size.
|
# and divide this list up into chunks of that size.
|
||||||
@ -2035,28 +2048,26 @@ class DictCompiler(object):
|
|||||||
out.extend(out1)
|
out.extend(out1)
|
||||||
value = value[numVal:]
|
value = value[numVal:]
|
||||||
else:
|
else:
|
||||||
firstList = [0] * numValues
|
firstList = [0] * numBlends
|
||||||
deltaList = [None] * (numValues)
|
deltaList = [None] * numBlends
|
||||||
i = 0
|
i = 0
|
||||||
prevValList = numMasters * [0]
|
prevVal = 0
|
||||||
while i < numValues:
|
while i < numBlends:
|
||||||
masterValList = value[i]
|
# For PrivateDict BlueValues, the default font
|
||||||
firstVal = firstList[i] = masterValList[0] - prevValList[0]
|
# values are absolute, not relative.
|
||||||
j = 1
|
# Must convert these back to relative coordinates
|
||||||
deltaEntry = (numMasters - 1) * [0]
|
# befor writing to CFF2.
|
||||||
while j < numMasters:
|
defaultValue = value[i][0]
|
||||||
masterValDelta = masterValList[j] - prevValList[j]
|
firstList[i] = defaultValue - prevVal
|
||||||
deltaEntry[j - 1] = masterValDelta - firstVal
|
prevVal = defaultValue
|
||||||
j += 1
|
deltaList[i] = value[i][1:]
|
||||||
deltaList[i] = deltaEntry
|
|
||||||
i += 1
|
i += 1
|
||||||
prevValList = masterValList
|
|
||||||
|
|
||||||
relValueList = firstList
|
relValueList = firstList
|
||||||
for blendList in deltaList:
|
for blendList in deltaList:
|
||||||
relValueList.extend(blendList)
|
relValueList.extend(blendList)
|
||||||
out = [encodeNumber(val) for val in relValueList]
|
out = [encodeNumber(val) for val in relValueList]
|
||||||
out.append(encodeNumber(numValues))
|
out.append(encodeNumber(numBlends))
|
||||||
out.append(bytechr(blendOp))
|
out.append(bytechr(blendOp))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -1246,36 +1246,34 @@ class DictDecompiler(object):
|
|||||||
def arg_array(self, name):
|
def arg_array(self, name):
|
||||||
return self.popall()
|
return self.popall()
|
||||||
def arg_blendList(self, name):
|
def arg_blendList(self, name):
|
||||||
# The last item on the stack is the number of return values, aka numValues.
|
"""
|
||||||
# before that we have [numValues: args from first master]
|
There may be non-blend args at the top of the stack. We first calculate
|
||||||
# then numValues blend lists, where each blend list is numMasters -1
|
where the blend args start in the stack. These are the last
|
||||||
# Total number of values is numValues + (numValues * (numMasters -1)), == numValues * numMasters.
|
numMasters*numBlends) +1 args.
|
||||||
# reformat list to be numReturnValues tuples, each tuple with nMaster values
|
The blend args starts with numMasters relative coordinate values, the BlueValues in the list from the default master font. This is followed by
|
||||||
|
numBlends list of values. Each of value in one of these lists is the
|
||||||
|
Variable Font delta for the matching region.
|
||||||
|
|
||||||
|
We re-arrange this to be a list of numMaster entries. Each entry starts with the corresponding default font relative value, and is followed by
|
||||||
|
the delta values. We then convert the default values, the first item in each entry, to an absolute value.
|
||||||
|
"""
|
||||||
vsindex = self.dict.get('vsindex', 0)
|
vsindex = self.dict.get('vsindex', 0)
|
||||||
numMasters = self.parent.getNumRegions(vsindex) + 1 # only a PrivateDict has blended ops.
|
numMasters = self.parent.getNumRegions(vsindex) + 1 # only a PrivateDict has blended ops.
|
||||||
numReturnValues = self.pop()
|
numBlends = self.pop()
|
||||||
stackIndex = -numMasters * numReturnValues
|
args = self.popall()
|
||||||
args = self.stack[stackIndex:]
|
|
||||||
del self.stack[stackIndex:]
|
|
||||||
numArgs = len(args)
|
numArgs = len(args)
|
||||||
value = [None]*numReturnValues
|
# The spec says that there should be no non-blended Blue Values,.
|
||||||
|
assert(numArgs == numMasters * numBlends)
|
||||||
|
value = [None]*numBlends
|
||||||
numDeltas = numMasters-1
|
numDeltas = numMasters-1
|
||||||
i = 0
|
i = 0
|
||||||
prevVal = 0
|
prevVal = 0
|
||||||
prevValueList = [0]*numMasters
|
while i < numBlends:
|
||||||
while i < numReturnValues:
|
|
||||||
newVal = args[i] + prevVal
|
newVal = args[i] + prevVal
|
||||||
blendList = [newVal]*numMasters
|
|
||||||
prevVal = newVal
|
prevVal = newVal
|
||||||
|
masterOffset = numBlends + (i* numDeltas)
|
||||||
|
blendList = [newVal] + args[masterOffset:masterOffset+numDeltas]
|
||||||
value[i] = blendList
|
value[i] = blendList
|
||||||
j = 1
|
|
||||||
while j < numMasters:
|
|
||||||
masterOffset = numReturnValues + (i* numDeltas)
|
|
||||||
mi = masterOffset +(j-1)
|
|
||||||
delta = args[i] + args[mi]
|
|
||||||
blendList[j]= delta + prevValueList[j]
|
|
||||||
j += 1
|
|
||||||
prevValueList = blendList
|
|
||||||
i += 1
|
i += 1
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user