root/trunk/robofab/Lib/robofab/objects/objectsRF.py

Revision 40, 38.4 kB (checked in by erik, 3 years ago)

Fix: if a psHintValues object is initialised without a parent, it should not try to load the lib.

  • Property svn:executable set to
Line 
1 """UFO for GlifLib"""
2
3 from robofab import RoboFabError, RoboFabWarning
4 from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseLib,\
5                 BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \
6                 relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\
7                 _interpolate, _interpolatePt, roundPt, addPt,\
8                 MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
9                 BasePostScriptFontHintValues, postScriptHintDataLibKey
10
11 import os
12
13
14 __all__ = [     "CurrentFont",
15                 "CurrentGlyph", 'OpenFont',
16                 'RFont', 'RGlyph', 'RContour',
17                 'RPoint', 'RBPoint', 'RAnchor',
18                 'RComponent'
19                 ]
20
21
22
23 def CurrentFont():
24         return None
25
26 def CurrentGlyph():
27         return None
28
29 def OpenFont(path=None, note=None):
30         """Open a font from a path. If path is not given, present the user with a dialog."""
31         if not note:
32                 note = 'select a .ufo directory'
33         if not path:
34                 from robofab.interface.all.dialogs import GetFolder
35                 path = GetFolder(note)
36         if path:
37                 try:
38                         return RFont(path)
39                 except OSError:
40                         from robofab.interface.all.dialogs import Message
41                         Message("%s is not a valid .UFO font. But considering it's all XML, why don't  you have a look inside with a simple text editor."%(path))
42         else:
43                 return None
44                
45 def NewFont(familyName=None, styleName=None):
46         """Make a new font"""
47         new = RFont()
48         new.info.familyName = familyName
49         new.info.styleName = styleName
50         return new
51        
52 def AllFonts():
53         """AllFonts can't work in plain python usage. It's really up to some sort of application
54         to keep track of which fonts are open."""
55         raise NotImplementedError
56        
57
58 class PostScriptFontHintValues(BasePostScriptFontHintValues):
59         """     PostScript hints object for objectsRF usage.
60                 If there are values in the lib, use those.
61                 If there are no values in the lib, use defaults.
62         """
63        
64         def __init__(self, aFont=None):
65                 # read the data from the font.lib, it won't be anywhere else
66                 BasePostScriptFontHintValues.__init__(self)
67                 data = None
68                 if aFont is not None:
69                         self.setParent(aFont)
70                         data = aFont.lib.get(postScriptHintDataLibKey)
71                 if data is not None:
72                         self.fromDict(data)
73
74 def getPostScriptHintDataFromLib(aFont, fontLib):
75         hintData = fontLib.get(postScriptHintDataLibKey)
76         psh = PostScriptFontHintValues(aFont)
77         psh.fromDict(hintData)
78         return psh
79
80 class RFont(BaseFont):
81         """UFO font object which reads and writes glif, and keeps the data in memory in between.
82         Bahviour:
83                 - comparable to Font
84                 - comparable to GlyphSet so that it can be passed to Glif widgets
85         """
86        
87         _title = "RoboFabFont"
88        
89         def __init__(self, path=None):
90                 BaseFont.__init__(self)
91                 if path is not None:
92                         self._path = os.path.normpath(os.path.abspath(path))
93                 else:
94                         self._path = None
95                 self._object = {}
96                
97                 self._glyphSet = None
98                 self._scheduledForDeletion = [] # this is a place for storing glyphs that need to be removed when the font is saved
99                
100                 self.kerning = RKerning()
101                 self.kerning.setParent(self)
102                 self.info = RInfo()
103                 self.info.setParent(self)
104                 self.groups = RGroups()
105                 self.groups.setParent(self)
106                 self.lib = RLib()
107                 self.lib.setParent(self)
108                
109                 if path:
110                         self._loadData(path)
111                
112         def __setitem__(self, glyphName, glyph):
113                 """Set a glyph at key."""
114                 self._object[glyphName] = glyph
115        
116         def __cmp__(self, other):
117                 """Compare this font with another, compare if they refer to the same file."""
118                 if not hasattr(other, '_path'):
119                         return -1
120                 if self._object._path == other._object._path and self._object._path is not None:
121                         return 0
122                 else:
123                         return -1
124        
125         def __len__(self):
126                 if self._glyphSet is None:
127                         return 0
128                 return len(self._glyphSet)
129        
130         def _loadData(self, path):
131                 #Load the data into the font
132                 from robofab.ufoLib import UFOReader
133                 u = UFOReader(path)
134                 u.readInfo(self.info)
135                 self.kerning.update(u.readKerning())
136                 self.kerning.setChanged(False)
137                 self.groups.update(u.readGroups())
138                 self.lib.update(u.readLib())
139                 # after reading the lib, read hinting data from the lib
140                 self.psHints = PostScriptFontHintValues(self)
141                 self._glyphSet = u.getGlyphSet()
142                 self._hasNotChanged(doGlyphs=False)
143                
144         def _loadGlyph(self, glyphName):
145                 """Load a single glyph from the glyphSet, on request."""
146                 from robofab.pens.rfUFOPen import RFUFOPointPen
147                 g =  RGlyph()
148                 g.name = glyphName
149                 pen = RFUFOPointPen(g)
150                 self._glyphSet.readGlyph(glyphName=glyphName, glyphObject=g, pointPen=pen)
151                 g.setParent(self)
152                 self._object[glyphName] = g
153                 self._object[glyphName]._hasNotChanged()
154                 return g
155                
156         #def _prepareSaveDir(self, dir):
157         #       path = os.path.join(dir, 'glyphs')
158         #       if not os.path.exists(path):
159         #               os.makedirs(path)
160
161         def _hasNotChanged(self, doGlyphs=True):
162                 #set the changed state of the font
163                 if doGlyphs:
164                         for glyph in self:
165                                 glyph._hasNotChanged()
166                 self.setChanged(False)
167        
168         #
169         # attributes
170         #
171        
172         def _get_path(self):
173                 return self._path
174        
175         path = property(_get_path, doc="path of the font")
176                
177         #
178         # methods for imitating GlyphSet?
179         #
180                        
181         def keys(self):
182                 # the keys are the superset of self._objects.keys() and
183                 # self._glyphSet.keys(), minus self._scheduledForDeletion
184                 keys = self._object.keys()
185                 if self._glyphSet is not None:
186                         keys.extend(self._glyphSet.keys())
187                 d = dict()
188                 for glyphName in keys:
189                         d[glyphName] = None
190                 for glyphName in self._scheduledForDeletion:
191                         if glyphName in d:
192                                 del d[glyphName]
193                 return d.keys()
194
195         def has_key(self, glyphName):
196                 # XXX ditto, see above.
197                 if self._glyphSet is not None:
198                         hasGlyph = glyphName in self._object or glyphName in self._glyphSet
199                 else:
200                         hasGlyph = glyphName in self._object
201                 return hasGlyph and not glyphName in self._scheduledForDeletion
202        
203         __contains__ = has_key
204        
205         def getWidth(self, glyphName):
206                 if self._object.has_key(glyphName):
207                         return self._object[glyphName].width
208                 raise IndexError                # or return None?
209                
210         def getReverseComponentMapping(self):
211                 """
212                 Get a reversed map of component references in the font.
213                 {
214                 'A' : ['Aacute', 'Aring']
215                 'acute' : ['Aacute']
216                 'ring' : ['Aring']
217                 etc.
218                 }
219                 """
220                 # a NON-REVERESED map is stored in the lib.
221                 # this is done because a reveresed map could
222                 # contain faulty data. for example: "Aacute" contains
223                 # a component that references "A". Glyph "Aacute" is
224                 # then deleted. The reverse map would still say that
225                 # "A" is referenced by "Aacute" even though the
226                 # glyph has been deleted. So, the stored lib works like this:
227                 # {
228                 # 'Aacute' : [
229                 #               # the last known mod time of the GLIF
230                 #               1098706856.75,
231                 #               # component references in a glyph
232                 #               ['A', 'acute']
233                 #               ]
234                 # }
235                 import time
236                 import os
237                 import re
238                 componentSearch_RE = re.compile(
239                         "<component\s+"         # <component
240                         "[^>]*?"                        # anything EXCEPT >
241                         "base\s*=\s*[\"\']"             # base="
242                         "(.*?)"                 # foo
243                         "[\"\']"                        # "
244                         )
245                 rightNow = time.time()
246                 libKey = "org.robofab.componentMapping"
247                 previousMap = None
248                 if self.lib.has_key(libKey):
249                         previousMap = self.lib[libKey]
250                 basicMap = {}
251                 reverseMap = {}
252                 for glyphName in self.keys():
253                         componentsToMap = None
254                         modTime = None
255                         # get the previous bits of data
256                         previousModTime = None
257                         previousList = None
258                         if previousMap is not None and previousMap.has_key(glyphName):
259                                 previousModTime, previousList = previousMap[glyphName]
260                         # the glyph has been loaded.
261                         # simply get the components from it.
262                         if self._object.has_key(glyphName):
263                                 componentsToMap = [component.baseGlyph for component in self._object[glyphName].components]
264                         # the glyph has not been loaded.
265                         else:
266                                 glyphPath = os.path.join(self._glyphSet.dirName, self._glyphSet.contents[glyphName])
267                                 scanGlyph = True
268                                 # test the modified time of the GLIF
269                                 fileModTime = os.path.getmtime(glyphPath)
270                                 if previousModTime is not None and fileModTime == previousModTime:
271                                         # the GLIF almost* certianly has not changed.
272                                         # *theoretically, a user could replace a GLIF
273                                         # with another GLIF that has precisely the same
274                                         # mod time.
275                                         scanGlyph = False
276                                         componentsToMap = previousList
277                                         modTime = previousModTime
278                                 else:
279                                         # the GLIF is different
280                                         modTime = fileModTime
281                                 if scanGlyph:
282                                         # use regex to extract component
283                                         # base glyphs from the file
284                                         f = open(glyphPath, 'rb')
285                                         data = f.read()
286                                         f.close()
287                                         componentsToMap = componentSearch_RE.findall(data)
288                         if componentsToMap is not None:
289                                 # store the non-reversed map
290                                 basicMap[glyphName] = (modTime, componentsToMap)
291                                 # reverse the map for the user
292                                 if componentsToMap:
293                                         for baseGlyphName in componentsToMap:
294                                                 if not reverseMap.has_key(baseGlyphName):
295                                                         reverseMap[baseGlyphName] = []
296                                                 reverseMap[baseGlyphName].append(glyphName)
297                                 # if a glyph has been loaded, we do not store data about it in the lib.
298                                 # this is done becuase there is not way to determine the proper mod time
299                                 # for a loaded glyph.
300                                 if modTime is None:
301                                         del basicMap[glyphName]
302                 # store the map in the lib for re-use
303                 self.lib[libKey] = basicMap
304                 return reverseMap
305                
306
307         def save(self, destDir=None, doProgress=False, saveNow=False):
308                 """Save the Font in UFO format."""
309                 # XXX note that when doing "save as" by specifying the destDir argument
310                 # _all_ glyphs get loaded into memory. This could be optimized by either
311                 # copying those .glif files that have not been edited or (not sure how
312                 # well that would work) by simply clearing out self._objects after the
313                 # save.
314                 from robofab.ufoLib import UFOWriter
315                 # if no destination is given, or if
316                 # the given destination is the current
317                 # path, this is not a save as operation
318                 if destDir is None or destDir == self._path:
319                         saveAs = False
320                         destDir = self._path
321                 else:
322                         saveAs = True
323                 u = UFOWriter(destDir)
324                 nonGlyphCount = 5
325                 bar = None
326                 if doProgress:
327                         from robofab.interface.all.dialogs import ProgressBar
328                         bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self._object.keys()))
329                 try:
330                         #if self.info.changed:
331                         if bar:
332                                 bar.label('Saving info...')
333                         u.writeInfo(self.info)
334                         if bar:
335                                 bar.tick()
336                         if self.kerning.changed or saveAs:
337                                 if bar:
338                                         bar.label('Saving kerning...')
339                                 u.writeKerning(self.kerning.asDict())
340                                 self.kerning.setChanged(False)
341                         if bar:
342                                 bar.tick()
343                         #if self.groups.changed:
344                         if bar:
345                                 bar.label('Saving groups...')
346                         u.writeGroups(self.groups)
347                         if bar:
348                                 bar.tick()
349
350                         if self._supportHints:
351                                 # save postscript hint data
352                                 self.lib[postScriptHintDataLibKey] = self.psHints.asDict()
353                         #if self.lib.changed:
354                         if bar:
355                                 bar.label('Saving lib...')
356                         u.writeLib(self.lib)
357                         if bar:
358                                 bar.tick()
359                         glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc()
360                         glyphSet = u.getGlyphSet(glyphNameToFileNameFunc)
361                         if len(self._scheduledForDeletion) != 0:
362                                 if bar:
363                                         bar.label('Removing deleted glyphs......')
364                                 for glyphName in self._scheduledForDeletion:
365                                         if glyphSet.has_key(glyphName):
366                                                 glyphSet.deleteGlyph(glyphName)
367                                 if bar:
368                                         bar.tick()
369                         if bar:
370                                 bar.label('Saving glyphs...')
371                         count = nonGlyphCount
372                         if saveAs:
373                                 glyphNames = self.keys()
374                         else:
375                                 glyphNames = self._object.keys()
376                         for glyphName in glyphNames:
377                                 glyph = self[glyphName]
378                                 glyph._saveToGlyphSet(glyphSet, glyphName=glyphName, force=saveAs)
379                                 if bar and not count % 10:
380                                         bar.tick(count)
381                                 count = count + 1
382                         glyphSet.writeContents()
383                         self._glyphSet = glyphSet
384                 except KeyboardInterrupt:
385                         bar.close()
386                         bar = None
387                 if bar:
388                         bar.close()
389                 self._path = destDir
390                 self._scheduledForDeletion = []
391                 self.setChanged(False)
392                
393         def newGlyph(self, glyphName, clear=True):
394                 """Make a new glyph with glyphName
395                 if the glyph exists and clear=True clear the glyph"""
396                 if clear and glyphName in self:
397                         g = self[glyphName]
398                         g.clear()
399                         g.width = self.info.defaultWidth
400                         return g
401                 g = RGlyph()
402                 g.setParent(self)
403                 g.name = glyphName
404                 g.width = self.info.defaultWidth
405                 g._hasChanged()
406                 self._object[glyphName] = g
407                 # is the user adding a glyph that has the same
408                 # name as one that was deleted earlier?
409                 if glyphName in self._scheduledForDeletion:
410                         self._scheduledForDeletion.remove(glyphName)
411                 return self.getGlyph(glyphName)
412                
413         def insertGlyph(self, glyph, as=None):
414                 """returns a new glyph that has been inserted into the font"""
415                 if as is None:
416                         name = glyph.name
417                 else:
418                         name = as
419                 glyph = glyph.copy()
420                 glyph.name = name
421                 glyph.setParent(self)
422                 glyph._hasChanged()
423                 self._object[name] = glyph
424                 # is the user adding a glyph that has the same
425                 # name as one that was deleted earlier?
426                 if name in self._scheduledForDeletion:
427                         self._scheduledForDeletion.remove(name)
428                 return self.getGlyph(name)
429                
430         def removeGlyph(self, glyphName):
431                 """remove a glyph from the font"""
432                 # XXX! Potential issue with removing glyphs.
433                 # if a glyph is removed from a font, but it is still referenced
434                 # by a component, it will give pens some trouble.
435                 # where does the resposibility for catching this fall?
436                 # the removeGlyph method? the addComponent method
437                 # of the various pens? somewhere else? hm... tricky.
438                 #
439                 #we won't actually remove it, we will just store it for removal
440                 # but only if the glyph does exist
441                 if self.has_key(glyphName) and glyphName not in self._scheduledForDeletion:
442                         self._scheduledForDeletion.append(glyphName)
443                 # now delete the object
444                 if self._object.has_key(glyphName):
445                         del self._object[glyphName]
446                 self._hasChanged()
447                
448         def getGlyph(self, glyphName):
449                 # XXX getGlyph may have to become private, to avoid duplication
450                 # with __getitem__
451                 n = None
452                 if self._object.has_key(glyphName):
453                         # have we served this glyph before? it should be in _object
454                         n = self._object[glyphName]
455                 else:
456                         # haven't served it before, is it in the glyphSet then?
457                         if self._glyphSet is not None and glyphName in self._glyphSet:
458                                 # yes, read the .glif file from disk
459                                 n = self._loadGlyph(glyphName)
460                 if n is None:
461                         raise KeyError, glyphName
462                 return n
463
464
465 class RGlyph(BaseGlyph):
466        
467         _title = "RGlyph"
468        
469         def __init__(self):
470                 BaseGlyph.__init__(self)
471                 self.contours = []
472                 self.components = []
473                 self.anchors = []
474                 self._unicodes = []
475                 self.width = 0
476                 self.note = None
477                 self._name = "Unnamed Glyph"
478                 self.selected = False
479                 self._properties = None
480                 self._lib = RLib()
481                 self._lib.setParent(self)
482                
483
484
485         def __len__(self):
486                 return len(self.contours)
487
488         def __getitem__(self, index):
489                 if index < len(self.contours):
490                         return self.contours[index]
491                 raise IndexError
492        
493         def _hasNotChanged(self):
494                 for contour in self.contours:
495                         contour.setChanged(False)
496                         for segment in contour.segments:
497                                 segment.setChanged(False)
498                                 for point in segment.points:
499                                         point.setChanged(False)
500                 for component in self.components:
501                         component.setChanged(False)
502                 for anchor in self.anchors:
503                         anchor.setChanged(False)
504                 self.setChanged(False)
505        
506         #
507         # attributes
508         #
509        
510         def _get_lib(self):
511                 return self._lib
512        
513         def _set_lib(self, obj):
514                 self._lib.clear()
515                 self._lib.update(obj)
516        
517         lib = property(_get_lib, _set_lib)
518        
519         def _get_name(self):
520                 return self._name
521        
522         def _set_name(self, value):
523                 prevName = self._name
524                 newName = value
525                 if newName == prevName:
526                         return
527                 self._name = newName
528                 self.setChanged(True)
529                 font = self.getParent()
530                 if font is not None:
531                         # but, this glyph could be linked to a
532                         # FontLab font, because objectsFL.RGlyph.copy()
533                         # creates an objectsRF.RGlyph with the parent
534                         # set to an objectsFL.RFont object. so, check to see
535                         # if this is a legitimate RFont before trying to
536                         # do the objectsRF.RFont glyph name change
537                         if isinstance(font, RFont):
538                                 font._object[newName] = self
539                                 # is the user changing a glyph's name to the
540                                 # name of a glyph that was deleted earlier?
541                                 if newName in font._scheduledForDeletion:
542                                         font._scheduledForDeletion.remove(newName)
543                                 font.removeGlyph(prevName)
544        
545         name = property(_get_name, _set_name)
546        
547         def _get_unicodes(self):
548                 return self._unicodes
549        
550         def _set_unicodes(self, value):
551                 if not isinstance(value, list):
552                         raise RoboFabError, "unicodes must be a list"
553                 self._unicodes = value
554                 self._hasChanged()
555                        
556         unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph")
557        
558         def _get_unicode(self):
559                 if len(self._unicodes) == 0:
560                         return None
561                 return self._unicodes[0]
562        
563         def _set_unicode(self, value):
564                 uni = self._unicodes
565                 if value is not None:
566                         if value not in uni:
567                                 self.unicodes.insert(0, value)
568                         elif uni.index(value) != 0:
569                                 uni.insert(0, uni.pop(uni.index(value)))
570                                 self.unicodes = uni
571                
572         unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph")
573        
574         def getPointPen(self):
575                 from robofab.pens.rfUFOPen import RFUFOPointPen
576                 return RFUFOPointPen(self)
577
578         def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)):
579                 """append a component to the glyph"""
580                 new = RComponent(baseGlyph, offset, scale)
581                 new.setParent(self)
582                 self.components.append(new)
583                 self._hasChanged()
584                
585         def appendAnchor(self, name, position, mark=None):
586                 """append an anchor to the glyph"""
587                 new = RAnchor(name, position, mark)
588                 new.setParent(self)
589                 self.anchors.append(new)
590                 self._hasChanged()
591        
592         def removeContour(self, index):
593                 """remove  a specific contour from the glyph"""
594                 del self.contours[index]
595                 self._hasChanged()
596                
597         def removeAnchor(self, anchor):
598                 """remove  a specific anchor from the glyph"""
599                 del self.anchors[anchor.index]
600                 self._hasChanged()
601        
602         def removeComponent(self, component):
603                 """remove  a specific component from the glyph"""
604                 del self.components[component.index]
605                 self._hasChanged()
606                        
607         def center(self, padding=None):
608                 """Equalise sidebearings, set to padding if wanted."""
609                 left = self.leftMargin
610                 right = self.rightMargin
611                 if padding:
612                         e_left = e_right = padding
613                 else:
614                         e_left = (left + right)/2
615                         e_right = (left + right) - e_left
616                 self.leftMargin = e_left
617                 self.rightMargin = e_right
618        
619         def decompose(self):
620                 """Decompose all components"""
621                 for i in range(len(self.components)):
622                         self.components[-1].decompose()
623                 self._hasChanged()
624                        
625         def clear(self, contours=True, components=True, anchors=True, guides=True):
626                 """Clear all items marked as True from the glyph"""
627                 if contours:
628                         self.clearContours()
629                 if components:
630                         self.clearComponents()
631                 if anchors:
632                         self.clearAnchors()
633                 if guides:
634                         self.clearHGuides()
635                         self.clearVGuides()
636        
637         def clearContours(self):
638                 """clear all contours"""
639                 self.contours = []
640                 self._hasChanged()
641        
642         def clearComponents(self):
643                 """clear all components"""
644                 self.components = []
645                 self._hasChanged()
646                
647         def clearAnchors(self):
648                 """clear all anchors"""
649                 self.anchors = []
650                 self._hasChanged()
651                
652         def clearHGuides(self):
653                 """clear all horizontal guides"""
654                 self.hGuides = []
655                 self._hasChanged()
656        
657         def clearVGuides(self):
658                 """clear all vertical guides"""
659                 self.vGuides = []
660                 self._hasChanged()
661                
662         def getAnchors(self):
663                 return self.anchors
664        
665         def getComponents(self):
666                 return self.components
667        
668         #
669         # stuff related to Glyph Properties
670         #
671        
672
673
674 class RContour(BaseContour):
675        
676         _title = "RoboFabContour"
677        
678         def __init__(self, object=None):
679                 #BaseContour.__init__(self)
680                 self.segments = []
681                 self.selected = False
682                
683         def __len__(self):
684                 return len(self.segments)
685
686         def __getitem__(self, index):
687                 if index < len(self.segments):
688                         return self.segments[index]
689                 raise IndexError
690                
691         def _get_index(self):
692                 return self.getParent().contours.index(self)
693                
694         def _set_index(self, index):
695                 ogIndex = self.index
696                 if index != ogIndex:
697                         contourList = self.getParent().contours
698                         contourList.insert(index, contourList.pop(ogIndex))
699                        
700        
701         index = property(_get_index, _set_index, doc="index of the contour")
702        
703         def _get_points(self):
704                 points = []
705                 for segment in self.segments:
706                         for point in segment.points:
707                                 points.append(point)
708                 return points
709        
710         points = property(_get_points, doc="view the contour as a list of points")
711        
712         def _get_bPoints(self):
713                 bPoints = []
714                 for segment in self.segments:
715                         segType = segment.type
716                         if segType == MOVE:
717                                 bType = CORNER
718                         elif segType == LINE:
719                                 bType = CORNER
720                         elif segType == CURVE:
721                                 if segment.smooth:
722                                         bType = CURVE
723                                 else:
724                                         bType = CORNER
725                         else:
726                                 raise RoboFabError, "encountered unknown segment type"
727                         b = RBPoint()
728                         b.setParent(segment)
729                         bPoints.append(b)
730                 return bPoints
731
732         bPoints = property(_get_bPoints, doc="view the contour as a list of bPoints")
733        
734         def appendSegment(self, segmentType, points, smooth=False):
735                 """append a segment to the contour"""
736                 segment = self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth)
737                 return segment
738                
739         def insertSegment(self, index, segmentType, points, smooth=False):
740                 """insert a segment into the contour"""
741                 segment = RSegment(segmentType, points, smooth)
742                 segment.setParent(self)
743                 self.segments.insert(index, segment)
744                 self._hasChanged()
745                 return segment
746                
747         def removeSegment(self, index):
748                 """remove a segment from the contour"""
749                 del self.segments[index]
750                 self._hasChanged()
751                                
752         def reverseContour(self):
753                 """reverse the contour"""
754                 from robofab.pens.reverseContourPointPen import ReverseContourPointPen
755                 index = self.index
756                 glyph = self.getParent()
757                 pen = glyph.getPointPen()
758                 reversePen = ReverseContourPointPen(pen)
759                 self.drawPoints(reversePen)
760                 # we've drawn the reversed contour onto our parent glyph,
761                 # so it sits at the end of the contours list:
762                 newContour = glyph.contours.pop(-1)
763                 for segment in newContour.segments:
764                         segment.setParent(self)
765                 self.segments = newContour.segments
766                 self._hasChanged()
767        
768         def setStartSegment(self, segmentIndex):
769                 """set the first segment on the contour"""
770                 # this obviously does not support open contours
771                 if len(self.segments) < 2:
772                         return
773                 if segmentIndex == 0:
774                         return
775                 if segmentIndex > len(self.segments)-1:
776                         raise IndexError, 'segment index not in segments list'
777                 oldStart = self.segments[0]
778                 oldLast = self.segments[-1]
779                  #check to see if the contour ended with a curve on top of the move
780                  #if we find one delete it,
781                 if oldLast.type == CURVE or oldLast.type == QCURVE:
782                         startOn = oldStart.onCurve
783                         lastOn = oldLast.onCurve
784                         if startOn.x == lastOn.x and startOn.y == lastOn.y:
785                                 del self.segments[0]
786                                 # since we deleted the first contour, the segmentIndex needs to shift
787                                 segmentIndex = segmentIndex - 1
788                 # if we DO have a move left over, we need to convert it to a line
789                 if self.segments[0].type == MOVE:
790                         self.segments[0].type = LINE
791                 # slice up the segments and reassign them to the contour
792                 segments = self.segments[segmentIndex:]
793                 self.segments = segments + self.segments[:segmentIndex]
794                 # now, draw the contour onto the parent glyph
795                 glyph = self.getParent()
796                 pen = glyph.getPointPen()
797                 self.drawPoints(pen)
798                 # we've drawn the new contour onto our parent glyph,
799                 # so it sits at the end of the contours list:
800                 newContour = glyph.contours.pop(-1)
801                 for segment in newContour.segments:
802                         segment.setParent(self)
803                 self.segments = newContour.segments
804                 self._hasChanged()
805
806
807 class RSegment(BaseSegment):
808        
809         _title = "RoboFabSegment"
810        
811         def __init__(self, segmentType=None, points=[], smooth=False):
812                 BaseSegment.__init__(self)
813                 self.selected = False
814                 self.points = []
815                 self.smooth = smooth
816                 if points:
817                         #the points in the segment should be RPoints, so create those objects
818                         for point in points[:-1]:
819                                 x, y = point
820                                 p = RPoint(x, y, pointType=OFFCURVE)
821                                 p.setParent(self)
822                                 self.points.append(p)
823                         aX, aY = points[-1]
824                         p = RPoint(aX, aY, segmentType)
825                         p.setParent(self)
826                         self.points.append(p)
827                
828         def _get_type(self):
829                 return self.points[-1].type
830        
831         def _set_type(self, pointType):
832                 onCurve = self.points[-1]
833                 ocType = onCurve.type
834                 if ocType == pointType:
835                         return
836                 #we are converting a cubic line into a cubic curve
837                 if pointType == CURVE and  ocType == LINE:
838                         onCurve.type = pointType
839                         parent = self.getParent()
840                         prev = parent._prevSegment(self.index)
841                         p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE)
842                         p1.setParent(self)
843                         p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE)
844                         p2.setParent(self)
845                         self.points.insert(0, p2)
846                         self.points.insert(0, p1)
847                 #we are converting a cubic move to a curve
848                 elif pointType == CURVE and ocType == MOVE:
849                         onCurve.type = pointType
850                         parent = self.getParent()
851                         prev = parent._prevSegment(self.index)
852                         p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE)
853                         p1.setParent(self)
854                         p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE)
855                         p2.setParent(self)
856                         self.points.insert(0, p2)
857                         self.points.insert(0, p1)
858                 #we are converting a quad curve to a cubic curve
859                 elif pointType == CURVE and ocType == QCURVE:
860                         onCurve.type == CURVE
861                 #we are converting a cubic curve into a cubic line
862                 elif pointType == LINE and ocType == CURVE:
863                         p = self.points.pop(-1)
864                         self.points = [p]
865                         onCurve.type = pointType
866                         self.smooth = False
867                 #we are converting a cubic move to a line
868                 elif pointType == LINE and ocType == MOVE:
869                         onCurve.type = pointType
870                 #we are converting a quad curve to a line:
871                 elif pointType == LINE and ocType == QCURVE:
872                         p = self.points.pop(-1)
873                         self.points = [p]
874                         onCurve.type = pointType
875                         self.smooth = False     
876                 # we are converting to a quad curve where just about anything is legal
877                 elif pointType == QCURVE:
878                         onCurve.type = pointType
879                 else:
880                         raise RoboFabError, 'unknown segment type'
881                        
882         type = property(_get_type, _set_type, doc="type of the segment")
883        
884         def _get_index(self):
885                 return self.getParent().segments.index(self)
886                
887         index = property(_get_index, doc="index of the segment")
888        
889         def insertPoint(self, index, pointType, point):
890                 x, y = point
891                 p = RPoint(x, y, pointType=pointType)
892                 p.setParent(self)
893                 self.points.insert(index, p)
894                 self._hasChanged()
895        
896         def removePoint(self, index):
897                 del self.points[index]
898                 self._hasChanged()
899                
900
901 class RBPoint(BaseBPoint):
902        
903         _title = "RoboFabBPoint"
904                
905         def _setAnchorChanged(self, value):
906                 self._anchorPoint.setChanged(value)
907        
908         def _setNextChanged(self, value):
909                 self._nextOnCurve.setChanged(value)     
910                
911         def _get__parentSegment(self):
912                 return self.getParent()
913                
914         _parentSegment = property(_get__parentSegment, doc="")
915        
916         def _get__nextOnCurve(self):
917                 pSeg = self._parentSegment
918                 contour = pSeg.getParent()
919                 #could this potentially return an incorrect index? say, if two segments are exactly the same?
920                 return contour.segments[(contour.segments.index(pSeg) + 1) % len(contour.segments)]
921        
922         _nextOnCurve = property(_get__nextOnCurve, doc="")
923        
924         def _get_index(self):
925                 return self._parentSegment.index
926        
927         index = property(_get_index, doc="index of the bPoint on the contour")
928
929
930 class RPoint(BasePoint):
931        
932         _title = "RoboFabPoint"
933        
934         def __init__(self, x=0, y=0, pointType=None, name=None):
935                 self.selected = False
936                 self._type = pointType
937                 self._x = x
938                 self._y = y
939                 self._name = None
940                
941         def _get_x(self):
942                 return self._x
943                
944         def _set_x(self, value):
945                 self._x = value
946                 self._hasChanged()
947        
948         x = property(_get_x, _set_x, doc="")
949
950         def _get_y(self):
951                 return self._y
952        
953         def _set_y(self, value):
954                 self._y = value
955                 self._hasChanged()
956
957         y = property(_get_y, _set_y, doc="")
958        
959         def _get_type(self):
960                 return self._type
961        
962         def _set_type(self, value):
963                 self._type = value
964                 self._hasChanged()
965
966         type = property(_get_type, _set_type, doc="")
967        
968         def _get_name(self):
969                 return self._name
970        
971         def _set_name(self, value):
972                 self._name = value
973                 self._hasChanged()
974
975         name = property(_get_name, _set_name, doc="")
976
977                
978 class RAnchor(BaseAnchor):
979        
980         _title = "RoboFabAnchor"
981        
982         def __init__(self, name=None, position=None, mark=None):
983                 BaseAnchor.__init__(self)
984                 self.selected = False
985                 self.name = name
986                 if position is None:
987                         self.x = self.y = None
988                 else:
989                         self.x, self.y = position
990                 self.mark = mark
991                
992         def _get_index(self):
993                 if self.getParent() is None: return None
994                 return self.getParent().anchors.index(self)
995        
996         index = property(_get_index, doc="index of the anchor")
997        
998         def _get_position(self):
999                 return (self.x, self.y)
1000        
1001         def _set_position(self, value):
1002                 self.x = value[0]
1003                 self.y = value[1]
1004                 self._hasChanged()
1005        
1006         position = property(_get_position, _set_position, doc="position of the anchor")
1007        
1008         def move(self, (x, y)):
1009                 """Move the anchor"""
1010                 self.x = self.x + x
1011                 self.y = self.y + y
1012                 self._hasChanged()
1013
1014                
1015 class RComponent(BaseComponent):
1016        
1017         _title = "RoboFabComponent"
1018        
1019         def __init__(self, baseGlyphName=None, offset=(0,0), scale=(1,1)):
1020                 BaseComponent.__init__(self)
1021                 self.selected = False
1022                 self._baseGlyph = baseGlyphName
1023                 self._offset = offset
1024                 self._scale = scale
1025                
1026         def _get_index(self):
1027                 if self.getParent() is None: return None
1028                 return self.getParent().components.index(self)
1029                
1030         index = property(_get_index, doc="index of the component")
1031        
1032         def _get_baseGlyph(self):
1033                 return self._baseGlyph
1034                
1035         def _set_baseGlyph(self, glyphName):
1036                 # XXXX needs to be implemented in objectsFL for symmetricity's sake. Eventually.
1037                 self._baseGlyph = glyphName
1038                 self._hasChanged()
1039                
1040         baseGlyph = property(_get_baseGlyph, _set_baseGlyph, doc="")
1041
1042         def _get_offset(self):
1043                 return self._offset
1044        
1045         def _set_offset(self, value):
1046                 self._offset = value
1047                 self._hasChanged()
1048                
1049         offset = property(_get_offset, _set_offset, doc="the offset of the component")
1050
1051         def _get_scale(self):
1052                 return self._scale
1053        
1054         def _set_scale(self, (x, y)):
1055                 self._scale = (x, y)
1056                 self._hasChanged()
1057                
1058         scale = property(_get_scale, _set_scale, doc="the scale of the component")
1059                
1060         def move(self, (x, y)):
1061                 """Move the component"""
1062                 self.offset = (self.offset[0] + x, self.offset[1] + y)
1063        
1064         def decompose(self):
1065                 """Decompose the component"""
1066                 baseGlyphName = self.baseGlyph
1067                 parentGlyph = self.getParent()
1068                 # if there is no parent glyph, there is nothing to decompose to
1069                 if baseGlyphName is not None and parentGlyph is not None:
1070                         parentFont = parentGlyph.getParent()
1071                         # we must have a parent glyph with the baseGlyph
1072                         # if not, we will simply remove the component from
1073                         # the parent glyph thereby decomposing the component
1074                         # to nothing.
1075                         if parentFont is not None and parentFont.has_key(baseGlyphName):
1076                                 from robofab.pens.adapterPens import TransformPointPen
1077                                 oX, oY = self.offset
1078                                 sX, sY = self.scale
1079                                 baseGlyph = parentFont[baseGlyphName]
1080                                 for contour in baseGlyph.contours:
1081                                         pointPen = parentGlyph.getPointPen()
1082                                         transPen = TransformPointPen(pointPen, (sX, 0, 0, sY, oX, oY))
1083                                         contour.drawPoints(transPen)
1084                         parentGlyph.components.remove(self)
1085        
1086                
1087 class RKerning(BaseKerning):
1088        
1089         _title = "RoboFabKerning"
1090
1091                
1092 class RGroups(BaseGroups):
1093        
1094         _title = "RoboFabGroups"
1095        
1096 class RLib(BaseLib):
1097        
1098         _title = "RoboFabLib"
1099
1100                
1101 class RInfo(BaseInfo):
1102        
1103         _title = "RoboFabFonInfo"
1104        
1105         def __init__(self):
1106                 BaseInfo.__init__(self)
1107                 self.selected = False
1108                
1109                 self._familyName = None
1110                 self._styleName = None
1111                 self._fullName = None
1112                 self._fontName = None
1113                 self._menuName = None
1114                 self._fondName = None
1115                 self._otFamilyName = None
1116                 self._otStyleName = None
1117                 self._otMacName = None
1118                 self._weightValue = None
1119                 self._weightName = None
1120                 self._widthName = None
1121                 self._fontStyle = None
1122                 self._msCharSet = None
1123                 self._note = None
1124                 self._fondID = None
1125                 self._uniqueID = None
1126                 self._versionMajor = None
1127                 self._versionMinor = None
1128                 self._year = None
1129                 self._copyright = None
1130                 self._notice = None
1131                 self._trademark = None
1132                 self._license = None
1133                 self._licenseURL = None
1134                 self._designer = None
1135                 self._designerURL = None
1136                 self._vendorURL = None
1137                 self._ttVendor = None
1138                 self._ttUniqueID = None
1139                 self._ttVersion = None
1140                 self._unitsPerEm = None
1141                 self._ascender = None
1142                 self._descender = None
1143                 self._capHeight = None
1144                 self._xHeight = None
1145                 self._defaultWidth = None
1146                 self._italicAngle = None
1147                 self._slantAngle = None
1148        
1149         def _get_familyName(self):
1150                 return self._familyName
1151        
1152         def _set_familyName(self, value):
1153                 self._familyName = value
1154        
1155         familyName = property(_get_familyName, _set_familyName, doc="family_name")
1156        
1157         def _get_styleName(self):
1158                 return self._styleName
1159        
1160         def _set_styleName(self, value):
1161                 self._styleName = value
1162        
1163         styleName = property(_get_styleName, _set_styleName, doc="style_name")
1164        
1165         def _get_fullName(self):
1166                 return self._fullName
1167        
1168         def _set_fullName(self, value):
1169                 self._fullName = value
1170        
1171         fullName = property(_get_fullName, _set_fullName, doc="full_name")
1172        
1173         def _get_fontName(self):
1174                 return self._fontName
1175        
1176         def _set_fontName(self, value):
1177                 self._fontName = value
1178        
1179         fontName = property(_get_fontName, _set_fontName, doc="font_name")
1180        
1181         def _get_menuName(self):
1182                 return self._menuName
1183        
1184         def _set_menuName(self, value):
1185                 self._menuName = value
1186        
1187         menuName = property(_get_menuName, _set_menuName, doc="menu_name")
1188        
1189         def _get_fondName(self):
1190                 return self._fondName
1191        
1192         def _set_fondName(self, value):
1193                 self._fondName = value
1194        
1195         fondName = property(_get_fondName, _set_fondName, doc="apple_name")
1196        
1197         def _get_otFamilyName(self):
1198                 return self._otFamilyName
1199        
1200         def _set_otFamilyName(self, value):
1201                 self._otFamilyName = value
1202        
1203         otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name")
1204        
1205         def _get_otStyleName(self):
1206                 return self._otStyleName
1207        
1208         def _set_otStyleName(self, value):
1209                 self._otStyleName = value
1210        
1211         otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name")
1212        
1213         def _get_otMacName(self):
1214                 return self._otMacName
1215        
1216         def _set_otMacName(self, value):
1217                 self._otMacName = value
1218        
1219         otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible")
1220        
1221         def _get_weightValue(self):
1222                 return self._weightValue
1223        
1224         def _set_weightValue(self, value):
1225                 self._weightValue = value
1226        
1227         weightValue = property(_get_weightValue, _set_weightValue, doc="weight value")
1228        
1229         def _get_weightName(self):
1230                 return self._weightName
1231        
1232         def _set_weightName(self, value):
1233                 self._weightName = value
1234        
1235         weightName = property(_get_weightName, _set_weightName, doc="weight name")
1236        
1237         def _get_widthName(self):
1238                 return self._widthName
1239        
1240         def _set_widthName(self, value):
1241                 self._widthName = value
1242        
1243         widthName = property(_get_widthName, _set_widthName, doc="width name")
1244
1245         def _get_fontStyle(self):
1246                 return self._fontStyle
1247        
1248         def _set_fontStyle(self, value):
1249                 self._fontStyle = value
1250        
1251         fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style")
1252        
1253         def _get_msCharSet(self):
1254                 return self._msCharSet
1255        
1256         def _set_msCharSet(self, value):
1257                 self._msCharSet = value
1258        
1259         msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset")
1260        
1261         def _get_note(self):
1262                 return self._note
1263        
1264         def _set_note(self, value):
1265                 self._note = value
1266        
1267         note = property(_get_note, _set_note, doc="note")
1268        
1269         def _get_fondID(self):
1270                 return self._fondID
1271        
1272         def _set_fondID(self, value):
1273                 self._fondID = value
1274        
1275         fondID = property(_get_fondID, _set_fondID, doc="fond_id")
1276        
1277         def _get_uniqueID(self):
1278                 return self._uniqueID
1279        
1280         def _set_uniqueID(self, value):
1281                 self._uniqueID = value
1282        
1283         uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id")
1284        
1285         def _get_versionMajor(self):
1286                 return self._versionMajor
1287        
1288         def _set_versionMajor(self, value):
1289                 self._versionMajor = value
1290        
1291         versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major")
1292        
1293         def _get_versionMinor(self):
1294                 return self._versionMinor
1295        
1296         def _set_versionMinor(self, value):
1297                 self._versionMinor = value
1298        
1299         versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor")
1300        
1301         def _get_year(self):
1302                 return self._year
1303        
1304         def _set_year(self, value):
1305                 self._year = value
1306        
1307         year = property(_get_year, _set_year, doc="year")
1308        
1309         def _get_copyright(self):
1310                 return self._copyright
1311        
1312         def _set_copyright(self, value):
1313                 self._copyright = value
1314        
1315         copyright = property(_get_copyright, _set_copyright, doc="copyright")
1316        
1317         def _get_notice(self):
1318                 return self._notice
1319        
1320         def _set_notice(self, value):
1321                 self._notice = value
1322        
1323         notice = property(_get_notice, _set_notice, doc="notice")
1324        
1325         def _get_trademark(self):
1326                 return self._trademark
1327        
1328         def _set_trademark(self, value):
1329                 self._trademark = value
1330        
1331         trademark = property(_get_trademark, _set_trademark, doc="trademark")
1332        
1333         def _get_license(self):
1334                 return self._license
1335        
1336         def _set_license(self, value):
1337                 self._license = value
1338        
1339         license = property(_get_license, _set_license, doc="license")
1340        
1341         def _get_licenseURL(self):
1342                 return self._licenseURL
1343        
1344         def _set_licenseURL(self, value):
1345                 self._licenseURL = value
1346        
1347         licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url")
1348        
1349         def _get_designer(self):
1350                 return self._designer
1351        
1352         def _set_designer(self, value):
1353                 self._designer = value
1354        
1355         designer = property(_get_designer, _set_designer, doc="designer")
1356        
1357         def _get_createdBy(self):
1358                 return self._createdBy
1359        
1360         def _set_createdBy(self, value):
1361                 self._createdBy = value
1362        
1363         createdBy = property(_get_createdBy, _set_createdBy, doc="source")
1364        
1365         def _get_designerURL(self):
1366                 return self._designerURL
1367        
1368         def _set_designerURL(self, value):
1369                 self._designerURL = value
1370        
1371         designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url")
1372        
1373         def _get_vendorURL(self):
1374                 return self._vendorURL
1375        
1376         def _set_vendorURL(self, value):
1377                 self._vendorURL = value
1378        
1379         vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url")
1380        
1381         def _get_ttVendor(self):
1382                 return self._ttVendor
1383        
1384         def _set_ttVendor(self, value):
1385                 self._ttVendor = value
1386        
1387         ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor")
1388        
1389         def _get_ttUniqueID(self):
1390                 return self._ttUniqueID
1391        
1392         def _set_ttUniqueID(self, value):
1393                 self._ttUniqueID = value
1394        
1395         ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id")
1396        
1397         def _get_ttVersion(self):
1398                 return self._ttVersion
1399        
1400         def _set_ttVersion(self, value):
1401                 self._ttVersion = value
1402        
1403         ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version")
1404        
1405         def _get_unitsPerEm(self):
1406                 return self._unitsPerEm
1407                
1408         def _set_unitsPerEm(self, value):
1409                 self._unitsPerEm = value
1410        
1411         unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="")
1412        
1413         def _get_ascender(self):
1414                 return self._ascender
1415        
1416         def _set_ascender(self, value):
1417                 self._ascender = value
1418        
1419         ascender = property(_get_ascender, _set_ascender, doc="ascender value")
1420        
1421         def _get_descender(self):
1422                 return self._descender
1423        
1424         def _set_descender(self, value):
1425                 self._descender = value
1426        
1427         descender = property(_get_descender, _set_descender, doc="descender value")
1428        
1429         def _get_capHeight(self):
1430                 return self._capHeight
1431        
1432         def _set_capHeight(self, value):
1433                 self._capHeight = value
1434        
1435         capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value")
1436        
1437         def _get_xHeight(self):
1438                 return self._xHeight
1439        
1440         def _set_xHeight(self, value):
1441                 self._xHeight = value
1442        
1443         xHeight = property(_get_xHeight, _set_xHeight, doc="x height value")
1444        
1445         def _get_defaultWidth(self):
1446                 return self._defaultWidth
1447        
1448         def _set_defaultWidth(self, value):
1449                 self._defaultWidth = value
1450        
1451         defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value")
1452        
1453         def _get_italicAngle(self):
1454                 return self._italicAngle
1455        
1456         def _set_italicAngle(self, value):
1457                 self._italicAngle = value
1458        
1459         italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle")
1460        
1461         def _get_slantAngle(self):
1462                 return self._slantAngle
1463        
1464         def _set_slantAngle(self, value):
1465                 self._slantAngle = value
1466        
1467         slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle")
1468
Note: See TracBrowser for help on using the browser.