fonttools/Lib/robofab/objects/objectsDefcon.py

1806 lines
46 KiB
Python
Raw Normal View History

#! /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):
2015-09-27 15:14:28 +02:00
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"