1630 lines
43 KiB
Python
1630 lines
43 KiB
Python
|
"""
|
||
|
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 kerningDict is None
|
||
|
- make BaseGroups subclass BaseObject like BaseKerning does?
|
||
|
- add __contains__ to all dict-like base objects
|
||
|
- get rid of BaseGroups.findGlyph
|
||
|
- 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
|
||
|
- how will obj.copy() work here?
|
||
|
probably need to make the object arguments in __init__ optional and create one if needed.
|
||
|
this could cause problems when their are defcon subclasses (consider a contour being created
|
||
|
with the defcon Contour object independently and then inserted into a glyph that uses a
|
||
|
Contour subclass). a way around this would be to test isinstance(incoming, self.objClass())
|
||
|
during all insertion methods.
|
||
|
- need to work out how selected will work in these objects
|
||
|
- note somewhere that the various get* methods should not be used externally
|
||
|
(they are only public because getGLyph was left public in RFont back in the day)
|
||
|
- need to add an index method to RGlyph for getting the index of a contour
|
||
|
glyph.contours.index(contour) won't work unless we rework comparisons to compare
|
||
|
the naked object. this is a problem because glyph.removeContour requires an index.
|
||
|
whereas glyph.removeComponent requires the component.
|
||
|
- what should happen to anchors in BaseGlyph.appendGlyph?
|
||
|
- what should be done with the anchor mark (used in appendAnchor and probably elsewhere)?
|
||
|
|
||
|
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.
|
||
|
|
||
|
Overall Design:
|
||
|
- The defcon objects are wrapped whenever they are requested and there
|
||
|
is no caching of the wrapped objects. This is not done because it would
|
||
|
likely cause synchronicity problems.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import os
|
||
|
from defcon import Font as DefconFont
|
||
|
from robofab import RoboFabError, RoboFabWarning
|
||
|
from robofab.objects.objectsBase import RBaseObject, BaseFont, BaseLayerSet, BaseLayer, \
|
||
|
BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\
|
||
|
BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \
|
||
|
BaseColor, \
|
||
|
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._layers = None
|
||
|
self._info = None
|
||
|
self._kerning = None
|
||
|
self._groups = None
|
||
|
self._features = None
|
||
|
self._lib = None
|
||
|
# XXX PS Hints?
|
||
|
|
||
|
# Object Classes
|
||
|
|
||
|
def layersClass(self):
|
||
|
return RLayerSet
|
||
|
|
||
|
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_layers(self):
|
||
|
if self._layers is None:
|
||
|
self._layers = self.layersClass()(self._object.layers)
|
||
|
return self._layers
|
||
|
|
||
|
layers = property(_get_layers)
|
||
|
|
||
|
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):
|
||
|
return len(self._object)
|
||
|
|
||
|
# 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(
|
||
|
# "<component\s+" # <component
|
||
|
# "[^>]*?" # 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
|
||
|
#
|
||
|
|
||
|
# --------
|
||
|
# LayerSet
|
||
|
# --------
|
||
|
|
||
|
class RLayerSet(BaseLayerSet):
|
||
|
|
||
|
def __init__(self, layerSet):
|
||
|
super(RLayerSet, self).__init__()
|
||
|
self._object = layerSet
|
||
|
|
||
|
def layerClass(self):
|
||
|
return RLayer
|
||
|
|
||
|
# layer creation, destruction and retrieval
|
||
|
|
||
|
def __contains__(self, layerName):
|
||
|
return layerName in self._object
|
||
|
|
||
|
has_key = __contains__
|
||
|
|
||
|
def getLayer(self, layerName):
|
||
|
if layerName not in self._object:
|
||
|
raise KeyError("Font does not have a layer named %s." % layerName)
|
||
|
return self.layerClass()(self._object[layerName])
|
||
|
|
||
|
def __getitem__(self, layerName):
|
||
|
return self.getLayer(layerName)
|
||
|
|
||
|
def newLayer(self, layerName):
|
||
|
assert layerName not in self._object, "A layer named %s already exists." % layerName
|
||
|
self._object.newLayer(layerName)
|
||
|
return self.getLayer(layerName)
|
||
|
|
||
|
def removeLayer(self, layerName):
|
||
|
layer = self.getLayer(layerName)
|
||
|
del self._object[layerName]
|
||
|
|
||
|
# layer names
|
||
|
|
||
|
def keys(self):
|
||
|
return self._object.layerOrder
|
||
|
|
||
|
def getLayerOrder(self):
|
||
|
return self._object.layerOrder()
|
||
|
|
||
|
def setLayerOrder(self, order):
|
||
|
self._object.layerOrder = order
|
||
|
|
||
|
# default layer
|
||
|
|
||
|
def getDefaultLayer(self):
|
||
|
layerName = self._object.defaultLayer.name
|
||
|
return self[layerName]
|
||
|
|
||
|
def setDefaultLayer(self, layer):
|
||
|
naked = layer.naked()
|
||
|
found = False
|
||
|
for l in self._object:
|
||
|
if l == naked:
|
||
|
found = True
|
||
|
break
|
||
|
assert found, "The layer being set as the default must already belong to the font."
|
||
|
self._object.setDefaultLayer(naked)
|
||
|
|
||
|
# -----
|
||
|
# Layer
|
||
|
# -----
|
||
|
|
||
|
class RLayer(BaseLayer):
|
||
|
|
||
|
"""Base class for all Layer objects."""
|
||
|
|
||
|
def __init__(self, layer):
|
||
|
super(RLayer, self).__init__()
|
||
|
self._object = layer
|
||
|
self._lib = None
|
||
|
|
||
|
def glyphClass(self):
|
||
|
return RGlyph
|
||
|
|
||
|
def libClass(self):
|
||
|
return RLib
|
||
|
|
||
|
# XXX def __repr__(self):
|
||
|
|
||
|
# layer info
|
||
|
|
||
|
def _get_lib(self):
|
||
|
if self._lib is None:
|
||
|
self._lib = self.libClass()(self._object.lib)
|
||
|
return self._lib
|
||
|
|
||
|
lib = property(_get_lib)
|
||
|
|
||
|
def _get_color(self):
|
||
|
color = self._object.color
|
||
|
if color is not None:
|
||
|
color = BaseColor(color)
|
||
|
return color
|
||
|
|
||
|
def _set_color(self, value):
|
||
|
self._object.color = value
|
||
|
|
||
|
color = property(_get_color, _set_color)
|
||
|
|
||
|
# glyph creation, destruction and retrieval
|
||
|
|
||
|
def getGlyph(self, glyphName):
|
||
|
if glyphName not in self._object:
|
||
|
raise KeyError("The layer does not contain a glyph named %s." % glyphName)
|
||
|
return self.glyphClass()(self._object[glyphName])
|
||
|
|
||
|
def newGlyph(self, glyphName, clear=True):
|
||
|
assert glyphName not in self._object, "A glyph named %s already exists." % glyphName
|
||
|
self._object.newGlyph(glyphName)
|
||
|
return self.getGlyph(glyphName)
|
||
|
|
||
|
def insertGlyph(self, glyph, name=None):
|
||
|
self._object.insertGlyph(glyph, name=name)
|
||
|
if name is None:
|
||
|
name = glyph.name
|
||
|
return self.getGlyph(name)
|
||
|
|
||
|
def removeGlyph(self, glyphName):
|
||
|
glyph = self.getGlyph(glyphName)
|
||
|
del self._object[glyphName]
|
||
|
|
||
|
# dict behavior
|
||
|
|
||
|
def keys(self):
|
||
|
return self._object.keys()
|
||
|
|
||
|
def __contains__(self, glyphName):
|
||
|
return glyphName in self._object
|
||
|
|
||
|
def __getitem__(self, glyphName):
|
||
|
return self.getGlyph(glyphName)
|
||
|
|
||
|
# dynamic data extraction
|
||
|
|
||
|
def getCharacterMapping(self):
|
||
|
"""Create a dictionary of unicode -> [glyphname, ...] mappings.
|
||
|
Note that this dict is created each time this method is called,
|
||
|
which can make it expensive for larger fonts. All glyphs are loaded.
|
||
|
Note that one glyph can have multiple unicode values,
|
||
|
and a unicode value can have multiple glyphs pointing to it."""
|
||
|
return dict(self._object.unicodeData)
|
||
|
|
||
|
def getReverseComponentMapping(self):
|
||
|
"""
|
||
|
Get a reversed map of component references in the font.
|
||
|
{
|
||
|
'A' : ['Aacute', 'Aring']
|
||
|
'acute' : ['Aacute']
|
||
|
'ring' : ['Aring']
|
||
|
etc.
|
||
|
}
|
||
|
"""
|
||
|
return self._object.componentReferences
|
||
|
|
||
|
# -----
|
||
|
# Glyph
|
||
|
# -----
|
||
|
|
||
|
class RGlyph(BaseGlyph):
|
||
|
|
||
|
_title = "RGlyph"
|
||
|
|
||
|
def __init__(self, glyph):
|
||
|
super(RGlyph, self).__init__()
|
||
|
self._object = glyph
|
||
|
|
||
|
def _hasNotChanged(self):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
# Identification
|
||
|
|
||
|
def _get_name(self):
|
||
|
return self._object.name
|
||
|
|
||
|
def _set_name(self, value):
|
||
|
self._object.name = value
|
||
|
|
||
|
name = property(_get_name, _set_name)
|
||
|
|
||
|
def _get_unicodes(self):
|
||
|
return self._object.unicodes
|
||
|
|
||
|
def _set_unicodes(self, value):
|
||
|
if not isinstance(value, list):
|
||
|
raise RoboFabError, "unicodes must be a list"
|
||
|
self._objects.unicodes = value
|
||
|
|
||
|
unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph")
|
||
|
|
||
|
def _get_unicode(self):
|
||
|
return self._object.unicode
|
||
|
|
||
|
def _set_unicode(self, value):
|
||
|
self._object.unicode = value
|
||
|
|
||
|
unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph")
|
||
|
|
||
|
# Metrics
|
||
|
|
||
|
def _get_box(self):
|
||
|
bounds = self._obejct.bounds
|
||
|
if bounds is None:
|
||
|
bounds = (0, 0, 0, 0)
|
||
|
return bounds
|
||
|
|
||
|
def _get_leftMargin(self):
|
||
|
if self.isEmpty():
|
||
|
return 0
|
||
|
return self._object.leftMargin
|
||
|
|
||
|
def _set_leftMargin(self, value):
|
||
|
if self.isEmpty():
|
||
|
self.width = self.width + value
|
||
|
else:
|
||
|
self._object.leftMargin = value
|
||
|
|
||
|
leftMargin = property(_get_leftMargin, _set_leftMargin, doc="the left margin")
|
||
|
|
||
|
def _get_rightMargin(self):
|
||
|
if self.isEmpty():
|
||
|
return self.width
|
||
|
return self._object.rightMargin
|
||
|
|
||
|
def _set_rightMargin(self, value):
|
||
|
if self.isEmpty():
|
||
|
self.width = value
|
||
|
else:
|
||
|
self._object.rightMargin = value
|
||
|
|
||
|
# Lib
|
||
|
|
||
|
def libClass(self):
|
||
|
return RLib
|
||
|
|
||
|
def _get_lib(self):
|
||
|
return self.libClass()(self._object.lib)
|
||
|
|
||
|
def _set_lib(self, obj):
|
||
|
self._object.lib.clear()
|
||
|
self._object.lib.update(obj)
|
||
|
|
||
|
lib = property(_get_lib, _set_lib)
|
||
|
|
||
|
# Contours
|
||
|
|
||
|
def contourClass(self):
|
||
|
return RContour
|
||
|
|
||
|
def getContour(self, index):
|
||
|
return self.contourClass()(self._object[index])
|
||
|
|
||
|
def _get_contours(self):
|
||
|
return [self.getContour(index) for index in range(len(self))]
|
||
|
|
||
|
contours = property(_get_contours)
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self._object)
|
||
|
|
||
|
def __getitem__(self, index):
|
||
|
if index < len(self._object):
|
||
|
return self._getContour(index)
|
||
|
raise IndexError
|
||
|
|
||
|
def removeContour(self, index):
|
||
|
"""remove a specific contour from the glyph"""
|
||
|
del self._object[index]
|
||
|
|
||
|
def clearContours(self):
|
||
|
"""clear all contours"""
|
||
|
self._object.clearContours()
|
||
|
|
||
|
# Components
|
||
|
|
||
|
def componentClass(self):
|
||
|
return RComponent
|
||
|
|
||
|
def getComponent(self, component):
|
||
|
return self.componentClass()(component)
|
||
|
|
||
|
def _get_components(self):
|
||
|
return [self.getComponent(component) for component in self._object.components]
|
||
|
|
||
|
components = property(_get_components)
|
||
|
|
||
|
def getComponents(self):
|
||
|
return self.components
|
||
|
|
||
|
def removeComponent(self, component):
|
||
|
"""remove a specific component from the glyph"""
|
||
|
self._object.removeComponent(component.naked())
|
||
|
|
||
|
def decompose(self):
|
||
|
"""Decompose all components"""
|
||
|
self._object.decomposeAllComponents()
|
||
|
|
||
|
def clearComponents(self):
|
||
|
"""clear all components"""
|
||
|
self._object.clearComponents()
|
||
|
|
||
|
# Anchors
|
||
|
|
||
|
def anchorClass(self):
|
||
|
return RAnchor
|
||
|
|
||
|
def getAnchor(self, anchor):
|
||
|
return self.anchorClass()(anchor)
|
||
|
|
||
|
def _get_anchors(self):
|
||
|
return [self.getAnchor(anchor) for anchor in self._object.anchors]
|
||
|
|
||
|
anchors = property(_get_anchors)
|
||
|
|
||
|
def getAnchors(self):
|
||
|
return self.anchors
|
||
|
|
||
|
def appendAnchor(self, name, position, mark=None):
|
||
|
"""append an anchor to the glyph"""
|
||
|
anchor = self._object.instantiateAnchor()
|
||
|
anchor.name = name
|
||
|
anchor.x = position[0]
|
||
|
anchor.y = position[1]
|
||
|
self._object.appendAnchor(anchor)
|
||
|
|
||
|
def removeAnchor(self, anchor):
|
||
|
"""remove a specific anchor from the glyph"""
|
||
|
self._object.removeAnchor(anchor.naked())
|
||
|
|
||
|
def clearAnchors(self):
|
||
|
"""clear all anchors"""
|
||
|
self._object.clearAnchors()
|
||
|
|
||
|
# Clear
|
||
|
|
||
|
def clear(self, contours=True, components=True, anchors=True):
|
||
|
"""Clear all items marked as True from the glyph"""
|
||
|
if contours:
|
||
|
self.clearContours()
|
||
|
if components:
|
||
|
self.clearComponents()
|
||
|
if anchors:
|
||
|
self.clearAnchors()
|
||
|
|
||
|
# Pens and Drawing
|
||
|
|
||
|
def getPen(self):
|
||
|
return self._object.getPen()
|
||
|
|
||
|
def getPointPen(self):
|
||
|
return self._object.getPointPen()
|
||
|
|
||
|
def draw(self, pen):
|
||
|
self._object.draw(pen)
|
||
|
|
||
|
def drawPoints(self, pointPen):
|
||
|
self._object.drawPoints(pointPen)
|
||
|
|
||
|
|
||
|
class RContour(BaseContour):
|
||
|
|
||
|
_title = "RoboFabContour"
|
||
|
|
||
|
def __init__(self, contour):
|
||
|
super(RContour, self).__init__()
|
||
|
self._object = contour
|
||
|
|
||
|
# Bounds
|
||
|
|
||
|
def _get_box(self):
|
||
|
bounds = self._obejct.bounds
|
||
|
if bounds is None:
|
||
|
bounds = (0, 0, 0, 0)
|
||
|
return bounds
|
||
|
|
||
|
box = property(_get_box, doc="the bounding box for the contour")
|
||
|
|
||
|
# Direction
|
||
|
|
||
|
def _set_clockwise(self, value):
|
||
|
self._object.clockwise = value
|
||
|
|
||
|
def _get_clockwise(self):
|
||
|
return self._object.clockwise
|
||
|
|
||
|
clockwise = property(_get_clockwise, _set_clockwise, doc="direction of contour: 1=clockwise 0=counterclockwise")
|
||
|
|
||
|
def reverseContour(self):
|
||
|
"""reverse the contour"""
|
||
|
self._object.reverse()
|
||
|
|
||
|
# Index
|
||
|
|
||
|
def _get_index(self):
|
||
|
glyph = self.getParent()
|
||
|
if glyph is None:
|
||
|
return None
|
||
|
return glyph.naked().contourIndex(self.naked())
|
||
|
|
||
|
def _set_index(self, index):
|
||
|
glyph = self.getParent()
|
||
|
if glyph is None:
|
||
|
return
|
||
|
originalIndex = self.index
|
||
|
if originalIndex < index:
|
||
|
index -= 1
|
||
|
if index != originalIndex:
|
||
|
glyph.insertContour(index, glyph.naked()[originalIndex])
|
||
|
|
||
|
index = property(_get_index, _set_index, doc="index of the contour")
|
||
|
|
||
|
# Points
|
||
|
|
||
|
def pointClass(self):
|
||
|
return RPoint
|
||
|
|
||
|
def getPoint(self, point):
|
||
|
return point
|
||
|
|
||
|
def _get_points(self):
|
||
|
return [self.getPoint(point) for point in self._object]
|
||
|
|
||
|
points = property(_get_points, doc="view the contour as a list of points")
|
||
|
|
||
|
# Segments
|
||
|
|
||
|
def segmentClass(self):
|
||
|
return RSegment
|
||
|
|
||
|
def getSegment(self, points):
|
||
|
return self.segmentClass()(points)
|
||
|
|
||
|
def _get_segments(self):
|
||
|
return [self.getSegment(points) for points in self._object.segments]
|
||
|
|
||
|
segments = property(_get_segments)
|
||
|
|
||
|
def __len__(self):
|
||
|
return len(self._object.segments)
|
||
|
|
||
|
def __getitem__(self, index):
|
||
|
segments = self._object.segments
|
||
|
if index < len(segments):
|
||
|
segment = segments[index]
|
||
|
return self.getSegment(segment)
|
||
|
raise IndexError
|
||
|
|
||
|
def appendSegment(self, segmentType, points, smooth=False):
|
||
|
"""append a segment to the contour"""
|
||
|
return self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth)
|
||
|
|
||
|
def insertSegment(self, index, segmentType, points, smooth=False):
|
||
|
"""insert a segment into the contour"""
|
||
|
originalIndex = index
|
||
|
for point in points:
|
||
|
x = point.x
|
||
|
y = point.y
|
||
|
segmentType = point.segmentType
|
||
|
smooth = point.smooth
|
||
|
name = point.name
|
||
|
identifier = point.identifier
|
||
|
point = self._object.pointClass((x, y), segmentType=segmentType, smooth=smooth, name=name, identifier=identifier)
|
||
|
self._object.insertPoint(index, point)
|
||
|
index += 1
|
||
|
onCurve = self._object[index]
|
||
|
for segment in self._object.segments:
|
||
|
if segment[-1] == onCurve:
|
||
|
return self.getSegment(segment)
|
||
|
|
||
|
def removeSegment(self, index):
|
||
|
"""remove a segment from the contour"""
|
||
|
segment = self.segments[index]
|
||
|
for point in segment:
|
||
|
point = point.naked()
|
||
|
self._object.removePoint(point)
|
||
|
|
||
|
def setStartSegment(self, segmentIndex):
|
||
|
"""set the first segment on the contour"""
|
||
|
segment = self._object.segments[segmentIndex]
|
||
|
point = segment[-1].naked()
|
||
|
index = self._object.index(point)
|
||
|
self._object.setStartPoint(index)
|
||
|
|
||
|
|
||
|
# bPoints
|
||
|
|
||
|
# 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")
|
||
|
|
||
|
# Drawing
|
||
|
|
||
|
def draw(self, pen):
|
||
|
self._object.draw(pen)
|
||
|
|
||
|
def drawPoints(self, pointPen):
|
||
|
self._object.drawPoints(pointPen)
|
||
|
|
||
|
|
||
|
|
||
|
#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):
|
||
|
super(RFeatures, self).__init__()
|
||
|
self._object = featuresObject
|
||
|
|
||
|
def _get_text(self):
|
||
|
return self._object.text
|
||
|
|
||
|
def _set_text(self, value):
|
||
|
assert isinstance(value, (basestring, None))
|
||
|
self._object.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):
|
||
|
# __init__ hack:
|
||
|
# during __init__ BaseFetaures tries self._text = "".
|
||
|
# In these objects, the text must not be maintained independently
|
||
|
# of the defcon object, so hack around this when self._object
|
||
|
# is a dict (as is the case when starting up)
|
||
|
if self._object == {}:
|
||
|
return
|
||
|
self.text = value
|
||
|
|
||
|
_text = property(_get__text, _set__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
|
||
|
print "layers:", font.layers
|
||
|
print
|
||
|
print "info:", font.info
|
||
|
print
|
||
|
print "groups:", font.groups
|
||
|
print
|
||
|
print "kerning:", font.kerning
|
||
|
print
|
||
|
print "features:", font.features
|
||
|
print
|
||
|
print "lib:", font.lib
|
||
|
print
|
||
|
print "layer:", font.layers[None]
|
||
|
font.layers[None].color = ".5, 1, 0, .2"
|
||
|
print "layer.color:", font.layers[None].color
|
||
|
print "layer.lib:", font.layers[None].lib
|
||
|
print
|
||
|
print "glyph:", font.layers[None]["A"]
|
||
|
print font.layers[None]["A"].leftMargin
|