2008-01-07 17:40:34 +00:00
|
|
|
"""UFO for GlifLib"""
|
|
|
|
|
|
|
|
from robofab import RoboFabError, RoboFabWarning
|
2009-02-28 15:47:24 +00:00
|
|
|
from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\
|
2008-01-07 17:40:34 +00:00
|
|
|
BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \
|
|
|
|
relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\
|
2008-01-08 20:23:26 +00:00
|
|
|
_interpolate, _interpolatePt, roundPt, addPt,\
|
2008-02-21 17:42:34 +00:00
|
|
|
MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
|
2008-02-25 11:28:46 +00:00
|
|
|
BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues
|
2008-01-07 17:40:34 +00:00
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
|
|
__all__ = [ "CurrentFont",
|
|
|
|
"CurrentGlyph", 'OpenFont',
|
|
|
|
'RFont', 'RGlyph', 'RContour',
|
|
|
|
'RPoint', 'RBPoint', 'RAnchor',
|
|
|
|
'RComponent'
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def CurrentFont():
|
|
|
|
return None
|
|
|
|
|
|
|
|
def CurrentGlyph():
|
|
|
|
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."""
|
|
|
|
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"""
|
|
|
|
new = RFont()
|
2009-02-28 15:47:24 +00:00
|
|
|
if familyName is not None:
|
|
|
|
new.info.familyName = familyName
|
|
|
|
if styleName is not None:
|
|
|
|
new.info.styleName = styleName
|
2008-01-07 17:40:34 +00:00
|
|
|
return new
|
2008-01-08 19:14:51 +00:00
|
|
|
|
|
|
|
def AllFonts():
|
|
|
|
"""AllFonts can't work in plain python usage. It's really up to some sort of application
|
|
|
|
to keep track of which fonts are open."""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2008-01-07 17:40:34 +00:00
|
|
|
|
2008-02-21 17:42:34 +00:00
|
|
|
class PostScriptFontHintValues(BasePostScriptFontHintValues):
|
2008-02-25 11:28:46 +00:00
|
|
|
""" Font level PostScript hints object for objectsRF usage.
|
2008-02-21 17:42:34 +00:00
|
|
|
If there are values in the lib, use those.
|
|
|
|
If there are no values in the lib, use defaults.
|
2008-02-25 11:28:46 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2008-02-21 17:42:34 +00:00
|
|
|
"""
|
|
|
|
|
2008-02-23 19:40:49 +00:00
|
|
|
def __init__(self, aFont=None, data=None):
|
2009-02-28 15:47:24 +00:00
|
|
|
self.setParent(aFont)
|
2008-02-21 17:42:34 +00:00
|
|
|
BasePostScriptFontHintValues.__init__(self)
|
|
|
|
if aFont is not None:
|
2009-02-28 15:47:24 +00:00
|
|
|
# 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.
|
2008-02-25 12:55:29 +00:00
|
|
|
libData = aFont.lib.get(postScriptHintDataLibKey)
|
2008-02-25 13:22:11 +00:00
|
|
|
if libData is not None:
|
|
|
|
self.fromDict(libData)
|
2009-02-28 15:47:24 +00:00
|
|
|
del libData[postScriptHintDataLibKey]
|
2008-02-23 13:17:21 +00:00
|
|
|
if data is not None:
|
|
|
|
self.fromDict(data)
|
2008-02-21 17:42:34 +00:00
|
|
|
|
|
|
|
def getPostScriptHintDataFromLib(aFont, fontLib):
|
|
|
|
hintData = fontLib.get(postScriptHintDataLibKey)
|
|
|
|
psh = PostScriptFontHintValues(aFont)
|
|
|
|
psh.fromDict(hintData)
|
|
|
|
return psh
|
2008-02-25 11:28:46 +00:00
|
|
|
|
|
|
|
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)
|
2008-02-25 12:55:29 +00:00
|
|
|
if data is not None:
|
|
|
|
self.fromDict(data)
|
2008-02-25 11:28:46 +00:00
|
|
|
|
|
|
|
|
2008-01-07 17:40:34 +00:00
|
|
|
class RFont(BaseFont):
|
|
|
|
"""UFO font object which reads and writes glif, and keeps the data in memory in between.
|
|
|
|
Bahviour:
|
|
|
|
- comparable to Font
|
|
|
|
- comparable to GlyphSet so that it can be passed to Glif widgets
|
|
|
|
"""
|
|
|
|
|
|
|
|
_title = "RoboFabFont"
|
|
|
|
|
|
|
|
def __init__(self, path=None):
|
|
|
|
BaseFont.__init__(self)
|
|
|
|
if path is not None:
|
|
|
|
self._path = os.path.normpath(os.path.abspath(path))
|
|
|
|
else:
|
|
|
|
self._path = None
|
|
|
|
self._object = {}
|
|
|
|
|
|
|
|
self._glyphSet = None
|
|
|
|
self._scheduledForDeletion = [] # this is a place for storing glyphs that need to be removed when the font is saved
|
|
|
|
|
|
|
|
self.kerning = RKerning()
|
|
|
|
self.kerning.setParent(self)
|
|
|
|
self.info = RInfo()
|
|
|
|
self.info.setParent(self)
|
2009-02-28 15:47:24 +00:00
|
|
|
self.features = RFeatures()
|
|
|
|
self.features.setParent(self)
|
2008-01-07 17:40:34 +00:00
|
|
|
self.groups = RGroups()
|
|
|
|
self.groups.setParent(self)
|
|
|
|
self.lib = RLib()
|
|
|
|
self.lib.setParent(self)
|
|
|
|
if path:
|
|
|
|
self._loadData(path)
|
2009-02-28 15:47:24 +00:00
|
|
|
else:
|
|
|
|
self.psHints = PostScriptFontHintValues(self)
|
|
|
|
self.psHints.setParent(self)
|
2008-01-07 17:40:34 +00:00
|
|
|
|
|
|
|
def __setitem__(self, glyphName, glyph):
|
|
|
|
"""Set a glyph at key."""
|
|
|
|
self._object[glyphName] = glyph
|
|
|
|
|
|
|
|
def __cmp__(self, other):
|
|
|
|
"""Compare this font with another, compare if they refer to the same file."""
|
|
|
|
if not hasattr(other, '_path'):
|
|
|
|
return -1
|
|
|
|
if self._object._path == other._object._path and self._object._path is not None:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return -1
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
if self._glyphSet is None:
|
|
|
|
return 0
|
|
|
|
return len(self._glyphSet)
|
|
|
|
|
|
|
|
def _loadData(self, path):
|
|
|
|
from robofab.ufoLib import UFOReader
|
2009-02-28 15:47:24 +00:00
|
|
|
reader = UFOReader(path)
|
|
|
|
fontLib = reader.readLib()
|
|
|
|
# info
|
|
|
|
reader.readInfo(self.info)
|
|
|
|
# kerning
|
|
|
|
self.kerning.update(reader.readKerning())
|
2008-01-07 17:40:34 +00:00
|
|
|
self.kerning.setChanged(False)
|
2009-02-28 15:47:24 +00:00
|
|
|
# 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
|
2008-02-21 17:42:34 +00:00
|
|
|
self.psHints = PostScriptFontHintValues(self)
|
2009-02-28 15:47:24 +00:00
|
|
|
if postScriptHintDataLibKey in fontLib:
|
|
|
|
del fontLib[postScriptHintDataLibKey]
|
|
|
|
# lib
|
|
|
|
self.lib.update(fontLib)
|
|
|
|
# glyphs
|
|
|
|
self._glyphSet = reader.getGlyphSet()
|
2008-01-07 17:40:34 +00:00
|
|
|
self._hasNotChanged(doGlyphs=False)
|
2009-02-28 15:47:24 +00:00
|
|
|
|
2008-01-07 17:40:34 +00:00
|
|
|
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)
|
2008-02-25 11:28:46 +00:00
|
|
|
g.psHints._loadFromLib(g.lib)
|
2008-01-07 17:40:34 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2009-02-28 15:47:24 +00:00
|
|
|
def save(self, destDir=None, doProgress=False, formatVersion=2):
|
2008-01-07 17:40:34 +00:00
|
|
|
"""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 robofab.ufoLib import UFOWriter
|
2009-02-28 15:47:24 +00:00
|
|
|
from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab
|
2008-01-07 17:40:34 +00:00
|
|
|
# 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
|
2009-02-28 15:47:24 +00:00
|
|
|
# start a progress bar
|
2008-01-07 17:40:34 +00:00
|
|
|
nonGlyphCount = 5
|
|
|
|
bar = None
|
|
|
|
if doProgress:
|
|
|
|
from robofab.interface.all.dialogs import ProgressBar
|
2009-02-28 15:47:24 +00:00
|
|
|
bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys()))
|
|
|
|
# write
|
|
|
|
writer = UFOWriter(destDir, formatVersion=formatVersion)
|
2008-01-07 17:40:34 +00:00
|
|
|
try:
|
2009-02-28 15:47:24 +00:00
|
|
|
# make a shallow copy of the lib. stuff may be added to it.
|
|
|
|
fontLib = dict(self.lib)
|
|
|
|
# info
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
2009-02-28 15:47:24 +00:00
|
|
|
bar.label("Saving info...")
|
|
|
|
writer.writeInfo(self.info)
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
|
|
|
bar.tick()
|
2009-02-28 15:47:24 +00:00
|
|
|
# kerning
|
2008-02-21 16:56:53 +00:00
|
|
|
if self.kerning.changed or saveAs:
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
2009-02-28 15:47:24 +00:00
|
|
|
bar.label("Saving kerning...")
|
|
|
|
writer.writeKerning(self.kerning.asDict())
|
|
|
|
if bar:
|
|
|
|
bar.tick()
|
|
|
|
# groups
|
|
|
|
if bar:
|
|
|
|
bar.label("Saving groups...")
|
|
|
|
writer.writeGroups(self.groups)
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
|
|
|
bar.tick()
|
2009-02-28 15:47:24 +00:00
|
|
|
# features
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
2009-02-28 15:47:24 +00:00
|
|
|
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]
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
|
|
|
bar.tick()
|
2009-02-28 15:47:24 +00:00
|
|
|
# lib
|
|
|
|
if formatVersion == 1:
|
|
|
|
fontLib[postScriptHintDataLibKey] = self.psHints.asDict()
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
2009-02-28 15:47:24 +00:00
|
|
|
bar.label("Saving lib...")
|
|
|
|
writer.writeLib(fontLib)
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
|
|
|
bar.tick()
|
2009-02-28 15:47:24 +00:00
|
|
|
# glyphs
|
2008-01-07 17:40:34 +00:00
|
|
|
glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc()
|
2009-02-28 15:47:24 +00:00
|
|
|
|
|
|
|
glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc)
|
2008-01-07 17:40:34 +00:00
|
|
|
if len(self._scheduledForDeletion) != 0:
|
|
|
|
if bar:
|
2009-02-28 15:47:24 +00:00
|
|
|
bar.label("Removing deleted glyphs...")
|
2008-01-07 17:40:34 +00:00
|
|
|
for glyphName in self._scheduledForDeletion:
|
|
|
|
if glyphSet.has_key(glyphName):
|
|
|
|
glyphSet.deleteGlyph(glyphName)
|
|
|
|
if bar:
|
|
|
|
bar.tick()
|
|
|
|
if bar:
|
2009-02-28 15:47:24 +00:00
|
|
|
bar.label("Saving glyphs...")
|
2008-01-07 17:40:34 +00:00
|
|
|
count = nonGlyphCount
|
|
|
|
if saveAs:
|
|
|
|
glyphNames = self.keys()
|
|
|
|
else:
|
|
|
|
glyphNames = self._object.keys()
|
|
|
|
for glyphName in glyphNames:
|
|
|
|
glyph = self[glyphName]
|
2008-02-25 11:28:46 +00:00
|
|
|
glyph.psHints._saveToLib(glyph.lib)
|
2008-01-07 17:40:34 +00:00
|
|
|
glyph._saveToGlyphSet(glyphSet, glyphName=glyphName, force=saveAs)
|
|
|
|
if bar and not count % 10:
|
|
|
|
bar.tick(count)
|
|
|
|
count = count + 1
|
|
|
|
glyphSet.writeContents()
|
|
|
|
self._glyphSet = glyphSet
|
2009-02-28 15:47:24 +00:00
|
|
|
# only blindly stop if the user says to
|
2008-01-07 17:40:34 +00:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
bar.close()
|
|
|
|
bar = None
|
2009-02-28 15:47:24 +00:00
|
|
|
# kill the progress bar
|
2008-01-07 17:40:34 +00:00
|
|
|
if bar:
|
|
|
|
bar.close()
|
2009-02-28 15:47:24 +00:00
|
|
|
# reset internal stuff
|
2008-01-07 17:40:34 +00:00
|
|
|
self._path = destDir
|
|
|
|
self._scheduledForDeletion = []
|
|
|
|
self.setChanged(False)
|
2009-02-28 15:47:24 +00:00
|
|
|
|
2008-01-07 17:40:34 +00:00
|
|
|
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()
|
2009-03-26 13:29:09 +00:00
|
|
|
w = self.info.postscriptDefaultWidthX
|
|
|
|
if w is None:
|
|
|
|
w = 0
|
|
|
|
g.width = w
|
2008-01-07 17:40:34 +00:00
|
|
|
return g
|
|
|
|
g = RGlyph()
|
|
|
|
g.setParent(self)
|
|
|
|
g.name = glyphName
|
2009-03-26 13:29:09 +00:00
|
|
|
w = self.info.postscriptDefaultWidthX
|
|
|
|
if w is None:
|
|
|
|
w = 0
|
|
|
|
g.width = w
|
2008-01-07 17:40:34 +00:00
|
|
|
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)
|
|
|
|
|
2009-09-03 04:09:29 +00:00
|
|
|
def insertGlyph(self, glyph, name=None):
|
2008-01-07 17:40:34 +00:00
|
|
|
"""returns a new glyph that has been inserted into the font"""
|
2009-09-03 04:09:29 +00:00
|
|
|
if name is None:
|
2008-01-07 17:40:34 +00:00
|
|
|
name = glyph.name
|
|
|
|
glyph = glyph.copy()
|
|
|
|
glyph.name = name
|
|
|
|
glyph.setParent(self)
|
|
|
|
glyph._hasChanged()
|
|
|
|
self._object[name] = glyph
|
|
|
|
# is the user adding a glyph that has the same
|
|
|
|
# name as one that was deleted earlier?
|
|
|
|
if name in self._scheduledForDeletion:
|
|
|
|
self._scheduledForDeletion.remove(name)
|
|
|
|
return self.getGlyph(name)
|
|
|
|
|
|
|
|
def removeGlyph(self, glyphName):
|
|
|
|
"""remove a glyph from the font"""
|
|
|
|
# XXX! Potential issue with removing glyphs.
|
|
|
|
# if a glyph is removed from a font, but it is still referenced
|
|
|
|
# by a component, it will give pens some trouble.
|
|
|
|
# where does the resposibility for catching this fall?
|
|
|
|
# the removeGlyph method? the addComponent method
|
|
|
|
# of the various pens? somewhere else? hm... tricky.
|
|
|
|
#
|
|
|
|
#we won't actually remove it, we will just store it for removal
|
|
|
|
# but only if the glyph does exist
|
|
|
|
if self.has_key(glyphName) and glyphName not in self._scheduledForDeletion:
|
|
|
|
self._scheduledForDeletion.append(glyphName)
|
|
|
|
# now delete the object
|
|
|
|
if self._object.has_key(glyphName):
|
|
|
|
del self._object[glyphName]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def getGlyph(self, glyphName):
|
|
|
|
# XXX getGlyph may have to become private, to avoid duplication
|
|
|
|
# with __getitem__
|
|
|
|
n = None
|
|
|
|
if self._object.has_key(glyphName):
|
|
|
|
# have we served this glyph before? it should be in _object
|
|
|
|
n = self._object[glyphName]
|
|
|
|
else:
|
|
|
|
# haven't served it before, is it in the glyphSet then?
|
|
|
|
if self._glyphSet is not None and glyphName in self._glyphSet:
|
|
|
|
# yes, read the .glif file from disk
|
|
|
|
n = self._loadGlyph(glyphName)
|
|
|
|
if n is None:
|
|
|
|
raise KeyError, glyphName
|
|
|
|
return n
|
|
|
|
|
|
|
|
|
|
|
|
class RGlyph(BaseGlyph):
|
|
|
|
|
|
|
|
_title = "RGlyph"
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
BaseGlyph.__init__(self)
|
|
|
|
self.contours = []
|
|
|
|
self.components = []
|
|
|
|
self.anchors = []
|
|
|
|
self._unicodes = []
|
|
|
|
self.width = 0
|
|
|
|
self.note = None
|
|
|
|
self._name = "Unnamed Glyph"
|
|
|
|
self.selected = False
|
|
|
|
self._properties = None
|
|
|
|
self._lib = RLib()
|
|
|
|
self._lib.setParent(self)
|
2008-02-25 11:28:46 +00:00
|
|
|
self.psHints = PostScriptGlyphHintValues()
|
|
|
|
self.psHints.setParent(self)
|
2008-01-07 17:40:34 +00:00
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.contours)
|
|
|
|
|
|
|
|
def __getitem__(self, index):
|
|
|
|
if index < len(self.contours):
|
|
|
|
return self.contours[index]
|
|
|
|
raise IndexError
|
|
|
|
|
|
|
|
def _hasNotChanged(self):
|
|
|
|
for contour in self.contours:
|
|
|
|
contour.setChanged(False)
|
|
|
|
for segment in contour.segments:
|
|
|
|
segment.setChanged(False)
|
|
|
|
for point in segment.points:
|
|
|
|
point.setChanged(False)
|
|
|
|
for component in self.components:
|
|
|
|
component.setChanged(False)
|
|
|
|
for anchor in self.anchors:
|
|
|
|
anchor.setChanged(False)
|
|
|
|
self.setChanged(False)
|
|
|
|
|
|
|
|
#
|
|
|
|
# attributes
|
|
|
|
#
|
|
|
|
|
|
|
|
def _get_lib(self):
|
|
|
|
return self._lib
|
|
|
|
|
|
|
|
def _set_lib(self, obj):
|
|
|
|
self._lib.clear()
|
|
|
|
self._lib.update(obj)
|
|
|
|
|
|
|
|
lib = property(_get_lib, _set_lib)
|
|
|
|
|
|
|
|
def _get_name(self):
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
def _set_name(self, value):
|
|
|
|
prevName = self._name
|
|
|
|
newName = value
|
|
|
|
if newName == prevName:
|
|
|
|
return
|
|
|
|
self._name = newName
|
|
|
|
self.setChanged(True)
|
|
|
|
font = self.getParent()
|
|
|
|
if font is not None:
|
|
|
|
# but, this glyph could be linked to a
|
|
|
|
# FontLab font, because objectsFL.RGlyph.copy()
|
|
|
|
# creates an objectsRF.RGlyph with the parent
|
|
|
|
# set to an objectsFL.RFont object. so, check to see
|
|
|
|
# if this is a legitimate RFont before trying to
|
|
|
|
# do the objectsRF.RFont glyph name change
|
|
|
|
if isinstance(font, RFont):
|
|
|
|
font._object[newName] = self
|
|
|
|
# is the user changing a glyph's name to the
|
|
|
|
# name of a glyph that was deleted earlier?
|
|
|
|
if newName in font._scheduledForDeletion:
|
|
|
|
font._scheduledForDeletion.remove(newName)
|
|
|
|
font.removeGlyph(prevName)
|
|
|
|
|
|
|
|
name = property(_get_name, _set_name)
|
|
|
|
|
|
|
|
def _get_unicodes(self):
|
|
|
|
return self._unicodes
|
|
|
|
|
|
|
|
def _set_unicodes(self, value):
|
|
|
|
if not isinstance(value, list):
|
|
|
|
raise RoboFabError, "unicodes must be a list"
|
|
|
|
self._unicodes = value
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph")
|
|
|
|
|
|
|
|
def _get_unicode(self):
|
|
|
|
if len(self._unicodes) == 0:
|
|
|
|
return None
|
|
|
|
return self._unicodes[0]
|
|
|
|
|
|
|
|
def _set_unicode(self, value):
|
|
|
|
uni = self._unicodes
|
|
|
|
if value is not None:
|
|
|
|
if value not in uni:
|
|
|
|
self.unicodes.insert(0, value)
|
|
|
|
elif uni.index(value) != 0:
|
|
|
|
uni.insert(0, uni.pop(uni.index(value)))
|
|
|
|
self.unicodes = uni
|
|
|
|
|
|
|
|
unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph")
|
|
|
|
|
|
|
|
def getPointPen(self):
|
|
|
|
from robofab.pens.rfUFOPen import RFUFOPointPen
|
|
|
|
return RFUFOPointPen(self)
|
|
|
|
|
|
|
|
def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)):
|
|
|
|
"""append a component to the glyph"""
|
|
|
|
new = RComponent(baseGlyph, offset, scale)
|
|
|
|
new.setParent(self)
|
|
|
|
self.components.append(new)
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def appendAnchor(self, name, position, mark=None):
|
|
|
|
"""append an anchor to the glyph"""
|
|
|
|
new = RAnchor(name, position, mark)
|
|
|
|
new.setParent(self)
|
|
|
|
self.anchors.append(new)
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def removeContour(self, index):
|
|
|
|
"""remove a specific contour from the glyph"""
|
|
|
|
del self.contours[index]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def removeAnchor(self, anchor):
|
|
|
|
"""remove a specific anchor from the glyph"""
|
|
|
|
del self.anchors[anchor.index]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def removeComponent(self, component):
|
|
|
|
"""remove a specific component from the glyph"""
|
|
|
|
del self.components[component.index]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def center(self, padding=None):
|
|
|
|
"""Equalise sidebearings, set to padding if wanted."""
|
|
|
|
left = self.leftMargin
|
|
|
|
right = self.rightMargin
|
|
|
|
if padding:
|
|
|
|
e_left = e_right = padding
|
|
|
|
else:
|
|
|
|
e_left = (left + right)/2
|
|
|
|
e_right = (left + right) - e_left
|
|
|
|
self.leftMargin = e_left
|
|
|
|
self.rightMargin = e_right
|
|
|
|
|
|
|
|
def decompose(self):
|
|
|
|
"""Decompose all components"""
|
|
|
|
for i in range(len(self.components)):
|
|
|
|
self.components[-1].decompose()
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def clear(self, contours=True, components=True, anchors=True, guides=True):
|
|
|
|
"""Clear all items marked as True from the glyph"""
|
|
|
|
if contours:
|
|
|
|
self.clearContours()
|
|
|
|
if components:
|
|
|
|
self.clearComponents()
|
|
|
|
if anchors:
|
|
|
|
self.clearAnchors()
|
|
|
|
if guides:
|
|
|
|
self.clearHGuides()
|
|
|
|
self.clearVGuides()
|
|
|
|
|
|
|
|
def clearContours(self):
|
|
|
|
"""clear all contours"""
|
|
|
|
self.contours = []
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def clearComponents(self):
|
|
|
|
"""clear all components"""
|
|
|
|
self.components = []
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def clearAnchors(self):
|
|
|
|
"""clear all anchors"""
|
|
|
|
self.anchors = []
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def clearHGuides(self):
|
|
|
|
"""clear all horizontal guides"""
|
|
|
|
self.hGuides = []
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def clearVGuides(self):
|
|
|
|
"""clear all vertical guides"""
|
|
|
|
self.vGuides = []
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def getAnchors(self):
|
|
|
|
return self.anchors
|
|
|
|
|
|
|
|
def getComponents(self):
|
|
|
|
return self.components
|
|
|
|
|
|
|
|
#
|
|
|
|
# stuff related to Glyph Properties
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RContour(BaseContour):
|
|
|
|
|
|
|
|
_title = "RoboFabContour"
|
|
|
|
|
|
|
|
def __init__(self, object=None):
|
|
|
|
#BaseContour.__init__(self)
|
|
|
|
self.segments = []
|
|
|
|
self.selected = False
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.segments)
|
|
|
|
|
|
|
|
def __getitem__(self, index):
|
|
|
|
if index < len(self.segments):
|
|
|
|
return self.segments[index]
|
|
|
|
raise IndexError
|
|
|
|
|
|
|
|
def _get_index(self):
|
|
|
|
return self.getParent().contours.index(self)
|
|
|
|
|
|
|
|
def _set_index(self, index):
|
|
|
|
ogIndex = self.index
|
|
|
|
if index != ogIndex:
|
|
|
|
contourList = self.getParent().contours
|
|
|
|
contourList.insert(index, contourList.pop(ogIndex))
|
|
|
|
|
|
|
|
|
|
|
|
index = property(_get_index, _set_index, doc="index of the contour")
|
|
|
|
|
|
|
|
def _get_points(self):
|
|
|
|
points = []
|
|
|
|
for segment in self.segments:
|
|
|
|
for point in segment.points:
|
|
|
|
points.append(point)
|
|
|
|
return points
|
|
|
|
|
|
|
|
points = property(_get_points, doc="view the contour as a list of points")
|
|
|
|
|
|
|
|
def _get_bPoints(self):
|
|
|
|
bPoints = []
|
|
|
|
for segment in self.segments:
|
|
|
|
segType = segment.type
|
|
|
|
if segType == MOVE:
|
|
|
|
bType = CORNER
|
|
|
|
elif segType == LINE:
|
|
|
|
bType = CORNER
|
|
|
|
elif segType == CURVE:
|
|
|
|
if segment.smooth:
|
|
|
|
bType = CURVE
|
|
|
|
else:
|
|
|
|
bType = CORNER
|
|
|
|
else:
|
|
|
|
raise RoboFabError, "encountered unknown segment type"
|
|
|
|
b = RBPoint()
|
|
|
|
b.setParent(segment)
|
|
|
|
bPoints.append(b)
|
|
|
|
return bPoints
|
|
|
|
|
|
|
|
bPoints = property(_get_bPoints, doc="view the contour as a list of bPoints")
|
|
|
|
|
|
|
|
def appendSegment(self, segmentType, points, smooth=False):
|
|
|
|
"""append a segment to the contour"""
|
|
|
|
segment = self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth)
|
|
|
|
return segment
|
|
|
|
|
|
|
|
def insertSegment(self, index, segmentType, points, smooth=False):
|
|
|
|
"""insert a segment into the contour"""
|
|
|
|
segment = RSegment(segmentType, points, smooth)
|
|
|
|
segment.setParent(self)
|
|
|
|
self.segments.insert(index, segment)
|
|
|
|
self._hasChanged()
|
|
|
|
return segment
|
|
|
|
|
|
|
|
def removeSegment(self, index):
|
|
|
|
"""remove a segment from the contour"""
|
|
|
|
del self.segments[index]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def reverseContour(self):
|
|
|
|
"""reverse the contour"""
|
|
|
|
from robofab.pens.reverseContourPointPen import ReverseContourPointPen
|
|
|
|
index = self.index
|
|
|
|
glyph = self.getParent()
|
|
|
|
pen = glyph.getPointPen()
|
|
|
|
reversePen = ReverseContourPointPen(pen)
|
|
|
|
self.drawPoints(reversePen)
|
|
|
|
# we've drawn the reversed contour onto our parent glyph,
|
|
|
|
# so it sits at the end of the contours list:
|
|
|
|
newContour = glyph.contours.pop(-1)
|
|
|
|
for segment in newContour.segments:
|
|
|
|
segment.setParent(self)
|
|
|
|
self.segments = newContour.segments
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def setStartSegment(self, segmentIndex):
|
|
|
|
"""set the first segment on the contour"""
|
|
|
|
# this obviously does not support open contours
|
|
|
|
if len(self.segments) < 2:
|
|
|
|
return
|
|
|
|
if segmentIndex == 0:
|
|
|
|
return
|
|
|
|
if segmentIndex > len(self.segments)-1:
|
|
|
|
raise IndexError, 'segment index not in segments list'
|
|
|
|
oldStart = self.segments[0]
|
|
|
|
oldLast = self.segments[-1]
|
|
|
|
#check to see if the contour ended with a curve on top of the move
|
|
|
|
#if we find one delete it,
|
|
|
|
if oldLast.type == CURVE or oldLast.type == QCURVE:
|
|
|
|
startOn = oldStart.onCurve
|
|
|
|
lastOn = oldLast.onCurve
|
|
|
|
if startOn.x == lastOn.x and startOn.y == lastOn.y:
|
|
|
|
del self.segments[0]
|
|
|
|
# since we deleted the first contour, the segmentIndex needs to shift
|
|
|
|
segmentIndex = segmentIndex - 1
|
|
|
|
# if we DO have a move left over, we need to convert it to a line
|
|
|
|
if self.segments[0].type == MOVE:
|
|
|
|
self.segments[0].type = LINE
|
|
|
|
# slice up the segments and reassign them to the contour
|
|
|
|
segments = self.segments[segmentIndex:]
|
|
|
|
self.segments = segments + self.segments[:segmentIndex]
|
|
|
|
# now, draw the contour onto the parent glyph
|
|
|
|
glyph = self.getParent()
|
|
|
|
pen = glyph.getPointPen()
|
|
|
|
self.drawPoints(pen)
|
|
|
|
# we've drawn the new contour onto our parent glyph,
|
|
|
|
# so it sits at the end of the contours list:
|
|
|
|
newContour = glyph.contours.pop(-1)
|
|
|
|
for segment in newContour.segments:
|
|
|
|
segment.setParent(self)
|
|
|
|
self.segments = newContour.segments
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
|
|
|
|
class RSegment(BaseSegment):
|
|
|
|
|
|
|
|
_title = "RoboFabSegment"
|
|
|
|
|
|
|
|
def __init__(self, segmentType=None, points=[], smooth=False):
|
|
|
|
BaseSegment.__init__(self)
|
|
|
|
self.selected = False
|
|
|
|
self.points = []
|
|
|
|
self.smooth = smooth
|
|
|
|
if points:
|
|
|
|
#the points in the segment should be RPoints, so create those objects
|
|
|
|
for point in points[:-1]:
|
|
|
|
x, y = point
|
|
|
|
p = RPoint(x, y, pointType=OFFCURVE)
|
|
|
|
p.setParent(self)
|
|
|
|
self.points.append(p)
|
|
|
|
aX, aY = points[-1]
|
|
|
|
p = RPoint(aX, aY, segmentType)
|
|
|
|
p.setParent(self)
|
|
|
|
self.points.append(p)
|
|
|
|
|
|
|
|
def _get_type(self):
|
|
|
|
return self.points[-1].type
|
|
|
|
|
|
|
|
def _set_type(self, pointType):
|
|
|
|
onCurve = self.points[-1]
|
|
|
|
ocType = onCurve.type
|
|
|
|
if ocType == pointType:
|
|
|
|
return
|
|
|
|
#we are converting a cubic line into a cubic curve
|
|
|
|
if pointType == CURVE and ocType == LINE:
|
|
|
|
onCurve.type = pointType
|
|
|
|
parent = self.getParent()
|
|
|
|
prev = parent._prevSegment(self.index)
|
|
|
|
p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE)
|
|
|
|
p1.setParent(self)
|
|
|
|
p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE)
|
|
|
|
p2.setParent(self)
|
|
|
|
self.points.insert(0, p2)
|
|
|
|
self.points.insert(0, p1)
|
|
|
|
#we are converting a cubic move to a curve
|
|
|
|
elif pointType == CURVE and ocType == MOVE:
|
|
|
|
onCurve.type = pointType
|
|
|
|
parent = self.getParent()
|
|
|
|
prev = parent._prevSegment(self.index)
|
|
|
|
p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE)
|
|
|
|
p1.setParent(self)
|
|
|
|
p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE)
|
|
|
|
p2.setParent(self)
|
|
|
|
self.points.insert(0, p2)
|
|
|
|
self.points.insert(0, p1)
|
|
|
|
#we are converting a quad curve to a cubic curve
|
|
|
|
elif pointType == CURVE and ocType == QCURVE:
|
|
|
|
onCurve.type == CURVE
|
|
|
|
#we are converting a cubic curve into a cubic line
|
|
|
|
elif pointType == LINE and ocType == CURVE:
|
|
|
|
p = self.points.pop(-1)
|
|
|
|
self.points = [p]
|
|
|
|
onCurve.type = pointType
|
|
|
|
self.smooth = False
|
|
|
|
#we are converting a cubic move to a line
|
|
|
|
elif pointType == LINE and ocType == MOVE:
|
|
|
|
onCurve.type = pointType
|
|
|
|
#we are converting a quad curve to a line:
|
|
|
|
elif pointType == LINE and ocType == QCURVE:
|
|
|
|
p = self.points.pop(-1)
|
|
|
|
self.points = [p]
|
|
|
|
onCurve.type = pointType
|
|
|
|
self.smooth = False
|
|
|
|
# we are converting to a quad curve where just about anything is legal
|
|
|
|
elif pointType == QCURVE:
|
|
|
|
onCurve.type = pointType
|
|
|
|
else:
|
|
|
|
raise RoboFabError, 'unknown segment type'
|
|
|
|
|
|
|
|
type = property(_get_type, _set_type, doc="type of the segment")
|
|
|
|
|
|
|
|
def _get_index(self):
|
|
|
|
return self.getParent().segments.index(self)
|
|
|
|
|
|
|
|
index = property(_get_index, doc="index of the segment")
|
|
|
|
|
|
|
|
def insertPoint(self, index, pointType, point):
|
|
|
|
x, y = point
|
|
|
|
p = RPoint(x, y, pointType=pointType)
|
|
|
|
p.setParent(self)
|
|
|
|
self.points.insert(index, p)
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
def removePoint(self, index):
|
|
|
|
del self.points[index]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
|
|
|
|
class RBPoint(BaseBPoint):
|
|
|
|
|
|
|
|
_title = "RoboFabBPoint"
|
|
|
|
|
|
|
|
def _setAnchorChanged(self, value):
|
|
|
|
self._anchorPoint.setChanged(value)
|
|
|
|
|
|
|
|
def _setNextChanged(self, value):
|
|
|
|
self._nextOnCurve.setChanged(value)
|
|
|
|
|
|
|
|
def _get__parentSegment(self):
|
|
|
|
return self.getParent()
|
|
|
|
|
|
|
|
_parentSegment = property(_get__parentSegment, doc="")
|
|
|
|
|
|
|
|
def _get__nextOnCurve(self):
|
|
|
|
pSeg = self._parentSegment
|
|
|
|
contour = pSeg.getParent()
|
|
|
|
#could this potentially return an incorrect index? say, if two segments are exactly the same?
|
|
|
|
return contour.segments[(contour.segments.index(pSeg) + 1) % len(contour.segments)]
|
|
|
|
|
|
|
|
_nextOnCurve = property(_get__nextOnCurve, doc="")
|
|
|
|
|
|
|
|
def _get_index(self):
|
|
|
|
return self._parentSegment.index
|
|
|
|
|
|
|
|
index = property(_get_index, doc="index of the bPoint on the contour")
|
|
|
|
|
|
|
|
|
|
|
|
class RPoint(BasePoint):
|
|
|
|
|
|
|
|
_title = "RoboFabPoint"
|
|
|
|
|
|
|
|
def __init__(self, x=0, y=0, pointType=None, name=None):
|
|
|
|
self.selected = False
|
|
|
|
self._type = pointType
|
|
|
|
self._x = x
|
|
|
|
self._y = y
|
|
|
|
self._name = None
|
|
|
|
|
|
|
|
def _get_x(self):
|
|
|
|
return self._x
|
|
|
|
|
|
|
|
def _set_x(self, value):
|
|
|
|
self._x = value
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
x = property(_get_x, _set_x, doc="")
|
|
|
|
|
|
|
|
def _get_y(self):
|
|
|
|
return self._y
|
|
|
|
|
|
|
|
def _set_y(self, value):
|
|
|
|
self._y = value
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
y = property(_get_y, _set_y, doc="")
|
|
|
|
|
|
|
|
def _get_type(self):
|
|
|
|
return self._type
|
|
|
|
|
|
|
|
def _set_type(self, value):
|
|
|
|
self._type = value
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
type = property(_get_type, _set_type, doc="")
|
|
|
|
|
|
|
|
def _get_name(self):
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
def _set_name(self, value):
|
|
|
|
self._name = value
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
name = property(_get_name, _set_name, doc="")
|
|
|
|
|
|
|
|
|
|
|
|
class RAnchor(BaseAnchor):
|
|
|
|
|
|
|
|
_title = "RoboFabAnchor"
|
|
|
|
|
|
|
|
def __init__(self, name=None, position=None, mark=None):
|
|
|
|
BaseAnchor.__init__(self)
|
|
|
|
self.selected = False
|
|
|
|
self.name = name
|
|
|
|
if position is None:
|
|
|
|
self.x = self.y = None
|
|
|
|
else:
|
|
|
|
self.x, self.y = position
|
|
|
|
self.mark = mark
|
|
|
|
|
|
|
|
def _get_index(self):
|
|
|
|
if self.getParent() is None: return None
|
|
|
|
return self.getParent().anchors.index(self)
|
|
|
|
|
|
|
|
index = property(_get_index, doc="index of the anchor")
|
|
|
|
|
|
|
|
def _get_position(self):
|
|
|
|
return (self.x, self.y)
|
|
|
|
|
|
|
|
def _set_position(self, value):
|
|
|
|
self.x = value[0]
|
|
|
|
self.y = value[1]
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
position = property(_get_position, _set_position, doc="position of the anchor")
|
|
|
|
|
|
|
|
def move(self, (x, y)):
|
|
|
|
"""Move the anchor"""
|
|
|
|
self.x = self.x + x
|
|
|
|
self.y = self.y + y
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
|
|
|
|
class RComponent(BaseComponent):
|
|
|
|
|
|
|
|
_title = "RoboFabComponent"
|
|
|
|
|
|
|
|
def __init__(self, baseGlyphName=None, offset=(0,0), scale=(1,1)):
|
|
|
|
BaseComponent.__init__(self)
|
|
|
|
self.selected = False
|
|
|
|
self._baseGlyph = baseGlyphName
|
|
|
|
self._offset = offset
|
|
|
|
self._scale = scale
|
|
|
|
|
|
|
|
def _get_index(self):
|
|
|
|
if self.getParent() is None: return None
|
|
|
|
return self.getParent().components.index(self)
|
|
|
|
|
|
|
|
index = property(_get_index, doc="index of the component")
|
|
|
|
|
|
|
|
def _get_baseGlyph(self):
|
|
|
|
return self._baseGlyph
|
|
|
|
|
|
|
|
def _set_baseGlyph(self, glyphName):
|
|
|
|
# XXXX needs to be implemented in objectsFL for symmetricity's sake. Eventually.
|
|
|
|
self._baseGlyph = glyphName
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
baseGlyph = property(_get_baseGlyph, _set_baseGlyph, doc="")
|
|
|
|
|
|
|
|
def _get_offset(self):
|
|
|
|
return self._offset
|
|
|
|
|
|
|
|
def _set_offset(self, value):
|
|
|
|
self._offset = value
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
offset = property(_get_offset, _set_offset, doc="the offset of the component")
|
|
|
|
|
|
|
|
def _get_scale(self):
|
|
|
|
return self._scale
|
|
|
|
|
|
|
|
def _set_scale(self, (x, y)):
|
|
|
|
self._scale = (x, y)
|
|
|
|
self._hasChanged()
|
|
|
|
|
|
|
|
scale = property(_get_scale, _set_scale, doc="the scale of the component")
|
|
|
|
|
|
|
|
def move(self, (x, y)):
|
|
|
|
"""Move the component"""
|
|
|
|
self.offset = (self.offset[0] + x, self.offset[1] + y)
|
|
|
|
|
|
|
|
def decompose(self):
|
|
|
|
"""Decompose the component"""
|
|
|
|
baseGlyphName = self.baseGlyph
|
|
|
|
parentGlyph = self.getParent()
|
|
|
|
# if there is no parent glyph, there is nothing to decompose to
|
|
|
|
if baseGlyphName is not None and parentGlyph is not None:
|
|
|
|
parentFont = parentGlyph.getParent()
|
|
|
|
# we must have a parent glyph with the baseGlyph
|
|
|
|
# if not, we will simply remove the component from
|
|
|
|
# the parent glyph thereby decomposing the component
|
|
|
|
# to nothing.
|
|
|
|
if parentFont is not None and parentFont.has_key(baseGlyphName):
|
|
|
|
from robofab.pens.adapterPens import TransformPointPen
|
|
|
|
oX, oY = self.offset
|
|
|
|
sX, sY = self.scale
|
|
|
|
baseGlyph = parentFont[baseGlyphName]
|
|
|
|
for contour in baseGlyph.contours:
|
|
|
|
pointPen = parentGlyph.getPointPen()
|
|
|
|
transPen = TransformPointPen(pointPen, (sX, 0, 0, sY, oX, oY))
|
|
|
|
contour.drawPoints(transPen)
|
|
|
|
parentGlyph.components.remove(self)
|
|
|
|
|
|
|
|
|
|
|
|
class RKerning(BaseKerning):
|
|
|
|
|
|
|
|
_title = "RoboFabKerning"
|
|
|
|
|
|
|
|
|
|
|
|
class RGroups(BaseGroups):
|
|
|
|
|
|
|
|
_title = "RoboFabGroups"
|
|
|
|
|
|
|
|
class RLib(BaseLib):
|
|
|
|
|
|
|
|
_title = "RoboFabLib"
|
|
|
|
|
|
|
|
|
|
|
|
class RInfo(BaseInfo):
|
|
|
|
|
2009-02-28 15:47:24 +00:00
|
|
|
_title = "RoboFabFontInfo"
|
2008-01-07 17:40:34 +00:00
|
|
|
|
2009-02-28 15:47:24 +00:00
|
|
|
class RFeatures(BaseFeatures):
|
|
|
|
|
|
|
|
_title = "RoboFabFeatures"
|
2008-01-07 17:40:34 +00:00
|
|
|
|