This implements add, sub, mul, rmul, div and rdiv for psHints. With a tip of the hat to Tal's fontMath for some ideas. This also includes a round() method which does some appropriate rounding and integerifying of the zone and stems.

git-svn-id: http://svn.robofab.com/trunk@44 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c
This commit is contained in:
Erik van Blokland 2008-02-23 19:44:09 +00:00
parent a1d2ca7469
commit 10b720b45d

View File

@ -16,6 +16,7 @@ do it with the objectsFL and objectsRF.
from __future__ import generators from __future__ import generators
from __future__ import division
from robofab import RoboFabError from robofab import RoboFabError
@ -39,29 +40,50 @@ DEGREE = 180 / math.pi
# the key for the postscript hint data stored in the UFO # the key for the postscript hint data stored in the UFO
postScriptHintDataLibKey = "org.robofab.postScriptHintData" postScriptHintDataLibKey = "org.robofab.postScriptHintData"
# from http://svn.typesupply.com/packages/fontMath/mathFunctions.py
def add(v1, v2):
return v1 + v2
def sub(v1, v2):
return v1 - v2
def mul(v, f):
return v * f
def div(v, f):
return v / f
def issequence(x):
"Is x a sequence? We say it is if it has a __getitem__ method."
return hasattr(x, '__getitem__')
class BasePostScriptFontHintValues(object): class BasePostScriptFontHintValues(object):
""" Base class for font-level postscript hinting information. """ Base class for font-level postscript hinting information.
Blues values, stem values. Blues values, stem values.
""" """
_attrs = { _attributeNames = {
# some of these values can have only a certain number of elements # some of these values can have only a certain number of elements
'blueFuzz': {'default': None, 'max':1}, 'blueFuzz': {'default': None, 'max':1},
'blueScale': {'default': None, 'max':1}, 'blueScale': {'default': None, 'max':1},
'blueShift': {'default': None, 'max':1}, 'blueShift': {'default': None, 'max':1},
'forceBold': {'default': None, 'max':1}, 'forceBold': {'default': None, 'max':1},
'blueValues': {'default': None, 'max':13}, 'blueValues': {'default': None, 'max':7},
'otherBlues': {'default': None, 'max':9}, 'otherBlues': {'default': None, 'max':5},
'familyBlues': {'default': None, 'max':13}, 'familyBlues': {'default': None, 'max':7},
'familyOtherBlues': {'default': None, 'max':9}, 'familyOtherBlues': {'default': None, 'max':5},
'vStems': {'default': None, 'max':11}, 'vStems': {'default': None, 'max':6},
'hStems': {'default': None, 'max':11}, 'hStems': {'default': None, 'max':11},
} }
def __init__(self): def __init__(self, data=None):
for name in self._attrs.keys(): if data is not None:
setattr(self, name, self._attrs[name]) self.fromDict(data)
else:
for name in self._attributeNames.keys():
setattr(self, name, self._attributeNames[name]['default'])
def getParent(self): def getParent(self):
"""this method will be overwritten with a weakref if there is a parent.""" """this method will be overwritten with a weakref if there is a parent."""
@ -72,13 +94,13 @@ class BasePostScriptFontHintValues(object):
self.getParent = weakref.ref(parent) self.getParent = weakref.ref(parent)
def fromDict(self, data): def fromDict(self, data):
for name in self._attrs: for name in self._attributeNames:
if name in data: if name in data:
setattr(self, name, data[name]) setattr(self, name, data[name])
def asDict(self): def asDict(self):
d = {} d = {}
for name in self._attrs: for name in self._attributeNames:
try: try:
value = getattr(self, name) value = getattr(self, name)
except AttributeError: except AttributeError:
@ -88,9 +110,171 @@ class BasePostScriptFontHintValues(object):
d[name] = getattr(self, name) d[name] = getattr(self, name)
return d return d
def update(self, other):
assert isinstance(other, BasePostScriptFontHintValues)
for name in self._attributeNames.keys():
v = getattr(other, name)
if v is not None:
setattr(self, name, v)
def __repr__(self): def __repr__(self):
return "<PostScript Font Hints Values>" return "<PostScript Font Hints Values>"
def copy(self, aParent=None):
"""Duplicate this object. Pass an object for parenting if you want."""
n = self.__class__(data=self.asDict())
if aParent is not None:
n.setParent(aParent)
elif self.getParent() is not None:
n.setParent(self.getParent())
dont = ['getParent']
for k in self.__dict__.keys():
if k in dont:
continue
dup = copy.deepcopy(self.__dict__[k])
setattr(n, k, dup)
return n
def round(self):
"""Round the values to reasonable values.
- blueScale is not rounded, it is a float
- forceBold is set to False if -0.5 < value < 0.5. Otherwise it will be True.
- blueShift, blueFuzz are rounded to int
- stems are rounded to int
- blues are rounded to int
"""
for name, values in self._attributeNames.items():
if name == "blueScale":
continue
elif name == "forceBold":
v = getattr(self, name)
if v is None:
continue
if -0.5 <= v <= 0.5:
setattr(self, name, False)
else:
setattr(self, name, True)
elif name in ['blueFuzz', 'blueShift']:
v = getattr(self, name)
if v is None:
continue
setattr(self, name, int(round(v)))
elif name in ['hStems', 'vStems']:
v = getattr(self, name)
if v is None:
continue
new = []
for n in v:
new.append(int(round(n)))
setattr(self, name, new)
else:
v = getattr(self, name)
if v is None:
continue
new = []
for n in v:
new.append([int(round(m)) for m in n])
setattr(self, name, new)
# math operations for psHint object
def __add__(self, other):
assert isinstance(other, BasePostScriptFontHintValues)
copied = self.copy()
self._processMathOne(copied, other, add)
return copied
def __sub__(self, other):
assert isinstance(other, BasePostScriptFontHintValues)
copied = self.copy()
self._processMathOne(copied, other, sub)
return copied
def __mul__(self, factor):
if isinstance(factor, tuple):
factor = factor[0]
copiedInfo = self.copy()
self._processMathTwo(copiedInfo, factor, mul)
return copiedInfo
__rmul__ = __mul__
def __div__(self, factor):
if isinstance(factor, tuple):
factor = factor[0]
copiedInfo = self.copy()
self._processMathTwo(copiedInfo, factor, mul)
return copiedInfo
__rdiv__ = __div__
def _processMathOne(self, copied, other, funct):
for name, values in self._attributeNames.items():
a = None
b = None
v = None
if hasattr(copied, name):
a = getattr(copied, name)
if hasattr(other, name):
b = getattr(other, name)
if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:
# process single values
if a is not None and b is not None:
v = funct(a, b)
elif a is not None and b is None:
v = a
elif b is not None and a is None:
v = b
if v is not None:
setattr(copied, name, v)
elif name in ['hStems', 'vStems']:
if a is not None and b is not None:
l = min(len(a), len(b))
v = [funct(a[i], b[i]) for i in range(l)]
if v is not None:
setattr(copied, name, v)
else:
if a is not None and b is not None:
l = min(len(a), len(b))
for i in range(l):
if v is None:
v = []
ai = a[i]
bi = b[i]
l2 = min(len(ai), len(bi))
v2 = [funct(ai[j], bi[j]) for j in range(l2)]
v.append(v2)
if v is not None:
setattr(copied, name, v)
def _processMathTwo(self, copied, factor, funct):
for name, values in self._attributeNames.items():
a = None
b = None
v = None
if hasattr(copied, name):
a = getattr(copied, name)
if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:
# process single values
if a is not None:
v = funct(a, factor)
if v is not None:
setattr(copied, name, v)
elif name in ['hStems', 'vStems']:
if a is not None:
v = [funct(a[i], factor) for i in range(len(a))]
if v is not None:
setattr(copied, name, v)
else:
if a is not None:
for i in range(len(a)):
if v is None:
v = []
v2 = [funct(a[i][j], factor) for j in range(len(a[i]))]
v.append(v2)
if v is not None:
setattr(copied, name, v)
class RoboFabInterpolationError(Exception): pass class RoboFabInterpolationError(Exception): pass