fonttools/Lib/robofab/objects/objectsFF.py
Just van Rossum feb0046176 Moved Data, Lib and install.py one level down the tree
git-svn-id: http://svn.robofab.com/trunk@67 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c
2008-03-11 09:18:32 +00:00

1252 lines
39 KiB
Python

__DEBUG__ = True
__version__ = "0.2"
"""
RoboFab API Objects for FontForge
http://fontforge.sourceforge.net
FontForge python docs:
http://fontforge.sourceforge.net/python.html
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()