""" This is the new doc for the objectsRF module. To Do: - make it so that BaseKerning.clear() uses self._kerning.clear() instead of self._kerning = {} - check all of BaseKerning to make sure that it doesn't replace self._kerning - in BaseKerning.__init__ use if kerningDixt is None - make BaseGroups subclass BaseObject like BaseKerning does? - add __contains__ to all dict-like base objects - get right of BaseGroups.findGlyph - look at notification disabling in RFeatures.__init__ - BaseObject.naked expects the wrapped object to be stored at ._object. this is not always true. - what should be done about the PSHint objects? - the repr methods in ObjectsBase use odd font.info name values I'm trying to find a way to make it possible for defcon based environments to use these objects in a simple way. For now, I have overridable methods that return uninstantiated classes for each of the sub-object types that are needed by a particular object. Subclasses can override these to implement their own R* subclasses. In __init__, "path" can be a path or None to open a basic defcon font or a defcon.Font (or subclass) object to be wrapped. """ import os from defcon import Font as DefconFont from robofab import RoboFabError, RoboFabWarning from robofab.objects.objectsBase import RBaseObject, BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\ _interpolate, _interpolatePt, roundPt, addPt,\ MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues __all__ = [ "CurrentFont", "CurrentGlyph", 'OpenFont', 'RFont', 'RGlyph', 'RContour', 'RPoint', 'RBPoint', 'RAnchor', 'RComponent' ] def CurrentFont(): """CurrentFont is not available in objectsRF.""" return None def CurrentGlyph(): """CurrentGlyph is not available in objectsRF.""" return None def OpenFont(path=None, note=None): """Open a font from a path. If path is not given, present the user with a dialog (availability of dialogs will vary)""" if not note: note = 'select a .ufo directory' if not path: from robofab.interface.all.dialogs import GetFolder path = GetFolder(note) if path: try: return RFont(path) except OSError: from robofab.interface.all.dialogs import Message 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)) else: return None def NewFont(familyName=None, styleName=None): """Make a new font object. Initialise with familyName and styleName.""" new = RFont() if familyName is not None: new.info.familyName = familyName if styleName is not None: new.info.styleName = styleName return new def AllFonts(): """AllFonts is not available in objectsRF.""" raise NotImplementedError class RFont(BaseFont): """ Font object representing the data in an UFO. - myFontInstace['A'] access the font as a dictionary with glyphnames as key """ _title = "RoboFabFont" def __init__(self, path=None): super(RFont, self).__init__() if isinstance(path, DefconFont): self._object = path else: self._object = DefconFont(path) self._info = None self._kerning = None self._groups = None self._features = None self._lib = None # XXX PS Hints? # Object Classes def infoClass(self): return RInfo def groupsClass(self): return RGroups def kerningClass(self): return RKerning def featuresClass(self): return RFeatures def libClass(self): return RLib # Sub-Objects def _get_info(self): if self._info is None: self._info = self.infoClass()(self._object.info) return self._info info = property(_get_info) def _get_groups(self): if self._groups is None: self._groups = self.groupsClass()(self._object.groups) return self._groups groups = property(_get_groups) def _get_kerning(self): if self._kerning is None: self._kerning = self.kerningClass()(self._object.kerning) return self._kerning kerning = property(_get_kerning) def _get_features(self): if self._features is None: self._features = self.featuresClass()(self._object.features) return self._features features = property(_get_features) def _get_lib(self): if self._lib is None: self._lib = self.libClass()(self._object.lib) return self._lib lib = property(_get_lib) # def __setitem__(self, glyphName, glyph): # """Set a glyph at key.""" # self._object[glyphName] = glyph # # def __cmp__(self, other): # """Compare this font with another, compare if they refer to the same file.""" # if not hasattr(other, '_path'): # return -1 # if self._object._path == other._object._path and self._object._path is not None: # return 0 # else: # return -1 # # def __len__(self): # if self._glyphSet is None: # return 0 # return len(self._glyphSet) # # def _loadData(self, path): # from ufoLib import UFOReader # reader = UFOReader(path) # fontLib = reader.readLib() # # info # reader.readInfo(self.info) # # kerning # self.kerning.update(reader.readKerning()) # self.kerning.setChanged(False) # # groups # self.groups.update(reader.readGroups()) # # features # if reader.formatVersion == 1: # # migrate features from the lib # features = [] # classes = fontLib.get("org.robofab.opentype.classes") # if classes is not None: # del fontLib["org.robofab.opentype.classes"] # features.append(classes) # splitFeatures = fontLib.get("org.robofab.opentype.features") # if splitFeatures is not None: # order = fontLib.get("org.robofab.opentype.featureorder") # if order is None: # order = splitFeatures.keys() # order.sort() # else: # del fontLib["org.robofab.opentype.featureorder"] # del fontLib["org.robofab.opentype.features"] # for tag in order: # oneFeature = splitFeatures.get(tag) # if oneFeature is not None: # features.append(oneFeature) # features = "\n".join(features) # else: # features = reader.readFeatures() # self.features.text = features # # hint data # self.psHints = PostScriptFontHintValues(self) # if postScriptHintDataLibKey in fontLib: # del fontLib[postScriptHintDataLibKey] # # lib # self.lib.update(fontLib) # # glyphs # self._glyphSet = reader.getGlyphSet() # self._hasNotChanged(doGlyphs=False) # # def _loadGlyph(self, glyphName): # """Load a single glyph from the glyphSet, on request.""" # from robofab.pens.rfUFOPen import RFUFOPointPen # g = RGlyph() # g.name = glyphName # pen = RFUFOPointPen(g) # self._glyphSet.readGlyph(glyphName=glyphName, glyphObject=g, pointPen=pen) # g.setParent(self) # g.psHints._loadFromLib(g.lib) # self._object[glyphName] = g # self._object[glyphName]._hasNotChanged() # return g # # #def _prepareSaveDir(self, dir): # # path = os.path.join(dir, 'glyphs') # # if not os.path.exists(path): # # os.makedirs(path) # # def _hasNotChanged(self, doGlyphs=True): # #set the changed state of the font # if doGlyphs: # for glyph in self: # glyph._hasNotChanged() # self.setChanged(False) # # # # # attributes # # # # def _get_path(self): # return self._path # # path = property(_get_path, doc="path of the font") # # # # # methods for imitating GlyphSet? # # # # def keys(self): # # the keys are the superset of self._objects.keys() and # # self._glyphSet.keys(), minus self._scheduledForDeletion # keys = self._object.keys() # if self._glyphSet is not None: # keys.extend(self._glyphSet.keys()) # d = dict() # for glyphName in keys: # d[glyphName] = None # for glyphName in self._scheduledForDeletion: # if glyphName in d: # del d[glyphName] # return d.keys() # # def has_key(self, glyphName): # # XXX ditto, see above. # if self._glyphSet is not None: # hasGlyph = glyphName in self._object or glyphName in self._glyphSet # else: # hasGlyph = glyphName in self._object # return hasGlyph and not glyphName in self._scheduledForDeletion # # __contains__ = has_key # # def getWidth(self, glyphName): # if self._object.has_key(glyphName): # return self._object[glyphName].width # raise IndexError # or return None? # # def getReverseComponentMapping(self): # """ # Get a reversed map of component references in the font. # { # 'A' : ['Aacute', 'Aring'] # 'acute' : ['Aacute'] # 'ring' : ['Aring'] # etc. # } # """ # # a NON-REVERESED map is stored in the lib. # # this is done because a reveresed map could # # contain faulty data. for example: "Aacute" contains # # a component that references "A". Glyph "Aacute" is # # then deleted. The reverse map would still say that # # "A" is referenced by "Aacute" even though the # # glyph has been deleted. So, the stored lib works like this: # # { # # 'Aacute' : [ # # # the last known mod time of the GLIF # # 1098706856.75, # # # component references in a glyph # # ['A', 'acute'] # # ] # # } # import time # import os # import re # componentSearch_RE = re.compile( # "]*?" # anything EXCEPT > # "base\s*=\s*[\"\']" # base=" # "(.*?)" # foo # "[\"\']" # " # ) # rightNow = time.time() # libKey = "org.robofab.componentMapping" # previousMap = None # if self.lib.has_key(libKey): # previousMap = self.lib[libKey] # basicMap = {} # reverseMap = {} # for glyphName in self.keys(): # componentsToMap = None # modTime = None # # get the previous bits of data # previousModTime = None # previousList = None # if previousMap is not None and previousMap.has_key(glyphName): # previousModTime, previousList = previousMap[glyphName] # # the glyph has been loaded. # # simply get the components from it. # if self._object.has_key(glyphName): # componentsToMap = [component.baseGlyph for component in self._object[glyphName].components] # # the glyph has not been loaded. # else: # glyphPath = os.path.join(self._glyphSet.dirName, self._glyphSet.contents[glyphName]) # scanGlyph = True # # test the modified time of the GLIF # fileModTime = os.path.getmtime(glyphPath) # if previousModTime is not None and fileModTime == previousModTime: # # the GLIF almost* certianly has not changed. # # *theoretically, a user could replace a GLIF # # with another GLIF that has precisely the same # # mod time. # scanGlyph = False # componentsToMap = previousList # modTime = previousModTime # else: # # the GLIF is different # modTime = fileModTime # if scanGlyph: # # use regex to extract component # # base glyphs from the file # f = open(glyphPath, 'rb') # data = f.read() # f.close() # componentsToMap = componentSearch_RE.findall(data) # if componentsToMap is not None: # # store the non-reversed map # basicMap[glyphName] = (modTime, componentsToMap) # # reverse the map for the user # if componentsToMap: # for baseGlyphName in componentsToMap: # if not reverseMap.has_key(baseGlyphName): # reverseMap[baseGlyphName] = [] # reverseMap[baseGlyphName].append(glyphName) # # if a glyph has been loaded, we do not store data about it in the lib. # # this is done becuase there is not way to determine the proper mod time # # for a loaded glyph. # if modTime is None: # del basicMap[glyphName] # # store the map in the lib for re-use # self.lib[libKey] = basicMap # return reverseMap # # # def save(self, destDir=None, doProgress=False, formatVersion=2): # """Save the Font in UFO format.""" # # XXX note that when doing "save as" by specifying the destDir argument # # _all_ glyphs get loaded into memory. This could be optimized by either # # copying those .glif files that have not been edited or (not sure how # # well that would work) by simply clearing out self._objects after the # # save. # from ufoLib import UFOWriter # from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab # # if no destination is given, or if # # the given destination is the current # # path, this is not a save as operation # if destDir is None or destDir == self._path: # saveAs = False # destDir = self._path # else: # saveAs = True # # start a progress bar # nonGlyphCount = 5 # bar = None # if doProgress: # from robofab.interface.all.dialogs import ProgressBar # bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys())) # # write # writer = UFOWriter(destDir, formatVersion=formatVersion) # try: # # make a shallow copy of the lib. stuff may be added to it. # fontLib = dict(self.lib) # # info # if bar: # bar.label("Saving info...") # writer.writeInfo(self.info) # if bar: # bar.tick() # # kerning # if self.kerning.changed or saveAs: # if bar: # bar.label("Saving kerning...") # writer.writeKerning(self.kerning.asDict()) # if bar: # bar.tick() # # groups # if bar: # bar.label("Saving groups...") # writer.writeGroups(self.groups) # if bar: # bar.tick() # # features # if bar: # bar.label("Saving features...") # features = self.features.text # if features is None: # features = "" # if formatVersion == 2: # writer.writeFeatures(features) # elif formatVersion == 1: # classes, features = splitFeaturesForFontLab(features) # if classes: # fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n" # if features: # featureDict = {} # for featureName, featureText in features: # featureDict[featureName] = featureText.strip() + "\n" # fontLib["org.robofab.opentype.features"] = featureDict # fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features] # if bar: # bar.tick() # # lib # if formatVersion == 1: # fontLib[postScriptHintDataLibKey] = self.psHints.asDict() # if bar: # bar.label("Saving lib...") # writer.writeLib(fontLib) # if bar: # bar.tick() # # glyphs # glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() # # glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) # if len(self._scheduledForDeletion) != 0: # if bar: # bar.label("Removing deleted glyphs...") # for glyphName in self._scheduledForDeletion: # if glyphSet.has_key(glyphName): # glyphSet.deleteGlyph(glyphName) # if bar: # bar.tick() # if bar: # bar.label("Saving glyphs...") # count = nonGlyphCount # if saveAs: # glyphNames = self.keys() # else: # glyphNames = self._object.keys() # for glyphName in glyphNames: # glyph = self[glyphName] # glyph.psHints._saveToLib(glyph.lib) # glyph._saveToGlyphSet(glyphSet, glyphName=glyphName, force=saveAs) # if bar and not count % 10: # bar.tick(count) # count = count + 1 # glyphSet.writeContents() # self._glyphSet = glyphSet # # only blindly stop if the user says to # except KeyboardInterrupt: # bar.close() # bar = None # # kill the progress bar # if bar: # bar.close() # # reset internal stuff # self._path = destDir # self._scheduledForDeletion = [] # self.setChanged(False) # # def newGlyph(self, glyphName, clear=True): # """Make a new glyph with glyphName # if the glyph exists and clear=True clear the glyph""" # if clear and glyphName in self: # g = self[glyphName] # g.clear() # w = self.info.postscriptDefaultWidthX # if w is None: # w = 0 # g.width = w # return g # g = RGlyph() # g.setParent(self) # g.name = glyphName # w = self.info.postscriptDefaultWidthX # if w is None: # w = 0 # g.width = w # g._hasChanged() # self._object[glyphName] = g # # is the user adding a glyph that has the same # # name as one that was deleted earlier? # if glyphName in self._scheduledForDeletion: # self._scheduledForDeletion.remove(glyphName) # return self.getGlyph(glyphName) # # def insertGlyph(self, glyph, name=None): # """returns a new glyph that has been inserted into the font""" # if name is None: # name = glyph.name # glyph = glyph.copy() # glyph.name = name # glyph.setParent(self) # glyph._hasChanged() # self._object[name] = glyph # # is the user adding a glyph that has the same # # name as one that was deleted earlier? # if name in self._scheduledForDeletion: # self._scheduledForDeletion.remove(name) # return self.getGlyph(name) # # def removeGlyph(self, glyphName): # """remove a glyph from the font""" # # XXX! Potential issue with removing glyphs. # # if a glyph is removed from a font, but it is still referenced # # by a component, it will give pens some trouble. # # where does the resposibility for catching this fall? # # the removeGlyph method? the addComponent method # # of the various pens? somewhere else? hm... tricky. # # # #we won't actually remove it, we will just store it for removal # # but only if the glyph does exist # if self.has_key(glyphName) and glyphName not in self._scheduledForDeletion: # self._scheduledForDeletion.append(glyphName) # # now delete the object # if self._object.has_key(glyphName): # del self._object[glyphName] # self._hasChanged() # # def getGlyph(self, glyphName): # # XXX getGlyph may have to become private, to avoid duplication # # with __getitem__ # n = None # if self._object.has_key(glyphName): # # have we served this glyph before? it should be in _object # n = self._object[glyphName] # else: # # haven't served it before, is it in the glyphSet then? # if self._glyphSet is not None and glyphName in self._glyphSet: # # yes, read the .glif file from disk # n = self._loadGlyph(glyphName) # if n is None: # raise KeyError, glyphName # return n # # #class RGlyph(BaseGlyph): # # _title = "RGlyph" # # def __init__(self): # BaseGlyph.__init__(self) # self.contours = [] # self.components = [] # self.anchors = [] # self._unicodes = [] # self.width = 0 # self.note = None # self._name = "Unnamed Glyph" # self.selected = False # self._properties = None # self._lib = RLib() # self._lib.setParent(self) # self.psHints = PostScriptGlyphHintValues() # self.psHints.setParent(self) # # def __len__(self): # return len(self.contours) # # def __getitem__(self, index): # if index < len(self.contours): # return self.contours[index] # raise IndexError # # def _hasNotChanged(self): # for contour in self.contours: # contour.setChanged(False) # for segment in contour.segments: # segment.setChanged(False) # for point in segment.points: # point.setChanged(False) # for component in self.components: # component.setChanged(False) # for anchor in self.anchors: # anchor.setChanged(False) # self.setChanged(False) # # # # # attributes # # # # def _get_lib(self): # return self._lib # # def _set_lib(self, obj): # self._lib.clear() # self._lib.update(obj) # # lib = property(_get_lib, _set_lib) # # def _get_name(self): # return self._name # # def _set_name(self, value): # prevName = self._name # newName = value # if newName == prevName: # return # self._name = newName # self.setChanged(True) # font = self.getParent() # if font is not None: # # but, this glyph could be linked to a # # FontLab font, because objectsFL.RGlyph.copy() # # creates an objectsRF.RGlyph with the parent # # set to an objectsFL.RFont object. so, check to see # # if this is a legitimate RFont before trying to # # do the objectsRF.RFont glyph name change # if isinstance(font, RFont): # font._object[newName] = self # # is the user changing a glyph's name to the # # name of a glyph that was deleted earlier? # if newName in font._scheduledForDeletion: # font._scheduledForDeletion.remove(newName) # font.removeGlyph(prevName) # # name = property(_get_name, _set_name) # # def _get_unicodes(self): # return self._unicodes # # def _set_unicodes(self, value): # if not isinstance(value, list): # raise RoboFabError, "unicodes must be a list" # self._unicodes = value # self._hasChanged() # # unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph") # # def _get_unicode(self): # if len(self._unicodes) == 0: # return None # return self._unicodes[0] # # def _set_unicode(self, value): # uni = self._unicodes # if value is not None: # if value not in uni: # self.unicodes.insert(0, value) # elif uni.index(value) != 0: # uni.insert(0, uni.pop(uni.index(value))) # self.unicodes = uni # # unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph") # # def getPointPen(self): # from robofab.pens.rfUFOPen import RFUFOPointPen # return RFUFOPointPen(self) # # def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): # """append a component to the glyph""" # new = RComponent(baseGlyph, offset, scale) # new.setParent(self) # self.components.append(new) # self._hasChanged() # # def appendAnchor(self, name, position, mark=None): # """append an anchor to the glyph""" # new = RAnchor(name, position, mark) # new.setParent(self) # self.anchors.append(new) # self._hasChanged() # # def removeContour(self, index): # """remove a specific contour from the glyph""" # del self.contours[index] # self._hasChanged() # # def removeAnchor(self, anchor): # """remove a specific anchor from the glyph""" # del self.anchors[anchor.index] # self._hasChanged() # # def removeComponent(self, component): # """remove a specific component from the glyph""" # del self.components[component.index] # self._hasChanged() # # def center(self, padding=None): # """Equalise sidebearings, set to padding if wanted.""" # left = self.leftMargin # right = self.rightMargin # if padding: # e_left = e_right = padding # else: # e_left = (left + right)/2 # e_right = (left + right) - e_left # self.leftMargin = e_left # self.rightMargin = e_right # # def decompose(self): # """Decompose all components""" # for i in range(len(self.components)): # self.components[-1].decompose() # self._hasChanged() # # def clear(self, contours=True, components=True, anchors=True, guides=True): # """Clear all items marked as True from the glyph""" # if contours: # self.clearContours() # if components: # self.clearComponents() # if anchors: # self.clearAnchors() # if guides: # self.clearHGuides() # self.clearVGuides() # # def clearContours(self): # """clear all contours""" # self.contours = [] # self._hasChanged() # # def clearComponents(self): # """clear all components""" # self.components = [] # self._hasChanged() # # def clearAnchors(self): # """clear all anchors""" # self.anchors = [] # self._hasChanged() # # def clearHGuides(self): # """clear all horizontal guides""" # self.hGuides = [] # self._hasChanged() # # def clearVGuides(self): # """clear all vertical guides""" # self.vGuides = [] # self._hasChanged() # # def getAnchors(self): # return self.anchors # # def getComponents(self): # return self.components # # # # # stuff related to Glyph Properties # # # # # #class RContour(BaseContour): # # _title = "RoboFabContour" # # def __init__(self, object=None): # #BaseContour.__init__(self) # self.segments = [] # self.selected = False # # def __len__(self): # return len(self.segments) # # def __getitem__(self, index): # if index < len(self.segments): # return self.segments[index] # raise IndexError # # def _get_index(self): # return self.getParent().contours.index(self) # # def _set_index(self, index): # ogIndex = self.index # if index != ogIndex: # contourList = self.getParent().contours # contourList.insert(index, contourList.pop(ogIndex)) # # # index = property(_get_index, _set_index, doc="index of the contour") # # def _get_points(self): # points = [] # for segment in self.segments: # for point in segment.points: # points.append(point) # return points # # points = property(_get_points, doc="view the contour as a list of points") # # def _get_bPoints(self): # bPoints = [] # for segment in self.segments: # segType = segment.type # if segType == MOVE: # bType = CORNER # elif segType == LINE: # bType = CORNER # elif segType == CURVE: # if segment.smooth: # bType = CURVE # else: # bType = CORNER # else: # raise RoboFabError, "encountered unknown segment type" # b = RBPoint() # b.setParent(segment) # bPoints.append(b) # return bPoints # # bPoints = property(_get_bPoints, doc="view the contour as a list of bPoints") # # def appendSegment(self, segmentType, points, smooth=False): # """append a segment to the contour""" # segment = self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth) # return segment # # def insertSegment(self, index, segmentType, points, smooth=False): # """insert a segment into the contour""" # segment = RSegment(segmentType, points, smooth) # segment.setParent(self) # self.segments.insert(index, segment) # self._hasChanged() # return segment # # def removeSegment(self, index): # """remove a segment from the contour""" # del self.segments[index] # self._hasChanged() # # def reverseContour(self): # """reverse the contour""" # from robofab.pens.reverseContourPointPen import ReverseContourPointPen # index = self.index # glyph = self.getParent() # pen = glyph.getPointPen() # reversePen = ReverseContourPointPen(pen) # self.drawPoints(reversePen) # # we've drawn the reversed contour onto our parent glyph, # # so it sits at the end of the contours list: # newContour = glyph.contours.pop(-1) # for segment in newContour.segments: # segment.setParent(self) # self.segments = newContour.segments # self._hasChanged() # # def setStartSegment(self, segmentIndex): # """set the first segment on the contour""" # # this obviously does not support open contours # if len(self.segments) < 2: # return # if segmentIndex == 0: # return # if segmentIndex > len(self.segments)-1: # raise IndexError, 'segment index not in segments list' # oldStart = self.segments[0] # oldLast = self.segments[-1] # #check to see if the contour ended with a curve on top of the move # #if we find one delete it, # if oldLast.type == CURVE or oldLast.type == QCURVE: # startOn = oldStart.onCurve # lastOn = oldLast.onCurve # if startOn.x == lastOn.x and startOn.y == lastOn.y: # del self.segments[0] # # since we deleted the first contour, the segmentIndex needs to shift # segmentIndex = segmentIndex - 1 # # if we DO have a move left over, we need to convert it to a line # if self.segments[0].type == MOVE: # self.segments[0].type = LINE # # slice up the segments and reassign them to the contour # segments = self.segments[segmentIndex:] # self.segments = segments + self.segments[:segmentIndex] # # now, draw the contour onto the parent glyph # glyph = self.getParent() # pen = glyph.getPointPen() # self.drawPoints(pen) # # we've drawn the new contour onto our parent glyph, # # so it sits at the end of the contours list: # newContour = glyph.contours.pop(-1) # for segment in newContour.segments: # segment.setParent(self) # self.segments = newContour.segments # self._hasChanged() # # #class RSegment(BaseSegment): # # _title = "RoboFabSegment" # # def __init__(self, segmentType=None, points=[], smooth=False): # BaseSegment.__init__(self) # self.selected = False # self.points = [] # self.smooth = smooth # if points: # #the points in the segment should be RPoints, so create those objects # for point in points[:-1]: # x, y = point # p = RPoint(x, y, pointType=OFFCURVE) # p.setParent(self) # self.points.append(p) # aX, aY = points[-1] # p = RPoint(aX, aY, segmentType) # p.setParent(self) # self.points.append(p) # # def _get_type(self): # return self.points[-1].type # # def _set_type(self, pointType): # onCurve = self.points[-1] # ocType = onCurve.type # if ocType == pointType: # return # #we are converting a cubic line into a cubic curve # if pointType == CURVE and ocType == LINE: # onCurve.type = pointType # parent = self.getParent() # prev = parent._prevSegment(self.index) # p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE) # p1.setParent(self) # p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE) # p2.setParent(self) # self.points.insert(0, p2) # self.points.insert(0, p1) # #we are converting a cubic move to a curve # elif pointType == CURVE and ocType == MOVE: # onCurve.type = pointType # parent = self.getParent() # prev = parent._prevSegment(self.index) # p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE) # p1.setParent(self) # p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE) # p2.setParent(self) # self.points.insert(0, p2) # self.points.insert(0, p1) # #we are converting a quad curve to a cubic curve # elif pointType == CURVE and ocType == QCURVE: # onCurve.type == CURVE # #we are converting a cubic curve into a cubic line # elif pointType == LINE and ocType == CURVE: # p = self.points.pop(-1) # self.points = [p] # onCurve.type = pointType # self.smooth = False # #we are converting a cubic move to a line # elif pointType == LINE and ocType == MOVE: # onCurve.type = pointType # #we are converting a quad curve to a line: # elif pointType == LINE and ocType == QCURVE: # p = self.points.pop(-1) # self.points = [p] # onCurve.type = pointType # self.smooth = False # # we are converting to a quad curve where just about anything is legal # elif pointType == QCURVE: # onCurve.type = pointType # else: # raise RoboFabError, 'unknown segment type' # # type = property(_get_type, _set_type, doc="type of the segment") # # def _get_index(self): # return self.getParent().segments.index(self) # # index = property(_get_index, doc="index of the segment") # # def insertPoint(self, index, pointType, point): # x, y = point # p = RPoint(x, y, pointType=pointType) # p.setParent(self) # self.points.insert(index, p) # self._hasChanged() # # def removePoint(self, index): # del self.points[index] # self._hasChanged() # # #class RBPoint(BaseBPoint): # # _title = "RoboFabBPoint" # # def _setAnchorChanged(self, value): # self._anchorPoint.setChanged(value) # # def _setNextChanged(self, value): # self._nextOnCurve.setChanged(value) # # def _get__parentSegment(self): # return self.getParent() # # _parentSegment = property(_get__parentSegment, doc="") # # def _get__nextOnCurve(self): # pSeg = self._parentSegment # contour = pSeg.getParent() # #could this potentially return an incorrect index? say, if two segments are exactly the same? # return contour.segments[(contour.segments.index(pSeg) + 1) % len(contour.segments)] # # _nextOnCurve = property(_get__nextOnCurve, doc="") # # def _get_index(self): # return self._parentSegment.index # # index = property(_get_index, doc="index of the bPoint on the contour") # # #class RPoint(BasePoint): # # _title = "RoboFabPoint" # # def __init__(self, x=0, y=0, pointType=None, name=None): # self.selected = False # self._type = pointType # self._x = x # self._y = y # self._name = None # # def _get_x(self): # return self._x # # def _set_x(self, value): # self._x = value # self._hasChanged() # # x = property(_get_x, _set_x, doc="") # # def _get_y(self): # return self._y # # def _set_y(self, value): # self._y = value # self._hasChanged() # # y = property(_get_y, _set_y, doc="") # # def _get_type(self): # return self._type # # def _set_type(self, value): # self._type = value # self._hasChanged() # # type = property(_get_type, _set_type, doc="") # # def _get_name(self): # return self._name # # def _set_name(self, value): # self._name = value # self._hasChanged() # # name = property(_get_name, _set_name, doc="") # # #class RAnchor(BaseAnchor): # # _title = "RoboFabAnchor" # # def __init__(self, name=None, position=None, mark=None): # BaseAnchor.__init__(self) # self.selected = False # self.name = name # if position is None: # self.x = self.y = None # else: # self.x, self.y = position # self.mark = mark # # def _get_index(self): # if self.getParent() is None: return None # return self.getParent().anchors.index(self) # # index = property(_get_index, doc="index of the anchor") # # def _get_position(self): # return (self.x, self.y) # # def _set_position(self, value): # self.x = value[0] # self.y = value[1] # self._hasChanged() # # position = property(_get_position, _set_position, doc="position of the anchor") # # def move(self, (x, y)): # """Move the anchor""" # self.x = self.x + x # self.y = self.y + y # self._hasChanged() # # #class RComponent(BaseComponent): # # _title = "RoboFabComponent" # # def __init__(self, baseGlyphName=None, offset=(0,0), scale=(1,1)): # BaseComponent.__init__(self) # self.selected = False # self._baseGlyph = baseGlyphName # self._offset = offset # self._scale = scale # # def _get_index(self): # if self.getParent() is None: return None # return self.getParent().components.index(self) # # index = property(_get_index, doc="index of the component") # # def _get_baseGlyph(self): # return self._baseGlyph # # def _set_baseGlyph(self, glyphName): # # XXXX needs to be implemented in objectsFL for symmetricity's sake. Eventually. # self._baseGlyph = glyphName # self._hasChanged() # # baseGlyph = property(_get_baseGlyph, _set_baseGlyph, doc="") # # def _get_offset(self): # return self._offset # # def _set_offset(self, value): # self._offset = value # self._hasChanged() # # offset = property(_get_offset, _set_offset, doc="the offset of the component") # # def _get_scale(self): # return self._scale # # def _set_scale(self, (x, y)): # self._scale = (x, y) # self._hasChanged() # # scale = property(_get_scale, _set_scale, doc="the scale of the component") # # def move(self, (x, y)): # """Move the component""" # self.offset = (self.offset[0] + x, self.offset[1] + y) # # def decompose(self): # """Decompose the component""" # baseGlyphName = self.baseGlyph # parentGlyph = self.getParent() # # if there is no parent glyph, there is nothing to decompose to # if baseGlyphName is not None and parentGlyph is not None: # parentFont = parentGlyph.getParent() # # we must have a parent glyph with the baseGlyph # # if not, we will simply remove the component from # # the parent glyph thereby decomposing the component # # to nothing. # if parentFont is not None and parentFont.has_key(baseGlyphName): # from robofab.pens.adapterPens import TransformPointPen # oX, oY = self.offset # sX, sY = self.scale # baseGlyph = parentFont[baseGlyphName] # for contour in baseGlyph.contours: # pointPen = parentGlyph.getPointPen() # transPen = TransformPointPen(pointPen, (sX, 0, 0, sY, oX, oY)) # contour.drawPoints(transPen) # parentGlyph.components.remove(self) # # ---- # Info # ---- class RInfo(BaseInfo): _title = "RoboFabFontInfo" def __init__(self, info): super(RInfo, self).__init__() self._object = info def _environmentSetAttr(self, attr, value): setattr(self._object, attr, value) def _environmentGetAttr(self, attr): return getattr(self._object, attr) # ------ # Groups # ------ class _RDict(RBaseObject): def __init__(self, obj): super(_RDict, self).__init__() self._object = obj def __len__(self): return len(self._object) def __contains__(self, key): return key in self._object has_key = __contains__ def __setitem__(self, key, value): self._object[key] = value def __getitem__(self, key): return self._object[key] def __delitem__(self, key): del self._object[key] def __iter__(self): for key in self.keys(): yield key def clear(self): self._object.clear() def keys(self): return self._object.keys() def values(self): return self._object.values() def items(self): return self._object.items() def pop(self, key, default=None): return self._object.pop(key, default) def popitem(self): return self._object.popitem() def update(self, other): self._object.update(other) class RGroups(_RDict): # We can't use BaseGroups here since it subclasses dict instead of BaseObject. # We can't point to defcon as a result of that. _title = "RoboFabGroups" # ------- # Kerning # ------- class RKerning(BaseKerning): _title = "RoboFabKerning" def clear(self): self._kerning.clear() # -------- # Features # -------- class RFeatures(BaseFeatures): _title = "RoboFabFeatures" def __init__(self, featuresObject): # XXX may need to temporarily disable notifications # so that defcon doesn't set dirty as a result of the # _text attribute hacking here. text = featuresObject.text super(RFeatures, self).__init__() self._text = text def _get_text(self): return self._features.text def _set_text(self, value): assert isinstance(value, (basestring, None)) self._features.text = value text = property(_get_text, _set_text, doc="raw feature text.") # these _text methods are necessary because BaseFeatures # stores text at _text. we need to reroute that to the # defcon object for proper storage. def _get__text(self): return self.text def _set__text(self, value): self.text = value _text = property(_get__text) # --- # Lib # --- class RLib(_RDict): # We can't use BaseLib here since it subclasses dict instead of BaseObject. # We can't point to defcon as a result of that. _title = "RoboFabLib" #class PostScriptFontHintValues(BasePostScriptFontHintValues): # """ Font level PostScript hints object for objectsRF usage. # # If there are values in the lib, use those. # If there are no values in the lib, use defaults. # # The psHints attribute for objectsRF.RFont is basically just the # data read from the Lib. When the object saves to UFO, the # hints are written back to the lib, which is then saved. # # """ # # def __init__(self, aFont=None, data=None): # self.setParent(aFont) # BasePostScriptFontHintValues.__init__(self) # if aFont is not None: # # in version 1, this data was stored in the lib # # if it is still there, guess that it is correct # # move it to font info and remove it from the lib. # libData = aFont.lib.get(postScriptHintDataLibKey) # if libData is not None: # self.fromDict(libData) # del libData[postScriptHintDataLibKey] # if data is not None: # self.fromDict(data) # #def getPostScriptHintDataFromLib(aFont, fontLib): # hintData = fontLib.get(postScriptHintDataLibKey) # psh = PostScriptFontHintValues(aFont) # psh.fromDict(hintData) # return psh # #class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): # """ Glyph level PostScript hints object for objectsRF usage. # If there are values in the lib, use those. # If there are no values in the lib, be empty. # # """ # def __init__(self, aGlyph=None, data=None): # # read the data from the glyph.lib, it won't be anywhere else # BasePostScriptGlyphHintValues.__init__(self) # if aGlyph is not None: # self.setParent(aGlyph) # self._loadFromLib(aGlyph.lib) # if data is not None: # self.fromDict(data) if __name__ == "__main__": from defcon.test.testTools import getTestFontPath font = RFont(getTestFontPath()) print font print font.info print font.groups print font.kerning #print font.features print font.lib