diff --git a/Lib/robofab/objects/objectsDefcon.py b/Lib/robofab/objects/objectsDefcon.py index 4cb6a51b7..6fd9b4bf5 100755 --- a/Lib/robofab/objects/objectsDefcon.py +++ b/Lib/robofab/objects/objectsDefcon.py @@ -1,7 +1,22 @@ """ This is the new doc for the objectsRF module. + +To Do: +- is the path normalization magic in RFont.__init__ necessary? +- 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? """ +import os +from defcon import Font as DefconFont from robofab import RoboFabError, RoboFabWarning from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ @@ -10,8 +25,6 @@ from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseI MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues -import os - __all__ = [ "CurrentFont", "CurrentGlyph", 'OpenFont', @@ -58,54 +71,6 @@ def NewFont(familyName=None, styleName=None): def AllFonts(): """AllFonts is not available in objectsRF.""" raise NotImplementedError - - -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) class RFont(BaseFont): @@ -117,1094 +82,1287 @@ class RFont(BaseFont): """ _title = "RoboFabFont" + + # available for overriding by subclasses that + # want to implement their own defcon subclasses. + _defconFontClass = DefconFont + _defconObjectSubclasses = {} def __init__(self, path=None): - BaseFont.__init__(self) + super(RFont, self).__init__() + # XXX is it really necessary to normalize paths? if path is not None: self._path = os.path.normpath(os.path.abspath(path)) else: self._path = None - self._object = {} - - self._glyphSet = None - self._scheduledForDeletion = [] # this is a place for storing glyphs that need to be removed when the font is saved - - self.kerning = RKerning() - self.kerning.setParent(self) - self.info = RInfo() - self.info.setParent(self) - self.features = RFeatures() - self.features.setParent(self) - self.groups = RGroups() - self.groups.setParent(self) - self.lib = RLib() - self.lib.setParent(self) - if path: - self._loadData(path) - else: - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - - 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) + # load the defcon object + self._object = DefconFont(path, **self._defconObjectSubclasses) + self._info = None + self._kerning = None + self._groups = None + self._features = None + self._lib = None + # XXX PS Hints? - 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) + # sub-objects - 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 _get_info(self): + if self._info is None: + self._info = RInfo(self._object.info) - 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 - + info = property(_get_info) - 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() + def _get_groups(self): + if self._groups is None: + self._groups = RGroups(self._object.groups) - 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) + groups = property(_get_groups) - 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 + def _get_kerning(self): + if self._kerning is None: + self._kerning = RKerning(self._object.kerning) + kerning = property(_get_kerning) -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 _get_features(self): + if self._features is None: + self._features = RFeatures(self._object.features) - def __len__(self): - return len(self.contours) + features = property(_get_features) - 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) + if self._lib is None: + self._lib = RLib(self._object.lib) - 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 - # - + 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) -class RContour(BaseContour): - - _title = "RoboFabContour" - - def __init__(self, object=None): - #BaseContour.__init__(self) - self.segments = [] - self.selected = False - +# ------ +# Groups +# ------ + +class _RDict(BaseObject): + + def __init__(self, obj): + super(_RDict, self).__init__() + self._object = obj + def __len__(self): - return len(self.segments) + return len(self._object) - 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 + def __contains__(self, key): + return key in self._object - 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() + 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 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 RGroups(_RDict): -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") + # 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" -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="") +# ------- +# Kerning +# ------- - 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) - - class RKerning(BaseKerning): - + _title = "RoboFabKerning" - -class RGroups(BaseGroups): - - _title = "RoboFabGroups" - -class RLib(BaseLib): - - _title = "RoboFabLib" + def clear(self): + self._kerning.clear() - -class RInfo(BaseInfo): - - _title = "RoboFabFontInfo" + +# -------- +# 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)