1806 lines
46 KiB
Python
1806 lines
46 KiB
Python
#! /usr/local/bin/apppython
|
|
|
|
"""
|
|
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.
|
|
f -------> or defcon should have a copy() and setting all the subclasses correctly
|
|
|
|
- need to work out how selected will work in these objects
|
|
f -------> why its a bool, subclasses should overwrite it with a property thing if they wants to use
|
|
|
|
- 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)?
|
|
f -------> ignore it or can be converted to a color object
|
|
|
|
|
|
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
|
|
import weakref
|
|
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._object.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)
|
|
|
|
# -------
|
|
# Contour
|
|
# -------
|
|
|
|
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):
|
|
rPoint = self.pointClass()(point)
|
|
rPoint.getSegment = self.getSegment
|
|
rPoint._setDefconContour(self._object)
|
|
return rPoint
|
|
|
|
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):
|
|
segment = self.segmentClass()(points=points)
|
|
segment.getPoint = self.getPoint
|
|
segment._RContourClass = self.__class__
|
|
segment._setDefconContour(self._object)
|
|
return segment
|
|
|
|
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 bPointClass(self):
|
|
return RBPoint
|
|
|
|
def getBPoint(self, point):
|
|
bPoint = self.bPointClass()(point)
|
|
bPoint.getSegment = self.getSegment
|
|
bPoint.getPoint = self.getPoint
|
|
bPoint._RContourClass = self.__class__
|
|
bPoint._setDefconContour(self._object)
|
|
return bPoint
|
|
|
|
def _get_bPoints(self):
|
|
return [self.getBPoint(point) for point in self._object.onCurvePoints]
|
|
|
|
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)
|
|
|
|
# -------
|
|
# Segment
|
|
# -------
|
|
|
|
class RSegment(BaseSegment):
|
|
|
|
_title = "RoboFabSegment"
|
|
|
|
def __init__(self, segmentType=None, points=[], smooth=False):
|
|
super(RSegment, self).__init__()
|
|
self._points = points
|
|
self._RContourClass = None
|
|
|
|
def getParent(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return None
|
|
if self._RContourClass is None:
|
|
return None
|
|
return self._RContourClass(defconContour)
|
|
|
|
def _setDefconContour(self, defconContour):
|
|
self._defconContour = weakref.ref(defconContour)
|
|
|
|
def _defconContour(self):
|
|
return None
|
|
|
|
def _setDefconContourDirty(self):
|
|
contour = self._defconContour()
|
|
if contour is not None:
|
|
contour.dirty = True
|
|
|
|
def _get_index(self):
|
|
contour = self._defconContour()
|
|
if contour is None:
|
|
return None
|
|
segments = contour.segments
|
|
if self._points in segments:
|
|
return segments.index(self._points)
|
|
return None
|
|
|
|
index = property(_get_index, doc="index of the segment")
|
|
|
|
# Points
|
|
|
|
def _get_points(self):
|
|
return [self.getPoint(point) for point in self._points]
|
|
|
|
points = property(_get_points, doc="view the segment as a list of points")
|
|
|
|
# smooth
|
|
|
|
def _get_smooth(self):
|
|
return self._points[-1].smooth
|
|
|
|
def _set_smooth(self, value):
|
|
old = self._points[-1].smooth
|
|
if old == value:
|
|
return
|
|
self._points[-1].smooth = value
|
|
self._setDefconContourDirty()
|
|
|
|
smooth = property(_get_smooth, _set_smooth, doc="smoothness of the segment")
|
|
|
|
# type
|
|
|
|
def _get_type(self):
|
|
value = self._points[-1].segmentType
|
|
if value is None:
|
|
value = "offcurve"
|
|
return value
|
|
|
|
def _set_type(self, pointType):
|
|
if pointType == "offcurve":
|
|
pointType = None
|
|
onCurve = self._points[-1]
|
|
ocType = onCurve.segmentType
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return
|
|
if ocType == pointType:
|
|
return
|
|
onCurve.segmentType = pointType
|
|
# we are converting a cubic line, move into a cubic curve
|
|
if pointType == "curve" and ocType in ["line", "move"]:
|
|
# add offcurves
|
|
index = defconContour.index(onCurve)
|
|
prevOnCurve = defconContour[index - 1]
|
|
p1 = defconContour.pointClass((prevOnCurve.x, prevOnCurve.y), segmentType=None)
|
|
p2 = defconContour.pointClass((onCurve.x, onCurve.y), segmentType=None)
|
|
defconContour.insertPoint(index, p2)
|
|
defconContour.insertPoint(index, p1)
|
|
found = False
|
|
for points in defconContour.segments:
|
|
if points[-1] == onCurve:
|
|
found = True
|
|
break
|
|
if found:
|
|
self._points = points
|
|
# we are converting a quad curve to a cubic curve
|
|
elif pointType == "curve" and ocType == "qcurve":
|
|
# do nothing
|
|
pass
|
|
# we are converting a cubic curve or quad curve into a line
|
|
elif pointType == "line" and ocType in ["curve", "qcurve"]:
|
|
# remove offcurves
|
|
offCurves = self._points[:-1]
|
|
for point in offCurves:
|
|
defconContour.removePoint(point)
|
|
self._points = [onCurve]
|
|
# we are converting a cubic move to a line
|
|
elif pointType == "line" and ocType == "move":
|
|
# do nothing
|
|
pass
|
|
# we are converting to a quad curve where just about anything is legal
|
|
elif pointType == "qcurve":
|
|
# do nothing
|
|
pass
|
|
else:
|
|
raise RoboFabError, 'unknown segment type'
|
|
self._setDefconContourDirty()
|
|
|
|
type = property(_get_type, _set_type, doc="type of the segment")
|
|
|
|
def insertPoint(self, index, pointType, point):
|
|
raise NotImplementedError
|
|
|
|
def removePoint(self, index):
|
|
raise NotImplementedError
|
|
|
|
|
|
# ------
|
|
# bPoint
|
|
# ------
|
|
|
|
class RBPoint(BaseBPoint):
|
|
|
|
_title = "RoboFabBPoint"
|
|
|
|
def __init__(self, anchor=None):
|
|
super(RBPoint, self).__init__()
|
|
self._anchor = anchor
|
|
self._RContourClass = None
|
|
|
|
def _setDefconContour(self, contour):
|
|
self._defconContour = weakref.ref(contour)
|
|
|
|
def _defconContour(self):
|
|
return None
|
|
|
|
def _setDefconContourDirty(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is not None:
|
|
defconContour.dirty = True
|
|
|
|
def getParent(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return None
|
|
if self._RContourClass is None:
|
|
return None
|
|
return self._RContourClass(defconContour)
|
|
|
|
def _get_index(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return None
|
|
onCurvePoints = defconContour.onCurvePoints
|
|
if self._anchor in onCurvePoints:
|
|
return onCurvePoints.index(self._anchor)
|
|
return None
|
|
|
|
index = property(_get_index, doc="index of the segment")
|
|
|
|
def _setAnchorChanged(self, value):
|
|
pass
|
|
|
|
def _setNextChanged(self, value):
|
|
pass
|
|
|
|
def _get__parentSegment(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return None
|
|
found = False
|
|
for points in defconContour.segments:
|
|
if self._anchor == points[-1]:
|
|
found = True
|
|
break
|
|
if found:
|
|
return self.getSegment(points)
|
|
return None
|
|
|
|
_parentSegment = property(_get__parentSegment)
|
|
|
|
def _get__nextOnCurve(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return None
|
|
onCurvePoints = defconContour.onCurvePoints
|
|
index = onCurvePoints.index(self._anchor)
|
|
return self.getPoint(onCurvePoints[(index + 1) % len(onCurvePoints)])
|
|
|
|
_nextOnCurve = property(_get__nextOnCurve)
|
|
|
|
# -----
|
|
# Point
|
|
# -----
|
|
|
|
class RPoint(BasePoint):
|
|
|
|
_title = "RoboFabPoint"
|
|
|
|
def __init__(self, obj=None):
|
|
super(RPoint, self).__init__()
|
|
self._object = obj
|
|
|
|
def __repr__(self):
|
|
return "<RPoint for position: (%s, %s) type: %s >" % (self.x, self.y, self.type)
|
|
|
|
def _setDefconContour(self, contour):
|
|
self._defconContour = weakref.ref(contour)
|
|
|
|
def _defconContour(self):
|
|
return None
|
|
|
|
def _setDefconContourDirty(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is not None:
|
|
defconContour.dirty = True
|
|
|
|
def getParent(self):
|
|
defconContour = self._defconContour()
|
|
if defconContour is None:
|
|
return None
|
|
segments = defconContour.segments
|
|
found = False
|
|
for points in segments:
|
|
if self._object in points:
|
|
found = True
|
|
break
|
|
if found:
|
|
return self.getSegment(points)
|
|
return None
|
|
|
|
def _get_x(self):
|
|
return self._object.x
|
|
|
|
def _set_x(self, value):
|
|
old = self._object.x
|
|
if old == value:
|
|
return
|
|
self._object.x = value
|
|
self._setDefconContourDirty()
|
|
|
|
x = property(_get_x, _set_x, doc="x attribute for point")
|
|
|
|
def _get_y(self):
|
|
return self._object.y
|
|
|
|
def _set_y(self, value):
|
|
old = self._object.y
|
|
if old == value:
|
|
return
|
|
self._object.y = value
|
|
self._setDefconContourDirty()
|
|
|
|
y = property(_get_y, _set_y, doc="y attribute for point")
|
|
|
|
def _get_smooth(self):
|
|
return self._points[-1].smooth
|
|
|
|
def _set_smooth(self, value):
|
|
old = self._object.smooth
|
|
if old == value:
|
|
return
|
|
self._object.smooth = value
|
|
self._setDefconContourDirty()
|
|
|
|
smooth = property(_get_smooth, _set_smooth, doc="smoothness of the segment")
|
|
|
|
def _get_type(self):
|
|
value = self._object.segmentType
|
|
if value is None:
|
|
value = "offcurve"
|
|
return value
|
|
|
|
def _set_type(self, value):
|
|
if value == "offcurve":
|
|
value = None
|
|
old = self._object.segmentType
|
|
if old == value:
|
|
return
|
|
self._object.segmentType = value
|
|
self._setDefconContourDirty()
|
|
|
|
type = property(_get_type, _set_type, doc="type of the segment")
|
|
|
|
def _get_name(self):
|
|
return self._object.name
|
|
|
|
def _set_name(self, value):
|
|
old = self._object.name
|
|
if old == value:
|
|
return
|
|
self._object.name = value
|
|
self._setDefconContourDirty()
|
|
|
|
name = property(_get_name, _set_name, doc="name attribute for point")
|
|
|
|
|
|
# ------
|
|
# Anchor
|
|
# ------
|
|
|
|
class RAnchor(BaseAnchor):
|
|
|
|
_title = "RoboFabAnchor"
|
|
|
|
def __init__(self, obj):
|
|
super(RAnchor, self).__init__()
|
|
self._object = obj
|
|
|
|
def _get_index(self):
|
|
glyph = self._object.glyph
|
|
if glyph is None:
|
|
return None
|
|
return glyph.anchorIndex(self._object)
|
|
|
|
index = property(_get_index, doc="index of the anchor")
|
|
|
|
def _get_position(self):
|
|
return (self._object.x, self._object.y)
|
|
|
|
def _set_position(self, value):
|
|
self._object.x = value[0]
|
|
self._object.y = value[1]
|
|
|
|
position = property(_get_position, _set_position, doc="position of the anchor")
|
|
|
|
def _get_name(self):
|
|
return self._object.name
|
|
|
|
def _set_name(self, value):
|
|
self._object.name = value
|
|
|
|
name = property(_get_name, _set_name, doc="name of the anchor")
|
|
|
|
|
|
# ---------
|
|
# Component
|
|
# ---------
|
|
|
|
class RComponent(BaseComponent):
|
|
|
|
_title = "RoboFabComponent"
|
|
|
|
def __init__(self, obj=None):
|
|
super(RComponent, self).__init__()
|
|
self._object = obj
|
|
|
|
def _get_index(self):
|
|
glyph = self._object.glyph
|
|
if glyph is None:
|
|
return None
|
|
return glyph.componentIndex(self._object)
|
|
|
|
index = property(_get_index, doc="index of the component")
|
|
|
|
def _get_baseGlyph(self):
|
|
return self._object.baseGlyph
|
|
|
|
def _set_baseGlyph(self, glyphName):
|
|
self._object.baseGlyph = glyphName
|
|
|
|
baseGlyph = property(_get_baseGlyph, _set_baseGlyph, doc="")
|
|
|
|
def _get_offset(self):
|
|
xScale, xyScale, yxScale, yScale, xOffset, yOffset = self._object.transformation
|
|
return xOffset, yOffset
|
|
|
|
def _set_offset(self, (x, y)):
|
|
xScale, xyScale, yxScale, yScale, xOffset, yOffset = self._object.transformation
|
|
self._object.transformation = (xScale, xyScale, yxScale, yScale, x, y)
|
|
|
|
offset = property(_get_offset, _set_offset, doc="the offset of the component")
|
|
|
|
def _get_scale(self):
|
|
xScale, xyScale, yxScale, yScale, xOffset, yOffset = self._object.transformation
|
|
return xScale, yScale
|
|
|
|
def _set_scale(self, (x, y)):
|
|
xScale, xyScale, yxScale, yScale, xOffset, yOffset = self._object.transformation
|
|
self._object.transformation = (x, xyScale, yxScale, y, xOffset, yOffset)
|
|
|
|
scale = property(_get_scale, _set_scale, doc="the scale of the component")
|
|
|
|
def move(self, (x, y)):
|
|
"""Move the component"""
|
|
self._object.move((x, y))
|
|
|
|
def decompose(self):
|
|
"""Decompose the component"""
|
|
glyph = self._object.glyph
|
|
if glyph is None:
|
|
return
|
|
glyph.decomposeComponent(self._object)
|
|
|
|
|
|
# ----
|
|
# 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 "glyph.leftMargin:", font.layers[None]["A"].leftMargin
|
|
print "contours:", list(font.layers[None]["A"])
|
|
print "contour:", font.layers[None]["A"][0]
|
|
print "points:", font.layers[None]["A"][0].points
|
|
print "segments:", font.layers[None]["A"][0].segments
|
|
|
|
for seg in font.layers[None]["A"][0].segments:
|
|
seg.round()
|
|
|
|
print "bPoints:", font.layers[None]["A"][0].bPoints
|
|
print
|
|
bPoints = font.layers[None]["A"][0].bPoints
|
|
for bPoint in bPoints:
|
|
print "bPoint:", bPoint
|
|
print "bPoint.anchor:", bPoint.anchor
|
|
print "bPoint.bcpIn:", bPoint.bcpIn
|
|
bPoint.bcpIn = (10, 10)
|
|
print bPoint.bcpIn
|
|
print "bPoint.bcpOut:", bPoint.bcpOut
|
|
print
|
|
for point in font.layers[None]["A"][0].points:
|
|
print "point", point
|
|
print "point.getParent():", point.getParent()
|
|
|
|
|
|
print
|
|
print "anchors:", font.layers[None]["A"].anchors
|
|
print "components for 'C':", font.layers[None]["C"].components
|
|
print "decompose..."
|
|
for component in font.layers[None]["C"].components:
|
|
component.decompose()
|
|
print "components for 'C':", font.layers[None]["C"].components
|
|
### endless loop
|
|
#print font.layers[None]["A"].appendComponent("C")
|
|
|
|
print "done"
|