__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 ""%(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 ""%(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()