From 93f57733fc65e2f9e9b59fa04255abdf000fc2e3 Mon Sep 17 00:00:00 2001 From: Erik van Blokland Date: Mon, 7 Jan 2008 21:12:34 +0000 Subject: [PATCH] Basic fab objects for use in FontForge. Not at all ready, but functional in parts. git-svn-id: http://svn.robofab.com/trunk@12 b5fa9d6c-a76f-4ffd-b3cb-f825fc41095c --- robofab/Lib/robofab/objects/objectsFF.py | 1252 ++++++++++++++++++++++ 1 file changed, 1252 insertions(+) create mode 100644 robofab/Lib/robofab/objects/objectsFF.py diff --git a/robofab/Lib/robofab/objects/objectsFF.py b/robofab/Lib/robofab/objects/objectsFF.py new file mode 100644 index 000000000..0db14774c --- /dev/null +++ b/robofab/Lib/robofab/objects/objectsFF.py @@ -0,0 +1,1252 @@ + + +__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() + \ No newline at end of file