Changeset 171

Show
Ignore:
Timestamp:
02/28/09 09:47:24 (3 years ago)
Author:
tal
Message:

Merged ufo2 branch r95:170 into the trunk.

Files:

Legend:

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

    r72 r171  
    7676 
    7777 
    78 numberVersion = (1, 1, "develop", 3
    79 version = "1.1.3
     78numberVersion = (1, 2, "develop", 0
     79version = "1.2.0d
  • trunk/Lib/robofab/objects/objectsBase.py

    r59 r171  
    1919from __future__ import division 
    2020 
    21  
     21from warnings import warn 
     22import math 
     23import copy 
     24 
     25from robofab import ufoLib 
    2226from robofab import RoboFabError 
    2327from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect 
    2428from fontTools.pens.basePen import AbstractPen 
    25 import math 
    26 import copy 
     29 
    2730 
    2831#constants for dealing with segments, points and bPoints 
     
    147150                return n 
    148151 
    149         # math operations for psHint object 
    150         # Note: math operations can change integers to floats. 
    151         def __add__(self, other): 
    152                 assert isinstance(other, BasePostScriptHintValues) 
    153                 copied = self.copy() 
    154                 self._processMathOne(copied, other, add) 
    155                 return copied 
    156  
    157         def __sub__(self, other): 
    158                 assert isinstance(other, BasePostScriptHintValues) 
    159                 copied = self.copy() 
    160                 self._processMathOne(copied, other, sub) 
    161                 return copied 
    162  
    163         def __mul__(self, factor): 
    164                 #if isinstance(factor, tuple): 
    165                 #       factor = factor[0] 
    166                 copiedInfo = self.copy() 
    167                 self._processMathTwo(copiedInfo, factor, mul) 
    168                 return copiedInfo 
    169  
    170         __rmul__ = __mul__ 
    171  
    172         def __div__(self, factor): 
    173                 #if isinstance(factor, tuple): 
    174                 #       factor = factor[0] 
    175                 copiedInfo = self.copy() 
    176                 self._processMathTwo(copiedInfo, factor, div) 
    177                 return copiedInfo 
    178  
    179         __rdiv__ = __div__ 
    180          
    181          
    182152class BasePostScriptGlyphHintValues(BasePostScriptHintValues): 
    183153        """ Base class for glyph-level postscript hinting information. 
     
    212182                                new.append((int(round(n[0])), int(round(n[1])))) 
    213183                        setattr(self, name, new) 
     184 
     185        # math operations for psHint object 
     186        # Note: math operations can change integers to floats. 
     187        def __add__(self, other): 
     188                assert isinstance(other, BasePostScriptHintValues) 
     189                copied = self.copy() 
     190                self._processMathOne(copied, other, add) 
     191                return copied 
     192 
     193        def __sub__(self, other): 
     194                assert isinstance(other, BasePostScriptHintValues) 
     195                copied = self.copy() 
     196                self._processMathOne(copied, other, sub) 
     197                return copied 
     198 
     199        def __mul__(self, factor): 
     200                #if isinstance(factor, tuple): 
     201                #       factor = factor[0] 
     202                copiedInfo = self.copy() 
     203                self._processMathTwo(copiedInfo, factor, mul) 
     204                return copiedInfo 
     205 
     206        __rmul__ = __mul__ 
     207 
     208        def __div__(self, factor): 
     209                #if isinstance(factor, tuple): 
     210                #       factor = factor[0] 
     211                copiedInfo = self.copy() 
     212                self._processMathTwo(copiedInfo, factor, div) 
     213                return copiedInfo 
     214 
     215        __rdiv__ = __div__ 
    214216 
    215217        def _processMathOne(self, copied, other, funct): 
     
    288290                if data is not None: 
    289291                        self.fromDict(data) 
    290                 else: 
    291                         for name in self._attributeNames.keys(): 
    292                                 setattr(self, name, self._attributeNames[name]['default']) 
    293292 
    294293        def __repr__(self): 
    295294                return "<PostScript Font Hints Values>" 
     295 
     296        # route attribute calls to info object 
     297 
     298        def _bluesToPairs(self, values): 
     299                values.sort() 
     300                finalValues = [] 
     301                for value in values: 
     302                        if not finalValues or len(finalValues[-1]) == 2: 
     303                                finalValues.append([]) 
     304                        finalValues[-1].append(value) 
     305                return finalValues 
     306 
     307        def _bluesFromPairs(self, values): 
     308                finalValues = [] 
     309                for value1, value2 in values: 
     310                        finalValues.append(value1) 
     311                        finalValues.append(value2) 
     312                finalValues.sort() 
     313                return finalValues 
     314 
     315        def _get_blueValues(self): 
     316                values = self.getParent().info.postscriptBlueValues 
     317                if values is None: 
     318                        values = [] 
     319                values = self._bluesToPairs(values) 
     320                return values 
     321 
     322        def _set_blueValues(self, values): 
     323                if values is None: 
     324                        values = [] 
     325                values = self._bluesFromPairs(values) 
     326                self.getParent().info.postscriptBlueValues = values 
     327 
     328        blueValues = property(_get_blueValues, _set_blueValues) 
     329 
     330        def _get_otherBlues(self): 
     331                values = self.getParent().info.postscriptOtherBlues 
     332                if values is None: 
     333                        values = [] 
     334                values = self._bluesToPairs(values) 
     335                return values 
     336 
     337        def _set_otherBlues(self, values): 
     338                if values is None: 
     339                        values = [] 
     340                values = self._bluesFromPairs(values) 
     341                self.getParent().info.postscriptOtherBlues = values 
     342 
     343        otherBlues = property(_get_otherBlues, _set_otherBlues) 
     344 
     345        def _get_familyBlues(self): 
     346                values = self.getParent().info.postscriptFamilyBlues 
     347                if values is None: 
     348                        values = [] 
     349                values = self._bluesToPairs(values) 
     350                return values 
     351 
     352        def _set_familyBlues(self, values): 
     353                if values is None: 
     354                        values = [] 
     355                values = self._bluesFromPairs(values) 
     356                self.getParent().info.postscriptFamilyBlues = values 
     357 
     358        familyBlues = property(_get_familyBlues, _set_familyBlues) 
     359 
     360        def _get_familyOtherBlues(self): 
     361                values = self.getParent().info.postscriptFamilyOtherBlues 
     362                if values is None: 
     363                        values = [] 
     364                values = self._bluesToPairs(values) 
     365                return values 
     366 
     367        def _set_familyOtherBlues(self, values): 
     368                if values is None: 
     369                        values = [] 
     370                values = self._bluesFromPairs(values) 
     371                self.getParent().info.postscriptFamilyOtherBlues = values 
     372 
     373        familyOtherBlues = property(_get_familyOtherBlues, _set_familyOtherBlues) 
     374 
     375        def _get_vStems(self): 
     376                return self.getParent().info.postscriptStemSnapV 
     377 
     378        def _set_vStems(self, value): 
     379                if value is None: 
     380                        value = [] 
     381                self.getParent().info.postscriptStemSnapV = list(value) 
     382 
     383        vStems = property(_get_vStems, _set_vStems) 
     384 
     385        def _get_hStems(self): 
     386                return self.getParent().info.postscriptStemSnapH 
     387 
     388        def _set_hStems(self, value): 
     389                if value is None: 
     390                        value = [] 
     391                self.getParent().info.postscriptStemSnapH = list(value) 
     392 
     393        hStems = property(_get_hStems, _set_hStems) 
     394 
     395        def _get_blueScale(self): 
     396                return self.getParent().info.postscriptBlueScale 
     397 
     398        def _set_blueScale(self, value): 
     399                self.getParent().info.postscriptBlueScale = value 
     400 
     401        blueScale = property(_get_blueScale, _set_blueScale) 
     402 
     403        def _get_blueShift(self): 
     404                return self.getParent().info.postscriptBlueShift 
     405 
     406        def _set_blueShift(self, value): 
     407                self.getParent().info.postscriptBlueShift = value 
     408 
     409        blueShift = property(_get_blueShift, _set_blueShift) 
     410 
     411        def _get_blueFuzz(self): 
     412                return self.getParent().info.postscriptBlueFuzz 
     413 
     414        def _set_blueFuzz(self, value): 
     415                self.getParent().info.postscriptBlueFuzz = value 
     416 
     417        blueFuzz = property(_get_blueFuzz, _set_blueFuzz) 
     418 
     419        def _get_forceBold(self): 
     420                return self.getParent().info.postscriptForceBold 
     421 
     422        def _set_forceBold(self, value): 
     423                self.getParent().info.postscriptForceBold = value 
     424 
     425        forceBold = property(_get_forceBold, _set_forceBold) 
    296426 
    297427        def round(self): 
     
    335465                                        new.append([int(round(m)) for m in n]) 
    336466                                setattr(self, name, new) 
    337          
    338          
    339         def _processMathOne(self, copied, other, funct): 
    340                 for name, values in self._attributeNames.items(): 
    341                         a = None 
    342                         b = None 
    343                         v = None 
    344                         if hasattr(copied, name): 
    345                                 a = getattr(copied, name) 
    346                         if hasattr(other, name): 
    347                                 b = getattr(other, name) 
    348                         if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']: 
    349                                 # process single values 
    350                                 if a is not None and b is not None: 
    351                                         v = funct(a, b) 
    352                                 elif a is not None and b is None: 
    353                                         v = a 
    354                                 elif b is not None and a is None: 
    355                                         v = b 
    356                                 if v is not None: 
    357                                         setattr(copied, name, v) 
    358                         elif name in ['hStems', 'vStems']: 
    359                                 if a is not None and b is not None: 
    360                                         if len(a) != len(b): 
    361                                                 # can't do math with non matching zones 
    362                                                 continue 
    363                                         l = len(a) 
    364                                         v = [funct(a[i], b[i]) for i in range(l)] 
    365                                 if v is not None: 
    366                                         setattr(copied, name, v) 
    367                         else: 
    368                                 if a is not None and b is not None: 
    369                                         if len(a) != len(b): 
    370                                                 # can't do math with non matching zones 
    371                                                 continue 
    372                                         l = len(a) 
    373                                         for i in range(l): 
    374                                                 if v is None: 
    375                                                         v = [] 
    376                                                 ai = a[i] 
    377                                                 bi = b[i] 
    378                                                 l2 = min(len(ai), len(bi)) 
    379                                                 v2 = [funct(ai[j], bi[j]) for j in range(l2)] 
    380                                                 v.append(v2) 
    381                                 if v is not None: 
    382                                         setattr(copied, name, v) 
    383  
    384         def _processMathTwo(self, copied, factor, funct): 
    385                 for name, values in self._attributeNames.items(): 
    386                         a = None 
    387                         b = None 
    388                         v = None 
    389                         if hasattr(copied, name): 
    390                                 a = getattr(copied, name) 
    391                         splitFactor = factor 
    392                         isVertical = self._attributeNames[name]['isVertical'] 
    393                         if isinstance(factor, tuple): 
    394                                 if isVertical: 
    395                                         splitFactor = factor[1] 
    396                                 else: 
    397                                         splitFactor = factor[0] 
    398                         if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']: 
    399                                 # process single values 
    400                                 if a is not None: 
    401                                         v = funct(a, splitFactor) 
    402                                 if v is not None: 
    403                                         setattr(copied, name, v) 
    404                         elif name in ['hStems', 'vStems']: 
    405                                 if a is not None: 
    406                                         v = [funct(a[i], splitFactor) for i in range(len(a))] 
    407                                 if v is not None: 
    408                                         setattr(copied, name, v) 
    409                         else: 
    410                                 if a is not None: 
    411                                         for i in range(len(a)): 
    412                                                 if v is None: 
    413                                                         v = [] 
    414                                                 v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))] 
    415                                                 v.append(v2) 
    416                                 if v is not None: 
    417                                         setattr(copied, name, v) 
    418467 
    419468 
     
    592641        def __repr__(self): 
    593642                try: 
    594                         name = self.info.fullName 
     643                        name = self.info.postscriptFullName 
    595644                except AttributeError: 
    596645                        name = "unnamed_font" 
     
    644693                return self.getGlyph(glyphName) 
    645694 
     695        def __contains__(self, glyphName): 
     696                return self.has_key(glyphName) 
     697 
    646698        def _hasChanged(self): 
    647699                #mark the object as changed 
     
    730782                                accent = self[accentName] 
    731783                        except IndexError: 
    732                                 errors["glyph '%s' is missing in font %s"%(accentName, self.fullName)] =  1 
     784                                errors["glyph '%s' is missing in font %s"%(accentName, self.postscriptFullName)] =  1 
    733785                                continue 
    734786                        localAnchors = {} 
     
    753805                                        baseX, baseY = anchors[foundAnchor[1:]] 
    754806                                except KeyError: 
    755                                         errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info.fullName)]=1 
     807                                        errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info.postscriptFullName)]=1 
    756808                                        continue 
    757809                                #calculate the accent componet offset values 
     
    832884                                if not errors.has_key('Missing Glyphs'): 
    833885                                        errors['Missing Glyphs'] = [] 
    834                                 errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.fullName)) 
     886                                errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.postscriptFullName)) 
    835887                        if glyphName not in maxGlyphNames: 
    836888                                fatalError = True 
    837889                                if not errors.has_key('Missing Glyphs'): 
    838890                                        errors['Missing Glyphs'] = [] 
    839                                 errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.fullName)) 
     891                                errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.postscriptFullName)) 
    840892                        # if no major problems, proceed. 
    841893                        if not fatalError: 
     
    881933                                item = getattr(item, sub) 
    882934                except (ImportError, AttributeError): 
    883                         from warnings import warn 
    884935                        warn("Can't find glyph name to file name converter function, " 
    885936                                "falling back to default scheme (%s)" % funcName, RoboFabWarning) 
     
    910961                if fontParent is not None: 
    911962                        try: 
    912                                 font = fontParent.info.fullName 
     963                                font = fontParent.info.postscriptFullName 
    913964                        except AttributeError: 
    914965                                pass 
     
    17761827                        if fontParent is not None: 
    17771828                                try: 
    1778                                         font = fontParent.info.fullName 
     1829                                        font = fontParent.info.postscriptFullName 
    17791830                                except AttributeError: pass 
    17801831                try: 
     
    17891840         
    17901841        def __mul__(self, factor): 
    1791                 from warnings import warn 
    17921842                warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) 
    17931843                n = self.copy() 
     
    18011851 
    18021852        def __add__(self, other): 
    1803                 from warnings import warn 
    18041853                warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) 
    18051854                n = self.copy() 
     
    18111860 
    18121861        def __sub__(self, other): 
    1813                 from warnings import warn 
    18141862                warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) 
    18151863                n = self.copy() 
     
    21862234                                if fontParent is not None: 
    21872235                                        try: 
    2188                                                 font = fontParent.info.fullName 
     2236                                                font = fontParent.info.postscriptFullName 
    21892237                                        except AttributeError: pass 
    21902238                try: 
     
    21952243                 
    21962244        def __mul__(self, factor): 
    2197                 from warnings import warn 
    21982245                warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) 
    21992246                n = self.copy() 
     
    22072254 
    22082255        def __add__(self, other): 
    2209                 from warnings import warn 
    22102256                warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) 
    22112257                n = self.copy() 
     
    22162262 
    22172263        def __sub__(self, other): 
    2218                 from warnings import warn 
    22192264                warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) 
    22202265                n = self.copy() 
     
    23252370                                        if fontParent is not None: 
    23262371                                                try: 
    2327                                                         font = fontParent.info.fullName 
     2372                                                        font = fontParent.info.postscriptFullName 
    23282373                                                except AttributeError: pass 
    23292374                return "<RPoint for %s.%s[%s][%s]>"%(font, glyph, contourIndex, segmentIndex) 
    23302375         
    23312376        def __add__(self, other): 
    2332                 from warnings import warn 
    23332377                warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) 
    23342378                #Add one point to another 
     
    23382382 
    23392383        def __sub__(self, other): 
    2340                 from warnings import warn 
    23412384                warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) 
    23422385                #Subtract one point from another 
     
    23462389 
    23472390        def __mul__(self, factor): 
    2348                 from warnings import warn 
    23492391                warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) 
    23502392                #Multiply the point with factor. Factor can be a tuple of 2 *(f1, f2) 
     
    24412483                                        if fontParent is not None: 
    24422484                                                try: 
    2443                                                         font = fontParent.info.fullName 
     2485                                                        font = fontParent.info.postscriptFullName 
    24442486                                                except AttributeError: pass 
    24452487                return "<RBPoint for %s.%s[%s][%s][%s]>"%(font, glyph, contourIndex, segmentIndex, `self.index`) 
     
    24472489         
    24482490        def __add__(self, other): 
    2449                 from warnings import warn 
    24502491                warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) 
    24512492                #Add one bPoint to another 
     
    24572498 
    24582499        def __sub__(self, other): 
    2459                 from warnings import warn 
    24602500                warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) 
    24612501                #Subtract one bPoint from another 
     
    24672507 
    24682508        def __mul__(self, factor): 
    2469                 from warnings import warn 
    24702509                warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) 
    24712510                #Multiply the bPoint with factor. Factor can be a tuple of 2 *(f1, f2) 
     
    26802719                        if fontParent is not None: 
    26812720                                try: 
    2682                                         font = fontParent.info.fullName 
     2721                                        font = fontParent.info.postscriptFullName 
    26832722                                except AttributeError: pass 
    26842723                return "<RComponent for %s.%s.components[%s]>"%(font, glyph, `self.index`) 
     
    27092748 
    27102749        def __add__(self, other): 
    2711                 from warnings import warn 
    27122750                warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) 
    27132751                #Add one Component to another 
     
    27182756 
    27192757        def __sub__(self, other): 
    2720                 from warnings import warn 
    27212758                warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) 
    27222759                #Subtract one Component from another 
     
    27272764 
    27282765        def __mul__(self, factor): 
    2729                 from warnings import warn 
    27302766                warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) 
    27312767                #Multiply the Component with factor. Factor can be a tuple of 2 *(f1, f2) 
     
    27992835                        if fontParent is not None: 
    28002836                                try: 
    2801                                         font = fontParent.info.fullName 
     2837                                        font = fontParent.info.postscriptFullName 
    28022838                                except AttributeError: pass 
    28032839                return "<RAnchor for %s.%s.anchors[%s]>"%(font, glyph, `self.index`) 
    28042840 
    28052841        def __add__(self, other): 
    2806                 from warnings import warn 
    28072842                warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) 
    28082843                #Add one anchor to another 
     
    28122847 
    28132848        def __sub__(self, other): 
    2814                 from warnings import warn 
    28152849                warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) 
    28162850                #Substract one anchor from another 
     
    28202854 
    28212855        def __mul__(self, factor): 
    2822                 from warnings import warn 
    28232856                warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) 
    28242857                #Multiply the anchor with factor. Factor can be a tuple of 2 *(f1, f2) 
     
    28992932                self.selected = False 
    29002933 
    2901                  
    2902  
    2903                          
     2934 
    29042935class BaseInfo(RBaseObject): 
    2905          
    2906         """Base class for all font.info objects.""" 
    2907          
     2936 
     2937        _baseAttributes = ["_object", "changed", "selected", "getParent"] 
     2938        _deprecatedAttributes = ufoLib.deprecatedFontInfoAttributesVersion2 
     2939        _infoAttributes = ufoLib.fontInfoAttributesVersion2 
     2940        # subclasses may define a list of environment 
     2941        # specific attributes that can be retrieved or set. 
     2942        _environmentAttributes = [] 
     2943        # subclasses may define a list of attributes 
     2944        # that should not follow the standard get/set 
     2945        # order provided by __setattr__ and __getattr__. 
     2946        # for these attributes, the environment specific 
     2947        # set and get methods must handle this value 
     2948        # without any pre-call validation. 
     2949        # (yeah. this is because of some FontLab dumbness.) 
     2950        _environmentOverrides = [] 
     2951 
     2952        def __setattr__(self, attr, value): 
     2953                # check to see if the attribute has been 
     2954                # deprecated. if so, warn the caller and 
     2955                # update the attribute and value. 
     2956                if attr in self._deprecatedAttributes: 
     2957                        newAttr, newValue = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) 
     2958                        note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) 
     2959                        warn(note, DeprecationWarning) 
     2960                        attr = newAttr 
     2961                        value = newValue 
     2962                # setting a known attribute 
     2963                if attr in self._infoAttributes or attr in self._environmentAttributes: 
     2964                        # lightly test the validity of the value 
     2965                        if value is not None: 
     2966                                isValidValue = ufoLib.validateFontInfoVersion2ValueForAttribute(attr, value) 
     2967                                if not isValidValue: 
     2968                                        raise RoboFabError("Invalid value (%s) for attribute (%s)." % (repr(value), attr)) 
     2969                        # use the environment specific info attr set 
     2970                        # method if it is defined. 
     2971                        if hasattr(self, "_environmentSetAttr"): 
     2972                                self._environmentSetAttr(attr, value) 
     2973                        # fallback to super 
     2974                        else: 
     2975                                super(BaseInfo, self).__setattr__(attr, value) 
     2976                # unknown attribute, test to see if it is a python attr 
     2977                elif attr in self.__dict__ or attr in self._baseAttributes: 
     2978                        super(BaseInfo, self).__setattr__(attr, value) 
     2979                # raise an attribute error 
     2980                else: 
     2981                        raise AttributeError("Unknown attribute %s." % attr) 
     2982 
     2983        # subclasses with environment specific attr setting can 
     2984        # implement this method. __setattr__ will call it if present. 
     2985        # def _environmentSetAttr(self, attr, value): 
     2986        #       pass 
     2987 
     2988        def __getattr__(self, attr): 
     2989                if attr in self._environmentOverrides: 
     2990                        return self._environmentGetAttr(attr) 
     2991                # check to see if the attribute has been 
     2992                # deprecated. if so, warn the caller and 
     2993                # flag the value as needing conversion. 
     2994                needValueConversionTo1 = False 
     2995                if attr in self._deprecatedAttributes: 
     2996                        oldAttr = attr 
     2997                        oldValue = attr 
     2998                        newAttr, x = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, None) 
     2999                        note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) 
     3000                        warn(note, DeprecationWarning) 
     3001                        attr = newAttr 
     3002                        needValueConversionTo1 = True 
     3003                # getting a known attribute 
     3004                if attr in self._infoAttributes or attr in self._environmentAttributes: 
     3005                        # use the environment specific info attr get 
     3006                        # method if it is defined. 
     3007                        if hasattr(self, "_environmentGetAttr"): 
     3008                                value = self._environmentGetAttr(attr) 
     3009                        # fallback to super 
     3010                        else: 
     3011                                try: 
     3012                                        value = super(BaseInfo, self).__getattribute__(attr) 
     3013                                except AttributeError: 
     3014                                        return None 
     3015                        if needValueConversionTo1: 
     3016                                oldAttr, value = ufoLib.convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) 
     3017                        return value 
     3018                # raise an attribute error 
     3019                else: 
     3020                        raise AttributeError("Unknown attribute %s." % attr) 
     3021 
     3022                # subclasses with environment specific attr retrieval can 
     3023                # implement this method. __getattr__ will call it if present. 
     3024                # it should return the requested value. 
     3025                # def _environmentGetAttr(self, attr): 
     3026                #       pass 
     3027 
     3028class BaseFeatures(RBaseObject): 
     3029 
    29083030        def __init__(self): 
    29093031                RBaseObject.__init__(self) 
    2910          
    2911         def __repr__(self): 
    2912                 font = self.fullName 
    2913                 return "<RInfo for %s>"%font 
    2914  
    2915         def _get_familyName(self): 
    2916                 raise NotImplementedError 
    2917          
    2918         def _set_familyName(self, value): 
    2919                 raise NotImplementedError 
    2920          
    2921         familyName = property(_get_familyName, _set_familyName, doc="family name") 
    2922          
    2923         def _get_styleName(self): 
    2924                 raise NotImplementedError 
    2925          
    2926         def _set_styleName(self, value): 
    2927                 raise NotImplementedError 
    2928          
    2929         styleName = property(_get_styleName, _set_styleName, doc="style name") 
    2930          
    2931         def _get_fullName(self): 
    2932                 raise NotImplementedError 
    2933          
    2934         def _set_fullName(self, value): 
    2935                 raise NotImplementedError 
    2936          
    2937         fullName = property(_get_fullName, _set_fullName, doc="full name") 
    2938          
    2939         def _get_fontName(self): 
    2940                 raise NotImplementedError 
    2941          
    2942         def _set_fontName(self, value): 
    2943                 raise NotImplementedError 
    2944          
    2945         fontName = property(_get_fontName, _set_fontName, doc="font name") 
    2946          
    2947         def _get_menuName(self): 
    2948                 raise NotImplementedError 
    2949          
    2950         def _set_menuName(self, value): 
    2951                 raise NotImplementedError 
    2952          
    2953         menuName = property(_get_menuName, _set_menuName, doc="menu name") 
    2954          
    2955         def _get_fondName(self): 
    2956                 raise NotImplementedError 
    2957          
    2958         def _set_fondName(self, value): 
    2959                 raise NotImplementedError 
    2960          
    2961         fondName = property(_get_fondName, _set_fondName, doc="fond name") 
    2962          
    2963         def _get_otFamilyName(self): 
    2964                 raise NotImplementedError 
    2965          
    2966         def _set_otFamilyName(self, value): 
    2967                 raise NotImplementedError 
    2968          
    2969         otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="OpenType family name") 
    2970          
    2971         def _get_otStyleName(self): 
    2972                 raise NotImplementedError 
    2973          
    2974         def _set_otStyleName(self, value): 
    2975                 raise NotImplementedError 
    2976          
    2977         otStyleName = property(_get_otStyleName, _set_otStyleName, doc="OpenType style name") 
    2978          
    2979         def _get_otMacName(self): 
    2980                 raise NotImplementedError 
    2981          
    2982         def _set_otMacName(self, value): 
    2983                 raise NotImplementedError 
    2984          
    2985         otMacName = property(_get_otMacName, _set_otMacName, doc="Mac specific OpenType name") 
    2986          
    2987         def _get_weightValue(self): 
    2988                 raise NotImplementedError 
    2989          
    2990         def _set_weightValue(self, value): 
    2991                 raise NotImplementedError 
    2992          
    2993         weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") 
    2994          
    2995         def _get_weightName(self): 
    2996                 raise NotImplementedError 
    2997          
    2998         def _set_weightName(self, value): 
    2999                 raise NotImplementedError 
    3000          
    3001         weightName = property(_get_weightName, _set_weightName, doc="weight name") 
    3002          
    3003         def _get_widthName(self): 
    3004                 raise NotImplementedError 
    3005          
    3006         def _set_widthName(self, value): 
    3007                 raise NotImplementedError 
    3008          
    3009         widthName = property(_get_widthName, _set_widthName, doc="width name") 
    3010          
    3011         def _get_fontStyle(self): 
    3012                 raise NotImplementedError 
    3013          
    3014         def _set_fontStyle(self, value): 
    3015                 raise NotImplementedError 
    3016          
    3017         fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font style") 
    3018          
    3019         def _get_msCharSet(self): 
    3020                 raise NotImplementedError 
    3021          
    3022         def _set_msCharSet(self, value): 
    3023                 raise NotImplementedError 
    3024          
    3025         msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms charset") 
    3026          
    3027         def _get_note(self): 
    3028                 raise NotImplementedError 
    3029          
    3030         def _set_note(self, value): 
    3031                 raise NotImplementedError 
    3032          
    3033         note = property(_get_note, _set_note, doc="note") 
    3034          
    3035         def _get_fondID(self): 
    3036                 raise NotImplementedError 
    3037          
    3038         def _set_fondID(self, value): 
    3039                 raise NotImplementedError 
    3040          
    3041         fondID = property(_get_fondID, _set_fondID, doc="fond id number") 
    3042          
    3043         def _get_uniqueID(self): 
    3044                 raise NotImplementedError 
    3045          
    3046         def _set_uniqueID(self, value): 
    3047                 raise NotImplementedError 
    3048          
    3049         uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique id number") 
    3050          
    3051         def _get_versionMajor(self): 
    3052                 raise NotImplementedError 
    3053          
    3054         def _set_versionMajor(self, value): 
    3055                 raise NotImplementedError 
    3056          
    3057         versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version major") 
    3058          
    3059         def _get_versionMinor(self): 
    3060                 raise NotImplementedError 
    3061          
    3062         def _set_versionMinor(self, value): 
    3063                 raise NotImplementedError 
    3064          
    3065         versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version minor") 
    3066          
    3067         def _get_year(self): 
    3068                 raise NotImplementedError 
    3069          
    3070         def _set_year(self, value): 
    3071                 raise NotImplementedError 
    3072          
    3073         year = property(_get_year, _set_year, doc="year") 
    3074          
    3075         def _get_copyright(self): 
    3076                 raise NotImplementedError 
    3077          
    3078         def _set_copyright(self, value): 
    3079                 raise NotImplementedError 
    3080          
    3081         copyright = property(_get_copyright, _set_copyright, doc="copyright") 
    3082          
    3083         def _get_notice(self): 
    3084                 raise NotImplementedError 
    3085          
    3086         def _set_notice(self, value): 
    3087                 raise NotImplementedError 
    3088          
    3089         notice = property(_get_notice, _set_notice, doc="notice") 
    3090          
    3091         def _get_trademark(self): 
    3092                 raise NotImplementedError 
    3093          
    3094         def _set_trademark(self, value): 
    3095                 raise NotImplementedError 
    3096          
    3097         trademark = property(_get_trademark, _set_trademark, doc="trademark") 
    3098          
    3099         def _get_license(self): 
    3100                 raise NotImplementedError 
    3101          
    3102         def _set_license(self, value): 
    3103                 raise NotImplementedError 
    3104          
    3105         license = property(_get_license, _set_license, doc="license") 
    3106          
    3107         def _get_licenseURL(self): 
    3108                 raise NotImplementedError 
    3109          
    3110         def _set_licenseURL(self, value): 
    3111                 raise NotImplementedError 
    3112          
    3113         licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license url") 
    3114          
    3115         def _get_createdBy(self): 
    3116                 raise NotImplementedError 
    3117          
    3118         def _set_createdBy(self, value): 
    3119                 raise NotImplementedError 
    3120          
    3121         createdBy = property(_get_createdBy, _set_createdBy, doc="source") 
    3122          
    3123         def _get_designer(self): 
    3124                 raise NotImplementedError 
    3125          
    3126         def _set_designer(self, value): 
    3127                 raise NotImplementedError 
    3128          
    3129         designer = property(_get_designer, _set_designer, doc="designer") 
    3130          
    3131         def _get_designerURL(self): 
    3132                 raise NotImplementedError 
    3133          
    3134         def _set_designerURL(self, value): 
    3135                 raise NotImplementedError 
    3136          
    3137         designerURL = property(_get_designerURL, _set_designerURL, doc="designer url") 
    3138          
    3139         def _get_vendorURL(self): 
    3140                 raise NotImplementedError 
    3141          
    3142         def _set_vendorURL(self, value): 
    3143                 raise NotImplementedError 
    3144          
    3145         vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor url") 
    3146          
    3147         def _get_ttVendor(self): 
    3148                 raise NotImplementedError 
    3149          
    3150         def _set_ttVendor(self, value): 
    3151                 raise NotImplementedError 
    3152          
    3153         ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") 
    3154          
    3155         def _get_ttUniqueID(self): 
    3156                 raise NotImplementedError 
    3157          
    3158         def _set_ttUniqueID(self, value): 
    3159                 raise NotImplementedError 
    3160          
    3161         ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="TrueType unique id number") 
    3162          
    3163         def _get_ttVersion(self): 
    3164                 raise NotImplementedError 
    3165          
    3166         def _set_ttVersion(self, value): 
    3167                 raise NotImplementedError 
    3168          
    3169         ttVersion = property(_get_ttVersion, _set_ttVersion, doc="TrueType version") 
    3170          
    3171         def _get_unitsPerEm(self): 
    3172                 raise NotImplementedError 
    3173          
    3174         def _set_unitsPerEm(self): 
    3175                 raise NotImplementedError 
    3176          
    3177         unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value") 
    3178          
    3179         def _get_ascender(self): 
    3180                 raise NotImplementedError 
    3181          
    3182         def _set_ascender(self, value): 
    3183                 raise NotImplementedError 
    3184          
    3185         ascender = property(_get_ascender, _set_ascender, doc="ascender value") 
    3186          
    3187         def _get_descender(self): 
    3188                 raise NotImplementedError 
    3189          
    3190         def _set_descender(self, value): 
    3191                 raise NotImplementedError 
    3192          
    3193         descender = property(_get_descender, _set_descender, doc="descender value") 
    3194          
    3195         def _get_capHeight(self): 
    3196                 raise NotImplementedError 
    3197          
    3198         def _set_capHeight(self, value): 
    3199                 raise NotImplementedError 
    3200          
    3201         capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") 
    3202          
    3203         def _get_xHeight(self): 
    3204                 raise NotImplementedError 
    3205          
    3206         def _set_xHeight(self, value): 
    3207                 raise NotImplementedError 
    3208          
    3209         xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") 
    3210          
    3211         def _get_defaultWidth(self): 
    3212                 raise NotImplementedError 
    3213          
    3214         def _set_defaultWidth(self, value): 
    3215                 raise NotImplementedError 
    3216          
    3217         defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") 
    3218          
    3219         def _get_italicAngle(self): 
    3220                 raise NotImplementedError 
    3221          
    3222         def _set_italicAngle(self, value): 
    3223                 raise NotImplementedError 
    3224          
    3225         italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") 
    3226          
    3227         def _get_slantAngle(self): 
    3228                 raise NotImplementedError 
    3229          
    3230         def _set_slantAngle(self, value): 
    3231                 raise NotImplementedError 
    3232          
    3233         slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") 
    3234          
    3235         def autoNaming(self, familyName=None, styleName=None): 
    3236                 """Automatically set the font naming info based on family and style names.""" 
    3237  
    3238                 if familyName is None: 
    3239                         if not self.familyName: 
    3240                                 raise RoboFabError, "Family name and style name must be complete" 
    3241                 else: 
    3242                         self.familyName = familyName 
    3243                 if styleName is None: 
    3244                         if not self.styleName: 
    3245                                 raise RoboFabError, "Family name and style name must be complete" 
    3246                 else: 
    3247                         self.styleName = styleName 
    3248                 family = self.familyName 
    3249                 style = self.styleName           
    3250                 self.fullName = ' '.join((family, style)) 
    3251                 self.fontName = '-'.join((family, style)).replace(' ', '') 
    3252                 self.fondName = family 
    3253                 self.menuName = ' '.join((family, style)) 
    3254                 self.otFamilyName = family 
    3255                 self.otStyleName = style 
    3256                 self.otMacName = ' '.join((family, style)) 
    3257                 #self._hasChanged() 
    3258                  
     3032                self._text = "" 
     3033 
     3034        def _get_text(self): 
     3035                return self._text 
     3036 
     3037        def _set_text(self, value): 
     3038                assert isinstance(value, basestring) 
     3039                self._text = value 
     3040 
     3041        text = property(_get_text, _set_text, doc="raw feature text.") 
     3042 
     3043 
    32593044class BaseGroups(dict): 
    32603045         
     
    32693054                if fontParent is not None: 
    32703055                        try: 
    3271                                 font = fontParent.info.fullName 
     3056                                font = fontParent.info.postscriptFullName 
    32723057                        except AttributeError: pass 
    32733058                return "<RGroups for %s>"%font 
     
    33133098                        #do we have a font? 
    33143099                        try: 
    3315                                 parent = parentObject.info.fullName 
     3100                                parent = parentObject.info.postscriptFullName 
    33163101                        except AttributeError: 
    33173102                                #or do we have a glyph? 
     
    33583143                if fontParent is not None: 
    33593144                        try: 
    3360                                 font = fontParent.info.fullName 
     3145                                font = fontParent.info.postscriptFullName 
    33613146                        except AttributeError: pass 
    33623147                return "<RKerning for %s>"%font 
     
    37063491                return self.__mul__(1.0/factor) 
    37073492         
    3708          
  • trunk/Lib/robofab/objects/objectsFL.py

    r163 r171  
    66                AllFonts, NewGlyph 
    77from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ 
    8                 BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\ 
     8                BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\ 
    99                roundPt, addPt, _box,\ 
    1010                MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ 
     
    1717from robofab.plistlib import Data, Dict, readPlist, writePlist 
    1818from StringIO import StringIO 
     19from robofab import ufoLib 
     20from warnings import warn 
     21import datetime 
     22from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab 
     23 
     24 
     25try: 
     26        set 
     27except NameError: 
     28        from sets import Set as set 
    1929 
    2030# local encoding 
     
    2333else: 
    2434        LOCAL_ENCODING = "latin-1" 
     35 
     36_IN_UFO_EXPORT = False 
    2537 
    2638# a list of attributes that are to be copied when copying a glyph. 
     
    106118        stem_snap_v_num(integer) 
    107119        stem_snap_v 
    108          
    109                  
    110          
    111120 """ 
    112121 
     
    122131                from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues 
    123132                return _PostScriptFontHintValues(data=self.asDict()) 
    124                          
    125         def _getBlueFuzz(self): 
    126                 return self._object.blue_fuzz[self._masterIndex] 
    127         def _setBlueFuzz(self, value): 
    128                 self._object.blue_fuzz[self._masterIndex] = value 
    129  
    130         def _getBlueScale(self): 
    131                 return self._object.blue_scale[self._masterIndex] 
    132         def _setBlueScale(self, value): 
    133                 self._object.blue_scale[self._masterIndex] = float(value) 
    134  
    135         def _getBlueShift(self): 
    136                 return self._object.blue_shift[self._masterIndex] 
    137         def _setBlueShift(self, value): 
    138                 self._object.blue_shift[self._masterIndex] = value 
    139  
    140         def _getForceBold(self): 
    141                 return self._object.force_bold[self._masterIndex] == 1 
    142                  
    143         def _setForceBold(self, value): 
    144                 if value: 
    145                         value = 1 
    146                 else: 
    147                         value = 0 
    148                 self._object.force_bold[self._masterIndex] = value 
    149          
    150         # Note: these attributes are wrapppers for lists, 
    151         # but regular list operatons won't have any effect. 
    152         # you really have to _get_ and _set_ a list. 
    153          
    154         def _asPairs(self, l): 
    155                 """Split a list of numbers into a list of pairs""" 
    156                 if not len(l)%2 == 0: 
    157                         l = l[:-1] 
    158                 n = [[l[i], l[i+1]] for i in range(0, len(l), 2)] 
    159                 n.sort() 
    160                 return n 
    161          
    162         def _flattenPairs(self, l): 
    163                 """The reverse of _asPairs""" 
    164                 n = [] 
    165                 l.sort() 
    166                 for i in l: 
    167                         assert len(i) == 2, "Each entry must consist of two numbers" 
    168                         n.append(i[0]) 
    169                         n.append(i[1]) 
    170                 return n 
    171          
    172         def _checkForFontLabSanity(self, attribute, values): 
    173                 """Function to handle problems with FontLab not allowing the max number of  
    174                 alignment zones to be set to the max number. 
    175                 Input:  the name of the zones and the values to be set 
    176                 Output: a warning when there are too many values to be set 
    177                                 and the max values which FontLab will allow. 
    178                 """ 
    179                 warn = False 
    180                 if attribute in ['vStems', 'hStems']: 
    181                         # the number of items to drop from the list if the list is too long, 
    182                         # stems are single values, but the zones are pairs. 
    183                         skip = 1 
    184                         total = min(self._attributeNames[attribute]['max'], len(values)) 
    185                         if total == self._attributeNames[attribute]['max']: 
    186                                 total = self._attributeNames[attribute]['max'] - skip 
    187                                 warn = True 
    188                 else: 
    189                         skip = 2 
    190                         values = self._flattenPairs(values) 
    191                         total = min(self._attributeNames[attribute]['max']*2, len(values)) 
    192                         if total == self._attributeNames[attribute]['max']*2: 
    193                                 total = self._attributeNames[attribute]['max']*2 - skip 
    194                                 warn = True 
    195                 if warn: 
    196                         print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s."%(self._attributeNames[attribute]['max']-1, attribute, `values[total:]`) 
    197                 return total, values[:total] 
    198                  
    199                  
    200         def _getBlueValues(self): 
    201                         return self._asPairs(self._object.blue_values[self._masterIndex]) 
    202         def _setBlueValues(self, values): 
    203                 total, values = self._checkForFontLabSanity('blueValues', values) 
    204                 self._object.blue_values_num = total 
    205                 for i in range(self._object.blue_values_num): 
    206                         self._object.blue_values[self._masterIndex][i] = values[i] 
    207  
    208         def _getOtherBlues(self): 
    209                         return self._asPairs(self._object.other_blues[self._masterIndex]) 
    210         def _setOtherBlues(self, values): 
    211                 total, values = self._checkForFontLabSanity('otherBlues', values) 
    212                 self._object.other_blues_num = total 
    213                 for i in range(self._object.other_blues_num): 
    214                         self._object.other_blues[self._masterIndex][i] = values[i] 
    215  
    216         def _getFamilyBlues(self): 
    217                         return self._asPairs(self._object.family_blues[self._masterIndex]) 
    218         def _setFamilyBlues(self, values): 
    219                 total, values = self._checkForFontLabSanity('familyBlues', values) 
    220                 self._object.family_blues_num = total 
    221                 for i in range(self._object.family_blues_num): 
    222                         self._object.family_blues[self._masterIndex][i] = values[i] 
    223  
    224         def _getFamilyOtherBlues(self): 
    225                         return self._asPairs(self._object.family_other_blues[self._masterIndex]) 
    226         def _setFamilyOtherBlues(self, values): 
    227                 total, values = self._checkForFontLabSanity('familyOtherBlues', values) 
    228                 self._object.family_other_blues_num = total 
    229                 for i in range(self._object.family_other_blues_num): 
    230                         self._object.family_other_blues[self._masterIndex][i] = values[i] 
    231  
    232         def _getVStems(self): 
    233                         return list(self._object.stem_snap_v[self._masterIndex]) 
    234         def _setVStems(self, values): 
    235                 total, values = self._checkForFontLabSanity('vStems', values) 
    236                 self._object.stem_snap_v_num = total 
    237                 for i in range(self._object.stem_snap_v_num): 
    238                         self._object.stem_snap_v[self._masterIndex][i] = values[i] 
    239  
    240         def _getHStems(self): 
    241                         return list(self._object.stem_snap_h[self._masterIndex]) 
    242         def _setHStems(self, values): 
    243                 total, values = self._checkForFontLabSanity('hStems', values) 
    244                 self._object.stem_snap_h_num = total 
    245                 for i in range(self._object.stem_snap_h_num): 
    246                         self._object.stem_snap_h[self._masterIndex][i] = values[i] 
    247  
    248         blueFuzz = property(_getBlueFuzz, _setBlueFuzz, doc="postscript hints: bluefuzz value") 
    249         blueScale = property(_getBlueScale, _setBlueScale, doc="postscript hints: bluescale value") 
    250         blueShift = property(_getBlueShift, _setBlueShift, doc="postscript hints: blueshift value") 
    251         forceBold = property(_getForceBold, _setForceBold, doc="postscript hints: force bold value") 
    252         blueValues = property(_getBlueValues, _setBlueValues, doc="postscript hints: blue values") 
    253         otherBlues = property(_getOtherBlues, _setOtherBlues, doc="postscript hints: other blue values") 
    254         familyBlues = property(_getFamilyBlues, _setFamilyBlues, doc="postscript hints: family blue values") 
    255         familyOtherBlues = property(_getFamilyOtherBlues, _setFamilyOtherBlues, doc="postscript hints: family other blue values") 
    256         vStems = property(_getVStems, _setVStems, doc="postscript hints: vertical stem values") 
    257         hStems = property(_getHStems, _setHStems, doc="postscript hints: horizontal stem values") 
    258                          
     133 
    259134 
    260135class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): 
     
    503378        fl.Add(f) 
    504379        rf = RFont(f) 
    505         rf.info.familyName = familyName 
    506         rf.info.styleName = styleName 
     380        if familyName is not None: 
     381                rf.info.familyName = familyName 
     382        if styleName is not None: 
     383                rf.info.styleName = styleName 
    507384        return rf 
    508385 
     
    560437                self._lib = {} 
    561438                self._supportHints = True 
     439                self.psHints = PostScriptFontHintValues(self) 
     440                self.psHints.setParent(self) 
    562441 
    563442        def keys(self): 
     
    601480         
    602481 
    603         def _get_psHints(self): 
    604                 return PostScriptFontHintValues(self) 
    605  
    606         psHints = property(_get_psHints, doc="font level postscript hint data") 
     482#       def _get_psHints(self): 
     483#               h = PostScriptFontHintValues(self) 
     484#               h.setParent(self) 
     485#               return h 
     486
     487#       psHints = property(_get_psHints, doc="font level postscript hint data") 
    607488 
    608489        def _get_info(self): 
     
    610491         
    611492        info = property(_get_info, doc="font info object") 
    612          
     493 
     494        def _get_features(self): 
     495                return RFeatures(self._object) 
     496 
     497        features = property(_get_features, doc="features object") 
     498 
    613499        def _get_kerning(self): 
    614500                kerning = {} 
     
    1026912                fl.ifont = self.fontIndex 
    1027913                fl.GenerateFont(flOutputType, finalPath) 
    1028          
     914 
     915        def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, 
     916                doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2): 
     917                from robofab.interface.all.dialogs import ProgressBar, Message 
     918                # special glyph name to file name conversion 
     919                if glyphNameToFileNameFunc is None: 
     920                        glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() 
     921                        if glyphNameToFileNameFunc is None: 
     922                                from robofab.tools.glyphNameSchemes import glyphNameToShortFileName 
     923                                glyphNameToFileNameFunc = glyphNameToShortFileName 
     924                # get a valid path 
     925                if not path: 
     926                        if self.path is None: 
     927                                Message("Please save this font first before exporting to UFO...") 
     928                                return 
     929                        else: 
     930                                path = ufoLib.makeUFOPath(self.path) 
     931                # get the glyphs to export 
     932                if glyphs is None: 
     933                        glyphs = self.keys() 
     934                # if the file exists, check the format version. 
     935                # if the format version being written is different 
     936                # from the format version of the existing UFO 
     937                # and only some files are set to be written 
     938                # raise an error. 
     939                if os.path.exists(path): 
     940                        if os.path.exists(os.path.join(path, "metainfo.plist")): 
     941                                reader = ufoLib.UFOReader(path) 
     942                                existingFormatVersion = reader.formatVersion 
     943                                if formatVersion != existingFormatVersion: 
     944                                        if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]: 
     945                                                Message("When overwriting an existing UFO with a different format version all files must be written.") 
     946                                                return 
     947                # the lib must be written if format version is 1 
     948                if not doLib and formatVersion == 1: 
     949                        Message("The lib must be written when exporting format version 1.") 
     950                        return 
     951                # set up the progress bar 
     952                nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) 
     953                bar = None 
     954                if doProgress: 
     955                        bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs)) 
     956                # try writing 
     957                try: 
     958                        writer = ufoLib.UFOWriter(path, formatVersion=formatVersion) 
     959                        ## We make a shallow copy if lib, since we add some stuff for export 
     960                        ## that doesn't need to be retained in memory. 
     961                        fontLib = dict(self.lib) 
     962                        # write the font info 
     963                        if doInfo: 
     964                                global _IN_UFO_EXPORT 
     965                                _IN_UFO_EXPORT = True 
     966                                writer.writeInfo(self.info) 
     967                                _IN_UFO_EXPORT = False 
     968                                if bar: 
     969                                        bar.tick() 
     970                        # write the kerning 
     971                        if doKerning: 
     972                                writer.writeKerning(self.kerning.asDict()) 
     973                                if bar: 
     974                                        bar.tick() 
     975                        # write the groups 
     976                        if doGroups: 
     977                                writer.writeGroups(self.groups) 
     978                                if bar: 
     979                                        bar.tick() 
     980                        # write the features 
     981                        if doFeatures: 
     982                                if formatVersion == 2: 
     983                                        writer.writeFeatures(self.features.text) 
     984                                else: 
     985                                        self._writeOpenTypeFeaturesToLib(fontLib) 
     986                                if bar: 
     987                                        bar.tick() 
     988                        # write the lib 
     989                        if doLib: 
     990                                ## Always export the postscript font hint values to the lib in format version 1 
     991                                if formatVersion == 1: 
     992                                        d = self.psHints.asDict() 
     993                                        fontLib[postScriptHintDataLibKey] = d 
     994                                ## Export the glyph order to the lib 
     995                                glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs] 
     996                                fontLib["org.robofab.glyphOrder"] = glyphOrder 
     997                                ## export the features 
     998                                if doFeatures and formatVersion == 1: 
     999                                        self._writeOpenTypeFeaturesToLib(fontLib) 
     1000                                        if bar: 
     1001                                                bar.tick() 
     1002                                writer.writeLib(fontLib) 
     1003                                if bar: 
     1004                                        bar.tick() 
     1005                        # write the glyphs 
     1006                        if glyphs: 
     1007                                glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) 
     1008                                count = nonGlyphCount 
     1009                                for nakedGlyph in self.naked().glyphs: 
     1010                                        if nakedGlyph.name not in glyphs: 
     1011                                                continue 
     1012                                        glyph = RGlyph(nakedGlyph) 
     1013                                        if doHints: 
     1014                                                hintStuff = _glyphHintsToDict(glyph.naked()) 
     1015                                                if hintStuff: 
     1016                                                        glyph.lib[postScriptHintDataLibKey] = hintStuff 
     1017                                        glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) 
     1018                                        # remove the hint dict from the lib 
     1019                                        if doHints and glyph.lib.has_key(postScriptHintDataLibKey): 
     1020                                                del glyph.lib[postScriptHintDataLibKey] 
     1021                                        if bar and not count % 10: 
     1022                                                bar.tick(count) 
     1023                                        count = count + 1 
     1024                                glyphSet.writeContents() 
     1025                # only blindly stop if the user says to 
     1026                except KeyboardInterrupt: 
     1027                        if bar: 
     1028                                bar.close() 
     1029                        bar = None 
     1030                # kill the bar 
     1031                if bar: 
     1032                        bar.close() 
     1033 
    10291034        def _writeOpenTypeFeaturesToLib(self, fontLib): 
     1035                # this should only be used for UFO format version 1 
    10301036                flFont = self.naked() 
    1031                 if flFont.ot_classes: 
    1032                         fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings( 
    1033                                         flFont.ot_classes) 
     1037                fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(flFont.ot_classes).rstrip() + "\n" 
    10341038                if flFont.features: 
    10351039                        features = {} 
     
    10371041                        for feature in flFont.features: 
    10381042                                order.append(feature.tag) 
    1039                                 features[feature.tag] = _normalizeLineEndings(feature.value) 
     1043                                features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n" 
    10401044                        fontLib["org.robofab.opentype.features"] = features 
    10411045                        fontLib["org.robofab.opentype.featureorder"] = order 
    1042          
    1043         def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, doHints=False): 
    1044                 """write a font to .ufo""" 
    1045                 from robofab.ufoLib import makeUFOPath, UFOWriter 
     1046 
     1047        def readUFO(self, path, doProgress=False, 
     1048                doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None): 
     1049                """read a .ufo into the font""" 
     1050                from robofab.pens.flPen import FLPointPen 
    10461051                from robofab.interface.all.dialogs import ProgressBar 
    1047                 if glyphNameToFileNameFunc is None: 
    1048                         glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() 
    1049                         if glyphNameToFileNameFunc is None: 
    1050                                 from robofab.tools.glyphNameSchemes import glyphNameToShortFileName 
    1051                                 glyphNameToFileNameFunc = glyphNameToShortFileName 
    1052                 if not path: 
    1053                         if self.path is None: 
    1054                                 # XXX this should really raise an exception instead 
    1055                                 from robofab.interface.all.dialogs import Message 
    1056                                 Message("Please save this font first before exporting to UFO...") 
    1057                                 return 
    1058                         else: 
    1059                                 path = makeUFOPath(self.path) 
    1060                 nonGlyphCount = 4 
     1052                # start up the reader 
     1053                reader = ufoLib.UFOReader(path) 
     1054                glyphSet = reader.getGlyphSet() 
     1055                # get a list of glyphs that should be imported 
     1056                if glyphs is None: 
     1057                        glyphs = glyphSet.keys() 
     1058                # set up the progress bar 
     1059                nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) 
    10611060                bar = None 
    10621061                if doProgress: 
    1063                         bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self.glyphs)) 
     1062                        bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs)) 
     1063                # start reading 
    10641064                try: 
    1065                         u = UFOWriter(path) 
    1066                         u.writeInfo(self.info) 
    1067                         if bar: 
    1068                                 bar.tick() 
    1069                         u.writeKerning(self.kerning.asDict()) 
    1070                         if bar: 
    1071                                 bar.tick() 
    1072                         u.writeGroups(self.groups) 
    1073                         if bar: 
    1074                                 bar.tick() 
    1075                         count = nonGlyphCount 
    1076                         glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) 
    1077                         glyphOrder = [] 
    1078                         for nakedGlyph in self.naked().glyphs: 
    1079                                 glyph = RGlyph(nakedGlyph) 
    1080                                 glyphOrder.append(glyph.name) 
     1065                        fontLib = reader.readLib() 
     1066                        # info 
     1067                        if doInfo: 
     1068                                reader.readInfo(self.info) 
     1069                                if bar: 
     1070                                        bar.tick() 
     1071                        # glyphs 
     1072                        count = 1 
     1073                        glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet) 
     1074                        for glyphName in glyphOrder: 
     1075                                if glyphName not in glyphs: 
     1076                                        continue 
     1077                                glyph = self.newGlyph(glyphName, clear=True) 
     1078                                pen = FLPointPen(glyph.naked()) 
     1079                                glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) 
    10811080                                if doHints: 
    1082                                         hintStuff = _glyphHintsToDict(glyph.naked()) 
    1083                                         if hintStuff: 
    1084                                                 glyph.lib[postScriptHintDataLibKey] = hintStuff 
    1085                                 glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) 
    1086                                 # remove the hint dict from the lib 
    1087                                 if doHints and glyph.lib.has_key(postScriptHintDataLibKey): 
    1088                                         del glyph.lib[postScriptHintDataLibKey] 
     1081                                        hintData = glyph.lib.get(postScriptHintDataLibKey) 
     1082                                        if hintData: 
     1083                                                _dictHintsToGlyph(glyph.naked(), hintData) 
     1084                                        # now that the hints have been extracted from the glyph 
     1085                                        # there is no reason to keep the location in the lib. 
     1086                                        if glyph.lib.has_key(postScriptHintDataLibKey): 
     1087                                                del glyph.lib[postScriptHintDataLibKey] 
     1088                                glyph.update() 
    10891089                                if bar and not count % 10: 
    10901090                                        bar.tick(count) 
    10911091                                count = count + 1 
    1092                         assert None not in glyphOrder, glyphOrder 
    1093                         glyphSet.writeContents() 
    1094                         # We make a shallow copy if lib, since we add some stuff for export 
    1095                         # that doesn't need to be retained in memory. 
    1096                         fontLib = dict(self.lib) 
    1097                         # Always export the postscript font hint values 
    1098                         psh = PostScriptFontHintValues(self) 
    1099                         d = psh.asDict() 
    1100                         fontLib[postScriptHintDataLibKey] = d 
    1101                         # Export the glyph order 
    1102                         fontLib["org.robofab.glyphOrder"] = glyphOrder 
    1103                         self._writeOpenTypeFeaturesToLib(fontLib) 
    1104                         u.writeLib(fontLib) 
    1105                         if bar: 
    1106                                 bar.tick() 
     1092                        # features 
     1093                        if doFeatures: 
     1094                                if reader.formatVersion == 1: 
     1095                                        self._readOpenTypeFeaturesFromLib(fontLib) 
     1096                                else: 
     1097                                        featureText = reader.readFeatures() 
     1098                                        self.features.text = featureText 
     1099                                if bar: 
     1100                                        bar.tick() 
     1101                        else: 
     1102                                # remove features stored in the lib 
     1103                                self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False) 
     1104                        # kerning 
     1105                        if doKerning: 
     1106                                self.kerning.clear() 
     1107                                self.kerning.update(reader.readKerning()) 
     1108                                if bar: 
     1109                                        bar.tick() 
     1110                        # groups 
     1111                        if doGroups: 
     1112                                self.groups.clear() 
     1113                                self.groups.update(reader.readGroups()) 
     1114                                if bar: 
     1115                                        bar.tick() 
     1116                        # hints in format version 1 
     1117                        if doHints and reader.formatVersion == 1: 
     1118                                self.psHints._loadFromLib(fontLib) 
     1119                        else: 
     1120                                # remove hint data stored in the lib 
     1121                                if fontLib.has_key(postScriptHintDataLibKey): 
     1122                                        del fontLib[postScriptHintDataLibKey] 
     1123                        # lib 
     1124                        if doLib: 
     1125                                self.lib.clear() 
     1126                                self.lib.update(fontLib) 
     1127                                if bar: 
     1128                                        bar.tick() 
     1129                # only blindly stop if the user says to 
    11071130                except KeyboardInterrupt: 
    1108                         if bar: 
    1109                                 bar.close() 
     1131                        bar.close() 
    11101132                        bar = None 
     1133                # kill the bar 
    11111134                if bar: 
    11121135                        bar.close() 
    1113          
     1136 
    11141137        def _getGlyphOrderFromLib(self, fontLib, glyphSet): 
    11151138                glyphOrder = fontLib.get("org.robofab.glyphOrder") 
     
    11301153                else: 
    11311154                        glyphNames = glyphSet.keys() 
    1132                         # Sort according to unicode would be best, but is really 
    1133                         # expensive... 
    11341155                        glyphNames.sort() 
    11351156                return glyphNames 
    11361157         
    1137         def _readOpenTypeFeaturesFromLib(self, fontLib): 
     1158        def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True): 
     1159                # setFeatures may be False. in this case, this method 
     1160                # should only clear the data from the lib. 
    11381161                classes = fontLib.get("org.robofab.opentype.classes") 
    11391162                if classes is not None: 
    11401163                        del fontLib["org.robofab.opentype.classes"] 
    1141                         self.naked().ot_classes = classes 
     1164                        if setFeatures: 
     1165                                self.naked().ot_classes = classes 
    11421166                features = fontLib.get("org.robofab.opentype.features") 
    11431167                if features is not None: 
     
    11561180                                if oneFeature is not None: 
    11571181                                        orderedFeatures.append((tag, oneFeature)) 
    1158                         self.naked().features.clean() 
    1159                         for tag, src in orderedFeatures: 
    1160                                 self.naked().features.append(Feature(tag, src)) 
    1161          
    1162         def readUFO(self, path, doProgress=False, doHints=True): 
    1163                 """read a .ufo into the font""" 
    1164                 from robofab.ufoLib import UFOReader 
    1165                 from robofab.pens.flPen import FLPointPen 
    1166                 from robofab.interface.all.dialogs import ProgressBar 
    1167                 nonGlyphCount = 4 
    1168                 bar = None 
    1169                 u = UFOReader(path) 
    1170                 glyphSet = u.getGlyphSet() 
    1171                 fontLib = u.readLib() 
    1172                 glyphNames = self._getGlyphOrderFromLib(fontLib, glyphSet) 
    1173                 if doProgress: 
    1174                         bar = ProgressBar('Importing UFO', nonGlyphCount+len(glyphNames)) 
    1175                 try: 
    1176                         u.readInfo(self.info) 
    1177                         if bar: 
    1178                                 bar.tick() 
    1179                         self._readOpenTypeFeaturesFromLib(fontLib) 
    1180                         self.lib.clear() 
    1181                         self.lib = fontLib 
    1182                         if bar: 
    1183                                 bar.tick() 
    1184                         count = 2 
    1185                         for glyphName in glyphNames: 
    1186                                 glyph = self.newGlyph(glyphName, clear=True) 
    1187                                 pen = FLPointPen(glyph.naked()) 
    1188                                 glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) 
    1189                                 if doHints: 
    1190                                         hintData = glyph.lib.get(postScriptHintDataLibKey) 
    1191                                         if hintData: 
    1192                                                 _dictHintsToGlyph(glyph.naked(), hintData) 
    1193                                         # now that the hints have been extracted from the glyph 
    1194                                         # there is no reason to keep the location in the lib. 
    1195                                         if glyph.lib.has_key(postScriptHintDataLibKey): 
    1196                                                 del glyph.lib[postScriptHintDataLibKey] 
    1197                                 glyph.update() 
    1198                                 if bar and not count % 10: 
    1199                                         bar.tick(count) 
    1200                                 count = count + 1 
    1201                         # import postscript font hint data 
    1202                         self.psHints._loadFromLib(fontLib) 
    1203                         self.kerning.clear() 
    1204                         self.kerning.update(u.readKerning()) 
    1205                         if bar: 
    1206                                 bar.tick() 
    1207                         self.groups.clear() 
    1208                         self.groups = u.readGroups() 
    1209                 except KeyboardInterrupt: 
    1210                         bar.close() 
    1211                         bar = None 
    1212                 if bar: 
    1213                         bar.close() 
     1182                        if setFeatures: 
     1183                                self.naked().features.clean() 
     1184                                for tag, src in orderedFeatures: 
     1185                                        self.naked().features.append(Feature(tag, src)) 
     1186 
    12141187 
    12151188 
     
    22922265                        # do we have a font? 
    22932266                        try: 
    2294                                 parent = parentObject.info.fullName 
     2267                                parent = parentObject.info.postscriptFullName 
    22952268                        except AttributeError: 
    22962269                                # or do we have a glyph? 
     
    26092582                return i 
    26102583 
    2611                          
     2584 
     2585def _infoMapDict(**kwargs): 
     2586        default = dict( 
     2587                nakedAttribute=None, 
     2588                type=None, 
     2589                requiresSetNum=False, 
     2590                masterSpecific=False, 
     2591                libLocation=None, 
     2592                specialGetSet=False 
     2593        ) 
     2594        default.update(kwargs) 
     2595        return default 
     2596 
     2597def _flipDict(d): 
     2598        f = {} 
     2599        for k, v in d.items(): 
     2600                f[v] = k 
     2601        return f 
     2602 
     2603_styleMapStyleName_fromFL = { 
     2604        64 : "regular", 
     2605        1  : "italic", 
     2606        32 : "bold", 
     2607        33 : "bold italic" 
     2608
     2609_styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL) 
     2610 
     2611_postscriptWindowsCharacterSet_fromFL = { 
     2612        0   : 1, 
     2613        1   : 2, 
     2614        2   : 3, 
     2615        77  : 4, 
     2616        128 : 5, 
     2617        129 : 6, 
     2618        130 : 7, 
     2619        134 : 8, 
     2620        136 : 9, 
     2621        161 : 10, 
     2622        162 : 11, 
     2623        163 : 12, 
     2624        177 : 13, 
     2625        178 : 14, 
     2626        186 : 15, 
     2627        200 : 16, 
     2628        204 : 17, 
     2629        222 : 18, 
     2630        238 : 19, 
     2631        255 : 20, 
     2632
     2633_postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL) 
     2634 
     2635_openTypeOS2Type_toFL = { 
     2636        1 : 0x0002, 
     2637        2 : 0x0004, 
     2638        3 : 0x0008, 
     2639        8 : 0x0100, 
     2640        9 : 0x0200, 
     2641
     2642_openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL) 
     2643 
     2644_openTypeOS2WidthClass_fromFL = { 
     2645        "Ultra-condensed" : 1, 
     2646        "Extra-condensed" : 2, 
     2647        "Condensed"               : 3, 
     2648        "Semi-condensed"  : 4, 
     2649        "Medium (normal)" : 5, 
     2650        "Semi-expanded"   : 6, 
     2651        "Expanded"                : 7, 
     2652        "Extra-expanded"  : 8, 
     2653        "Ultra-expanded"  : 9, 
     2654
     2655_openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL) 
     2656 
     2657_postscriptHintAttributes = set(( 
     2658        "postscriptBlueValues", 
     2659        "postscriptOtherBlues", 
     2660        "postscriptFamilyBlues", 
     2661        "postscriptFamilyOtherBlues", 
     2662        "postscriptStemSnapH", 
     2663        "postscriptStemSnapV", 
     2664)) 
     2665 
     2666 
    26122667class RInfo(BaseInfo): 
    2613          
     2668 
    26142669        """RoboFab wrapper for FL Font Info""" 
    2615          
     2670 
    26162671        _title = "FLInfo" 
    2617          
     2672 
     2673        _ufoToFLAttrMapping = { 
     2674                "familyName"                                                    : _infoMapDict(valueType=str, nakedAttribute="family_name"), 
     2675                "styleName"                                                             : _infoMapDict(valueType=str, nakedAttribute="style_name"), 
     2676                "styleMapFamilyName"                                    : _infoMapDict(valueType=str, nakedAttribute="menu_name"), 
     2677                "styleMapStyleName"                                             : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True), 
     2678                "versionMajor"                                                  : _infoMapDict(valueType=int, nakedAttribute="version_major"), 
     2679                "versionMinor"                                                  : _infoMapDict(valueType=int, nakedAttribute="version_minor"), 
     2680                "year"                                                                  : _infoMapDict(valueType=int, nakedAttribute="year"), 
     2681                "copyright"                                                             : _infoMapDict(valueType=str, nakedAttribute="copyright"), 
     2682                "trademark"                                                             : _infoMapDict(valueType=str, nakedAttribute="trademark"), 
     2683                "unitsPerEm"                                                    : _infoMapDict(valueType=int, nakedAttribute="upm"), 
     2684                "descender"                                                             : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True), 
     2685                "xHeight"                                                               : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True), 
     2686                "capHeight"                                                             : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True), 
     2687                "ascender"                                                              : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True), 
     2688                "italicAngle"                                                   : _infoMapDict(valueType=float, nakedAttribute="italic_angle"), 
     2689                "note"                                                                  : _infoMapDict(valueType=str, nakedAttribute="note"), 
     2690                "openTypeHeadCreated"                                   : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values 
     2691                "openTypeHeadLowestRecPPEM"                             : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"), 
     2692                "openTypeHeadFlags"                                             : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface. 
     2693                "openTypeHheaAscender"                                  : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"), 
     2694                "openTypeHheaDescender"                                 : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"), 
     2695                "openTypeHheaLineGap"                                   : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"), 
     2696                "openTypeHheaCaretSlopeRise"                    : _infoMapDict(valueType=int, nakedAttribute=None), 
     2697                "openTypeHheaCaretSlopeRun"                             : _infoMapDict(valueType=int, nakedAttribute=None), 
     2698                "openTypeHheaCaretOffset"                               : _infoMapDict(valueType=int, nakedAttribute=None), 
     2699                "openTypeNameDesigner"                                  : _infoMapDict(valueType=str, nakedAttribute="designer"), 
     2700                "openTypeNameDesignerURL"                               : _infoMapDict(valueType=str, nakedAttribute="designer_url"), 
     2701                "openTypeNameManufacturer"                              : _infoMapDict(valueType=str, nakedAttribute="source"), 
     2702                "openTypeNameManufacturerURL"                   : _infoMapDict(valueType=str, nakedAttribute="vendor_url"), 
     2703                "openTypeNameLicense"                                   : _infoMapDict(valueType=str, nakedAttribute="license"), 
     2704                "openTypeNameLicenseURL"                                : _infoMapDict(valueType=str, nakedAttribute="license_url"), 
     2705                "openTypeNameVersion"                                   : _infoMapDict(valueType=str, nakedAttribute="tt_version"), 
     2706                "openTypeNameUniqueID"                                  : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"), 
     2707                "openTypeNameDescription"                               : _infoMapDict(valueType=str, nakedAttribute="notice"), 
     2708                "openTypeNamePreferredFamilyName"               : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"), 
     2709                "openTypeNamePreferredSubfamilyName"    : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"), 
     2710                "openTypeNameCompatibleFullName"                : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"), 
     2711                "openTypeNameSampleText"                                : _infoMapDict(valueType=str, nakedAttribute=None), 
     2712                "openTypeNameWWSFamilyName"                             : _infoMapDict(valueType=str, nakedAttribute=None), 
     2713                "openTypeNameWWSSubfamilyName"                  : _infoMapDict(valueType=str, nakedAttribute=None), 
     2714                "openTypeOS2WidthClass"                                 : _infoMapDict(valueType=int, nakedAttribute="width"), 
     2715                "openTypeOS2WeightClass"                                : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True), 
     2716                "openTypeOS2Selection"                                  : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0 
     2717                "openTypeOS2VendorID"                                   : _infoMapDict(valueType=str, nakedAttribute="vendor"), 
     2718                "openTypeOS2Panose"                                             : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True), 
     2719                "openTypeOS2FamilyClass"                                : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True), 
     2720                "openTypeOS2UnicodeRanges"                              : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"), 
     2721                "openTypeOS2CodePageRanges"                             : _infoMapDict(valueType="intList", nakedAttribute="codepages"), 
     2722                "openTypeOS2TypoAscender"                               : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"), 
     2723                "openTypeOS2TypoDescender"                              : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"), 
     2724                "openTypeOS2TypoLineGap"                                : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"), 
     2725                "openTypeOS2WinAscent"                                  : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"), 
     2726                "openTypeOS2WinDescent"                                 : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True), 
     2727                "openTypeOS2Type"                                               : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True), 
     2728                "openTypeOS2SubscriptXSize"                             : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"), 
     2729                "openTypeOS2SubscriptYSize"                             : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"), 
     2730                "openTypeOS2SubscriptXOffset"                   : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"), 
     2731                "openTypeOS2SubscriptYOffset"                   : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"), 
     2732                "openTypeOS2SuperscriptXSize"                   : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"), 
     2733                "openTypeOS2SuperscriptYSize"                   : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"), 
     2734                "openTypeOS2SuperscriptXOffset"                 : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"), 
     2735                "openTypeOS2SuperscriptYOffset"                 : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"), 
     2736                "openTypeOS2StrikeoutSize"                              : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"), 
     2737                "openTypeOS2StrikeoutPosition"                  : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"), 
     2738                "openTypeVheaVertTypoAscender"                  : _infoMapDict(valueType=int, nakedAttribute=None), 
     2739                "openTypeVheaVertTypoDescender"                 : _infoMapDict(valueType=int, nakedAttribute=None), 
     2740                "openTypeVheaVertTypoLineGap"                   : _infoMapDict(valueType=int, nakedAttribute=None), 
     2741                "openTypeVheaCaretSlopeRise"                    : _infoMapDict(valueType=int, nakedAttribute=None), 
     2742                "openTypeVheaCaretSlopeRun"                             : _infoMapDict(valueType=int, nakedAttribute=None), 
     2743                "openTypeVheaCaretOffset"                               : _infoMapDict(valueType=int, nakedAttribute=None), 
     2744                "postscriptFontName"                                    : _infoMapDict(valueType=str, nakedAttribute="font_name"), 
     2745                "postscriptFullName"                                    : _infoMapDict(valueType=str, nakedAttribute="full_name"), 
     2746                "postscriptSlantAngle"                                  : _infoMapDict(valueType=float, nakedAttribute="slant_angle"), 
     2747                "postscriptUniqueID"                                    : _infoMapDict(valueType=int, nakedAttribute="unique_id"), 
     2748                "postscriptUnderlineThickness"                  : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"), 
     2749                "postscriptUnderlinePosition"                   : _infoMapDict(valueType=int, nakedAttribute="underline_position"), 
     2750                "postscriptIsFixedPitch"                                : _infoMapDict(valueType=bool, nakedAttribute="is_fixed_pitch"), 
     2751                "postscriptBlueValues"                                  : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True), 
     2752                "postscriptOtherBlues"                                  : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True), 
     2753                "postscriptFamilyBlues"                                 : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True), 
     2754                "postscriptFamilyOtherBlues"                    : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True), 
     2755                "postscriptStemSnapH"                                   : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True), 
     2756                "postscriptStemSnapV"                                   : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True), 
     2757                "postscriptBlueFuzz"                                    : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True), 
     2758                "postscriptBlueShift"                                   : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True), 
     2759                "postscriptBlueScale"                                   : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True), 
     2760                "postscriptForceBold"                                   : _infoMapDict(valueType=bool, nakedAttribute="force_bold", masterSpecific=True), 
     2761                "postscriptDefaultWidthX"                               : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True), 
     2762                "postscriptNominalWidthX"                               : _infoMapDict(valueType=int, nakedAttribute=None), 
     2763                "postscriptWeightName"                                  : _infoMapDict(valueType=str, nakedAttribute="weight"), 
     2764                "postscriptDefaultCharacter"                    : _infoMapDict(valueType=str, nakedAttribute="default_character"), 
     2765                "postscriptWindowsCharacterSet"                 : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True), 
     2766                "macintoshFONDFamilyID"                                 : _infoMapDict(valueType=int, nakedAttribute="fond_id"), 
     2767                "macintoshFONDName"                                             : _infoMapDict(valueType=str, nakedAttribute="apple_name"), 
     2768        } 
     2769        _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh. 
     2770 
    26182771        def __init__(self, font): 
    2619                 BaseInfo.__init__(self
     2772                super(RInfo, self).__init__(
    26202773                self._object = font 
    2621          
    2622         def _get_familyName(self): 
    2623                 return self._object.family_name 
    2624  
    2625         def _set_familyName(self, value): 
    2626                 self._object.family_name = value 
    2627  
    2628         familyName = property(_get_familyName, _set_familyName, doc="family_name") 
    2629          
    2630         def _get_styleName(self): 
    2631                 return self._object.style_name 
    2632  
    2633         def _set_styleName(self, value): 
    2634                 self._object.style_name = value 
    2635  
    2636         styleName = property(_get_styleName, _set_styleName, doc="style_name") 
    2637          
    2638         def _get_fullName(self): 
    2639                 return self._object.full_name 
    2640  
    2641         def _set_fullName(self, value): 
    2642                 self._object.full_name = value 
    2643  
    2644         fullName = property(_get_fullName, _set_fullName, doc="full_name") 
    2645          
    2646         def _get_fontName(self): 
    2647                 return self._object.font_name 
    2648  
    2649         def _set_fontName(self, value): 
    2650                 self._object.font_name = value 
    2651  
    2652         fontName = property(_get_fontName, _set_fontName, doc="font_name") 
    2653          
    2654         def _get_menuName(self): 
    2655                 return self._object.menu_name 
    2656  
    2657         def _set_menuName(self, value): 
    2658                 self._object.menu_name = value 
    2659  
    2660         menuName = property(_get_menuName, _set_menuName, doc="menu_name") 
    2661  
    2662         def _get_fondName(self): 
    2663                 return self._object.apple_name 
    2664  
    2665         def _set_fondName(self, value): 
    2666                 self._object.apple_name = value 
    2667  
    2668         fondName = property(_get_fondName, _set_fondName, doc="apple_name") 
    2669          
    2670         def _get_otFamilyName(self): 
    2671                 return self._object.pref_family_name 
    2672  
    2673         def _set_otFamilyName(self, value): 
    2674                 self._object.pref_family_name = value 
    2675  
    2676         otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") 
    2677  
    2678         def _get_otStyleName(self): 
    2679                 return self._object.pref_style_name 
    2680  
    2681         def _set_otStyleName(self, value): 
    2682                 self._object.pref_style_name = value 
    2683  
    2684         otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") 
    2685          
    2686         def _get_otMacName(self): 
    2687                 return self._object.mac_compatible 
    2688  
    2689         def _set_otMacName(self, value): 
    2690                 self._object.mac_compatible = value 
    2691  
    2692         otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") 
    2693          
    2694         def _get_weightValue(self): 
    2695                 return self._object.weight_code 
    2696          
    2697         def _set_weightValue(self, value): 
    2698                 value = int(round(value))       # FL can't take float - 28/8/07 / evb 
     2774 
     2775        def _environmentSetAttr(self, attr, value): 
     2776                # special fontlab workarounds 
     2777                if attr == "width": 
     2778                        warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) 
     2779                        attr = "openTypeOS2WidthClass" 
     2780                if attr == "openTypeOS2WidthClass": 
     2781                        if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL: 
     2782                                print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value 
     2783                                self._object.width = value 
     2784                        else: 
     2785                                self._object.width = _openTypeOS2WidthClass_toFL[value] 
     2786                        return 
     2787                # get the attribute data 
     2788                data = self._ufoToFLAttrMapping[attr] 
     2789                flAttr = data["nakedAttribute"] 
     2790                valueType = data["valueType"] 
     2791                masterSpecific = data["masterSpecific"] 
     2792                requiresSetNum = data["requiresSetNum"] 
     2793                specialGetSet = data["specialGetSet"] 
     2794                # warn about setting attributes not supported by FL 
     2795                if flAttr is None: 
     2796                        print "The attribute %s is not supported by FontLab. This data will not be set." % attr 
     2797                        return 
     2798                # make sure that the value is the proper type for FL 
     2799                if valueType == "intList": 
     2800                        value = [int(i) for i in value] 
     2801                elif valueType == str: 
     2802                        if value is None: 
     2803                                value = "" 
     2804                        value = value.encode(LOCAL_ENCODING) 
     2805                elif valueType == int and not isinstance(value, int): 
     2806                        value = int(round(value)) 
     2807                elif not isinstance(value, valueType): 
     2808                        value = valueType(value) 
     2809                # handle postscript hint bug in FL 
     2810                if attr in _postscriptHintAttributes: 
     2811                        value = self._handlePSHintBug(attr, value) 
     2812                # handle special cases 
     2813                if specialGetSet: 
     2814                        attr = "_set_%s" % attr 
     2815                        method = getattr(self, attr) 
     2816                        return method(value) 
     2817                # set the value 
     2818                obj = self._object 
     2819                if len(flAttr.split(".")) > 1: 
     2820                        flAttrList = flAttr.split(".") 
     2821                        for i in flAttrList[:-1]: 
     2822                                obj = getattr(obj, i) 
     2823                        flAttr = flAttrList[-1] 
     2824                ## set the foo_num attribute if necessary 
     2825                if requiresSetNum: 
     2826                        numAttr = flAttr + "_num" 
     2827                        setattr(obj, numAttr, len(value)) 
     2828                ## set master 0 if the data is master specific 
     2829                if masterSpecific: 
     2830                        subObj = getattr(obj, flAttr) 
     2831                        if valueType == "intList": 
     2832                                for index, v in enumerate(value): 
     2833                                        subObj[0][index] = v 
     2834                        else: 
     2835                                subObj[0] = value 
     2836                ## otherwise use a regular set 
     2837                else: 
     2838                        setattr(obj, flAttr, value) 
     2839 
     2840        def _environmentGetAttr(self, attr): 
     2841                # special fontlab workarounds 
     2842                if attr == "width": 
     2843                        warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) 
     2844                        attr = "openTypeOS2WidthClass" 
     2845                if attr == "openTypeOS2WidthClass": 
     2846                        value = self._object.width 
     2847                        if value not in _openTypeOS2WidthClass_fromFL: 
     2848                                print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value 
     2849                                return 
     2850                        else: 
     2851                                return _openTypeOS2WidthClass_fromFL[value] 
     2852                # get the attribute data 
     2853                data = self._ufoToFLAttrMapping[attr] 
     2854                flAttr = data["nakedAttribute"] 
     2855                valueType = data["valueType"] 
     2856                masterSpecific = data["masterSpecific"] 
     2857                specialGetSet = data["specialGetSet"] 
     2858                # warn about setting attributes not supported by FL 
     2859                if flAttr is None: 
     2860                        if not _IN_UFO_EXPORT: 
     2861                                print "The attribute %s is not supported by FontLab." % attr 
     2862                        return 
     2863                # handle special cases 
     2864                if specialGetSet: 
     2865                        attr = "_get_%s" % attr 
     2866                        method = getattr(self, attr) 
     2867                        return method() 
     2868                # get the value 
     2869                if len(flAttr.split(".")) > 1: 
     2870                        flAttrList = flAttr.split(".") 
     2871                        obj = self._object 
     2872                        for i in flAttrList: 
     2873                                obj = getattr(obj, i) 
     2874                        value = obj 
     2875                else: 
     2876                        value = getattr(self._object, flAttr) 
     2877                # grab the first master value if necessary 
     2878                if masterSpecific: 
     2879                        value = value[0] 
     2880                # convert if necessary 
     2881                if valueType == "intList": 
     2882                        value = [int(i) for i in value] 
     2883                elif valueType == str: 
     2884                        if value is None: 
     2885                                pass 
     2886                        else: 
     2887                                value = unicode(value, LOCAL_ENCODING) 
     2888                elif not isinstance(value, valueType): 
     2889                        value = valueType(value) 
     2890                return value 
     2891 
     2892        # ------------------------------ 
     2893        # individual attribute overrides 
     2894        # ------------------------------ 
     2895 
     2896        # styleMapStyleName 
     2897 
     2898        def _get_styleMapStyleName(self): 
     2899                return _styleMapStyleName_fromFL[self._object.font_style] 
     2900 
     2901        def _set_styleMapStyleName(self, value): 
     2902                value = _styleMapStyleName_toFL[value] 
     2903                self._object.font_style = value 
     2904 
     2905#       # openTypeHeadCreated 
     2906
     2907#       # fontlab epoch: 1969-12-31 19:00:00 
     2908
     2909#       def _get_openTypeHeadCreated(self): 
     2910#               value = self._object.ttinfo.head_creation 
     2911#               epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) 
     2912#               delta = datetime.timedelta(seconds=value[0]) 
     2913#               t = epoch - delta 
     2914#               string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2)) 
     2915#               return string 
     2916
     2917#       def _set_openTypeHeadCreated(self, value): 
     2918#               date, time = value.split(" ") 
     2919#               year, month, day = [int(i) for i in date.split("-")] 
     2920#               hour, minute, second = [int(i) for i in time.split(":")] 
     2921#               value = datetime.datetime(year, month, day, hour, minute, second) 
     2922#               epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) 
     2923#               delta = epoch - value 
     2924#               seconds = delta.seconds 
     2925#               self._object.ttinfo.head_creation[0] = seconds 
     2926 
     2927        # openTypeOS2WeightClass 
     2928 
     2929        def _get_openTypeOS2WeightClass(self): 
     2930                value = self._object.weight_code 
     2931                if value == -1: 
     2932                        value = None 
     2933                return value 
     2934 
     2935        def _set_openTypeOS2WeightClass(self, value): 
    26992936                self._object.weight_code = value 
    2700          
    2701         weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") 
    2702          
    2703         def _get_weightName(self): 
    2704                 return self._object.weight 
    2705          
    2706         def _set_weightName(self, value): 
    2707                 self._object.weight = value 
    2708          
    2709         weightName = property(_get_weightName, _set_weightName, doc="weight name") 
    2710          
    2711         def _get_widthName(self): 
    2712                 return self._object.width 
    2713          
    2714         def _set_widthName(self, value): 
    2715                 self._object.width = value 
    2716          
    2717         widthName = property(_get_widthName, _set_widthName, doc="width name") 
    2718  
    2719         def _get_fontStyle(self): 
    2720                 return self._object.font_style 
    2721  
    2722         def _set_fontStyle(self, value): 
    2723                 self._object.font_style = value 
    2724  
    2725         fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") 
    2726          
    2727         def _get_msCharSet(self): 
    2728                 return self._object.ms_charset 
    2729  
    2730         def _set_msCharSet(self, value): 
     2937 
     2938        # openTypeOS2WinDescent 
     2939 
     2940        def _get_openTypeOS2WinDescent(self): 
     2941                return -self._object.ttinfo.os2_us_win_descent 
     2942 
     2943        def _set_openTypeOS2WinDescent(self, value): 
     2944                if value > 0: 
     2945                        raise ValueError("FontLab can only handle negative values for openTypeOS2WinDescent.") 
     2946                self._object.ttinfo.os2_us_win_descent = abs(value) 
     2947 
     2948        # openTypeOS2Type 
     2949 
     2950        def _get_openTypeOS2Type(self): 
     2951                value = self._object.ttinfo.os2_fs_type 
     2952                intList = [] 
     2953                for bit, bitNumber in _openTypeOS2Type_fromFL.items(): 
     2954                        if value & bit: 
     2955                                intList.append(bitNumber) 
     2956                return intList 
     2957 
     2958        def _set_openTypeOS2Type(self, values): 
     2959                value = 0 
     2960                for bitNumber in values: 
     2961                        bit = _openTypeOS2Type_toFL[bitNumber] 
     2962                        value = value | bit 
     2963                self._object.ttinfo.os2_fs_type = value 
     2964 
     2965        # openTypeOS2Panose 
     2966 
     2967        def _get_openTypeOS2Panose(self): 
     2968                return [i for i in self._object.panose] 
     2969 
     2970        def _set_openTypeOS2Panose(self, values): 
     2971                for index, value in enumerate(values): 
     2972                        self._object.panose[index] = value 
     2973 
     2974        # openTypeOS2FamilyClass 
     2975 
     2976        def _get_openTypeOS2FamilyClass(self): 
     2977                value = self._object.ttinfo.os2_s_family_class 
     2978                for classID in range(15): 
     2979                        classValue = classID * 256 
     2980                        if classValue > value: 
     2981                                classID -= 1 
     2982                                classValue = classID * 256 
     2983                                break 
     2984                subclassID = value - classValue 
     2985                return [classID, subclassID] 
     2986 
     2987        def _set_openTypeOS2FamilyClass(self, values): 
     2988                classID, subclassID = values 
     2989                classID = classID * 256 
     2990                value = classID + subclassID 
     2991                self._object.ttinfo.os2_s_family_class = value 
     2992 
     2993        # postscriptWindowsCharacterSet 
     2994 
     2995        def _get_postscriptWindowsCharacterSet(self): 
     2996                value = self._object.ms_charset 
     2997                value = _postscriptWindowsCharacterSet_fromFL[value] 
     2998                return value 
     2999 
     3000        def _set_postscriptWindowsCharacterSet(self, value): 
     3001                value = _postscriptWindowsCharacterSet_toFL[value] 
    27313002                self._object.ms_charset = value 
    27323003 
    2733         msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") 
    2734          
    2735         def _get_fondID(self): 
    2736                 return self._object.fond_id 
    2737  
    2738         def _set_fondID(self, value): 
    2739                 self._object.fond_id = value 
    2740  
    2741         fondID = property(_get_fondID, _set_fondID, doc="fond_id") 
    2742          
    2743         def _get_uniqueID(self): 
    2744                 return self._object.unique_id 
    2745  
    2746         def _set_uniqueID(self, value): 
    2747                 self._object.unique_id = value 
    2748  
    2749         uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") 
    2750          
    2751         def _get_versionMajor(self): 
    2752                 return self._object.version_major 
    2753  
    2754         def _set_versionMajor(self, value): 
    2755                 self._object.version_major = value 
    2756  
    2757         versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") 
    2758          
    2759         def _get_versionMinor(self): 
    2760                 return self._object.version_minor 
    2761  
    2762         def _set_versionMinor(self, value): 
    2763                 self._object.version_minor = value 
    2764  
    2765         versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") 
    2766          
    2767         def _get_year(self): 
    2768                 return self._object.year 
    2769  
    2770         def _set_year(self, value): 
    2771                 self._object.year = value 
    2772  
    2773         year = property(_get_year, _set_year, doc="year") 
    2774          
    2775         def _get_note(self): 
    2776                 s = self._object.note 
    2777                 if s is None: 
    2778                         return s 
    2779                 return unicode(s, LOCAL_ENCODING) 
    2780  
    2781         def _set_note(self, value): 
    2782                 if value is not None: 
    2783                         value = value.encode(LOCAL_ENCODING) 
    2784                 self._object.note = value 
    2785  
    2786         note = property(_get_note, _set_note, doc="note") 
    2787          
    2788         def _get_copyright(self): 
    2789                 s = self._object.copyright 
    2790                 if s is None: 
    2791                         return s 
    2792                 return unicode(s, LOCAL_ENCODING) 
    2793  
    2794         def _set_copyright(self, value): 
    2795                 if value is not None: 
    2796                         value = value.encode(LOCAL_ENCODING) 
    2797                 self._object.copyright = value 
    2798  
    2799         copyright = property(_get_copyright, _set_copyright, doc="copyright") 
    2800          
    2801         def _get_notice(self): 
    2802                 s = self._object.notice 
    2803                 if s is None: 
    2804                         return s 
    2805                 return unicode(s, LOCAL_ENCODING) 
    2806  
    2807         def _set_notice(self, value): 
    2808                 if value is not None: 
    2809                         value = value.encode(LOCAL_ENCODING) 
    2810                 self._object.notice = value 
    2811  
    2812         notice = property(_get_notice, _set_notice, doc="notice") 
    2813          
    2814         def _get_trademark(self): 
    2815                 s = self._object.trademark 
    2816                 if s is None: 
    2817                         return s 
    2818                 return unicode(s, LOCAL_ENCODING) 
    2819  
    2820         def _set_trademark(self, value): 
    2821                 if value is not None: 
    2822                         value = value.encode(LOCAL_ENCODING) 
    2823                 self._object.trademark = value 
    2824  
    2825         trademark = property(_get_trademark, _set_trademark, doc="trademark") 
    2826          
    2827         def _get_license(self): 
    2828                 s = self._object.license 
    2829                 if s is None: 
    2830                         return s 
    2831                 return unicode(s, LOCAL_ENCODING) 
    2832  
    2833         def _set_license(self, value): 
    2834                 if value is not None: 
    2835                         value = value.encode(LOCAL_ENCODING) 
    2836                 self._object.license = value 
    2837  
    2838         license = property(_get_license, _set_license, doc="license") 
    2839          
    2840         def _get_licenseURL(self): 
    2841                 return self._object.license_url 
    2842  
    2843         def _set_licenseURL(self, value): 
    2844                 self._object.license_url = value 
    2845  
    2846         licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") 
    2847          
    2848         def _get_createdBy(self): 
    2849                 s = self._object.source 
    2850                 if s is None: 
    2851                         return s 
    2852                 return unicode(s, LOCAL_ENCODING) 
    2853  
    2854         def _set_createdBy(self, value): 
    2855                 if value is not None: 
    2856                         value = value.encode(LOCAL_ENCODING) 
    2857                 self._object.source = value 
    2858  
    2859         createdBy = property(_get_createdBy, _set_createdBy, doc="source") 
    2860          
    2861         def _get_designer(self): 
    2862                 s = self._object.designer 
    2863                 if s is None: 
    2864                         return s 
    2865                 return unicode(s, LOCAL_ENCODING) 
    2866  
    2867         def _set_designer(self, value): 
    2868                 if value is not None: 
    2869                         value = value.encode(LOCAL_ENCODING) 
    2870                 self._object.designer = value 
    2871  
    2872         designer = property(_get_designer, _set_designer, doc="designer") 
    2873          
    2874         def _get_designerURL(self): 
    2875                 return self._object.designer_url 
    2876  
    2877         def _set_designerURL(self, value): 
    2878                 self._object.designer_url = value 
    2879  
    2880         designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") 
    2881          
    2882         def _get_vendorURL(self): 
    2883                 return self._object.vendor_url 
    2884  
    2885         def _set_vendorURL(self, value): 
    2886                 self._object.vendor_url = value 
    2887  
    2888         vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") 
    2889          
    2890         def _get_ttVendor(self): 
    2891                 return self._object.vendor 
    2892  
    2893         def _set_ttVendor(self, value): 
    2894                 self._object.vendor = value 
    2895  
    2896         ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") 
    2897          
    2898         def _get_ttUniqueID(self): 
    2899                 return self._object.tt_u_id 
    2900  
    2901         def _set_ttUniqueID(self, value): 
    2902                 self._object.tt_u_id = value 
    2903  
    2904         ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") 
    2905          
    2906         def _get_ttVersion(self): 
    2907                 return self._object.tt_version 
    2908  
    2909         def _set_ttVersion(self, value): 
    2910                 self._object.tt_version = value 
    2911  
    2912         ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") 
    2913          
    2914         def _get_unitsPerEm(self): 
    2915                 return self._object.upm 
    2916                  
    2917         def _set_unitsPerEm(self, value): 
    2918                 self._object.upm = int(round(value)) 
    2919  
    2920         unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") 
    2921          
    2922         def _get_ascender(self): 
    2923                 return self._object.ascender[0] 
    2924          
    2925         def _set_ascender(self, value): 
    2926                 value = int(round(value)) 
    2927                 self._object.ascender[0] = value 
    2928          
    2929         ascender = property(_get_ascender, _set_ascender, doc="ascender value") 
    2930  
    2931         def _get_descender(self): 
    2932                 return self._object.descender[0] 
    2933          
    2934         def _set_descender(self, value): 
    2935                 value = int(round(value)) 
    2936                 self._object.descender[0] = value 
    2937                  
    2938         descender = property(_get_descender, _set_descender, doc="descender value") 
    2939          
    2940         def _get_capHeight(self): 
    2941                 return self._object.cap_height[0] 
    2942          
    2943         def _set_capHeight(self, value): 
    2944                 value = int(round(value)) 
    2945                 self._object.cap_height[0] = value 
    2946                  
    2947         capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") 
    2948  
    2949         def _get_xHeight(self): 
    2950                 return self._object.x_height[0] 
    2951                  
    2952         def _set_xHeight(self, value): 
    2953                 value = int(round(value)) 
    2954                 self._object.x_height[0] = value 
    2955                  
    2956         xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") 
    2957  
    2958         def _get_defaultWidth(self): 
    2959                 return self._object.default_width[0] 
    2960          
    2961         def _set_defaultWidth(self, value): 
    2962                 value = int(round(value)) 
    2963                 self._object.default_width[0] = value    
    2964  
    2965         defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") 
    2966          
    2967         def _get_italicAngle(self): 
    2968                 return self._object.italic_angle 
    2969  
    2970         def _set_italicAngle(self, value): 
    2971                 try: 
    2972                         self._object.italic_angle = float(value) 
    2973                 except TypeError: 
    2974                         print "robofab.objects.objectsFL: can't set italic angle, possibly a FontLab API limitation" 
    2975  
    2976         italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") 
    2977          
    2978         def _get_slantAngle(self): 
    2979                 return self._object.slant_angle 
    2980  
    2981         def _set_slantAngle(self, value): 
    2982                 try: 
    2983                         self._object.slant_angle = float(value) 
    2984                 except TypeError: 
    2985                         print "robofab.objects.objectsFL: can't set slant angle, possibly a FontLab API limitation" 
    2986  
    2987         slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") 
    2988          
    2989         #is this still needed? 
    2990         def _get_full_name(self): 
    2991                 return self._object.full_name 
    2992  
    2993         def _set_full_name(self, value): 
    2994                 self._object.full_name = value 
    2995  
    2996         full_name = property(_get_full_name, _set_full_name, doc="FL: full_name") 
    2997          
    2998         #is this still needed? 
    2999         def _get_ms_charset(self): 
    3000                 return self._object.ms_charset 
    3001  
    3002         def _set_ms_charset(self, value): 
    3003                 self._object.ms_charset = value 
    3004  
    3005         ms_charset = property(_get_ms_charset, _set_ms_charset, doc="FL: ms_charset") 
     3004        # ----------------- 
     3005        # FL bug workaround 
     3006        # ----------------- 
     3007 
     3008        def _handlePSHintBug(self, attribute, values): 
     3009                """Function to handle problems with FontLab not allowing the max number of 
     3010                alignment zones to be set to the max number. 
     3011                Input:  the name of the zones and the values to be set 
     3012                Output: a warning when there are too many values to be set 
     3013                                and the max values which FontLab will allow. 
     3014                """ 
     3015                originalValues = values 
     3016                truncatedLength = None 
     3017                if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"): 
     3018                        if len(values) > 10: 
     3019                                values = values[:10] 
     3020                                truncatedLength = 10 
     3021                elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"): 
     3022                        if len(values) > 12: 
     3023                                values = values[:12] 
     3024                                truncatedLength = 12 
     3025                elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"): 
     3026                        if len(values) > 8: 
     3027                                values = values[:8] 
     3028                                truncatedLength = 8 
     3029                if truncatedLength is not None: 
     3030                         print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:])) 
     3031                return values 
     3032 
     3033 
     3034class RFeatures(BaseFeatures): 
     3035 
     3036        _title = "FLFeatures" 
     3037 
     3038        def __init__(self, font): 
     3039                super(RFeatures, self).__init__() 
     3040                self._object = font 
     3041 
     3042        def _get_text(self): 
     3043                naked = self._object 
     3044                features = [] 
     3045                if naked.ot_classes: 
     3046                        features.append(_normalizeLineEndings(naked.ot_classes)) 
     3047                for feature in naked.features: 
     3048                        features.append(_normalizeLineEndings(feature.value)) 
     3049                return "".join(features) 
     3050 
     3051        def _set_text(self, value): 
     3052                classes, features = splitFeaturesForFontLab(value) 
     3053                naked = self._object 
     3054                naked.ot_classes = classes 
     3055                naked.features.clean() 
     3056                for featureName, featureText in features: 
     3057                        f = Feature(featureName, featureText) 
     3058                        naked.features.append(f) 
     3059 
     3060        text = property(_get_text, _set_text, doc="raw feature text.") 
     3061 
  • trunk/Lib/robofab/objects/objectsRF.py

    r60 r171  
    22 
    33from robofab import RoboFabError, RoboFabWarning 
    4 from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseLib,\ 
     4from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ 
    55                BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ 
    66                relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\ 
     
    4646        """Make a new font""" 
    4747        new = RFont() 
    48         new.info.familyName = familyName 
    49         new.info.styleName = styleName 
     48        if familyName is not None: 
     49                new.info.familyName = familyName 
     50        if styleName is not None: 
     51                new.info.styleName = styleName 
    5052        return new 
    5153         
     
    6870         
    6971        def __init__(self, aFont=None, data=None): 
    70                 # read the data from the font.lib, it won't be anywhere else 
     72                self.setParent(aFont) 
    7173                BasePostScriptFontHintValues.__init__(self) 
    7274                if aFont is not None: 
    73                         self.setParent(aFont) 
     75                        # in version 1, this data was stored in the lib 
     76                        # if it is still there, guess that it is correct 
     77                        # move it to font info and remove it from the lib. 
    7478                        libData = aFont.lib.get(postScriptHintDataLibKey) 
    7579                        if libData is not None: 
    7680                                self.fromDict(libData) 
     81                                del libData[postScriptHintDataLibKey] 
    7782                if data is not None: 
    7883                        self.fromDict(data) 
     
    124129                self.info = RInfo() 
    125130                self.info.setParent(self) 
     131                self.features = RFeatures() 
     132                self.features.setParent(self) 
    126133                self.groups = RGroups() 
    127134                self.groups.setParent(self) 
    128135                self.lib = RLib() 
    129136                self.lib.setParent(self) 
    130                 self.psHints = PostScriptFontHintValues(self) 
    131                 self.psHints.setParent(self) 
    132                  
    133137                if path: 
    134138                        self._loadData(path) 
     139                else: 
     140                        self.psHints = PostScriptFontHintValues(self) 
     141                        self.psHints.setParent(self) 
    135142                 
    136143        def __setitem__(self, glyphName, glyph): 
     
    153160         
    154161        def _loadData(self, path): 
    155                 #Load the data into the font 
    156162                from robofab.ufoLib import UFOReader 
    157                 u = UFOReader(path) 
    158                 u.readInfo(self.info) 
    159                 self.kerning.update(u.readKerning()) 
     163                reader = UFOReader(path) 
     164                fontLib = reader.readLib() 
     165                # info 
     166                reader.readInfo(self.info) 
     167                # kerning 
     168                self.kerning.update(reader.readKerning()) 
    160169                self.kerning.setChanged(False) 
    161                 self.groups.update(u.readGroups()) 
    162                 self.lib.update(u.readLib()) 
    163                 # after reading the lib, read hinting data from the lib 
     170                # groups 
     171                self.groups.update(reader.readGroups()) 
     172                # features 
     173                if reader.formatVersion == 1: 
     174                        # migrate features from the lib 
     175                        features = [] 
     176                        classes = fontLib.get("org.robofab.opentype.classes") 
     177                        if classes is not None: 
     178                                del fontLib["org.robofab.opentype.classes"] 
     179                                features.append(classes) 
     180                        splitFeatures = fontLib.get("org.robofab.opentype.features") 
     181                        if splitFeatures is not None: 
     182                                order = fontLib.get("org.robofab.opentype.featureorder") 
     183                                if order is None: 
     184                                        order = splitFeatures.keys() 
     185                                        order.sort() 
     186                                else: 
     187                                        del fontLib["org.robofab.opentype.featureorder"] 
     188                                del fontLib["org.robofab.opentype.features"] 
     189                                for tag in order: 
     190                                        oneFeature = splitFeatures.get(tag) 
     191                                        if oneFeature is not None: 
     192                                                features.append(oneFeature) 
     193                        features = "\n".join(features) 
     194                else: 
     195                        features = reader.readFeatures() 
     196                self.features.text = features 
     197                # hint data 
    164198                self.psHints = PostScriptFontHintValues(self) 
    165                 self._glyphSet = u.getGlyphSet() 
     199                if postScriptHintDataLibKey in fontLib: 
     200                        del fontLib[postScriptHintDataLibKey] 
     201                # lib 
     202                self.lib.update(fontLib) 
     203                # glyphs 
     204                self._glyphSet = reader.getGlyphSet() 
    166205                self._hasNotChanged(doGlyphs=False) 
    167                  
     206 
    168207        def _loadGlyph(self, glyphName): 
    169208                """Load a single glyph from the glyphSet, on request.""" 
     
    330369                 
    331370 
    332         def save(self, destDir=None, doProgress=False, saveNow=False): 
     371        def save(self, destDir=None, doProgress=False, formatVersion=2): 
    333372                """Save the Font in UFO format.""" 
    334373                # XXX note that when doing "save as" by specifying the destDir argument 
     
    338377                # save. 
    339378                from robofab.ufoLib import UFOWriter 
     379                from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab 
    340380                # if no destination is given, or if 
    341381                # the given destination is the current 
     
    346386                else: 
    347387                        saveAs = True 
    348                 u = UFOWriter(destDir) 
     388                # start a progress bar 
    349389                nonGlyphCount = 5 
    350390                bar = None 
    351391                if doProgress: 
    352392                        from robofab.interface.all.dialogs import ProgressBar 
    353                         bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self._object.keys())) 
     393                        bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys())) 
     394                # write 
     395                writer = UFOWriter(destDir, formatVersion=formatVersion) 
    354396                try: 
    355                         #if self.info.changed: 
     397                        # make a shallow copy of the lib. stuff may be added to it. 
     398                        fontLib = dict(self.lib) 
     399                        # info 
    356400                        if bar: 
    357                                 bar.label('Saving info...'
    358                         u.writeInfo(self.info) 
     401                                bar.label("Saving info..."
     402                        writer.writeInfo(self.info) 
    359403                        if bar: 
    360404                                bar.tick() 
     405                        # kerning 
    361406                        if self.kerning.changed or saveAs: 
    362407                                if bar: 
    363                                         bar.label('Saving kerning...') 
    364                                 u.writeKerning(self.kerning.asDict()) 
    365                                 self.kerning.setChanged(False) 
     408                                        bar.label("Saving kerning...") 
     409                                writer.writeKerning(self.kerning.asDict()) 
     410                                if bar: 
     411                                        bar.tick() 
     412                        # groups 
     413                        if bar: 
     414                                bar.label("Saving groups...") 
     415                        writer.writeGroups(self.groups) 
    366416                        if bar: 
    367417                                bar.tick() 
    368                         #if self.groups.changed: 
     418                        # features 
    369419                        if bar: 
    370                                 bar.label('Saving groups...') 
    371                         u.writeGroups(self.groups) 
     420                                bar.label("Saving features...") 
     421                        features = self.features.text 
     422                        if features is None: 
     423                                features = "" 
     424                        if formatVersion == 2: 
     425                                writer.writeFeatures(features) 
     426                        elif formatVersion == 1: 
     427                                classes, features = splitFeaturesForFontLab(features) 
     428                                if classes: 
     429                                        fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n" 
     430                                if features: 
     431                                        featureDict = {} 
     432                                        for featureName, featureText in features: 
     433                                                featureDict[featureName] = featureText.strip() + "\n" 
     434                                        fontLib["org.robofab.opentype.features"] = featureDict 
     435                                        fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features] 
    372436                        if bar: 
    373437                                bar.tick() 
    374  
    375                         # save postscript hint data 
    376                         self.lib[postScriptHintDataLibKey] = self.psHints.asDict() 
    377  
    378                         #if self.lib.changed: 
     438                        # lib 
     439                        if formatVersion == 1: 
     440                                fontLib[postScriptHintDataLibKey] = self.psHints.asDict() 
    379441                        if bar: 
    380                                 bar.label('Saving lib...'
    381                         u.writeLib(self.lib) 
     442                                bar.label("Saving lib..."
     443                        writer.writeLib(fontLib) 
    382444                        if bar: 
    383445                                bar.tick() 
     446                        # glyphs 
    384447                        glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() 
    385                         glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) 
     448 
     449                        glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) 
    386450                        if len(self._scheduledForDeletion) != 0: 
    387451                                if bar: 
    388                                         bar.label('Removing deleted glyphs......'
     452                                        bar.label("Removing deleted glyphs..."
    389453                                for glyphName in self._scheduledForDeletion: 
    390454                                        if glyphSet.has_key(glyphName): 
     
    393457                                        bar.tick() 
    394458                        if bar: 
    395                                 bar.label('Saving glyphs...'
     459                                bar.label("Saving glyphs..."
    396460                        count = nonGlyphCount 
    397461                        if saveAs: 
     
    408472                        glyphSet.writeContents() 
    409473                        self._glyphSet = glyphSet 
     474                # only blindly stop if the user says to 
    410475                except KeyboardInterrupt: 
    411476                        bar.close() 
    412477                        bar = None 
     478                # kill the progress bar 
    413479                if bar: 
    414480                        bar.close() 
     481                # reset internal stuff 
    415482                self._path = destDir 
    416483                self._scheduledForDeletion = [] 
    417484                self.setChanged(False) 
    418                  
     485 
    419486        def newGlyph(self, glyphName, clear=True): 
    420487                """Make a new glyph with glyphName 
     
    11271194class RInfo(BaseInfo): 
    11281195         
    1129         _title = "RoboFabFonInfo" 
    1130          
    1131         def __init__(self): 
    1132                 BaseInfo.__init__(self) 
    1133                 self.selected = False 
    1134                  
    1135                 self._familyName = None 
    1136                 self._styleName = None 
    1137                 self._fullName = None 
    1138                 self._fontName = None 
    1139                 self._menuName = None 
    1140                 self._fondName = None 
    1141                 self._otFamilyName = None 
    1142                 self._otStyleName = None 
    1143                 self._otMacName = None 
    1144                 self._weightValue = None 
    1145                 self._weightName = None 
    1146                 self._widthName = None 
    1147                 self._fontStyle = None 
    1148                 self._msCharSet = None 
    1149                 self._note = None 
    1150                 self._fondID = None 
    1151                 self._uniqueID = None 
    1152                 self._versionMajor = None 
    1153                 self._versionMinor = None 
    1154                 self._year = None 
    1155                 self._copyright = None 
    1156                 self._notice = None 
    1157                 self._trademark = None 
    1158                 self._license = None 
    1159                 self._licenseURL = None 
    1160                 self._createdBy = None 
    1161                 self._designer = None 
    1162                 self._designerURL = None 
    1163                 self._vendorURL = None 
    1164                 self._ttVendor = None 
    1165                 self._ttUniqueID = None 
    1166                 self._ttVersion = None 
    1167                 self._unitsPerEm = None 
    1168                 self._ascender = None 
    1169                 self._descender = None 
    1170                 self._capHeight = None 
    1171                 self._xHeight = None 
    1172                 self._defaultWidth = None 
    1173                 self._italicAngle = None 
    1174                 self._slantAngle = None 
    1175          
    1176         def _get_familyName(self): 
    1177                 return self._familyName 
    1178          
    1179         def _set_familyName(self, value): 
    1180                 self._familyName = value 
    1181          
    1182         familyName = property(_get_familyName, _set_familyName, doc="family_name") 
    1183          
    1184         def _get_styleName(self): 
    1185                 return self._styleName 
    1186          
    1187         def _set_styleName(self, value): 
    1188                 self._styleName = value 
    1189          
    1190         styleName = property(_get_styleName, _set_styleName, doc="style_name") 
    1191          
    1192         def _get_fullName(self): 
    1193                 return self._fullName 
    1194          
    1195         def _set_fullName(self, value): 
    1196                 self._fullName = value 
    1197          
    1198         fullName = property(_get_fullName, _set_fullName, doc="full_name") 
    1199          
    1200         def _get_fontName(self): 
    1201                 return self._fontName 
    1202          
    1203         def _set_fontName(self, value): 
    1204                 self._fontName = value 
    1205          
    1206         fontName = property(_get_fontName, _set_fontName, doc="font_name") 
    1207          
    1208         def _get_menuName(self): 
    1209                 return self._menuName 
    1210          
    1211         def _set_menuName(self, value): 
    1212                 self._menuName = value 
    1213          
    1214         menuName = property(_get_menuName, _set_menuName, doc="menu_name") 
    1215          
    1216         def _get_fondName(self): 
    1217                 return self._fondName 
    1218          
    1219         def _set_fondName(self, value): 
    1220                 self._fondName = value 
    1221          
    1222         fondName = property(_get_fondName, _set_fondName, doc="apple_name") 
    1223          
    1224         def _get_otFamilyName(self): 
    1225                 return self._otFamilyName 
    1226          
    1227         def _set_otFamilyName(self, value): 
    1228                 self._otFamilyName = value 
    1229          
    1230         otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") 
    1231          
    1232         def _get_otStyleName(self): 
    1233                 return self._otStyleName 
    1234          
    1235         def _set_otStyleName(self, value): 
    1236                 self._otStyleName = value 
    1237          
    1238         otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") 
    1239          
    1240         def _get_otMacName(self): 
    1241                 return self._otMacName 
    1242          
    1243         def _set_otMacName(self, value): 
    1244                 self._otMacName = value 
    1245          
    1246         otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") 
    1247          
    1248         def _get_weightValue(self): 
    1249                 return self._weightValue 
    1250          
    1251         def _set_weightValue(self, value): 
    1252                 self._weightValue = value 
    1253          
    1254         weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") 
    1255          
    1256         def _get_weightName(self): 
    1257                 return self._weightName 
    1258          
    1259         def _set_weightName(self, value): 
    1260                 self._weightName = value 
    1261          
    1262         weightName = property(_get_weightName, _set_weightName, doc="weight name") 
    1263          
    1264         def _get_widthName(self): 
    1265                 return self._widthName 
    1266          
    1267         def _set_widthName(self, value): 
    1268                 self._widthName = value 
    1269          
    1270         widthName = property(_get_widthName, _set_widthName, doc="width name") 
    1271  
    1272         def _get_fontStyle(self): 
    1273                 return self._fontStyle 
    1274          
    1275         def _set_fontStyle(self, value): 
    1276                 self._fontStyle = value 
    1277          
    1278         fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") 
    1279          
    1280         def _get_msCharSet(self): 
    1281                 return self._msCharSet 
    1282          
    1283         def _set_msCharSet(self, value): 
    1284                 self._msCharSet = value 
    1285          
    1286         msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") 
    1287          
    1288         def _get_note(self): 
    1289                 return self._note 
    1290          
    1291         def _set_note(self, value): 
    1292                 self._note = value 
    1293          
    1294         note = property(_get_note, _set_note, doc="note") 
    1295          
    1296         def _get_fondID(self): 
    1297                 return self._fondID 
    1298          
    1299         def _set_fondID(self, value): 
    1300                 self._fondID = value 
    1301          
    1302         fondID = property(_get_fondID, _set_fondID, doc="fond_id") 
    1303          
    1304         def _get_uniqueID(self): 
    1305                 return self._uniqueID 
    1306          
    1307         def _set_uniqueID(self, value): 
    1308                 self._uniqueID = value 
    1309          
    1310         uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") 
    1311          
    1312         def _get_versionMajor(self): 
    1313                 return self._versionMajor 
    1314          
    1315         def _set_versionMajor(self, value): 
    1316                 self._versionMajor = value 
    1317          
    1318         versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") 
    1319          
    1320         def _get_versionMinor(self): 
    1321                 return self._versionMinor 
    1322          
    1323         def _set_versionMinor(self, value): 
    1324                 self._versionMinor = value 
    1325          
    1326         versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") 
    1327          
    1328         def _get_year(self): 
    1329                 return self._year 
    1330          
    1331         def _set_year(self, value): 
    1332                 self._year = value 
    1333          
    1334         year = property(_get_year, _set_year, doc="year") 
    1335          
    1336         def _get_copyright(self): 
    1337                 return self._copyright 
    1338          
    1339         def _set_copyright(self, value): 
    1340                 self._copyright = value 
    1341          
    1342         copyright = property(_get_copyright, _set_copyright, doc="copyright") 
    1343          
    1344         def _get_notice(self): 
    1345                 return self._notice 
    1346          
    1347         def _set_notice(self, value): 
    1348                 self._notice = value 
    1349          
    1350         notice = property(_get_notice, _set_notice, doc="notice") 
    1351          
    1352         def _get_trademark(self): 
    1353                 return self._trademark 
    1354          
    1355         def _set_trademark(self, value): 
    1356                 self._trademark = value 
    1357          
    1358         trademark = property(_get_trademark, _set_trademark, doc="trademark") 
    1359          
    1360         def _get_license(self): 
    1361                 return self._license 
    1362          
    1363         def _set_license(self, value): 
    1364                 self._license = value 
    1365          
    1366         license = property(_get_license, _set_license, doc="license") 
    1367          
    1368         def _get_licenseURL(self): 
    1369                 return self._licenseURL 
    1370          
    1371         def _set_licenseURL(self, value): 
    1372                 self._licenseURL = value 
    1373          
    1374         licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") 
    1375          
    1376         def _get_designer(self): 
    1377                 return self._designer 
    1378          
    1379         def _set_designer(self, value): 
    1380                 self._designer = value 
    1381          
    1382         designer = property(_get_designer, _set_designer, doc="designer") 
    1383          
    1384         def _get_createdBy(self): 
    1385                 return self._createdBy 
    1386          
    1387         def _set_createdBy(self, value): 
    1388                 self._createdBy = value 
    1389          
    1390         createdBy = property(_get_createdBy, _set_createdBy, doc="source") 
    1391          
    1392         def _get_designerURL(self): 
    1393                 return self._designerURL 
    1394          
    1395         def _set_designerURL(self, value): 
    1396                 self._designerURL = value 
    1397          
    1398         designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") 
    1399          
    1400         def _get_vendorURL(self): 
    1401                 return self._vendorURL 
    1402          
    1403         def _set_vendorURL(self, value): 
    1404                 self._vendorURL = value 
    1405          
    1406         vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") 
    1407          
    1408         def _get_ttVendor(self): 
    1409                 return self._ttVendor 
    1410          
    1411         def _set_ttVendor(self, value): 
    1412                 self._ttVendor = value 
    1413          
    1414         ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") 
    1415          
    1416         def _get_ttUniqueID(self): 
    1417                 return self._ttUniqueID 
    1418          
    1419         def _set_ttUniqueID(self, value): 
    1420                 self._ttUniqueID = value 
    1421          
    1422         ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") 
    1423          
    1424         def _get_ttVersion(self): 
    1425                 return self._ttVersion 
    1426          
    1427         def _set_ttVersion(self, value): 
    1428                 self._ttVersion = value 
    1429          
    1430         ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") 
    1431          
    1432         def _get_unitsPerEm(self): 
    1433                 return self._unitsPerEm 
    1434                  
    1435         def _set_unitsPerEm(self, value): 
    1436                 self._unitsPerEm = value 
    1437          
    1438         unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") 
    1439          
    1440         def _get_ascender(self): 
    1441                 return self._ascender 
    1442          
    1443         def _set_ascender(self, value): 
    1444                 self._ascender = value 
    1445          
    1446         ascender = property(_get_ascender, _set_ascender, doc="ascender value") 
    1447          
    1448         def _get_descender(self): 
    1449                 return self._descender 
    1450          
    1451         def _set_descender(self, value): 
    1452                 self._descender = value 
    1453          
    1454         descender = property(_get_descender, _set_descender, doc="descender value") 
    1455          
    1456         def _get_capHeight(self): 
    1457                 return self._capHeight 
    1458          
    1459         def _set_capHeight(self, value): 
    1460                 self._capHeight = value 
    1461          
    1462         capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") 
    1463          
    1464         def _get_xHeight(self): 
    1465                 return self._xHeight 
    1466          
    1467         def _set_xHeight(self, value): 
    1468                 self._xHeight = value 
    1469          
    1470         xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") 
    1471          
    1472         def _get_defaultWidth(self): 
    1473                 return self._defaultWidth 
    1474          
    1475         def _set_defaultWidth(self, value): 
    1476                 self._defaultWidth = value 
    1477          
    1478         defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") 
    1479          
    1480         def _get_italicAngle(self): 
    1481                 return self._italicAngle 
    1482          
    1483         def _set_italicAngle(self, value): 
    1484                 self._italicAngle = value 
    1485          
    1486         italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") 
    1487          
    1488         def _get_slantAngle(self): 
    1489                 return self._slantAngle 
    1490          
    1491         def _set_slantAngle(self, value): 
    1492                 self._slantAngle = value 
    1493          
    1494         slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") 
    1495  
     1196        _title = "RoboFabFontInfo" 
     1197 
     1198class RFeatures(BaseFeatures): 
     1199 
     1200        _title = "RoboFabFeatures" 
     1201 
  • trunk/Lib/robofab/test/runAll.py

    r1 r171  
    1818                except ImportError: 
    1919                        print "*** skipped", fileName 
     20                        continue 
    2021         
    2122                suites.append(loader.loadTestsFromModule(mod)) 
  • trunk/Lib/robofab/test/testSupport.py

    r1 r171  
    4848        testRunner.run(testSuite) 
    4949 
     50# font info values used by several tests 
     51 
     52fontInfoVersion1 = { 
     53        "familyName"   : "Some Font (Family Name)", 
     54        "styleName"        : "Regular (Style Name)", 
     55        "fullName"         : "Some Font-Regular (Postscript Full Name)", 
     56        "fontName"         : "SomeFont-Regular (Postscript Font Name)", 
     57        "menuName"         : "Some Font Regular (Style Map Family Name)", 
     58        "fontStyle"        : 64, 
     59        "note"             : "A note.", 
     60        "versionMajor" : 1, 
     61        "versionMinor" : 0, 
     62        "year"             : 2008, 
     63        "copyright"        : "Copyright Some Foundry.", 
     64        "notice"           : "Some Font by Some Designer for Some Foundry.", 
     65        "trademark"        : "Trademark Some Foundry", 
     66        "license"          : "License info for Some Foundry.", 
     67        "licenseURL"   : "http://somefoundry.com/license", 
     68        "createdBy"        : "Some Foundry", 
     69        "designer"         : "Some Designer", 
     70        "designerURL"  : "http://somedesigner.com", 
     71        "vendorURL"        : "http://somefoundry.com", 
     72        "unitsPerEm"   : 1000, 
     73        "ascender"         : 750, 
     74        "descender"        : -250, 
     75        "capHeight"        : 750, 
     76        "xHeight"          : 500, 
     77        "defaultWidth" : 400, 
     78        "slantAngle"   : -12.5, 
     79        "italicAngle"  : -12.5, 
     80        "widthName"        : "Medium (normal)", 
     81        "weightName"   : "Medium", 
     82        "weightValue"  : 500, 
     83        "fondName"         : "SomeFont Regular (FOND Name)", 
     84        "otFamilyName" : "Some Font (Preferred Family Name)", 
     85        "otStyleName"  : "Regular (Preferred Subfamily Name)", 
     86        "otMacName"        : "Some Font Regular (Compatible Full Name)", 
     87        "msCharSet"        : 0, 
     88        "fondID"           : 15000, 
     89        "uniqueID"         : 4000000, 
     90        "ttVendor"         : "SOME", 
     91        "ttUniqueID"   : "OpenType name Table Unique ID", 
     92        "ttVersion"        : "OpenType name Table Version", 
     93} 
     94 
     95fontInfoVersion2 = { 
     96        "familyName"                                             : "Some Font (Family Name)", 
     97        "styleName"                                                      : "Regular (Style Name)", 
     98        "styleMapFamilyName"                             : "Some Font Regular (Style Map Family Name)", 
     99        "styleMapStyleName"                                      : "regular", 
     100        "versionMajor"                                           : 1, 
     101        "versionMinor"                                           : 0, 
     102        "year"                                                           : 2008, 
     103        "copyright"                                                      : "Copyright Some Foundry.", 
     104        "trademark"                                                      : "Trademark Some Foundry", 
     105        "unitsPerEm"                                             : 1000, 
     106        "descender"                                                      : -250, 
     107        "xHeight"                                                        : 500, 
     108        "capHeight"                                                      : 750, 
     109        "ascender"                                                       : 750, 
     110        "italicAngle"                                            : -12.5, 
     111        "note"                                                           : "A note.", 
     112        "openTypeHeadCreated"                            : "2000/01/01 00:00:00", 
     113        "openTypeHeadLowestRecPPEM"                      : 10, 
     114        "openTypeHeadFlags"                                      : [0, 1], 
     115        "openTypeHheaAscender"                           : 750, 
     116        "openTypeHheaDescender"                          : -250, 
     117        "openTypeHheaLineGap"                            : 200, 
     118        "openTypeHheaCaretSlopeRise"             : 1, 
     119        "openTypeHheaCaretSlopeRun"                      : 0, 
     120        "openTypeHheaCaretOffset"                        : 0, 
     121        "openTypeNameDesigner"                           : "Some Designer", 
     122        "openTypeNameDesignerURL"                        : "http://somedesigner.com", 
     123        "openTypeNameManufacturer"                       : "Some Foundry", 
     124        "openTypeNameManufacturerURL"            : "http://somefoundry.com", 
     125        "openTypeNameLicense"                            : "License info for Some Foundry.", 
     126        "openTypeNameLicenseURL"                         : "http://somefoundry.com/license", 
     127        "openTypeNameVersion"                            : "OpenType name Table Version", 
     128        "openTypeNameUniqueID"                           : "OpenType name Table Unique ID", 
     129        "openTypeNameDescription"                        : "Some Font by Some Designer for Some Foundry.", 
     130        "openTypeNamePreferredFamilyName"        : "Some Font (Preferred Family Name)", 
     131        "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)", 
     132        "openTypeNameCompatibleFullName"         : "Some Font Regular (Compatible Full Name)", 
     133        "openTypeNameSampleText"                         : "Sample Text for Some Font.", 
     134        "openTypeNameWWSFamilyName"                      : "Some Font (WWS Family Name)", 
     135        "openTypeNameWWSSubfamilyName"           : "Regular (WWS Subfamily Name)", 
     136        "openTypeOS2WidthClass"                          : 5, 
     137        "openTypeOS2WeightClass"                         : 500, 
     138        "openTypeOS2Selection"                           : [3], 
     139        "openTypeOS2VendorID"                            : "SOME", 
     140        "openTypeOS2Panose"                                      : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 
     141        "openTypeOS2FamilyClass"                         : [1, 1], 
     142        "openTypeOS2UnicodeRanges"                       : [0, 1], 
     143        "openTypeOS2CodePageRanges"                      : [0, 1], 
     144        "openTypeOS2TypoAscender"                        : 750, 
     145        "openTypeOS2TypoDescender"                       : -250, 
     146        "openTypeOS2TypoLineGap"                         : 200, 
     147        "openTypeOS2WinAscent"                           : 750, 
     148        "openTypeOS2WinDescent"                          : -250, 
     149        "openTypeOS2Type"                                        : [], 
     150        "openTypeOS2SubscriptXSize"                      : 200, 
     151        "openTypeOS2SubscriptYSize"                      : 400, 
     152        "openTypeOS2SubscriptXOffset"            : 0, 
     153        "openTypeOS2SubscriptYOffset"            : -100, 
     154        "openTypeOS2SuperscriptXSize"            : 200, 
     155        "openTypeOS2SuperscriptYSize"            : 400, 
     156        "openTypeOS2SuperscriptXOffset"          : 0, 
     157        "openTypeOS2SuperscriptYOffset"          : 200, 
     158        "openTypeOS2StrikeoutSize"                       : 20, 
     159        "openTypeOS2StrikeoutPosition"           : 300, 
     160        "openTypeVheaVertTypoAscender"           : 750, 
     161        "openTypeVheaVertTypoDescender"          : -250, 
     162        "openTypeVheaVertTypoLineGap"            : 200, 
     163        "openTypeVheaCaretSlopeRise"             : 0, 
     164        "openTypeVheaCaretSlopeRun"                      : 1, 
     165        "openTypeVheaCaretOffset"                        : 0, 
     166        "postscriptFontName"                             : "SomeFont-Regular (Postscript Font Name)", 
     167        "postscriptFullName"                             : "Some Font-Regular (Postscript Full Name)", 
     168        "postscriptSlantAngle"                           : -12.5, 
     169        "postscriptUniqueID"                             : 4000000, 
     170        "postscriptUnderlineThickness"           : 20, 
     171        "postscriptUnderlinePosition"            : -200, 
     172        "postscriptIsFixedPitch"                         : False, 
     173        "postscriptBlueValues"                           : [500, 510], 
     174        "postscriptOtherBlues"                           : [-250, -260], 
     175        "postscriptFamilyBlues"                          : [500, 510], 
     176        "postscriptFamilyOtherBlues"             : [-250, -260], 
     177        "postscriptStemSnapH"                            : [100, 120], 
     178        "postscriptStemSnapV"                            : [80, 90], 
     179        "postscriptBlueFuzz"                             : 1, 
     180        "postscriptBlueShift"                            : 7, 
     181        "postscriptBlueScale"                            : 0.039625, 
     182        "postscriptForceBold"                            : True, 
     183        "postscriptDefaultWidthX"                        : 400, 
     184        "postscriptNominalWidthX"                        : 400, 
     185        "postscriptWeightName"                           : "Medium", 
     186        "postscriptDefaultCharacter"             : ".notdef", 
     187        "postscriptWindowsCharacterSet"          : 1, 
     188        "macintoshFONDFamilyID"                          : 15000, 
     189        "macintoshFONDName"                                      : "SomeFont Regular (FOND Name)", 
     190} 
     191 
     192expectedFontInfo1To2Conversion = { 
     193        "familyName"                                            : "Some Font (Family Name)", 
     194        "styleMapFamilyName"                            : "Some Font Regular (Style Map Family Name)", 
     195        "styleMapStyleName"                                     : "regular", 
     196        "styleName"                                                     : "Regular (Style Name)", 
     197        "unitsPerEm"                                            : 1000, 
     198        "ascender"                                                      : 750, 
     199        "capHeight"                                                     : 750, 
     200        "xHeight"                                                       : 500, 
     201        "descender"                                                     : -250, 
     202        "italicAngle"                                           : -12.5, 
     203        "versionMajor"                                          : 1, 
     204        "versionMinor"                                          : 0, 
     205        "year"                                                          : 2008, 
     206        "copyright"                                                     : "Copyright Some Foundry.", 
     207        "trademark"                                                     : "Trademark Some Foundry", 
     208        "note"                                                          : "A note.", 
     209        "macintoshFONDFamilyID"                         : 15000, 
     210        "macintoshFONDName"                                     : "SomeFont Regular (FOND Name)", 
     211        "openTypeNameCompatibleFullName"        : "Some Font Regular (Compatible Full Name)", 
     212        "openTypeNameDescription"                       : "Some Font by Some Designer for Some Foundry.", 
     213        "openTypeNameDesigner"                          : "Some Designer", 
     214        "openTypeNameDesignerURL"                       : "http://somedesigner.com", 
     215        "openTypeNameLicense"                           : "License info for Some Foundry.", 
     216        "openTypeNameLicenseURL"                        : "http://somefoundry.com/license", 
     217        "openTypeNameManufacturer"                      : "Some Foundry", 
     218        "openTypeNameManufacturerURL"           : "http://somefoundry.com", 
     219        "openTypeNamePreferredFamilyName"       : "Some Font (Preferred Family Name)", 
     220        "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)", 
     221        "openTypeNameCompatibleFullName"        : "Some Font Regular (Compatible Full Name)", 
     222        "openTypeNameUniqueID"                          : "OpenType name Table Unique ID", 
     223        "openTypeNameVersion"                           : "OpenType name Table Version", 
     224        "openTypeOS2VendorID"                           : "SOME", 
     225        "openTypeOS2WeightClass"                        : 500, 
     226        "openTypeOS2WidthClass"                         : 5, 
     227        "postscriptDefaultWidthX"                       : 400, 
     228        "postscriptFontName"                            : "SomeFont-Regular (Postscript Font Name)", 
     229        "postscriptFullName"                            : "Some Font-Regular (Postscript Full Name)", 
     230        "postscriptSlantAngle"                          : -12.5, 
     231        "postscriptUniqueID"                            : 4000000, 
     232        "postscriptWeightName"                          : "Medium", 
     233        "postscriptWindowsCharacterSet"         : 1 
     234} 
     235 
     236expectedFontInfo2To1Conversion = { 
     237        "familyName"    : "Some Font (Family Name)", 
     238        "menuName"              : "Some Font Regular (Style Map Family Name)", 
     239        "fontStyle"             : 64, 
     240        "styleName"             : "Regular (Style Name)", 
     241        "unitsPerEm"    : 1000, 
     242        "ascender"              : 750, 
     243        "capHeight"             : 750, 
     244        "xHeight"               : 500, 
     245        "descender"             : -250, 
     246        "italicAngle"   : -12.5, 
     247        "versionMajor"  : 1, 
     248        "versionMinor"  : 0, 
     249        "copyright"             : "Copyright Some Foundry.", 
     250        "trademark"             : "Trademark Some Foundry", 
     251        "note"                  : "A note.", 
     252        "fondID"                : 15000, 
     253        "fondName"              : "SomeFont Regular (FOND Name)", 
     254        "fullName"              : "Some Font Regular (Compatible Full Name)", 
     255        "notice"                : "Some Font by Some Designer for Some Foundry.", 
     256        "designer"              : "Some Designer", 
     257        "designerURL"   : "http://somedesigner.com", 
     258        "license"               : "License info for Some Foundry.", 
     259        "licenseURL"    : "http://somefoundry.com/license", 
     260        "createdBy"             : "Some Foundry", 
     261        "vendorURL"             : "http://somefoundry.com", 
     262        "otFamilyName"  : "Some Font (Preferred Family Name)", 
     263        "otStyleName"   : "Regular (Preferred Subfamily Name)", 
     264        "otMacName"             : "Some Font Regular (Compatible Full Name)", 
     265        "ttUniqueID"    : "OpenType name Table Unique ID", 
     266        "ttVersion"             : "OpenType name Table Version", 
     267        "ttVendor"              : "SOME", 
     268        "weightValue"   : 500, 
     269        "widthName"             : "Medium (normal)", 
     270        "defaultWidth"  : 400, 
     271        "fontName"              : "SomeFont-Regular (Postscript Font Name)", 
     272        "fullName"              : "Some Font-Regular (Postscript Full Name)", 
     273        "slantAngle"    : -12.5, 
     274        "uniqueID"              : 4000000, 
     275        "weightName"    : "Medium", 
     276        "msCharSet"             : 0, 
     277        "year"                  : 2008 
     278} 
  • trunk/Lib/robofab/test/test_objectsFL.py

    r1 r171  
    1313from robofab.tools.glifImport import importAllGlifFiles 
    1414from robofab.pens.digestPen import DigestPointPen 
    15 from robofab.pens.adapterPens import SegmentToPointPen, FabToFontToolsPenAdapter 
     15from robofab.pens.adapterPens import SegmentToPointPen 
    1616 
    1717 
  • trunk/Lib/robofab/test/test_psHints.py

    r56 r171  
    103103                >>> f["new"].psHints.asDict() == g.psHints.asDict() 
    104104                True 
    105  
    106                 # multiplication 
    107                 >>> v = f.psHints * 2 
    108                 >>> v.asDict() == {'vStems': [1000, 20], 'blueFuzz': 2, 'blueShift': 2, 'forceBold': 2, 'blueScale': 1.0, 'hStems': [200, 180]} 
    109                 True 
    110  
    111                 # division 
    112                 >>> v = f.psHints / 2 
    113                 >>> v.asDict() == {'vStems': [250.0, 5.0], 'blueFuzz': 0.5, 'blueShift': 0.5, 'forceBold': 0.5, 'blueScale': 0.25, 'hStems': [50.0, 45.0]} 
    114                 True 
    115  
    116                 # multiplication with x, y, factor 
    117                 # note the h stems are multiplied by .5, the v stems (and blue values) are multiplied by 10 
    118                 >>> v = f.psHints * (.5, 10) 
    119                 >>> v.asDict() == {'vStems': [5000, 100], 'blueFuzz': 10, 'blueShift': 10, 'forceBold': 0.5, 'blueScale': 5.0, 'hStems': [50.0, 45.0]} 
    120                 True 
    121  
    122                 # multiplication with x, y, factor 
    123                 # note the h stems are divided by .5, the v stems (and blue values) are divided by 10 
    124                 >>> v = f.psHints / (.5, 10) 
    125                 >>> v.asDict() == {'vStems': [50.0, 1.0], 'blueFuzz': 0.10000000000000001, 'blueShift': 0.10000000000000001, 'forceBold': 2.0, 'blueScale': 0.050000000000000003, 'hStems': [200.0, 180.0]} 
    126                 True 
    127  
    128                 >>> v = f.psHints * .333 
    129                 >>> v.round() 
    130                 >>> v.asDict() == {'vStems': [167, 3], 'blueScale': 0.16650000000000001, 'hStems': [33, 30]} 
    131                 True 
    132  
    133105        """ 
    134106 
  • trunk/Lib/robofab/tools/toolsAll.py

    r65 r171  
    1313 
    1414 
    15 import robofab 
    16 from robofab.plistlib import readPlist, writePlist 
    17  
    18 def readFoundrySettings(dstPath): 
    19         """read the foundry settings xml file and return a keyed dict.""" 
    20         fileName = os.path.basename(dstPath) 
    21         if not os.path.exists(dstPath): 
    22                 import shutil 
    23                 # hm -- a fresh install, make a new default settings file 
    24                 print "RoboFab: creating a new foundry settings file at", dstPath 
    25                 srcDir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__))), 'Data') 
    26                 srcPath = os.path.join(srcDir, 'template_' + fileName) 
    27                 shutil.copy(srcPath, dstPath) 
    28         return readPlist(dstPath) 
    29  
    30 def getFoundrySetting(key, path): 
    31         """get a specific setting from the foundry settings xml file.""" 
    32         d = readFoundrySettings(path) 
    33         return d.get(key) 
    34  
    35 writeFoundrySettings = writePlist 
    36  
    37 def setFoundrySetting(key, value, dstPath): 
    38         """write a specific entry in the foundry settings xml file.""" 
    39         d = readFoundrySettings(dstPath) 
    40         d[key] = value 
    41         writeFoundrySettings(d, dstPath) 
    42          
    4315def readGlyphConstructions(): 
    4416        """read GlyphConstruction and turn it into a dict""" 
     
    5830                        glyphConstructions[name] = build 
    5931        return glyphConstructions 
    60  
    61  
    62  
    6332 
    6433# 
  • trunk/Lib/robofab/ufoLib.py

    r1 r171  
    11"""" 
    22A library for importing .ufo files and their descendants. 
    3 This library works with robofab objects. Using the magic of the 
    4 U.F.O., common attributes are exported to and read from .plist files. 
    5  
    6 It contains two very simple classes for reading and writing the 
    7 various components of the .ufo. Currently, the .ufo supports the 
    8 files detailed below. But, these files are not absolutely required. 
    9 If the a file is not included in the .ufo, it is implied that the data 
    10 of that file is empty. 
    11  
    12 FontName.ufo/ 
    13         metainfo.plist      # meta info about the .ufo bundle, most impartantly the 
    14                             # format version number. 
    15         glyphs/ 
    16                 contents.plist  # a plist mapping all glyph names to file names 
    17                 a.glif          # a glif file 
    18                 ...etc... 
    19         fontinfo.plist      # font names, versions, copyright, dimentions, etc. 
    20         kerning.plist       # kerning 
    21         lib.plist           # user definable data 
    22         groups.plist        # glyph group definitions 
     3Refer to http://unifiedfontobject.com for the UFO specification. 
     4 
     5The UFOReader and UFOWriter classes support versions 1 and 2 
     6of the specification. Up and down conversion functions are also 
     7supplied in this library. These conversion functions are only 
     8necessary if conversion without loading the UFO data into 
     9a set of objects is desired. These functions are: 
     10        convertUFOFormatVersion1ToFormatVersion2 
     11        convertUFOFormatVersion2ToFormatVersion1 
     12 
     13Two sets that list the font info attribute names for the two 
     14fontinfo.plist formats are available for external use. These are: 
     15        fontInfoAttributesVersion1 
     16        fontInfoAttributesVersion2 
     17 
     18A set listing the fontinfo.plist attributes that were deprecated 
     19in version 2 is available for external use: 
     20        deprecatedFontInfoAttributesVersion2 
     21 
     22A function, validateFontInfoVersion2ValueForAttribute, that does 
     23some basic validation on values for a fontinfo.plist value is 
     24available for external use. 
     25 
     26Two value conversion functions are availble for converting 
     27fontinfo.plist values between the possible format versions. 
     28        convertFontInfoValueForAttributeFromVersion1ToVersion2 
     29        convertFontInfoValueForAttributeFromVersion2ToVersion1 
    2330""" 
    2431 
    2532 
    2633import os 
     34import shutil 
    2735from cStringIO import StringIO 
     36import calendar 
    2837from robofab.plistlib import readPlist, writePlist 
    2938from robofab.glifLib import GlyphSet, READ_MODE, WRITE_MODE 
    3039 
    31  
    32 def writePlistAtomically(obj, path): 
    33         """Write a plist for 'obj' to 'path'. Do this sort of atomically, 
    34         making it harder to cause corrupt files, for example when writePlist 
    35         encounters an error halfway during write. Also: don't write out the 
    36         file if it would be identical to what's already there, meaning the 
    37         modification date won't get stomped when writing the same data. 
    38         """ 
    39         f = StringIO() 
    40         writePlist(obj, f) 
    41         data = f.getvalue() 
    42         if os.path.exists(path): 
    43                 f = open(path, READ_MODE) 
    44                 oldData = f.read() 
    45                 f.close() 
    46                 if data == oldData: 
    47                         return 
    48         f = open(path, WRITE_MODE) 
    49         f.write(data) 
    50         f.close() 
    51  
    52  
    53 GLYPHS_DIRNAME = 'glyphs' 
    54 METAINFO_FILENAME = 'metainfo.plist' 
    55 FONTINFO_FILENAME = 'fontinfo.plist' 
    56 LIB_FILENAME = 'lib.plist'       
    57 GROUPS_FILENAME = 'groups.plist' 
    58 KERNING_FILENAME = 'kerning.plist' 
    59  
    60  
    61 fontInfoAttrs = [ 
    62         # XXX we need to document how these map to OTF 'name' table fields 
    63         'familyName', 
    64         'styleName', 
    65         'fullName', 
    66         'fontName', 
    67         'menuName', 
    68         'fontStyle', 
    69         'note', 
    70         'versionMajor', 
    71         'versionMinor', 
    72         'year', 
    73         'copyright', 
    74         'notice', 
    75         'trademark', 
    76         'license', 
    77         'licenseURL', 
    78         'createdBy', 
    79         'designer', 
    80         'designerURL', 
    81         'vendorURL', 
    82         'unitsPerEm', 
    83         'ascender', 
    84         'descender', 
    85         'capHeight', 
    86         'xHeight', 
    87         'defaultWidth', 
    88         'slantAngle', 
    89         'italicAngle', 
    90         'widthName', 
    91         'weightName', 
    92         'weightValue', 
    93  
    94         # dubious format-specific fields 
    95         'fondName', 
    96         'otFamilyName', 
    97         'otStyleName', 
    98         'otMacName', 
    99         'msCharSet', 
    100         'fondID', 
    101         'uniqueID', 
    102         'ttVendor', 
    103         'ttUniqueID', 
    104         'ttVersion', 
     40try: 
     41        set 
     42except NameError: 
     43        from sets import Set as set 
     44 
     45__all__ = [ 
     46        "makeUFOPath" 
     47        "UFOLibError", 
     48        "UFOReader", 
     49        "UFOWriter", 
     50        "convertUFOFormatVersion1ToFormatVersion2", 
     51        "convertUFOFormatVersion2ToFormatVersion1", 
     52        "fontInfoAttributesVersion1", 
     53        "fontInfoAttributesVersion2", 
     54        "deprecatedFontInfoAttributesVersion2", 
     55        "validateFontInfoVersion2ValueForAttribute", 
     56        "convertFontInfoValueForAttributeFromVersion1ToVersion2", 
     57        "convertFontInfoValueForAttributeFromVersion2ToVersion1" 
    10558] 
    10659 
    10760 
    108 def makeUFOPath(fontPath): 
    109         """return a .ufo pathname based on a .vfb pathname""" 
    110         dir, name = os.path.split(fontPath) 
    111         name = '.'.join([name.split('.')[0], 'ufo']) 
    112         return os.path.join(dir, name) 
     61class UFOLibError(Exception): pass 
     62 
     63 
     64# ---------- 
     65# File Names 
     66# ---------- 
     67 
     68GLYPHS_DIRNAME = "glyphs" 
     69METAINFO_FILENAME = "metainfo.plist" 
     70FONTINFO_FILENAME = "fontinfo.plist" 
     71LIB_FILENAME = "lib.plist"       
     72GROUPS_FILENAME = "groups.plist" 
     73KERNING_FILENAME = "kerning.plist" 
     74FEATURES_FILENAME = "features.fea" 
     75 
     76supportedUFOFormatVersions = [1, 2] 
     77 
     78 
     79# --------------------------- 
     80# Format Conversion Functions 
     81# --------------------------- 
     82 
     83 
     84def convertUFOFormatVersion1ToFormatVersion2(inPath, outPath=None): 
     85        """ 
     86        Function for converting a version format 1 UFO 
     87        to version format 2. inPath should be a path 
     88        to a UFO. outPath is the path where the new UFO 
     89        should be written. If outPath is not given, the 
     90        inPath will be used and, therefore, the UFO will 
     91        be converted in place. Otherwise, if outPath is 
     92        specified, nothing must exist at that path. 
     93        """ 
     94        if outPath is None: 
     95                outPath = inPath 
     96        if inPath != outPath and os.path.exists(outPath): 
     97                raise UFOLibError("A file already exists at %s." % outPath) 
     98        # use a reader for loading most of the data 
     99        reader = UFOReader(inPath) 
     100        if reader.formatVersion == 2: 
     101                raise UFOLibError("The UFO at %s is already format version 2." % inPath) 
     102        groups = reader.readGroups() 
     103        kerning = reader.readKerning() 
     104        libData = reader.readLib() 
     105        # read the info data manually and convert 
     106        infoPath = os.path.join(inPath, FONTINFO_FILENAME) 
     107        if not os.path.exists(infoPath): 
     108                infoData = {} 
     109        else: 
     110                infoData = readPlist(infoPath) 
     111        infoData = _convertFontInfoDataVersion1ToVersion2(infoData) 
     112        # if the paths are the same, only need to change the 
     113        # fontinfo and meta info files. 
     114        infoPath = os.path.join(outPath, FONTINFO_FILENAME) 
     115        if inPath == outPath: 
     116                metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) 
     117                metaInfo = dict( 
     118                        creator="org.robofab.ufoLib", 
     119                        formatVersion=2 
     120                ) 
     121                writePlistAtomically(metaInfo, metaInfoPath) 
     122                writePlistAtomically(infoData, infoPath) 
     123        # otherwise write everything. 
     124        else: 
     125                writer = UFOWriter(outPath) 
     126                writer.writeGroups(groups) 
     127                writer.writeKerning(kerning) 
     128                writer.writeLib(libData) 
     129                # write the info manually 
     130                writePlistAtomically(infoData, infoPath) 
     131                # copy the glyph tree 
     132                inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) 
     133                outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) 
     134                if os.path.exists(inGlyphs): 
     135                        shutil.copytree(inGlyphs, outGlyphs) 
     136 
     137def convertUFOFormatVersion2ToFormatVersion1(inPath, outPath=None): 
     138        """ 
     139        Function for converting a version format 2 UFO 
     140        to version format 1. inPath should be a path 
     141        to a UFO. outPath is the path where the new UFO 
     142        should be written. If outPath is not given, the 
     143        inPath will be used and, therefore, the UFO will 
     144        be converted in place. Otherwise, if outPath is 
     145        specified, nothing must exist at that path. 
     146        """ 
     147        if outPath is None: 
     148                outPath = inPath 
     149        if inPath != outPath and os.path.exists(outPath): 
     150                raise UFOLibError("A file already exists at %s." % outPath) 
     151        # use a reader for loading most of the data 
     152        reader = UFOReader(inPath) 
     153        if reader.formatVersion == 1: 
     154                raise UFOLibError("The UFO at %s is already format version 1." % inPath) 
     155        groups = reader.readGroups() 
     156        kerning = reader.readKerning() 
     157        libData = reader.readLib() 
     158        # read the info data manually and convert 
     159        infoPath = os.path.join(inPath, FONTINFO_FILENAME) 
     160        if not os.path.exists(infoPath): 
     161                infoData = {} 
     162        else: 
     163                infoData = readPlist(infoPath) 
     164        infoData = _convertFontInfoDataVersion2ToVersion1(infoData) 
     165        # if the paths are the same, only need to change the 
     166        # fontinfo, metainfo and feature files. 
     167        infoPath = os.path.join(outPath, FONTINFO_FILENAME) 
     168        if inPath == outPath: 
     169                metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) 
     170                metaInfo = dict( 
     171                        creator="org.robofab.ufoLib", 
     172                        formatVersion=1 
     173                ) 
     174                writePlistAtomically(metaInfo, metaInfoPath) 
     175                writePlistAtomically(infoData, infoPath) 
     176                featuresPath = os.path.join(inPath, FEATURES_FILENAME) 
     177                if os.path.exists(featuresPath): 
     178                        os.remove(featuresPath) 
     179        # otherwise write everything. 
     180        else: 
     181                writer = UFOWriter(outPath, formatVersion=1) 
     182                writer.writeGroups(groups) 
     183                writer.writeKerning(kerning) 
     184                writer.writeLib(libData) 
     185                # write the info manually 
     186                writePlistAtomically(infoData, infoPath) 
     187                # copy the glyph tree 
     188                inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) 
     189                outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) 
     190                if os.path.exists(inGlyphs): 
     191                        shutil.copytree(inGlyphs, outGlyphs) 
     192 
     193 
     194# ---------- 
     195# UFO Reader 
     196# ---------- 
    113197 
    114198 
    115199class UFOReader(object): 
    116          
    117         """read the various components of the .ufo""" 
    118          
     200 
     201        """Read the various components of the .ufo.""" 
     202 
    119203        def __init__(self, path): 
    120204                self._path = path 
    121                  
     205                self.readMetaInfo() 
     206 
     207        def _get_formatVersion(self): 
     208                return self._formatVersion 
     209 
     210        formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.") 
     211 
    122212        def _checkForFile(self, path): 
    123213                if not os.path.exists(path): 
    124                         #print "missing file: %s" % path 
    125214                        return False 
    126215                else: 
    127216                        return True 
    128                          
     217 
    129218        def readMetaInfo(self): 
    130                 """read metainfo.plist. mostly used 
    131                 for internal operations""" 
     219                """ 
     220                Read metainfo.plist. Only used for internal operations. 
     221                """ 
    132222                path = os.path.join(self._path, METAINFO_FILENAME) 
    133223                if not self._checkForFile(path): 
    134                         return 
    135          
     224                        raise UFOLibError("metainfo.plist is missing in %s. This file is required." % self._path) 
     225                # should there be a blind try/except with a UFOLibError 
     226                # raised in except here (and elsewhere)? It would be nice to 
     227                # provide external callers with a single exception to catch. 
     228                data = readPlist(path) 
     229                formatVersion = data["formatVersion"] 
     230                if formatVersion not in supportedUFOFormatVersions: 
     231                        raise UFOLibError("Unsupported UFO format (%d) in %s." % (formatVersion, self._path)) 
     232                self._formatVersion = formatVersion 
     233 
    136234        def readGroups(self): 
    137                 """read groups.plist. returns a dict that should 
    138                 be applied to a font.groups object.""" 
     235                """ 
     236                Read groups.plist. Returns a dict. 
     237                """ 
    139238                path = os.path.join(self._path, GROUPS_FILENAME) 
    140239                if not self._checkForFile(path): 
    141240                        return {} 
    142241                return readPlist(path) 
    143          
     242 
    144243        def readInfo(self, info): 
    145                 """read info.plist. it requires a font.info object 
    146                 as an argument. this will write the attributes 
    147                 defined in the file into the info object.""" 
     244                """ 
     245                Read fontinfo.plist. It requires an object that allows 
     246                setting attributes with names that follow the fontinfo.plist 
     247                version 2 specification. This will write the attributes 
     248                defined in the file into the object. 
     249                """ 
     250                # load the file and return if there is no file 
    148251                path = os.path.join(self._path, FONTINFO_FILENAME) 
    149252                if not self._checkForFile(path): 
    150                         return {} 
     253                        return 
    151254                infoDict = readPlist(path) 
    152                 for key, value in infoDict.items(): 
     255                infoDataToSet = {} 
     256                # version 1 
     257                if self._formatVersion == 1: 
     258                        for attr in fontInfoAttributesVersion1: 
     259                                value = infoDict.get(attr) 
     260                                if value is not None: 
     261                                        infoDataToSet[attr] = value 
     262                        infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet) 
     263                # version 2 
     264                elif self._formatVersion == 2: 
     265                        for attr, dataValidationDict in _fontInfoAttributesVersion2ValueData.items(): 
     266                                value = infoDict.get(attr) 
     267                                if value is None: 
     268                                        continue 
     269                                infoDataToSet[attr] = value 
     270                # unsupported version 
     271                else: 
     272                        raise NotImplementedError 
     273                # validate data 
     274                infoDataToSet = _validateInfoVersion2Data(infoDataToSet) 
     275                # populate the object 
     276                for attr, value in infoDataToSet.items(): 
    153277                        try: 
    154                                 setattr(info, key, value) 
     278                                setattr(info, attr, value) 
    155279                        except AttributeError: 
    156                                 # object doesn't support setting this attribute 
    157                                 pass 
    158          
     280                                raise UFOLibError("The supplied info object does not support setting a necessary attribute (%s)." % attr) 
     281 
    159282        def readKerning(self): 
    160                 """read kerning.plist. returns a dict that should 
    161                 be applied to a font.kerning object.""" 
     283                """ 
     284                Read kerning.plist. Returns a dict. 
     285                """ 
    162286                path = os.path.join(self._path, KERNING_FILENAME) 
    163287                if not self._checkForFile(path): 
     
    170294                                kerning[left, right] = value 
    171295                return kerning 
    172          
     296 
    173297        def readLib(self): 
    174                 """read lib.plist. returns a dict that should 
    175                 be applied to a font.lib object.""" 
     298                """ 
     299                Read lib.plist. Returns a dict. 
     300                """ 
    176301                path = os.path.join(self._path, LIB_FILENAME) 
    177302                if not self._checkForFile(path): 
    178303                        return {} 
    179304                return readPlist(path) 
    180          
     305 
     306        def readFeatures(self): 
     307                """ 
     308                Read features.fea. Returns a string. 
     309                """ 
     310                path = os.path.join(self._path, FEATURES_FILENAME) 
     311                if not self._checkForFile(path): 
     312                        return "" 
     313                f = open(path, READ_MODE) 
     314                text = f.read() 
     315                f.close() 
     316                return text 
     317 
    181318        def getGlyphSet(self): 
    182                 """return the GlyphSet associated with the 
    183                 glyphs directory in the .ufo""" 
     319                """ 
     320                Return the GlyphSet associated with the 
     321                glyphs directory in the .ufo. 
     322                """ 
    184323                glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) 
    185324                return GlyphSet(glyphsPath) 
    186325 
    187326        def getCharacterMapping(self): 
    188                 """Return a dictionary that maps unicode values (ints) to 
     327                """ 
     328                Return a dictionary that maps unicode values (ints) to 
    189329                lists of glyph names. 
    190330                """ 
     
    202342 
    203343 
     344# ---------- 
     345# UFO Writer 
     346# ---------- 
     347 
     348 
    204349class UFOWriter(object): 
    205          
    206         """write the various components of the .ufo""" 
    207          
    208         fileCreator = 'org.robofab.ufoLib' 
    209         formatVersion = 1  # the format version is an int, the next version will be 2. 
    210          
    211         def __init__(self, path): 
     350 
     351        """Write the various components of the .ufo.""" 
     352 
     353        def __init__(self, path, formatVersion=2, fileCreator="org.robofab.ufoLib"): 
     354                if formatVersion not in supportedUFOFormatVersions: 
     355                        raise UFOLibError("Unsupported UFO format (%d)." % formatVersion) 
    212356                self._path = path 
    213                  
     357                self._formatVersion = formatVersion 
     358                self._fileCreator = fileCreator 
     359                self._writeMetaInfo() 
     360                # handle down conversion 
     361                if formatVersion == 1: 
     362                        ## remove existing features.fea 
     363                        featuresPath = os.path.join(path, FEATURES_FILENAME) 
     364                        if os.path.exists(featuresPath): 
     365                                os.remove(featuresPath) 
     366 
     367        def _get_formatVersion(self): 
     368                return self._formatVersion 
     369 
     370        formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is set into metainfo.plist during __init__.") 
     371 
     372        def _get_fileCreator(self): 
     373                return self._fileCreator 
     374 
     375        fileCreator = property(_get_fileCreator, doc="The file creator of the UFO. This is set into metainfo.plist during __init__.") 
     376 
    214377        def _makeDirectory(self, subDirectory=None): 
    215378                path = self._path 
     
    218381                if not os.path.exists(path): 
    219382                        os.makedirs(path) 
    220                 if not os.path.exists(os.path.join(path, METAINFO_FILENAME)): 
    221                         self._writeMetaInfo() 
    222383                return path 
    223          
     384 
    224385        def _writeMetaInfo(self): 
     386                self._makeDirectory() 
    225387                path = os.path.join(self._path, METAINFO_FILENAME) 
    226                 metaInfo = { 
    227                         'creator': self.fileCreator, 
    228                         'formatVersion': self.formatVersion, 
    229                 } 
     388                metaInfo = dict( 
     389                        creator=self._fileCreator, 
     390                        formatVersion=self._formatVersion 
     391                ) 
    230392                writePlistAtomically(metaInfo, path) 
    231          
     393 
    232394        def writeGroups(self, groups): 
    233                 """write groups.plist. this method requires a 
    234                 dict of glyph groups as an argument.""" 
     395                """ 
     396                Write groups.plist. This method requires a 
     397                dict of glyph groups as an argument. 
     398                """ 
    235399                self._makeDirectory() 
    236400                path = os.path.join(self._path, GROUPS_FILENAME) 
     
    242406                elif os.path.exists(path): 
    243407                        os.remove(path) 
    244          
     408 
    245409        def writeInfo(self, info): 
    246                 """write info.plist. this method requires a 
    247                 font.info object. attributes will be taken from 
    248                 the given object and written into the file""" 
     410                """ 
     411                Write info.plist. This method requires an object 
     412                that supports getting attributes that follow the 
     413                fontinfo.plist version 2 secification. Attributes 
     414                will be taken from the given object and written 
     415                into the file. 
     416                """ 
    249417                self._makeDirectory() 
    250418                path = os.path.join(self._path, FONTINFO_FILENAME) 
    251                 infoDict = {} 
    252                 for name in fontInfoAttrs: 
    253                         value = getattr(info, name, None) 
    254                         if value is not None: 
    255                                 infoDict[name] = value 
    256                 writePlistAtomically(infoDict, path) 
    257          
     419                # gather version 2 data 
     420                infoData = {} 
     421                for attr in _fontInfoAttributesVersion2ValueData.keys(): 
     422                        try: 
     423                                value = getattr(info, attr) 
     424                        except AttributeError: 
     425                                raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr) 
     426                        if value is None: 
     427                                continue 
     428                        infoData[attr] = value 
     429                # validate data 
     430                infoData = _validateInfoVersion2Data(infoData) 
     431                # down convert data to version 1 if necessary 
     432                if self._formatVersion == 1: 
     433                        infoData = _convertFontInfoDataVersion2ToVersion1(infoData) 
     434                # write file 
     435                writePlistAtomically(infoData, path) 
     436 
    258437        def writeKerning(self, kerning): 
    259                 """write kerning.plist. this method requires a 
    260                 dict of kerning pairs as an argument""" 
     438                """ 
     439                Write kerning.plist. This method requires a 
     440                dict of kerning pairs as an argument. 
     441                """ 
    261442                self._makeDirectory() 
    262443                path = os.path.join(self._path, KERNING_FILENAME) 
     
    271452                elif os.path.exists(path): 
    272453                        os.remove(path) 
    273          
     454 
    274455        def writeLib(self, libDict): 
    275                 """write lib.plist. this method requires a 
    276                 lib dict as an argument""" 
     456                """ 
     457                Write lib.plist. This method requires a 
     458                lib dict as an argument. 
     459                """ 
    277460                self._makeDirectory() 
    278461                path = os.path.join(self._path, LIB_FILENAME) 
     
    282465                        os.remove(path) 
    283466 
     467        def writeFeatures(self, features): 
     468                """ 
     469                Write features.fea. This method requires a 
     470                features string as an argument. 
     471                """ 
     472                if self._formatVersion == 1: 
     473                        raise UFOLibError("features.fea is not allowed in UFO Format Version 1.") 
     474                self._makeDirectory() 
     475                path = os.path.join(self._path, FEATURES_FILENAME) 
     476                writeFileAtomically(features, path) 
     477 
    284478        def makeGlyphPath(self): 
    285                 """make the glyphs directory in the .ufo 
    286                 returns the path of the directory created""" 
     479                """ 
     480                Make the glyphs directory in the .ufo. 
     481                Returns the path of the directory created. 
     482                """ 
    287483                glyphDir = self._makeDirectory(GLYPHS_DIRNAME) 
    288484                return glyphDir 
    289485 
    290486        def getGlyphSet(self, glyphNameToFileNameFunc=None): 
    291                 """return the GlyphSet associated with the 
    292                 glyphs directory in the .ufo""" 
     487                """ 
     488                Return the GlyphSet associated with the 
     489                glyphs directory in the .ufo. 
     490                """ 
    293491                return GlyphSet(self.makeGlyphPath(), glyphNameToFileNameFunc) 
     492 
     493# ---------------- 
     494# Helper Functions 
     495# ---------------- 
     496 
     497def makeUFOPath(path): 
     498        """ 
     499        Return a .ufo pathname. 
     500 
     501        >>> makeUFOPath("/directory/something.ext") 
     502        '/directory/something.ufo' 
     503        >>> makeUFOPath("/directory/something.another.thing.ext") 
     504        '/directory/something.another.thing.ufo' 
     505        """ 
     506        dir, name = os.path.split(path) 
     507        name = ".".join([".".join(name.split(".")[:-1]), "ufo"]) 
     508        return os.path.join(dir, name) 
     509 
     510def writePlistAtomically(obj, path): 
     511        """ 
     512        Write a plist for "obj" to "path". Do this sort of atomically, 
     513        making it harder to cause corrupt files, for example when writePlist 
     514        encounters an error halfway during write. This also checks to see 
     515        if text matches the text that is already in the file at path. 
     516        If so, the file is not rewritten so that the modification date 
     517        is preserved. 
     518        """ 
     519        f = StringIO() 
     520        writePlist(obj, f) 
     521        data = f.getvalue() 
     522        writeFileAtomically(data, path) 
     523 
     524def writeFileAtomically(text, path): 
     525        """Write text into a file at path. Do this sort of atomically 
     526        making it harder to cause corrupt files. This also checks to see 
     527        if text matches the text that is already in the file at path. 
     528        If so, the file is not rewritten so that the modification date 
     529        is preserved.""" 
     530        if os.path.exists(path): 
     531                f = open(path, READ_MODE) 
     532                oldText = f.read() 
     533                f.close() 
     534                if text == oldText: 
     535                        return 
     536                # if the text is empty, remove the existing file 
     537                if not text: 
     538                        os.remove(path) 
     539        if text: 
     540                f = open(path, WRITE_MODE) 
     541                f.write(text) 
     542                f.close() 
     543 
     544# ---------------------- 
     545# fontinfo.plist Support 
     546# ---------------------- 
     547 
     548# Version 1 
     549 
     550fontInfoAttributesVersion1 = set([ 
     551        "familyName", 
     552        "styleName", 
     553        "fullName", 
     554        "fontName", 
     555        "menuName", 
     556        "fontStyle", 
     557        "note", 
     558        "versionMajor", 
     559        "versionMinor", 
     560        "year", 
     561        "copyright", 
     562        "notice", 
     563        "trademark", 
     564        "license", 
     565        "licenseURL", 
     566        "createdBy", 
     567        "designer", 
     568        "designerURL", 
     569        "vendorURL", 
     570        "unitsPerEm", 
     571        "ascender", 
     572        "descender", 
     573        "capHeight", 
     574        "xHeight", 
     575        "defaultWidth", 
     576        "slantAngle", 
     577        "italicAngle", 
     578        "widthName", 
     579        "weightName", 
     580        "weightValue", 
     581        "fondName", 
     582        "otFamilyName", 
     583        "otStyleName", 
     584        "otMacName", 
     585        "msCharSet", 
     586        "fondID", 
     587        "uniqueID", 
     588        "ttVendor", 
     589        "ttUniqueID", 
     590        "ttVersion", 
     591]) 
     592 
     593# Version 2 
     594 
     595# Validators 
     596 
     597def validateFontInfoVersion2ValueForAttribute(attr, value): 
     598        """ 
     599        This performs very basic validation of the value for attribute 
     600        following the UFO fontinfo.plist specification. The results 
     601        of this should not be interpretted as *correct* for the font 
     602        that they are part of. This merely indicates that the value 
     603        is of the proper type and, where the specification defines 
     604        a set range of possible values for an attribute, that the 
     605        value is in the accepted range. 
     606        """ 
     607        dataValidationDict = _fontInfoAttributesVersion2ValueData[attr] 
     608        valueType = dataValidationDict.get("type") 
     609        validator = dataValidationDict.get("valueValidator") 
     610        valueOptions = dataValidationDict.get("valueOptions") 
     611        # have specific options for the validator 
     612        if valueOptions is not None: 
     613                isValidValue = validator(value, valueOptions) 
     614        # no specific options 
     615        else: 
     616                if validator == _fontInfoTypeValidator: 
     617                        isValidValue = validator(value, valueType) 
     618                else: 
     619                        isValidValue = validator(value) 
     620        return isValidValue 
     621 
     622def _validateInfoVersion2Data(infoData): 
     623        validInfoData = {} 
     624        for attr, value in infoData.items(): 
     625                isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value) 
     626                if not isValidValue: 
     627                        raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value))) 
     628                else: 
     629                        validInfoData[attr] = value 
     630        return infoData 
     631 
     632def _fontInfoTypeValidator(value, typ): 
     633        return isinstance(value, typ) 
     634 
     635def _fontInfoVersion2IntListValidator(values, validValues): 
     636        if not isinstance(values, (list, tuple)): 
     637                return False 
     638        valuesSet = set(values) 
     639        validValuesSet = set(validValues) 
     640        if len(valuesSet - validValuesSet) > 0: 
     641                return False 
     642        for value in values: 
     643                if not isinstance(value, int): 
     644                        return False 
     645        return True 
     646 
     647def _fontInfoVersion2StyleMapStyleNameValidator(value): 
     648        options = ["regular", "italic", "bold", "bold italic"] 
     649        return value in options 
     650 
     651def _fontInfoVersion2OpenTypeHeadCreatedValidator(value): 
     652        # format: 0000/00/00 00:00:00 
     653        if not isinstance(value, (str, unicode)): 
     654                return False 
     655        # basic formatting 
     656        if not len(value) == 19: 
     657                return False 
     658        if value.count(" ") != 1: 
     659                return False 
     660        date, time = value.split(" ") 
     661        if date.count("/") != 2: 
     662                return False 
     663        if time.count(":") != 2: 
     664                return False 
     665        # date 
     666        year, month, day = date.split("/") 
     667        if len(year) != 4: 
     668                return False 
     669        if len(month) != 2: 
     670                return False 
     671        if len(day) != 2: 
     672                return False 
     673        try: 
     674                year = int(year) 
     675                month = int(month) 
     676                day = int(day) 
     677        except ValueError: 
     678                return False 
     679        if month < 1 or month > 12: 
     680                return False 
     681        monthMaxDay = calendar.monthrange(year, month) 
     682        if month > monthMaxDay: 
     683                return False 
     684        # time 
     685        hour, minute, second = time.split(":") 
     686        if len(hour) != 2: 
     687                return False 
     688        if len(minute) != 2: 
     689                return False 
     690        if len(second) != 2: 
     691                return False 
     692        try: 
     693                hour = int(hour) 
     694                minute = int(minute) 
     695                second = int(second) 
     696        except ValueError: 
     697                return False 
     698        if hour < 0 or hour > 23: 
     699                return False 
     700        if minute < 0 or minute > 59: 
     701                return False 
     702        if second < 0 or second > 59: 
     703                return True 
     704        # fallback 
     705        return True 
     706 
     707def _fontInfoVersion2OpenTypeOS2WeightClassValidator(value): 
     708        if not isinstance(value, int): 
     709                return False 
     710        if value < 0: 
     711                return False 
     712        return True 
     713 
     714def _fontInfoVersion2OpenTypeOS2WidthClassValidator(value): 
     715        if not isinstance(value, int): 
     716                return False 
     717        if value < 1: 
     718                return False 
     719        if value > 9: 
     720                return False 
     721        return True 
     722 
     723def _fontInfoVersion2OpenTypeOS2PanoseValidator(values): 
     724        if not isinstance(values, (list, tuple)): 
     725                return False 
     726        if len(values) != 10: 
     727                return False 
     728        for value in values: 
     729                if not isinstance(value, int): 
     730                        return False 
     731        # XXX further validation? 
     732        return True 
     733 
     734def _fontInfoVersion2OpenTypeOS2FamilyClassValidator(values): 
     735        if not isinstance(values, (list, tuple)): 
     736                return False 
     737        if len(values) != 2: 
     738                return False 
     739        for value in values: 
     740                if not isinstance(value, int): 
     741                        return False 
     742        classID, subclassID = values 
     743        if classID < 0 or classID > 14: 
     744                return False 
     745        if subclassID < 0 or subclassID > 15: 
     746                return False 
     747        return True 
     748 
     749def _fontInfoVersion2PostscriptBluesValidator(values): 
     750        if not isinstance(values, (list, tuple)): 
     751                return False 
     752        if len(values) > 14: 
     753                return False 
     754        if len(values) % 2: 
     755                return False 
     756        for value in values: 
     757                if not isinstance(value, (int, float)): 
     758                        return False 
     759        return True 
     760 
     761def _fontInfoVersion2PostscriptOtherBluesValidator(values): 
     762        if not isinstance(values, (list, tuple)): 
     763                return False 
     764        if len(values) > 10: 
     765                return False 
     766        if len(values) % 2: 
     767                return False 
     768        for value in values: 
     769                if not isinstance(value, (int, float)): 
     770                        return False 
     771        return True 
     772 
     773def _fontInfoVersion2PostscriptStemsValidator(values): 
     774        if not isinstance(values, (list, tuple)): 
     775                return False 
     776        if len(values) > 12: 
     777                return False 
     778        for value in values: 
     779                if not isinstance(value, (int, float)): 
     780                        return False 
     781        return True 
     782 
     783def _fontInfoVersion2PostscriptWindowsCharacterSetValidator(value): 
     784        validValues = range(1, 21) 
     785        if value not in validValues: 
     786                return False 
     787        return True 
     788 
     789# Attribute Definitions 
     790# This defines the attributes, types and, in some 
     791# cases the possible values, that can exist is 
     792# fontinfo.plist. 
     793 
     794_fontInfoVersion2OpenTypeHeadFlagsOptions = range(0, 14) 
     795_fontInfoVersion2OpenTypeOS2SelectionOptions = [1, 2, 3, 4] 
     796_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions = range(0, 128) 
     797_fontInfoVersion2OpenTypeOS2CodePageRangesOptions = range(0, 64) 
     798_fontInfoVersion2OpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9] 
     799 
     800_fontInfoAttributesVersion2ValueData = { 
     801        "familyName"                                                    : dict(type=(str, unicode)), 
     802        "styleName"                                                             : dict(type=(str, unicode)), 
     803        "styleMapFamilyName"                                    : dict(type=(str, unicode)), 
     804        "styleMapStyleName"                                             : dict(type=(str, unicode), valueValidator=_fontInfoVersion2StyleMapStyleNameValidator), 
     805        "versionMajor"                                                  : dict(type=int), 
     806        "versionMinor"                                                  : dict(type=int), 
     807        "year"                                                                  : dict(type=int), 
     808        "copyright"                                                             : dict(type=(str, unicode)), 
     809        "trademark"                                                             : dict(type=(str, unicode)), 
     810        "unitsPerEm"                                                    : dict(type=(int, float)), 
     811        "descender"                                                             : dict(type=(int, float)), 
     812        "xHeight"                                                               : dict(type=(int, float)), 
     813        "capHeight"                                                             : dict(type=(int, float)), 
     814        "ascender"                                                              : dict(type=(int, float)), 
     815        "italicAngle"                                                   : dict(type=(float, int)), 
     816        "note"                                                                  : dict(type=(str, unicode)), 
     817        "openTypeHeadCreated"                                   : dict(type=(str, unicode), valueValidator=_fontInfoVersion2OpenTypeHeadCreatedValidator), 
     818        "openTypeHeadLowestRecPPEM"                             : dict(type=(int, float)), 
     819        "openTypeHeadFlags"                                             : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeHeadFlagsOptions), 
     820        "openTypeHheaAscender"                                  : dict(type=(int, float)), 
     821        "openTypeHheaDescender"                                 : dict(type=(int, float)), 
     822        "openTypeHheaLineGap"                                   : dict(type=(int, float)), 
     823        "openTypeHheaCaretSlopeRise"                    : dict(type=int), 
     824        "openTypeHheaCaretSlopeRun"                             : dict(type=int), 
     825        "openTypeHheaCaretOffset"                               : dict(type=(int, float)), 
     826        "openTypeNameDesigner"                                  : dict(type=(str, unicode)), 
     827        "openTypeNameDesignerURL"                               : dict(type=(str, unicode)), 
     828        "openTypeNameManufacturer"                              : dict(type=(str, unicode)), 
     829        "openTypeNameManufacturerURL"                   : dict(type=(str, unicode)), 
     830        "openTypeNameLicense"                                   : dict(type=(str, unicode)), 
     831        "openTypeNameLicenseURL"                                : dict(type=(str, unicode)), 
     832        "openTypeNameVersion"                                   : dict(type=(str, unicode)), 
     833        "openTypeNameUniqueID"                                  : dict(type=(str, unicode)), 
     834        "openTypeNameDescription"                               : dict(type=(str, unicode)), 
     835        "openTypeNamePreferredFamilyName"               : dict(type=(str, unicode)), 
     836        "openTypeNamePreferredSubfamilyName"    : dict(type=(str, unicode)), 
     837        "openTypeNameCompatibleFullName"                : dict(type=(str, unicode)), 
     838        "openTypeNameSampleText"                                : dict(type=(str, unicode)), 
     839        "openTypeNameWWSFamilyName"                             : dict(type=(str, unicode)), 
     840        "openTypeNameWWSSubfamilyName"                  : dict(type=(str, unicode)), 
     841        "openTypeOS2WidthClass"                                 : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WidthClassValidator), 
     842        "openTypeOS2WeightClass"                                : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WeightClassValidator), 
     843        "openTypeOS2Selection"                                  : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2SelectionOptions), 
     844        "openTypeOS2VendorID"                                   : dict(type=(str, unicode)), 
     845        "openTypeOS2Panose"                                             : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2PanoseValidator), 
     846        "openTypeOS2FamilyClass"                                : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2FamilyClassValidator), 
     847        "openTypeOS2UnicodeRanges"                              : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions), 
     848        "openTypeOS2CodePageRanges"                             : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2CodePageRangesOptions), 
     849        "openTypeOS2TypoAscender"                               : dict(type=(int, float)), 
     850        "openTypeOS2TypoDescender"                              : dict(type=(int, float)), 
     851        "openTypeOS2TypoLineGap"                                : dict(type=(int, float)), 
     852        "openTypeOS2WinAscent"                                  : dict(type=(int, float)), 
     853        "openTypeOS2WinDescent"                                 : dict(type=(int, float)), 
     854        "openTypeOS2Type"                                               : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2TypeOptions), 
     855        "openTypeOS2SubscriptXSize"                             : dict(type=(int, float)), 
     856        "openTypeOS2SubscriptYSize"                             : dict(type=(int, float)), 
     857        "openTypeOS2SubscriptXOffset"                   : dict(type=(int, float)), 
     858        "openTypeOS2SubscriptYOffset"                   : dict(type=(int, float)), 
     859        "openTypeOS2SuperscriptXSize"                   : dict(type=(int, float)), 
     860        "openTypeOS2SuperscriptYSize"                   : dict(type=(int, float)), 
     861        "openTypeOS2SuperscriptXOffset"                 : dict(type=(int, float)), 
     862        "openTypeOS2SuperscriptYOffset"                 : dict(type=(int, float)), 
     863        "openTypeOS2StrikeoutSize"                              : dict(type=(int, float)), 
     864        "openTypeOS2StrikeoutPosition"                  : dict(type=(int, float)), 
     865        "openTypeVheaVertTypoAscender"                  : dict(type=(int, float)), 
     866        "openTypeVheaVertTypoDescender"                 : dict(type=(int, float)), 
     867        "openTypeVheaVertTypoLineGap"                   : dict(type=(int, float)), 
     868        "openTypeVheaCaretSlopeRise"                    : dict(type=int), 
     869        "openTypeVheaCaretSlopeRun"                             : dict(type=int), 
     870        "openTypeVheaCaretOffset"                               : dict(type=(int, float)), 
     871        "postscriptFontName"                                    : dict(type=(str, unicode)), 
     872        "postscriptFullName"                                    : dict(type=(str, unicode)), 
     873        "postscriptSlantAngle"                                  : dict(type=(float, int)), 
     874        "postscriptUniqueID"                                    : dict(type=int), 
     875        "postscriptUnderlineThickness"                  : dict(type=(int, float)), 
     876        "postscriptUnderlinePosition"                   : dict(type=(int, float)), 
     877        "postscriptIsFixedPitch"                                : dict(type=bool), 
     878        "postscriptBlueValues"                                  : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), 
     879        "postscriptOtherBlues"                                  : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), 
     880        "postscriptFamilyBlues"                                 : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), 
     881        "postscriptFamilyOtherBlues"                    : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), 
     882        "postscriptStemSnapH"                                   : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), 
     883        "postscriptStemSnapV"                                   : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), 
     884        "postscriptBlueFuzz"                                    : dict(type=(int, float)), 
     885        "postscriptBlueShift"                                   : dict(type=(int, float)), 
     886        "postscriptBlueScale"                                   : dict(type=(float, int)), 
     887        "postscriptForceBold"                                   : dict(type=bool), 
     888        "postscriptDefaultWidthX"                               : dict(type=(int, float)), 
     889        "postscriptNominalWidthX"                               : dict(type=(int, float)), 
     890        "postscriptWeightName"                                  : dict(type=(str, unicode)), 
     891        "postscriptDefaultCharacter"                    : dict(type=(str, unicode)), 
     892        "postscriptWindowsCharacterSet"                 : dict(type=int, valueValidator=_fontInfoVersion2PostscriptWindowsCharacterSetValidator), 
     893        "macintoshFONDFamilyID"                                 : dict(type=int), 
     894        "macintoshFONDName"                                             : dict(type=(str, unicode)), 
     895} 
     896fontInfoAttributesVersion2 = set(_fontInfoAttributesVersion2ValueData.keys()) 
     897 
     898# insert the type validator for all attrs that 
     899# have no defined validator. 
     900for attr, dataDict in _fontInfoAttributesVersion2ValueData.items(): 
     901        if "valueValidator" not in dataDict: 
     902                dataDict["valueValidator"] = _fontInfoTypeValidator 
     903 
     904# Version Conversion Support 
     905# These are used from converting from version 1 
     906# to version 2 or vice-versa. 
     907 
     908def _flipDict(d): 
     909        flipped = {} 
     910        for key, value in d.items(): 
     911                flipped[value] = key 
     912        return flipped 
     913 
     914_fontInfoAttributesVersion1To2 = { 
     915        "menuName"              : "styleMapFamilyName", 
     916        "designer"              : "openTypeNameDesigner", 
     917        "designerURL"   : "openTypeNameDesignerURL", 
     918        "createdBy"             : "openTypeNameManufacturer", 
     919        "vendorURL"             : "openTypeNameManufacturerURL", 
     920        "license"               : "openTypeNameLicense", 
     921        "licenseURL"    : "openTypeNameLicenseURL", 
     922        "ttVersion"             : "openTypeNameVersion", 
     923        "ttUniqueID"    : "openTypeNameUniqueID", 
     924        "notice"                : "openTypeNameDescription", 
     925        "otFamilyName"  : "openTypeNamePreferredFamilyName", 
     926        "otStyleName"   : "openTypeNamePreferredSubfamilyName", 
     927        "otMacName"             : "openTypeNameCompatibleFullName", 
     928        "weightName"    : "postscriptWeightName", 
     929        "weightValue"   : "openTypeOS2WeightClass", 
     930        "ttVendor"              : "openTypeOS2VendorID", 
     931        "uniqueID"              : "postscriptUniqueID", 
     932        "fontName"              : "postscriptFontName", 
     933        "fondID"                : "macintoshFONDFamilyID", 
     934        "fondName"              : "macintoshFONDName", 
     935        "defaultWidth"  : "postscriptDefaultWidthX", 
     936        "slantAngle"    : "postscriptSlantAngle", 
     937        "fullName"              : "postscriptFullName", 
     938        # require special value conversion 
     939        "fontStyle"             : "styleMapStyleName", 
     940        "widthName"             : "openTypeOS2WidthClass", 
     941        "msCharSet"             : "postscriptWindowsCharacterSet" 
     942} 
     943_fontInfoAttributesVersion2To1 = _flipDict(_fontInfoAttributesVersion1To2) 
     944deprecatedFontInfoAttributesVersion2 = set(_fontInfoAttributesVersion1To2.keys()) 
     945 
     946_fontStyle1To2 = { 
     947        64 : "regular", 
     948        1  : "italic", 
     949        32 : "bold", 
     950        33 : "bold italic" 
     951} 
     952_fontStyle2To1 = _flipDict(_fontStyle1To2) 
     953# Some UFO 1 files have 0 
     954_fontStyle1To2[0] = "regular" 
     955 
     956_widthName1To2 = { 
     957        "Ultra-condensed" : 1, 
     958        "Extra-condensed" : 2, 
     959        "Condensed"               : 3, 
     960        "Semi-condensed"  : 4, 
     961        "Medium (normal)" : 5, 
     962        "Semi-expanded"   : 6, 
     963        "Expanded"                : 7, 
     964        "Extra-expanded"  : 8, 
     965        "Ultra-expanded"  : 9 
     966} 
     967_widthName2To1 = _flipDict(_widthName1To2) 
     968# FontLab's default width value is "Normal". 
     969# Many format version 1 UFOs will have this. 
     970_widthName1To2["Normal"] = 5 
     971# FontLab has an "All" width value. In UFO 1 
     972# move this up to "Normal". 
     973_widthName1To2["All"] = 5 
     974# "medium" appears in a lot of UFO 1 files. 
     975_widthName1To2["medium"] = 5 
     976 
     977_msCharSet1To2 = { 
     978        0       : 1, 
     979        1       : 2, 
     980        2       : 3, 
     981        77      : 4, 
     982        128 : 5, 
     983        129 : 6, 
     984        130 : 7, 
     985        134 : 8, 
     986        136 : 9, 
     987        161 : 10, 
     988        162 : 11, 
     989        163 : 12, 
     990        177 : 13, 
     991        178 : 14, 
     992        186 : 15, 
     993        200 : 16, 
     994        204 : 17, 
     995        222 : 18, 
     996        238 : 19, 
     997        255 : 20 
     998} 
     999_msCharSet2To1 = _flipDict(_msCharSet1To2) 
     1000 
     1001def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value): 
     1002        """ 
     1003        Convert value from version 1 to version 2 format. 
     1004        Returns the new attribute name and the converted value. 
     1005        If the value is None, None will be returned for the new value. 
     1006        """ 
     1007        # convert floats to ints if possible 
     1008        if isinstance(value, float): 
     1009                if int(value) == value: 
     1010                        value = int(value) 
     1011        if value is not None: 
     1012                if attr == "fontStyle": 
     1013                        v = _fontStyle1To2.get(value) 
     1014                        if v is None: 
     1015                                raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) 
     1016                        value = v 
     1017                elif attr == "widthName": 
     1018                        v = _widthName1To2.get(value) 
     1019                        if v is None: 
     1020                                raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) 
     1021                        value = v 
     1022                elif attr == "msCharSet": 
     1023                        v = _msCharSet1To2.get(value) 
     1024                        if v is None: 
     1025                                raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) 
     1026                        value = v 
     1027        attr = _fontInfoAttributesVersion1To2.get(attr, attr) 
     1028        return attr, value 
     1029 
     1030def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value): 
     1031        """ 
     1032        Convert value from version 2 to version 1 format. 
     1033        Returns the new attribute name and the converted value. 
     1034        If the value is None, None will be returned for the new value. 
     1035        """ 
     1036        if value is not None: 
     1037                if attr == "styleMapStyleName": 
     1038                        value = _fontStyle2To1.get(value) 
     1039                elif attr == "openTypeOS2WidthClass": 
     1040                        value = _widthName2To1.get(value) 
     1041                elif attr == "postscriptWindowsCharacterSet": 
     1042                        value = _msCharSet2To1.get(value) 
     1043        attr = _fontInfoAttributesVersion2To1.get(attr, attr) 
     1044        return attr, value 
     1045 
     1046def _convertFontInfoDataVersion1ToVersion2(data): 
     1047        converted = {} 
     1048        for attr, value in data.items(): 
     1049                # FontLab gives -1 for the weightValue 
     1050                # for fonts wil no defined value. Many 
     1051                # format version 1 UFOs will have this. 
     1052                if attr == "weightValue" and value == -1: 
     1053                        continue 
     1054                newAttr, newValue = convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) 
     1055                # skip if the attribute is not part of version 2 
     1056                if newAttr not in fontInfoAttributesVersion2: 
     1057                        continue 
     1058                # catch values that can't be converted 
     1059                if value is None: 
     1060                        raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) 
     1061                # store 
     1062                converted[newAttr] = newValue 
     1063        return converted 
     1064 
     1065def _convertFontInfoDataVersion2ToVersion1(data): 
     1066        converted = {} 
     1067        for attr, value in data.items(): 
     1068                newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) 
     1069                # only take attributes that are registered for version 1 
     1070                if newAttr not in fontInfoAttributesVersion1: 
     1071                        continue 
     1072                # catch values that can't be converted 
     1073                if value is None: 
     1074                        raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) 
     1075                # store 
     1076                converted[newAttr] = newValue 
     1077        return converted 
     1078 
     1079 
     1080if __name__ == "__main__": 
     1081        import doctest 
     1082        doctest.testmod() 
  • trunk/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py

    r22 r171  
    2222 
    2323print 
    24 print 'In %s, these glyphs could interpolate:'%(f.info.fullName) 
     24print 'In %s, these glyphs could interpolate:'%(f.info.postscriptFullName) 
    2525for d, names in compatibles.items(): 
    2626        if len(names) > 1: 
  • trunk/Scripts/RoboFabIntro/intro_FontObject.py

    r22 r171  
    3131else: 
    3232        # and another dialog. 
    33         Message("The current font is %s"%(f.info.fullName)) 
     33        Message("The current font is %s"%(f.info.postscriptFullName)) 
    3434 
    3535        # let's have a look at some of the attributes a RoboFab Font object has 
     
    3838        # some of the attributes map straight to the FontLab Font class 
    3939        # We just straightened the camelCase here and there 
    40         print "full name of this font:", f.info.fullName 
     40        print "full name of this font:", f.info.postscriptFullName 
    4141        print "list of glyph names:", f.keys() 
    4242        print 'ascender:', f.info.ascender 
  • trunk/Scripts/RoboFabIntro/intro_FoundrySettings.py

    r22 r171  
    5151font.info.copyright = mySettings['copyright'] 
    5252font.info.trademark = mySettings['trademark'] 
    53 font.info.license = mySettings['license'] 
    54 font.info.licenseURL = mySettings['licenseurl'] 
    55 font.info.notice = mySettings['notice'] 
    56 font.info.ttVendor = mySettings['ttvendor'] 
    57 font.info.vendorURL = mySettings['vendorurl'] 
    58 font.info.designer = mySettings['designer'] 
    59 font.info.designerURL = mySettings['designerurl'] 
     53font.info.openTypeNameLicense = mySettings['license'] 
     54font.info.openTypeNameLicenseURL = mySettings['licenseurl'] 
     55font.info.openTypeNameDescription = mySettings['notice'] 
     56font.info.openTypeOS2VendorID = mySettings['ttvendor'] 
     57font.info.openTypeNameManufacturerURL = mySettings['vendorurl'] 
     58font.info.openTypeNameDesigner = mySettings['designer'] 
     59font.info.openTypeNameDesignerURL = mySettings['designerurl'] 
    6060 
    6161# and call the update method 
  • trunk/Scripts/RoboFabIntro/intro_Kerning.py

    r22 r171  
    3030 
    3131# kerning gives you access to some bits of global data 
    32 print "%s has %s kerning pairs"%(f.info.fullName, len(kerning)) 
     32print "%s has %s kerning pairs"%(f.info.postscriptFullName, len(kerning)) 
    3333print "the average kerning value is %s"%kerning.getAverage() 
    3434min, max = kerning.getExtremes() 
  • trunk/Scripts/RoboFabUtils/RobustBatchGenerate.py

    r22 r171  
    2626 
    2727def generateOne(f, dstDir): 
    28         print "generating %s"%f.info.fullName 
     28        print "generating %s"%f.info.postscriptFullName 
    2929        f.generate('mactype1',  dstDir) 
    3030         
  • trunk/Scripts/RoboFabUtils/TestFontEquality.py

    r22 r171  
    99line = [] 
    1010for n in af: 
    11         line.append(`n.info.fullName`) 
     11        line.append(`n.info.postscriptFullName`) 
    1212results.append(line) 
    1313 
     
    1515        one = af[i] 
    1616        line = [] 
    17         line.append(af[i].info.fullName) 
     17        line.append(af[i].info.postscriptFullName) 
    1818        for j in range(len(af)): 
    1919                other = af[j]