Changeset 44

Show
Ignore:
Timestamp:
02/23/08 13:44:09 (9 months ago)
Author:
erik
Message:

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.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/robofab/Lib/robofab/objects/objectsBase.py

    r34 r44  
    1 """     
     1"""  
    22Base classes for the Unified Font Objects (UFO), 
    33a series of classes that deal with fonts, glyphs, 
     
    1717 
    1818from __future__ import generators 
     19from __future__ import division 
    1920 
    2021 
     
    4041postScriptHintDataLibKey = "org.robofab.postScriptHintData" 
    4142 
     43# from http://svn.typesupply.com/packages/fontMath/mathFunctions.py 
     44 
     45def add(v1, v2): 
     46        return v1 + v2 
     47 
     48def sub(v1, v2): 
     49        return v1 - v2 
     50 
     51def mul(v, f): 
     52        return v * f 
     53 
     54def div(v, f): 
     55        return v / f 
     56         
     57def issequence(x): 
     58        "Is x a sequence? We say it is if it has a __getitem__ method." 
     59        return hasattr(x, '__getitem__') 
     60 
    4261 
    4362class BasePostScriptFontHintValues(object): 
     
    4665        """ 
    4766         
    48         _attrs = { 
     67        _attributeNames = { 
    4968                # some of these values can have only a certain number of elements 
    50                 'blueFuzz':   {'default': None, 'max':1}, 
    51                 'blueScale': {'default': None, 'max':1}, 
    52                 'blueShift': {'default': None, 'max':1}, 
    53                 'forceBold': {'default': None, 'max':1}, 
    54                 'blueValues':  {'default': None, 'max':13}, 
    55                 'otherBlues':  {'default': None, 'max':9}, 
    56                 'familyBlues':         {'default': None, 'max':13}, 
    57                 'familyOtherBlues': {'default': None, 'max':9}, 
    58                 'vStems':              {'default': None, 'max':11}, 
    59                 'hStems':             {'default': None, 'max':11}, 
     69                'blueFuzz':           {'default': None, 'max':1}, 
     70                'blueScale':  {'default': None, 'max':1}, 
     71                'blueShift':  {'default': None, 'max':1}, 
     72                'forceBold':  {'default': None, 'max':1}, 
     73                'blueValues':  {'default': None, 'max':7}, 
     74                'otherBlues':  {'default': None, 'max':5}, 
     75                'familyBlues': {'default': None, 'max':7}, 
     76                'familyOtherBlues': {'default': None, 'max':5}, 
     77                'vStems':              {'default': None, 'max':6}, 
     78                'hStems':             {'default': None, 'max':11}, 
    6079                } 
    6180                 
    62         def __init__(self): 
    63                 for name in self._attrs.keys(): 
    64                         setattr(self, name, self._attrs[name]) 
     81        def __init__(self, data=None): 
     82                if data is not None: 
     83                        self.fromDict(data) 
     84                else: 
     85                        for name in self._attributeNames.keys(): 
     86                                setattr(self, name, self._attributeNames[name]['default']) 
    6587                 
    6688        def getParent(self): 
     
    7395 
    7496        def fromDict(self, data): 
    75                 for name in self._attrs: 
     97                for name in self._attributeNames: 
    7698                        if name in data: 
    7799                                setattr(self, name, data[name]) 
     
    79101        def asDict(self): 
    80102                d = {} 
    81                 for name in self._attrs: 
     103                for name in self._attributeNames: 
    82104                        try: 
    83105                                value = getattr(self, name) 
     
    89111                return d 
    90112         
     113        def update(self, other): 
     114                assert isinstance(other, BasePostScriptFontHintValues) 
     115                for name in self._attributeNames.keys(): 
     116                        v = getattr(other, name) 
     117                        if v is not None: 
     118                                setattr(self, name, v) 
     119 
    91120        def __repr__(self): 
    92121                return "<PostScript Font Hints Values>" 
     122 
     123        def copy(self, aParent=None): 
     124                """Duplicate this object. Pass an object for parenting if you want.""" 
     125                n = self.__class__(data=self.asDict()) 
     126                if aParent is not None: 
     127                        n.setParent(aParent) 
     128                elif self.getParent() is not None: 
     129                        n.setParent(self.getParent()) 
     130                dont = ['getParent'] 
     131                for k in self.__dict__.keys(): 
     132                        if k in dont: 
     133                                continue 
     134                        dup = copy.deepcopy(self.__dict__[k]) 
     135                        setattr(n, k, dup) 
     136                return n 
     137 
     138        def round(self): 
     139                """Round the values to reasonable values. 
     140                        - blueScale is not rounded, it is a float 
     141                        - forceBold is set to False if -0.5 < value < 0.5. Otherwise it will be True. 
     142                        - blueShift, blueFuzz are rounded to int 
     143                        - stems are rounded to int 
     144                        - blues are rounded to int 
     145                """ 
     146                for name, values in self._attributeNames.items(): 
     147                        if name == "blueScale": 
     148                                continue 
     149                        elif name == "forceBold": 
     150                                v = getattr(self, name) 
     151                                if v is None: 
     152                                        continue 
     153                                if -0.5 <= v <= 0.5: 
     154                                        setattr(self, name, False) 
     155                                else: 
     156                                        setattr(self, name, True) 
     157                        elif name in ['blueFuzz', 'blueShift']: 
     158                                v = getattr(self, name) 
     159                                if v is None: 
     160                                        continue 
     161                                setattr(self, name, int(round(v))) 
     162                        elif name in ['hStems', 'vStems']: 
     163                                v = getattr(self, name) 
     164                                if v is None: 
     165                                        continue 
     166                                new = [] 
     167                                for n in v: 
     168                                        new.append(int(round(n))) 
     169                                setattr(self, name, new) 
     170                        else: 
     171                                v = getattr(self, name) 
     172                                if v is None: 
     173                                        continue 
     174                                new = [] 
     175                                for n in v: 
     176                                        new.append([int(round(m)) for m in n]) 
     177                                setattr(self, name, new) 
     178         
     179        # math operations for psHint object 
     180        def __add__(self, other): 
     181                assert isinstance(other, BasePostScriptFontHintValues) 
     182                copied = self.copy() 
     183                self._processMathOne(copied, other, add) 
     184                return copied 
     185 
     186        def __sub__(self, other): 
     187                assert isinstance(other, BasePostScriptFontHintValues) 
     188                copied = self.copy() 
     189                self._processMathOne(copied, other, sub) 
     190                return copied 
     191 
     192        def __mul__(self, factor): 
     193                if isinstance(factor, tuple): 
     194                        factor = factor[0] 
     195                copiedInfo = self.copy() 
     196                self._processMathTwo(copiedInfo, factor, mul) 
     197                return copiedInfo 
     198 
     199        __rmul__ = __mul__ 
     200 
     201        def __div__(self, factor): 
     202                if isinstance(factor, tuple): 
     203                        factor = factor[0] 
     204                copiedInfo = self.copy() 
     205                self._processMathTwo(copiedInfo, factor, mul) 
     206                return copiedInfo 
     207 
     208        __rdiv__ = __div__ 
     209         
     210        def _processMathOne(self, copied, other, funct): 
     211                for name, values in self._attributeNames.items(): 
     212                        a = None 
     213                        b = None 
     214                        v = None 
     215                        if hasattr(copied, name): 
     216                                a = getattr(copied, name) 
     217                        if hasattr(other, name): 
     218                                b = getattr(other, name) 
     219                        if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']: 
     220                                # process single values 
     221                                if a is not None and b is not None: 
     222                                        v = funct(a, b) 
     223                                elif a is not None and b is None: 
     224                                        v = a 
     225                                elif b is not None and a is None: 
     226                                        v = b 
     227                                if v is not None: 
     228                                        setattr(copied, name, v) 
     229                        elif name in ['hStems', 'vStems']: 
     230                                if a is not None and b is not None: 
     231                                        l = min(len(a), len(b)) 
     232                                        v = [funct(a[i], b[i]) for i in range(l)] 
     233                                if v is not None: 
     234                                        setattr(copied, name, v) 
     235                        else: 
     236                                if a is not None and b is not None: 
     237                                        l = min(len(a), len(b)) 
     238                                        for i in range(l): 
     239                                                if v is None: 
     240                                                        v = [] 
     241                                                ai = a[i] 
     242                                                bi = b[i] 
     243                                                l2 = min(len(ai), len(bi)) 
     244                                                v2 = [funct(ai[j], bi[j]) for j in range(l2)] 
     245                                                v.append(v2) 
     246                                if v is not None: 
     247                                        setattr(copied, name, v) 
     248 
     249        def _processMathTwo(self, copied, factor, funct): 
     250                for name, values in self._attributeNames.items(): 
     251                        a = None 
     252                        b = None 
     253                        v = None 
     254                        if hasattr(copied, name): 
     255                                a = getattr(copied, name) 
     256                        if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']: 
     257                                # process single values 
     258                                if a is not None: 
     259                                        v = funct(a, factor) 
     260                                if v is not None: 
     261                                        setattr(copied, name, v) 
     262                        elif name in ['hStems', 'vStems']: 
     263                                if a is not None: 
     264                                        v = [funct(a[i], factor) for i in range(len(a))] 
     265                                if v is not None: 
     266                                        setattr(copied, name, v) 
     267                        else: 
     268                                if a is not None: 
     269                                        for i in range(len(a)): 
     270                                                if v is None: 
     271                                                        v = [] 
     272                                                v2 = [funct(a[i][j], factor) for j in range(len(a[i]))] 
     273                                                v.append(v2) 
     274                                if v is not None: 
     275                                        setattr(copied, name, v) 
     276 
    93277 
    94278 
     
    13191503                It returns a list of lists containing bool values 
    13201504                that indicate the black (True) or white (False) 
    1321                 value of that particular cell.  These lists are 
     1505                value of that particular cell. These lists are 
    13221506                arranged from top to bottom of the glyph and 
    13231507                proceed from left to right.