diff options
Diffstat (limited to 'misc/pylib/robofab/objects')
-rwxr-xr-x | misc/pylib/robofab/objects/__init__.py | 15 | ||||
-rwxr-xr-x | misc/pylib/robofab/objects/objectsBase.pyx | 3426 | ||||
-rw-r--r-- | misc/pylib/robofab/objects/objectsFF.py | 1253 | ||||
-rwxr-xr-x | misc/pylib/robofab/objects/objectsFL.py | 3112 | ||||
-rwxr-xr-x | misc/pylib/robofab/objects/objectsRF.pyx | 1233 |
5 files changed, 0 insertions, 9039 deletions
diff --git a/misc/pylib/robofab/objects/__init__.py b/misc/pylib/robofab/objects/__init__.py deleted file mode 100755 index ad85fd002..000000000 --- a/misc/pylib/robofab/objects/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - -Directory for modules supporting - - Unified - - Font - - Objects - -""" - - - - diff --git a/misc/pylib/robofab/objects/objectsBase.pyx b/misc/pylib/robofab/objects/objectsBase.pyx deleted file mode 100755 index 3154fb3f4..000000000 --- a/misc/pylib/robofab/objects/objectsBase.pyx +++ /dev/null @@ -1,3426 +0,0 @@ -""" -Base classes for the Unified Font Objects (UFO), -a series of classes that deal with fonts, glyphs, -contours and related things. - -Unified Font Objects are: -- platform independent -- application independent - -About Object Inheritance: -objectsFL and objectsRF objects inherit -methods and attributes from these objects. -In other words, if it is in here, you can -do it with the objectsFL and objectsRF. -""" - - -from __future__ import generators -from __future__ import division - -from warnings import warn -import math -import copy - -from robofab import ufoLib -from robofab import RoboFabError -from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect -from fontTools.pens.basePen import AbstractPen -from fontTools.pens.areaPen import AreaPen -from ..exceptions import RoboFabError, RoboFabWarning - -try: - set -except NameError: - from sets import Set as set - -#constants for dealing with segments, points and bPoints -MOVE = 'move' -LINE = 'line' -CORNER = 'corner' -CURVE = 'curve' -QCURVE = 'qcurve' -OFFCURVE = 'offcurve' - -DEGREE = 180 / math.pi - - - -# the key for the postscript hint data stored in the UFO -postScriptHintDataLibKey = "org.robofab.postScriptHintData" - -# from http://svn.typesupply.com/packages/fontMath/mathFunctions.py - -def add(v1, v2): - return v1 + v2 - -def sub(v1, v2): - return v1 - v2 - -def mul(v, f): - return v * f - -def div(v, f): - return v / f - -def issequence(x): - "Is x a sequence? We say it is if it has a __getitem__ method." - return hasattr(x, '__getitem__') - - - -class BasePostScriptHintValues(object): - """ Base class for postscript hinting information. - """ - - def __init__(self, data=None): - if data is not None: - self.fromDict(data) - else: - for name in self._attributeNames.keys(): - setattr(self, name, self._attributeNames[name]['default']) - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - return None - - def setParent(self, parent): - import weakref - self.getParent = weakref.ref(parent) - - def isEmpty(self): - """Check all attrs and decide if they're all empty.""" - empty = True - for name in self._attributeNames: - if getattr(self, name): - empty = False - break - return empty - - def clear(self): - """Set all attributes to default / empty""" - for name in self._attributeNames: - setattr(self, name, self._attributeNames[name]['default']) - - def _loadFromLib(self, lib): - data = lib.get(postScriptHintDataLibKey) - if data is not None: - self.fromDict(data) - - def _saveToLib(self, lib): - parent = self.getParent() - if parent is not None: - parent.setChanged(True) - hintsDict = self.asDict() - if hintsDict: - lib[postScriptHintDataLibKey] = hintsDict - - def fromDict(self, data): - for name in self._attributeNames: - if name in data: - setattr(self, name, data[name]) - - def asDict(self): - d = {} - for name in self._attributeNames: - try: - value = getattr(self, name) - except AttributeError: - print "%s attribute not supported"%name - continue - if value: - d[name] = getattr(self, name) - return d - - def update(self, other): - assert isinstance(other, BasePostScriptHintValues) - for name in self._attributeNames.keys(): - v = getattr(other, name) - if v is not None: - setattr(self, name, v) - - def __repr__(self): - return "<Base PS Hint Data>" - - def copy(self, aParent=None): - """Duplicate this object. Pass an object for parenting if you want.""" - n = self.__class__(data=self.asDict()) - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent'] - for k in self.__dict__.keys(): - if k in dont: - continue - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - -class BasePostScriptGlyphHintValues(BasePostScriptHintValues): - """ Base class for glyph-level postscript hinting information. - vStems, hStems - """ - _attributeNames = { - # some of these values can have only a certain number of elements - 'vHints': {'default': None, 'max':100, 'isVertical':True}, - 'hHints': {'default': None, 'max':100, 'isVertical':False}, - } - - def __init__(self, data=None): - if data is not None: - self.fromDict(data) - else: - for name in self._attributeNames.keys(): - setattr(self, name, self._attributeNames[name]['default']) - - def __repr__(self): - return "<PostScript Glyph Hints Values>" - - def round(self): - """Round the values to reasonable values. - - stems are rounded to int - """ - for name, values in self._attributeNames.items(): - v = getattr(self, name) - if v is None: - continue - new = [] - for n in v: - new.append((int(round(n[0])), int(round(n[1])))) - setattr(self, name, new) - - # math operations for psHint object - # Note: math operations can change integers to floats. - def __add__(self, other): - assert isinstance(other, BasePostScriptHintValues) - copied = self.copy() - self._processMathOne(copied, other, add) - return copied - - def __sub__(self, other): - assert isinstance(other, BasePostScriptHintValues) - copied = self.copy() - self._processMathOne(copied, other, sub) - return copied - - def __mul__(self, factor): - #if isinstance(factor, tuple): - # factor = factor[0] - copiedInfo = self.copy() - self._processMathTwo(copiedInfo, factor, mul) - return copiedInfo - - __rmul__ = __mul__ - - def __div__(self, factor): - #if isinstance(factor, tuple): - # factor = factor[0] - copiedInfo = self.copy() - self._processMathTwo(copiedInfo, factor, div) - return copiedInfo - - __rdiv__ = __div__ - - def _processMathOne(self, copied, other, funct): - for name, values in self._attributeNames.items(): - a = None - b = None - v = None - if hasattr(copied, name): - a = getattr(copied, name) - if hasattr(other, name): - b = getattr(other, name) - if a is not None and b is not None: - if len(a) != len(b): - # can't do math with non matching zones - continue - l = len(a) - for i in range(l): - if v is None: - v = [] - ai = a[i] - bi = b[i] - l2 = min(len(ai), len(bi)) - v2 = [funct(ai[j], bi[j]) for j in range(l2)] - v.append(v2) - if v is not None: - setattr(copied, name, v) - - def _processMathTwo(self, copied, factor, funct): - for name, values in self._attributeNames.items(): - a = None - b = None - v = None - isVertical = self._attributeNames[name]['isVertical'] - splitFactor = factor - if isinstance(factor, tuple): - #print "mathtwo", name, funct, factor, isVertical - if isVertical: - splitFactor = factor[1] - else: - splitFactor = factor[0] - if hasattr(copied, name): - a = getattr(copied, name) - if a is not None: - for i in range(len(a)): - if v is None: - v = [] - v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))] - v.append(v2) - if v is not None: - setattr(copied, name, v) - - -class BasePostScriptFontHintValues(BasePostScriptHintValues): - """ Base class for font-level postscript hinting information. - Blues values, stem values. - """ - - _attributeNames = { - # some of these values can have only a certain number of elements - # default: what the value should be when initialised - # max: the maximum number of items this attribute is allowed to have - # isVertical: the vertical relevance - 'blueFuzz': {'default': None, 'max':1, 'isVertical':True}, - 'blueScale': {'default': None, 'max':1, 'isVertical':True}, - 'blueShift': {'default': None, 'max':1, 'isVertical':True}, - 'forceBold': {'default': None, 'max':1, 'isVertical':False}, - 'blueValues': {'default': None, 'max':7, 'isVertical':True}, - 'otherBlues': {'default': None, 'max':5, 'isVertical':True}, - 'familyBlues': {'default': None, 'max':7, 'isVertical':True}, - 'familyOtherBlues': {'default': None, 'max':5, 'isVertical':True}, - 'vStems': {'default': None, 'max':6, 'isVertical':True}, - 'hStems': {'default': None, 'max':11, 'isVertical':False}, - } - - def __init__(self, data=None): - if data is not None: - self.fromDict(data) - - def __repr__(self): - return "<PostScript Font Hints Values>" - - # route attribute calls to info object - - def _bluesToPairs(self, values): - values.sort() - finalValues = [] - for value in values: - if not finalValues or len(finalValues[-1]) == 2: - finalValues.append([]) - finalValues[-1].append(value) - return finalValues - - def _bluesFromPairs(self, values): - finalValues = [] - for value1, value2 in values: - finalValues.append(value1) - finalValues.append(value2) - finalValues.sort() - return finalValues - - def _get_blueValues(self): - values = self.getParent().info.postscriptBlueValues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_blueValues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptBlueValues = values - - blueValues = property(_get_blueValues, _set_blueValues) - - def _get_otherBlues(self): - values = self.getParent().info.postscriptOtherBlues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_otherBlues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptOtherBlues = values - - otherBlues = property(_get_otherBlues, _set_otherBlues) - - def _get_familyBlues(self): - values = self.getParent().info.postscriptFamilyBlues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_familyBlues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptFamilyBlues = values - - familyBlues = property(_get_familyBlues, _set_familyBlues) - - def _get_familyOtherBlues(self): - values = self.getParent().info.postscriptFamilyOtherBlues - if values is None: - values = [] - values = self._bluesToPairs(values) - return values - - def _set_familyOtherBlues(self, values): - if values is None: - values = [] - values = self._bluesFromPairs(values) - self.getParent().info.postscriptFamilyOtherBlues = values - - familyOtherBlues = property(_get_familyOtherBlues, _set_familyOtherBlues) - - def _get_vStems(self): - return self.getParent().info.postscriptStemSnapV - - def _set_vStems(self, value): - if value is None: - value = [] - self.getParent().info.postscriptStemSnapV = list(value) - - vStems = property(_get_vStems, _set_vStems) - - def _get_hStems(self): - return self.getParent().info.postscriptStemSnapH - - def _set_hStems(self, value): - if value is None: - value = [] - self.getParent().info.postscriptStemSnapH = list(value) - - hStems = property(_get_hStems, _set_hStems) - - def _get_blueScale(self): - return self.getParent().info.postscriptBlueScale - - def _set_blueScale(self, value): - self.getParent().info.postscriptBlueScale = value - - blueScale = property(_get_blueScale, _set_blueScale) - - def _get_blueShift(self): - return self.getParent().info.postscriptBlueShift - - def _set_blueShift(self, value): - self.getParent().info.postscriptBlueShift = value - - blueShift = property(_get_blueShift, _set_blueShift) - - def _get_blueFuzz(self): - return self.getParent().info.postscriptBlueFuzz - - def _set_blueFuzz(self, value): - self.getParent().info.postscriptBlueFuzz = value - - blueFuzz = property(_get_blueFuzz, _set_blueFuzz) - - def _get_forceBold(self): - return self.getParent().info.postscriptForceBold - - def _set_forceBold(self, value): - self.getParent().info.postscriptForceBold = value - - forceBold = property(_get_forceBold, _set_forceBold) - - def round(self): - """Round the values to reasonable values. - - blueScale is not rounded, it is a float - - forceBold is set to False if -0.5 < value < 0.5. Otherwise it will be True. - - blueShift, blueFuzz are rounded to int - - stems are rounded to int - - blues are rounded to int - """ - for name, values in self._attributeNames.items(): - if name == "blueScale": - continue - elif name == "forceBold": - v = getattr(self, name) - if v is None: - continue - if -0.5 <= v <= 0.5: - setattr(self, name, False) - else: - setattr(self, name, True) - elif name in ['blueFuzz', 'blueShift']: - v = getattr(self, name) - if v is None: - continue - setattr(self, name, int(round(v))) - elif name in ['hStems', 'vStems']: - v = getattr(self, name) - if v is None: - continue - new = [] - for n in v: - new.append(int(round(n))) - setattr(self, name, new) - else: - v = getattr(self, name) - if v is None: - continue - new = [] - for n in v: - new.append([int(round(m)) for m in n]) - setattr(self, name, new) - - - -class RoboFabInterpolationError(Exception): pass - - -def _interpolate(a,b,v): - """interpolate values by factor v""" - return a + (b-a) * v - -def _interpolatePt(a, b, v): - """interpolate point by factor v""" - xa, ya = a - xb, yb = b - if not isinstance(v, tuple): - xv = v - yv = v - else: - xv, yv = v - return xa + (xb-xa) * xv, ya + (yb-ya) * yv - -def _scalePointFromCenter(pt, scale, center): - """scale a point from a center point""" - pointX, pointY = pt - scaleX, scaleY = scale - centerX, centerY = center - ogCenter = center - scaledCenter = (centerX * scaleX, centerY * scaleY) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - scaledPointX = (pointX * scaleX) - shiftVal[0] - scaledPointY = (pointY * scaleY) - shiftVal[1] - return (scaledPointX, scaledPointY) - -def _box(objectToMeasure, fontObject=None): - """calculate the bounds of the object and return it as a (xMin, yMin, xMax, yMax)""" - #from fontTools.pens.boundsPen import BoundsPen - from robofab.pens.boundsPen import BoundsPen - boundsPen = BoundsPen(glyphSet=fontObject) - objectToMeasure.draw(boundsPen) - bounds = boundsPen.bounds - if bounds is None: - bounds = (0, 0, 0, 0) - return bounds - -def roundPt(pt): - """Round a vector""" - return int(round(pt[0])), int(round(pt[1])) - -def addPt(ptA, ptB): - """Add two vectors""" - return ptA[0] + ptB[0], ptA[1] + ptB[1] - -def subPt(ptA, ptB): - """Substract two vectors""" - return ptA[0] - ptB[0], ptA[1] - ptB[1] - -def mulPt(ptA, scalar): - """Multiply a vector with scalar""" - if not isinstance(scalar, tuple): - f1 = scalar - f2 = scalar - else: - f1, f2 = scalar - return ptA[0]*f1, ptA[1]*f2 - -def relativeBCPIn(anchor, BCPIn): - """convert absolute incoming bcp value to a relative value""" - return (BCPIn[0] - anchor[0], BCPIn[1] - anchor[1]) - -def absoluteBCPIn(anchor, BCPIn): - """convert relative incoming bcp value to an absolute value""" - return (BCPIn[0] + anchor[0], BCPIn[1] + anchor[1]) - -def relativeBCPOut(anchor, BCPOut): - """convert absolute outgoing bcp value to a relative value""" - return (BCPOut[0] - anchor[0], BCPOut[1] - anchor[1]) - -def absoluteBCPOut(anchor, BCPOut): - """convert relative outgoing bcp value to an absolute value""" - return (BCPOut[0] + anchor[0], BCPOut[1] + anchor[1]) - -class FuzzyNumber(object): - - def __init__(self, value, threshold): - self.value = value - self.threshold = threshold - - def __cmp__(self, other): - if abs(self.value - other.value) < self.threshold: - return 0 - else: - return cmp(self.value, other.value) - - -class RBaseObject(object): - - """Base class for wrapper objects""" - - attrMap= {} - _title = "RoboFab Wrapper" - - def __init__(self): - self._object = {} - self.changed = False # if the object needs to be saved - self.selected = False - - def __len__(self): - return len(self._object) - - def __repr__(self): - try: - name = `self._object` - except: - name = "None" - return "<%s for %s>" %(self._title, name) - - def copy(self, aParent=None): - """Duplicate this object. Pass an object for parenting if you want.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent'] - for k in self.__dict__.keys(): - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - - def round(self): - pass - - def isRobofab(self): - """Presence of this method indicates a Robofab object""" - return 1 - - def naked(self): - """Return the wrapped object itself, in case it is needed for direct access.""" - return self._object - - def setChanged(self, state=True): - self.changed = state - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - return None - - def setParent(self, parent): - import weakref - self.getParent = weakref.ref(parent) - - def _writeXML(self, writer): - pass - - def dump(self, private=False): - """Print a dump of this object to the std out.""" - from robofab.tools.objectDumper import dumpObject - dumpObject(self, private) - - - -class BaseFont(RBaseObject): - - """Base class for all font objects.""" - - _allFonts = [] - - def __init__(self): - import weakref - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self._allFonts.append(weakref.ref(self)) - self._supportHints = False - - def __repr__(self): - try: - name = self.info.postscriptFullName - except AttributeError: - name = "unnamed_font" - return "<RFont font for %s>" %(name) - - def __eq__(self, other): - #Compare this font with another, compare if they refer to the same file. - return self._compare(other) - - def _compare(self, other): - """Compare this font to other. RF and FL UFO implementations need - slightly different ways of comparing fonts. This method does the - basic stuff. Start with simple and quick comparisons, then move into - detailed comparisons of glyphs.""" - if not hasattr(other, "fileName"): - return False - if self.fileName is not None and self.fileName == other.fileName: - return True - if self.fileName <> other.fileName: - return False - # this will falsely identify two distinct "Untitled" as equal - # so test some more. A lot of work to please some dolt who - # does not save his fonts while running scripts. - try: - if len(self) <> len(other): - return False - except TypeError: - return False - # same name and length. start comparing glyphs - namesSelf = self.keys() - namesOther = other.keys() - namesSelf.sort() - namesOther.sort() - for i in range(len(namesSelf)): - if namesSelf[i] <> namesOther[i]: - return False - for c in self: - if not c == other[c.name]: - return False - return True - - def keys(self): - # must be implemented by subclass - raise NotImplementedError - - def __iter__(self): - for glyphName in self.keys(): - yield self.getGlyph(glyphName) - - def __getitem__(self, glyphName): - return self.getGlyph(glyphName) - - def __contains__(self, glyphName): - return self.has_key(glyphName) - - def _hasChanged(self): - #mark the object as changed - self.setChanged(True) - - def update(self): - """update the font""" - pass - - def close(self, save=1): - """Close the font, saving is optional.""" - pass - - def round(self): - """round all of the points in all of the glyphs""" - for glyph in self: - glyph.round() - - def autoUnicodes(self): - """Using fontTools.agl, assign Unicode lists to all glyphs in the font""" - for glyph in self: - glyph.autoUnicodes() - - 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.""" - map = {} - for glyph in self: - for u in glyph.unicodes: - if not map.has_key(u): - map[u] = [] - map[u].append(glyph.name) - return map - - def getReverseComponentMapping(self): - """ - Get a reversed map of component references in the font. - { - 'A' : ['Aacute', 'Aring'] - 'acute' : ['Aacute'] - 'ring' : ['Aring'] - etc. - } - """ - map = {} - for glyph in self: - glyphName = glyph.name - for component in glyph.components: - baseGlyphName = component.baseGlyph - if not map.has_key(baseGlyphName): - map[baseGlyphName] = [] - map[baseGlyphName].append(glyphName) - return map - - def compileGlyph(self, glyphName, baseName, accentNames, \ - adjustWidth=False, preflight=False, printErrors=True): - """Compile components into a new glyph using components and anchorpoints. - glyphName: the name of the glyph where it all needs to go - baseName: the name of the base glyph - accentNames: a list of accentName, anchorName tuples, [('acute', 'top'), etc] - """ - anchors = {} - errors = {} - baseGlyph = self[baseName] - for anchor in baseGlyph.getAnchors(): - anchors[anchor.name] = anchor.position - destGlyph = self.newGlyph(glyphName, clear=True) - destGlyph.appendComponent(baseName) - destGlyph.width = baseGlyph.width - for accentName, anchorName in accentNames: - try: - accent = self[accentName] - except IndexError: - errors["glyph '%s' is missing in font %s"%(accentName, self.info.fullName)] = 1 - continue - shift = None - for accentAnchor in accent.getAnchors(): - if '_'+anchorName == accentAnchor.name: - shift = anchors[anchorName][0] - accentAnchor.position[0], anchors[anchorName][1] - accentAnchor.position[1] - destGlyph.appendComponent(accentName, offset=shift) - break - if shift is not None: - for accentAnchor in accent.getAnchors(): - if accentAnchor.name in anchors: - anchors[accentAnchor.name] = shift[0]+accentAnchor.position[0], shift[1]+accentAnchor.position[1] - if printErrors: - for px in errors.keys(): - print px - return destGlyph - - def generateGlyph(self, glyphName, replace=1, preflight=False, printErrors=True): - """Generate a glyph and return it. Assembled from GlyphConstruction.txt""" - from robofab.tools.toolsAll import readGlyphConstructions - con = readGlyphConstructions() - entry = con.get(glyphName, None) - if not entry: - print "glyph '%s' is not listed in the robofab/Data/GlyphConstruction.txt"%(glyphName) - return - baseName = con[glyphName][0] - parts = con[glyphName][1:] - return self.compileGlyph(glyphName, baseName, parts, adjustWidth=1, preflight=preflight, printErrors=printErrors) - - def interpolate(self, factor, minFont, maxFont, suppressError=True, analyzeOnly=False, doProgress=False): - """Traditional interpolation method. Interpolates by factor between minFont and maxFont. - suppressError will supress all tracebacks and analyze only will not perform the interpolation - but it will analyze all glyphs and return a dict of problems.""" - errors = {} - if not isinstance(factor, tuple): - factor = factor, factor - minGlyphNames = minFont.keys() - maxGlyphNames = maxFont.keys() - allGlyphNames = list(set(minGlyphNames) | set(maxGlyphNames)) - if doProgress: - from robofab.interface.all.dialogs import ProgressBar - progress = ProgressBar('Interpolating...', len(allGlyphNames)) - tickCount = 0 - # some dimensions and values - self.info.ascender = _interpolate(minFont.info.ascender, maxFont.info.ascender, factor[1]) - self.info.descender = _interpolate(minFont.info.descender, maxFont.info.descender, factor[1]) - # check for the presence of the glyph in each of the fonts - for glyphName in allGlyphNames: - if doProgress: - progress.label(glyphName) - fatalError = False - if glyphName not in minGlyphNames: - fatalError = True - if not errors.has_key('Missing Glyphs'): - errors['Missing Glyphs'] = [] - errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.postscriptFullName)) - if glyphName not in maxGlyphNames: - fatalError = True - if not errors.has_key('Missing Glyphs'): - errors['Missing Glyphs'] = [] - errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.postscriptFullName)) - # if no major problems, proceed. - if not fatalError: - # remove the glyph since FontLab has a problem with - # interpolating an existing glyph that contains - # some contour data. - oldLib = {} - oldMark = None - oldNote = None - if self.has_key(glyphName): - glyph = self[glyphName] - oldLib = dict(glyph.lib) - oldMark = glyph.mark - oldNote = glyph.note - self.removeGlyph(glyphName) - selfGlyph = self.newGlyph(glyphName) - selfGlyph.lib.update(oldLib) - if oldMark != None: - selfGlyph.mark = oldMark - selfGlyph.note = oldNote - min = minFont[glyphName] - max = maxFont[glyphName] - ok, glyphErrors = selfGlyph.interpolate(factor, min, max, suppressError=suppressError, analyzeOnly=analyzeOnly) - if not errors.has_key('Glyph Errors'): - errors['Glyph Errors'] = {} - errors['Glyph Errors'][glyphName] = glyphErrors - if doProgress: - progress.tick(tickCount) - tickCount = tickCount + 1 - if doProgress: - progress.close() - return errors - - def getGlyphNameToFileNameFunc(self): - funcName = self.lib.get("org.robofab.glyphNameToFileNameFuncName") - if funcName is None: - return None - parts = funcName.split(".") - module = ".".join(parts[:-1]) - try: - item = __import__(module) - for sub in parts[1:]: - item = getattr(item, sub) - except (ImportError, AttributeError): - warn("Can't find glyph name to file name converter function, " - "falling back to default scheme (%s)" % funcName, RoboFabWarning) - return None - else: - return item - - -class BaseGlyph(RBaseObject): - - """Base class for all glyph objects.""" - - def __init__(self): - RBaseObject.__init__(self) - #self.contours = [] - #self.components = [] - #self.anchors = [] - #self.width = 0 - #self.note = None - ##self.unicodes = [] - #self.selected = None - self.changed = False # if the object needs to be saved - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - fontParent = self.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: - pass - try: - glyph = self.name - except AttributeError: - pass - return "<RGlyph for %s.%s>" %(font, glyph) - - # - # Glyph Math - # - - def _getMathData(self): - from robofab.pens.mathPens import GetMathDataPointPen - pen = GetMathDataPointPen() - self.drawPoints(pen) - data = pen.getData() - return data - - def _setMathData(self, data, destination=None): - from robofab.pens.mathPens import CurveSegmentFilterPointPen - if destination is None: - newGlyph = self._mathCopy() - else: - newGlyph = destination - newGlyph.clear() - # - # draw the data onto the glyph - pointPen = newGlyph.getPointPen() - filterPen = CurveSegmentFilterPointPen(pointPen) - for contour in data['contours']: - filterPen.beginPath() - for segmentType, pt, smooth, name in contour: - filterPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name) - filterPen.endPath() - for baseName, transformation in data['components']: - filterPen.addComponent(baseName, transformation) - for pt, name in data['anchors']: - filterPen.beginPath() - filterPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name) - filterPen.endPath() - newGlyph.width = data['width'] - psHints = data.get('psHints') - if psHints is not None: - newGlyph.psHints.update(psHints) - # - return newGlyph - - def _getMathDestination(self): - # make a new, empty glyph - return self.__class__() - - def _mathCopy(self): - # copy self without contour, component and anchor data - glyph = self._getMathDestination() - glyph.name = self.name - glyph.unicodes = list(self.unicodes) - glyph.width = self.width - glyph.note = self.note - glyph.lib = dict(self.lib) - return glyph - - def _processMathOne(self, otherGlyph, funct): - # used by: __add__, __sub__ - # - newData = { - 'contours':[], - 'components':[], - 'anchors':[], - 'width':None - } - selfData = self._getMathData() - otherData = otherGlyph._getMathData() - # - # contours - selfContours = selfData['contours'] - otherContours = otherData['contours'] - newContours = newData['contours'] - if len(selfContours) > 0: - for contourIndex in xrange(len(selfContours)): - newContours.append([]) - selfContour = selfContours[contourIndex] - otherContour = otherContours[contourIndex] - for pointIndex in xrange(len(selfContour)): - segType, pt, smooth, name = selfContour[pointIndex] - newX, newY = funct(selfContour[pointIndex][1], otherContour[pointIndex][1]) - newContours[-1].append((segType, (newX, newY), smooth, name)) - # anchors - selfAnchors = selfData['anchors'] - otherAnchors = otherData['anchors'] - newAnchors = newData['anchors'] - if len(selfAnchors) > 0: - selfAnchors, otherAnchors = self._mathAnchorCompare(selfAnchors, otherAnchors) - anchorNames = selfAnchors.keys() - for anchorName in anchorNames: - selfAnchorList = selfAnchors[anchorName] - otherAnchorList = otherAnchors[anchorName] - for i in range(len(selfAnchorList)): - selfAnchor = selfAnchorList[i] - otherAnchor = otherAnchorList[i] - newAnchor = funct(selfAnchor, otherAnchor) - newAnchors.append((newAnchor, anchorName)) - # components - selfComponents = selfData['components'] - otherComponents = otherData['components'] - newComponents = newData['components'] - if len(selfComponents) > 0: - selfComponents, otherComponents = self._mathComponentCompare(selfComponents, otherComponents) - componentNames = selfComponents.keys() - for componentName in componentNames: - selfComponentList = selfComponents[componentName] - otherComponentList = otherComponents[componentName] - for i in range(len(selfComponentList)): - # transformation breakdown: xScale, xyScale, yxScale, yScale, xOffset, yOffset - selfXScale, selfXYScale, selfYXScale, selfYScale, selfXOffset, selfYOffset = selfComponentList[i] - otherXScale, otherXYScale, otherYXScale, otherYScale, otherXOffset, otherYOffset = otherComponentList[i] - newXScale, newXYScale = funct((selfXScale, selfXYScale), (otherXScale, otherXYScale)) - newYXScale, newYScale = funct((selfYXScale, selfYScale), (otherYXScale, otherYScale)) - newXOffset, newYOffset = funct((selfXOffset, selfYOffset), (otherXOffset, otherYOffset)) - newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) - return newData - - def _processMathTwo(self, factor, funct): - # used by: __mul__, __div__ - # - newData = { - 'contours':[], - 'components':[], - 'anchors':[], - 'width':None - } - selfData = self._getMathData() - # contours - selfContours = selfData['contours'] - newContours = newData['contours'] - for selfContour in selfContours: - newContours.append([]) - for segType, pt, smooth, name in selfContour: - newX, newY = funct(pt, factor) - newContours[-1].append((segType, (newX, newY), smooth, name)) - # anchors - selfAnchors = selfData['anchors'] - newAnchors = newData['anchors'] - for pt, anchorName in selfAnchors: - newPt = funct(pt, factor) - newAnchors.append((newPt, anchorName)) - # components - selfComponents = selfData['components'] - newComponents = newData['components'] - for baseName, transformation in selfComponents: - xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation - newXOffset, newYOffset = funct((xOffset, yOffset), factor) - newXScale, newYScale = funct((xScale, yScale), factor) - newXYScale, newYXScale = funct((xyScale, yxScale), factor) - newComponents.append((baseName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) - # return the data - return newData - - def _mathAnchorCompare(self, selfMathAnchors, otherMathAnchors): - # collect compatible anchors - selfAnchors = {} - for pt, name in selfMathAnchors: - if not selfAnchors.has_key(name): - selfAnchors[name] = [] - selfAnchors[name].append(pt) - otherAnchors = {} - for pt, name in otherMathAnchors: - if not otherAnchors.has_key(name): - otherAnchors[name] = [] - otherAnchors[name].append(pt) - compatAnchors = set(selfAnchors.keys()) & set(otherAnchors.keys()) - finalSelfAnchors = {} - finalOtherAnchors = {} - for name in compatAnchors: - if not finalSelfAnchors.has_key(name): - finalSelfAnchors[name] = [] - if not finalOtherAnchors.has_key(name): - finalOtherAnchors[name] = [] - selfList = selfAnchors[name] - otherList = otherAnchors[name] - selfCount = len(selfList) - otherCount = len(otherList) - if selfCount != otherCount: - r = range(min(selfCount, otherCount)) - else: - r = range(selfCount) - for i in r: - finalSelfAnchors[name].append(selfList[i]) - finalOtherAnchors[name].append(otherList[i]) - return finalSelfAnchors, finalOtherAnchors - - def _mathComponentCompare(self, selfMathComponents, otherMathComponents): - # collect compatible components - selfComponents = {} - for baseName, transformation in selfMathComponents: - if not selfComponents.has_key(baseName): - selfComponents[baseName] = [] - selfComponents[baseName].append(transformation) - otherComponents = {} - for baseName, transformation in otherMathComponents: - if not otherComponents.has_key(baseName): - otherComponents[baseName] = [] - otherComponents[baseName].append(transformation) - compatComponents = set(selfComponents.keys()) & set(otherComponents.keys()) - finalSelfComponents = {} - finalOtherComponents = {} - for baseName in compatComponents: - if not finalSelfComponents.has_key(baseName): - finalSelfComponents[baseName] = [] - if not finalOtherComponents.has_key(baseName): - finalOtherComponents[baseName] = [] - selfList = selfComponents[baseName] - otherList = otherComponents[baseName] - selfCount = len(selfList) - otherCount = len(otherList) - if selfCount != otherCount: - r = range(min(selfCount, otherCount)) - else: - r = range(selfCount) - for i in r: - finalSelfComponents[baseName].append(selfList[i]) - finalOtherComponents[baseName].append(otherList[i]) - return finalSelfComponents, finalOtherComponents - - def __mul__(self, factor): - assert isinstance(factor, (int, float, tuple)), "Glyphs can only be multiplied by int, float or a 2-tuple." - if not isinstance(factor, tuple): - factor = (factor, factor) - data = self._processMathTwo(factor, mulPt) - data['width'] = self.width * factor[0] - # psHints - if not self.psHints.isEmpty(): - newPsHints = self.psHints * factor - data['psHints'] = newPsHints - return self._setMathData(data) - - __rmul__ = __mul__ - - def __div__(self, factor): - assert isinstance(factor, (int, float, tuple)), "Glyphs can only be divided by int, float or a 2-tuple." - # calculate reverse factor, and cause nice ZeroDivisionError if it can't - if isinstance(factor, tuple): - reverse = 1.0/factor[0], 1.0/factor[1] - else: - reverse = 1.0/factor - return self.__mul__(reverse) - - def __add__(self, other): - assert isinstance(other, BaseGlyph), "Glyphs can only be added to other glyphs." - data = self._processMathOne(other, addPt) - data['width'] = self.width + other.width - return self._setMathData(data) - - def __sub__(self, other): - assert isinstance(other, BaseGlyph), "Glyphs can only be substracted from other glyphs." - data = self._processMathOne(other, subPt) - data['width'] = self.width + other.width - return self._setMathData(data) - - # - # Interpolation - # - - def interpolate(self, factor, minGlyph, maxGlyph, suppressError=True, analyzeOnly=False): - """Traditional interpolation method. Interpolates by factor between minGlyph and maxGlyph. - suppressError will supress all tracebacks and analyze only will not perform the interpolation - but it will analyze all glyphs and return a dict of problems.""" - if not isinstance(factor, tuple): - factor = factor, factor - fatalError = False - if analyzeOnly: - ok, errors = minGlyph.isCompatible(maxGlyph) - return ok, errors - minData = None - maxData = None - minName = minGlyph.name - maxName = maxGlyph.name - try: - minData = minGlyph._getMathData() - maxData = maxGlyph._getMathData() - newContours = self._interpolateContours(factor, minData['contours'], maxData['contours']) - newComponents = self._interpolateComponents(factor, minData['components'], maxData['components']) - newAnchors = self._interpolateAnchors(factor, minData['anchors'], maxData['anchors']) - newWidth = _interpolate(minGlyph.width, maxGlyph.width, factor[0]) - newData = { - 'contours':newContours, - 'components':newComponents, - 'anchors':newAnchors, - 'width':newWidth - } - self._setMathData(newData, self) - except IndexError: - if not suppressError: - ok, errors = minGlyph.isCompatible(maxGlyph) - ok = not ok - return ok, errors - self.update() - return False, [] - - def isCompatible(self, otherGlyph, report=True): - """Return a bool value if the glyph is compatible with otherGlyph. - With report = True, isCompatible will return a report of what's wrong. - The interpolate method requires absolute equality between contour data. - Absolute equality is preferred among component and anchor data, but - it is NOT required. Interpolation between components and anchors - will only deal with compatible data and incompatible data will be - ignored. This method reflects this system.""" - selfName = self.name - selfData = self._getMathData() - otherName = otherGlyph.name - otherData = otherGlyph._getMathData() - compatible, errors = self._isCompatibleInternal(selfName, otherName, selfData, otherData) - if report: - return compatible, errors - return compatible - - def _isCompatibleInternal(self, selfName, otherName, selfData, otherData): - fatalError = False - errors = [] - ## contours - # any contour incompatibilities - # result in fatal errors - selfContours = selfData['contours'] - otherContours = otherData['contours'] - if len(selfContours) != len(otherContours): - fatalError = True - errors.append("Fatal error: glyph %s and glyph %s don't have the same number of contours." %(selfName, otherName)) - else: - for contourIndex in xrange(len(selfContours)): - selfContour = selfContours[contourIndex] - otherContour = otherContours[contourIndex] - if len(selfContour) != len(otherContour): - fatalError = True - errors.append("Fatal error: contour %d in glyph %s and glyph %s don't have the same number of segments." %(contourIndex, selfName, otherName)) - ## components - # component incompatibilities - # do not result in fatal errors - selfComponents = selfData['components'] - otherComponents = otherData['components'] - if len(selfComponents) != len(otherComponents): - errors.append("Error: glyph %s and glyph %s don't have the same number of components." %(selfName, otherName)) - for componentIndex in xrange(min(len(selfComponents), len(otherComponents))): - selfBaseName, selfTransformation = selfComponents[componentIndex] - otherBaseName, otherTransformation = otherComponents[componentIndex] - if selfBaseName != otherBaseName: - errors.append("Error: component %d in glyph %s and glyph %s don't have the same base glyph." %(componentIndex, selfName, otherName)) - ## anchors - # anchor incompatibilities - # do not result in fatal errors - selfAnchors = selfData['anchors'] - otherAnchors = otherData['anchors'] - if len(selfAnchors) != len(otherAnchors): - errors.append("Error: glyph %s and glyph %s don't have the same number of anchors." %(selfName, otherName)) - for anchorIndex in xrange(min(len(selfAnchors), len(otherAnchors))): - selfPt, selfAnchorName = selfAnchors[anchorIndex] - otherPt, otherAnchorName = otherAnchors[anchorIndex] - if selfAnchorName != otherAnchorName: - errors.append("Error: anchor %d in glyph %s and glyph %s don't have the same name." %(anchorIndex, selfName, otherName)) - return not fatalError, errors - - def _interpolateContours(self, factor, minContours, maxContours): - newContours = [] - for contourIndex in xrange(len(minContours)): - minContour = minContours[contourIndex] - maxContour = maxContours[contourIndex] - newContours.append([]) - for pointIndex in xrange(len(minContour)): - segType, pt, smooth, name = minContour[pointIndex] - minPoint = minContour[pointIndex][1] - maxPoint = maxContour[pointIndex][1] - newX, newY = _interpolatePt(minPoint, maxPoint, factor) - newContours[-1].append((segType, (newX, newY), smooth, name)) - return newContours - - def _interpolateComponents(self, factor, minComponents, maxComponents): - newComponents = [] - minComponents, maxComponents = self._mathComponentCompare(minComponents, maxComponents) - componentNames = minComponents.keys() - for componentName in componentNames: - minComponentList = minComponents[componentName] - maxComponentList = maxComponents[componentName] - for i in xrange(len(minComponentList)): - # transformation breakdown: xScale, xyScale, yxScale, yScale, xOffset, yOffset - minXScale, minXYScale, minYXScale, minYScale, minXOffset, minYOffset = minComponentList[i] - maxXScale, maxXYScale, maxYXScale, maxYScale, maxXOffset, maxYOffset = maxComponentList[i] - newXScale, newXYScale = _interpolatePt((minXScale, minXYScale), (maxXScale, maxXYScale), factor) - newYXScale, newYScale = _interpolatePt((minYXScale, minYScale), (maxYXScale, maxYScale), factor) - newXOffset, newYOffset = _interpolatePt((minXOffset, minYOffset), (maxXOffset, maxYOffset), factor) - newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) - return newComponents - - def _interpolateAnchors(self, factor, minAnchors, maxAnchors): - newAnchors = [] - minAnchors, maxAnchors = self._mathAnchorCompare(minAnchors, maxAnchors) - anchorNames = minAnchors.keys() - for anchorName in anchorNames: - minAnchorList = minAnchors[anchorName] - maxAnchorList = maxAnchors[anchorName] - for i in range(len(minAnchorList)): - minAnchor = minAnchorList[i] - maxAnchor = maxAnchorList[i] - newAnchor = _interpolatePt(minAnchor, maxAnchor, factor) - newAnchors.append((newAnchor, anchorName)) - return newAnchors - - # - # comparisons - # - - def __eq__(self, other): - if isinstance(other, BaseGlyph): - return self._getDigest() == other._getDigest() - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def _getDigest(self, pointsOnly=False): - """Calculate a digest of coordinates, points, things in this glyph. - With pointsOnly == True the digest consists of a flat tuple of all - coordinate pairs in the glyph, without the order of contours. - """ - from robofab.pens.digestPen import DigestPointPen - mp = DigestPointPen() - self.drawPoints(mp) - if pointsOnly: - return "%s|%d|%s"%(mp.getDigestPointsOnly(), self.width, self.unicode) - else: - return "%s|%d|%s"%(mp.getDigest(), self.width, self.unicode) - - def _getStructure(self): - """Calculate a digest of points, things in this glyph, but NOT coordinates.""" - from robofab.pens.digestPen import DigestPointStructurePen - mp = DigestPointStructurePen() - self.drawPoints(mp) - return mp.getDigest() - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def _get_box(self): - bounds = _box(self, fontObject=self.getParent()) - return bounds - - box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)") - - def _get_leftMargin(self): - if self.isEmpty(): - return 0 - xMin, yMin, xMax, yMax = self.box - return xMin - - def _set_leftMargin(self, value): - if self.isEmpty(): - self.width = self.width + value - else: - diff = value - self.leftMargin - self.move((diff, 0)) - self.width = self.width + diff - - leftMargin = property(_get_leftMargin, _set_leftMargin, doc="the left margin") - - def _get_rightMargin(self): - if self.isEmpty(): - return self.width - xMin, yMin, xMax, yMax = self.box - return self.width - xMax - - def _set_rightMargin(self, value): - if self.isEmpty(): - self.width = value - else: - xMin, yMin, xMax, yMax = self.box - self.width = xMax + value - - rightMargin = property(_get_rightMargin, _set_rightMargin, doc="the right margin") - - def copy(self, aParent=None): - """Duplicate this glyph""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - dont = ['_object', 'getParent'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - elif k == "contours": - dup = [] - for i in self.contours: - dup.append(i.copy(n)) - elif k == "components": - dup = [] - for i in self.components: - dup.append(i.copy(n)) - elif k == "anchors": - dup = [] - for i in self.anchors: - dup.append(i.copy(n)) - elif k == "psHints": - dup = self.psHints.copy() - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def _setParentTree(self): - """Set the parents of all contained and dependent objects (and their dependents) right.""" - for item in self.contours: - item.setParent(self) - item._setParentTree() - for item in self.components: - item.setParent(self) - for items in self.anchors: - item.setParent(self) - - def getGlyph(self, glyphName): - """Provided there is a font parent for this glyph, return a sibling glyph.""" - if glyphName == self.name: - return self - if self.getParent() is not None: - return self.getParent()[glyphName] - return None - - def getPen(self): - """Return a Pen object for creating an outline in this glyph.""" - from robofab.pens.adapterPens import SegmentToPointPen - return SegmentToPointPen(self.getPointPen()) - - def getPointPen(self): - """Return a PointPen object for creating an outline in this glyph.""" - raise NotImplementedError, "getPointPen() must be implemented by subclass" - - def deSelect(self): - """Set all selected attrs in glyph to False: for the glyph, components, anchors, points.""" - for a in self.anchors: - a.selected = False - for a in self.components: - a.selected = False - for c in self.contours: - for p in c.points: - p.selected = False - self.selected = False - - def isEmpty(self): - """return true if the glyph has no contours or components""" - if len(self.contours) + len(self.components) == 0: - return True - else: - return False - - def _saveToGlyphSet(self, glyphSet, glyphName=None, force=False): - """Save the glyph to GlyphSet, a private method that's part of the saving process.""" - # save stuff in the lib first - if force or self.changed: - if glyphName is None: - glyphName = self.name - glyphSet.writeGlyph(glyphName, self, self.drawPoints) - - def update(self): - """update the glyph""" - pass - - def draw(self, pen): - """draw the object with a RoboFab segment pen""" - try: - pen.setWidth(self.width) - if self.note is not None: - pen.setNote(self.note) - except AttributeError: - # FontTools pens don't have these methods - pass - for a in self.anchors: - a.draw(pen) - for c in self.contours: - c.draw(pen) - for c in self.components: - c.draw(pen) - try: - pen.doneDrawing() - except AttributeError: - # FontTools pens don't have a doneDrawing() method - pass - - def drawPoints(self, pen): - """draw the object with a point pen""" - for a in self.anchors: - a.drawPoints(pen) - for c in self.contours: - c.drawPoints(pen) - for c in self.components: - c.drawPoints(pen) - - def appendContour(self, aContour, offset=(0, 0)): - """append a contour to the glyph""" - x, y = offset - pen = self.getPointPen() - aContour.drawPoints(pen) - self.contours[-1].move((x, y)) - - def appendGlyph(self, aGlyph, offset=(0, 0)): - """append another glyph to the glyph""" - x, y = offset - pen = self.getPointPen() - #to handle the offsets, move the source glyph and then move it back! - aGlyph.move((x, y)) - aGlyph.drawPoints(pen) - aGlyph.move((-x, -y)) - - def round(self): - """round all coordinates in all contours, components and anchors""" - for n in self.contours: - n.round() - for n in self.components: - n.round() - for n in self.anchors: - n.round() - self.width = int(round(self.width)) - - def autoUnicodes(self): - """Using fontTools.agl, assign Unicode list to the glyph""" - from fontTools.agl import AGL2UV - if AGL2UV.has_key(self.name): - self.unicode = AGL2UV[self.name] - self._hasChanged() - - def pointInside(self, pt, evenOdd=0): - """determine if the point is in the black or white of the glyph""" - x, y = pt - from fontTools.pens.pointInsidePen import PointInsidePen - font = self.getParent() - piPen = PointInsidePen(glyphSet=font, testPoint=(x, y), evenOdd=evenOdd) - self.draw(piPen) - return piPen.getResult() - - def correctDirection(self, trueType=False): - """corect the direction of the contours in the glyph.""" - #this is a bit slow, but i'm not sure how much more it can be optimized. - #it also has a bug somewhere that is causeing some contours to be set incorrectly. - #try to run it on the copyright symbol to see the problem. hm. - # - #establish the default direction that an outer contour should follow - #i believe for TT this is clockwise and for PS it is counter - #i could be wrong about this, i need to double check. - from fontTools.pens.pointInsidePen import PointInsidePen - baseDirection = 0 - if trueType: - baseDirection = 1 - #we don't need to do all the work if the contour count is < 2 - count = len(self.contours) - if count == 0: - return - elif count == 1: - self.contours[0].clockwise = baseDirection - return - #store up needed before we start - #i think the .box calls are eating a big chunk of the time - contourDict = {} - for contourIndex in range(len(self.contours)): - contour = self.contours[contourIndex] - contourDict[contourIndex] = {'box':contour.box, 'dir':contour.clockwise, 'hit':[], 'notHit':[]} - #now, for every contour, determine which contours it intersects - #as we go, we will also store contours that it doesn't intersct - #and we store this value for both contours - allIndexes = contourDict.keys() - for contourIndex in allIndexes: - for otherContourIndex in allIndexes: - if otherContourIndex != contourIndex: - if contourIndex not in contourDict[otherContourIndex]['hit'] and contourIndex not in contourDict[otherContourIndex]['notHit']: - xMin1, yMin1, xMax1, yMax1 = contourDict[contourIndex]['box'] - xMin2, yMin2, xMax2, yMax2= contourDict[otherContourIndex]['box'] - hit, pos = sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)) - if hit == 1: - contourDict[contourIndex]['hit'].append(otherContourIndex) - contourDict[otherContourIndex]['hit'].append(contourIndex) - else: - contourDict[contourIndex]['notHit'].append(otherContourIndex) - contourDict[otherContourIndex]['notHit'].append(contourIndex) - #set up the pen here to shave a bit of time - font = self.getParent() - piPen = PointInsidePen(glyphSet=font, testPoint=(0, 0), evenOdd=0) - #now do the pointInside work - for contourIndex in allIndexes: - direction = baseDirection - contour = self.contours[contourIndex] - startPoint = contour.segments[0].onCurve - if startPoint is not None: #skip TT paths with no onCurve - if len(contourDict[contourIndex]['hit']) != 0: - for otherContourIndex in contourDict[contourIndex]['hit']: - piPen.setTestPoint(testPoint=(startPoint.x, startPoint.y)) - otherContour = self.contours[otherContourIndex] - otherContour.draw(piPen) - direction = direction + piPen.getResult() - newDirection = direction % 2 - #now set the direction if we need to - if newDirection != contourDict[contourIndex]['dir']: - contour.reverseContour() - - def autoContourOrder(self): - """attempt to sort the contours based on their centers""" - # sort is based on (in this order): - # - the (negative) point count - # - the (negative) segment count - # - fuzzy x value of the center of the contour - # - fuzzy y value of the center of the contour - # - the (negative) surface of the bounding box of the contour: width * height - # the latter is a safety net for for instances like a very thin 'O' where the - # x centers could be close enough to rely on the y for the sort which could - # very well be the same for both contours. We use the _negative_ of the surface - # to ensure that larger contours appear first, which seems more natural. - tempContourList = [] - contourList = [] - xThreshold = None - yThreshold = None - for contour in self.contours: - xMin, yMin, xMax, yMax = contour.box - width = xMax - xMin - height = yMax - yMin - xC = 0.5 * (xMin + xMax) - yC = 0.5 * (yMin + yMax) - xTh = abs(width * .5) - yTh = abs(height * .5) - if xThreshold is None or xThreshold > xTh: - xThreshold = xTh - if yThreshold is None or yThreshold > yTh: - yThreshold = yTh - tempContourList.append((-len(contour.points), -len(contour.segments), xC, yC, -(width * height), contour)) - for points, segments, x, y, surface, contour in tempContourList: - contourList.append((points, segments, FuzzyNumber(x, xThreshold), FuzzyNumber(y, yThreshold), surface, contour)) - contourList.sort() - for i in range(len(contourList)): - points, segments, xO, yO, surface, contour = contourList[i] - contour.index = i - - def rasterize(self, cellSize=50, xMin=None, yMin=None, xMax=None, yMax=None): - """ - Slice the glyph into a grid based on the cell size. - It returns a list of lists containing bool values - that indicate the black (True) or white (False) - value of that particular cell. These lists are - arranged from top to bottom of the glyph and - proceed from left to right. - This is an expensive operation! - """ - from fontTools.pens.pointInsidePen import PointInsidePen - piPen = PointInsidePen(glyphSet=self.getParent(), testPoint=(0, 0), evenOdd=0) - if xMin is None or yMin is None or xMax is None or yMax is None: - _xMin, _yMin, _xMax, _yMax = self.box - if xMin is None: - xMin = _xMin - if yMin is None: - yMin = _yMin - if xMax is None: - xMax = _xMax - if yMax is None: - yMax = _yMax - # - hitXMax = False - hitYMin = False - xSlice = 0 - ySlice = 0 - halfCellSize = cellSize / 2.0 - # - map = [] - # - while not hitYMin: - map.append([]) - yScan = -(ySlice * cellSize) + yMax - halfCellSize - if yScan < yMin: - hitYMin = True - while not hitXMax: - xScan = (xSlice * cellSize) + xMin - halfCellSize - if xScan > xMax: - hitXMax = True - piPen.setTestPoint((xScan, yScan)) - self.draw(piPen) - test = piPen.getResult() - if test: - map[-1].append(True) - else: - map[-1].append(False) - xSlice = xSlice + 1 - hitXMax = False - xSlice = 0 - ySlice = ySlice + 1 - return map - - def move(self, pt, contours=True, components=True, anchors=True): - """Move a glyph's items that are flagged as True""" - x, y = roundPt(pt) - if contours: - for contour in self.contours: - contour.move((x, y)) - if components: - for component in self.components: - component.move((x, y)) - if anchors: - for anchor in self.anchors: - anchor.move((x, y)) - - def scale(self, pt, center=(0, 0)): - """scale the glyph""" - x, y = pt - for contour in self.contours: - contour.scale((x, y), center=center) - for component in self.components: - offset = component.offset - component.offset = _scalePointFromCenter(offset, pt, center) - sX, sY = component.scale - component.scale = (sX*x, sY*y) - for anchor in self.anchors: - anchor.scale((x, y), center=center) - - def transform(self, matrix): - """Transform this glyph. - Use a Transform matrix object from - robofab.transform""" - n = [] - for c in self.contours: - c.transform(matrix) - for a in self.anchors: - a.transform(matrix) - - def rotate(self, angle, offset=None): - """rotate the glyph""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.rotate(radAngle) - rT = rT.translate(-offset[0], -offset[1]) - self.transform(rT) - - def skew(self, angle, offset=None): - """skew the glyph""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.skew(radAngle) - self.transform(rT) - - -class BaseContour(RBaseObject): - - """Base class for all contour objects.""" - - def __init__(self): - RBaseObject.__init__(self) - #self.index = None - self.changed = False # if the object needs to be saved - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - glyphParent = self.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - try: - idx = `self.index` - except ValueError: - # XXXX - idx = "XXX" - return "<RContour for %s.%s[%s]>"%(font, glyph, idx) - - def __len__(self): - return len(self.segments) - - def __mul__(self, factor): - warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.segments = [] - for i in range(len(self.segments)): - n.segments.append(self.segments[i] * factor) - n._setParentTree() - return n - - __rmul__ = __mul__ - - def __add__(self, other): - warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.segments = [] - for i in range(len(self.segments)): - n.segments.append(self.segments[i] + other.segments[i]) - n._setParentTree() - return n - - def __sub__(self, other): - warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.segments = [] - for i in range(len(self.segments)): - n.segments.append(self.segments[i] - other.segments[i]) - n._setParentTree() - return n - - def __getitem__(self, index): - return self.segments[index] - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def _nextSegment(self, segmentIndex): - return self.segments[(segmentIndex + 1) % len(self.segments)] - - def _prevSegment(self, segmentIndex): - segments = self.segments - return self.segments[(segmentIndex - 1) % len(self.segments)] - - def _get_box(self): - bounds = _box(self) - return bounds - - box = property(_get_box, doc="the bounding box for the contour") - - def _set_clockwise(self, value): - if self.clockwise != value: - self.reverseContour() - - def _get_clockwise(self): - pen = AreaPen(self) - self.draw(pen) - return pen.value < 0 - - clockwise = property(_get_clockwise, _set_clockwise, doc="direction of contour: positive=counterclockwise negative=clockwise") - - def copy(self, aParent=None): - """Duplicate this contour""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['_object', 'points', 'bPoints', 'getParent'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - elif k == "segments": - dup = [] - for i in self.segments: - dup.append(i.copy(n)) - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def _setParentTree(self): - """Set the parents of all contained and dependent objects (and their dependents) right.""" - for item in self.segments: - item.setParent(self) - - def round(self): - """round the value of all points in the contour""" - for n in self.points: - n.round() - - def draw(self, pen): - """draw the object with a fontTools pen""" - firstOn = self.segments[0].onCurve - firstType = self.segments[0].type - lastOn = self.segments[-1].onCurve - # this is a special exception for FontLab - # FL can have a contour that does not contain a move. - # this will only happen if the contour begins with a qcurve. - # in this case, we move to the segment's on curve, - # then we iterate through the rest of the points, - # then we add the first qcurve and finally we - # close the path. after this, i say "ugh." - if firstType == QCURVE: - pen.moveTo((firstOn.x, firstOn.y)) - for segment in self.segments[1:]: - segmentType = segment.type - pt = segment.onCurve.x, segment.onCurve.y - if segmentType == LINE: - pen.lineTo(pt) - elif segmentType == CURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.curveTo(*pts) - elif segmentType == QCURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.qCurveTo(*pts) - else: - assert 0, "unsupported segment type" - pts = [(point.x, point.y) for point in self.segments[0].points] - pen.qCurveTo(*pts) - pen.closePath() - else: - if firstType == MOVE and (firstOn.x, firstOn.y) == (lastOn.x, lastOn.y): - closed = True - else: - closed = True - for segment in self.segments: - segmentType = segment.type - pt = segment.onCurve.x, segment.onCurve.y - if segmentType == MOVE: - pen.moveTo(pt) - elif segmentType == LINE: - pen.lineTo(pt) - elif segmentType == CURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.curveTo(*pts) - elif segmentType == QCURVE: - pts = [(point.x, point.y) for point in segment.points] - pen.qCurveTo(*pts) - else: - assert 0, "unsupported segment type" - if closed: - pen.closePath() - else: - pen.endPath() - - def drawPoints(self, pen): - """draw the object with a point pen""" - pen.beginPath() - lastOn = self.segments[-1].onCurve - didLastOn = False - flQCurveException = False - lastIndex = len(self.segments) - 1 - for i in range(len(self.segments)): - segment = self.segments[i] - segmentType = segment.type - # the new protocol states that we start with an onCurve - # so, if we have a move and a nd a last point overlapping, - # add the last point to the beginning and skip the move - if segmentType == MOVE and (segment.onCurve.x, segment.onCurve.y) == (lastOn.x, lastOn.y): - point = self.segments[-1].onCurve - name = getattr(segment.onCurve, 'name', None) - pen.addPoint((point.x, point.y), point.type, smooth=self.segments[-1].smooth, name=name) - didLastOn = True - continue - # this is an exception for objectsFL - # the problem is that quad contours are - # represented differently that they are in - # objectsRF: - # FL: [qcurve, qcurve, qcurve, qcurve] - # RF: [move, qcurve, qcurve, qcurve, qcurve] - # so, we need to catch this, and shift the offCurves to - # to the end of the contour - if i == 0 and segmentType == QCURVE: - flQCurveException = True - if segmentType == MOVE: - segmentType = LINE - ## the offCurves - if i == 0 and flQCurveException: - pass - else: - for point in segment.offCurve: - name = getattr(point, 'name', None) - pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected) - ## the onCurve - # skip the last onCurve if it was used as the move - if i == lastIndex and didLastOn: - continue - point = segment.onCurve - name = getattr(point, 'name', None) - pen.addPoint((point.x, point.y), segmentType, smooth=segment.smooth, name=name, selected=point.selected) - # if we have the special qCurve case with objectsFL - # take care of the offCurves associated with the first contour - if flQCurveException: - for point in self.segments[0].offCurve: - name = getattr(point, 'name', None) - pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected) - pen.endPath() - - def move(self, pt): - """move the contour""" - #this will be faster if we go straight to the points - for point in self.points: - point.move(pt) - - def scale(self, pt, center=(0, 0)): - """scale the contour""" - #this will be faster if we go straight to the points - for point in self.points: - point.scale(pt, center=center) - - def transform(self, matrix): - """Transform this contour. - Use a Transform matrix object from - robofab.transform""" - n = [] - for s in self.segments: - s.transform(matrix) - - def rotate(self, angle, offset=None): - """rotate the contour""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.rotate(radAngle) - self.transform(rT) - - def skew(self, angle, offset=None): - """skew the contour""" - from fontTools.misc.transform import Identity - radAngle = angle / DEGREE # convert from degrees to radians - if offset is None: - offset = (0,0) - rT = Identity.translate(offset[0], offset[1]) - rT = rT.skew(radAngle) - self.transform(rT) - - def pointInside(self, pt, evenOdd=0): - """determine if the point is inside or ouside of the contour""" - from fontTools.pens.pointInsidePen import PointInsidePen - glyph = self.getParent() - font = glyph.getParent() - piPen = PointInsidePen(glyphSet=font, testPoint=pt, evenOdd=evenOdd) - self.draw(piPen) - return piPen.getResult() - - def autoStartSegment(self): - """automatically set the lower left point of the contour as the first point.""" - #adapted from robofog - startIndex = 0 - startSegment = self.segments[0] - for i in range(len(self.segments)): - segment = self.segments[i] - startOn = startSegment.onCurve - on = segment.onCurve - if on.y <= startOn.y: - if on.y == startOn.y: - if on.x < startOn.x: - startSegment = segment - startIndex = i - else: - startSegment = segment - startIndex = i - if startIndex != 0: - self.setStartSegment(startIndex) - - def appendBPoint(self, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)): - """append a bPoint to the contour""" - self.insertBPoint(len(self.segments), pointType=pointType, anchor=anchor, bcpIn=bcpIn, bcpOut=bcpOut) - - def insertBPoint(self, index, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)): - """insert a bPoint at index on the contour""" - #insert a CURVE point that we can work with - nextSegment = self._nextSegment(index-1) - if nextSegment.type == QCURVE: - return - if nextSegment.type == MOVE: - prevSegment = self.segments[index-1] - prevOn = prevSegment.onCurve - if bcpIn != (0, 0): - new = self.appendSegment(CURVE, [(prevOn.x, prevOn.y), absoluteBCPIn(anchor, bcpIn), anchor], smooth=False) - if pointType == CURVE: - new.smooth = True - else: - new = self.appendSegment(LINE, [anchor], smooth=False) - #if the user wants an outgoing bcp, we must add a CURVE ontop of the move - if bcpOut != (0, 0): - nextOn = nextSegment.onCurve - self.appendSegment(CURVE, [absoluteBCPOut(anchor, bcpOut), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False) - else: - #handle the bcps - if nextSegment.type != CURVE: - prevSegment = self.segments[index-1] - prevOn = prevSegment.onCurve - prevOutX, prevOutY = (prevOn.x, prevOn.y) - else: - prevOut = nextSegment.offCurve[0] - prevOutX, prevOutY = (prevOut.x, prevOut.y) - self.insertSegment(index, segmentType=CURVE, points=[(prevOutX, prevOutY), anchor, anchor], smooth=False) - newSegment = self.segments[index] - prevSegment = self._prevSegment(index) - nextSegment = self._nextSegment(index) - if nextSegment.type == MOVE: - raise RoboFabError, 'still working out curving at the end of a contour' - elif nextSegment.type == QCURVE: - return - #set the new incoming bcp - newIn = newSegment.offCurve[1] - nIX, nIY = absoluteBCPIn(anchor, bcpIn) - newIn.x = nIX - newIn.y = nIY - #set the new outgoing bcp - hasCurve = True - if nextSegment.type != CURVE: - if bcpOut != (0, 0): - nextSegment.type = CURVE - hasCurve = True - else: - hasCurve = False - if hasCurve: - newOut = nextSegment.offCurve[0] - nOX, nOY = absoluteBCPOut(anchor, bcpOut) - newOut.x = nOX - newOut.y = nOY - #now check to see if we can convert the CURVE segment to a LINE segment - newAnchor = newSegment.onCurve - newA = newSegment.offCurve[0] - newB = newSegment.offCurve[1] - nextAnchor = nextSegment.onCurve - prevAnchor = prevSegment.onCurve - if (prevAnchor.x, prevAnchor.y) == (newA.x, newA.y) and (newAnchor.x, newAnchor.y) == (newB.x, newB.y): - newSegment.type = LINE - #the user wants a smooth segment - if pointType == CURVE: - newSegment.smooth = True - - -class BaseSegment(RBaseObject): - - """Base class for all segment objects""" - - def __init__(self): - self.changed = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - contourParent = self.getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - try: - idx = `self.index` - except ValueError: - idx = "XXX" - return "<RSegment for %s.%s[%s][%s]>"%(font, glyph, contourIndex, idx) - - def __mul__(self, factor): - warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.points = [] - for i in range(len(self.points)): - n.points.append(self.points[i] * factor) - n._setParentTree() - return n - - __rmul__ = __mul__ - - def __add__(self, other): - warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.points = [] - for i in range(len(self.points)): - n.points.append(self.points[i] + other.points[i]) - return n - - def __sub__(self, other): - warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) - n = self.copy() - n.points = [] - for i in range(len(self.points)): - n.points.append(self.points[i] - other.points[i]) - return n - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this segment""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['_object', 'getParent', 'offCurve', 'onCurve'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - if k == "points": - dup = [] - for i in self.points: - dup.append(i.copy(n)) - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def _setParentTree(self): - """Set the parents of all contained and dependent objects (and their dependents) right.""" - for item in self.points: - item.setParent(self) - - def round(self): - """round all points in the segment""" - for point in self.points: - point.round() - - def move(self, pt): - """move the segment""" - for point in self.points: - point.move(pt) - - def scale(self, pt, center=(0, 0)): - """scale the segment""" - for point in self.points: - point.scale(pt, center=center) - - def transform(self, matrix): - """Transform this segment. - Use a Transform matrix object from - robofab.transform""" - n = [] - for p in self.points: - p.transform(matrix) - - def _get_onCurve(self): - return self.points[-1] - - def _get_offCurve(self): - return self.points[:-1] - - offCurve = property(_get_offCurve, doc="on curve point for the segment") - onCurve = property(_get_onCurve, doc="list of off curve points for the segment") - - - -class BasePoint(RBaseObject): - - """Base class for point objects.""" - - def __init__(self): - #RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - segmentIndex = "unknown_segment" - segmentParent = self.getParent() - if segmentParent is not None: - try: - segmentIndex = `segmentParent.index` - except AttributeError: pass - contourParent = self.getParent().getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RPoint for %s.%s[%s][%s]>"%(font, glyph, contourIndex, segmentIndex) - - def __add__(self, other): - warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one point to another - n = self.copy() - n.x, n.y = addPt((self.x, self.y), (other.x, other.y)) - return n - - def __sub__(self, other): - warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) - #Subtract one point from another - n = self.copy() - n.x, n.y = subPt((self.x, self.y), (other.x, other.y)) - return n - - def __mul__(self, factor): - warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the point with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.x, n.y = mulPt((self.x, self.y), factor) - return n - - __rmul__ = __mul__ - - def _hasChanged(self): - #mark the object and it's parent as changed - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this point""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent', 'offCurve', 'onCurve'] - for k in self.__dict__.keys(): - ok = True - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - if ok: - setattr(n, k, dup) - return n - - def select(self, state=True): - """Set the selection of this point. - XXXX This method should be a lot more versatile, dealing with - different kinds of selection, select the bcp's seperately etc. - But that's for later when we need it more. For now it's just - one flag for the entire thing.""" - self.selected = state - - def round(self): - """round the values in the point""" - self.x, self.y = roundPt((self.x, self.y)) - - def move(self, pt): - """Move the point""" - self.x, self.y = addPt((self.x, self.y), pt) - - def scale(self, pt, center=(0, 0)): - """scale the point""" - nX, nY = _scalePointFromCenter((self.x, self.y), pt, center) - self.x = nX - self.y = nY - - def transform(self, matrix): - """Transform this point. Use a Transform matrix - object from fontTools.misc.transform""" - self.x, self.y = matrix.transformPoint((self.x, self.y)) - - -class BaseBPoint(RBaseObject): - - """Base class for bPoints objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - segmentIndex = "unknown_segment" - segmentParent = self.getParent() - if segmentParent is not None: - try: - segmentIndex = `segmentParent.index` - except AttributeError: pass - contourParent = segmentParent.getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RBPoint for %s.%s[%s][%s][%s]>"%(font, glyph, contourIndex, segmentIndex, `self.index`) - - - def __add__(self, other): - warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one bPoint to another - n = self.copy() - n.anchor = addPt(self.anchor, other.anchor) - n.bcpIn = addPt(self.bcpIn, other.bcpIn) - n.bcpOut = addPt(self.bcpOut, other.bcpOut) - return n - - def __sub__(self, other): - warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) - #Subtract one bPoint from another - n = self.copy() - n.anchor = subPt(self.anchor, other.anchor) - n.bcpIn = subPt(self.bcpIn, other.bcpIn) - n.bcpOut = subPt(self.bcpOut, other.bcpOut) - return n - - def __mul__(self, factor): - warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the bPoint with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.anchor = mulPt(self.anchor, factor) - n.bcpIn = mulPt(self.bcpIn, factor) - n.bcpOut = mulPt(self.bcpOut, factor) - return n - - __rmul__ = __mul__ - - def _hasChanged(self): - #mark the object and it's parent as changed - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def select(self, state=True): - """Set the selection of this point. - XXXX This method should be a lot more versatile, dealing with - different kinds of selection, select the bcp's seperately etc. - But that's for later when we need it more. For now it's just - one flag for the entire thing.""" - self.selected = state - - def round(self): - """Round the coordinates to integers""" - self.anchor = roundPt(self.anchor) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = roundPt(self.bcpIn) - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = roundPt(self.bcpOut) - - def move(self, pt): - """move the bPoint""" - x, y = pt - bcpIn = self.bcpIn - bcpOut = self.bcpOut - self.anchor = (self.anchor[0] + x, self.anchor[1] + y) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = bcpIn - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = bcpOut - - def scale(self, pt, center=(0, 0)): - """scale the bPoint""" - x, y = pt - centerX, centerY = center - ogCenter = (centerX, centerY) - scaledCenter = (centerX * x, centerY * y) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - anchor = self.anchor - bcpIn = self.bcpIn - bcpOut = self.bcpOut - self.anchor = ((anchor[0] * x) - shiftVal[0], (anchor[1] * y) - shiftVal[1]) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = ((bcpIn[0] * x), (bcpIn[1] * y)) - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = ((bcpOut[0] * x), (bcpOut[1] * y)) - - def transform(self, matrix): - """Transform this point. Use a Transform matrix - object from fontTools.misc.transform""" - self.anchor = matrix.transformPoint(self.anchor) - pSeg = self._parentSegment - if pSeg.type != MOVE: - self.bcpIn = matrix.transformPoint(self.bcpIn) - if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: - self.bcpOut = matrix.transformPoint(self.bcpOut) - - def _get__anchorPoint(self): - return self._parentSegment.onCurve - - _anchorPoint = property(_get__anchorPoint, doc="the oncurve point in the parent segment") - - def _get_anchor(self): - point = self._anchorPoint - return (point.x, point.y) - - def _set_anchor(self, value): - x, y = value - point = self._anchorPoint - point.x = x - point.y = y - - anchor = property(_get_anchor, _set_anchor, doc="the position of the anchor") - - def _get_bcpIn(self): - pSeg = self._parentSegment - pCount = len(pSeg.offCurve) - if pCount == 2: - p = pSeg.offCurve[1] - pOn = pSeg.onCurve - return relativeBCPIn((pOn.x, pOn.y), (p.x, p.y)) - else: - return (0, 0) - - def _set_bcpIn(self, value): - x, y = (absoluteBCPIn(self.anchor, value)) - pSeg = self._parentSegment - if pSeg.type == MOVE: - #the user wants to have a bcp leading into the MOVE - if value == (0, 0) and self.bcpOut == (0, 0): - #we have a straight line between the two anchors - pass - else: - #we need to insert a new CURVE segment ontop of the move - contour = self._parentSegment.getParent() - #set the prev segment outgoing bcp to the onCurve - prevSeg = contour._prevSegment(self._parentSegment.index) - prevOn = prevSeg.onCurve - contour.appendSegment(CURVE, [(prevOn.x, prevOn.y), (x, y), self.anchor], smooth=False) - else: - pCount = len(pSeg.offCurve) - if pCount == 2: - #if the two points in the offCurvePoints list are located at the - #anchor coordinates we can switch to a LINE segment type - if value == (0, 0) and self.bcpOut == (0, 0): - pSeg.type = LINE - pSeg.smooth = False - else: - pSeg.offCurve[1].x = x - pSeg.offCurve[1].y = y - elif value != (0, 0): - pSeg.type = CURVE - pSeg.offCurve[1].x = x - pSeg.offCurve[1].y = y - - bcpIn = property(_get_bcpIn, _set_bcpIn, doc="the (x,y) for the incoming bcp") - - def _get_bcpOut(self): - pSeg = self._parentSegment - nextSeg = pSeg.getParent()._nextSegment(pSeg.index) - nsCount = len(nextSeg.offCurve) - if nsCount == 2: - p = nextSeg.offCurve[0] - return relativeBCPOut(self.anchor, (p.x, p.y)) - else: - return (0, 0) - - def _set_bcpOut(self, value): - x, y = (absoluteBCPOut(self.anchor, value)) - pSeg = self._parentSegment - nextSeg = pSeg.getParent()._nextSegment(pSeg.index) - if nextSeg.type == MOVE: - if value == (0, 0) and self.bcpIn == (0, 0): - pass - else: - #we need to insert a new CURVE segment ontop of the move - contour = self._parentSegment.getParent() - nextOn = nextSeg.onCurve - contour.appendSegment(CURVE, [(x, y), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False) - else: - nsCount = len(nextSeg.offCurve) - if nsCount == 2: - #if the two points in the offCurvePoints list are located at the - #anchor coordinates we can switch to a LINE segment type - if value == (0, 0) and self.bcpIn == (0, 0): - nextSeg.type = LINE - nextSeg.smooth = False - else: - nextSeg.offCurve[0].x = x - nextSeg.offCurve[0].y = y - elif value != (0, 0): - nextSeg.type = CURVE - nextSeg.offCurve[0].x = x - nextSeg.offCurve[0].y = y - - bcpOut = property(_get_bcpOut, _set_bcpOut, doc="the (x,y) for the outgoing bcp") - - def _get_type(self): - pType = self._parentSegment.type - bpType = CORNER - if pType == CURVE: - if self._parentSegment.smooth: - bpType = CURVE - return bpType - - def _set_type(self, pointType): - pSeg = self._parentSegment - segType = pSeg.type - #user wants a curve where there is a line - if pointType == CURVE and segType == LINE: - pSeg.type = CURVE - pSeg.smooth = True - #the anchor is a curve segment. so, all we need to do is turn the smooth off - elif pointType == CORNER and segType == CURVE: - pSeg.smooth = False - - type = property(_get_type, _set_type, doc="the type of bPoint, either 'corner' or 'curve'") - - -class BaseComponent(RBaseObject): - - """Base class for all component objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - glyphParent = self.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RComponent for %s.%s.components[%s]>"%(font, glyph, `self.index`) - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this component.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent', '_object'] - for k in self.__dict__.keys(): - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - - def __add__(self, other): - warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one Component to another - n = self.copy() - n.offset = addPt(self.offset, other.offset) - n.scale = addPt(self.scale, other.scale) - return n - - def __sub__(self, other): - warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) - #Subtract one Component from another - n = self.copy() - n.offset = subPt(self.offset, other.offset) - n.scale = subPt(self.scale, other.scale) - return n - - def __mul__(self, factor): - warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the Component with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.offset = mulPt(self.offset, factor) - n.scale = mulPt(self.scale, factor) - return n - - __rmul__ = __mul__ - - def _get_box(self): - parentGlyph = self.getParent() - # the component is an orphan - if parentGlyph is None: - return None - parentFont = parentGlyph.getParent() - # the glyph that contains the component - # does not hae a parent - if parentFont is None: - return None - # the font does not have a glyph - # that matches the glyph that - # this component references - if not parentFont.has_key(self.baseGlyph): - return None - return _box(self, parentFont) - - box = property(_get_box, doc="the bounding box of the component: (xMin, yMin, xMax, yMax)") - - def round(self): - """round the offset values""" - self.offset = roundPt(self.offset) - self._hasChanged() - - def draw(self, pen): - """Segment pen drawing method.""" - if isinstance(pen, AbstractPen): - # It's a FontTools pen, which for addComponent is identical - # to PointPen. - self.drawPoints(pen) - else: - # It's an "old" 'Fab pen - pen.addComponent(self.baseGlyph, self.offset, self.scale) - - def drawPoints(self, pen): - """draw the object with a point pen""" - oX, oY = self.offset - sX, sY = self.scale - #xScale, xyScale, yxScale, yScale, xOffset, yOffset - pen.addComponent(self.baseGlyph, (sX, 0, 0, sY, oX, oY)) - - -class BaseAnchor(RBaseObject): - - """Base class for all anchor point objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - glyphParent = self.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RAnchor for %s.%s.anchors[%s]>"%(font, glyph, `self.index`) - - def __add__(self, other): - warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) - #Add one anchor to another - n = self.copy() - n.x, n.y = addPt((self.x, self.y), (other.x, other.y)) - return n - - def __sub__(self, other): - warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) - #Substract one anchor from another - n = self.copy() - n.x, n.y = subPt((self.x, self.y), (other.x, other.y)) - return n - - def __mul__(self, factor): - warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) - #Multiply the anchor with factor. Factor can be a tuple of 2 *(f1, f2) - n = self.copy() - n.x, n.y = mulPt((self.x, self.y), factor) - return n - - __rmul__ = __mul__ - - def _hasChanged(self): - #mark the object and it's parent as changed - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def copy(self, aParent=None): - """Duplicate this anchor.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - dont = ['getParent', '_object'] - for k in self.__dict__.keys(): - if k in dont: - continue - elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): - dup = self.__dict__[k].copy(n) - else: - dup = copy.deepcopy(self.__dict__[k]) - setattr(n, k, dup) - return n - - def round(self): - """round the values in the anchor""" - self.x, self.y = roundPt((self.x, self.y)) - self._hasChanged() - - def draw(self, pen): - """Draw the object onto a segment pen""" - if isinstance(pen, AbstractPen): - # It's a FontTools pen - pen.moveTo((self.x, self.y)) - pen.endPath() - else: - # It's an "old" 'Fab pen - pen.addAnchor(self.name, (self.x, self.y)) - - def drawPoints(self, pen): - """draw the object with a point pen""" - pen.beginPath() - pen.addPoint((self.x, self.y), segmentType="move", smooth=False, name=self.name) - pen.endPath() - - def move(self, pt): - """Move the anchor""" - x, y = pt - pX, pY = self.position - self.position = (pX+x, pY+y) - - def scale(self, pt, center=(0, 0)): - """scale the anchor""" - pos = self.position - self.position = _scalePointFromCenter(pos, pt, center) - - def transform(self, matrix): - """Transform this anchor. Use a Transform matrix - object from fontTools.misc.transform""" - self.x, self.y = matrix.transformPoint((self.x, self.y)) - - -class BaseGuide(RBaseObject): - - """Base class for all guide objects.""" - - def __init__(self): - RBaseObject.__init__(self) - self.changed = False # if the object needs to be saved - self.selected = False - - -class BaseInfo(RBaseObject): - - _baseAttributes = ["_object", "changed", "selected", "getParent"] - _deprecatedAttributes = ufoLib.deprecatedFontInfoAttributesVersion2 - _infoAttributes = ufoLib.fontInfoAttributesVersion2 - # subclasses may define a list of environment - # specific attributes that can be retrieved or set. - _environmentAttributes = [] - # subclasses may define a list of attributes - # that should not follow the standard get/set - # order provided by __setattr__ and __getattr__. - # for these attributes, the environment specific - # set and get methods must handle this value - # without any pre-call validation. - # (yeah. this is because of some FontLab dumbness.) - _environmentOverrides = [] - - def __setattr__(self, attr, value): - # check to see if the attribute has been - # deprecated. if so, warn the caller and - # update the attribute and value. - if attr in self._deprecatedAttributes: - newAttr, newValue = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) - note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) - warn(note, DeprecationWarning) - attr = newAttr - value = newValue - # setting a known attribute - if attr in self._infoAttributes or attr in self._environmentAttributes: - # lightly test the validity of the value - if value is not None: - isValidValue = ufoLib.validateFontInfoVersion2ValueForAttribute(attr, value) - if not isValidValue: - raise RoboFabError("Invalid value (%s) for attribute (%s)." % (repr(value), attr)) - # use the environment specific info attr set - # method if it is defined. - if hasattr(self, "_environmentSetAttr"): - self._environmentSetAttr(attr, value) - # fallback to super - else: - super(BaseInfo, self).__setattr__(attr, value) - # unknown attribute, test to see if it is a python attr - elif attr in self.__dict__ or attr in self._baseAttributes: - super(BaseInfo, self).__setattr__(attr, value) - # raise an attribute error - else: - raise AttributeError("Unknown attribute %s." % attr) - - # subclasses with environment specific attr setting can - # implement this method. __setattr__ will call it if present. - # def _environmentSetAttr(self, attr, value): - # pass - - def __getattr__(self, attr): - if attr in self._environmentOverrides: - return self._environmentGetAttr(attr) - # check to see if the attribute has been - # deprecated. if so, warn the caller and - # flag the value as needing conversion. - needValueConversionTo1 = False - if attr in self._deprecatedAttributes: - oldAttr = attr - oldValue = attr - newAttr, x = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, None) - note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) - warn(note, DeprecationWarning) - attr = newAttr - needValueConversionTo1 = True - # getting a known attribute - if attr in self._infoAttributes or attr in self._environmentAttributes: - # use the environment specific info attr get - # method if it is defined. - if hasattr(self, "_environmentGetAttr"): - value = self._environmentGetAttr(attr) - # fallback to super - else: - try: - value = super(BaseInfo, self).__getattribute__(attr) - except AttributeError: - return None - if needValueConversionTo1: - oldAttr, value = ufoLib.convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) - return value - # raise an attribute error - else: - raise AttributeError("Unknown attribute %s." % attr) - - # subclasses with environment specific attr retrieval can - # implement this method. __getattr__ will call it if present. - # it should return the requested value. - # def _environmentGetAttr(self, attr): - # pass - -class BaseFeatures(RBaseObject): - - def __init__(self): - RBaseObject.__init__(self) - self._text = "" - - def _get_text(self): - return self._text - - def _set_text(self, value): - assert isinstance(value, basestring) - self._text = value - - text = property(_get_text, _set_text, doc="raw feature text.") - - -class BaseGroups(dict): - - """Base class for all RFont.groups objects""" - - def __init__(self): - pass - - def __repr__(self): - font = "unnamed_font" - fontParent = self.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RGroups for %s>"%font - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - pass - - def setParent(self, parent): - import weakref - self.__dict__['getParent'] = weakref.ref(parent) - - def __setitem__(self, key, value): - #override base class to insure proper data is being stored - if not isinstance(key, str): - raise RoboFabError, 'key must be a string' - if not isinstance(value, list): - raise RoboFabError, 'group must be a list' - super(BaseGroups, self).__setitem__(key, value) - - def findGlyph(self, glyphName): - """return a list of all groups contianing glyphName""" - found = [] - for i in self.keys(): - l = self[i] - if glyphName in l: - found.append(i) - return found - - -class BaseLib(dict): - - """Base class for all lib objects""" - - def __init__(self): - pass - - def __repr__(self): - #this is a doozy! - parent = "unknown_parent" - parentObject = self.getParent() - if parentObject is not None: - #do we have a font? - try: - parent = parentObject.info.postscriptFullName - except AttributeError: - #or do we have a glyph? - try: - parent = parentObject.name - #we must be an orphan - except AttributeError: pass - return "<RLib for %s>"%parent - - def getParent(self): - """this method will be overwritten with a weakref if there is a parent.""" - pass - - def setParent(self, parent): - import weakref - self.__dict__['getParent'] = weakref.ref(parent) - - def copy(self, aParent=None): - """Duplicate this lib.""" - n = self.__class__() - if aParent is not None: - n.setParent(aParent) - elif self.getParent() is not None: - n.setParent(self.getParent()) - for k in self.keys(): - n[k] = copy.deepcopy(self[k]) - return n - - -class BaseKerning(RBaseObject): - - """Base class for all kerning objects. Object behaves like a dict but has - some special kerning specific tricks.""" - - def __init__(self, kerningDict=None): - if not kerningDict: - kerningDict = {} - self._kerning = kerningDict - self.changed = False # if the object needs to be saved - - def __repr__(self): - font = "unnamed_font" - fontParent = self.getParent() - if fontParent is not None: - try: - font = fontParent.info.postscriptFullName - except AttributeError: pass - return "<RKerning for %s>"%font - - def __getitem__(self, key): - if isinstance(key, tuple): - pair = key - return self.get(pair) - elif isinstance(key, str): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - keys = self.keys() - if key > len(keys): - raise IndexError - keys.sort() - pair = keys[key] - if not self._kerning.has_key(pair): - raise IndexError - else: - return pair - - def __setitem__(self, pair, value): - if not isinstance(pair, tuple): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if len(pair) != 2: - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if value == 0: - if self._kerning.get(pair) is not None: - del self._kerning[pair] - else: - self._kerning[pair] = value - self._hasChanged() - - def __len__(self): - return len(self._kerning.keys()) - - def _hasChanged(self): - """mark the object and it's parent as changed""" - self.setChanged(True) - if self.getParent() is not None: - self.getParent()._hasChanged() - - def keys(self): - """return list of kerning pairs""" - return self._kerning.keys() - - def values(self): - """return a list of kerning values""" - return self._kerning.values() - - def items(self): - """return a list of kerning items""" - return self._kerning.items() - - def has_key(self, pair): - return self._kerning.has_key(pair) - - def get(self, pair, default=None): - """get a value. return None if the pair does not exist""" - value = self._kerning.get(pair, default) - return value - - def remove(self, pair): - """remove a kerning pair""" - self[pair] = 0 - - def getAverage(self): - """return average of all kerning pairs""" - if len(self) == 0: - return 0 - value = 0 - for i in self.values(): - value = value + i - return value / float(len(self)) - - def getExtremes(self): - """return the lowest and highest kerning values""" - if len(self) == 0: - return 0 - values = self.values() - values.append(0) - values.sort() - return (values[0], values[-1]) - - def update(self, kerningDict): - """replace kerning data with the data in the given kerningDict""" - for pair in kerningDict.keys(): - self[pair] = kerningDict[pair] - - def clear(self): - """clear all kerning""" - self._kerning = {} - - def add(self, value): - """add value to all kerning pairs""" - for pair in self.keys(): - self[pair] = self[pair] + value - - def scale(self, value): - """scale all kernng pairs by value""" - for pair in self.keys(): - self[pair] = self[pair] * value - - def minimize(self, minimum=10): - """eliminate pairs with value less than minimum""" - for pair in self.keys(): - if abs(self[pair]) < minimum: - self[pair] = 0 - - def eliminate(self, leftGlyphsToEliminate=None, rightGlyphsToEliminate=None, analyzeOnly=False): - """eliminate pairs containing a left glyph that is in the leftGlyphsToEliminate list - or a right glyph that is in the rightGlyphsToELiminate list. - sideGlyphsToEliminate can be a string: 'a' or list: ['a', 'b']. - analyzeOnly will not remove pairs. it will return a count - of all pairs that would be removed.""" - if analyzeOnly: - count = 0 - lgte = leftGlyphsToEliminate - rgte = rightGlyphsToEliminate - if isinstance(lgte, str): - lgte = [lgte] - if isinstance(rgte, str): - rgte = [rgte] - for pair in self.keys(): - left, right = pair - if left in lgte or right in rgte: - if analyzeOnly: - count = count + 1 - else: - self[pair] = 0 - if analyzeOnly: - return count - else: - return None - - def interpolate(self, sourceDictOne, sourceDictTwo, value, clearExisting=True): - """interpolate the kerning between sourceDictOne - and sourceDictTwo. clearExisting will clear existing - kerning first.""" - if isinstance(value, tuple): - # in case the value is a x, y tuple: use the x only. - value = value[0] - if clearExisting: - self.clear() - pairs = set(sourceDictOne.keys()) | set(sourceDictTwo.keys()) - for pair in pairs: - s1 = sourceDictOne.get(pair, 0) - s2 = sourceDictTwo.get(pair, 0) - self[pair] = _interpolate(s1, s2, value) - - def round(self, multiple=10): - """round the kerning pair values to increments of multiple""" - for pair in self.keys(): - value = self[pair] - self[pair] = int(round(value / float(multiple))) * multiple - - def occurrenceCount(self, glyphsToCount): - """return a dict with glyphs as keys and the number of - occurances of that glyph in the kerning pairs as the value - glyphsToCount can be a string: 'a' or list: ['a', 'b']""" - gtc = glyphsToCount - if isinstance(gtc, str): - gtc = [gtc] - gtcDict = {} - for glyph in gtc: - gtcDict[glyph] = 0 - for pair in self.keys(): - left, right = pair - if not gtcDict.get(left): - gtcDict[left] = 0 - if not gtcDict.get(right): - gtcDict[right] = 0 - gtcDict[left] = gtcDict[left] + 1 - gtcDict[right] = gtcDict[right] + 1 - found = {} - for glyphName in gtc: - found[glyphName] = gtcDict[glyphName] - return found - - def getLeft(self, glyphName): - """Return a list of kerns with glyphName as left character.""" - hits = [] - for k, v in self.items(): - if k[0] == glyphName: - hits.append((k, v)) - return hits - - def getRight(self, glyphName): - """Return a list of kerns with glyphName as left character.""" - hits = [] - for k, v in self.items(): - if k[1] == glyphName: - hits.append((k, v)) - return hits - - def combine(self, kerningDicts, overwriteExisting=True): - """combine two or more kerning dictionaries. - overwrite exsisting duplicate pairs if overwriteExisting=True""" - if isinstance(kerningDicts, dict): - kerningDicts = [kerningDicts] - for kd in kerningDicts: - for pair in kd.keys(): - exists = self.has_key(pair) - if exists and overwriteExisting: - self[pair] = kd[pair] - elif not exists: - self[pair] = kd[pair] - - def swapNames(self, swapTable): - """change glyph names in all kerning pairs based on swapTable. - swapTable = {'BeforeName':'AfterName', ...}""" - for pair in self.keys(): - foundInstance = False - left, right = pair - if swapTable.has_key(left): - left = swapTable[left] - foundInstance = True - if swapTable.has_key(right): - right = swapTable[right] - foundInstance = True - if foundInstance: - self[(left, right)] = self[pair] - self[pair] = 0 - - def explodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False): - """turn class kerns into real kerning pairs. classes should - be defined in dicts: {'O':['C', 'G', 'Q'], 'H':['B', 'D', 'E', 'F', 'I']}. - analyzeOnly will not remove pairs. it will return a count - of all pairs that would be added""" - if not leftClassDict: - leftClassDict = {} - if not rightClassDict: - rightClassDict = {} - if analyzeOnly: - count = 0 - for pair in self.keys(): - left, right = pair - value = self[pair] - if leftClassDict.get(left) and rightClassDict.get(right): - allLeft = leftClassDict[left] + [left] - allRight = rightClassDict[right] + [right] - for leftSub in allLeft: - for rightSub in allRight: - if analyzeOnly: - count = count + 1 - else: - self[(leftSub, rightSub)] = value - elif leftClassDict.get(left) and not rightClassDict.get(right): - allLeft = leftClassDict[left] + [left] - for leftSub in allLeft: - if analyzeOnly: - count = count + 1 - else: - self[(leftSub, right)] = value - elif rightClassDict.get(right) and not leftClassDict.get(left): - allRight = rightClassDict[right] + [right] - for rightSub in allRight: - if analyzeOnly: - count = count + 1 - else: - self[(left, rightSub)] = value - if analyzeOnly: - return count - else: - return None - - def implodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False): - """condense the number of kerning pairs by applying classes. - this will eliminate all pairs containg the classed glyphs leaving - pairs that contain the key glyphs behind. analyzeOnly will not - remove pairs. it will return a count of all pairs that would be removed.""" - if not leftClassDict: - leftClassDict = {} - if not rightClassDict: - rightClassDict = {} - leftImplode = [] - rightImplode = [] - for value in leftClassDict.values(): - leftImplode = leftImplode + value - for value in rightClassDict.values(): - rightImplode = rightImplode + value - analyzed = self.eliminate(leftGlyphsToEliminate=leftImplode, rightGlyphsToEliminate=rightImplode, analyzeOnly=analyzeOnly) - if analyzeOnly: - return analyzed - else: - return None - - def importAFM(self, path, clearExisting=True): - """Import kerning pairs from an AFM file. clearExisting=True will - clear all exising kerning""" - from fontTools.afmLib import AFM - #a nasty hack to fix line ending problems - f = open(path, 'rb') - text = f.read().replace('\r', '\n') - f.close() - f = open(path, 'wb') - f.write(text) - f.close() - #/nasty hack - kerning = AFM(path)._kerning - if clearExisting: - self.clear() - for pair in kerning.keys(): - self[pair] = kerning[pair] - - def asDict(self, returnIntegers=True): - """return the object as a dictionary""" - if not returnIntegers: - return self._kerning - else: - #duplicate the kerning dict so that we aren't destroying it - kerning = {} - for pair in self.keys(): - kerning[pair] = int(round(self[pair])) - return kerning - - def __add__(self, other): - new = self.__class__() - k = set(self.keys()) | set(other.keys()) - for key in k: - new[key] = self.get(key, 0) + other.get(key, 0) - return new - - def __sub__(self, other): - new = self.__class__() - k = set(self.keys()) | set(other.keys()) - for key in k: - new[key] = self.get(key, 0) - other.get(key, 0) - return new - - def __mul__(self, factor): - new = self.__class__() - for name, value in self.items(): - new[name] = value * factor - return new - - __rmul__ = __mul__ - - def __div__(self, factor): - if factor == 0: - raise ZeroDivisionError - return self.__mul__(1.0/factor) - diff --git a/misc/pylib/robofab/objects/objectsFF.py b/misc/pylib/robofab/objects/objectsFF.py deleted file mode 100644 index 4ed6aae1e..000000000 --- a/misc/pylib/robofab/objects/objectsFF.py +++ /dev/null @@ -1,1253 +0,0 @@ - - -__DEBUG__ = True -__version__ = "0.2" - -""" - RoboFab API Objects for FontForge - http://fontforge.sourceforge.net - - FontForge python docs: - http://fontforge.sourceforge.net/python.html - - Note: This is dead. EvB: "objectsFF.py is very dead and should only serve as an example of "dead" - - History - Version zero. May 2007. EvB - Experiment to see how far the API can be made to work. - - 0.1 extended testing and comparisons for attributes. - 0.2 checked into svn. Still quite raw. Lots of print statements and tests at the end. - - Notes - This code is best used with fontforge compiled as a python extension. - - FontForge Python API: - __doc__ - str(object) -> string - - Return a nice string representation of the object. - If the argument is a string, the return value is the same object. - - __file__ - str(object) -> string - - Return a nice string representation of the object. - If the argument is a string, the return value is the same object. - - __name__ - str(object) -> string - - Return a nice string representation of the object. - If the argument is a string, the return value is the same object. - - activeFont - If invoked from the UI, this returns the currently active font. When not in UI this returns None - - activeFontInUI - If invoked from the UI, this returns the currently active font. When not in UI this returns None - - activeGlyph - If invoked from the UI, this returns the currently active glyph (or None) - - ask - Pops up a dialog asking the user a question and providing a set of buttons for the user to reply with - - askChoices - Pops up a dialog asking the user a question and providing a scrolling list for the user to reply with - - askString - Pops up a dialog asking the user a question and providing a textfield for the user to reply with - - contour - fontforge Contour objects - - contouriter - None - - cvt - fontforge cvt objects - - defaultOtherSubrs - Use FontForge's default "othersubrs" functions for Type1 fonts - - font - FontForge Font object - - fontiter - None - - fonts - Returns a tuple of all loaded fonts - - fontsInFile - Returns a tuple containing the names of any fonts in an external file - - getPrefs - Get FontForge preference items - - glyph - FontForge GlyphPen object - - glyphPen - FontForge Glyph object - - hasSpiro - Returns whether this fontforge has access to Raph Levien's spiro package - - hasUserInterface - Returns whether this fontforge session has a user interface (True if it has opened windows) or is just running a script (False) - - hooks - dict() -> new empty dictionary. - dict(mapping) -> new dictionary initialized from a mapping object's - (key, value) pairs. - dict(seq) -> new dictionary initialized as if via: - d = {} - for k, v in seq: - d[k] = v - dict(**kwargs) -> new dictionary initialized with the name=value pairs - in the keyword argument list. For example: dict(one=1, two=2) - - layer - fontforge Layer objects - - layeriter - None - - loadEncodingFile - Load an encoding file into the list of encodings - - loadNamelist - Load a namelist into the list of namelists - - loadNamelistDir - Load a directory of namelist files into the list of namelists - - loadPlugin - Load a FontForge plugin - - loadPluginDir - Load a directory of FontForge plugin files - - loadPrefs - Load FontForge preference items - - logWarning - Adds a non-fatal message to the Warnings window - - open - Opens a font and returns it - - openFilename - Pops up a file picker dialog asking the user for a filename to open - - parseTTInstrs - Takes a string and parses it into a tuple of truetype instruction bytes - - point - fontforge Point objects - - postError - Pops up an error dialog box with the given title and message - - postNotice - Pops up an notice window with the given title and message - - preloadCidmap - Load a cidmap file - - printSetup - Prepare to print a font sample (select default printer or file, page size, etc.) - - private - FontForge private dictionary - - privateiter - None - - readOtherSubrsFile - Read from a file, "othersubrs" functions for Type1 fonts - - registerImportExport - Adds an import/export spline conversion module - - registerMenuItem - Adds a menu item (which runs a python script) to the font or glyph (or both) windows -- in the Tools menu - - saveFilename - Pops up a file picker dialog asking the user for a filename to use for saving - - savePrefs - Save FontForge preference items - - selection - fontforge selection objects - - setPrefs - Set FontForge preference items - - spiroCorner - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroG2 - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroG4 - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroLeft - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroOpen - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - spiroRight - int(x[, base]) -> integer - - Convert a string or number to an integer, if possible. A floating point - argument will be truncated towards zero (this does not include a string - representation of a floating point number!) When converting a string, use - the optional base. It is an error to supply a base when converting a - non-string. If the argument is outside the integer range a long object - will be returned instead. - - unParseTTInstrs - Takes a tuple of truetype instruction bytes and converts to a human readable string - - unicodeFromName - Given a name, look it up in the namelists and find what unicode code point it maps to (returns -1 if not found) - - version - Returns a string containing the current version of FontForge, as 20061116 - - - - -Problems: - XXX: reading glif from UFO: is the contour order changed in some way? - - -ToDo: - - segments ? - - -""" - -import os -from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ - BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\ - roundPt, addPt, _box,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut - -from robofab.objects.objectsRF import RGlyph as _RGlyph - -import fontforge -import psMat - - -# a list of attributes that are to be copied when copying a glyph. -# this is used by glyph.copy and font.insertGlyph -GLYPH_COPY_ATTRS = [ - "name", - "width", - "unicodes", - "note", - "lib", - ] - - - -def CurrentFont(): - if fontforge.hasUserInterface(): - _font = fontforge.activeFontInUI() - return RFont(_font) - if __DEBUG__: - print "CurrentFont(): fontforge not running with user interface," - return None - -def OpenFont(fontPath): - obj = fontforge.open(fontPath) - if __DEBUG__: - print "OpenFont", fontPath - print "result:", obj - return RFont(obj) - -def NewFont(fontPath=None): - _font = fontforge.font() - if __DEBUG__: - print "NewFont", fontPath - print "result:", _font - return RFont(_font) - - - - -class RFont(BaseFont): - def __init__(self, font=None): - if font is None: - # make a new font - pass - else: - self._object = font - - # ----------------------------------------------------------------- - # - # access - - def keys(self): - """FF implements __iter__ for the font object - better?""" - return [n.glyphname for n in self._object.glyphs()] - - def has_key(self, glyphName): - return glyphName in self - - def _get_info(self): - return RInfo(self._object) - - info = property(_get_info, doc="font info object") - - def __iter__(self): - for glyphName in self.keys(): - yield self.getGlyph(glyphName) - - - # ----------------------------------------------------------------- - # - # file - - def _get_path(self): - return self._object.path - - path = property(_get_path, doc="path of this file") - - def __contains__(self, glyphName): - return glyphName in self.keys() - - def save(self, path=None): - """Save this font as sfd file. - XXX: how to set a sfd path if is none - """ - if path is not None: - # trying to save it somewhere else - _path = path - else: - _path = self.path - if os.path.splitext(_path)[-1] != ".sfd": - _path = os.path.splitext(_path)[0]+".sfd" - if __DEBUG__: - print "RFont.save() to", _path - self._object.save(_path) - - def naked(self): - return self._object - - def close(self): - if __DEBUG__: - print "RFont.close()" - self._object.close() - - - # ----------------------------------------------------------------- - # - # generate - - def dummyGeneratePreHook(self, *args): - print "dummyGeneratePreHook", args - - def dummyGeneratePostHook(self, *args): - print "dummyGeneratePostHook", args - - def generate(self, outputType, path=None): - """ - generate the font. outputType is the type of font to ouput. - --Ouput Types: - 'pctype1' : PC Type 1 font (binary/PFB) - 'pcmm' : PC MultipleMaster font (PFB) - 'pctype1ascii' : PC Type 1 font (ASCII/PFA) - 'pcmmascii' : PC MultipleMaster font (ASCII/PFA) - 'unixascii' : UNIX ASCII font (ASCII/PFA) - 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file) - 'otfcff' : PS OpenType (CFF-based) font (OTF) - 'otfttf' : PC TrueType/TT OpenType font (TTF) - 'macttf' : Mac TrueType font (generates suitcase) - 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork) - (doc adapted from http://dev.fontlab.net/flpydoc/) - - path can be a directory or a directory file name combo: - path="DirectoryA/DirectoryB" - path="DirectoryA/DirectoryB/MyFontName" - if no path is given, the file will be output in the same directory - as the vfb file. if no file name is given, the filename will be the - vfb file name with the appropriate suffix. - """ - - extensions = { - 'pctype1': 'pfm', - 'otfcff': 'otf', - } - - if __DEBUG__: - print "font.generate", outputType, path - - # set pre and post hooks (necessary?) - temp = getattr(self._object, "temporary") - if temp is None: - self._object.temporary = {} - else: - if type(self._object.temporary)!=dict: - self._object.temporary = {} - self._object.temporary['generateFontPreHook'] = self.dummyGeneratePreHook - self._object.temporary['generateFontPostHook'] = self.dummyGeneratePostHook - - # make a path for the destination - if path is None: - fileName = os.path.splitext(os.path.basename(self.path))[0] - dirName = os.path.dirname(self.path) - extension = extensions.get(outputType) - if extension is not None: - fileName = "%s.%s"%(fileName, extension) - else: - if __DEBUG__: - print "can't generate font in %s format"%outputType - return - path = os.path.join(dirName, fileName) - - # prepare OTF fields - generateFlags = [] - generateFlags.append('opentype') - # generate - self._object.generate(filename=path, flags=generateFlags) - if __DEBUG__: - print "font.generate():", path - return path - - - # ----------------------------------------------------------------- - # - # kerning stuff - - def _get_kerning(self): - kerning = {} - f = self._object - for g in f.glyphs: - for p in g.kerning: - try: - key = (g.name, f[p.key].name) - kerning[key] = p.value - except AttributeError: pass #catch for TT exception - rk = RKerning(kerning) - rk.setParent(self) - return rk - - kerning = property(_get_kerning, doc="a kerning object") - - # ----------------------------------------------------------------- - # - # glyph stuff - - def getGlyph(self, glyphName): - try: - ffGlyph = self._object[glyphName] - except TypeError: - print "font.getGlyph, can't find glyphName, returning new glyph" - return self.newGlyph(glyphName) - glyph = RGlyph(ffGlyph) - glyph.setParent(self) - return glyph - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph - - Notes: not sure how to make a new glyph without an encoded name. - createChar() seems to be intended for that, but when I pass it -1 - for the unicode, it complains that it wants -1. Perhaps a bug? - """ - # is the glyph already there? - glyph = None - if glyphName in self: - if clear: - self._object[glyphName].clear() - return self[glyphName] - else: - # is the glyph in an encodable place: - slot = self._object.findEncodingSlot(glyphName) - if slot == -1: - # not encoded - print "font.newGlyph: unencoded slot", slot, glyphName - glyph = self._object.createChar(-1, glyphName) - else: - glyph = self._object.createMappedChar(glyphName) - glyph = RGlyph(self._object[glyphName]) - glyph.setParent(self) - return glyph - - def removeGlyph(self, glyphName): - self._object.removeGlyph(glyphName) - - - - -class RGlyph(BaseGlyph): - """Fab wrapper for FF Glyph object""" - def __init__(self, ffGlyph=None): - if ffGlyph is None: - raise RoboFabError - self._object = ffGlyph - # XX anchors seem to be supported, but in a different way - # XX so, I will ignore them for now to get something working. - self.anchors = [] - self.lib = {} - - def naked(self): - return self._object - - def setChanged(self): - self._object.changed() - - - # ----------------------------------------------------------------- - # - # attributes - - def _get_name(self): - return self._object.glyphname - def _set_name(self, value): - self._object.glyphname = value - name = property(_get_name, _set_name, doc="name") - - def _get_note(self): - return self._object.comment - def _set_note(self, note): - self._object.comment = note - note = property(_get_note, _set_note, doc="note") - - def _get_width(self): - return self._object.width - def _set_width(self, width): - self._object.width = width - width = property(_get_width, _set_width, doc="width") - - def _get_leftMargin(self): - return self._object.left_side_bearing - def _set_leftMargin(self, leftMargin): - self._object.left_side_bearing = leftMargin - leftMargin = property(_get_leftMargin, _set_leftMargin, doc="leftMargin") - - def _get_rightMargin(self): - return self._object.right_side_bearing - def _set_rightMargin(self, rightMargin): - self._object.right_side_bearing = rightMargin - rightMargin = property(_get_rightMargin, _set_rightMargin, doc="rightMargin") - - def _get_unicodes(self): - return [self._object.unicode] - def _set_unicodes(self, unicodes): - assert len(unicodes)==1 - self._object.unicode = unicodes[0] - unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes") - - def _get_unicode(self): - return self._object.unicode - def _set_unicode(self, unicode): - self._object.unicode = unicode - unicode = property(_get_unicode, _set_unicode, doc="unicode") - - def _get_box(self): - bounds = self._object.boundingBox() - return bounds - box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)") - - def _get_mark(self): - """color of the glyph box in the font view. This accepts a 6 hex digit number. - - XXX the FL implementation accepts a - """ - import colorsys - r = (self._object.color&0xff0000)>>16 - g = (self._object.color&0xff00)>>8 - g = (self._object.color&0xff)>>4 - return colorsys.rgb_to_hsv( r, g, b)[0] - - def _set_mark(self, markColor=-1): - import colorsys - self._object.color = colorSys.hsv_to_rgb(markColor, 1, 1) - - mark = property(_get_mark, _set_mark, doc="the color of the glyph box in the font view") - - - # ----------------------------------------------------------------- - # - # pen, drawing - - def getPen(self): - return self._object.glyphPen() - - def __getPointPen(self): - """Return a point pen. - - Note: FontForge doesn't support segment pen, so return an adapter. - """ - from robofab.pens.adapterPens import PointToSegmentPen - segmentPen = self._object.glyphPen() - return PointToSegmentPen(segmentPen) - - def getPointPen(self): - from robofab.pens.rfUFOPen import RFUFOPointPen - pen = RFUFOPointPen(self) - #print "getPointPen", pen, pen.__class__, dir(pen) - return pen - - def draw(self, pen): - """draw - - """ - self._object.draw(pen) - pen = None - - def drawPoints(self, pen): - """drawPoints - - Note: FontForge implements glyph.draw, but not glyph.drawPoints. - """ - from robofab.pens.adapterPens import PointToSegmentPen, SegmentToPointPen - adapter = SegmentToPointPen(pen) - self._object.draw(adapter) - pen = None - - def appendGlyph(self, other): - pen = self.getPen() - other.draw(pen) - - # ----------------------------------------------------------------- - # - # glyphmath - - def round(self): - self._object.round() - - def _getMathDestination(self): - from robofab.objects.objectsRF import RGlyph as _RGlyph - return _RGlyph() - - def _mathCopy(self): - # copy self without contour, component and anchor data - glyph = self._getMathDestination() - glyph.name = self.name - glyph.unicodes = list(self.unicodes) - glyph.width = self.width - glyph.note = self.note - glyph.lib = dict(self.lib) - return glyph - - def __mul__(self, factor): - if __DEBUG__: - print "glyphmath mul", factor - return self.copy() *factor - - __rmul__ = __mul__ - - def __sub__(self, other): - if __DEBUG__: - print "glyphmath sub", other, other.__class__ - return self.copy() - other.copy() - - def __add__(self, other): - if __DEBUG__: - print "glyphmath add", other, other.__class__ - return self.copy() + other.copy() - - def getParent(self): - return self - - def copy(self, aParent=None): - """Make a copy of this glyph. - Note: the copy is not a duplicate fontlab glyph, but - a RF RGlyph with the same outlines. The new glyph is - not part of the fontlab font in any way. Use font.appendGlyph(glyph) - to get it in a FontLab glyph again.""" - from robofab.objects.objectsRF import RGlyph as _RGlyph - newGlyph = _RGlyph() - newGlyph.appendGlyph(self) - for attr in GLYPH_COPY_ATTRS: - value = getattr(self, attr) - setattr(newGlyph, attr, value) - parent = self.getParent() - if aParent is not None: - newGlyph.setParent(aParent) - elif self.getParent() is not None: - newGlyph.setParent(self.getParent()) - return newGlyph - - def _get_contours(self): - # find the contour data and wrap it - - """get the contours in this glyph""" - contours = [] - for n in range(len(self._object.foreground)): - item = self._object.foreground[n] - rc = RContour(item, n) - rc.setParent(self) - contours.append(rc) - #print contours - return contours - - contours = property(_get_contours, doc="allow for iteration through glyph.contours") - - # ----------------------------------------------------------------- - # - # transformations - - def move(self, (x, y)): - matrix = psMat.translate((x,y)) - self._object.transform(matrix) - - def scale(self, (x, y), center=(0,0)): - matrix = psMat.scale(x,y) - self._object.transform(matrix) - - def transform(self, matrix): - self._object.transform(matrix) - - def rotate(self, angle, offset=None): - matrix = psMat.rotate(angle) - self._object.transform(matrix) - - def skew(self, angle, offset=None): - matrix = psMat.skew(angle) - self._object.transform(matrix) - - # ----------------------------------------------------------------- - # - # components stuff - - def decompose(self): - self._object.unlinkRef() - - # ----------------------------------------------------------------- - # - # unicode stuff - - def autoUnicodes(self): - if __DEBUG__: - print "objectsFF.RGlyph.autoUnicodes() not implemented yet." - - # ----------------------------------------------------------------- - # - # contour stuff - - def removeOverlap(self): - self._object.removeOverlap() - - def correctDirection(self, trueType=False): - # no option for trueType, really. - self._object.correctDirection() - - def clear(self): - self._object.clear() - - def __getitem__(self, index): - return self.contours[index] - - -class RContour(BaseContour): - def __init__(self, contour, index=None): - self._object = contour - self.index = index - - def _get_points(self): - pts = [] - for pt in self._object: - wpt = RPoint(pt) - wpt.setParent(self) - pts.append(wpt) - return pts - - points = property(_get_points, doc="get contour points") - - def _get_box(self): - return self._object.boundingBox() - - box = property(_get_box, doc="get contour bounding box") - - def __len__(self): - return len(self._object) - - def __getitem__(self, index): - return self.points[index] - - - -class RPoint(BasePoint): - - def __init__(self, pointObject): - self._object = pointObject - - def _get_x(self): - return self._object.x - - def _set_x(self, value): - self._object.x = value - - x = property(_get_x, _set_x, doc="") - - def _get_y(self): - return self._object.y - - def _set_y(self, value): - self._object.y = value - - y = property(_get_y, _set_y, doc="") - - def _get_type(self): - if self._object.on_curve == 0: - return OFFCURVE - - # XXX not always curve - return CURVE - - def _set_type(self, value): - self._type = value - self._hasChanged() - - type = property(_get_type, _set_type, doc="") - - def __repr__(self): - font = "unnamed_font" - glyph = "unnamed_glyph" - contourIndex = "unknown_contour" - contourParent = self.getParent() - if contourParent is not None: - try: - contourIndex = `contourParent.index` - except AttributeError: pass - glyphParent = contourParent.getParent() - if glyphParent is not None: - try: - glyph = glyphParent.name - except AttributeError: pass - fontParent = glyphParent.getParent() - if fontParent is not None: - try: - font = fontParent.info.fullName - except AttributeError: pass - return "<RPoint for %s.%s[%s]>"%(font, glyph, contourIndex) - - -class RInfo(BaseInfo): - def __init__(self, font): - BaseInfo.__init__(self) - self._object = font - - def _get_familyName(self): - return self._object.familyname - def _set_familyName(self, value): - self._object.familyname = value - familyName = property(_get_familyName, _set_familyName, doc="familyname") - - def _get_fondName(self): - return self._object.fondname - def _set_fondName(self, value): - self._object.fondname = value - fondName = property(_get_fondName, _set_fondName, doc="fondname") - - def _get_fontName(self): - return self._object.fontname - def _set_fontName(self, value): - self._object.fontname = value - fontName = property(_get_fontName, _set_fontName, doc="fontname") - - # styleName doesn't have a specific field, FF has a whole sfnt dict. - # implement fullName because a repr depends on it - def _get_fullName(self): - return self._object.fullname - def _set_fullName(self, value): - self._object.fullname = value - fullName = property(_get_fullName, _set_fullName, doc="fullname") - - def _get_unitsPerEm(self): - return self._object.em - def _set_unitsPerEm(self, value): - self._object.em = value - unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value") - - def _get_ascender(self): - return self._object.ascent - def _set_ascender(self, value): - self._object.ascent = value - ascender = property(_get_ascender, _set_ascender, doc="ascender value") - - def _get_descender(self): - return -self._object.descent - def _set_descender(self, value): - self._object.descent = -value - descender = property(_get_descender, _set_descender, doc="descender value") - - def _get_copyright(self): - return self._object.copyright - def _set_copyright(self, value): - self._object.copyright = value - copyright = property(_get_copyright, _set_copyright, doc="copyright") - - - -class RKerning(BaseKerning): - - """ Object representing the kerning. - This is going to need some thinking about. - """ - - -__all__ = [ 'RFont', 'RGlyph', 'RContour', 'RPoint', 'RInfo', - 'OpenFont', 'CurrentFont', 'NewFont', 'CurrentFont' - ] - - - -if __name__ == "__main__": - import os - from robofab.objects.objectsRF import RFont as _RFont - from sets import Set - - def dumpFontForgeAPI(testFontPath, printModule=False, - printFont=False, printGlyph=False, - printLayer=False, printContour=False, printPoint=False): - def printAPI(item, name): - print - print "-"*80 - print "API of", item - names = dir(item) - names.sort() - print - - if printAPI: - for n in names: - print - print "%s.%s"%(name, n) - try: - print getattr(item, n).__doc__ - except: - print "# error showing", n - # module - if printModule: - print "module file:", fontforge.__file__ - print "version:", fontforge.version() - print "module doc:", fontforge.__doc__ - print "has User Interface:", fontforge.hasUserInterface() - print "has Spiro:", fontforge.hasSpiro() - printAPI(fontforge, "fontforge") - - # font - fontObj = fontforge.open(testFontPath) - if printFont: - printAPI(fontObj, "font") - - # glyph - glyphObj = fontObj["A"] - if printGlyph: - printAPI(glyphObj, "glyph") - - # layer - layerObj = glyphObj.foreground - if printLayer: - printAPI(layerObj, "layer") - - # contour - contourObj = layerObj[0] - if printContour: - printAPI(contourObj, "contour") - - # point - if printPoint: - pointObj = contourObj[0] - printAPI(pointObj, "point") - - - # other objects - penObj = glyphObj.glyphPen() - printAPI(penObj, "glyphPen") - - # use your own paths here. - demoRoot = "/Users/erik/Develop/Mess/FontForge/objectsFF_work/" - UFOPath = os.path.join(demoRoot, "Test.ufo") - SFDPath = os.path.join(demoRoot, "Test_realSFD2.sfd") - - #dumpFontForgeAPI(UFOPath, printPoint=True) - - # should return None - CurrentFont() - - def compareAttr(obj1, obj2, attrName, isMethod=False): - if isMethod: - a = getattr(obj1, attrName)() - b = getattr(obj2, attrName)() - else: - a = getattr(obj1, attrName) - b = getattr(obj2, attrName) - if a == b and a is not None and b is not None: - print "\tattr %s ok"%attrName, a - return True - else: - print "\t?\t%s error:"%attrName, "%s:"%obj1.__class__, a, "%s:"%obj2.__class__, b - return False - - f = OpenFont(UFOPath) - #f = OpenFont(SFDPath) - ref = _RFont(UFOPath) - - if False: - print - print "test font attributes" - compareAttr(f, ref, "path") - - a = Set(f.keys()) - b = Set(ref.keys()) - print "glyphs in ref, not in f", b.difference(a) - print "glyphs in f, not in ref", a.difference(b) - - print "A" in f, "A" in ref - print f.has_key("A"), ref.has_key("A") - - print - print "test font info attributes" - compareAttr(f.info, ref.info, "ascender") - compareAttr(f.info, ref.info, "descender") - compareAttr(f.info, ref.info, "unitsPerEm") - compareAttr(f.info, ref.info, "copyright") - compareAttr(f.info, ref.info, "fullName") - compareAttr(f.info, ref.info, "familyName") - compareAttr(f.info, ref.info, "fondName") - compareAttr(f.info, ref.info, "fontName") - - # crash - f.save() - - otfOutputPath = os.path.join(demoRoot, "test_ouput.otf") - ufoOutputPath = os.path.join(demoRoot, "test_ouput.ufo") - # generate without path, should end up in the source folder - - f['A'].removeOverlap() - f.generate('otfcff') #, otfPath) - f.generate('pctype1') #, otfPath) - - # generate with path. Type is taken from the extension. - f.generate('otfcff', otfOutputPath) #, otfPath) - f.generate(None, ufoOutputPath) #, otfPath) - - featurePath = os.path.join(demoRoot, "testFeatureOutput.fea") - f.naked().generateFeatureFile(featurePath) - - if False: - # new glyphs - # unencoded - print "new glyph: unencoded", f.newGlyph("test_unencoded_glyph") - # encoded - print "new glyph: encoded", f.newGlyph("Adieresis") - # existing - print "new glyph: existing", f.newGlyph("K") - - print - print "test glyph attributes" - compareAttr(f['A'], ref['A'], "width") - compareAttr(f['A'], ref['A'], "unicode") - compareAttr(f['A'], ref['A'], "name") - compareAttr(f['A'], ref['A'], "box") - compareAttr(f['A'], ref['A'], "leftMargin") - compareAttr(f['A'], ref['A'], "rightMargin") - - if False: - print - print "comparing glyph digests" - failed = [] - for n in f.keys(): - g1 = f[n] - #g1.round() - g2 = ref[n] - #g2.round() - d1 = g1._getDigest() - d2 = g2._getDigest() - if d1 != d2: - failed.append(n) - #print "f: ", d1 - #print "ref: ", d2 - print "digest failed for %s"%". ".join(failed) - - g3 = f['A'] *.333 - print g3 - print g3._getDigest() - f.save() - - if False: - print - print "test contour attributes" - compareAttr(f['A'].contours[0], ref['A'].contours[0], "index") - - #for c in f['A'].contours: - # for p in c.points: - # print p, p.type - - # test with a glyph with just 1 contour so we can be sure we're comparing the same thing - compareAttr(f['C'].contours[0], ref['C'].contours[0], "box") - compareAttr(f['C'].contours[0], ref['C'].contours[0], "__len__", isMethod=True) - - ptf = f['C'].contours[0].points[0] - ptref = ref['C'].contours[0].points[0] - print "x, y", (ptf.x, ptf.y) == (ptref.x, ptref.y), (ptref.x, ptref.y) - print 'type', ptf.type, ptref.type - - print "point inside", f['A'].pointInside((50,10)), ref['A'].pointInside((50,10)) - - - print ref.kerning.keys() - - class GlyphLookupWrapper(dict): - """A wrapper for the lookups / subtable data in a FF glyph. - A lot of data is stored there, so it helps to have something to sort things out. - """ - def __init__(self, ffGlyph): - self._object = ffGlyph - self.refresh() - - def __repr__(self): - return "<GlyphLookupWrapper for %s, %d keys>"%(self._object.glyphname, len(self)) - - def refresh(self): - """Pick some of the values apart.""" - lookups = self._object.getPosSub('*') - for t in lookups: - print 'lookup', t - lookupName = t[0] - lookupType = t[1] - if not lookupName in self: - self[lookupName] = [] - self[lookupName].append(t[1:]) - - def getKerning(self): - """Get a regular kerning dict for this glyph""" - d = {} - left = self._object.glyphname - for name in self.keys(): - for item in self[name]: - print 'item', item - if item[0]!="Pair": - continue - #print 'next glyph:', item[1] - #print 'first glyph x Pos:', item[2] - #print 'first glyph y Pos:', item[3] - #print 'first glyph h Adv:', item[4] - #print 'first glyph v Adv:', item[5] - - #print 'second glyph x Pos:', item[6] - #print 'second glyph y Pos:', item[7] - #print 'second glyph h Adv:', item[8] - #print 'second glyph v Adv:', item[9] - right = item[1] - d[(left, right)] = item[4] - return d - - def setKerning(self, kernDict): - """Set the values of a regular kerning dict to the lookups in a FF glyph.""" - for left, right in kernDict.keys(): - if left != self._object.glyphname: - # should we filter the dict before it gets here? - # easier just to filter it here. - continue - - - - # lets try to find the kerning - A = f['A'].naked() - positionTypes = [ "Position", "Pair", "Substitution", "AltSubs", "MultSubs","Ligature"] - print A.getPosSub('*') - #for t in A.getPosSub('*'): - # print 'lookup subtable name:', t[0] - # print 'positioning type:', t[1] - # if t[1]in positionTypes: - # print 'next glyph:', t[2] - # print 'first glyph x Pos:', t[3] - # print 'first glyph y Pos:', t[4] - # print 'first glyph h Adv:', t[5] - # print 'first glyph v Adv:', t[6] - - # print 'second glyph x Pos:', t[7] - # print 'second glyph y Pos:', t[8] - # print 'second glyph h Adv:', t[9] - # print 'second glyph v Adv:', t[10] - - gw = GlyphLookupWrapper(A) - print gw - print gw.keys() - print gw.getKerning() - - name = "'kern' Horizontal Kerning in Latin lookup 0 subtable" - item = (name, 'quoteright', 0, 0, -200, 0, 0, 0, 0, 0) - - A.removePosSub(name) - apply(A.addPosSub, item) - - - print "after", A.getPosSub('*') - - fn = f.naked() - - name = "'kern' Horizontal Kerning in Latin lookup 0" - - - print "before removing stuff", fn.gpos_lookups - print "removing stuff", fn.removeLookup(name) - print "after removing stuff", fn.gpos_lookups - - flags = () - feature_script_lang = (("kern",(("latn",("dflt")),)),) - print fn.addLookup('kern', 'gpos_pair', flags, feature_script_lang) - print fn.addLookupSubtable('kern', 'myKerning') - - - print fn.gpos_lookups - A.addPosSub('myKerning', 'A', 0, 0, -400, 0, 0, 0, 0, 0) - A.addPosSub('myKerning', 'B', 0, 0, 200, 0, 0, 0, 0, 0) - A.addPosSub('myKerning', 'C', 0, 0, 10, 0, 0, 0, 0, 0) - A.addPosSub('myKerning', 'A', 0, 0, 77, 0, 0, 0, 0, 0) - - - gw = GlyphLookupWrapper(A) - print gw - print gw.keys() - print gw.getKerning() - diff --git a/misc/pylib/robofab/objects/objectsFL.py b/misc/pylib/robofab/objects/objectsFL.py deleted file mode 100755 index 3b78ddc14..000000000 --- a/misc/pylib/robofab/objects/objectsFL.py +++ /dev/null @@ -1,3112 +0,0 @@ -"""UFO implementation for the objects as used by FontLab 4.5 and higher""" - -from FL import * - -from robofab.tools.toolsFL import GlyphIndexTable, NewGlyph -from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ - BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\ - roundPt, addPt, _box,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\ - BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues -from robofab.misc import arrayTools -from robofab.pens.flPen import FLPointPen, FLPointContourPen -from robofab import RoboFabError -import os -from robofab.plistlib import Data, Dict, readPlist, writePlist -from StringIO import StringIO -from robofab import ufoLib -from warnings import warn -import datetime -from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab - - -try: - set -except NameError: - from sets import Set as set - -# local encoding -if os.name in ["mac", "posix"]: - LOCAL_ENCODING = "macroman" -else: - LOCAL_ENCODING = "latin-1" - -_IN_UFO_EXPORT = False - -# a list of attributes that are to be copied when copying a glyph. -# this is used by glyph.copy and font.insertGlyph -GLYPH_COPY_ATTRS = [ - "name", - "width", - "unicodes", - "note", - "lib", - ] - -# Generate Types -PC_TYPE1 = 'pctype1' -PC_MM = 'pcmm' -PC_TYPE1_ASCII = 'pctype1ascii' -PC_MM_ASCII = 'pcmmascii' -UNIX_ASCII = 'unixascii' -MAC_TYPE1 = 'mactype1' -OTF_CFF = 'otfcff' -OTF_TT = 'otfttf' -MAC_TT = 'macttf' -MAC_TT_DFONT = 'macttdfont' - -# doc for these functions taken from: http://dev.fontlab.net/flpydoc/ -# internal name (FontLab name, extension) -_flGenerateTypes ={ PC_TYPE1 : (ftTYPE1, 'pfb'), # PC Type 1 font (binary/PFB) - PC_MM : (ftTYPE1_MM, 'mm'), # PC MultipleMaster font (PFB) - PC_TYPE1_ASCII : (ftTYPE1ASCII, 'pfa'), # PC Type 1 font (ASCII/PFA) - PC_MM_ASCII : (ftTYPE1ASCII_MM, 'mm'), # PC MultipleMaster font (ASCII/PFA) - UNIX_ASCII : (ftTYPE1ASCII, 'pfa'), # UNIX ASCII font (ASCII/PFA) - OTF_TT : (ftTRUETYPE, 'ttf'), # PC TrueType/TT OpenType font (TTF) - OTF_CFF : (ftOPENTYPE, 'otf'), # PS OpenType (CFF-based) font (OTF) - MAC_TYPE1 : (ftMACTYPE1, 'suit'), # Mac Type 1 font (generates suitcase and LWFN file, optionally AFM) - MAC_TT : (ftMACTRUETYPE, 'ttf'), # Mac TrueType font (generates suitcase) - MAC_TT_DFONT : (ftMACTRUETYPE_DFONT, 'dfont'), # Mac TrueType font (generates suitcase with resources in data fork) - } - -## FL Hint stuff -# this should not be referenced outside of this module -# since we may be changing the way this works in the future. - - -""" - - FontLab implementation of psHints objects - - Most of the FL methods relating to ps hints return a list of 16 items. - These values are for the 16 corners of a 4 axis multiple master. - The odd thing is that even single masters get these 16 values. - RoboFab doesn't access the MM masters, so by default, the psHints - object only works with the first element. If you want to access the other - values in the list, give a value between 0 and 15 for impliedMasterIndex - when creating the object. - - From the FontLab docs: - http://dev.fontlab.net/flpydoc/ - - blue_fuzz - blue_scale - blue_shift - - blue_values_num(integer) - number of defined blue values - blue_values[integer[integer]] - two-dimentional array of BlueValues - master index is top-level index - - other_blues_num(integer) - number of defined OtherBlues values - other_blues[integer[integer]] - two-dimentional array of OtherBlues - master index is top-level index - - family_blues_num(integer) - number of FamilyBlues records - family_blues[integer[integer]] - two-dimentional array of FamilyBlues - master index is top-level index - - family_other_blues_num(integer) - number of FamilyOtherBlues records - family_other_blues[integer[integer]] - two-dimentional array of FamilyOtherBlues - master index is top-level index - - force_bold[integer] - list of Force Bold values, one for - each master - stem_snap_h_num(integer) - stem_snap_h - stem_snap_v_num(integer) - stem_snap_v - """ - -class PostScriptFontHintValues(BasePostScriptFontHintValues): - """ Wrapper for font-level PostScript hinting information for FontLab. - Blues values, stem values. - """ - def __init__(self, font=None, impliedMasterIndex=0): - self._object = font.naked() - self._masterIndex = impliedMasterIndex - - def copy(self): - from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues - return _PostScriptFontHintValues(data=self.asDict()) - - -class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): - """ Wrapper for glyph-level PostScript hinting information for FontLab. - vStems, hStems. - """ - def __init__(self, glyph=None): - self._object = glyph.naked() - - def copy(self): - from robofab.objects.objectsRF import PostScriptGlyphHintValues as _PostScriptGlyphHintValues - return _PostScriptGlyphHintValues(data=self.asDict()) - - def _hintObjectsToList(self, item): - data = [] - done = [] - for hint in item: - p = (hint.position, hint.width) - if p in done: - continue - data.append(p) - done.append(p) - data.sort() - return data - - def _listToHintObjects(self, item): - hints = [] - done = [] - for pos, width in item: - if (pos, width) in done: - # we don't want to set duplicates - continue - hints.append(Hint(pos, width)) - done.append((pos,width)) - return hints - - def _getVHints(self): - return self._hintObjectsToList(self._object.vhints) - - def _setVHints(self, values): - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(2) - if values is None: - # just clearing it then - return - values.sort() - for hint in self._listToHintObjects(values): - self._object.vhints.append(hint) - - def _getHHints(self): - return self._hintObjectsToList(self._object.hhints) - - def _setHHints(self, values): - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(1) - if values is None: - # just clearing it then - return - values.sort() - for hint in self._listToHintObjects(values): - self._object.hhints.append(hint) - - vHints = property(_getVHints, _setVHints, doc="postscript hints: vertical hint zones") - hHints = property(_getHHints, _setHHints, doc="postscript hints: horizontal hint zones") - - - -def _glyphHintsToDict(glyph): - data = {} - ## - ## horizontal and vertical hints - ## - # glyph.hhints and glyph.vhints returns a list of Hint objects. - # Hint objects have position and width attributes. - data['hHints'] = [] - for index in xrange(len(glyph.hhints)): - hint = glyph.hhints[index] - data['hHints'].append((hint.position, hint.width)) - if not data['hHints']: - del data['hHints'] - data['vHints'] = [] - for index in xrange(len(glyph.vhints)): - hint = glyph.vhints[index] - data['vHints'].append((hint.position, hint.width)) - if not data['vHints']: - del data['vHints'] - ## - ## horizontal and vertical links - ## - # glyph.hlinks and glyph.vlinks returns a list of Link objects. - # Link objects have node1 and node2 attributes. - data['hLinks'] = [] - for index in xrange(len(glyph.hlinks)): - link = glyph.hlinks[index] - d = { 'node1' : link.node1, - 'node2' : link.node2, - } - data['hLinks'].append(d) - if not data['hLinks']: - del data['hLinks'] - data['vLinks'] = [] - for index in xrange(len(glyph.vlinks)): - link = glyph.vlinks[index] - d = { 'node1' : link.node1, - 'node2' : link.node2, - } - data['vLinks'].append(d) - if not data['vLinks']: - del data['vLinks'] - ## - ## replacement table - ## - # glyph.replace_table returns a list of Replace objects. - # Replace objects have type and index attributes. - data['replaceTable'] = [] - for index in xrange(len(glyph.replace_table)): - replace = glyph.replace_table[index] - d = { 'type' : replace.type, - 'index' : replace.index, - } - data['replaceTable'].append(d) - if not data['replaceTable']: - del data['replaceTable'] - # XXX - # need to support glyph.instructions and glyph.hdmx? - # they are not documented very well. - return data - -def _dictHintsToGlyph(glyph, aDict): - # clear existing hints first - # RemoveHints requires an "integer mode" argument - # but it is not documented. from some simple experiments - # i deduced that - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - glyph.RemoveHints(3) - ## - ## horizontal and vertical hints - ## - if aDict.has_key('hHints'): - for d in aDict['hHints']: - glyph.hhints.append(Hint(d[0], d[1])) - if aDict.has_key('vHints'): - for d in aDict['vHints']: - glyph.vhints.append(Hint(d[0], d[1])) - ## - ## horizontal and vertical links - ## - if aDict.has_key('hLinks'): - for d in aDict['hLinks']: - glyph.hlinks.append(Link(d['node1'], d['node2'])) - if aDict.has_key('vLinks'): - for d in aDict['vLinks']: - glyph.vlinks.append(Link(d['node1'], d['node2'])) - ## - ## replacement table - ## - if aDict.has_key('replaceTable'): - for d in aDict['replaceTable']: - glyph.replace_table.append(Replace(d['type'], d['index'])) - -# FL Node Types -flMOVE = 17 -flLINE = 1 -flCURVE = 35 -flOFFCURVE = 65 -flSHARP = 0 -# I have no idea what the difference between -# "smooth" and "fixed" is, but booth values -# are returned by FL -flSMOOTH = 4096 -flFIXED = 12288 - - -_flToRFSegmentDict = { flMOVE : MOVE, - flLINE : LINE, - flCURVE : CURVE, - flOFFCURVE : OFFCURVE - } - -_rfToFLSegmentDict = {} -for k, v in _flToRFSegmentDict.items(): - _rfToFLSegmentDict[v] = k - -def _flToRFSegmentType(segmentType): - return _flToRFSegmentDict[segmentType] - -def _rfToFLSegmentType(segmentType): - return _rfToFLSegmentDict[segmentType] - -def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)): - ogCenter = (centerX, centerY) - scaledCenter = (centerX * scaleX, centerY * scaleY) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - scaledPointX = (pointX * scaleX) - shiftVal[0] - scaledPointY = (pointY * scaleY) - shiftVal[1] - return (scaledPointX, scaledPointY) - -# Nostalgia code: -def CurrentFont(): - """Return a RoboFab font object for the currently selected font.""" - f = fl.font - if f is not None: - return RFont(fl.font) - return None - -def CurrentGlyph(): - """Return a RoboFab glyph object for the currently selected glyph.""" - currentPath = fl.font.file_name - if fl.glyph is None: - return None - glyphName = fl.glyph.name - currentFont = None - # is this font already loaded as an RFont? - for font in AllFonts(): - # ugh this won't work because AllFonts sees non RFonts as well.... - if font.path == currentPath: - currentFont = font - break - xx = currentFont[glyphName] - #print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent() - return xx - -def OpenFont(path=None, note=None): - """Open a font from a path.""" - if path == None: - from robofab.interface.all.dialogs import GetFile - path = GetFile(note) - if path: - if path[-4:].lower() in ['.vfb', '.VFB', '.bak', '.BAK']: - f = Font(path) - fl.Add(f) - return RFont(f) - return None - -def NewFont(familyName=None, styleName=None): - """Make a new font""" - from FL import fl, Font - f = Font() - fl.Add(f) - rf = RFont(f) - if familyName is not None: - rf.info.familyName = familyName - if styleName is not None: - rf.info.styleName = styleName - return rf - -def AllFonts(): - """Return a list of all open fonts.""" - fontCount = len(fl) - all = [] - for index in xrange(fontCount): - naked = fl[index] - all.append(RFont(naked)) - return all - - from robofab.world import CurrentGlyph - -def getGlyphFromMask(g): - """Get a Fab glyph object for the data in the mask layer.""" - from robofab.objects.objectsFL import RGlyph as FL_RGlyph - from robofab.objects.objectsRF import RGlyph as RF_RGlyph - n = g.naked() - mask = n.mask - fg = FL_RGlyph(mask) - rf = RF_RGlyph() - pen = rf.getPointPen() - fg.drawPoints(pen) - rf.width = g.width # can we get to the mask glyph width without flipping the UI? - return rf - -def setMaskToGlyph(maskGlyph, targetGlyph, clear=True): - """Set the maskGlyph as a mask layer in targetGlyph. - maskGlyph is a FontLab or RoboFab RGlyph, orphaned or not. - targetGlyph is a FontLab RGLyph. - clear is a bool. False: keep the existing mask data, True: clear the existing mask data. - """ - from robofab.objects.objectsFL import RGlyph as FL_RGlyph - from FL import Glyph as FL_NakedGlyph - flGlyph = FL_NakedGlyph() # new, orphaned FL glyph - wrapped = FL_RGlyph(flGlyph) # rf wrapper for FL glyph - if not clear: - # copy the existing mask data first - existingMask = getGlyphFromMask(targetGlyph) - if existingMask is not None: - pen = FLPointContourPen(existingMask) - existingMask.drawPoints(pen) - pen = FLPointContourPen(wrapped) - maskGlyph.drawPoints(pen) # draw the data - targetGlyph.naked().mask = wrapped .naked() - targetGlyph.update() - -# the lib getter and setter are shared by RFont and RGlyph -def _get_lib(self): - data = self._object.customdata - if data: - f = StringIO(data) - try: - pList = readPlist(f) - except: # XXX ugh, plistlib can raise lots of things - # Anyway, customdata does not contain valid plist data, - # but we don't need to toss it! - pList = {"org.robofab.fontlab.customdata": Data(data)} - else: - pList = {} - # pass it along to the lib object - l = RLib(pList) - l.setParent(self) - return l - -def _set_lib(self, aDict): - l = RLib({}) - l.setParent(self) - l.update(aDict) - - -def _normalizeLineEndings(s): - return s.replace("\r\n", "\n").replace("\r", "\n") - - -class RFont(BaseFont): - """RoboFab UFO wrapper for FL Font object""" - - _title = "FLFont" - - def __init__(self, font=None): - BaseFont.__init__(self) - if font is None: - from FL import fl, Font - # rather than raise an error we could just start a new font. - font = Font() - fl.Add(font) - #raise RoboFabError, "RFont: there's nothing to wrap!?" - self._object = font - self._lib = {} - self._supportHints = True - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - - def keys(self): - keys = {} - for glyph in self._object.glyphs: - glyphName = glyph.name - if glyphName in keys: - n = 1 - while ("%s#%s" % (glyphName, n)) in keys: - n += 1 - newGlyphName = "%s#%s" % (glyphName, n) - print "RoboFab encountered a duplicate glyph name, renaming %r to %r" % (glyphName, newGlyphName) - glyphName = newGlyphName - glyph.name = glyphName - keys[glyphName] = None - return keys.keys() - - def has_key(self, glyphName): - glyph = self._object[glyphName] - if glyph is None: - return False - else: - return True - - __contains__ = has_key - - def __setitem__(self, glyphName, glyph): - self._object[glyphName] = glyph.naked() - - def __cmp__(self, other): - if not hasattr(other, '_object'): - return -1 - return self._compare(other) - # if self._object.file_name == other._object.file_name: - # # so, names match. - # # this will falsely identify two distinct "Untitled" - # # let's check some more - # return 0 - # else: - # return -1 - - -# def _get_psHints(self): -# h = PostScriptFontHintValues(self) -# h.setParent(self) -# return h -# -# psHints = property(_get_psHints, doc="font level postscript hint data") - - def _get_info(self): - return RInfo(self._object) - - info = property(_get_info, doc="font info object") - - def _get_features(self): - return RFeatures(self._object) - - features = property(_get_features, doc="features object") - - def _get_kerning(self): - kerning = {} - f = self._object - for g in f.glyphs: - for p in g.kerning: - try: - key = (g.name, f[p.key].name) - kerning[key] = p.value - except AttributeError: pass #catch for TT exception - rk = RKerning(kerning) - rk.setParent(self) - return rk - - kerning = property(_get_kerning, doc="a kerning object") - - def _set_groups(self, aDict): - g = RGroups({}) - g.setParent(self) - g.update(aDict) - - def _get_groups(self): - groups = {} - for i in self._object.classes: - # test to make sure that the class is properly formatted - if i.find(':') == -1: - continue - key = i.split(':')[0] - value = i.split(':')[1].lstrip().split(' ') - groups[key] = value - rg = RGroups(groups) - rg.setParent(self) - return rg - - groups = property(_get_groups, _set_groups, doc="a group object") - - lib = property(_get_lib, _set_lib, doc="font lib object") - - # - # attributes - # - - def _get_fontIndex(self): - # find the index of the font - # by comparing the file_name - # to all open fonts. if the - # font has no file_name, meaning - # it is a new, unsaved font, - # return the index of the first - # font with no file_name. - selfFileName = self._object.file_name - fontCount = len(fl) - for index in xrange(fontCount): - other = fl[index] - if other.file_name == selfFileName: - return index - - fontIndex = property(_get_fontIndex, doc="the fontindex for this font") - - def _get_path(self): - return self._object.file_name - - path = property(_get_path, doc="path to the font") - - def _get_fileName(self): - if self.path is None: - return None - return os.path.split(self.path) - - fileName = property(_get_fileName, doc="the font's file name") - - def _get_selection(self): - # return a list of glyph names for glyphs selected in the font window - l=[] - for i in range(len(self._object.glyphs)): - if fl.Selected(i) == 1: - l.append(self._object[i].name) - return l - - def _set_selection(self, list): - fl.Unselect() - for i in list: - fl.Select(i) - - selection = property(_get_selection, _set_selection, doc="the glyph selection in the font window") - - - def _makeGlyphlist(self): - # To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters. - gl = [] - for c in self: - gl.append(c) - return gl - - def _get_glyphs(self): - return self._makeGlyphlist() - - glyphs = property(_get_glyphs, doc="A list of all glyphs in the font, to allow iterations through Font.glyphs") - - def update(self): - """Don't forget to update the font when you are done.""" - fl.UpdateFont(self.fontIndex) - - def save(self, path=None): - """Save the font, path is required.""" - if not path: - if not self._object.file_name: - raise RoboFabError, "No destination path specified." - else: - path = self._object.file_name - fl.Save(self.fontIndex, path) - - def close(self, save=False): - """Close the font, saving is optional.""" - if save: - self.save() - else: - self._object.modified = 0 - fl.Close(self.fontIndex) - - def getGlyph(self, glyphName): - # XXX may need to become private - flGlyph = self._object[glyphName] - if flGlyph is not None: - glyph = RGlyph(flGlyph) - glyph.setParent(self) - return glyph - return self.newGlyph(glyphName) - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph.""" - # the old implementation always updated the font. - # that proved to be very slow. so, the updating is - # now left up to the caller where it can be more - # efficiently managed. - g = NewGlyph(self._object, glyphName, clear, updateFont=False) - return RGlyph(g) - - def insertGlyph(self, glyph, name=None): - """Returns a new glyph that has been inserted into the font. - name = another glyphname if you want to insert as with that.""" - from robofab.objects.objectsRF import RFont as _RFont - from robofab.objects.objectsRF import RGlyph as _RGlyph - oldGlyph = glyph - if name is None: - name = oldGlyph.name - # clear the destination glyph if it exists. - if self.has_key(name): - self[name].clear() - # get the parent for the glyph - otherFont = oldGlyph.getParent() - # in some cases we will use the native - # FL method for appending a glyph. - useNative = True - testingNative = True - while testingNative: - # but, maybe it is an orphan glyph. - # in that case we should not use the native method. - if otherFont is None: - useNative = False - testingNative = False - # or maybe the glyph is coming from a NoneLab font - if otherFont is not None: - if isinstance(otherFont, _RFont): - useNative = False - testingNative = False - # but, it could be a copied FL glyph - # which is a NoneLab glyph that - # has a FontLab font as the parent - elif isinstance(otherFont, RFont): - useNative = False - testingNative = False - # or, maybe the glyph is being replaced, in which - # case the native method should not be used - # since FL will destroy any references to the glyph - if self.has_key(name): - useNative = False - testingNative = False - # if the glyph contains components the native - # method should not be used since FL does - # not reference glyphs in components by - # name, but by index (!!!). - if len(oldGlyph.components) != 0: - useNative = False - testingNative = False - testingNative = False - # finally, insert the glyph. - if useNative: - font = self.naked() - otherFont = oldGlyph.getParent().naked() - self.naked().glyphs.append(otherFont[name]) - newGlyph = self.getGlyph(name) - else: - newGlyph = self.newGlyph(name) - newGlyph.appendGlyph(oldGlyph) - for attr in GLYPH_COPY_ATTRS: - if attr == "name": - value = name - else: - value = getattr(oldGlyph, attr) - setattr(newGlyph, attr, value) - if self._supportHints: - # now we need to transfer the hints from - # the old glyph to the new glyph. we'll do this - # via the dict to hint functions. - hintDict = {} - # if the glyph is a NoneLab glyph, then we need - # to extract the ps hints from the lib - if isinstance(oldGlyph, _RGlyph): - hintDict = oldGlyph.lib.get(postScriptHintDataLibKey, {}) - # otherwise we need to extract the hint dict from the glyph - else: - hintDict = _glyphHintsToDict(oldGlyph.naked()) - # now apply the hint data - if hintDict: - _dictHintsToGlyph(newGlyph.naked(), hintDict) - # delete any remaining hint data from the glyph lib - if newGlyph.lib.has_key(postScriptHintDataLibKey): - del newGlyph.lib[postScriptHintDataLibKey] - return newGlyph - - def removeGlyph(self, glyphName): - """remove a glyph from the font""" - index = self._object.FindGlyph(glyphName) - if index != -1: - del self._object.glyphs[index] - - # - # opentype - # - - def getOTClasses(self): - """Return all OpenType classes as a dict. Relies on properly formatted classes.""" - classes = {} - c = self._object.ot_classes - if c is None: - return classes - c = c.replace('\r', '').replace('\n', '').split(';') - for i in c: - if i.find('=') != -1: - value = [] - i = i.replace(' = ', '=') - name = i.split('=')[0] - v = i.split('=')[1].replace('[', '').replace(']', '').split(' ') - #catch double spaces? - for j in v: - if len(j) > 0: - value.append(j) - classes[name] = value - return classes - - def setOTClasses(self, dict): - """Set all OpenType classes.""" - l = [] - for i in dict.keys(): - l.append(''.join([i, ' = [', ' '.join(dict[i]), '];'])) - self._object.ot_classes = '\n'.join(l) - - def getOTClass(self, name): - """Get a specific OpenType class.""" - classes = self.getOTClasses() - return classes[name] - - def setOTClass(self, name, list): - """Set a specific OpenType class.""" - classes = self.getOTClasses() - classes[name] = list - self.setOTClasses(classes) - - def getOTFeatures(self): - """Return all OpenType features as a dict keyed by name. - The value is a string of the text of the feature.""" - features = {} - for i in self._object.features: - v = [] - for j in i.value.replace('\r', '\n').split('\n'): - if j.find(i.tag) == -1: - v.append(j) - features[i.tag] = '\n'.join(v) - return features - - def setOTFeatures(self, dict): - """Set all OpenType features in the font.""" - features= {} - for i in dict.keys(): - f = [] - f.append('feature %s {'%i) - f.append(dict[i]) - f.append('} %s;'%i) - features[i] = '\n'.join(f) - self._object.features.clean() - for i in features.keys(): - self._object.features.append(Feature(i, features[i])) - - def getOTFeature(self, name): - """return a specific OpenType feature.""" - features = self.getOTFeatures() - return features[name] - - def setOTFeature(self, name, text): - """Set a specific OpenType feature.""" - features = self.getOTFeatures() - features[name] = text - self.setOTFeatures(features) - - # - # guides - # - - def getVGuides(self): - """Return a list of wrapped vertical guides in this RFont""" - vguides=[] - for i in range(len(self._object.vguides)): - g = RGuide(self._object.vguides[i], i) - g.setParent(self) - vguides.append(g) - return vguides - - def getHGuides(self): - """Return a list of wrapped horizontal guides in this RFont""" - hguides=[] - for i in range(len(self._object.hguides)): - g = RGuide(self._object.hguides[i], i) - g.setParent(self) - hguides.append(g) - return hguides - - def appendHGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - angle = int(round(angle)) - g=Guide(position, angle) - self._object.hguides.append(g) - - def appendVGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - angle = int(round(angle)) - g=Guide(position, angle) - self._object.vguides.append(g) - - def removeHGuide(self, guide): - """Remove a horizontal guide.""" - pos = (guide.position, guide.angle) - for g in self.getHGuides(): - if (g.position, g.angle) == pos: - del self._object.hguides[g.index] - break - - def removeVGuide(self, guide): - """Remove a vertical guide.""" - pos = (guide.position, guide.angle) - for g in self.getVGuides(): - if (g.position, g.angle) == pos: - del self._object.vguides[g.index] - break - - def clearHGuides(self): - """Clear all horizontal guides.""" - self._object.hguides.clean() - - def clearVGuides(self): - """Clear all vertical guides.""" - self._object.vguides.clean() - - - # - # generators - # - - def generate(self, outputType, path=None): - """ - generate the font. outputType is the type of font to ouput. - --Ouput Types: - 'pctype1' : PC Type 1 font (binary/PFB) - 'pcmm' : PC MultipleMaster font (PFB) - 'pctype1ascii' : PC Type 1 font (ASCII/PFA) - 'pcmmascii' : PC MultipleMaster font (ASCII/PFA) - 'unixascii' : UNIX ASCII font (ASCII/PFA) - 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file) - 'otfcff' : PS OpenType (CFF-based) font (OTF) - 'otfttf' : PC TrueType/TT OpenType font (TTF) - 'macttf' : Mac TrueType font (generates suitcase) - 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork) - (doc adapted from http://dev.fontlab.net/flpydoc/) - - path can be a directory or a directory file name combo: - path="DirectoryA/DirectoryB" - path="DirectoryA/DirectoryB/MyFontName" - if no path is given, the file will be output in the same directory - as the vfb file. if no file name is given, the filename will be the - vfb file name with the appropriate suffix. - """ - outputType = outputType.lower() - if not _flGenerateTypes.has_key(outputType): - raise RoboFabError, "%s output type is not supported"%outputType - flOutputType, suffix = _flGenerateTypes[outputType] - if path is None: - filePath, fileName = os.path.split(self.path) - fileName = fileName.replace('.vfb', '') - else: - if os.path.isdir(path): - filePath = path - fileName = os.path.split(self.path)[1].replace('.vfb', '') - else: - filePath, fileName = os.path.split(path) - if '.' in fileName: - raise RoboFabError, "filename cannot contain periods.", fileName - fileName = '.'.join([fileName, suffix]) - finalPath = os.path.join(filePath, fileName) - if isinstance(finalPath, unicode): - finalPath = finalPath.encode("utf-8") - # generate is (oddly) an application level method - # rather than a font level method. because of this, - # the font must be the current font. so, make it so. - fl.ifont = self.fontIndex - fl.GenerateFont(flOutputType, finalPath) - - def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, - doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2): - from robofab.interface.all.dialogs import ProgressBar, Message - # special glyph name to file name conversion - if glyphNameToFileNameFunc is None: - glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() - if glyphNameToFileNameFunc is None: - from robofab.tools.glyphNameSchemes import glyphNameToShortFileName - glyphNameToFileNameFunc = glyphNameToShortFileName - # get a valid path - if not path: - if self.path is None: - Message("Please save this font first before exporting to UFO...") - return - else: - path = ufoLib.makeUFOPath(self.path) - # get the glyphs to export - if glyphs is None: - glyphs = self.keys() - # if the file exists, check the format version. - # if the format version being written is different - # from the format version of the existing UFO - # and only some files are set to be written - # raise an error. - if os.path.exists(path): - if os.path.exists(os.path.join(path, "metainfo.plist")): - reader = ufoLib.UFOReader(path) - existingFormatVersion = reader.formatVersion - if formatVersion != existingFormatVersion: - if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]: - Message("When overwriting an existing UFO with a different format version all files must be written.") - return - # the lib must be written if format version is 1 - if not doLib and formatVersion == 1: - Message("The lib must be written when exporting format version 1.") - return - # set up the progress bar - nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) - bar = None - if doProgress: - bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs)) - # try writing - try: - writer = ufoLib.UFOWriter(path, formatVersion=formatVersion) - ## We make a shallow copy if lib, since we add some stuff for export - ## that doesn't need to be retained in memory. - fontLib = dict(self.lib) - # write the font info - if doInfo: - global _IN_UFO_EXPORT - _IN_UFO_EXPORT = True - writer.writeInfo(self.info) - _IN_UFO_EXPORT = False - if bar: - bar.tick() - # write the kerning - if doKerning: - writer.writeKerning(self.kerning.asDict()) - if bar: - bar.tick() - # write the groups - if doGroups: - writer.writeGroups(self.groups) - if bar: - bar.tick() - # write the features - if doFeatures: - if formatVersion == 2: - writer.writeFeatures(self.features.text) - else: - self._writeOpenTypeFeaturesToLib(fontLib) - if bar: - bar.tick() - # write the lib - if doLib: - ## Always export the postscript font hint values to the lib in format version 1 - if formatVersion == 1: - d = self.psHints.asDict() - fontLib[postScriptHintDataLibKey] = d - ## Export the glyph order to the lib - glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs] - fontLib["public.glyphOrder"] = glyphOrder - ## export the features - if doFeatures and formatVersion == 1: - self._writeOpenTypeFeaturesToLib(fontLib) - if bar: - bar.tick() - writer.writeLib(fontLib) - if bar: - bar.tick() - # write the glyphs - if glyphs: - glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) - count = nonGlyphCount - for nakedGlyph in self.naked().glyphs: - if nakedGlyph.name not in glyphs: - continue - glyph = RGlyph(nakedGlyph) - if doHints: - hintStuff = _glyphHintsToDict(glyph.naked()) - if hintStuff: - glyph.lib[postScriptHintDataLibKey] = hintStuff - glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) - # remove the hint dict from the lib - if doHints and glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - glyphSet.writeContents() - # only blindly stop if the user says to - except KeyboardInterrupt: - if bar: - bar.close() - bar = None - # kill the bar - if bar: - bar.close() - - def _writeOpenTypeFeaturesToLib(self, fontLib): - # this should only be used for UFO format version 1 - flFont = self.naked() - cls = flFont.ot_classes - if cls is not None: - fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(cls).rstrip() + "\n" - if flFont.features: - features = {} - order = [] - for feature in flFont.features: - order.append(feature.tag) - features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n" - fontLib["org.robofab.opentype.features"] = features - fontLib["org.robofab.opentype.featureorder"] = order - - def readUFO(self, path, doProgress=False, - doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None): - """read a .ufo into the font""" - from robofab.pens.flPen import FLPointPen - from robofab.interface.all.dialogs import ProgressBar - # start up the reader - reader = ufoLib.UFOReader(path) - glyphSet = reader.getGlyphSet() - # get a list of glyphs that should be imported - if glyphs is None: - glyphs = glyphSet.keys() - # set up the progress bar - nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) - bar = None - if doProgress: - bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs)) - # start reading - try: - fontLib = reader.readLib() - # info - if doInfo: - reader.readInfo(self.info) - if bar: - bar.tick() - # glyphs - count = 1 - glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet) - for glyphName in glyphOrder: - if glyphName not in glyphs: - continue - glyph = self.newGlyph(glyphName, clear=True) - pen = FLPointPen(glyph.naked()) - glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) - if doHints: - hintData = glyph.lib.get(postScriptHintDataLibKey) - if hintData: - _dictHintsToGlyph(glyph.naked(), hintData) - # now that the hints have been extracted from the glyph - # there is no reason to keep the location in the lib. - if glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - # features - if doFeatures: - if reader.formatVersion == 1: - self._readOpenTypeFeaturesFromLib(fontLib) - else: - featureText = reader.readFeatures() - self.features.text = featureText - if bar: - bar.tick() - else: - # remove features stored in the lib - self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False) - # kerning - if doKerning: - self.kerning.clear() - self.kerning.update(reader.readKerning()) - if bar: - bar.tick() - # groups - if doGroups: - self.groups.clear() - self.groups.update(reader.readGroups()) - if bar: - bar.tick() - # hints in format version 1 - if doHints and reader.formatVersion == 1: - self.psHints._loadFromLib(fontLib) - else: - # remove hint data stored in the lib - if fontLib.has_key(postScriptHintDataLibKey): - del fontLib[postScriptHintDataLibKey] - # lib - if doLib: - self.lib.clear() - self.lib.update(fontLib) - if bar: - bar.tick() - # update the font - self.update() - # only blindly stop if the user says to - except KeyboardInterrupt: - bar.close() - bar = None - # kill the bar - if bar: - bar.close() - - def _getGlyphOrderFromLib(self, fontLib, glyphSet): - key = "public.glyphOrder" - glyphOrder = fontLib.get(key) - if glyphOrder is None: - key = "org.robofab.glyphOrder" - glyphOrder = fontLib.get(key) - if glyphOrder is not None: - # no need to keep track if the glyph order in lib once the font is loaded. - del fontLib[key] - glyphNames = [] - done = {} - for glyphName in glyphOrder: - if glyphName in glyphSet: - glyphNames.append(glyphName) - done[glyphName] = 1 - allGlyphNames = glyphSet.keys() - allGlyphNames.sort() - for glyphName in allGlyphNames: - if glyphName not in done: - glyphNames.append(glyphName) - else: - glyphNames = glyphSet.keys() - glyphNames.sort() - return glyphNames - - def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True): - # setFeatures may be False. in this case, this method - # should only clear the data from the lib. - classes = fontLib.get("org.robofab.opentype.classes") - if classes is not None: - del fontLib["org.robofab.opentype.classes"] - if setFeatures: - self.naked().ot_classes = classes - features = fontLib.get("org.robofab.opentype.features") - if features is not None: - order = fontLib.get("org.robofab.opentype.featureorder") - if order is None: - # for UFOs saved without the feature order, do the same as before. - order = features.keys() - order.sort() - else: - del fontLib["org.robofab.opentype.featureorder"] - del fontLib["org.robofab.opentype.features"] - #features = features.items() - orderedFeatures = [] - for tag in order: - oneFeature = features.get(tag) - if oneFeature is not None: - orderedFeatures.append((tag, oneFeature)) - if setFeatures: - self.naked().features.clean() - for tag, src in orderedFeatures: - self.naked().features.append(Feature(tag, src)) - - - -class RGlyph(BaseGlyph): - """RoboFab wrapper for FL Glyph object""" - - _title = "FLGlyph" - - def __init__(self, flGlyph): - #BaseGlyph.__init__(self) - if flGlyph is None: - raise RoboFabError, "RGlyph: there's nothing to wrap!?" - self._object = flGlyph - self._lib = {} - self._contours = None - - def __getitem__(self, index): - return self.contours[index] - - def __delitem__(self, index): - self._object.DeleteContour(index) - self._invalidateContours() - - def __len__(self): - return len(self.contours) - - lib = property(_get_lib, _set_lib, doc="glyph lib object") - - def _invalidateContours(self): - self._contours = None - - def _buildContours(self): - self._contours = [] - for contourIndex in range(self._object.GetContoursNumber()): - c = RContour(contourIndex) - c.setParent(self) - c._buildSegments() - self._contours.append(c) - - # - # attribute handlers - # - - def _get_index(self): - return self._object.parent.FindGlyph(self.name) - - index = property(_get_index, doc="return the index of the glyph in the font") - - 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") - - def _get_psName(self): - return self._object.name - - def _set_psName(self, value): - self._object.name = value - - psName = property(_get_psName, _set_psName, doc="name") - - def _get_baseName(self): - return self._object.name.split('.')[0] - - baseName = property(_get_baseName, doc="") - - def _get_unicode(self): - return self._object.unicode - - def _set_unicode(self, value): - self._object.unicode = value - - unicode = property(_get_unicode, _set_unicode, doc="unicode") - - def _get_unicodes(self): - return self._object.unicodes - - def _set_unicodes(self, value): - self._object.unicodes = value - - unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes") - - def _get_width(self): - return self._object.width - - def _set_width(self, value): - value = int(round(value)) - self._object.width = value - - width = property(_get_width, _set_width, doc="the width") - - def _get_box(self): - if not len(self.contours) and not len(self.components): - return (0, 0, 0, 0) - r = self._object.GetBoundingRect() - return (int(round(r.ll.x)), int(round(r.ll.y)), int(round(r.ur.x)), int(round(r.ur.y))) - - box = property(_get_box, doc="box of glyph as a tuple (xMin, yMin, xMax, yMax)") - - def _get_selected(self): - if fl.Selected(self._object.parent.FindGlyph(self._object.name)): - return 1 - else: - return 0 - - def _set_selected(self, value): - fl.Select(self._object.name, value) - - selected = property(_get_selected, _set_selected, doc="Select or deselect the glyph in the font window") - - def _get_mark(self): - return self._object.mark - - def _set_mark(self, value): - self._object.mark = value - - mark = property(_get_mark, _set_mark, doc="mark") - - def _get_note(self): - s = self._object.note - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_note(self, value): - if value is None: - value = '' - if type(value) == type(u""): - value = value.encode(LOCAL_ENCODING) - self._object.note = value - - note = property(_get_note, _set_note, doc="note") - - def _get_psHints(self): - # get an object representing the postscript zone information - return PostScriptGlyphHintValues(self) - - psHints = property(_get_psHints, doc="postscript hint data") - - # - # necessary evil - # - - def update(self): - """Don't forget to update the glyph when you are done.""" - fl.UpdateGlyph(self._object.parent.FindGlyph(self._object.name)) - - # - # methods to make RGlyph compatible with FL.Glyph - # ##are these still needed? - # - - def GetBoundingRect(self, masterIndex): - """FL compatibility""" - return self._object.GetBoundingRect(masterIndex) - - def GetMetrics(self, masterIndex): - """FL compatibility""" - return self._object.GetMetrics(masterIndex) - - def SetMetrics(self, value, masterIndex): - """FL compatibility""" - return self._object.SetMetrics(value, masterIndex) - - # - # object builders - # - - def _get_anchors(self): - return self.getAnchors() - - anchors = property(_get_anchors, doc="allow for iteration through glyph.anchors") - - def _get_components(self): - return self.getComponents() - - components = property(_get_components, doc="allow for iteration through glyph.components") - - def _get_contours(self): - if self._contours is None: - self._buildContours() - return self._contours - - contours = property(_get_contours, doc="allow for iteration through glyph.contours") - - def getAnchors(self): - """Return a list of wrapped anchors in this RGlyph.""" - anchors=[] - for i in range(len(self._object.anchors)): - a = RAnchor(self._object.anchors[i], i) - a.setParent(self) - anchors.append(a) - return anchors - - def getComponents(self): - """Return a list of wrapped components in this RGlyph.""" - components=[] - for i in range(len(self._object.components)): - c = RComponent(self._object.components[i], i) - c.setParent(self) - components.append(c) - return components - - def getVGuides(self): - """Return a list of wrapped vertical guides in this RGlyph""" - vguides=[] - for i in range(len(self._object.vguides)): - g = RGuide(self._object.vguides[i], i) - g.setParent(self) - vguides.append(g) - return vguides - - def getHGuides(self): - """Return a list of wrapped horizontal guides in this RGlyph""" - hguides=[] - for i in range(len(self._object.hguides)): - g = RGuide(self._object.hguides[i], i) - g.setParent(self) - hguides.append(g) - return hguides - - # - # tools - # - - def getPointPen(self): - self._invalidateContours() - # Now just don't muck with glyph.contours before you're done drawing... - return FLPointPen(self) - - def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): - """Append a component to the glyph. x and y are optional offset values""" - offset = roundPt((offset[0], offset[1])) - p = FLPointPen(self.naked()) - xx, yy = scale - dx, dy = offset - p.addComponent(baseGlyph, (xx, 0, 0, yy, dx, dy)) - - def appendAnchor(self, name, position): - """Append an anchor to the glyph""" - value = roundPt((position[0], position[1])) - anchor = Anchor(name, value[0], value[1]) - self._object.anchors.append(anchor) - - def appendHGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - g = Guide(position, angle) - self._object.hguides.append(g) - - def appendVGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - g = Guide(position, angle) - self._object.vguides.append(g) - - def clearContours(self): - self._object.Clear() - self._invalidateContours() - - def clearComponents(self): - """Clear all components.""" - self._object.components.clean() - - def clearAnchors(self): - """Clear all anchors.""" - self._object.anchors.clean() - - def clearHGuides(self): - """Clear all horizontal guides.""" - self._object.hguides.clean() - - def clearVGuides(self): - """Clear all vertical guides.""" - self._object.vguides.clean() - - def removeComponent(self, component): - """Remove a specific component from the glyph. This only works - if the glyph does not have duplicate components in the same location.""" - pos = (component.baseGlyph, component.offset, component.scale) - a = self.getComponents() - found = [] - for i in a: - if (i.baseGlyph, i.offset, i.scale) == pos: - found.append(i) - if len(found) > 1: - raise RoboFabError, 'Found more than one possible component to remove' - elif len(found) == 1: - del self._object.components[found[0].index] - else: - raise RoboFabError, 'Component does not exist' - - def removeContour(self, index): - """remove a specific contour from the glyph""" - self._object.DeleteContour(index) - self._invalidateContours() - - def removeAnchor(self, anchor): - """Remove a specific anchor from the glyph. This only works - if the glyph does not have anchors with duplicate names - in exactly the same location with the same mark.""" - pos = (anchor.name, anchor.position, anchor.mark) - a = self.getAnchors() - found = [] - for i in a: - if (i.name, i.position, i.mark) == pos: - found.append(i) - if len(found) > 1: - raise RoboFabError, 'Found more than one possible anchor to remove' - elif len(found) == 1: - del self._object.anchors[found[0].index] - else: - raise RoboFabError, 'Anchor does not exist' - - def removeHGuide(self, guide): - """Remove a horizontal guide.""" - pos = (guide.position, guide.angle) - for g in self.getHGuides(): - if (g.position, g.angle) == pos: - del self._object.hguides[g.index] - break - - def removeVGuide(self, guide): - """Remove a vertical guide.""" - pos = (guide.position, guide.angle) - for g in self.getVGuides(): - if (g.position, g.angle) == pos: - del self._object.vguides[g.index] - break - - 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 removeOverlap(self): - """Remove overlap""" - self._object.RemoveOverlap() - self._invalidateContours() - - def decompose(self): - """Decompose all components""" - self._object.Decompose() - self._invalidateContours() - - ##broken! - #def removeHints(self): - # """Remove the hints.""" - # self._object.RemoveHints() - - def autoHint(self): - """Automatically generate type 1 hints.""" - self._object.Autohint() - - def move(self, (x, y), contours=True, components=True, anchors=True): - """Move a glyph's items that are flagged as True""" - x, y = roundPt((x, y)) - self._object.Shift(Point(x, y)) - for c in self.getComponents(): - c.move((x, y)) - for a in self.getAnchors(): - a.move((x, y)) - - def clear(self, contours=True, components=True, anchors=True, guides=True, hints=True): - """Clear all items marked as true from the glyph""" - if contours: - self._object.Clear() - self._invalidateContours() - if components: - self._object.components.clean() - if anchors: - self._object.anchors.clean() - if guides: - self._object.hguides.clean() - self._object.vguides.clean() - if hints: - # RemoveHints requires an "integer mode" argument - # but it is not documented. from some simple experiments - # i deduced that - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(3) - - # - # special treatment for GlyphMath support in FontLab - # - - def _getMathDestination(self): - from robofab.objects.objectsRF import RGlyph as _RGlyph - return _RGlyph() - - def copy(self, aParent=None): - """Make a copy of this glyph. - Note: the copy is not a duplicate fontlab glyph, but - a RF RGlyph with the same outlines. The new glyph is - not part of the fontlab font in any way. Use font.appendGlyph(glyph) - to get it in a FontLab glyph again.""" - from robofab.objects.objectsRF import RGlyph as _RGlyph - newGlyph = _RGlyph() - newGlyph.appendGlyph(self) - for attr in GLYPH_COPY_ATTRS: - value = getattr(self, attr) - setattr(newGlyph, attr, value) - # hints - doHints = False - parent = self.getParent() - if parent is not None and parent._supportHints: - hintStuff = _glyphHintsToDict(self.naked()) - if hintStuff: - newGlyph.lib[postScriptHintDataLibKey] = hintStuff - if aParent is not None: - newGlyph.setParent(aParent) - elif self.getParent() is not None: - newGlyph.setParent(self.getParent()) - return newGlyph - - def __mul__(self, factor): - return self.copy() *factor - - __rmul__ = __mul__ - - def __sub__(self, other): - return self.copy() - other.copy() - - def __add__(self, other): - return self.copy() + other.copy() - - - -class RContour(BaseContour): - - """RoboFab wrapper for non FL contour object""" - - _title = "FLContour" - - def __init__(self, index): - self._index = index - self._parentGlyph = None - self.segments = [] - - def __len__(self): - return len(self.points) - - def _buildSegments(self): - ####################### - # Notes about FL node contour structure - ####################### - # for TT curves, FL lists them as seperate nodes: - # [move, off, off, off, line, off, off] - # and, this list is sequential. after the last on curve, - # it is possible (and likely) that there will be more offCurves - # in our segment object, these should be associated with the - # first segment in the contour. - # - # for PS curves, it is a very different scenerio. - # curve nodes contain points: - # [on, off, off] - # and the list is not in sequential order. the first point in - # the list is the on curve and the subsequent points are the off - # curve points leading up to that on curve. - # - # it is very important to remember these structures when trying - # to understand the code below - - self.segments = [] - offList = [] - nodes = self._nakedParent.nodes - for index in range(self._nodeLength): - x = index + self._startNodeIndex - node = nodes[x] - # we do have a loose off curve. deal with it. - if node.type == flOFFCURVE: - offList.append(x) - # we are not dealing with a loose off curve - else: - s = RSegment(x) - s.setParent(self) - # but do we have a collection of loose off curves above? - # if so, apply them to the segment, and clear the list - if len(offList) != 0: - s._looseOffCurve = offList - offList = [] - self.segments.append(s) - # do we have some off curves now that the contour is complete? - if len(offList) != 0: - # ugh. apply them to the first segment - self.segments[0]._looseOffCurve = offList - - def setParent(self, parentGlyph): - self._parentGlyph = parentGlyph - - def getParent(self): - return self._parentGlyph - - def _get__nakedParent(self): - return self._parentGlyph.naked() - - _nakedParent = property(_get__nakedParent, doc="") - - def _get__startNodeIndex(self): - return self._nakedParent.GetContourBegin(self._index) - - _startNodeIndex = property(_get__startNodeIndex, doc="") - - def _get__nodeLength(self): - return self._nakedParent.GetContourLength(self._index) - - _nodeLength = property(_get__nodeLength, doc="") - - def _get__lastNodeIndex(self): - return self._startNodeIndex + self._nodeLength - 1 - - _lastNodeIndex = property(_get__lastNodeIndex, doc="") - - def _previousNodeIndex(self, index): - return (index - 1) % self._nodeLength - - def _nextNodeIndex(self, index): - return (index + 1) % self._nodeLength - - def _getNode(self, index): - return self._nodes[index] - - def _get__nodes(self): - nodes = [] - for node in self._nakedParent.nodes[self._startNodeIndex:self._startNodeIndex+self._nodeLength-1]: - nodes.append(node) - return nodes - - _nodes = property(_get__nodes, doc="") - - 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="") - - def _get_bPoints(self): - bPoints = [] - for segment in self.segments: - bp = RBPoint(segment.index) - bp.setParent(self) - bPoints.append(bp) - return bPoints - - bPoints = property(_get_bPoints, doc="") - - def _get_index(self): - return self._index - - def _set_index(self, index): - if index != self._index: - self._nakedParent.ReorderContour(self._index, index) - # reorder and set the _index of the existing RContour objects - # this will be a better solution than reconstructing all the objects - # segment objects will still, sadly, have to be reconstructed - contourList = self.getParent().contours - contourList.insert(index, contourList.pop(self._index)) - for i in range(len(contourList)): - contourList[i]._index = i - contourList[i]._buildSegments() - - - index = property(_get_index, _set_index, doc="the index of the contour") - - def _get_selected(self): - selected = 0 - nodes = self._nodes - for node in nodes: - if node.selected == 1: - selected = 1 - break - return selected - - def _set_selected(self, value): - if value == 1: - self._nakedParent.SelectContour(self._index) - else: - for node in self._nodes: - node.selected = value - - selected = property(_get_selected, _set_selected, doc="selection of the contour: 1-selected or 0-unselected") - - def appendSegment(self, segmentType, points, smooth=False): - segment = self.insertSegment(index=self._nodeLength, segmentType=segmentType, points=points, smooth=smooth) - return segment - - def insertSegment(self, index, segmentType, points, smooth=False): - """insert a seggment into the contour""" - # do a qcurve insertion - if segmentType == QCURVE: - count = 0 - for point in points[:-1]: - newNode = Node(flOFFCURVE, Point(point[0], point[1])) - self._nakedParent.Insert(newNode, self._startNodeIndex + index + count) - count = count + 1 - newNode = Node(flLINE, Point(points[-1][0], points[-1][1])) - self._nakedParent.Insert(newNode, self._startNodeIndex + index +len(points) - 1) - # do a regular insertion - else: - onX, onY = points[-1] - newNode = Node(_rfToFLSegmentType(segmentType), Point(onX, onY)) - # fix the off curves in case the user is inserting a curve - # but is not specifying off curve points - if segmentType == CURVE and len(points) == 1: - pSeg = self._prevSegment(index) - pOn = pSeg.onCurve - newNode.points[1].Assign(Point(pOn.x, pOn.y)) - newNode.points[2].Assign(Point(onX, onY)) - for pointIndex in range(len(points[:-1])): - x, y = points[pointIndex] - newNode.points[1 + pointIndex].Assign(Point(x, y)) - if smooth: - newNode.alignment = flSMOOTH - self._nakedParent.Insert(newNode, self._startNodeIndex + index) - self._buildSegments() - return self.segments[index] - - def removeSegment(self, index): - """remove a segment from the contour""" - segment = self.segments[index] - # we have a qcurve. umph. - if segment.type == QCURVE: - indexList = [segment._nodeIndex] + segment._looseOffCurve - indexList.sort() - indexList.reverse() - parent = self._nakedParent - for nodeIndex in indexList: - parent.DeleteNode(nodeIndex) - # we have a more sane structure to follow - else: - # store some info for later - next = self._nextSegment(index) - nextOffA = None - nextOffB = None - nextType = next.type - if nextType != LINE and nextType != MOVE: - pA = next.offCurve[0] - nextOffA = (pA.x, pA.y) - pB = next.offCurve[-1] - nextOffB = (pB.x, pB.y) - nodeIndex = segment._nodeIndex - self._nakedParent.DeleteNode(nodeIndex) - self._buildSegments() - # now we must override FL guessing about offCurves - next = self._nextSegment(index - 1) - nextType = next.type - if nextType != LINE and nextType != MOVE: - pA = next.offCurve[0] - pB = next.offCurve[-1] - pA.x, pA.y = nextOffA - pB.x, pB.y = nextOffB - - def reverseContour(self): - """reverse contour direction""" - self._nakedParent.ReverseContour(self._index) - self._buildSegments() - - def setStartSegment(self, segmentIndex): - """set the first node on the contour""" - self._nakedParent.SetStartNode(self._startNodeIndex + segmentIndex) - self.getParent()._invalidateContours() - self.getParent()._buildContours() - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RContour is not implemented." - - - -class RSegment(BaseSegment): - - _title = "FLSegment" - - def __init__(self, flNodeIndex): - BaseSegment.__init__(self) - self._nodeIndex = flNodeIndex - self._looseOffCurve = [] #a list of indexes to loose off curve nodes - - def _get__node(self): - glyph = self.getParent()._nakedParent - return glyph.nodes[self._nodeIndex] - - _node = property(_get__node, doc="") - - def _get_qOffCurve(self): - nodes = self.getParent()._nakedParent.nodes - off = [] - for x in self._looseOffCurve: - off.append(nodes[x]) - return off - - _qOffCurve = property(_get_qOffCurve, doc="free floating off curve nodes in the segment") - - def _get_index(self): - contour = self.getParent() - return self._nodeIndex - contour._startNodeIndex - - index = property(_get_index, doc="") - - def _isQCurve(self): - # loose off curves only appear in q curves - if len(self._looseOffCurve) != 0: - return True - return False - - def _get_type(self): - if self._isQCurve(): - return QCURVE - return _flToRFSegmentType(self._node.type) - - def _set_type(self, segmentType): - if self._isQCurve(): - raise RoboFabError, 'qcurve point types cannot be changed' - oldNode = self._node - oldType = oldNode.type - oldPointType = _flToRFSegmentType(oldType) - if oldPointType == MOVE: - raise RoboFabError, '%s point types cannot be changed'%oldPointType - if segmentType == MOVE or segmentType == OFFCURVE: - raise RoboFabError, '%s point types cannot be assigned'%oldPointType - if oldPointType == segmentType: - return - oldNode.type = _rfToFLSegmentType(segmentType) - - type = property(_get_type, _set_type, doc="") - - def _get_smooth(self): - alignment = self._node.alignment - if alignment == flSMOOTH or alignment == flFIXED: - return True - return False - - def _set_smooth(self, value): - if value: - self._node.alignment = flSMOOTH - else: - self._node.alignment = flSHARP - - smooth = property(_get_smooth, _set_smooth, doc="") - - def _get_points(self): - points = [] - node = self._node - # gather the off curves - # - # are we dealing with a qCurve? ugh. - # gather the loose off curves - if self._isQCurve(): - off = self._qOffCurve - x = 0 - for n in off: - p = RPoint(0) - p.setParent(self) - p._qOffIndex = x - points.append(p) - x = x + 1 - # otherwise get the points associated with the node - else: - index = 1 - for point in node.points[1:]: - p = RPoint(index) - p.setParent(self) - points.append(p) - index = index + 1 - # the last point should always be the on curve - p = RPoint(0) - p.setParent(self) - points.append(p) - return points - - points = property(_get_points, doc="") - - def _get_selected(self): - return self._node.selected - - def _set_selected(self, value): - self._node.selected = value - - selected = property(_get_selected, _set_selected, doc="") - - def move(self, (x, y)): - x, y = roundPt((x, y)) - self._node.Shift(Point(x, y)) - if self._isQCurve(): - qOff = self._qOffCurve - for node in qOff: - node.Shift(Point(x, y)) - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RSegment is not implemented." - - - -class RPoint(BasePoint): - - _title = "FLPoint" - - def __init__(self, pointIndex): - #BasePoint.__init__(self) - self._pointIndex = pointIndex - self._qOffIndex = None - - def _get__parentGlyph(self): - return self._parentContour.getParent() - - _parentGlyph = property(_get__parentGlyph, doc="") - - def _get__parentContour(self): - return self._parentSegment.getParent() - - _parentContour = property(_get__parentContour, doc="") - - def _get__parentSegment(self): - return self.getParent() - - _parentSegment = property(_get__parentSegment, doc="") - - def _get__node(self): - if self._qOffIndex is not None: - return self.getParent()._qOffCurve[self._qOffIndex] - return self.getParent()._node - - _node = property(_get__node, doc="") - - def _get__point(self): - return self._node.points[self._pointIndex] - - _point = property(_get__point, doc="") - - def _get_x(self): - return self._point.x - - def _set_x(self, value): - value = int(round(value)) - self._point.x = value - - x = property(_get_x, _set_x, doc="") - - def _get_y(self): - return self._point.y - - def _set_y(self, value): - value = int(round(value)) - self._point.y = value - - y = property(_get_y, _set_y, doc="") - - def _get_type(self): - if self._pointIndex == 0: - # FL store quad contour data as a list of off curves and lines - # (see note in RContour._buildSegments). So, we need to do - # a bit of trickery to return a decent point type. - # if the straight FL node type is off curve, it is a loose - # quad off curve. return that. - tp = _flToRFSegmentType(self._node.type) - if tp == OFFCURVE: - return OFFCURVE - # otherwise we are dealing with an on curve. in this case, - # we attempt to get the parent segment type and return it. - segment = self.getParent() - if segment is not None: - return segment.type - # we must not have a segment, fall back to straight conversion - return tp - return OFFCURVE - - type = property(_get_type, doc="") - - def _set_selected(self, value): - if self._pointIndex == 0: - self._node.selected = value - - def _get_selected(self): - if self._pointIndex == 0: - return self._node.selected - return False - - selected = property(_get_selected, _set_selected, doc="") - - def move(self, (x, y)): - x, y = roundPt((x, y)) - self._point.Shift(Point(x, y)) - - def scale(self, (x, y), center=(0, 0)): - centerX, centerY = roundPt(center) - point = self._point - point.x, point.y = _scalePointFromCenter((point.x, point.y), (x, y), (centerX, centerY)) - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RPoint is not implemented." - - -class RBPoint(BaseBPoint): - - _title = "FLBPoint" - - def __init__(self, segmentIndex): - #BaseBPoint.__init__(self) - self._segmentIndex = segmentIndex - - def _get__parentSegment(self): - return self.getParent().segments[self._segmentIndex] - - _parentSegment = property(_get__parentSegment, doc="") - - def _get_index(self): - return self._segmentIndex - - index = property(_get_index, doc="") - - def _get_selected(self): - return self._parentSegment.selected - - def _set_selected(self, value): - self._parentSegment.selected = value - - selected = property(_get_selected, _set_selected, doc="") - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RBPoint is not implemented." - - -class RComponent(BaseComponent): - - """RoboFab wrapper for FL Component object""" - - _title = "FLComponent" - - def __init__(self, flComponent, index): - BaseComponent.__init__(self) - self._object = flComponent - self._index=index - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of component") - - def _get_baseGlyph(self): - return self._object.parent.parent[self._object.index].name - - baseGlyph = property(_get_baseGlyph, doc="") - - def _get_offset(self): - return (int(self._object.delta.x), int(self._object.delta.y)) - - def _set_offset(self, value): - value = roundPt((value[0], value[1])) - self._object.delta=Point(value[0], value[1]) - - offset = property(_get_offset, _set_offset, doc="the offset of the component") - - def _get_scale(self): - return (self._object.scale.x, self._object.scale.y) - - def _set_scale(self, (x, y)): - self._object.scale=Point(x, y) - - scale = property(_get_scale, _set_scale, doc="the scale of the component") - - def move(self, (x, y)): - """Move the component""" - x, y = roundPt((x, y)) - self._object.delta=Point(self._object.delta.x+x, self._object.delta.y+y) - - def decompose(self): - """Decompose the component""" - self._object.Paste() - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RComponent is not implemented." - - - -class RAnchor(BaseAnchor): - """RoboFab wrapper for FL Anchor object""" - - _title = "FLAnchor" - - def __init__(self, flAnchor, index): - BaseAnchor.__init__(self) - self._object = flAnchor - self._index = index - - def _get_y(self): - return self._object.y - - def _set_y(self, value): - self._object.y = int(round(value)) - - y = property(_get_y, _set_y, doc="y") - - def _get_x(self): - return self._object.x - - def _set_x(self, value): - self._object.x = int(round(value)) - - x = property(_get_x, _set_x, doc="x") - - 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") - - def _get_mark(self): - return self._object.mark - - def _set_mark(self, value): - self._object.mark = value - - mark = property(_get_mark, _set_mark, doc="mark") - - def _get_index(self): - return self._index - - 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): - value = roundPt((value[0], value[1])) - self._object.x=value[0] - self._object.y=value[1] - - position = property(_get_position, _set_position, doc="position of the anchor") - - - -class RGuide(BaseGuide): - - """RoboFab wrapper for FL Guide object""" - - _title = "FLGuide" - - def __init__(self, flGuide, index): - BaseGuide.__init__(self) - self._object = flGuide - self._index = index - - def __repr__(self): - # this is a doozy! - parent = "unknown_parent" - parentObject = self.getParent() - if parentObject is not None: - # do we have a font? - try: - parent = parentObject.info.postscriptFullName - except AttributeError: - # or do we have a glyph? - try: - parent = parentObject.name - # we must be an orphan - except AttributeError: pass - return "<Robofab guide wrapper for %s>"%parent - - def _get_position(self): - return self._object.position - - def _set_position(self, value): - self._object.position = value - - position = property(_get_position, _set_position, doc="position") - - def _get_angle(self): - return self._object.angle - - def _set_angle(self, value): - self._object.angle = value - - angle = property(_get_angle, _set_angle, doc="angle") - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of the guide") - - -class RGroups(BaseGroups): - - """RoboFab wrapper for FL group data""" - - _title = "FLGroups" - - def __init__(self, aDict): - self.update(aDict) - - def __setitem__(self, key, value): - # override baseclass so that data is stored in FL classes - if not isinstance(key, str): - raise RoboFabError, 'key must be a string' - if not isinstance(value, list): - raise RoboFabError, 'group must be a list' - super(RGroups, self).__setitem__(key, value) - self._setFLGroups() - - def __delitem__(self, key): - # override baseclass so that data is stored in FL classes - super(RGroups, self).__delitem__(key) - self._setFLGroups() - - def _setFLGroups(self): - # set the group data into the font. - if self.getParent() is not None: - groups = [] - for i in self.keys(): - value = ' '.join(self[i]) - groups.append(': '.join([i, value])) - groups.sort() - self.getParent().naked().classes = groups - - def update(self, aDict): - # override baseclass so that data is stored in FL classes - super(RGroups, self).update(aDict) - self._setFLGroups() - - def clear(self): - # override baseclass so that data is stored in FL classes - super(RGroups, self).clear() - self._setFLGroups() - - def pop(self, key): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).pop(key) - self._setFLGroups() - return i - - def popitem(self): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).popitem() - self._setFLGroups() - return i - - def setdefault(self, key, value=None): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).setdefault(key, value) - self._setFLGroups() - return i - - -class RKerning(BaseKerning): - - """RoboFab wrapper for FL Kerning data""" - - _title = "FLKerning" - - def __setitem__(self, pair, value): - if not isinstance(pair, tuple): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if len(pair) != 2: - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if value == 0: - if self._kerning.get(pair) is not None: - #see note about setting kerning values to 0 below - self._setFLKerning(pair, 0) - del self._kerning[pair] - else: - #self._kerning[pair] = value - self._setFLKerning(pair, value) - - def _setFLKerning(self, pair, value): - # write a pair back into the font - # - # this is fairly speedy, but setting a pair to 0 is roughly - # 2-3 times slower than setting a real value. this is because - # of all the hoops that must be jumped through to keep FL - # from storing kerning pairs with a value of 0. - parentFont = self.getParent().naked() - left = parentFont[pair[0]] - right = parentFont.FindGlyph(pair[1]) - # the left glyph doesn not exist - if left is None: - return - # the right glyph doesn not exist - if right == -1: - return - self._kerning[pair] = value - leftName = pair[0] - value = int(round(value)) - # pairs set to 0 need to be handled carefully. FL will allow - # for pairs to have a value of 0 (!?), so we must catch them - # when they pop up and make sure that the pair is actually - # removed from the font. - if value == 0: - foundPair = False - # if the value is 0, we don't need to construct a pair - # we just need to make sure that the pair is not in the list - pairs = [] - # so, go through all the pairs and add them to a new list - for flPair in left.kerning: - # we have found the pair. flag it. - if flPair.key == right: - foundPair = True - # not the pair. add it to the list. - else: - pairs.append((flPair.key, flPair.value)) - # if we found it, write it back to the glyph. - if foundPair: - left.kerning = [] - for p in pairs: - new = KerningPair(p[0], p[1]) - left.kerning.append(new) - else: - # non-zero pairs are a bit easier to handle - # we just need to look to see if the pair exists - # if so, change the value and stop the loop. - # if not, add a new pair to the glyph - self._kerning[pair] = value - foundPair = False - for flPair in left.kerning: - if flPair.key == right: - flPair.value = value - foundPair = True - break - if not foundPair: - p = KerningPair(right, value) - left.kerning.append(p) - - def update(self, kerningDict): - """replace kerning data with the data in the given kerningDict""" - # override base class here for speed - parentFont = self.getParent().naked() - # add existing data to the new kerning dict is not being replaced - for pair in self.keys(): - if not kerningDict.has_key(pair): - kerningDict[pair] = self._kerning[pair] - # now clear the existing kerning to make sure that - # all the kerning in residing in the glyphs is gone - self.clear() - self._kerning = kerningDict - kDict = {} - # nest the pairs into a dict keyed by the left glyph - # {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...} - for left, right in kerningDict.keys(): - value = kerningDict[left, right] - if not left in kDict: - kDict[left] = {} - kDict[left][right] = value - for left in kDict.keys(): - leftGlyph = parentFont[left] - if leftGlyph is not None: - for right in kDict[left].keys(): - value = kDict[left][right] - if value != 0: - rightIndex = parentFont.FindGlyph(right) - if rightIndex != -1: - p = KerningPair(rightIndex, value) - leftGlyph.kerning.append(p) - - def clear(self): - """clear all kerning""" - # override base class here for speed - self._kerning = {} - for glyph in self.getParent().naked().glyphs: - glyph.kerning = [] - - def __add__(self, other): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from sets import Set - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - k = Set(self.keys()) | Set(other.keys()) - for key in k: - new[key] = self.get(key, 0) + other.get(key, 0) - return new - - def __sub__(self, other): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from sets import Set - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - k = Set(self.keys()) | Set(other.keys()) - for key in k: - new[key] = self.get(key, 0) - other.get(key, 0) - return new - - def __mul__(self, factor): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - for name, value in self.items(): - new[name] = value * factor - return new - - __rmul__ = __mul__ - - def __div__(self, factor): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - if factor == 0: - raise ZeroDivisionError - return self.__mul__(1.0/factor) - - -class RLib(BaseLib): - - """RoboFab wrapper for FL lib""" - - # XXX: As of FL 4.6 the customdata field in glyph objects is busted. - # storing anything there causes the glyph to become uneditable. - # however, the customdata field in font objects is stable. - - def __init__(self, aDict): - self.update(aDict) - - def __setitem__(self, key, value): - # override baseclass so that data is stored in customdata field - super(RLib, self).__setitem__(key, value) - self._stashLib() - - def __delitem__(self, key): - # override baseclass so that data is stored in customdata field - super(RLib, self).__delitem__(key) - self._stashLib() - - def _stashLib(self): - # write the plist into the customdata field of the FL object - if self.getParent() is None: - return - if not self: - data = None - elif len(self) == 1 and "org.robofab.fontlab.customdata" in self: - data = self["org.robofab.fontlab.customdata"].data - else: - f = StringIO() - writePlist(self, f) - data = f.getvalue() - f.close() - parent = self.getParent() - parent.naked().customdata = data - - def update(self, aDict): - # override baseclass so that data is stored in customdata field - super(RLib, self).update(aDict) - self._stashLib() - - def clear(self): - # override baseclass so that data is stored in customdata field - super(RLib, self).clear() - self._stashLib() - - def pop(self, key): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).pop(key) - self._stashLib() - return i - - def popitem(self): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).popitem() - self._stashLib() - return i - - def setdefault(self, key, value=None): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).setdefault(key, value) - self._stashLib() - return i - - -def _infoMapDict(**kwargs): - default = dict( - nakedAttribute=None, - type=None, - requiresSetNum=False, - masterSpecific=False, - libLocation=None, - specialGetSet=False - ) - default.update(kwargs) - return default - -def _flipDict(d): - f = {} - for k, v in d.items(): - f[v] = k - return f - -_styleMapStyleName_fromFL = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" -} -_styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL) - -_postscriptWindowsCharacterSet_fromFL = { - 0 : 1, - 1 : 2, - 2 : 3, - 77 : 4, - 128 : 5, - 129 : 6, - 130 : 7, - 134 : 8, - 136 : 9, - 161 : 10, - 162 : 11, - 163 : 12, - 177 : 13, - 178 : 14, - 186 : 15, - 200 : 16, - 204 : 17, - 222 : 18, - 238 : 19, - 255 : 20, -} -_postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL) - -_openTypeOS2Type_toFL = { - 1 : 0x0002, - 2 : 0x0004, - 3 : 0x0008, - 8 : 0x0100, - 9 : 0x0200, -} -_openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL) - -_openTypeOS2WidthClass_fromFL = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9, -} -_openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL) - -_postscriptHintAttributes = set(( - "postscriptBlueValues", - "postscriptOtherBlues", - "postscriptFamilyBlues", - "postscriptFamilyOtherBlues", - "postscriptStemSnapH", - "postscriptStemSnapV", -)) - - -class RInfo(BaseInfo): - - """RoboFab wrapper for FL Font Info""" - - _title = "FLInfo" - - _ufoToFLAttrMapping = { - "familyName" : _infoMapDict(valueType=str, nakedAttribute="family_name"), - "styleName" : _infoMapDict(valueType=str, nakedAttribute="style_name"), - "styleMapFamilyName" : _infoMapDict(valueType=str, nakedAttribute="menu_name"), - "styleMapStyleName" : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True), - "versionMajor" : _infoMapDict(valueType=int, nakedAttribute="version_major"), - "versionMinor" : _infoMapDict(valueType=int, nakedAttribute="version_minor"), - "year" : _infoMapDict(valueType=int, nakedAttribute="year"), - "copyright" : _infoMapDict(valueType=str, nakedAttribute="copyright"), - "trademark" : _infoMapDict(valueType=str, nakedAttribute="trademark"), - "unitsPerEm" : _infoMapDict(valueType=int, nakedAttribute="upm"), - "descender" : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True), - "xHeight" : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True), - "capHeight" : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True), - "ascender" : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True), - "italicAngle" : _infoMapDict(valueType=float, nakedAttribute="italic_angle"), - "note" : _infoMapDict(valueType=str, nakedAttribute="note"), - "openTypeHeadCreated" : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values - "openTypeHeadLowestRecPPEM" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"), - "openTypeHeadFlags" : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface. - "openTypeHheaAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"), - "openTypeHheaDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"), - "openTypeHheaLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"), - "openTypeHheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeHheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeHheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeNameDesigner" : _infoMapDict(valueType=str, nakedAttribute="designer"), - "openTypeNameDesignerURL" : _infoMapDict(valueType=str, nakedAttribute="designer_url"), - "openTypeNameManufacturer" : _infoMapDict(valueType=str, nakedAttribute="source"), - "openTypeNameManufacturerURL" : _infoMapDict(valueType=str, nakedAttribute="vendor_url"), - "openTypeNameLicense" : _infoMapDict(valueType=str, nakedAttribute="license"), - "openTypeNameLicenseURL" : _infoMapDict(valueType=str, nakedAttribute="license_url"), - "openTypeNameVersion" : _infoMapDict(valueType=str, nakedAttribute="tt_version"), - "openTypeNameUniqueID" : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"), - "openTypeNameDescription" : _infoMapDict(valueType=str, nakedAttribute="notice"), - "openTypeNamePreferredFamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"), - "openTypeNamePreferredSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"), - "openTypeNameCompatibleFullName" : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"), - "openTypeNameSampleText" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeNameWWSFamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeNameWWSSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeOS2WidthClass" : _infoMapDict(valueType=int, nakedAttribute="width"), - "openTypeOS2WeightClass" : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True), - "openTypeOS2Selection" : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0 - "openTypeOS2VendorID" : _infoMapDict(valueType=str, nakedAttribute="vendor"), - "openTypeOS2Panose" : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True), - "openTypeOS2FamilyClass" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True), - "openTypeOS2UnicodeRanges" : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"), - "openTypeOS2CodePageRanges" : _infoMapDict(valueType="intList", nakedAttribute="codepages"), - "openTypeOS2TypoAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"), - "openTypeOS2TypoDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"), - "openTypeOS2TypoLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"), - "openTypeOS2WinAscent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"), - "openTypeOS2WinDescent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True), - "openTypeOS2Type" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True), - "openTypeOS2SubscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"), - "openTypeOS2SubscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"), - "openTypeOS2SubscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"), - "openTypeOS2SubscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"), - "openTypeOS2SuperscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"), - "openTypeOS2SuperscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"), - "openTypeOS2SuperscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"), - "openTypeOS2SuperscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"), - "openTypeOS2StrikeoutSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"), - "openTypeOS2StrikeoutPosition" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"), - "openTypeVheaVertTypoAscender" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaVertTypoDescender" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaVertTypoLineGap" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), - "postscriptFontName" : _infoMapDict(valueType=str, nakedAttribute="font_name"), - "postscriptFullName" : _infoMapDict(valueType=str, nakedAttribute="full_name"), - "postscriptSlantAngle" : _infoMapDict(valueType=float, nakedAttribute="slant_angle"), - "postscriptUniqueID" : _infoMapDict(valueType=int, nakedAttribute="unique_id"), - "postscriptUnderlineThickness" : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"), - "postscriptUnderlinePosition" : _infoMapDict(valueType=int, nakedAttribute="underline_position"), - "postscriptIsFixedPitch" : _infoMapDict(valueType="boolint", nakedAttribute="is_fixed_pitch"), - "postscriptBlueValues" : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True), - "postscriptOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True), - "postscriptFamilyBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True), - "postscriptFamilyOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True), - "postscriptStemSnapH" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True), - "postscriptStemSnapV" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True), - "postscriptBlueFuzz" : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True), - "postscriptBlueShift" : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True), - "postscriptBlueScale" : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True), - "postscriptForceBold" : _infoMapDict(valueType="boolint", nakedAttribute="force_bold", masterSpecific=True), - "postscriptDefaultWidthX" : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True), - "postscriptNominalWidthX" : _infoMapDict(valueType=int, nakedAttribute=None), - "postscriptWeightName" : _infoMapDict(valueType=str, nakedAttribute="weight"), - "postscriptDefaultCharacter" : _infoMapDict(valueType=str, nakedAttribute="default_character"), - "postscriptWindowsCharacterSet" : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True), - "macintoshFONDFamilyID" : _infoMapDict(valueType=int, nakedAttribute="fond_id"), - "macintoshFONDName" : _infoMapDict(valueType=str, nakedAttribute="apple_name"), - } - _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh. - - def __init__(self, font): - super(RInfo, self).__init__() - self._object = font - - def _environmentSetAttr(self, attr, value): - # special fontlab workarounds - if attr == "width": - warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) - attr = "openTypeOS2WidthClass" - if attr == "openTypeOS2WidthClass": - if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL: - print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value - self._object.width = value - else: - self._object.width = _openTypeOS2WidthClass_toFL[value] - return - # get the attribute data - data = self._ufoToFLAttrMapping[attr] - flAttr = data["nakedAttribute"] - valueType = data["valueType"] - masterSpecific = data["masterSpecific"] - requiresSetNum = data["requiresSetNum"] - specialGetSet = data["specialGetSet"] - # warn about setting attributes not supported by FL - if flAttr is None: - print "The attribute %s is not supported by FontLab. This data will not be set." % attr - return - # make sure that the value is the proper type for FL - if valueType == "intList": - value = [int(i) for i in value] - elif valueType == "boolint": - value = int(bool(value)) - elif valueType == str: - if value is None: - value = "" - value = value.encode(LOCAL_ENCODING) - elif valueType == int and not isinstance(value, int): - value = int(round(value)) - elif not isinstance(value, valueType): - value = valueType(value) - # handle postscript hint bug in FL - if attr in _postscriptHintAttributes: - value = self._handlePSHintBug(attr, value) - # handle special cases - if specialGetSet: - attr = "_set_%s" % attr - method = getattr(self, attr) - return method(value) - # set the value - obj = self._object - if len(flAttr.split(".")) > 1: - flAttrList = flAttr.split(".") - for i in flAttrList[:-1]: - obj = getattr(obj, i) - flAttr = flAttrList[-1] - ## set the foo_num attribute if necessary - if requiresSetNum: - numAttr = flAttr + "_num" - setattr(obj, numAttr, len(value)) - ## set master 0 if the data is master specific - if masterSpecific: - subObj = getattr(obj, flAttr) - if valueType == "intList": - for index, v in enumerate(value): - subObj[0][index] = v - else: - subObj[0] = value - ## otherwise use a regular set - else: - setattr(obj, flAttr, value) - - def _environmentGetAttr(self, attr): - # special fontlab workarounds - if attr == "width": - warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) - attr = "openTypeOS2WidthClass" - if attr == "openTypeOS2WidthClass": - value = self._object.width - if value not in _openTypeOS2WidthClass_fromFL: - print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value - return - else: - return _openTypeOS2WidthClass_fromFL[value] - # get the attribute data - data = self._ufoToFLAttrMapping[attr] - flAttr = data["nakedAttribute"] - valueType = data["valueType"] - masterSpecific = data["masterSpecific"] - specialGetSet = data["specialGetSet"] - # warn about setting attributes not supported by FL - if flAttr is None: - if not _IN_UFO_EXPORT: - print "The attribute %s is not supported by FontLab." % attr - return - # handle special cases - if specialGetSet: - attr = "_get_%s" % attr - method = getattr(self, attr) - return method() - # get the value - if len(flAttr.split(".")) > 1: - flAttrList = flAttr.split(".") - obj = self._object - for i in flAttrList: - obj = getattr(obj, i) - value = obj - else: - value = getattr(self._object, flAttr) - # grab the first master value if necessary - if masterSpecific: - value = value[0] - # convert if necessary - if valueType == "intList": - value = [int(i) for i in value] - elif valueType == "boolint": - value = bool(value) - elif valueType == str: - if value is None: - pass - else: - value = unicode(value, LOCAL_ENCODING) - elif not isinstance(value, valueType): - value = valueType(value) - return value - - # ------------------------------ - # individual attribute overrides - # ------------------------------ - - # styleMapStyleName - - def _get_styleMapStyleName(self): - return _styleMapStyleName_fromFL[self._object.font_style] - - def _set_styleMapStyleName(self, value): - value = _styleMapStyleName_toFL[value] - self._object.font_style = value - -# # openTypeHeadCreated -# -# # fontlab epoch: 1969-12-31 19:00:00 -# -# def _get_openTypeHeadCreated(self): -# value = self._object.ttinfo.head_creation -# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) -# delta = datetime.timedelta(seconds=value[0]) -# t = epoch - delta -# string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2)) -# return string -# -# def _set_openTypeHeadCreated(self, value): -# date, time = value.split(" ") -# year, month, day = [int(i) for i in date.split("-")] -# hour, minute, second = [int(i) for i in time.split(":")] -# value = datetime.datetime(year, month, day, hour, minute, second) -# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) -# delta = epoch - value -# seconds = delta.seconds -# self._object.ttinfo.head_creation[0] = seconds - - # openTypeOS2WeightClass - - def _get_openTypeOS2WeightClass(self): - value = self._object.weight_code - if value == -1: - value = None - return value - - def _set_openTypeOS2WeightClass(self, value): - self._object.weight_code = value - - # openTypeOS2WinDescent - - def _get_openTypeOS2WinDescent(self): - return self._object.ttinfo.os2_us_win_descent - - def _set_openTypeOS2WinDescent(self, value): - if value < 0: - warn("FontLab can only handle positive values for openTypeOS2WinDescent.") - value = abs(value) - self._object.ttinfo.os2_us_win_descent = value - - # openTypeOS2Type - - def _get_openTypeOS2Type(self): - value = self._object.ttinfo.os2_fs_type - intList = [] - for bit, bitNumber in _openTypeOS2Type_fromFL.items(): - if value & bit: - intList.append(bitNumber) - return intList - - def _set_openTypeOS2Type(self, values): - value = 0 - for bitNumber in values: - bit = _openTypeOS2Type_toFL[bitNumber] - value = value | bit - self._object.ttinfo.os2_fs_type = value - - # openTypeOS2Panose - - def _get_openTypeOS2Panose(self): - return [i for i in self._object.panose] - - def _set_openTypeOS2Panose(self, values): - for index, value in enumerate(values): - self._object.panose[index] = value - - # openTypeOS2FamilyClass - - def _get_openTypeOS2FamilyClass(self): - value = self._object.ttinfo.os2_s_family_class - for classID in range(15): - classValue = classID * 256 - if classValue > value: - classID -= 1 - classValue = classID * 256 - break - subclassID = value - classValue - return [classID, subclassID] - - def _set_openTypeOS2FamilyClass(self, values): - classID, subclassID = values - classID = classID * 256 - value = classID + subclassID - self._object.ttinfo.os2_s_family_class = value - - # postscriptWindowsCharacterSet - - def _get_postscriptWindowsCharacterSet(self): - value = self._object.ms_charset - value = _postscriptWindowsCharacterSet_fromFL[value] - return value - - def _set_postscriptWindowsCharacterSet(self, value): - value = _postscriptWindowsCharacterSet_toFL[value] - self._object.ms_charset = value - - # ----------------- - # FL bug workaround - # ----------------- - - def _handlePSHintBug(self, attribute, values): - """Function to handle problems with FontLab not allowing the max number of - alignment zones to be set to the max number. - Input: the name of the zones and the values to be set - Output: a warning when there are too many values to be set - and the max values which FontLab will allow. - """ - originalValues = values - truncatedLength = None - if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"): - if len(values) > 10: - values = values[:10] - truncatedLength = 10 - elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"): - if len(values) > 12: - values = values[:12] - truncatedLength = 12 - elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"): - if len(values) > 8: - values = values[:8] - truncatedLength = 8 - if truncatedLength is not None: - print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:])) - return values - - -class RFeatures(BaseFeatures): - - _title = "FLFeatures" - - def __init__(self, font): - super(RFeatures, self).__init__() - self._object = font - - def _get_text(self): - naked = self._object - features = [] - if naked.ot_classes: - features.append(_normalizeLineEndings(naked.ot_classes)) - for feature in naked.features: - features.append(_normalizeLineEndings(feature.value)) - return "".join(features) - - def _set_text(self, value): - classes, features = splitFeaturesForFontLab(value) - naked = self._object - naked.ot_classes = classes - naked.features.clean() - for featureName, featureText in features: - f = Feature(featureName, featureText) - naked.features.append(f) - - text = property(_get_text, _set_text, doc="raw feature text.") - diff --git a/misc/pylib/robofab/objects/objectsRF.pyx b/misc/pylib/robofab/objects/objectsRF.pyx deleted file mode 100755 index 50e1f13a7..000000000 --- a/misc/pylib/robofab/objects/objectsRF.pyx +++ /dev/null @@ -1,1233 +0,0 @@ -"""UFO for GlifLib""" - -from robofab import RoboFabError, RoboFabWarning -from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ - BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\ - _interpolate, _interpolatePt, roundPt, addPt,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues - -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() - if familyName is not None: - new.info.familyName = familyName - if styleName is not None: - new.info.styleName = styleName - return new - -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 - - -class PostScriptFontHintValues(BasePostScriptFontHintValues): - """ Font level PostScript hints object for objectsRF usage. - If there are values in the lib, use those. - If there are no values in the lib, use defaults. - - The psHints attribute for objectsRF.RFont is basically just the - data read from the Lib. When the object saves to UFO, the - hints are written back to the lib, which is then saved. - - """ - - def __init__(self, aFont=None, data=None): - self.setParent(aFont) - BasePostScriptFontHintValues.__init__(self) - if aFont is not None: - # in version 1, this data was stored in the lib - # if it is still there, guess that it is correct - # move it to font info and remove it from the lib. - libData = aFont.lib.get(postScriptHintDataLibKey) - if libData is not None: - self.fromDict(libData) - del libData[postScriptHintDataLibKey] - if data is not None: - self.fromDict(data) - -def getPostScriptHintDataFromLib(aFont, fontLib): - hintData = fontLib.get(postScriptHintDataLibKey) - psh = PostScriptFontHintValues(aFont) - psh.fromDict(hintData) - return psh - -class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): - """ Glyph level PostScript hints object for objectsRF usage. - If there are values in the lib, use those. - If there are no values in the lib, be empty. - - """ - def __init__(self, aGlyph=None, data=None): - # read the data from the glyph.lib, it won't be anywhere else - BasePostScriptGlyphHintValues.__init__(self) - if aGlyph is not None: - self.setParent(aGlyph) - self._loadFromLib(aGlyph.lib) - if data is not None: - self.fromDict(data) - - -class RFont(BaseFont): - """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) - self.features = RFeatures() - self.features.setParent(self) - self.groups = RGroups() - self.groups.setParent(self) - self.lib = RLib() - self.lib.setParent(self) - if path: - self._loadData(path) - else: - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - - def __setitem__(self, glyphName, glyph): - """Set a glyph at key.""" - self._object[glyphName] = glyph - - def __cmp__(self, other): - """Compare this font with another, compare if they refer to the same file.""" - if not hasattr(other, '_path'): - return -1 - if self._object._path == other._object._path and self._object._path is not None: - return 0 - else: - return -1 - - def __len__(self): - if self._glyphSet is None: - return 0 - return len(self._glyphSet) - - def _loadData(self, path): - from robofab.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 robofab.ufoLib import UFOWriter - from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab - # if no destination is given, or if - # the given destination is the current - # path, this is not a save as operation - if destDir is None or destDir == self._path: - saveAs = False - destDir = self._path - else: - saveAs = True - # start a progress bar - nonGlyphCount = 5 - bar = None - if doProgress: - from robofab.interface.all.dialogs import ProgressBar - bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys())) - # write - writer = UFOWriter(destDir, formatVersion=formatVersion) - try: - # make a shallow copy of the lib. stuff may be added to it. - fontLib = dict(self.lib) - # info - if bar: - bar.label("Saving info...") - writer.writeInfo(self.info) - if bar: - bar.tick() - # kerning - if self.kerning.changed or saveAs: - if bar: - bar.label("Saving kerning...") - writer.writeKerning(self.kerning.asDict()) - if bar: - bar.tick() - # groups - if bar: - bar.label("Saving groups...") - writer.writeGroups(self.groups) - if bar: - bar.tick() - # features - if bar: - bar.label("Saving features...") - features = self.features.text - if features is None: - features = "" - if formatVersion == 2: - writer.writeFeatures(features) - elif formatVersion == 1: - classes, features = splitFeaturesForFontLab(features) - if classes: - fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n" - if features: - featureDict = {} - for featureName, featureText in features: - featureDict[featureName] = featureText.strip() + "\n" - fontLib["org.robofab.opentype.features"] = featureDict - fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features] - if bar: - bar.tick() - # lib - if formatVersion == 1: - fontLib[postScriptHintDataLibKey] = self.psHints.asDict() - if bar: - bar.label("Saving lib...") - writer.writeLib(fontLib) - if bar: - bar.tick() - # glyphs - glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() - - glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) - if len(self._scheduledForDeletion) != 0: - if bar: - bar.label("Removing deleted glyphs...") - for glyphName in self._scheduledForDeletion: - if glyphSet.has_key(glyphName): - glyphSet.deleteGlyph(glyphName) - if bar: - bar.tick() - if bar: - bar.label("Saving glyphs...") - count = nonGlyphCount - if saveAs: - glyphNames = self.keys() - else: - glyphNames = self._object.keys() - for glyphName in glyphNames: - glyph = self[glyphName] - glyph.psHints._saveToLib(glyph.lib) - glyph._saveToGlyphSet(glyphSet, glyphName=glyphName, force=saveAs) - if bar and not count % 10: - bar.tick(count) - count = count + 1 - glyphSet.writeContents() - self._glyphSet = glyphSet - # only blindly stop if the user says to - except KeyboardInterrupt: - bar.close() - bar = None - # kill the progress bar - if bar: - bar.close() - # reset internal stuff - self._path = destDir - self._scheduledForDeletion = [] - self.setChanged(False) - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph with glyphName - if the glyph exists and clear=True clear the glyph""" - if clear and glyphName in self: - g = self[glyphName] - g.clear() - w = self.info.postscriptDefaultWidthX - if w is None: - w = 0 - g.width = w - return g - g = RGlyph() - g.setParent(self) - g.name = glyphName - w = self.info.postscriptDefaultWidthX - if w is None: - w = 0 - g.width = w - g._hasChanged() - self._object[glyphName] = g - # is the user adding a glyph that has the same - # name as one that was deleted earlier? - if glyphName in self._scheduledForDeletion: - self._scheduledForDeletion.remove(glyphName) - return self.getGlyph(glyphName) - - def insertGlyph(self, glyph, name=None): - """returns a new glyph that has been inserted into the font""" - if name is None: - name = glyph.name - glyph = glyph.copy() - glyph.name = name - glyph.setParent(self) - glyph._hasChanged() - self._object[name] = glyph - # is the user adding a glyph that has the same - # name as one that was deleted earlier? - if name in self._scheduledForDeletion: - self._scheduledForDeletion.remove(name) - return self.getGlyph(name) - - def removeGlyph(self, glyphName): - """remove a glyph from the font""" - # XXX! Potential issue with removing glyphs. - # if a glyph is removed from a font, but it is still referenced - # by a component, it will give pens some trouble. - # where does the resposibility for catching this fall? - # the removeGlyph method? the addComponent method - # of the various pens? somewhere else? hm... tricky. - # - #we won't actually remove it, we will just store it for removal - # but only if the glyph does exist - if self.has_key(glyphName) and glyphName not in self._scheduledForDeletion: - self._scheduledForDeletion.append(glyphName) - # now delete the object - if self._object.has_key(glyphName): - del self._object[glyphName] - self._hasChanged() - - def getGlyph(self, glyphName): - # XXX getGlyph may have to become private, to avoid duplication - # with __getitem__ - n = None - if self._object.has_key(glyphName): - # have we served this glyph before? it should be in _object - n = self._object[glyphName] - else: - # haven't served it before, is it in the glyphSet then? - if self._glyphSet is not None and glyphName in self._glyphSet: - # yes, read the .glif file from disk - n = self._loadGlyph(glyphName) - if n is None: - raise KeyError, glyphName - return n - - -class RGlyph(BaseGlyph): - - _title = "RGlyph" - - def __init__(self): - BaseGlyph.__init__(self) - self.contours = [] - self.components = [] - self.anchors = [] - self._unicodes = [] - self.width = 0 - self.note = None - self._name = "Unnamed Glyph" - self.selected = False - self._properties = None - self._lib = RLib() - self._lib.setParent(self) - self.psHints = PostScriptGlyphHintValues() - self.psHints.setParent(self) - - def __len__(self): - return len(self.contours) - - def __getitem__(self, index): - if index < len(self.contours): - return self.contours[index] - raise IndexError - - def _hasNotChanged(self): - for contour in self.contours: - contour.setChanged(False) - for segment in contour.segments: - segment.setChanged(False) - for point in segment.points: - point.setChanged(False) - for component in self.components: - component.setChanged(False) - for anchor in self.anchors: - anchor.setChanged(False) - self.setChanged(False) - - # - # attributes - # - - def _get_lib(self): - return self._lib - - def _set_lib(self, obj): - self._lib.clear() - self._lib.update(obj) - - lib = property(_get_lib, _set_lib) - - def _get_name(self): - return self._name - - def _set_name(self, value): - prevName = self._name - newName = value - if newName == prevName: - return - self._name = newName - self.setChanged(True) - font = self.getParent() - if font is not None: - # but, this glyph could be linked to a - # FontLab font, because objectsFL.RGlyph.copy() - # creates an objectsRF.RGlyph with the parent - # set to an objectsFL.RFont object. so, check to see - # if this is a legitimate RFont before trying to - # do the objectsRF.RFont glyph name change - if isinstance(font, RFont): - font._object[newName] = self - # is the user changing a glyph's name to the - # name of a glyph that was deleted earlier? - if newName in font._scheduledForDeletion: - font._scheduledForDeletion.remove(newName) - font.removeGlyph(prevName) - - name = property(_get_name, _set_name) - - def _get_unicodes(self): - return self._unicodes - - def _set_unicodes(self, value): - if not isinstance(value, list): - raise RoboFabError, "unicodes must be a list" - self._unicodes = value - self._hasChanged() - - unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph") - - def _get_unicode(self): - if len(self._unicodes) == 0: - return None - return self._unicodes[0] - - def _set_unicode(self, value): - uni = self._unicodes - if value is not None: - if value not in uni: - self.unicodes.insert(0, value) - elif uni.index(value) != 0: - uni.insert(0, uni.pop(uni.index(value))) - self.unicodes = uni - - unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph") - - def getPointPen(self): - from robofab.pens.rfUFOPen import RFUFOPointPen - return RFUFOPointPen(self) - - def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): - """append a component to the glyph""" - new = RComponent(baseGlyph, offset, scale) - new.setParent(self) - self.components.append(new) - self._hasChanged() - - def appendAnchor(self, name, position, mark=None): - """append an anchor to the glyph""" - new = RAnchor(name, position, mark) - new.setParent(self) - self.anchors.append(new) - self._hasChanged() - - def removeContour(self, index): - """remove a specific contour from the glyph""" - del self.contours[index] - self._hasChanged() - - def removeAnchor(self, anchor): - """remove a specific anchor from the glyph""" - del self.anchors[anchor.index] - self._hasChanged() - - def removeComponent(self, component): - """remove a specific component from the glyph""" - del self.components[component.index] - self._hasChanged() - - def center(self, padding=None): - """Equalise sidebearings, set to padding if wanted.""" - left = self.leftMargin - right = self.rightMargin - if padding: - e_left = e_right = padding - else: - e_left = (left + right)/2 - e_right = (left + right) - e_left - self.leftMargin = e_left - self.rightMargin = e_right - - def decompose(self): - """Decompose all components""" - for i in range(len(self.components)): - self.components[-1].decompose() - self._hasChanged() - - def clear(self, contours=True, components=True, anchors=True, guides=True): - """Clear all items marked as True from the glyph""" - if contours: - self.clearContours() - if components: - self.clearComponents() - if anchors: - self.clearAnchors() - if guides: - self.clearHGuides() - self.clearVGuides() - - def clearContours(self): - """clear all contours""" - self.contours = [] - self._hasChanged() - - def clearComponents(self): - """clear all components""" - self.components = [] - self._hasChanged() - - def clearAnchors(self): - """clear all anchors""" - self.anchors = [] - self._hasChanged() - - def clearHGuides(self): - """clear all horizontal guides""" - self.hGuides = [] - self._hasChanged() - - def clearVGuides(self): - """clear all vertical guides""" - self.vGuides = [] - self._hasChanged() - - def getAnchors(self): - return self.anchors - - def getComponents(self): - return self.components - - # - # stuff related to Glyph Properties - # - - - -class RContour(BaseContour): - - _title = "RoboFabContour" - - def __init__(self, object=None): - #BaseContour.__init__(self) - self.segments = [] - self.selected = False - - def __len__(self): - return len(self.segments) - - def __getitem__(self, index): - if index < len(self.segments): - return self.segments[index] - raise IndexError - - def _get_index(self): - return self.getParent().contours.index(self) - - def _set_index(self, index): - ogIndex = self.index - if index != ogIndex: - contourList = self.getParent().contours - contourList.insert(index, contourList.pop(ogIndex)) - - - index = property(_get_index, _set_index, doc="index of the contour") - - def _get_points(self): - points = [] - for segment in self.segments: - for point in segment.points: - points.append(point) - return points - - points = property(_get_points, doc="view the contour as a list of points") - - def _get_bPoints(self): - bPoints = [] - for segment in self.segments: - segType = segment.type - if segType == MOVE: - bType = CORNER - elif segType == LINE: - bType = CORNER - elif segType == CURVE: - if segment.smooth: - bType = CURVE - else: - bType = CORNER - else: - raise RoboFabError, "encountered unknown segment type" - b = RBPoint() - b.setParent(segment) - bPoints.append(b) - return bPoints - - bPoints = property(_get_bPoints, doc="view the contour as a list of bPoints") - - def appendSegment(self, segmentType, points, smooth=False): - """append a segment to the contour""" - segment = self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth) - return segment - - def insertSegment(self, index, segmentType, points, smooth=False): - """insert a segment into the contour""" - segment = RSegment(segmentType, points, smooth) - segment.setParent(self) - self.segments.insert(index, segment) - self._hasChanged() - return segment - - def removeSegment(self, index): - """remove a segment from the contour""" - del self.segments[index] - self._hasChanged() - - def reverseContour(self): - """reverse the contour""" - from robofab.pens.reverseContourPointPen import ReverseContourPointPen - index = self.index - glyph = self.getParent() - pen = glyph.getPointPen() - reversePen = ReverseContourPointPen(pen) - self.drawPoints(reversePen) - # we've drawn the reversed contour onto our parent glyph, - # so it sits at the end of the contours list: - newContour = glyph.contours.pop(-1) - for segment in newContour.segments: - segment.setParent(self) - self.segments = newContour.segments - self._hasChanged() - - def setStartSegment(self, segmentIndex): - """set the first segment on the contour""" - # this obviously does not support open contours - if len(self.segments) < 2: - return - if segmentIndex == 0: - return - if segmentIndex > len(self.segments)-1: - raise IndexError, 'segment index not in segments list' - oldStart = self.segments[0] - oldLast = self.segments[-1] - #check to see if the contour ended with a curve on top of the move - #if we find one delete it, - if oldLast.type == CURVE or oldLast.type == QCURVE: - startOn = oldStart.onCurve - lastOn = oldLast.onCurve - if startOn.x == lastOn.x and startOn.y == lastOn.y: - del self.segments[0] - # since we deleted the first contour, the segmentIndex needs to shift - segmentIndex = segmentIndex - 1 - # if we DO have a move left over, we need to convert it to a line - if self.segments[0].type == MOVE: - self.segments[0].type = LINE - # slice up the segments and reassign them to the contour - segments = self.segments[segmentIndex:] - self.segments = segments + self.segments[:segmentIndex] - # now, draw the contour onto the parent glyph - glyph = self.getParent() - pen = glyph.getPointPen() - self.drawPoints(pen) - # we've drawn the new contour onto our parent glyph, - # so it sits at the end of the contours list: - newContour = glyph.contours.pop(-1) - for segment in newContour.segments: - segment.setParent(self) - self.segments = newContour.segments - self._hasChanged() - - -class RSegment(BaseSegment): - - _title = "RoboFabSegment" - - def __init__(self, segmentType=None, points=[], smooth=False): - BaseSegment.__init__(self) - self.selected = False - self.points = [] - self.smooth = smooth - if points: - #the points in the segment should be RPoints, so create those objects - for point in points[:-1]: - x, y = point - p = RPoint(x, y, pointType=OFFCURVE) - p.setParent(self) - self.points.append(p) - aX, aY = points[-1] - p = RPoint(aX, aY, segmentType) - p.setParent(self) - self.points.append(p) - - def _get_type(self): - return self.points[-1].type - - def _set_type(self, pointType): - onCurve = self.points[-1] - ocType = onCurve.type - if ocType == pointType: - return - #we are converting a cubic line into a cubic curve - if pointType == CURVE and ocType == LINE: - onCurve.type = pointType - parent = self.getParent() - prev = parent._prevSegment(self.index) - p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE) - p1.setParent(self) - p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE) - p2.setParent(self) - self.points.insert(0, p2) - self.points.insert(0, p1) - #we are converting a cubic move to a curve - elif pointType == CURVE and ocType == MOVE: - onCurve.type = pointType - parent = self.getParent() - prev = parent._prevSegment(self.index) - p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE) - p1.setParent(self) - p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE) - p2.setParent(self) - self.points.insert(0, p2) - self.points.insert(0, p1) - #we are converting a quad curve to a cubic curve - elif pointType == CURVE and ocType == QCURVE: - onCurve.type == CURVE - #we are converting a cubic curve into a cubic line - elif pointType == LINE and ocType == CURVE: - p = self.points.pop(-1) - self.points = [p] - onCurve.type = pointType - self.smooth = False - #we are converting a cubic move to a line - elif pointType == LINE and ocType == MOVE: - onCurve.type = pointType - #we are converting a quad curve to a line: - elif pointType == LINE and ocType == QCURVE: - p = self.points.pop(-1) - self.points = [p] - onCurve.type = pointType - self.smooth = False - # we are converting to a quad curve where just about anything is legal - elif pointType == QCURVE: - onCurve.type = pointType - else: - raise RoboFabError, 'unknown segment type' - - type = property(_get_type, _set_type, doc="type of the segment") - - def _get_index(self): - return self.getParent().segments.index(self) - - index = property(_get_index, doc="index of the segment") - - def insertPoint(self, index, pointType, point): - x, y = point - p = RPoint(x, y, pointType=pointType) - p.setParent(self) - self.points.insert(index, p) - self._hasChanged() - - def removePoint(self, index): - del self.points[index] - self._hasChanged() - - -class RBPoint(BaseBPoint): - - _title = "RoboFabBPoint" - - def _setAnchorChanged(self, value): - self._anchorPoint.setChanged(value) - - def _setNextChanged(self, value): - self._nextOnCurve.setChanged(value) - - def _get__parentSegment(self): - return self.getParent() - - _parentSegment = property(_get__parentSegment, doc="") - - def _get__nextOnCurve(self): - pSeg = self._parentSegment - contour = pSeg.getParent() - #could this potentially return an incorrect index? say, if two segments are exactly the same? - return contour.segments[(contour.segments.index(pSeg) + 1) % len(contour.segments)] - - _nextOnCurve = property(_get__nextOnCurve, doc="") - - def _get_index(self): - return self._parentSegment.index - - index = property(_get_index, doc="index of the bPoint on the contour") - - -class RPoint(BasePoint): - - _title = "RoboFabPoint" - - def __init__(self, x=0, y=0, pointType=None, name=None): - self.selected = False - self._type = pointType - self._x = x - self._y = y - self._name = name - - 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, pt): - """Move the anchor""" - self.x = self.x + pt[0] - self.y = self.y + pt[0] - self._hasChanged() - - -class RComponent(BaseComponent): - - _title = "RoboFabComponent" - - def __init__(self, baseGlyphName=None, offset=(0,0), scale=(1,1), transform=None): - BaseComponent.__init__(self) - self.selected = False - self._baseGlyph = baseGlyphName - self._offset = offset - self._scale = scale - if transform is None: - xx, yy = scale - dx, dy = offset - self.transformation = (xx, 0, 0, yy, dx, dy) - else: - self.transformation = transform - - 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): - """ Get the offset component of the transformation.=""" - (xx, xy, yx, yy, dx, dy) = self._transformation - return dx, dy - - def _set_offset(self, value): - """ Set the offset component of the transformation.""" - (xx, xy, yx, yy, dx, dy) = self._transformation - self._transformation = (xx, xy, yx, yy, value[0], value[1]) - self._hasChanged() - - offset = property(_get_offset, _set_offset, doc="the offset of the component") - - def _get_scale(self): - """ Return the scale components of the transformation.""" - (xx, xy, yx, yy, dx, dy) = self._transformation - return xx, yy - - def _set_scale(self, scale): - """ Set the scale component of the transformation. - Note: setting this value effectively makes the xy and yx values meaningless. - We're assuming that if you're setting the xy and yx values, you will use - the transformation attribute rather than the scale and offset attributes. - """ - xScale, yScale = scale - (xx, xy, yx, yy, dx, dy) = self._transformation - self._transformation = (xScale, xy, yx, yScale, dx, dy) - self._hasChanged() - - scale = property(_get_scale, _set_scale, doc="the scale of the component") - - def _get_transformation(self): - return self._transformation - - def _set_transformation(self, transformation): - assert len(transformation)==6, "Transformation matrix must have 6 values" - self._transformation = transformation - - transformation = property(_get_transformation, _set_transformation, doc="the transformation matrix of the component") - - def move(self, pt): - """Move the component""" - (xx, xy, yx, yy, dx, dy) = self._transformation - self._transformation = (xx, xy, yx, yy, dx+pt[0], dy+pt[1]) - self._hasChanged() - - 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 - baseGlyph = parentFont[baseGlyphName] - for contour in baseGlyph.contours: - pointPen = parentGlyph.getPointPen() - transPen = TransformPointPen(pointPen, self._transformation) - 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): - - _title = "RoboFabFontInfo" - -class RFeatures(BaseFeatures): - - _title = "RoboFabFeatures" - |