psHint object for glyphs. Support for math. Support for (x,y) factors for multiplication and division. Math operations on glyph take psHints into account. Refer to robofab/test/test_psHints.py
git-svn-id: http://svn.robofab.com/trunk@49 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c
This commit is contained in:
parent
bb9b773615
commit
89a3dcd1a6
@ -59,24 +59,10 @@ def issequence(x):
|
|||||||
return hasattr(x, '__getitem__')
|
return hasattr(x, '__getitem__')
|
||||||
|
|
||||||
|
|
||||||
class BasePostScriptFontHintValues(object):
|
|
||||||
""" Base class for font-level postscript hinting information.
|
|
||||||
Blues values, stem values.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_attributeNames = {
|
class BasePostScriptHintValues(object):
|
||||||
# some of these values can have only a certain number of elements
|
""" Base class for postscript hinting information.
|
||||||
'blueFuzz': {'default': None, 'max':1},
|
"""
|
||||||
'blueScale': {'default': None, 'max':1},
|
|
||||||
'blueShift': {'default': None, 'max':1},
|
|
||||||
'forceBold': {'default': None, 'max':1},
|
|
||||||
'blueValues': {'default': None, 'max':7},
|
|
||||||
'otherBlues': {'default': None, 'max':5},
|
|
||||||
'familyBlues': {'default': None, 'max':7},
|
|
||||||
'familyOtherBlues': {'default': None, 'max':5},
|
|
||||||
'vStems': {'default': None, 'max':6},
|
|
||||||
'hStems': {'default': None, 'max':11},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, data=None):
|
def __init__(self, data=None):
|
||||||
if data is not None:
|
if data is not None:
|
||||||
@ -93,6 +79,26 @@ class BasePostScriptFontHintValues(object):
|
|||||||
import weakref
|
import weakref
|
||||||
self.getParent = weakref.ref(parent)
|
self.getParent = weakref.ref(parent)
|
||||||
|
|
||||||
|
def isEmpty(self):
|
||||||
|
"""Check all attrs and decide if they're all empty."""
|
||||||
|
empty = True
|
||||||
|
for name in self._attributeNames:
|
||||||
|
if getattr(self, name):
|
||||||
|
empty = False
|
||||||
|
break
|
||||||
|
return empty
|
||||||
|
|
||||||
|
def _loadFromLib(self, lib):
|
||||||
|
data = lib.get(postScriptHintDataLibKey)
|
||||||
|
if data is not None:
|
||||||
|
self.fromDict(data)
|
||||||
|
|
||||||
|
def _saveToLib(self, lib):
|
||||||
|
parent = self.getParent()
|
||||||
|
if parent is not None:
|
||||||
|
parent.setChanged(True)
|
||||||
|
lib[postScriptHintDataLibKey] = self.asDict()
|
||||||
|
|
||||||
def fromDict(self, data):
|
def fromDict(self, data):
|
||||||
for name in self._attributeNames:
|
for name in self._attributeNames:
|
||||||
if name in data:
|
if name in data:
|
||||||
@ -106,19 +112,19 @@ class BasePostScriptFontHintValues(object):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
print "%s attribute not supported"%name
|
print "%s attribute not supported"%name
|
||||||
continue
|
continue
|
||||||
if value is not None or not value:
|
if value:
|
||||||
d[name] = getattr(self, name)
|
d[name] = getattr(self, name)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def update(self, other):
|
def update(self, other):
|
||||||
assert isinstance(other, BasePostScriptFontHintValues)
|
assert isinstance(other, BasePostScriptHintValues)
|
||||||
for name in self._attributeNames.keys():
|
for name in self._attributeNames.keys():
|
||||||
v = getattr(other, name)
|
v = getattr(other, name)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
setattr(self, name, v)
|
setattr(self, name, v)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<PostScript Font Hints Values>"
|
return "<Base PS Hint Data>"
|
||||||
|
|
||||||
def copy(self, aParent=None):
|
def copy(self, aParent=None):
|
||||||
"""Duplicate this object. Pass an object for parenting if you want."""
|
"""Duplicate this object. Pass an object for parenting if you want."""
|
||||||
@ -135,6 +141,154 @@ class BasePostScriptFontHintValues(object):
|
|||||||
setattr(n, k, dup)
|
setattr(n, k, dup)
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
# math operations for psHint object
|
||||||
|
# Note: math operations can change integers to floats.
|
||||||
|
def __add__(self, other):
|
||||||
|
assert isinstance(other, BasePostScriptHintValues)
|
||||||
|
copied = self.copy()
|
||||||
|
self._processMathOne(copied, other, add)
|
||||||
|
return copied
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
assert isinstance(other, BasePostScriptHintValues)
|
||||||
|
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, div)
|
||||||
|
return copiedInfo
|
||||||
|
|
||||||
|
__rdiv__ = __div__
|
||||||
|
|
||||||
|
|
||||||
|
class BasePostScriptGlyphHintValues(BasePostScriptHintValues):
|
||||||
|
""" Base class for glyph-level postscript hinting information.
|
||||||
|
vStems, hStems
|
||||||
|
"""
|
||||||
|
_attributeNames = {
|
||||||
|
# some of these values can have only a certain number of elements
|
||||||
|
'vHints': {'default': None, 'max':100, 'isVertical':True},
|
||||||
|
'hHints': {'default': None, 'max':100, 'isVertical':False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, data=None):
|
||||||
|
if data is not None:
|
||||||
|
self.fromDict(data)
|
||||||
|
else:
|
||||||
|
for name in self._attributeNames.keys():
|
||||||
|
setattr(self, name, self._attributeNames[name]['default'])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<PostScript Glyph Hints Values>"
|
||||||
|
|
||||||
|
def round(self):
|
||||||
|
"""Round the values to reasonable values.
|
||||||
|
- stems are rounded to int
|
||||||
|
"""
|
||||||
|
for name, values in self._attributeNames.items():
|
||||||
|
v = getattr(self, name)
|
||||||
|
if v is None:
|
||||||
|
continue
|
||||||
|
new = []
|
||||||
|
for n in v:
|
||||||
|
new.append((int(round(n[0])), int(round(n[1]))))
|
||||||
|
setattr(self, name, new)
|
||||||
|
|
||||||
|
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 a is not None and b is not None:
|
||||||
|
if len(a) != len(b):
|
||||||
|
# can't do math with non matching zones
|
||||||
|
continue
|
||||||
|
l = len(a)
|
||||||
|
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
|
||||||
|
isVertical = self._attributeNames[name]['isVertical']
|
||||||
|
splitFactor = factor
|
||||||
|
if isinstance(factor, tuple):
|
||||||
|
#print "mathtwo", name, funct, factor, isVertical
|
||||||
|
if isVertical:
|
||||||
|
splitFactor = factor[1]
|
||||||
|
else:
|
||||||
|
splitFactor = factor[0]
|
||||||
|
if hasattr(copied, name):
|
||||||
|
a = getattr(copied, name)
|
||||||
|
if a is not None:
|
||||||
|
for i in range(len(a)):
|
||||||
|
if v is None:
|
||||||
|
v = []
|
||||||
|
v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))]
|
||||||
|
v.append(v2)
|
||||||
|
if v is not None:
|
||||||
|
setattr(copied, name, v)
|
||||||
|
|
||||||
|
|
||||||
|
class BasePostScriptFontHintValues(BasePostScriptHintValues):
|
||||||
|
""" Base class for font-level postscript hinting information.
|
||||||
|
Blues values, stem values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_attributeNames = {
|
||||||
|
# some of these values can have only a certain number of elements
|
||||||
|
# default: what the value should be when initialised
|
||||||
|
# max: the maximum number of items this attribute is allowed to have
|
||||||
|
# isVertical: the vertical relevance
|
||||||
|
'blueFuzz': {'default': None, 'max':1, 'isVertical':True},
|
||||||
|
'blueScale': {'default': None, 'max':1, 'isVertical':True},
|
||||||
|
'blueShift': {'default': None, 'max':1, 'isVertical':True},
|
||||||
|
'forceBold': {'default': None, 'max':1, 'isVertical':False},
|
||||||
|
'blueValues': {'default': None, 'max':7, 'isVertical':True},
|
||||||
|
'otherBlues': {'default': None, 'max':5, 'isVertical':True},
|
||||||
|
'familyBlues': {'default': None, 'max':7, 'isVertical':True},
|
||||||
|
'familyOtherBlues': {'default': None, 'max':5, 'isVertical':True},
|
||||||
|
'vStems': {'default': None, 'max':6, 'isVertical':True},
|
||||||
|
'hStems': {'default': None, 'max':11, 'isVertical':False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, data=None):
|
||||||
|
if data is not None:
|
||||||
|
self.fromDict(data)
|
||||||
|
else:
|
||||||
|
for name in self._attributeNames.keys():
|
||||||
|
setattr(self, name, self._attributeNames[name]['default'])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<PostScript Font Hints Values>"
|
||||||
|
|
||||||
def round(self):
|
def round(self):
|
||||||
"""Round the values to reasonable values.
|
"""Round the values to reasonable values.
|
||||||
- blueScale is not rounded, it is a float
|
- blueScale is not rounded, it is a float
|
||||||
@ -176,37 +330,6 @@ class BasePostScriptFontHintValues(object):
|
|||||||
new.append([int(round(m)) for m in n])
|
new.append([int(round(m)) for m in n])
|
||||||
setattr(self, name, new)
|
setattr(self, name, new)
|
||||||
|
|
||||||
# math operations for psHint object
|
|
||||||
# Note: math operations can change integers to floats.
|
|
||||||
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, div)
|
|
||||||
return copiedInfo
|
|
||||||
|
|
||||||
__rdiv__ = __div__
|
|
||||||
|
|
||||||
def _processMathOne(self, copied, other, funct):
|
def _processMathOne(self, copied, other, funct):
|
||||||
for name, values in self._attributeNames.items():
|
for name, values in self._attributeNames.items():
|
||||||
@ -217,6 +340,15 @@ class BasePostScriptFontHintValues(object):
|
|||||||
a = getattr(copied, name)
|
a = getattr(copied, name)
|
||||||
if hasattr(other, name):
|
if hasattr(other, name):
|
||||||
b = getattr(other, name)
|
b = getattr(other, name)
|
||||||
|
# handle isVertical factors:
|
||||||
|
# Values with vertical relevance should respond to vertical scalars.
|
||||||
|
# Values with horizontal relevance should respond to horizontal scalars.
|
||||||
|
isVertical = self._attributeNames[name]['isVertical']
|
||||||
|
if isinstance(factor, tuple):
|
||||||
|
if isVertical:
|
||||||
|
factor = factor[1]
|
||||||
|
else:
|
||||||
|
factor = factor[0]
|
||||||
if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:
|
if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:
|
||||||
# process single values
|
# process single values
|
||||||
if a is not None and b is not None:
|
if a is not None and b is not None:
|
||||||
@ -229,13 +361,19 @@ class BasePostScriptFontHintValues(object):
|
|||||||
setattr(copied, name, v)
|
setattr(copied, name, v)
|
||||||
elif name in ['hStems', 'vStems']:
|
elif name in ['hStems', 'vStems']:
|
||||||
if a is not None and b is not None:
|
if a is not None and b is not None:
|
||||||
l = min(len(a), len(b))
|
if len(a) != len(b):
|
||||||
|
# can't do math with non matching zones
|
||||||
|
continue
|
||||||
|
l = len(a)
|
||||||
v = [funct(a[i], b[i]) for i in range(l)]
|
v = [funct(a[i], b[i]) for i in range(l)]
|
||||||
if v is not None:
|
if v is not None:
|
||||||
setattr(copied, name, v)
|
setattr(copied, name, v)
|
||||||
else:
|
else:
|
||||||
if a is not None and b is not None:
|
if a is not None and b is not None:
|
||||||
l = min(len(a), len(b))
|
if len(a) != len(b):
|
||||||
|
# can't do math with non matching zones
|
||||||
|
continue
|
||||||
|
l = len(a)
|
||||||
for i in range(l):
|
for i in range(l):
|
||||||
if v is None:
|
if v is None:
|
||||||
v = []
|
v = []
|
||||||
@ -254,15 +392,22 @@ class BasePostScriptFontHintValues(object):
|
|||||||
v = None
|
v = None
|
||||||
if hasattr(copied, name):
|
if hasattr(copied, name):
|
||||||
a = getattr(copied, name)
|
a = getattr(copied, name)
|
||||||
|
splitFactor = factor
|
||||||
|
isVertical = self._attributeNames[name]['isVertical']
|
||||||
|
if isinstance(factor, tuple):
|
||||||
|
if isVertical:
|
||||||
|
splitFactor = factor[1]
|
||||||
|
else:
|
||||||
|
splitFactor = factor[0]
|
||||||
if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:
|
if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:
|
||||||
# process single values
|
# process single values
|
||||||
if a is not None:
|
if a is not None:
|
||||||
v = funct(a, factor)
|
v = funct(a, splitFactor)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
setattr(copied, name, v)
|
setattr(copied, name, v)
|
||||||
elif name in ['hStems', 'vStems']:
|
elif name in ['hStems', 'vStems']:
|
||||||
if a is not None:
|
if a is not None:
|
||||||
v = [funct(a[i], factor) for i in range(len(a))]
|
v = [funct(a[i], splitFactor) for i in range(len(a))]
|
||||||
if v is not None:
|
if v is not None:
|
||||||
setattr(copied, name, v)
|
setattr(copied, name, v)
|
||||||
else:
|
else:
|
||||||
@ -270,7 +415,7 @@ class BasePostScriptFontHintValues(object):
|
|||||||
for i in range(len(a)):
|
for i in range(len(a)):
|
||||||
if v is None:
|
if v is None:
|
||||||
v = []
|
v = []
|
||||||
v2 = [funct(a[i][j], factor) for j in range(len(a[i]))]
|
v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))]
|
||||||
v.append(v2)
|
v.append(v2)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
setattr(copied, name, v)
|
setattr(copied, name, v)
|
||||||
@ -811,6 +956,9 @@ class BaseGlyph(RBaseObject):
|
|||||||
filterPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name)
|
filterPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name)
|
||||||
filterPen.endPath()
|
filterPen.endPath()
|
||||||
newGlyph.width = data['width']
|
newGlyph.width = data['width']
|
||||||
|
psHints = data.get('psHints')
|
||||||
|
if psHints is not None:
|
||||||
|
newGlyph.psHints.update(psHints)
|
||||||
#
|
#
|
||||||
return newGlyph
|
return newGlyph
|
||||||
|
|
||||||
@ -998,6 +1146,10 @@ class BaseGlyph(RBaseObject):
|
|||||||
factor = (factor, factor)
|
factor = (factor, factor)
|
||||||
data = self._processMathTwo(factor, mulPt)
|
data = self._processMathTwo(factor, mulPt)
|
||||||
data['width'] = self.width * factor[0]
|
data['width'] = self.width * factor[0]
|
||||||
|
# psHints
|
||||||
|
if not self.psHints.isEmpty():
|
||||||
|
newPsHints = self.psHints * factor
|
||||||
|
data['psHints'] = newPsHints
|
||||||
return self._setMathData(data)
|
return self._setMathData(data)
|
||||||
|
|
||||||
__rmul__ = __mul__
|
__rmul__ = __mul__
|
||||||
@ -1263,6 +1415,8 @@ class BaseGlyph(RBaseObject):
|
|||||||
dup = []
|
dup = []
|
||||||
for i in self.anchors:
|
for i in self.anchors:
|
||||||
dup.append(i.copy(n))
|
dup.append(i.copy(n))
|
||||||
|
elif k == "psHints":
|
||||||
|
dup = self.psHints.copy()
|
||||||
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
|
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
|
||||||
dup = self.__dict__[k].copy(n)
|
dup = self.__dict__[k].copy(n)
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user