summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/objects
diff options
context:
space:
mode:
Diffstat (limited to 'misc/pylib/robofab/objects')
-rwxr-xr-xmisc/pylib/robofab/objects/__init__.py15
-rwxr-xr-xmisc/pylib/robofab/objects/objectsBase.pyx3426
-rw-r--r--misc/pylib/robofab/objects/objectsFF.py1253
-rwxr-xr-xmisc/pylib/robofab/objects/objectsFL.py3112
-rwxr-xr-xmisc/pylib/robofab/objects/objectsRF.pyx1233
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"
-