source: trunk/robofab/Lib/robofab/objects/objectsFL.py @ 42

Revision 42, 81.7 KB checked in by erik, 5 years ago (diff)

Reimplements the maximum items for the bluesvalues. New copy() method fo the FL flavored psHints object.

Line 
1"""UFO implementation for the objects as used by FontLab 4.5 and higher"""
2
3from FL import *       
4
5from robofab.tools.toolsFL import GlyphIndexTable,\
6                AllFonts, NewGlyph
7from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\
8                BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\
9                roundPt, addPt, _box,\
10                MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
11                relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\
12                BasePostScriptFontHintValues, postScriptHintDataLibKey
13from fontTools.misc import arrayTools
14from robofab.pens.flPen import FLPointPen
15from robofab import RoboFabError
16import os
17from robofab.plistlib import Data, Dict, readPlist, writePlist
18from StringIO import StringIO
19
20# local encoding
21if os.name in ["mac", "posix"]:
22        LOCAL_ENCODING = "macroman"
23else:
24        LOCAL_ENCODING = "latin-1"
25
26# a list of attributes that are to be copied when copying a glyph.
27# this is used by glyph.copy and font.insertGlyph
28GLYPH_COPY_ATTRS = [
29        "name",
30        "width",
31        "unicodes",
32        "note",
33        "lib",
34        ]
35
36# Generate Types
37PC_TYPE1 = 'pctype1'
38PC_MM = 'pcmm'
39PC_TYPE1_ASCII = 'pctype1ascii'
40PC_MM_ASCII = 'pcmmascii'
41UNIX_ASCII = 'unixascii'
42MAC_TYPE1 = 'mactype1'
43OTF_CFF = 'otfcff'
44OTF_TT = 'otfttf'
45MAC_TT = 'macttf'
46MAC_TT_DFONT = 'macttdfont'
47
48# doc for these functions taken from: http://dev.fontlab.net/flpydoc/
49#                       internal name                   (FontLab name,          extension)
50_flGenerateTypes ={     PC_TYPE1                :       (ftTYPE1,                       'pfb'),         # PC Type 1 font (binary/PFB)
51                        PC_MM           :       (ftTYPE1_MM,            'mm'),          # PC MultipleMaster font (PFB)
52                        PC_TYPE1_ASCII  :       (ftTYPE1ASCII,          'pfa'),         # PC Type 1 font (ASCII/PFA)
53                        PC_MM_ASCII             :       (ftTYPE1ASCII_MM,               'mm'),          # PC MultipleMaster font (ASCII/PFA)
54                        UNIX_ASCII              :       (ftTYPE1ASCII,          'pfa'),         # UNIX ASCII font (ASCII/PFA)
55                        OTF_TT          :       (ftTRUETYPE,                    'ttf'),         # PC TrueType/TT OpenType font (TTF)
56                        OTF_CFF         :       (ftOPENTYPE,                    'otf'),         # PS OpenType (CFF-based) font (OTF)
57                        MAC_TYPE1               :       (ftMACTYPE1,                    'suit'),                # Mac Type 1 font (generates suitcase  and LWFN file, optionally AFM)
58                        MAC_TT          :       (ftMACTRUETYPE,         'ttf'),         # Mac TrueType font (generates suitcase)
59                        MAC_TT_DFONT    :       (ftMACTRUETYPE_DFONT,   'dfont'),       # Mac TrueType font (generates suitcase with resources in data fork)
60                        }
61
62## FL Hint stuff
63# this should not be referenced outside of this module
64# since we may be changing the way this works in the future.
65
66
67"""
68
69        FontLab implementation of psHints object
70       
71        Most of the FL methods relating to ps hints return a list of 16 items.
72        These values are for the 16 corners of a 4 axis multiple master.
73        The odd thing is that even single masters get these 16 values.
74        RoboFab doesn't access the MM masters, so by default, the psHints
75        object only works with the first element. If you want to access the other
76        values in the list, give a value between 0 and 15 for impliedMasterIndex
77        when creating the object.
78
79        From the FontLab docs:
80        http://dev.fontlab.net/flpydoc/
81
82        blue_fuzz
83        blue_scale
84        blue_shift
85
86        blue_values_num(integer)             - number of defined blue values
87        blue_values[integer[integer]]        - two-dimentional array of BlueValues
88                                         master index is top-level index
89
90        other_blues_num(integer)             - number of defined OtherBlues values
91        other_blues[integer[integer]]        - two-dimentional array of OtherBlues
92                                               master index is top-level index
93
94        family_blues_num(integer)            - number of FamilyBlues records
95        family_blues[integer[integer]]       - two-dimentional array of FamilyBlues
96                                               master index is top-level index
97
98        family_other_blues_num(integer)      - number of FamilyOtherBlues records
99        family_other_blues[integer[integer]] - two-dimentional array of FamilyOtherBlues
100                                               master index is top-level index
101
102        force_bold[integer]                  - list of Force Bold values, one for
103                                               each master
104        stem_snap_h_num(integer)
105        stem_snap_h
106        stem_snap_v_num(integer)
107        stem_snap_v
108 """
109
110class PostScriptFontHintValues(BasePostScriptFontHintValues):
111        """     Wrapper for font-level PostScript hinting information for FontLab.
112                Blues values, stem values.
113        """
114        def __init__(self, font=None, impliedMasterIndex=0):
115                self._object = font.naked()
116                self._masterIndex = impliedMasterIndex
117       
118        def copy(self):
119                from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues
120                return _PostScriptFontHintValues(data=self.asDict())
121                       
122        def _getBlueFuzz(self):
123                return self._object.blue_fuzz[self._masterIndex]
124        def _setBlueFuzz(self, value):
125                self._object.blue_fuzz[self._masterIndex] = value
126
127        def _getBlueScale(self):
128                return self._object.blue_scale[self._masterIndex]
129        def _setBlueScale(self, value):
130                self._object.blue_scale[self._masterIndex] = float(value)
131
132        def _getBlueShift(self):
133                return self._object.blue_shift[self._masterIndex]
134        def _setBlueShift(self, value):
135                self._object.blue_shift[self._masterIndex] = value
136
137        def _getForceBold(self):
138                return self._object.force_bold[self._masterIndex] == 1
139               
140        def _setForceBold(self, value):
141                if value:
142                        value = 1
143                else:
144                        value = 0
145                self._object.force_bold[self._masterIndex] = value
146       
147        # Note: these attributes are wrapppers for lists,
148        # but regular list operatons won't have any effect.
149        # you really have to _get_ and _set_ a list.
150       
151        def _asPairs(self, l):
152                """Split a list of numbers into a list of pairs"""
153                assert len(l)%2 == 0, "Even number of values required: %s"%(`l`)
154                n = [[l[i], l[i+1]] for i in range(0, len(l), 2)]
155                n.sort()
156                return n
157       
158        def _flattenPairs(self, l):
159                """The reverse of _asPairs"""
160                n = []
161                l.sort()
162                for i in l:
163                        assert len(i) == 2, "Each entry must consist of two numbers"
164                        n.append(i[0])
165                        n.append(i[1])
166                return n
167       
168        def _getBlueValues(self):
169                        return self._asPairs(self._object.blue_values[self._masterIndex])
170        def _setBlueValues(self, values):
171                values = self._flattenPairs(values)
172                self._object.blue_values_num = min(self._attributeNames['blueValues']['max']*2, len(values))
173                for i in range(self._object.blue_values_num):
174                        self._object.blue_values[self._masterIndex][i] = values[i]
175
176        def _getOtherBlues(self):
177                        return self._asPairs(self._object.other_blues[self._masterIndex])
178        def _setOtherBlues(self, values):
179                values = self._flattenPairs(values)
180                self._object.other_blues_num = min(self._attributeNames['otherBlues']['max']*2, len(values))
181                for i in range(self._object.other_blues_num):
182                        self._object.other_blues[self._masterIndex][i] = values[i]
183
184        def _getFamilyBlues(self):
185                        return self._asPairs(self._object.family_blues[self._masterIndex])
186        def _setFamilyBlues(self, values):
187                values = self._flattenPairs(values)
188                self._object.family_blues_num = min(self._attributeNames['familyBlues']['max']*2, len(values))
189                for i in range(self._object.family_blues_num):
190                        self._object.family_blues[self._masterIndex][i] = values[i]
191
192        def _getFamilyOtherBlues(self):
193                        return self._asPairs(self._object.family_other_blues[self._masterIndex])
194        def _setFamilyOtherBlues(self, values):
195                values = self._flattenPairs(values)
196                self._object.family_other_blues_num = min(self._attributeNames['familyOtherBlues']['max']*2, len(values))
197                for i in range(self._object.family_other_blues_num):
198                        self._object.family_other_blues[self._masterIndex][i] = values[i]
199
200        def _getVStems(self):
201                        return list(self._object.stem_snap_v[self._masterIndex])
202        def _setVStems(self, values):
203                self._object.stem_snap_v_num = min(self._attributeNames['vStems']['max'], len(values))
204                for i in range(self._object.stem_snap_v_num):
205                        self._object.stem_snap_v[self._masterIndex][i] = values[i]
206
207        def _getHStems(self):
208                        return list(self._object.stem_snap_h[self._masterIndex])
209        def _setHStems(self, values):
210                self._object.stem_snap_h_num = min(self._attributeNames['hStems']['max'], len(values))
211                for i in range(self._object.stem_snap_h_num):
212                        self._object.stem_snap_h[self._masterIndex][i] = values[i]
213
214        blueFuzz = property(_getBlueFuzz, _setBlueFuzz, doc="postscript hints: bluefuzz value")
215        blueScale = property(_getBlueScale, _setBlueScale, doc="postscript hints: bluescale value")
216        blueShift = property(_getBlueShift, _setBlueShift, doc="postscript hints: blueshift value")
217        forceBold = property(_getForceBold, _setForceBold, doc="postscript hints: force bold value")
218        blueValues = property(_getBlueValues, _setBlueValues, doc="postscript hints: blue values")
219        otherBlues = property(_getOtherBlues, _setOtherBlues, doc="postscript hints: other blue values")
220        familyBlues = property(_getFamilyBlues, _setFamilyBlues, doc="postscript hints: family blue values")
221        familyOtherBlues = property(_getFamilyOtherBlues, _setFamilyOtherBlues, doc="postscript hints: family other blue values")
222        vStems = property(_getVStems, _setVStems, doc="postscript hints: vertical stem values")
223        hStems = property(_getHStems, _setHStems, doc="postscript hints: horizontal stem values")
224       
225               
226
227def getPostScriptHintDataFromLib(aFont, fontLib):
228        hintData = fontLib.get(postScriptHintDataLibKey)
229        psh = PostScriptFontHintValues(aFont)
230        psh.fromDict(hintData)
231
232
233def _glyphHintsToDict(glyph):
234        data = {}
235        ##
236        ## horizontal and vertical hints
237        ##
238        # glyph.hhints and glyph.vhints returns a list of Hint objects.
239        # Hint objects have position and width attributes.
240        data['hhints'] = []
241        for index in xrange(len(glyph.hhints)):
242                hint = glyph.hhints[index]
243                d = {   'position' : hint.position,
244                        'width' : hint.width,
245                        }
246                data['hhints'].append(d)
247        if not data['hhints']:
248                del data['hhints']
249        data['vhints'] = []
250        for index in xrange(len(glyph.vhints)):
251                hint = glyph.vhints[index]
252                d = {   'position' : hint.position,
253                        'width' : hint.width,
254                        }
255                data['vhints'].append(d)
256        if not data['vhints']:
257                del data['vhints']
258        ##
259        ## horizontal and vertical links
260        ##
261        # glyph.hlinks and glyph.vlinks returns a list of Link objects.
262        # Link objects have node1 and node2 attributes.
263        data['hlinks'] = []
264        for index in xrange(len(glyph.hlinks)):
265                link = glyph.hlinks[index]
266                d = {   'node1' : link.node1,
267                        'node2' : link.node2,
268                        }
269                data['hlinks'].append(d)
270        if not data['hlinks']:
271                del data['hlinks']
272        data['vlinks'] = []
273        for index in xrange(len(glyph.vlinks)):
274                link = glyph.vlinks[index]
275                d = {   'node1' : link.node1,
276                        'node2' : link.node2,
277                        }
278                data['vlinks'].append(d)
279        if not data['vlinks']:
280                del data['vlinks']
281        ##
282        ## replacement table
283        ##
284        # glyph.replace_table returns a list of Replace objects.
285        # Replace objects have type and index attributes.
286        data['replace_table'] = []
287        for index in xrange(len(glyph.replace_table)):
288                replace = glyph.replace_table[index]
289                d = {   'type' : replace.type,
290                        'index' : replace.index,
291                        }
292                data['replace_table'].append(d)
293        if not data['replace_table']:
294                del data['replace_table']
295        # XXX
296        # need to support glyph.instructions and glyph.hdmx?
297        # they are not documented very well.
298        return data
299
300def _dictHintsToGlyph(glyph, aDict):
301        # clear existing hints first
302        # RemoveHints requires an "integer mode" argument
303        # but it is not documented. from some simple experiments
304        # i deduced that
305        # 1 = horizontal hints and links,
306        # 2 = vertical hints and links
307        # 3 = all hints and links
308        glyph.RemoveHints(3)
309        ##
310        ## horizontal and vertical hints
311        ##
312        if aDict.has_key('hhints'):
313                for d in aDict['hhints']:
314                        glyph.hhints.append(Hint(d['position'], d['width']))
315        if aDict.has_key('vhints'):
316                for d in aDict['vhints']:
317                        glyph.vhints.append(Hint(d['position'], d['width']))
318        ##
319        ## horizontal and vertical links
320        ##
321        if aDict.has_key('hlinks'):
322                for d in aDict['hlinks']:
323                        glyph.hlinks.append(Link(d['node1'], d['node2']))
324        if aDict.has_key('vlinks'):
325                for d in aDict['vlinks']:
326                        glyph.vlinks.append(Link(d['node1'], d['node2']))
327        ##
328        ## replacement table
329        ##
330        if aDict.has_key('replace_table'):
331                for d in aDict['replace_table']:
332                        glyph.replace_table.append(Replace(d['type'], d['index']))
333       
334# FL Node Types
335flMOVE = 17                     
336flLINE = 1
337flCURVE = 35
338flOFFCURVE = 65
339flSHARP = 0
340# I have no idea what the difference between
341# "smooth" and "fixed" is, but booth values
342# are returned by FL
343flSMOOTH = 4096
344flFIXED = 12288
345
346
347_flToRFSegmentDict = {  flMOVE          :       MOVE,
348                                flLINE          :       LINE,
349                                flCURVE :       CURVE,
350                                flOFFCURVE      :       OFFCURVE
351                        }
352
353_rfToFLSegmentDict = {}
354for k, v in _flToRFSegmentDict.items():
355        _rfToFLSegmentDict[v] = k
356               
357def _flToRFSegmentType(segmentType):
358        return _flToRFSegmentDict[segmentType]
359
360def _rfToFLSegmentType(segmentType):
361        return _rfToFLSegmentDict[segmentType]
362               
363def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)):
364        ogCenter = (centerX, centerY)
365        scaledCenter = (centerX * scaleX, centerY * scaleY)
366        shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1])
367        scaledPointX = (pointX * scaleX) - shiftVal[0]
368        scaledPointY = (pointY * scaleY) - shiftVal[1]
369        return (scaledPointX, scaledPointY)
370
371# Nostalgia code:
372def CurrentFont():
373        """Return a RoboFab font object for the currently selected font."""
374        f = fl.font
375        if f is not None:
376                return RFont(fl.font)
377        return None
378       
379def CurrentGlyph():
380        """Return a RoboFab glyph object for the currently selected glyph."""
381        from robofab.world import AllFonts
382        currentPath = fl.font.file_name
383        if fl.glyph is None:
384                return None
385        glyphName = fl.glyph.name
386        currentFont = None
387        # is this font already loaded as an RFont?
388        for font in AllFonts():
389                # ugh this won't work because AllFonts sees non RFonts as well....
390                if font.path == currentPath:
391                        currentFont = font
392                        break
393        xx =  currentFont[glyphName]
394        #print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent()
395        return xx
396       
397def OpenFont(path=None, note=None):
398        """Open a font from a path."""
399        if path == None:
400                from robofab.interface.all.dialogs import GetFile
401                path = GetFile(note)
402        if path:
403                if path[-4:].lower() in ['.vfb', '.VFB', '.bak', '.BAK']:
404                        f = Font(path)
405                        fl.Add(f)
406                        return RFont(f)
407        return None
408       
409def NewFont(familyName=None, styleName=None):
410        """Make a new font"""
411        from FL import fl, Font
412        f = Font()
413        fl.Add(f)
414        rf = RFont(f)
415        rf.info.familyName = familyName
416        rf.info.styleName = styleName
417        return rf
418
419def AllFonts():
420        """Return a list of all open fonts."""
421        fontCount = len(fl)
422        all = []
423        for index in xrange(fontCount):
424                naked = fl[index]
425                all.append(RFont(naked))
426        return all
427
428# the lib getter and setter are shared by RFont and RGlyph     
429def _get_lib(self):
430        data = self._object.customdata
431        if data:
432                f = StringIO(data)
433                try:
434                        pList = readPlist(f)
435                except: # XXX ugh, plistlib can raise lots of things
436                        # Anyway, customdata does not contain valid plist data,
437                        # but we don't need to toss it!
438                        pList = {"org.robofab.fontlab.customdata": Data(data)}
439        else:
440                pList = {}
441        # pass it along to the lib object
442        l = RLib(pList)
443        l.setParent(self)
444        return l
445               
446def _set_lib(self, aDict):
447        l = RLib({})
448        l.setParent(self)
449        l.update(aDict)
450
451
452def _normalizeLineEndings(s):
453        return s.replace("\r\n", "\n").replace("\r", "\n")
454
455
456class RFont(BaseFont):
457        """RoboFab UFO wrapper for FL Font object"""
458
459        _title = "FLFont"
460
461        def __init__(self, font=None):
462                BaseFont.__init__(self)
463                if font is None:
464                        from FL import fl, Font
465                        # rather than raise an error we could just start a new font.
466                        font = Font()
467                        fl.Add(font)
468                        #raise RoboFabError, "RFont: there's nothing to wrap!?"
469                self._object = font
470                self._lib = {}
471                self._supportHints = False
472
473        def keys(self):
474                keys = {}
475                for glyph in self._object.glyphs:
476                        glyphName = glyph.name
477                        if glyphName in keys:
478                                n = 1
479                                while ("%s#%s" % (glyphName, n)) in keys:
480                                        n += 1
481                                newGlyphName = "%s#%s" % (glyphName, n)
482                                print "RoboFab encountered a duplicate glyph name, renaming %r to %r" % (glyphName, newGlyphName)
483                                glyphName = newGlyphName
484                                glyph.name = glyphName
485                        keys[glyphName] = None
486                return keys.keys()
487
488        def has_key(self, glyphName):
489                glyph = self._object[glyphName]
490                if glyph is None:
491                        return False
492                else:
493                        return True
494
495        __contains__ = has_key
496
497        def __setitem__(self, glyphName, glyph):
498                self._object[glyphName] = glyph.naked()
499               
500        def __cmp__(self, other):
501                if not hasattr(other, '_object'):
502                        return -1
503                return self._compare(other)
504        #       if self._object.file_name == other._object.file_name:
505        #               # so, names match.
506        #               # this will falsely identify two distinct "Untitled"
507        #               # let's check some more
508        #               return 0
509        #       else:
510        #               return -1
511       
512
513        def _get_psHints(self):
514                return PostScriptFontHintValues(self)
515
516        psHints = property(_get_psHints, doc="font level postscript hint data")
517
518        def _get_info(self):
519                return RInfo(self._object)
520       
521        info = property(_get_info, doc="font info object")
522       
523        def _get_kerning(self):
524                kerning = {}
525                f = self._object
526                for g in f.glyphs:
527                        for p in g.kerning:
528                                try:
529                                        key = (g.name, f[p.key].name)
530                                        kerning[key] = p.value
531                                except AttributeError: pass #catch for TT exception
532                rk = RKerning(kerning)
533                rk.setParent(self)
534                return rk
535               
536        kerning = property(_get_kerning, doc="a kerning object")
537       
538        def _set_groups(self, aDict):
539                g = RGroups({})
540                g.setParent(self)
541                g.update(aDict)
542       
543        def _get_groups(self):
544                groups = {}
545                for i in self._object.classes:
546                        # test to make sure that the class is properly formatted
547                        if i.find(':') == -1:
548                                continue
549                        key = i.split(':')[0]
550                        value = i.split(':')[1].lstrip().split(' ')
551                        groups[key] = value
552                rg = RGroups(groups)
553                rg.setParent(self)
554                return rg
555               
556        groups = property(_get_groups, _set_groups, doc="a group object")
557       
558        lib = property(_get_lib, _set_lib, doc="font lib object")
559       
560        #
561        # attributes
562        #
563       
564        def _get_fontIndex(self):
565                # find the index of the font
566                # by comparing the file_name
567                # to all open fonts. if the
568                # font has no file_name, meaning
569                # it is a new, unsaved font,
570                # return the index of the first
571                # font with no file_name.
572                selfFileName = self._object.file_name
573                fontCount = len(fl)
574                for index in xrange(fontCount):
575                        other = fl[index]
576                        if other.file_name == selfFileName:
577                                return index
578       
579        fontIndex = property(_get_fontIndex, doc="the fontindex for this font")
580       
581        def _get_path(self):
582                return self._object.file_name
583
584        path = property(_get_path, doc="path to the font")
585       
586        def _get_fileName(self):
587                if self.path is None:
588                        return None
589                return os.path.split(self.path)
590       
591        fileName = property(_get_fileName, doc="the font's file name")
592       
593        def _get_selection(self):
594                # return a list of glyph names for glyphs selected in the font window
595                l=[]
596                for i in range(len(self._object.glyphs)):
597                        if fl.Selected(i) == 1:
598                                l.append(self._object[i].name)
599                return l
600               
601        def _set_selection(self, list):
602                fl.Unselect()
603                for i in list:
604                        fl.Select(i)
605       
606        selection = property(_get_selection, _set_selection, doc="the glyph selection in the font window")
607       
608               
609        def _makeGlyphlist(self):
610                # To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters.
611                gl = []
612                for c in self:
613                        gl.append(c)
614                return gl
615       
616        def _get_glyphs(self):
617                return self._makeGlyphlist()
618       
619        glyphs = property(_get_glyphs, doc="A list of all glyphs in the font, to allow iterations through Font.glyphs")
620                       
621        def update(self):
622                """Don't forget to update the font when you are done."""
623                fl.UpdateFont(self.fontIndex)
624
625        def save(self, path=None):
626                """Save the font, path is required."""
627                if not path:
628                        if not self._object.file_name:
629                                raise RoboFabError, "No destination path specified."
630                        else:
631                                path = self._object.file_name
632                fl.Save(self.fontIndex, path)
633       
634        def close(self, save=False):
635                """Close the font, saving is optional."""
636                if save:
637                        self.save()
638                else:
639                        self._object.modified = 0
640                fl.Close(self.fontIndex)
641       
642        def getGlyph(self, glyphName):
643                # XXX may need to become private
644                flGlyph = self._object[glyphName]
645                if flGlyph is not None:
646                        glyph = RGlyph(flGlyph)
647                        glyph.setParent(self)
648                        return glyph
649                return self.newGlyph(glyphName)
650
651        def newGlyph(self, glyphName, clear=True):
652                """Make a new glyph"""
653                #if generate:
654                #       g = GenerateGlyph(self._object, glyphName, replace=clear)
655                #else:
656                g = NewGlyph(self._object, glyphName, clear)
657                return RGlyph(g)
658       
659        def insertGlyph(self, glyph, as=None):
660                """Returns a new glyph that has been inserted into the font.
661                as = another glyphname if you want to insert as with that."""
662                from robofab.objects.objectsRF import RFont as _RFont
663                from robofab.objects.objectsRF import RGlyph as _RGlyph
664                oldGlyph = glyph
665                if as is None:
666                        name = oldGlyph.name
667                else:
668                        name = as
669                # clear the destination glyph if it exists.
670                if self.has_key(name):
671                        self[name].clear()
672                # get the parent for the glyph
673                otherFont = oldGlyph.getParent()
674                # in some cases we will use the native
675                # FL method for appending a glyph.
676                useNative = True
677                testingNative = True
678                while testingNative:
679                        # but, maybe it is an orphan glyph.
680                        # in that case we should not use the native method.
681                        if otherFont is None:
682                                useNative = False
683                                testingNative = False
684                        # or maybe the glyph is coming from a NoneLab font
685                        if otherFont is not None:
686                                if isinstance(otherFont, _RFont):
687                                        useNative = False
688                                        testingNative = False
689                                # but, it could be a copied FL glyph
690                                # which is a NoneLab glyph that
691                                # has a FontLab font as the parent
692                                elif isinstance(otherFont, RFont):
693                                        useNative = False
694                                        testingNative = False
695                        # or, maybe the glyph is being replaced, in which
696                        # case the native method should not be used
697                        # since FL will destroy any references to the glyph
698                        if self.has_key(name):
699                                useNative = False
700                                testingNative = False
701                        # if the glyph contains components the native
702                        # method should not be used since FL does
703                        # not reference glyphs in components by
704                        # name, but by index (!!!).
705                        if len(oldGlyph.components) != 0:
706                                useNative = False
707                                testingNative = False
708                        testingNative = False
709                # finally, insert the glyph.
710                if useNative:
711                        font = self.naked()
712                        otherFont = oldGlyph.getParent().naked()
713                        self.naked().glyphs.append(otherFont[name])
714                        newGlyph = self.getGlyph(name)
715                else:   
716                        newGlyph = self.newGlyph(name)
717                        newGlyph.appendGlyph(oldGlyph)
718                        for attr in GLYPH_COPY_ATTRS:
719                                if attr == "name":
720                                        value = name
721                                else:
722                                        value = getattr(oldGlyph, attr)
723                                setattr(newGlyph, attr, value)
724                if self._supportHints:
725                        # now we need to transfer the hints from
726                        # the old glyph to the new glyph. we'll do this
727                        # via the dict to hint functions.
728                        hintDict = {}
729                        # if the glyph is a NoneLab glyph, then we need
730                        # to extract the ps hints from the lib
731                        if isinstance(oldGlyph, _RGlyph):
732                                hintDict = oldGlyph.lib.get(postScriptHintDataLibKey, {})
733                        # otherwise we need to extract the hint dict from the glyph
734                        else:
735                                hintDict = _glyphHintsToDict(oldGlyph.naked())
736                        # now apply the hint data
737                        if hintDict:
738                                _dictHintsToGlyph(newGlyph.naked(), hintDict)
739                        # delete any remaining hint data from the glyph lib
740                        if newGlyph.lib.has_key(postScriptHintDataLibKey):
741                                del newGlyph.lib[postScriptHintDataLibKey]
742                return newGlyph
743       
744        def removeGlyph(self, glyphName):
745                """remove a glyph from the font"""
746                index = self._object.FindGlyph(glyphName)
747                if index != -1:
748                        del self._object.glyphs[index]
749
750        #
751        # opentype
752        #
753
754        def getOTClasses(self):
755                """Return all OpenType classes as a dict. Relies on properly formatted classes."""
756                classes = {}
757                c = self._object.ot_classes
758                if c is None:
759                        return classes
760                c = c.replace('\r', '').replace('\n', '').split(';')
761                for i in c:
762                        if i.find('=') != -1:
763                                value = []
764                                i = i.replace(' = ', '=')
765                                name = i.split('=')[0]
766                                v = i.split('=')[1].replace('[', '').replace(']', '').split(' ')
767                                #catch double spaces?
768                                for j in v:
769                                        if len(j) > 0:
770                                                value.append(j)
771                                classes[name] = value
772                return classes
773               
774        def setOTClasses(self, dict):
775                """Set all OpenType classes."""
776                l = []
777                for i in dict.keys():
778                        l.append(''.join([i, ' = [', ' '.join(dict[i]), '];']))
779                self._object.ot_classes = '\n'.join(l)
780               
781        def getOTClass(self, name):
782                """Get a specific OpenType class."""
783                classes = self.getOTClasses()
784                return classes[name]
785       
786        def setOTClass(self, name, list):
787                """Set a specific OpenType class."""
788                classes = self.getOTClasses()
789                classes[name] = list
790                self.setOTClasses(classes)
791       
792        def getOTFeatures(self):
793                """Return all OpenType features as a dict keyed by name.
794                The value is a string of the text of the feature."""
795                features = {}
796                for i in self._object.features:
797                        v = []
798                        for j in i.value.replace('\r', '\n').split('\n'):
799                                if j.find(i.tag) == -1:
800                                        v.append(j)
801                        features[i.tag] = '\n'.join(v)
802                return features
803       
804        def setOTFeatures(self, dict):
805                """Set all OpenType features in the font."""
806                features= {}
807                for i in dict.keys():
808                        f = []
809                        f.append('feature %s {'%i)
810                        f.append(dict[i])
811                        f.append('} %s;'%i)
812                        features[i] = '\n'.join(f)
813                self._object.features.clean()
814                for i in features.keys():
815                        self._object.features.append(Feature(i, features[i]))
816                       
817        def getOTFeature(self, name):
818                """return a specific OpenType feature."""
819                features = self.getOTFeatures()
820                return features[name]
821       
822        def setOTFeature(self, name, text):
823                """Set a specific OpenType feature."""
824                features = self.getOTFeatures()
825                features[name] = text
826                self.setOTFeatures(features)
827               
828        #
829        # guides
830        #
831       
832        def getVGuides(self):
833                """Return a list of wrapped vertical guides in this RFont"""
834                vguides=[]
835                for i in range(len(self._object.vguides)):
836                        g = RGuide(self._object.vguides[i], i)
837                        g.setParent(self)
838                        vguides.append(g)
839                return vguides
840       
841        def getHGuides(self):
842                """Return a list of wrapped horizontal guides in this RFont"""
843                hguides=[]
844                for i in range(len(self._object.hguides)):
845                        g = RGuide(self._object.hguides[i], i)
846                        g.setParent(self)
847                        hguides.append(g)
848                return hguides
849               
850        def appendHGuide(self, position, angle=0):
851                """Append a horizontal guide"""
852                position = int(round(position))
853                angle = int(round(angle))
854                g=Guide(position, angle)
855                self._object.hguides.append(g)
856               
857        def appendVGuide(self, position, angle=0):
858                """Append a horizontal guide"""
859                position = int(round(position))
860                angle = int(round(angle))
861                g=Guide(position, angle)
862                self._object.vguides.append(g)
863               
864        def removeHGuide(self, guide):
865                """Remove a horizontal guide."""
866                pos = (guide.position, guide.angle)
867                for g in self.getHGuides():
868                        if  (g.position, g.angle) == pos:
869                                del self._object.hguides[g.index]
870                                break
871                               
872        def removeVGuide(self, guide):
873                """Remove a vertical guide."""
874                pos = (guide.position, guide.angle)
875                for g in self.getVGuides():
876                        if  (g.position, g.angle) == pos:
877                                del self._object.vguides[g.index]
878                                break
879
880        def clearHGuides(self):
881                """Clear all horizontal guides."""
882                self._object.hguides.clean()
883       
884        def clearVGuides(self):
885                """Clear all vertical guides."""
886                self._object.vguides.clean()
887
888
889        #
890        # generators
891        #
892       
893        def generate(self, outputType, path=None):
894                """
895                generate the font. outputType is the type of font to ouput.
896                --Ouput Types:
897                'pctype1'       :       PC Type 1 font (binary/PFB)
898                'pcmm'          :       PC MultipleMaster font (PFB)
899                'pctype1ascii'  :       PC Type 1 font (ASCII/PFA)
900                'pcmmascii'     :       PC MultipleMaster font (ASCII/PFA)
901                'unixascii'     :       UNIX ASCII font (ASCII/PFA)
902                'mactype1'      :       Mac Type 1 font (generates suitcase  and LWFN file)
903                'otfcff'                :       PS OpenType (CFF-based) font (OTF)
904                'otfttf'                :       PC TrueType/TT OpenType font (TTF)
905                'macttf'        :       Mac TrueType font (generates suitcase)
906                'macttdfont'    :       Mac TrueType font (generates suitcase with resources in data fork)
907                                        (doc adapted from http://dev.fontlab.net/flpydoc/)
908               
909                path can be a directory or a directory file name combo:
910                path="DirectoryA/DirectoryB"
911                path="DirectoryA/DirectoryB/MyFontName"
912                if no path is given, the file will be output in the same directory
913                as the vfb file. if no file name is given, the filename will be the
914                vfb file name with the appropriate suffix.
915                """
916                outputType = outputType.lower()
917                if not _flGenerateTypes.has_key(outputType):
918                        raise RoboFabError, "%s output type is not supported"%outputType
919                flOutputType, suffix = _flGenerateTypes[outputType]
920                if path is None:
921                        filePath, fileName = os.path.split(self.path)
922                        fileName = fileName.replace('.vfb', '')
923                else:
924                        if os.path.isdir(path):
925                                filePath = path
926                                fileName = os.path.split(self.path)[1].replace('.vfb', '')
927                        else:
928                                filePath, fileName = os.path.split(path)
929                if '.' in fileName:
930                        raise RoboFabError, "filename cannot contain periods.", fileName
931                fileName = '.'.join([fileName, suffix])
932                finalPath = os.path.join(filePath, fileName)
933                # generate is (oddly) an application level method
934                # rather than a font level method. because of this,
935                # the font must be the current font. so, make it so.
936                fl.ifont = self.fontIndex
937                fl.GenerateFont(flOutputType, finalPath)
938       
939        def _writeOpenTypeFeaturesToLib(self, fontLib):
940                flFont = self.naked()
941                if flFont.ot_classes:
942                        fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(
943                                        flFont.ot_classes)
944                if flFont.features:
945                        features = {}
946                        order = []
947                        for feature in flFont.features:
948                                order.append(feature.tag)
949                                features[feature.tag] = _normalizeLineEndings(feature.value)
950                        fontLib["org.robofab.opentype.features"] = features
951                        fontLib["org.robofab.opentype.featureorder"] = order
952       
953        def writeUFO(self, path=None, doProgress=False,
954                        glyphNameToFileNameFunc=None):
955                """write a font to .ufo"""
956                from robofab.ufoLib import makeUFOPath, UFOWriter
957                from robofab.interface.all.dialogs import ProgressBar
958                if glyphNameToFileNameFunc is None:
959                        glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc()
960                        if glyphNameToFileNameFunc is None:
961                                from robofab.tools.glyphNameSchemes import glyphNameToShortFileName
962                                glyphNameToFileNameFunc = glyphNameToShortFileName
963                if not path:
964                        if self.path is None:
965                                # XXX this should really raise an exception instead
966                                from robofab.interface.all.dialogs import Message
967                                Message("Please save this font first before exporting to UFO...")
968                                return
969                        else:
970                                path = makeUFOPath(self.path)
971                doHints = self._supportHints
972                nonGlyphCount = 4
973                bar = None
974                if doProgress:
975                        bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self.glyphs))
976                try:
977                        u = UFOWriter(path)
978                        u.writeInfo(self.info)
979                        if bar:
980                                bar.tick()
981                        u.writeKerning(self.kerning.asDict())
982                        if bar:
983                                bar.tick()
984                        u.writeGroups(self.groups)
985                        if bar:
986                                bar.tick()
987                        count = nonGlyphCount
988                        glyphSet = u.getGlyphSet(glyphNameToFileNameFunc)
989                        glyphOrder = []
990                        for nakedGlyph in self.naked().glyphs:
991                                glyph = RGlyph(nakedGlyph)
992                                glyphOrder.append(glyph.name)
993                                if doHints:
994                                        hintStuff = _glyphHintsToDict(glyph.naked())
995                                        if hintStuff:
996                                                glyph.lib[postScriptHintDataLibKey] = hintStuff
997                                glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints)
998                                # remove the hint dict from the lib
999                                if doHints and glyph.lib.has_key(postScriptHintDataLibKey):
1000                                        del glyph.lib[postScriptHintDataLibKey]
1001                                if bar and not count % 10:
1002                                        bar.tick(count)
1003                                count = count + 1
1004                        assert None not in glyphOrder, glyphOrder
1005                        glyphSet.writeContents()
1006                        # We make a shallow copy if lib, since we add some stuff for export
1007                        # that doesn't need to be retained in memory.
1008                        fontLib = dict(self.lib)
1009                        # Always export the postscript font hint values
1010                        psh = PostScriptFontHintValues(self)
1011                        d = psh.asDict()
1012                        fontLib[postScriptHintDataLibKey] = d
1013                        # Export the glyph order
1014                        fontLib["org.robofab.glyphOrder"] = glyphOrder
1015                        self._writeOpenTypeFeaturesToLib(fontLib)
1016                        u.writeLib(fontLib)
1017                        if bar:
1018                                bar.tick()
1019                except KeyboardInterrupt:
1020                        if bar:
1021                                bar.close()
1022                        bar = None
1023                if bar:
1024                        bar.close()
1025       
1026        def _getGlyphOrderFromLib(self, fontLib, glyphSet):
1027                glyphOrder = fontLib.get("org.robofab.glyphOrder")
1028                if glyphOrder is not None:
1029                        # no need to keep track if the glyph order in lib once the font is loaded.
1030                        del fontLib["org.robofab.glyphOrder"]
1031                        glyphNames = []
1032                        done = {}
1033                        for glyphName in glyphOrder:
1034                                if glyphName in glyphSet:
1035                                        glyphNames.append(glyphName)
1036                                        done[glyphName] = 1
1037                        allGlyphNames = glyphSet.keys()
1038                        allGlyphNames.sort()
1039                        for glyphName in allGlyphNames:
1040                                if glyphName not in done:
1041                                        glyphNames.append(glyphName)
1042                else:
1043                        glyphNames = glyphSet.keys()
1044                        # Sort according to unicode would be best, but is really
1045                        # expensive...
1046                        glyphNames.sort()
1047                return glyphNames
1048       
1049        def _readOpenTypeFeaturesFromLib(self, fontLib):
1050                classes = fontLib.get("org.robofab.opentype.classes")
1051                if classes is not None:
1052                        del fontLib["org.robofab.opentype.classes"]
1053                        self.naked().ot_classes = classes
1054                features = fontLib.get("org.robofab.opentype.features")
1055                if features is not None:
1056                        order = fontLib.get("org.robofab.opentype.featureorder")
1057                        if order is None:
1058                                # for UFOs saved without the feature order, do the same as before.
1059                                order = features.keys()
1060                                order.sort()
1061                        else:
1062                                del fontLib["org.robofab.opentype.featureorder"]
1063                        del fontLib["org.robofab.opentype.features"]
1064                        #features = features.items()
1065                        orderedFeatures = []
1066                        for tag in order:
1067                                oneFeature = features.get(tag)
1068                                if oneFeature is not None:
1069                                        orderedFeatures.append((tag, oneFeature))
1070                        self.naked().features.clean()
1071                        for tag, src in orderedFeatures:
1072                                self.naked().features.append(Feature(tag, src))
1073       
1074        def readUFO(self, path, doProgress=False):
1075                """read a .ufo into the font"""
1076                from robofab.ufoLib import UFOReader
1077                from robofab.pens.flPen import FLPointPen
1078                from robofab.interface.all.dialogs import ProgressBar
1079                doHints = self._supportHints
1080                nonGlyphCount = 4
1081                bar = None
1082                u = UFOReader(path)
1083                glyphSet = u.getGlyphSet()
1084                fontLib = u.readLib()
1085                glyphNames = self._getGlyphOrderFromLib(fontLib, glyphSet)
1086                if doProgress:
1087                        bar = ProgressBar('Importing UFO', nonGlyphCount+len(glyphNames))
1088                try:
1089                        u.readInfo(self.info)
1090                        if bar:
1091                                bar.tick()
1092                        self._readOpenTypeFeaturesFromLib(fontLib)
1093                        self.lib.clear()
1094                        self.lib = fontLib
1095                        if bar:
1096                                bar.tick()
1097                        count = 2
1098                        for glyphName in glyphNames:
1099                                glyph = self.newGlyph(glyphName, clear=True)
1100                                pen = FLPointPen(glyph.naked())
1101                                glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen)
1102                                if doHints:
1103                                        hintData = glyph.lib.get(postScriptHintDataLibKey)
1104                                        if hintData:
1105                                                _dictHintsToGlyph(glyph.naked(), hintData)
1106                                        # now that the hints have been extracted from the glyph
1107                                        # there is no reason to keep the location in the lib.
1108                                        if glyph.lib.has_key(postScriptHintDataLibKey):
1109                                                del glyph.lib[postScriptHintDataLibKey]
1110                                glyph.update()
1111                                if bar and not count % 10:
1112                                        bar.tick(count)
1113                                count = count + 1
1114                        # import postscript font hint data
1115                        getPostScriptHintDataFromLib(self, fontLib)
1116                        self.kerning.clear()
1117                        self.kerning.update(u.readKerning())
1118                        if bar:
1119                                bar.tick()
1120                        self.groups.clear()
1121                        self.groups = u.readGroups()
1122                except KeyboardInterrupt:
1123                        bar.close()
1124                        bar = None
1125                if bar:
1126                        bar.close()
1127
1128
1129class RGlyph(BaseGlyph):
1130        """RoboFab wrapper for FL Glyph object"""
1131
1132        _title = "FLGlyph"
1133
1134        def __init__(self, flGlyph):
1135                #BaseGlyph.__init__(self)
1136                if flGlyph is None:
1137                        raise RoboFabError, "RGlyph: there's nothing to wrap!?"
1138                self._object = flGlyph
1139                self._lib = {}
1140                self._contours = None
1141               
1142        def __getitem__(self, index):
1143                return self.contours[index]
1144                       
1145        def __delitem__(self, index):
1146                self._object.DeleteContour(index)
1147                self._invalidateContours()
1148       
1149        def __len__(self):
1150                return len(self.contours)
1151               
1152        lib = property(_get_lib, _set_lib, doc="glyph lib object")
1153       
1154        def _invalidateContours(self):
1155                self._contours = None
1156       
1157        def _buildContours(self):
1158                self._contours = []
1159                for contourIndex in range(self._object.GetContoursNumber()):
1160                        c = RContour(contourIndex)
1161                        c.setParent(self)
1162                        c._buildSegments()
1163                        self._contours.append(c)
1164               
1165        #
1166        # attribute handlers
1167        #
1168       
1169        def _get_index(self):
1170                return self._object.parent.FindGlyph(self.name)
1171       
1172        index = property(_get_index, doc="return the index of the glyph in the font")
1173       
1174        def _get_name(self):
1175                return self._object.name
1176
1177        def _set_name(self, value):
1178                self._object.name = value
1179
1180        name = property(_get_name, _set_name, doc="name")
1181       
1182        def _get_psName(self):
1183                return self._object.name
1184
1185        def _set_psName(self, value):
1186                self._object.name = value
1187
1188        psName = property(_get_psName, _set_psName, doc="name")
1189       
1190        def _get_baseName(self):
1191                return self._object.name.split('.')[0]
1192       
1193        baseName = property(_get_baseName, doc="")
1194       
1195        def _get_unicode(self):
1196                return self._object.unicode
1197
1198        def _set_unicode(self, value):
1199                self._object.unicode = value
1200
1201        unicode = property(_get_unicode, _set_unicode, doc="unicode")
1202       
1203        def _get_unicodes(self):
1204                return self._object.unicodes
1205
1206        def _set_unicodes(self, value):
1207                self._object.unicodes = value
1208
1209        unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes")
1210
1211        def _get_width(self):
1212                return self._object.width
1213       
1214        def _set_width(self, value):
1215                value = int(round(value))
1216                self._object.width = value
1217               
1218        width = property(_get_width, _set_width, doc="the width")
1219       
1220        def _get_box(self):
1221                if not len(self.contours) and not len(self.components):
1222                        return (0, 0, 0, 0)
1223                r = self._object.GetBoundingRect()
1224                return (int(round(r.ll.x)), int(round(r.ll.y)), int(round(r.ur.x)), int(round(r.ur.y)))
1225                       
1226        box = property(_get_box, doc="box of glyph as a tuple (xMin, yMin, xMax, yMax)")
1227       
1228        def _get_selected(self):
1229                if fl.Selected(self._object.parent.FindGlyph(self._object.name)):
1230                        return 1
1231                else:
1232                        return 0
1233                       
1234        def _set_selected(self, value):
1235                fl.Select(self._object.name, value)
1236       
1237        selected = property(_get_selected, _set_selected, doc="Select or deselect the glyph in the font window")
1238       
1239        def _get_mark(self):
1240                return self._object.mark
1241
1242        def _set_mark(self, value):
1243                self._object.mark = value
1244
1245        mark = property(_get_mark, _set_mark, doc="mark")
1246       
1247        def _get_note(self):
1248                s = self._object.note
1249                if s is None:
1250                        return s
1251                return unicode(s, LOCAL_ENCODING)
1252
1253        def _set_note(self, value):
1254                if value is None:
1255                        value = ''
1256                if type(value) == type(u""):
1257                        value = value.encode(LOCAL_ENCODING)
1258                self._object.note = value
1259
1260        note = property(_get_note, _set_note, doc="note")
1261       
1262        #def _get_psHints(self):
1263        #       # get an object representing the postscript zone information
1264        #       # thes
1265        #       raise NotImplementedError
1266               
1267        #psHints = property(_get_psHints, doc="postscript hint data")
1268       
1269        #
1270        #       necessary evil
1271        #
1272                       
1273        def update(self):
1274                """Don't forget to update the glyph when you are done."""
1275                fl.UpdateGlyph(self._object.parent.FindGlyph(self._object.name))
1276       
1277        #
1278        #       methods to make RGlyph compatible with FL.Glyph
1279        #       ##are these still needed?
1280        #
1281       
1282        def GetBoundingRect(self, masterIndex):
1283                """FL compatibility"""
1284                return self._object.GetBoundingRect(masterIndex)
1285               
1286        def GetMetrics(self, masterIndex):
1287                """FL compatibility"""
1288                return self._object.GetMetrics(masterIndex)
1289               
1290        def SetMetrics(self, value, masterIndex):
1291                """FL compatibility"""
1292                return self._object.SetMetrics(value, masterIndex)
1293       
1294        #
1295        # object builders
1296        #
1297       
1298        def _get_anchors(self):
1299                return self.getAnchors()
1300       
1301        anchors = property(_get_anchors, doc="allow for iteration through glyph.anchors")
1302               
1303        def _get_components(self):
1304                return self.getComponents()
1305               
1306        components = property(_get_components, doc="allow for iteration through glyph.components")
1307
1308        def _get_contours(self):
1309                if self._contours is None:
1310                        self._buildContours()
1311                return self._contours
1312       
1313        contours = property(_get_contours, doc="allow for iteration through glyph.contours")
1314
1315        def getAnchors(self):
1316                """Return a list of wrapped anchors in this RGlyph."""
1317                anchors=[]
1318                for i in range(len(self._object.anchors)):
1319                        a = RAnchor(self._object.anchors[i], i)
1320                        a.setParent(self)
1321                        anchors.append(a)
1322                return anchors
1323       
1324        def getComponents(self):
1325                """Return a list of wrapped components in this RGlyph."""
1326                components=[]
1327                for i in range(len(self._object.components)):
1328                        c = RComponent(self._object.components[i], i)
1329                        c.setParent(self)
1330                        components.append(c)
1331                return components
1332       
1333        def getVGuides(self):
1334                """Return a list of wrapped vertical guides in this RGlyph"""
1335                vguides=[]
1336                for i in range(len(self._object.vguides)):
1337                        g = RGuide(self._object.vguides[i], i)
1338                        g.setParent(self)
1339                        vguides.append(g)
1340                return vguides
1341       
1342        def getHGuides(self):
1343                """Return a list of wrapped horizontal guides in this RGlyph"""
1344                hguides=[]
1345                for i in range(len(self._object.hguides)):
1346                        g = RGuide(self._object.hguides[i], i)
1347                        g.setParent(self)
1348                        hguides.append(g)
1349                return hguides 
1350       
1351        #
1352        # tools
1353        #
1354
1355        def getPointPen(self):
1356                self._invalidateContours()
1357                # Now just don't muck with glyph.contours before you're done drawing...
1358                return FLPointPen(self)
1359
1360        def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)):
1361                """Append a component to the glyph. x and y are optional offset values"""
1362                offset = roundPt((offset[0], offset[1]))
1363                p = FLPointPen(self.naked())
1364                xx, yy = scale
1365                dx, dy = offset
1366                p.addComponent(baseGlyph, (xx, 0, 0, yy, dx, dy))
1367               
1368        def appendAnchor(self, name, position):
1369                """Append an anchor to the glyph"""
1370                value = roundPt((position[0], position[1]))
1371                anchor = Anchor(name, value[0], value[1])
1372                self._object.anchors.append(anchor)
1373       
1374        def appendHGuide(self, position, angle=0):
1375                """Append a horizontal guide"""
1376                position = int(round(position))
1377                g = Guide(position, angle)
1378                self._object.hguides.append(g)
1379               
1380        def appendVGuide(self, position, angle=0):
1381                """Append a horizontal guide"""
1382                position = int(round(position))
1383                g = Guide(position, angle)
1384                self._object.vguides.append(g)
1385               
1386        def clearComponents(self):
1387                """Clear all components."""
1388                self._object.components.clean()
1389       
1390        def clearAnchors(self):
1391                """Clear all anchors."""
1392                self._object.anchors.clean()
1393       
1394        def clearHGuides(self):
1395                """Clear all horizontal guides."""
1396                self._object.hguides.clean()
1397       
1398        def clearVGuides(self):
1399                """Clear all vertical guides."""
1400                self._object.vguides.clean()
1401               
1402        def removeComponent(self, component):
1403                """Remove a specific component from the glyph. This only works
1404                if the glyph does not have duplicate components in the same location."""
1405                pos = (component.baseGlyph, component.offset, component.scale)
1406                a = self.getComponents()
1407                found = []
1408                for i in a:
1409                        if (i.baseGlyph, i.offset, i.scale) == pos:
1410                                found.append(i)
1411                if len(found) > 1:
1412                        raise RoboFabError, 'Found more than one possible component to remove'
1413                elif len(found) == 1:
1414                        del self._object.components[found[0].index]
1415                else:
1416                        raise RoboFabError, 'Component does not exist'
1417       
1418        def removeContour(self, index):
1419                """remove a specific contour  from the glyph"""
1420                self._object.DeleteContour(index)
1421                self._invalidateContours()
1422               
1423        def removeAnchor(self, anchor):
1424                """Remove a specific anchor from the glyph. This only works
1425                if the glyph does not have anchors with duplicate names
1426                in exactly the same location with the same mark."""
1427                pos = (anchor.name, anchor.position, anchor.mark)
1428                a = self.getAnchors()
1429                found = []
1430                for i in a:
1431                        if (i.name, i.position, i.mark) == pos:
1432                                found.append(i)
1433                if len(found) > 1:
1434                        raise RoboFabError, 'Found more than one possible anchor to remove'
1435                elif len(found) == 1:
1436                        del self._object.anchors[found[0].index]
1437                else:
1438                        raise RoboFabError, 'Anchor does not exist'
1439               
1440        def removeHGuide(self, guide):
1441                """Remove a horizontal guide."""
1442                pos = (guide.position, guide.angle)
1443                for g in self.getHGuides():
1444                        if  (g.position, g.angle) == pos:
1445                                del self._object.hguides[g.index]
1446                                break
1447                               
1448        def removeVGuide(self, guide):
1449                """Remove a vertical guide."""
1450                pos = (guide.position, guide.angle)
1451                for g in self.getVGuides():
1452                        if  (g.position, g.angle) == pos:
1453                                del self._object.vguides[g.index]
1454                                break
1455
1456        def center(self, padding=None):
1457                """Equalise sidebearings, set to padding if wanted."""
1458                left = self.leftMargin
1459                right = self.rightMargin
1460                if padding:
1461                        e_left = e_right = padding
1462                else:
1463                        e_left = (left + right)/2
1464                        e_right = (left + right) - e_left
1465                self.leftMargin= e_left
1466                self.rightMargin= e_right
1467               
1468        def removeOverlap(self):
1469                """Remove overlap"""
1470                self._object.RemoveOverlap()
1471                self._invalidateContours()
1472       
1473        def decompose(self):
1474                """Decompose all components"""
1475                self._object.Decompose()
1476                self._invalidateContours()
1477       
1478        ##broken!
1479        #def removeHints(self):
1480        #       """Remove the hints."""
1481        #       self._object.RemoveHints()
1482               
1483        def autoHint(self):
1484                """Automatically generate type 1 hints."""
1485                self._object.Autohint()
1486               
1487        def move(self, (x, y), contours=True, components=True, anchors=True):
1488                """Move a glyph's items that are flagged as True"""
1489                x, y = roundPt((x, y))
1490                self._object.Shift(Point(x, y))
1491                for c in self.getComponents():
1492                        c.move((x, y))
1493                for a in self.getAnchors():
1494                        a.move((x, y))
1495       
1496        def clear(self, contours=True, components=True, anchors=True, guides=True, hints=True):
1497                """Clear all items marked as true from the glyph"""
1498                if contours:
1499                        self._object.Clear()
1500                        self._invalidateContours()
1501                if components:
1502                        self._object.components.clean()
1503                if anchors:
1504                        self._object.anchors.clean()
1505                if guides:
1506                        self._object.hguides.clean()
1507                        self._object.vguides.clean()
1508                if hints:
1509                        # RemoveHints requires an "integer mode" argument
1510                        # but it is not documented. from some simple experiments
1511                        # i deduced that
1512                        # 1 = horizontal hints and links,
1513                        # 2 = vertical hints and links
1514                        # 3 = all hints and links
1515                        self._object.RemoveHints(3)
1516       
1517        #
1518        #       special treatment for GlyphMath support in FontLab
1519        #
1520       
1521        def _getMathDestination(self):
1522                from robofab.objects.objectsRF import RGlyph as _RGlyph
1523                return _RGlyph()
1524       
1525        def copy(self, aParent=None):
1526                """Make a copy of this glyph.
1527                Note: the copy is not a duplicate fontlab glyph, but
1528                a RF RGlyph with the same outlines. The new glyph is
1529                not part of the fontlab font in any way. Use font.appendGlyph(glyph)
1530                to get it in a FontLab glyph again."""
1531                from robofab.objects.objectsRF import RGlyph as _RGlyph
1532                newGlyph = _RGlyph()
1533                newGlyph.appendGlyph(self)
1534                for attr in GLYPH_COPY_ATTRS:
1535                        value = getattr(self, attr)
1536                        setattr(newGlyph, attr, value)
1537                # hints
1538                doHints = False
1539                parent = self.getParent()
1540                if parent is not None and parent._supportHints:
1541                        hintStuff = _glyphHintsToDict(self.naked())
1542                        if hintStuff:
1543                                newGlyph.lib[postScriptHintDataLibKey] = hintStuff
1544                if aParent is not None:
1545                        newGlyph.setParent(aParent)
1546                elif self.getParent() is not None:
1547                        newGlyph.setParent(self.getParent())
1548                return newGlyph
1549       
1550        def __mul__(self, factor):
1551                return self.copy() *factor
1552       
1553        __rmul__ = __mul__
1554
1555        def __sub__(self, other):
1556                return self.copy() - other.copy()
1557       
1558        def __add__(self, other):
1559                return self.copy() + other.copy()
1560       
1561
1562
1563class RContour(BaseContour):
1564       
1565        """RoboFab wrapper for non FL contour object"""
1566               
1567        _title = "FLContour"
1568       
1569        def __init__(self, index):
1570                self._index = index
1571                self._parentGlyph = None
1572                self.segments = []
1573       
1574        def __len__(self):
1575                return len(self.points)
1576               
1577        def _buildSegments(self):               
1578                #######################
1579                # Notes about FL node contour structure
1580                #######################
1581                # for TT curves, FL lists them as seperate nodes:
1582                #       [move, off, off, off, line, off, off]
1583                # and, this list is sequential. after the last on curve,
1584                # it is possible (and likely) that there will be more offCurves
1585                # in our segment object, these should be associated with the
1586                # first segment in the contour.
1587                #
1588                # for PS curves, it is a very different scenerio.
1589                # curve nodes contain points:
1590                #       [on, off, off]
1591                # and the list is not in sequential order. the first point in
1592                # the list is the on curve and the subsequent points are the off
1593                # curve points leading up to that on curve.
1594                #
1595                # it is very important to remember these structures when trying
1596                # to understand the code below
1597               
1598                self.segments = []
1599                offList = []
1600                nodes = self._nakedParent.nodes
1601                for index in range(self._nodeLength):
1602                        x = index + self._startNodeIndex
1603                        node = nodes[x]
1604                        # we do have a loose off curve. deal with it.
1605                        if node.type == flOFFCURVE:
1606                                offList.append(x)
1607                        # we are not dealing with a loose off curve
1608                        else:
1609                                s = RSegment(x)
1610                                s.setParent(self)
1611                                # but do we have a collection of loose off curves above?
1612                                # if so, apply them to the segment, and clear the list
1613                                if len(offList) != 0:
1614                                        s._looseOffCurve = offList
1615                                offList = []
1616                                self.segments.append(s)
1617                # do we have some off curves now that the contour is complete?
1618                if len(offList) != 0:
1619                        # ugh. apply them to the first segment
1620                        self.segments[0]._looseOffCurve = offList
1621       
1622        def setParent(self, parentGlyph):
1623                self._parentGlyph = parentGlyph
1624               
1625        def getParent(self):
1626                return self._parentGlyph
1627               
1628        def _get__nakedParent(self):
1629                return self._parentGlyph.naked()
1630       
1631        _nakedParent = property(_get__nakedParent, doc="")
1632       
1633        def _get__startNodeIndex(self):
1634                return self._nakedParent.GetContourBegin(self._index)
1635       
1636        _startNodeIndex = property(_get__startNodeIndex, doc="")
1637       
1638        def _get__nodeLength(self):
1639                return self._nakedParent.GetContourLength(self._index)
1640               
1641        _nodeLength = property(_get__nodeLength, doc="")
1642
1643        def _get__lastNodeIndex(self):
1644                return self._startNodeIndex + self._nodeLength - 1
1645               
1646        _lastNodeIndex = property(_get__lastNodeIndex, doc="")
1647
1648        def _previousNodeIndex(self, index):
1649                return (index - 1) % self._nodeLength
1650
1651        def _nextNodeIndex(self, index):
1652                return (index + 1) % self._nodeLength
1653       
1654        def _getNode(self, index):
1655                return self._nodes[index]
1656       
1657        def _get__nodes(self):
1658                nodes = []
1659                for node in self._nakedParent.nodes[self._startNodeIndex:self._startNodeIndex+self._nodeLength-1]:
1660                        nodes.append(node)
1661                return nodes
1662       
1663        _nodes = property(_get__nodes, doc="")
1664
1665        def _get_points(self):
1666                points = []
1667                for segment in self.segments:
1668                        for point in segment.points:
1669                                points.append(point)
1670                return points
1671               
1672        points = property(_get_points, doc="")
1673
1674        def _get_bPoints(self):
1675                bPoints = []
1676                for segment in self.segments:
1677                        bp = RBPoint(segment.index)
1678                        bp.setParent(self)
1679                        bPoints.append(bp)
1680                return bPoints
1681               
1682        bPoints = property(_get_bPoints, doc="")
1683       
1684        def _get_index(self):
1685                return self._index
1686
1687        def _set_index(self, index):
1688                if index != self._index:
1689                        self._nakedParent.ReorderContour(self._index, index)
1690                        # reorder and set the _index of the existing RContour objects
1691                        # this will be a better solution than reconstructing all the objects
1692                        # segment objects will still, sadly, have to be reconstructed
1693                        contourList = self.getParent().contours
1694                        contourList.insert(index, contourList.pop(self._index))
1695                        for i in range(len(contourList)):
1696                                contourList[i]._index = i
1697                                contourList[i]._buildSegments()
1698               
1699       
1700        index = property(_get_index, _set_index, doc="the index of the contour")
1701
1702        def _get_selected(self):
1703                selected = 0
1704                nodes = self._nodes
1705                for node in nodes:
1706                        if node.selected == 1:
1707                                selected = 1
1708                                break
1709                return selected
1710               
1711        def _set_selected(self, value):
1712                if value == 1:
1713                        self._nakedParent.SelectContour(self._index)
1714                else:
1715                        for node in self._nodes:
1716                                node.selected = value
1717
1718        selected = property(_get_selected, _set_selected, doc="selection of the contour: 1-selected or 0-unselected")
1719
1720        def appendSegment(self, segmentType, points, smooth=False):
1721                segment = self.insertSegment(index=self._nodeLength, segmentType=segmentType, points=points, smooth=smooth)
1722                return segment
1723               
1724        def insertSegment(self, index, segmentType, points, smooth=False):
1725                """insert a seggment into the contour"""
1726                # do a  qcurve insertion
1727                if segmentType == QCURVE:
1728                        count = 0
1729                        for point in points[:-1]:
1730                                newNode = Node(flOFFCURVE, Point(point[0], point[1]))
1731                                self._nakedParent.Insert(newNode, self._startNodeIndex + index + count)
1732                                count = count + 1
1733                        newNode = Node(flLINE, Point(points[-1][0], points[-1][1]))
1734                        self._nakedParent.Insert(newNode, self._startNodeIndex + index +len(points) - 1)
1735                # do a regular insertion
1736                else:   
1737                        onX, onY = points[-1]
1738                        newNode = Node(_rfToFLSegmentType(segmentType), Point(onX, onY))
1739                        # fix the off curves in case the user is inserting a curve
1740                        # but is not specifying off curve points
1741                        if segmentType == CURVE and len(points) == 1:
1742                                pSeg = self._prevSegment(index)
1743                                pOn = pSeg.onCurve
1744                                newNode.points[1].Assign(Point(pOn.x, pOn.y))
1745                                newNode.points[2].Assign(Point(onX, onY))
1746                        for pointIndex in range(len(points[:-1])):
1747                                x, y = points[pointIndex]
1748                                newNode.points[1 + pointIndex].Assign(Point(x, y))
1749                        if smooth:
1750                                node.alignment = flSMOOTH
1751                        self._nakedParent.Insert(newNode, self._startNodeIndex + index)
1752                self._buildSegments()
1753                return self.segments[index]
1754               
1755        def removeSegment(self, index):
1756                """remove a segment from the contour"""
1757                segment = self.segments[index]
1758                # we have a qcurve. umph.
1759                if segment.type == QCURVE:
1760                        indexList = [segment._nodeIndex] + segment._looseOffCurve
1761                        indexList.sort()
1762                        indexList.reverse()
1763                        parent = self._nakedParent
1764                        for nodeIndex in indexList:
1765                                parent.DeleteNode(nodeIndex)
1766                # we have a more sane structure to follow
1767                else:
1768                        # store some info for later
1769                        next = self._nextSegment(index)
1770                        nextOffA = None
1771                        nextOffB = None
1772                        nextType = next.type
1773                        if nextType != LINE and nextType != MOVE:
1774                                pA = next.offCurve[0]
1775                                nextOffA = (pA.x, pA.y)
1776                                pB = next.offCurve[-1]
1777                                nextOffB = (pB.x, pB.y)
1778                        nodeIndex = segment._nodeIndex
1779                        self._nakedParent.DeleteNode(nodeIndex)
1780                        self._buildSegments()
1781                        # now we must override FL guessing about offCurves
1782                        next = self._nextSegment(index - 1)
1783                        nextType = next.type
1784                        if nextType != LINE and nextType != MOVE:
1785                                pA = next.offCurve[0]
1786                                pB = next.offCurve[-1]
1787                                pA.x, pA.y = nextOffA
1788                                pB.x, pB.y = nextOffB
1789               
1790        def reverseContour(self):
1791                """reverse contour direction"""
1792                self._nakedParent.ReverseContour(self._index)
1793                self._buildSegments()
1794                       
1795        def setStartSegment(self, segmentIndex):
1796                """set the first node on the contour"""
1797                self._nakedParent.SetStartNode(self._startNodeIndex + segmentIndex)
1798                self.getParent()._invalidateContours()
1799                self.getParent()._buildContours()
1800
1801        def copy(self, aParent=None):
1802                """Copy this object -- result is an ObjectsRF flavored object.
1803                There is no way to make this work using FontLab objects.
1804                Copy is mainly used for glyphmath.
1805                """
1806                raise RoboFabError, "copy() for objectsFL.RContour is not implemented."
1807               
1808
1809
1810class RSegment(BaseSegment):
1811       
1812        _title = "FLSegment"
1813       
1814        def __init__(self, flNodeIndex):
1815                BaseSegment.__init__(self)
1816                self._nodeIndex = flNodeIndex
1817                self._looseOffCurve = []        #a list of indexes to loose off curve nodes
1818       
1819        def _get__node(self):
1820                glyph = self.getParent()._nakedParent
1821                return glyph.nodes[self._nodeIndex]
1822       
1823        _node = property(_get__node, doc="")
1824       
1825        def _get_qOffCurve(self):
1826                nodes = self.getParent()._nakedParent.nodes
1827                off = []
1828                for x in self._looseOffCurve:
1829                        off.append(nodes[x])
1830                return off
1831               
1832        _qOffCurve = property(_get_qOffCurve, doc="free floating off curve nodes in the segment")
1833
1834        def _get_index(self):
1835                contour = self.getParent()
1836                return self._nodeIndex - contour._startNodeIndex
1837               
1838        index = property(_get_index, doc="")
1839       
1840        def _isQCurve(self):
1841                # loose off curves only appear in q curves
1842                if len(self._looseOffCurve) != 0:
1843                        return True
1844                return False
1845
1846        def _get_type(self):
1847                if self._isQCurve():
1848                        return QCURVE
1849                return _flToRFSegmentType(self._node.type)
1850       
1851        def _set_type(self, segmentType):
1852                if self._isQCurve():
1853                        raise RoboFabError, 'qcurve point types cannot be changed'
1854                oldNode = self._node
1855                oldType = oldNode.type
1856                oldPointType = _flToRFSegmentType(oldType)
1857                if oldPointType == MOVE:
1858                        raise RoboFabError, '%s point types cannot be changed'%oldPointType
1859                if segmentType == MOVE or segmentType == OFFCURVE:
1860                        raise RoboFabError, '%s point types cannot be assigned'%oldPointType
1861                if oldPointType == segmentType:
1862                        return
1863                oldNode.type = _rfToFLSegmentType(segmentType)
1864               
1865        type = property(_get_type, _set_type, doc="")
1866                       
1867        def _get_smooth(self):
1868                alignment = self._node.alignment
1869                if alignment == flSMOOTH or alignment == flFIXED:
1870                        return True
1871                return False
1872       
1873        def _set_smooth(self, value):
1874                if value:
1875                        self._node.alignment = flSMOOTH
1876                else:
1877                        self._node.alignment = flSHARP
1878               
1879        smooth = property(_get_smooth, _set_smooth, doc="")
1880
1881        def _get_points(self):
1882                points = []
1883                node = self._node
1884                # gather the off curves
1885                #
1886                # are we dealing with a qCurve? ugh.
1887                # gather the loose off curves
1888                if self._isQCurve():
1889                        off = self._qOffCurve
1890                        x = 0
1891                        for n in off:
1892                                p = RPoint(0)
1893                                p.setParent(self)
1894                                p._qOffIndex = x
1895                                points.append(p)
1896                                x = x + 1
1897                # otherwise get the points associated with the node
1898                else:
1899                        index = 1
1900                        for point in node.points[1:]:
1901                                p = RPoint(index)
1902                                p.setParent(self)
1903                                points.append(p)
1904                                index = index + 1
1905                # the last point should always be the on curve
1906                p = RPoint(0)
1907                p.setParent(self)
1908                points.append(p)
1909                return points
1910               
1911        points = property(_get_points, doc="")
1912
1913        def _get_selected(self):
1914                return self._node.selected
1915       
1916        def _set_selected(self, value):
1917                self._node.selected = value
1918       
1919        selected = property(_get_selected, _set_selected, doc="")
1920
1921        def move(self, (x, y)):
1922                x, y = roundPt((x, y))
1923                self._node.Shift(Point(x, y))
1924                if self._isQCurve():
1925                        qOff = self._qOffCurve
1926                        for node in qOff:
1927                                node.Shift(Point(x, y))
1928
1929        def copy(self, aParent=None):
1930                """Copy this object -- result is an ObjectsRF flavored object.
1931                There is no way to make this work using FontLab objects.
1932                Copy is mainly used for glyphmath.
1933                """
1934                raise RoboFabError, "copy() for objectsFL.RSegment is not implemented."
1935
1936               
1937
1938class RPoint(BasePoint):
1939                               
1940        _title = "FLPoint"
1941       
1942        def __init__(self, pointIndex):
1943                #BasePoint.__init__(self)
1944                self._pointIndex = pointIndex
1945                self._qOffIndex = None
1946               
1947        def _get__parentGlyph(self):
1948                return self._parentContour.getParent()
1949       
1950        _parentGlyph = property(_get__parentGlyph, doc="")
1951
1952        def _get__parentContour(self):
1953                return self._parentSegment.getParent()
1954               
1955        _parentContour = property(_get__parentContour, doc="")
1956
1957        def _get__parentSegment(self):
1958                return self.getParent()
1959       
1960        _parentSegment = property(_get__parentSegment, doc="")
1961
1962        def _get__node(self):
1963                if self._qOffIndex is not None:
1964                        return self.getParent()._qOffCurve[self._qOffIndex]
1965                return self.getParent()._node
1966       
1967        _node = property(_get__node, doc="")
1968
1969        def _get__point(self):
1970                return self._node.points[self._pointIndex]
1971
1972        _point = property(_get__point, doc="")
1973
1974        def _get_x(self):
1975                return self._point.x
1976               
1977        def _set_x(self, value):
1978                value = int(round(value))
1979                self._point.x = value
1980       
1981        x = property(_get_x, _set_x, doc="")
1982
1983        def _get_y(self):
1984                return self._point.y
1985       
1986        def _set_y(self, value):
1987                value = int(round(value))
1988                self._point.y = value
1989
1990        y = property(_get_y, _set_y, doc="")
1991
1992        def _get_type(self):
1993                if self._pointIndex == 0:
1994                        # FL store quad contour data as a list of off curves and lines
1995                        # (see note in RContour._buildSegments). So, we need to do
1996                        # a bit of trickery to return a decent point type.
1997                        # if the straight FL node type is off curve, it is a loose
1998                        # quad off curve. return that.
1999                        tp = _flToRFSegmentType(self._node.type)
2000                        if tp == OFFCURVE:
2001                                return OFFCURVE
2002                        # otherwise we are dealing with an on curve. in this case,
2003                        # we attempt to get the parent segment type and return it.
2004                        segment = self.getParent()
2005                        if segment is not None:
2006                                return segment.type
2007                        # we must not have a segment, fall back to straight conversion
2008                        return tp
2009                return OFFCURVE
2010       
2011        type = property(_get_type, doc="")
2012       
2013        def _set_selected(self, value):
2014                if self._pointIndex == 0:
2015                        self._node.selected = value
2016       
2017        def _get_selected(self):
2018                if self._pointIndex == 0:
2019                        return self._node.selected
2020                return False
2021               
2022        selected = property(_get_selected, _set_selected, doc="")
2023
2024        def move(self, (x, y)):
2025                x, y = roundPt((x, y))
2026                self._point.Shift(Point(x, y))
2027       
2028        def scale(self, (x, y), center=(0, 0)):
2029                centerX, centerY = roundPt(center)
2030                point = self._point
2031                point.x, point.y = _scalePointFromCenter((point.x, point.y), (x, y), (centerX, centerY))
2032       
2033        def copy(self, aParent=None):
2034                """Copy this object -- result is an ObjectsRF flavored object.
2035                There is no way to make this work using FontLab objects.
2036                Copy is mainly used for glyphmath.
2037                """
2038                raise RoboFabError, "copy() for objectsFL.RPoint is not implemented."
2039               
2040
2041class RBPoint(BaseBPoint):
2042       
2043        _title = "FLBPoint"
2044       
2045        def __init__(self, segmentIndex):
2046                #BaseBPoint.__init__(self)
2047                self._segmentIndex = segmentIndex
2048               
2049        def _get__parentSegment(self):
2050                return self.getParent().segments[self._segmentIndex]
2051       
2052        _parentSegment = property(_get__parentSegment, doc="")
2053       
2054        def _get_index(self):
2055                return self._segmentIndex
2056       
2057        index = property(_get_index, doc="")
2058       
2059        def _get_selected(self):
2060                return self._parentSegment.selected
2061       
2062        def _set_selected(self, value):
2063                self._parentSegment.selected = value
2064               
2065        selected = property(_get_selected, _set_selected, doc="")
2066
2067        def copy(self, aParent=None):
2068                """Copy this object -- result is an ObjectsRF flavored object.
2069                There is no way to make this work using FontLab objects.
2070                Copy is mainly used for glyphmath.
2071                """
2072                raise RoboFabError, "copy() for objectsFL.RBPoint is not implemented."
2073                       
2074               
2075class RComponent(BaseComponent):
2076       
2077        """RoboFab wrapper for FL Component object"""
2078
2079        _title = "FLComponent"
2080
2081        def __init__(self, flComponent, index):
2082                BaseComponent.__init__(self)
2083                self._object =  flComponent
2084                self._index=index
2085               
2086        def _get_index(self):
2087                return self._index
2088               
2089        index = property(_get_index, doc="index of component")
2090
2091        def _get_baseGlyph(self):
2092                return self._object.parent.parent[self._object.index].name
2093               
2094        baseGlyph = property(_get_baseGlyph, doc="")
2095
2096        def _get_offset(self):
2097                return (int(self._object.delta.x), int(self._object.delta.y))
2098       
2099        def _set_offset(self, value):
2100                value = roundPt((value[0], value[1]))
2101                self._object.delta=Point(value[0], value[1])
2102               
2103        offset = property(_get_offset, _set_offset, doc="the offset of the component")
2104
2105        def _get_scale(self):
2106                return (self._object.scale.x, self._object.scale.y)
2107       
2108        def _set_scale(self, (x, y)):
2109                self._object.scale=Point(x, y)
2110               
2111        scale = property(_get_scale, _set_scale, doc="the scale of the component")
2112
2113        def move(self, (x, y)):
2114                """Move the component"""
2115                x, y = roundPt((x, y))
2116                self._object.delta=Point(self._object.delta.x+x, self._object.delta.y+y)
2117       
2118        def decompose(self):
2119                """Decompose the component"""
2120                self._object.Paste()
2121
2122        def copy(self, aParent=None):
2123                """Copy this object -- result is an ObjectsRF flavored object.
2124                There is no way to make this work using FontLab objects.
2125                Copy is mainly used for glyphmath.
2126                """
2127                raise RoboFabError, "copy() for objectsFL.RComponent is not implemented."
2128               
2129
2130
2131class RAnchor(BaseAnchor):
2132        """RoboFab wrapper for FL Anchor object"""
2133
2134        _title = "FLAnchor"
2135
2136        def __init__(self, flAnchor, index):
2137                BaseAnchor.__init__(self)
2138                self._object =  flAnchor
2139                self._index = index
2140       
2141        def _get_y(self):
2142                return self._object.y
2143
2144        def _set_y(self, value):
2145                self._object.y = int(round(value))
2146
2147        y = property(_get_y, _set_y, doc="y")
2148       
2149        def _get_x(self):
2150                return self._object.x
2151
2152        def _set_x(self, value):
2153                self._object.x = int(round(value))
2154
2155        x = property(_get_x, _set_x, doc="x")
2156       
2157        def _get_name(self):
2158                return self._object.name
2159
2160        def _set_name(self, value):
2161                self._object.name = value
2162
2163        name = property(_get_name, _set_name, doc="name")
2164       
2165        def _get_mark(self):
2166                return self._object.mark
2167
2168        def _set_mark(self, value):
2169                self._object.mark = value
2170
2171        mark = property(_get_mark, _set_mark, doc="mark")
2172       
2173        def _get_index(self):
2174                return self._index
2175               
2176        index = property(_get_index, doc="index of the anchor")
2177
2178        def _get_position(self):
2179                return (self._object.x, self._object.y)
2180               
2181        def _set_position(self, value):
2182                value = roundPt((value[0], value[1]))
2183                self._object.x=value[0]
2184                self._object.y=value[1]
2185
2186        position = property(_get_position, _set_position, doc="position of the anchor")
2187
2188
2189
2190class RGuide(BaseGuide):
2191       
2192        """RoboFab wrapper for FL Guide object"""
2193
2194        _title = "FLGuide"
2195
2196        def __init__(self, flGuide, index):
2197                BaseGuide.__init__(self)
2198                self._object = flGuide
2199                self._index = index
2200               
2201        def __repr__(self):
2202                # this is a doozy!
2203                parent = "unknown_parent"
2204                parentObject = self.getParent()
2205                if parentObject is not None:
2206                        # do we have a font?
2207                        try:
2208                                parent = parentObject.info.fullName
2209                        except AttributeError:
2210                                # or do we have a glyph?
2211                                try:
2212                                        parent = parentObject.name
2213                                # we must be an orphan
2214                                except AttributeError: pass
2215                return "<Robofab guide wrapper for %s>"%parent
2216               
2217        def _get_position(self):
2218                return self._object.position
2219
2220        def _set_position(self, value):
2221                self._object.position = value
2222
2223        position = property(_get_position, _set_position, doc="position")
2224       
2225        def _get_angle(self):
2226                return self._object.angle
2227
2228        def _set_angle(self, value):
2229                self._object.angle = value
2230
2231        angle = property(_get_angle, _set_angle, doc="angle")
2232       
2233        def _get_index(self):
2234                return self._index
2235
2236        index = property(_get_index, doc="index of the guide")
2237       
2238
2239class RGroups(BaseGroups):
2240       
2241        """RoboFab wrapper for FL group data"""
2242       
2243        _title = "FLGroups"
2244       
2245        def __init__(self, aDict):
2246                self.update(aDict)
2247       
2248        def __setitem__(self, key, value):
2249                # override baseclass so that data is stored in FL classes
2250                if not isinstance(key, str):
2251                        raise RoboFabError, 'key must be a string'
2252                if not isinstance(value, list):
2253                        raise RoboFabError, 'group must be a list'
2254                super(RGroups, self).__setitem__(key, value)
2255                self._setFLGroups()
2256                       
2257        def __delitem__(self, key):
2258                # override baseclass so that data is stored in FL classes
2259                super(RGroups, self).__delitem__(key, value)
2260                self._setFLGroups()
2261               
2262        def _setFLGroups(self):
2263                # set the group data into the font.
2264                if self.getParent() is not None:
2265                        groups = []
2266                        for i in self.keys():
2267                                value = ' '.join(self[i])
2268                                groups.append(': '.join([i, value]))
2269                        groups.sort()
2270                        self.getParent().naked().classes = groups
2271       
2272        def update(self, aDict):
2273                # override baseclass so that data is stored in FL classes
2274                super(RGroups, self).update(aDict)
2275                self._setFLGroups()
2276
2277        def clear(self):
2278                # override baseclass so that data is stored in FL classes
2279                super(RGroups, self).clear()
2280                self._setFLGroups()
2281                       
2282        def pop(self, key):
2283                # override baseclass so that data is stored in FL classes
2284                i = super(RGroups, self).pop(key)
2285                self._setFLGroups()
2286                return i
2287               
2288        def popitem(self):
2289                # override baseclass so that data is stored in FL classes
2290                i = super(RGroups, self).popitem()
2291                self._setFLGroups()
2292                return i
2293       
2294        def setdefault(self, key, value=None):
2295                # override baseclass so that data is stored in FL classes
2296                i = super(RGroups, self).setdefault(key, value)
2297                self._setFLGroups()
2298                return i
2299
2300
2301class RKerning(BaseKerning):
2302       
2303        """RoboFab wrapper for FL Kerning data"""
2304       
2305        _title = "FLKerning"
2306
2307        def __setitem__(self, pair, value):
2308                if not isinstance(pair, tuple):
2309                        raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
2310                else:
2311                        if len(pair) != 2:
2312                                raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
2313                        else:
2314                                if value == 0:
2315                                        if self._kerning.get(pair) is not None:
2316                                                #see note about setting kerning values to 0 below
2317                                                self._setFLKerning(pair, 0)
2318                                                del self._kerning[pair]
2319                                else:
2320                                        #self._kerning[pair] = value
2321                                        self._setFLKerning(pair, value)
2322                                       
2323        def _setFLKerning(self, pair, value):
2324                # write a pair back into the font
2325                #
2326                # this is fairly speedy, but setting a pair to 0 is roughly
2327                # 2-3 times slower than setting a real value. this is because
2328                # of all the hoops that must be jumped through to keep FL
2329                # from storing kerning pairs with a value of 0.
2330                parentFont = self.getParent().naked()
2331                left = parentFont[pair[0]]
2332                right = parentFont.FindGlyph(pair[1])
2333                # the left glyph doesn not exist
2334                if left is None:
2335                        return
2336                # the right glyph doesn not exist
2337                if right == -1:
2338                        return
2339                self._kerning[pair] = value
2340                leftName = pair[0]
2341                value = int(round(value))
2342                # pairs set to 0 need to be handled carefully. FL will allow
2343                # for pairs to have a value of 0 (!?), so we must catch them
2344                # when they pop up and make sure that the pair is actually
2345                # removed from the font.
2346                if value == 0:
2347                        foundPair = False
2348                        # if the value is 0, we don't need to construct a pair
2349                        # we just need to make sure that the pair is not in the list
2350                        pairs = []
2351                        # so, go through all the pairs and add them to a new list
2352                        for flPair in left.kerning:
2353                                # we have found the pair. flag it.
2354                                if flPair.key == right:
2355                                        foundPair = True
2356                                # not the pair. add it to the list.
2357                                else:
2358                                        pairs.append((flPair.key, flPair.value))
2359                        # if we found it, write it back to the glyph.
2360                        if foundPair:
2361                                left.kerning = []
2362                                for p in pairs:
2363                                        new = KerningPair(p[0], p[1])
2364                                        left.kerning.append(new)
2365                else:
2366                        # non-zero pairs are a bit easier to handle
2367                        # we just need to look to see if the pair exists
2368                        # if so, change the value and stop the loop.
2369                        # if not, add a new pair to the glyph
2370                        self._kerning[pair] = value
2371                        foundPair = False
2372                        for flPair in left.kerning:
2373                                if flPair.key == right:
2374                                        flPair.value = value
2375                                        foundPair = True
2376                                        break
2377                        if not foundPair:
2378                                p = KerningPair(right, value)
2379                                left.kerning.append(p)
2380               
2381        def update(self, kerningDict):
2382                """replace kerning data with the data in the given kerningDict"""
2383                # override base class here for speed
2384                parentFont = self.getParent().naked()
2385                # add existing data to the new kerning dict is not being replaced
2386                for pair in self.keys():
2387                        if not kerningDict.has_key(pair):
2388                                kerningDict[pair] = self._kerning[pair]
2389                # now clear the existing kerning to make sure that
2390                # all the kerning in residing in the glyphs is gone
2391                self.clear()
2392                self._kerning = kerningDict
2393                kDict = {}
2394                # nest the pairs into a dict keyed by the left glyph
2395                # {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...}
2396                for left, right in kerningDict.keys():
2397                        value = kerningDict[left, right]
2398                        if not left in kDict:
2399                                kDict[left] = {}
2400                        kDict[left][right] = value
2401                for left in kDict.keys():
2402                        leftGlyph = parentFont[left]
2403                        if leftGlyph is not None:
2404                                for right in kDict[left].keys():
2405                                        value = kDict[left][right]
2406                                        if value != 0:
2407                                                rightIndex = parentFont.FindGlyph(right)
2408                                                if rightIndex != -1:
2409                                                        p = KerningPair(rightIndex, value)
2410                                                        leftGlyph.kerning.append(p)
2411                               
2412        def clear(self):
2413                """clear all kerning"""
2414                # override base class here for speed
2415                self._kerning = {}
2416                for glyph in self.getParent().naked().glyphs:
2417                        glyph.kerning = []
2418
2419        def __add__(self, other):
2420                """Math operations on FL Kerning objects return RF Kerning objects
2421                as they need to be orphaned objects and FL can't deal with that."""
2422                from sets import Set
2423                from robofab.objects.objectsRF import RKerning as _RKerning
2424                new = _RKerning()
2425                k = Set(self.keys()) | Set(other.keys())
2426                for key in k:
2427                        new[key] = self.get(key, 0) + other.get(key, 0)
2428                return new
2429       
2430        def __sub__(self, other):
2431                """Math operations on FL Kerning objects return RF Kerning objects
2432                as they need to be orphaned objects and FL can't deal with that."""
2433                from sets import Set
2434                from robofab.objects.objectsRF import RKerning as _RKerning
2435                new = _RKerning()
2436                k = Set(self.keys()) | Set(other.keys())
2437                for key in k:
2438                        new[key] = self.get(key, 0) - other.get(key, 0)
2439                return new
2440
2441        def __mul__(self, factor):
2442                """Math operations on FL Kerning objects return RF Kerning objects
2443                as they need to be orphaned objects and FL can't deal with that."""
2444                from robofab.objects.objectsRF import RKerning as _RKerning
2445                new = _RKerning()
2446                for name, value in self.items():
2447                        new[name] = value * factor
2448                return new
2449       
2450        __rmul__ = __mul__
2451
2452        def __div__(self, factor):
2453                """Math operations on FL Kerning objects return RF Kerning objects
2454                as they need to be orphaned objects and FL can't deal with that."""
2455                if factor == 0:
2456                        raise ZeroDivisionError
2457                return self.__mul__(1.0/factor)
2458                       
2459
2460class RLib(BaseLib):
2461       
2462        """RoboFab wrapper for FL lib"""
2463       
2464        # XXX: As of FL 4.6 the customdata field in glyph objects is busted.
2465        # storing anything there causes the glyph to become uneditable.
2466        # however, the customdata field in font objects is stable.
2467       
2468        def __init__(self, aDict):
2469                self.update(aDict)
2470               
2471        def __setitem__(self, key, value):
2472                # override baseclass so that data is stored in customdata field
2473                super(RLib, self).__setitem__(key, value)
2474                self._stashLib()
2475                       
2476        def __delitem__(self, key):
2477                # override baseclass so that data is stored in customdata field
2478                super(RLib, self).__delitem__(key)
2479                self._stashLib()
2480               
2481        def _stashLib(self):
2482                # write the plist into the customdata field of the FL object
2483                if self.getParent() is None:
2484                        return
2485                if not self:
2486                        data = None
2487                elif len(self) == 1 and "org.robofab.fontlab.customdata" in self:
2488                        data = self["org.robofab.fontlab.customdata"].data
2489                else:
2490                        f = StringIO()
2491                        writePlist(self, f)
2492                        data = f.getvalue()
2493                        f.close()
2494                parent = self.getParent()
2495                parent.naked().customdata = data
2496       
2497        def update(self, aDict):
2498                # override baseclass so that data is stored in customdata field
2499                super(RLib, self).update(aDict)
2500                self._stashLib()
2501
2502        def clear(self):
2503                # override baseclass so that data is stored in customdata field
2504                super(RLib, self).clear()
2505                self._stashLib()
2506                       
2507        def pop(self, key):
2508                # override baseclass so that data is stored in customdata field
2509                i = super(RLib, self).pop(key)
2510                self._stashLib()
2511                return i
2512               
2513        def popitem(self):
2514                # override baseclass so that data is stored in customdata field
2515                i = super(RLib, self).popitem()
2516                self._stashLib()
2517                return i
2518       
2519        def setdefault(self, key, value=None):
2520                # override baseclass so that data is stored in customdata field
2521                i = super(RLib, self).setdefault(key, value)
2522                self._stashLib()
2523                return i
2524
2525                       
2526class RInfo(BaseInfo):
2527       
2528        """RoboFab wrapper for FL Font Info"""
2529       
2530        _title = "FLInfo"
2531       
2532        def __init__(self, font):
2533                BaseInfo.__init__(self)
2534                self._object = font
2535       
2536        def _get_familyName(self):
2537                return self._object.family_name
2538
2539        def _set_familyName(self, value):
2540                self._object.family_name = value
2541
2542        familyName = property(_get_familyName, _set_familyName, doc="family_name")
2543       
2544        def _get_styleName(self):
2545                return self._object.style_name
2546
2547        def _set_styleName(self, value):
2548                self._object.style_name = value
2549
2550        styleName = property(_get_styleName, _set_styleName, doc="style_name")
2551       
2552        def _get_fullName(self):
2553                return self._object.full_name
2554
2555        def _set_fullName(self, value):
2556                self._object.full_name = value
2557
2558        fullName = property(_get_fullName, _set_fullName, doc="full_name")
2559       
2560        def _get_fontName(self):
2561                return self._object.font_name
2562
2563        def _set_fontName(self, value):
2564                self._object.font_name = value
2565
2566        fontName = property(_get_fontName, _set_fontName, doc="font_name")
2567       
2568        def _get_menuName(self):
2569                return self._object.menu_name
2570
2571        def _set_menuName(self, value):
2572                self._object.menu_name = value
2573
2574        menuName = property(_get_menuName, _set_menuName, doc="menu_name")
2575
2576        def _get_fondName(self):
2577                return self._object.apple_name
2578
2579        def _set_fondName(self, value):
2580                self._object.apple_name = value
2581
2582        fondName = property(_get_fondName, _set_fondName, doc="apple_name")
2583       
2584        def _get_otFamilyName(self):
2585                return self._object.pref_family_name
2586
2587        def _set_otFamilyName(self, value):
2588                self._object.pref_family_name = value
2589
2590        otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name")
2591
2592        def _get_otStyleName(self):
2593                return self._object.pref_style_name
2594
2595        def _set_otStyleName(self, value):
2596                self._object.pref_style_name = value
2597
2598        otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name")
2599       
2600        def _get_otMacName(self):
2601                return self._object.mac_compatible
2602
2603        def _set_otMacName(self, value):
2604                self._object.mac_compatible = value
2605
2606        otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible")
2607       
2608        def _get_weightValue(self):
2609                return self._object.weight_code
2610       
2611        def _set_weightValue(self, value):
2612                value = int(round(value))       # FL can't take float - 28/8/07 / evb
2613                self._object.weight_code = value
2614       
2615        weightValue = property(_get_weightValue, _set_weightValue, doc="weight value")
2616       
2617        def _get_weightName(self):
2618                return self._object.weight
2619       
2620        def _set_weightName(self, value):
2621                self._object.weight = value
2622       
2623        weightName = property(_get_weightName, _set_weightName, doc="weight name")
2624       
2625        def _get_widthName(self):
2626                return self._object.width
2627       
2628        def _set_widthName(self, value):
2629                self._object.width = value
2630       
2631        widthName = property(_get_widthName, _set_widthName, doc="width name")
2632
2633        def _get_fontStyle(self):
2634                return self._object.font_style
2635
2636        def _set_fontStyle(self, value):
2637                self._object.font_style = value
2638
2639        fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style")
2640       
2641        def _get_msCharSet(self):
2642                return self._object.ms_charset
2643
2644        def _set_msCharSet(self, value):
2645                self._object.ms_charset = value
2646
2647        msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset")
2648       
2649        def _get_fondID(self):
2650                return self._object.fond_id
2651
2652        def _set_fondID(self, value):
2653                self._object.fond_id = value
2654
2655        fondID = property(_get_fondID, _set_fondID, doc="fond_id")
2656       
2657        def _get_uniqueID(self):
2658                return self._object.unique_id
2659
2660        def _set_uniqueID(self, value):
2661                self._object.unique_id = value
2662
2663        uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id")
2664       
2665        def _get_versionMajor(self):
2666                return self._object.version_major
2667
2668        def _set_versionMajor(self, value):
2669                self._object.version_major = value
2670
2671        versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major")
2672       
2673        def _get_versionMinor(self):
2674                return self._object.version_minor
2675
2676        def _set_versionMinor(self, value):
2677                self._object.version_minor = value
2678
2679        versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor")
2680       
2681        def _get_year(self):
2682                return self._object.year
2683
2684        def _set_year(self, value):
2685                self._object.year = value
2686
2687        year = property(_get_year, _set_year, doc="year")
2688       
2689        def _get_note(self):
2690                s = self._object.note
2691                if s is None:
2692                        return s
2693                return unicode(s, LOCAL_ENCODING)
2694
2695        def _set_note(self, value):
2696                if value is not None:
2697                        value = value.encode(LOCAL_ENCODING)
2698                self._object.note = value
2699
2700        note = property(_get_note, _set_note, doc="note")
2701       
2702        def _get_copyright(self):
2703                s = self._object.copyright
2704                if s is None:
2705                        return s
2706                return unicode(s, LOCAL_ENCODING)
2707
2708        def _set_copyright(self, value):
2709                if value is not None:
2710                        value = value.encode(LOCAL_ENCODING)
2711                self._object.copyright = value
2712
2713        copyright = property(_get_copyright, _set_copyright, doc="copyright")
2714       
2715        def _get_notice(self):
2716                s = self._object.notice
2717                if s is None:
2718                        return s
2719                return unicode(s, LOCAL_ENCODING)
2720
2721        def _set_notice(self, value):
2722                if value is not None:
2723                        value = value.encode(LOCAL_ENCODING)
2724                self._object.notice = value
2725
2726        notice = property(_get_notice, _set_notice, doc="notice")
2727       
2728        def _get_trademark(self):
2729                s = self._object.trademark
2730                if s is None:
2731                        return s
2732                return unicode(s, LOCAL_ENCODING)
2733
2734        def _set_trademark(self, value):
2735                if value is not None:
2736                        value = value.encode(LOCAL_ENCODING)
2737                self._object.trademark = value
2738
2739        trademark = property(_get_trademark, _set_trademark, doc="trademark")
2740       
2741        def _get_license(self):
2742                s = self._object.license
2743                if s is None:
2744                        return s
2745                return unicode(s, LOCAL_ENCODING)
2746
2747        def _set_license(self, value):
2748                if value is not None:
2749                        value = value.encode(LOCAL_ENCODING)
2750                self._object.license = value
2751
2752        license = property(_get_license, _set_license, doc="license")
2753       
2754        def _get_licenseURL(self):
2755                return self._object.license_url
2756
2757        def _set_licenseURL(self, value):
2758                self._object.license_url = value
2759
2760        licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url")
2761       
2762        def _get_createdBy(self):
2763                s = self._object.source
2764                if s is None:
2765                        return s
2766                return unicode(s, LOCAL_ENCODING)
2767
2768        def _set_createdBy(self, value):
2769                if value is not None:
2770                        value = value.encode(LOCAL_ENCODING)
2771                self._object.source = value
2772
2773        createdBy = property(_get_createdBy, _set_createdBy, doc="source")
2774       
2775        def _get_designer(self):
2776                s = self._object.designer
2777                if s is None:
2778                        return s
2779                return unicode(s, LOCAL_ENCODING)
2780
2781        def _set_designer(self, value):
2782                if value is not None:
2783                        value = value.encode(LOCAL_ENCODING)
2784                self._object.designer = value
2785
2786        designer = property(_get_designer, _set_designer, doc="designer")
2787       
2788        def _get_designerURL(self):
2789                return self._object.designer_url
2790
2791        def _set_designerURL(self, value):
2792                self._object.designer_url = value
2793
2794        designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url")
2795       
2796        def _get_vendorURL(self):
2797                return self._object.vendor_url
2798
2799        def _set_vendorURL(self, value):
2800                self._object.vendor_url = value
2801
2802        vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url")
2803       
2804        def _get_ttVendor(self):
2805                return self._object.vendor
2806
2807        def _set_ttVendor(self, value):
2808                self._object.vendor = value
2809
2810        ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor")
2811       
2812        def _get_ttUniqueID(self):
2813                return self._object.tt_u_id
2814
2815        def _set_ttUniqueID(self, value):
2816                self._object.tt_u_id = value
2817
2818        ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id")
2819       
2820        def _get_ttVersion(self):
2821                return self._object.tt_version
2822
2823        def _set_ttVersion(self, value):
2824                self._object.tt_version = value
2825
2826        ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version")
2827       
2828        def _get_unitsPerEm(self):
2829                return self._object.upm
2830               
2831        def _set_unitsPerEm(self, value):
2832                self._object.upm = int(round(value))
2833
2834        unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="")
2835       
2836        def _get_ascender(self):
2837                return self._object.ascender[0]
2838       
2839        def _set_ascender(self, value):
2840                value = int(round(value))
2841                self._object.ascender[0] = value
2842       
2843        ascender = property(_get_ascender, _set_ascender, doc="ascender value")
2844
2845        def _get_descender(self):
2846                return self._object.descender[0]
2847       
2848        def _set_descender(self, value):
2849                value = int(round(value))
2850                self._object.descender[0] = value
2851               
2852        descender = property(_get_descender, _set_descender, doc="descender value")
2853       
2854        def _get_capHeight(self):
2855                return self._object.cap_height[0]
2856       
2857        def _set_capHeight(self, value):
2858                value = int(round(value))
2859                self._object.cap_height[0] = value
2860               
2861        capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value")
2862
2863        def _get_xHeight(self):
2864                return self._object.x_height[0]
2865               
2866        def _set_xHeight(self, value):
2867                value = int(round(value))
2868                self._object.x_height[0] = value
2869               
2870        xHeight = property(_get_xHeight, _set_xHeight, doc="x height value")
2871
2872        def _get_defaultWidth(self):
2873                return self._object.default_width[0]
2874       
2875        def _set_defaultWidth(self, value):
2876                value = int(round(value))
2877                self._object.default_width[0] = value   
2878
2879        defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value")
2880       
2881        def _get_italicAngle(self):
2882                return self._object.italic_angle
2883
2884        def _set_italicAngle(self, value):
2885                try:
2886                        self._object.italic_angle = float(value)
2887                except TypeError:
2888                        print "robofab.objects.objectsFL: can't set italic angle, possibly a FontLab API limitation"
2889
2890        italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle")
2891       
2892        def _get_slantAngle(self):
2893                return self._object.slant_angle
2894
2895        def _set_slantAngle(self, value):
2896                try:
2897                        self._object.slant_angle = float(value)
2898                except TypeError:
2899                        print "robofab.objects.objectsFL: can't set slant angle, possibly a FontLab API limitation"
2900
2901        slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle")
2902       
2903        #is this still needed?
2904        def _get_full_name(self):
2905                return self._object.full_name
2906
2907        def _set_full_name(self, value):
2908                self._object.full_name = value
2909
2910        full_name = property(_get_full_name, _set_full_name, doc="FL: full_name")
2911       
2912        #is this still needed?
2913        def _get_ms_charset(self):
2914                return self._object.ms_charset
2915
2916        def _set_ms_charset(self, value):
2917                self._object.ms_charset = value
2918
2919        ms_charset = property(_get_ms_charset, _set_ms_charset, doc="FL: ms_charset")
Note: See TracBrowser for help on using the repository browser.