diff options
author | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
commit | c833e252c925e8dd68108660710ca835d95daa6f (patch) | |
tree | 6b2e28264ed45efd7f054e453b622098d0d875b8 /misc/pylib/robofab/objects/objectsFL.py | |
parent | 8c1a4c181ef12000179dfec541f1af87e9b03122 (diff) | |
download | inter-c833e252c925e8dd68108660710ca835d95daa6f.tar.xz |
Major overhaul, moving from UFO2 to Glyphs and UFO3, plus a brand new and much simpler fontbuild
Diffstat (limited to 'misc/pylib/robofab/objects/objectsFL.py')
-rwxr-xr-x | misc/pylib/robofab/objects/objectsFL.py | 3112 |
1 files changed, 0 insertions, 3112 deletions
diff --git a/misc/pylib/robofab/objects/objectsFL.py b/misc/pylib/robofab/objects/objectsFL.py deleted file mode 100755 index 3b78ddc14..000000000 --- a/misc/pylib/robofab/objects/objectsFL.py +++ /dev/null @@ -1,3112 +0,0 @@ -"""UFO implementation for the objects as used by FontLab 4.5 and higher""" - -from FL import * - -from robofab.tools.toolsFL import GlyphIndexTable, NewGlyph -from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ - BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\ - roundPt, addPt, _box,\ - MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ - relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\ - BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues -from robofab.misc import arrayTools -from robofab.pens.flPen import FLPointPen, FLPointContourPen -from robofab import RoboFabError -import os -from robofab.plistlib import Data, Dict, readPlist, writePlist -from StringIO import StringIO -from robofab import ufoLib -from warnings import warn -import datetime -from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab - - -try: - set -except NameError: - from sets import Set as set - -# local encoding -if os.name in ["mac", "posix"]: - LOCAL_ENCODING = "macroman" -else: - LOCAL_ENCODING = "latin-1" - -_IN_UFO_EXPORT = False - -# a list of attributes that are to be copied when copying a glyph. -# this is used by glyph.copy and font.insertGlyph -GLYPH_COPY_ATTRS = [ - "name", - "width", - "unicodes", - "note", - "lib", - ] - -# Generate Types -PC_TYPE1 = 'pctype1' -PC_MM = 'pcmm' -PC_TYPE1_ASCII = 'pctype1ascii' -PC_MM_ASCII = 'pcmmascii' -UNIX_ASCII = 'unixascii' -MAC_TYPE1 = 'mactype1' -OTF_CFF = 'otfcff' -OTF_TT = 'otfttf' -MAC_TT = 'macttf' -MAC_TT_DFONT = 'macttdfont' - -# doc for these functions taken from: http://dev.fontlab.net/flpydoc/ -# internal name (FontLab name, extension) -_flGenerateTypes ={ PC_TYPE1 : (ftTYPE1, 'pfb'), # PC Type 1 font (binary/PFB) - PC_MM : (ftTYPE1_MM, 'mm'), # PC MultipleMaster font (PFB) - PC_TYPE1_ASCII : (ftTYPE1ASCII, 'pfa'), # PC Type 1 font (ASCII/PFA) - PC_MM_ASCII : (ftTYPE1ASCII_MM, 'mm'), # PC MultipleMaster font (ASCII/PFA) - UNIX_ASCII : (ftTYPE1ASCII, 'pfa'), # UNIX ASCII font (ASCII/PFA) - OTF_TT : (ftTRUETYPE, 'ttf'), # PC TrueType/TT OpenType font (TTF) - OTF_CFF : (ftOPENTYPE, 'otf'), # PS OpenType (CFF-based) font (OTF) - MAC_TYPE1 : (ftMACTYPE1, 'suit'), # Mac Type 1 font (generates suitcase and LWFN file, optionally AFM) - MAC_TT : (ftMACTRUETYPE, 'ttf'), # Mac TrueType font (generates suitcase) - MAC_TT_DFONT : (ftMACTRUETYPE_DFONT, 'dfont'), # Mac TrueType font (generates suitcase with resources in data fork) - } - -## FL Hint stuff -# this should not be referenced outside of this module -# since we may be changing the way this works in the future. - - -""" - - FontLab implementation of psHints objects - - Most of the FL methods relating to ps hints return a list of 16 items. - These values are for the 16 corners of a 4 axis multiple master. - The odd thing is that even single masters get these 16 values. - RoboFab doesn't access the MM masters, so by default, the psHints - object only works with the first element. If you want to access the other - values in the list, give a value between 0 and 15 for impliedMasterIndex - when creating the object. - - From the FontLab docs: - http://dev.fontlab.net/flpydoc/ - - blue_fuzz - blue_scale - blue_shift - - blue_values_num(integer) - number of defined blue values - blue_values[integer[integer]] - two-dimentional array of BlueValues - master index is top-level index - - other_blues_num(integer) - number of defined OtherBlues values - other_blues[integer[integer]] - two-dimentional array of OtherBlues - master index is top-level index - - family_blues_num(integer) - number of FamilyBlues records - family_blues[integer[integer]] - two-dimentional array of FamilyBlues - master index is top-level index - - family_other_blues_num(integer) - number of FamilyOtherBlues records - family_other_blues[integer[integer]] - two-dimentional array of FamilyOtherBlues - master index is top-level index - - force_bold[integer] - list of Force Bold values, one for - each master - stem_snap_h_num(integer) - stem_snap_h - stem_snap_v_num(integer) - stem_snap_v - """ - -class PostScriptFontHintValues(BasePostScriptFontHintValues): - """ Wrapper for font-level PostScript hinting information for FontLab. - Blues values, stem values. - """ - def __init__(self, font=None, impliedMasterIndex=0): - self._object = font.naked() - self._masterIndex = impliedMasterIndex - - def copy(self): - from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues - return _PostScriptFontHintValues(data=self.asDict()) - - -class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): - """ Wrapper for glyph-level PostScript hinting information for FontLab. - vStems, hStems. - """ - def __init__(self, glyph=None): - self._object = glyph.naked() - - def copy(self): - from robofab.objects.objectsRF import PostScriptGlyphHintValues as _PostScriptGlyphHintValues - return _PostScriptGlyphHintValues(data=self.asDict()) - - def _hintObjectsToList(self, item): - data = [] - done = [] - for hint in item: - p = (hint.position, hint.width) - if p in done: - continue - data.append(p) - done.append(p) - data.sort() - return data - - def _listToHintObjects(self, item): - hints = [] - done = [] - for pos, width in item: - if (pos, width) in done: - # we don't want to set duplicates - continue - hints.append(Hint(pos, width)) - done.append((pos,width)) - return hints - - def _getVHints(self): - return self._hintObjectsToList(self._object.vhints) - - def _setVHints(self, values): - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(2) - if values is None: - # just clearing it then - return - values.sort() - for hint in self._listToHintObjects(values): - self._object.vhints.append(hint) - - def _getHHints(self): - return self._hintObjectsToList(self._object.hhints) - - def _setHHints(self, values): - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(1) - if values is None: - # just clearing it then - return - values.sort() - for hint in self._listToHintObjects(values): - self._object.hhints.append(hint) - - vHints = property(_getVHints, _setVHints, doc="postscript hints: vertical hint zones") - hHints = property(_getHHints, _setHHints, doc="postscript hints: horizontal hint zones") - - - -def _glyphHintsToDict(glyph): - data = {} - ## - ## horizontal and vertical hints - ## - # glyph.hhints and glyph.vhints returns a list of Hint objects. - # Hint objects have position and width attributes. - data['hHints'] = [] - for index in xrange(len(glyph.hhints)): - hint = glyph.hhints[index] - data['hHints'].append((hint.position, hint.width)) - if not data['hHints']: - del data['hHints'] - data['vHints'] = [] - for index in xrange(len(glyph.vhints)): - hint = glyph.vhints[index] - data['vHints'].append((hint.position, hint.width)) - if not data['vHints']: - del data['vHints'] - ## - ## horizontal and vertical links - ## - # glyph.hlinks and glyph.vlinks returns a list of Link objects. - # Link objects have node1 and node2 attributes. - data['hLinks'] = [] - for index in xrange(len(glyph.hlinks)): - link = glyph.hlinks[index] - d = { 'node1' : link.node1, - 'node2' : link.node2, - } - data['hLinks'].append(d) - if not data['hLinks']: - del data['hLinks'] - data['vLinks'] = [] - for index in xrange(len(glyph.vlinks)): - link = glyph.vlinks[index] - d = { 'node1' : link.node1, - 'node2' : link.node2, - } - data['vLinks'].append(d) - if not data['vLinks']: - del data['vLinks'] - ## - ## replacement table - ## - # glyph.replace_table returns a list of Replace objects. - # Replace objects have type and index attributes. - data['replaceTable'] = [] - for index in xrange(len(glyph.replace_table)): - replace = glyph.replace_table[index] - d = { 'type' : replace.type, - 'index' : replace.index, - } - data['replaceTable'].append(d) - if not data['replaceTable']: - del data['replaceTable'] - # XXX - # need to support glyph.instructions and glyph.hdmx? - # they are not documented very well. - return data - -def _dictHintsToGlyph(glyph, aDict): - # clear existing hints first - # RemoveHints requires an "integer mode" argument - # but it is not documented. from some simple experiments - # i deduced that - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - glyph.RemoveHints(3) - ## - ## horizontal and vertical hints - ## - if aDict.has_key('hHints'): - for d in aDict['hHints']: - glyph.hhints.append(Hint(d[0], d[1])) - if aDict.has_key('vHints'): - for d in aDict['vHints']: - glyph.vhints.append(Hint(d[0], d[1])) - ## - ## horizontal and vertical links - ## - if aDict.has_key('hLinks'): - for d in aDict['hLinks']: - glyph.hlinks.append(Link(d['node1'], d['node2'])) - if aDict.has_key('vLinks'): - for d in aDict['vLinks']: - glyph.vlinks.append(Link(d['node1'], d['node2'])) - ## - ## replacement table - ## - if aDict.has_key('replaceTable'): - for d in aDict['replaceTable']: - glyph.replace_table.append(Replace(d['type'], d['index'])) - -# FL Node Types -flMOVE = 17 -flLINE = 1 -flCURVE = 35 -flOFFCURVE = 65 -flSHARP = 0 -# I have no idea what the difference between -# "smooth" and "fixed" is, but booth values -# are returned by FL -flSMOOTH = 4096 -flFIXED = 12288 - - -_flToRFSegmentDict = { flMOVE : MOVE, - flLINE : LINE, - flCURVE : CURVE, - flOFFCURVE : OFFCURVE - } - -_rfToFLSegmentDict = {} -for k, v in _flToRFSegmentDict.items(): - _rfToFLSegmentDict[v] = k - -def _flToRFSegmentType(segmentType): - return _flToRFSegmentDict[segmentType] - -def _rfToFLSegmentType(segmentType): - return _rfToFLSegmentDict[segmentType] - -def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)): - ogCenter = (centerX, centerY) - scaledCenter = (centerX * scaleX, centerY * scaleY) - shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) - scaledPointX = (pointX * scaleX) - shiftVal[0] - scaledPointY = (pointY * scaleY) - shiftVal[1] - return (scaledPointX, scaledPointY) - -# Nostalgia code: -def CurrentFont(): - """Return a RoboFab font object for the currently selected font.""" - f = fl.font - if f is not None: - return RFont(fl.font) - return None - -def CurrentGlyph(): - """Return a RoboFab glyph object for the currently selected glyph.""" - currentPath = fl.font.file_name - if fl.glyph is None: - return None - glyphName = fl.glyph.name - currentFont = None - # is this font already loaded as an RFont? - for font in AllFonts(): - # ugh this won't work because AllFonts sees non RFonts as well.... - if font.path == currentPath: - currentFont = font - break - xx = currentFont[glyphName] - #print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent() - return xx - -def OpenFont(path=None, note=None): - """Open a font from a path.""" - if path == None: - from robofab.interface.all.dialogs import GetFile - path = GetFile(note) - if path: - if path[-4:].lower() in ['.vfb', '.VFB', '.bak', '.BAK']: - f = Font(path) - fl.Add(f) - return RFont(f) - return None - -def NewFont(familyName=None, styleName=None): - """Make a new font""" - from FL import fl, Font - f = Font() - fl.Add(f) - rf = RFont(f) - if familyName is not None: - rf.info.familyName = familyName - if styleName is not None: - rf.info.styleName = styleName - return rf - -def AllFonts(): - """Return a list of all open fonts.""" - fontCount = len(fl) - all = [] - for index in xrange(fontCount): - naked = fl[index] - all.append(RFont(naked)) - return all - - from robofab.world import CurrentGlyph - -def getGlyphFromMask(g): - """Get a Fab glyph object for the data in the mask layer.""" - from robofab.objects.objectsFL import RGlyph as FL_RGlyph - from robofab.objects.objectsRF import RGlyph as RF_RGlyph - n = g.naked() - mask = n.mask - fg = FL_RGlyph(mask) - rf = RF_RGlyph() - pen = rf.getPointPen() - fg.drawPoints(pen) - rf.width = g.width # can we get to the mask glyph width without flipping the UI? - return rf - -def setMaskToGlyph(maskGlyph, targetGlyph, clear=True): - """Set the maskGlyph as a mask layer in targetGlyph. - maskGlyph is a FontLab or RoboFab RGlyph, orphaned or not. - targetGlyph is a FontLab RGLyph. - clear is a bool. False: keep the existing mask data, True: clear the existing mask data. - """ - from robofab.objects.objectsFL import RGlyph as FL_RGlyph - from FL import Glyph as FL_NakedGlyph - flGlyph = FL_NakedGlyph() # new, orphaned FL glyph - wrapped = FL_RGlyph(flGlyph) # rf wrapper for FL glyph - if not clear: - # copy the existing mask data first - existingMask = getGlyphFromMask(targetGlyph) - if existingMask is not None: - pen = FLPointContourPen(existingMask) - existingMask.drawPoints(pen) - pen = FLPointContourPen(wrapped) - maskGlyph.drawPoints(pen) # draw the data - targetGlyph.naked().mask = wrapped .naked() - targetGlyph.update() - -# the lib getter and setter are shared by RFont and RGlyph -def _get_lib(self): - data = self._object.customdata - if data: - f = StringIO(data) - try: - pList = readPlist(f) - except: # XXX ugh, plistlib can raise lots of things - # Anyway, customdata does not contain valid plist data, - # but we don't need to toss it! - pList = {"org.robofab.fontlab.customdata": Data(data)} - else: - pList = {} - # pass it along to the lib object - l = RLib(pList) - l.setParent(self) - return l - -def _set_lib(self, aDict): - l = RLib({}) - l.setParent(self) - l.update(aDict) - - -def _normalizeLineEndings(s): - return s.replace("\r\n", "\n").replace("\r", "\n") - - -class RFont(BaseFont): - """RoboFab UFO wrapper for FL Font object""" - - _title = "FLFont" - - def __init__(self, font=None): - BaseFont.__init__(self) - if font is None: - from FL import fl, Font - # rather than raise an error we could just start a new font. - font = Font() - fl.Add(font) - #raise RoboFabError, "RFont: there's nothing to wrap!?" - self._object = font - self._lib = {} - self._supportHints = True - self.psHints = PostScriptFontHintValues(self) - self.psHints.setParent(self) - - def keys(self): - keys = {} - for glyph in self._object.glyphs: - glyphName = glyph.name - if glyphName in keys: - n = 1 - while ("%s#%s" % (glyphName, n)) in keys: - n += 1 - newGlyphName = "%s#%s" % (glyphName, n) - print "RoboFab encountered a duplicate glyph name, renaming %r to %r" % (glyphName, newGlyphName) - glyphName = newGlyphName - glyph.name = glyphName - keys[glyphName] = None - return keys.keys() - - def has_key(self, glyphName): - glyph = self._object[glyphName] - if glyph is None: - return False - else: - return True - - __contains__ = has_key - - def __setitem__(self, glyphName, glyph): - self._object[glyphName] = glyph.naked() - - def __cmp__(self, other): - if not hasattr(other, '_object'): - return -1 - return self._compare(other) - # if self._object.file_name == other._object.file_name: - # # so, names match. - # # this will falsely identify two distinct "Untitled" - # # let's check some more - # return 0 - # else: - # return -1 - - -# def _get_psHints(self): -# h = PostScriptFontHintValues(self) -# h.setParent(self) -# return h -# -# psHints = property(_get_psHints, doc="font level postscript hint data") - - def _get_info(self): - return RInfo(self._object) - - info = property(_get_info, doc="font info object") - - def _get_features(self): - return RFeatures(self._object) - - features = property(_get_features, doc="features object") - - def _get_kerning(self): - kerning = {} - f = self._object - for g in f.glyphs: - for p in g.kerning: - try: - key = (g.name, f[p.key].name) - kerning[key] = p.value - except AttributeError: pass #catch for TT exception - rk = RKerning(kerning) - rk.setParent(self) - return rk - - kerning = property(_get_kerning, doc="a kerning object") - - def _set_groups(self, aDict): - g = RGroups({}) - g.setParent(self) - g.update(aDict) - - def _get_groups(self): - groups = {} - for i in self._object.classes: - # test to make sure that the class is properly formatted - if i.find(':') == -1: - continue - key = i.split(':')[0] - value = i.split(':')[1].lstrip().split(' ') - groups[key] = value - rg = RGroups(groups) - rg.setParent(self) - return rg - - groups = property(_get_groups, _set_groups, doc="a group object") - - lib = property(_get_lib, _set_lib, doc="font lib object") - - # - # attributes - # - - def _get_fontIndex(self): - # find the index of the font - # by comparing the file_name - # to all open fonts. if the - # font has no file_name, meaning - # it is a new, unsaved font, - # return the index of the first - # font with no file_name. - selfFileName = self._object.file_name - fontCount = len(fl) - for index in xrange(fontCount): - other = fl[index] - if other.file_name == selfFileName: - return index - - fontIndex = property(_get_fontIndex, doc="the fontindex for this font") - - def _get_path(self): - return self._object.file_name - - path = property(_get_path, doc="path to the font") - - def _get_fileName(self): - if self.path is None: - return None - return os.path.split(self.path) - - fileName = property(_get_fileName, doc="the font's file name") - - def _get_selection(self): - # return a list of glyph names for glyphs selected in the font window - l=[] - for i in range(len(self._object.glyphs)): - if fl.Selected(i) == 1: - l.append(self._object[i].name) - return l - - def _set_selection(self, list): - fl.Unselect() - for i in list: - fl.Select(i) - - selection = property(_get_selection, _set_selection, doc="the glyph selection in the font window") - - - def _makeGlyphlist(self): - # To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters. - gl = [] - for c in self: - gl.append(c) - return gl - - def _get_glyphs(self): - return self._makeGlyphlist() - - glyphs = property(_get_glyphs, doc="A list of all glyphs in the font, to allow iterations through Font.glyphs") - - def update(self): - """Don't forget to update the font when you are done.""" - fl.UpdateFont(self.fontIndex) - - def save(self, path=None): - """Save the font, path is required.""" - if not path: - if not self._object.file_name: - raise RoboFabError, "No destination path specified." - else: - path = self._object.file_name - fl.Save(self.fontIndex, path) - - def close(self, save=False): - """Close the font, saving is optional.""" - if save: - self.save() - else: - self._object.modified = 0 - fl.Close(self.fontIndex) - - def getGlyph(self, glyphName): - # XXX may need to become private - flGlyph = self._object[glyphName] - if flGlyph is not None: - glyph = RGlyph(flGlyph) - glyph.setParent(self) - return glyph - return self.newGlyph(glyphName) - - def newGlyph(self, glyphName, clear=True): - """Make a new glyph.""" - # the old implementation always updated the font. - # that proved to be very slow. so, the updating is - # now left up to the caller where it can be more - # efficiently managed. - g = NewGlyph(self._object, glyphName, clear, updateFont=False) - return RGlyph(g) - - def insertGlyph(self, glyph, name=None): - """Returns a new glyph that has been inserted into the font. - name = another glyphname if you want to insert as with that.""" - from robofab.objects.objectsRF import RFont as _RFont - from robofab.objects.objectsRF import RGlyph as _RGlyph - oldGlyph = glyph - if name is None: - name = oldGlyph.name - # clear the destination glyph if it exists. - if self.has_key(name): - self[name].clear() - # get the parent for the glyph - otherFont = oldGlyph.getParent() - # in some cases we will use the native - # FL method for appending a glyph. - useNative = True - testingNative = True - while testingNative: - # but, maybe it is an orphan glyph. - # in that case we should not use the native method. - if otherFont is None: - useNative = False - testingNative = False - # or maybe the glyph is coming from a NoneLab font - if otherFont is not None: - if isinstance(otherFont, _RFont): - useNative = False - testingNative = False - # but, it could be a copied FL glyph - # which is a NoneLab glyph that - # has a FontLab font as the parent - elif isinstance(otherFont, RFont): - useNative = False - testingNative = False - # or, maybe the glyph is being replaced, in which - # case the native method should not be used - # since FL will destroy any references to the glyph - if self.has_key(name): - useNative = False - testingNative = False - # if the glyph contains components the native - # method should not be used since FL does - # not reference glyphs in components by - # name, but by index (!!!). - if len(oldGlyph.components) != 0: - useNative = False - testingNative = False - testingNative = False - # finally, insert the glyph. - if useNative: - font = self.naked() - otherFont = oldGlyph.getParent().naked() - self.naked().glyphs.append(otherFont[name]) - newGlyph = self.getGlyph(name) - else: - newGlyph = self.newGlyph(name) - newGlyph.appendGlyph(oldGlyph) - for attr in GLYPH_COPY_ATTRS: - if attr == "name": - value = name - else: - value = getattr(oldGlyph, attr) - setattr(newGlyph, attr, value) - if self._supportHints: - # now we need to transfer the hints from - # the old glyph to the new glyph. we'll do this - # via the dict to hint functions. - hintDict = {} - # if the glyph is a NoneLab glyph, then we need - # to extract the ps hints from the lib - if isinstance(oldGlyph, _RGlyph): - hintDict = oldGlyph.lib.get(postScriptHintDataLibKey, {}) - # otherwise we need to extract the hint dict from the glyph - else: - hintDict = _glyphHintsToDict(oldGlyph.naked()) - # now apply the hint data - if hintDict: - _dictHintsToGlyph(newGlyph.naked(), hintDict) - # delete any remaining hint data from the glyph lib - if newGlyph.lib.has_key(postScriptHintDataLibKey): - del newGlyph.lib[postScriptHintDataLibKey] - return newGlyph - - def removeGlyph(self, glyphName): - """remove a glyph from the font""" - index = self._object.FindGlyph(glyphName) - if index != -1: - del self._object.glyphs[index] - - # - # opentype - # - - def getOTClasses(self): - """Return all OpenType classes as a dict. Relies on properly formatted classes.""" - classes = {} - c = self._object.ot_classes - if c is None: - return classes - c = c.replace('\r', '').replace('\n', '').split(';') - for i in c: - if i.find('=') != -1: - value = [] - i = i.replace(' = ', '=') - name = i.split('=')[0] - v = i.split('=')[1].replace('[', '').replace(']', '').split(' ') - #catch double spaces? - for j in v: - if len(j) > 0: - value.append(j) - classes[name] = value - return classes - - def setOTClasses(self, dict): - """Set all OpenType classes.""" - l = [] - for i in dict.keys(): - l.append(''.join([i, ' = [', ' '.join(dict[i]), '];'])) - self._object.ot_classes = '\n'.join(l) - - def getOTClass(self, name): - """Get a specific OpenType class.""" - classes = self.getOTClasses() - return classes[name] - - def setOTClass(self, name, list): - """Set a specific OpenType class.""" - classes = self.getOTClasses() - classes[name] = list - self.setOTClasses(classes) - - def getOTFeatures(self): - """Return all OpenType features as a dict keyed by name. - The value is a string of the text of the feature.""" - features = {} - for i in self._object.features: - v = [] - for j in i.value.replace('\r', '\n').split('\n'): - if j.find(i.tag) == -1: - v.append(j) - features[i.tag] = '\n'.join(v) - return features - - def setOTFeatures(self, dict): - """Set all OpenType features in the font.""" - features= {} - for i in dict.keys(): - f = [] - f.append('feature %s {'%i) - f.append(dict[i]) - f.append('} %s;'%i) - features[i] = '\n'.join(f) - self._object.features.clean() - for i in features.keys(): - self._object.features.append(Feature(i, features[i])) - - def getOTFeature(self, name): - """return a specific OpenType feature.""" - features = self.getOTFeatures() - return features[name] - - def setOTFeature(self, name, text): - """Set a specific OpenType feature.""" - features = self.getOTFeatures() - features[name] = text - self.setOTFeatures(features) - - # - # guides - # - - def getVGuides(self): - """Return a list of wrapped vertical guides in this RFont""" - vguides=[] - for i in range(len(self._object.vguides)): - g = RGuide(self._object.vguides[i], i) - g.setParent(self) - vguides.append(g) - return vguides - - def getHGuides(self): - """Return a list of wrapped horizontal guides in this RFont""" - hguides=[] - for i in range(len(self._object.hguides)): - g = RGuide(self._object.hguides[i], i) - g.setParent(self) - hguides.append(g) - return hguides - - def appendHGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - angle = int(round(angle)) - g=Guide(position, angle) - self._object.hguides.append(g) - - def appendVGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - angle = int(round(angle)) - g=Guide(position, angle) - self._object.vguides.append(g) - - def removeHGuide(self, guide): - """Remove a horizontal guide.""" - pos = (guide.position, guide.angle) - for g in self.getHGuides(): - if (g.position, g.angle) == pos: - del self._object.hguides[g.index] - break - - def removeVGuide(self, guide): - """Remove a vertical guide.""" - pos = (guide.position, guide.angle) - for g in self.getVGuides(): - if (g.position, g.angle) == pos: - del self._object.vguides[g.index] - break - - def clearHGuides(self): - """Clear all horizontal guides.""" - self._object.hguides.clean() - - def clearVGuides(self): - """Clear all vertical guides.""" - self._object.vguides.clean() - - - # - # generators - # - - def generate(self, outputType, path=None): - """ - generate the font. outputType is the type of font to ouput. - --Ouput Types: - 'pctype1' : PC Type 1 font (binary/PFB) - 'pcmm' : PC MultipleMaster font (PFB) - 'pctype1ascii' : PC Type 1 font (ASCII/PFA) - 'pcmmascii' : PC MultipleMaster font (ASCII/PFA) - 'unixascii' : UNIX ASCII font (ASCII/PFA) - 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file) - 'otfcff' : PS OpenType (CFF-based) font (OTF) - 'otfttf' : PC TrueType/TT OpenType font (TTF) - 'macttf' : Mac TrueType font (generates suitcase) - 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork) - (doc adapted from http://dev.fontlab.net/flpydoc/) - - path can be a directory or a directory file name combo: - path="DirectoryA/DirectoryB" - path="DirectoryA/DirectoryB/MyFontName" - if no path is given, the file will be output in the same directory - as the vfb file. if no file name is given, the filename will be the - vfb file name with the appropriate suffix. - """ - outputType = outputType.lower() - if not _flGenerateTypes.has_key(outputType): - raise RoboFabError, "%s output type is not supported"%outputType - flOutputType, suffix = _flGenerateTypes[outputType] - if path is None: - filePath, fileName = os.path.split(self.path) - fileName = fileName.replace('.vfb', '') - else: - if os.path.isdir(path): - filePath = path - fileName = os.path.split(self.path)[1].replace('.vfb', '') - else: - filePath, fileName = os.path.split(path) - if '.' in fileName: - raise RoboFabError, "filename cannot contain periods.", fileName - fileName = '.'.join([fileName, suffix]) - finalPath = os.path.join(filePath, fileName) - if isinstance(finalPath, unicode): - finalPath = finalPath.encode("utf-8") - # generate is (oddly) an application level method - # rather than a font level method. because of this, - # the font must be the current font. so, make it so. - fl.ifont = self.fontIndex - fl.GenerateFont(flOutputType, finalPath) - - def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, - doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2): - from robofab.interface.all.dialogs import ProgressBar, Message - # special glyph name to file name conversion - if glyphNameToFileNameFunc is None: - glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() - if glyphNameToFileNameFunc is None: - from robofab.tools.glyphNameSchemes import glyphNameToShortFileName - glyphNameToFileNameFunc = glyphNameToShortFileName - # get a valid path - if not path: - if self.path is None: - Message("Please save this font first before exporting to UFO...") - return - else: - path = ufoLib.makeUFOPath(self.path) - # get the glyphs to export - if glyphs is None: - glyphs = self.keys() - # if the file exists, check the format version. - # if the format version being written is different - # from the format version of the existing UFO - # and only some files are set to be written - # raise an error. - if os.path.exists(path): - if os.path.exists(os.path.join(path, "metainfo.plist")): - reader = ufoLib.UFOReader(path) - existingFormatVersion = reader.formatVersion - if formatVersion != existingFormatVersion: - if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]: - Message("When overwriting an existing UFO with a different format version all files must be written.") - return - # the lib must be written if format version is 1 - if not doLib and formatVersion == 1: - Message("The lib must be written when exporting format version 1.") - return - # set up the progress bar - nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) - bar = None - if doProgress: - bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs)) - # try writing - try: - writer = ufoLib.UFOWriter(path, formatVersion=formatVersion) - ## We make a shallow copy if lib, since we add some stuff for export - ## that doesn't need to be retained in memory. - fontLib = dict(self.lib) - # write the font info - if doInfo: - global _IN_UFO_EXPORT - _IN_UFO_EXPORT = True - writer.writeInfo(self.info) - _IN_UFO_EXPORT = False - if bar: - bar.tick() - # write the kerning - if doKerning: - writer.writeKerning(self.kerning.asDict()) - if bar: - bar.tick() - # write the groups - if doGroups: - writer.writeGroups(self.groups) - if bar: - bar.tick() - # write the features - if doFeatures: - if formatVersion == 2: - writer.writeFeatures(self.features.text) - else: - self._writeOpenTypeFeaturesToLib(fontLib) - if bar: - bar.tick() - # write the lib - if doLib: - ## Always export the postscript font hint values to the lib in format version 1 - if formatVersion == 1: - d = self.psHints.asDict() - fontLib[postScriptHintDataLibKey] = d - ## Export the glyph order to the lib - glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs] - fontLib["public.glyphOrder"] = glyphOrder - ## export the features - if doFeatures and formatVersion == 1: - self._writeOpenTypeFeaturesToLib(fontLib) - if bar: - bar.tick() - writer.writeLib(fontLib) - if bar: - bar.tick() - # write the glyphs - if glyphs: - glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) - count = nonGlyphCount - for nakedGlyph in self.naked().glyphs: - if nakedGlyph.name not in glyphs: - continue - glyph = RGlyph(nakedGlyph) - if doHints: - hintStuff = _glyphHintsToDict(glyph.naked()) - if hintStuff: - glyph.lib[postScriptHintDataLibKey] = hintStuff - glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) - # remove the hint dict from the lib - if doHints and glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - glyphSet.writeContents() - # only blindly stop if the user says to - except KeyboardInterrupt: - if bar: - bar.close() - bar = None - # kill the bar - if bar: - bar.close() - - def _writeOpenTypeFeaturesToLib(self, fontLib): - # this should only be used for UFO format version 1 - flFont = self.naked() - cls = flFont.ot_classes - if cls is not None: - fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(cls).rstrip() + "\n" - if flFont.features: - features = {} - order = [] - for feature in flFont.features: - order.append(feature.tag) - features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n" - fontLib["org.robofab.opentype.features"] = features - fontLib["org.robofab.opentype.featureorder"] = order - - def readUFO(self, path, doProgress=False, - doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None): - """read a .ufo into the font""" - from robofab.pens.flPen import FLPointPen - from robofab.interface.all.dialogs import ProgressBar - # start up the reader - reader = ufoLib.UFOReader(path) - glyphSet = reader.getGlyphSet() - # get a list of glyphs that should be imported - if glyphs is None: - glyphs = glyphSet.keys() - # set up the progress bar - nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) - bar = None - if doProgress: - bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs)) - # start reading - try: - fontLib = reader.readLib() - # info - if doInfo: - reader.readInfo(self.info) - if bar: - bar.tick() - # glyphs - count = 1 - glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet) - for glyphName in glyphOrder: - if glyphName not in glyphs: - continue - glyph = self.newGlyph(glyphName, clear=True) - pen = FLPointPen(glyph.naked()) - glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) - if doHints: - hintData = glyph.lib.get(postScriptHintDataLibKey) - if hintData: - _dictHintsToGlyph(glyph.naked(), hintData) - # now that the hints have been extracted from the glyph - # there is no reason to keep the location in the lib. - if glyph.lib.has_key(postScriptHintDataLibKey): - del glyph.lib[postScriptHintDataLibKey] - if bar and not count % 10: - bar.tick(count) - count = count + 1 - # features - if doFeatures: - if reader.formatVersion == 1: - self._readOpenTypeFeaturesFromLib(fontLib) - else: - featureText = reader.readFeatures() - self.features.text = featureText - if bar: - bar.tick() - else: - # remove features stored in the lib - self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False) - # kerning - if doKerning: - self.kerning.clear() - self.kerning.update(reader.readKerning()) - if bar: - bar.tick() - # groups - if doGroups: - self.groups.clear() - self.groups.update(reader.readGroups()) - if bar: - bar.tick() - # hints in format version 1 - if doHints and reader.formatVersion == 1: - self.psHints._loadFromLib(fontLib) - else: - # remove hint data stored in the lib - if fontLib.has_key(postScriptHintDataLibKey): - del fontLib[postScriptHintDataLibKey] - # lib - if doLib: - self.lib.clear() - self.lib.update(fontLib) - if bar: - bar.tick() - # update the font - self.update() - # only blindly stop if the user says to - except KeyboardInterrupt: - bar.close() - bar = None - # kill the bar - if bar: - bar.close() - - def _getGlyphOrderFromLib(self, fontLib, glyphSet): - key = "public.glyphOrder" - glyphOrder = fontLib.get(key) - if glyphOrder is None: - key = "org.robofab.glyphOrder" - glyphOrder = fontLib.get(key) - if glyphOrder is not None: - # no need to keep track if the glyph order in lib once the font is loaded. - del fontLib[key] - glyphNames = [] - done = {} - for glyphName in glyphOrder: - if glyphName in glyphSet: - glyphNames.append(glyphName) - done[glyphName] = 1 - allGlyphNames = glyphSet.keys() - allGlyphNames.sort() - for glyphName in allGlyphNames: - if glyphName not in done: - glyphNames.append(glyphName) - else: - glyphNames = glyphSet.keys() - glyphNames.sort() - return glyphNames - - def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True): - # setFeatures may be False. in this case, this method - # should only clear the data from the lib. - classes = fontLib.get("org.robofab.opentype.classes") - if classes is not None: - del fontLib["org.robofab.opentype.classes"] - if setFeatures: - self.naked().ot_classes = classes - features = fontLib.get("org.robofab.opentype.features") - if features is not None: - order = fontLib.get("org.robofab.opentype.featureorder") - if order is None: - # for UFOs saved without the feature order, do the same as before. - order = features.keys() - order.sort() - else: - del fontLib["org.robofab.opentype.featureorder"] - del fontLib["org.robofab.opentype.features"] - #features = features.items() - orderedFeatures = [] - for tag in order: - oneFeature = features.get(tag) - if oneFeature is not None: - orderedFeatures.append((tag, oneFeature)) - if setFeatures: - self.naked().features.clean() - for tag, src in orderedFeatures: - self.naked().features.append(Feature(tag, src)) - - - -class RGlyph(BaseGlyph): - """RoboFab wrapper for FL Glyph object""" - - _title = "FLGlyph" - - def __init__(self, flGlyph): - #BaseGlyph.__init__(self) - if flGlyph is None: - raise RoboFabError, "RGlyph: there's nothing to wrap!?" - self._object = flGlyph - self._lib = {} - self._contours = None - - def __getitem__(self, index): - return self.contours[index] - - def __delitem__(self, index): - self._object.DeleteContour(index) - self._invalidateContours() - - def __len__(self): - return len(self.contours) - - lib = property(_get_lib, _set_lib, doc="glyph lib object") - - def _invalidateContours(self): - self._contours = None - - def _buildContours(self): - self._contours = [] - for contourIndex in range(self._object.GetContoursNumber()): - c = RContour(contourIndex) - c.setParent(self) - c._buildSegments() - self._contours.append(c) - - # - # attribute handlers - # - - def _get_index(self): - return self._object.parent.FindGlyph(self.name) - - index = property(_get_index, doc="return the index of the glyph in the font") - - def _get_name(self): - return self._object.name - - def _set_name(self, value): - self._object.name = value - - name = property(_get_name, _set_name, doc="name") - - def _get_psName(self): - return self._object.name - - def _set_psName(self, value): - self._object.name = value - - psName = property(_get_psName, _set_psName, doc="name") - - def _get_baseName(self): - return self._object.name.split('.')[0] - - baseName = property(_get_baseName, doc="") - - def _get_unicode(self): - return self._object.unicode - - def _set_unicode(self, value): - self._object.unicode = value - - unicode = property(_get_unicode, _set_unicode, doc="unicode") - - def _get_unicodes(self): - return self._object.unicodes - - def _set_unicodes(self, value): - self._object.unicodes = value - - unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes") - - def _get_width(self): - return self._object.width - - def _set_width(self, value): - value = int(round(value)) - self._object.width = value - - width = property(_get_width, _set_width, doc="the width") - - def _get_box(self): - if not len(self.contours) and not len(self.components): - return (0, 0, 0, 0) - r = self._object.GetBoundingRect() - return (int(round(r.ll.x)), int(round(r.ll.y)), int(round(r.ur.x)), int(round(r.ur.y))) - - box = property(_get_box, doc="box of glyph as a tuple (xMin, yMin, xMax, yMax)") - - def _get_selected(self): - if fl.Selected(self._object.parent.FindGlyph(self._object.name)): - return 1 - else: - return 0 - - def _set_selected(self, value): - fl.Select(self._object.name, value) - - selected = property(_get_selected, _set_selected, doc="Select or deselect the glyph in the font window") - - def _get_mark(self): - return self._object.mark - - def _set_mark(self, value): - self._object.mark = value - - mark = property(_get_mark, _set_mark, doc="mark") - - def _get_note(self): - s = self._object.note - if s is None: - return s - return unicode(s, LOCAL_ENCODING) - - def _set_note(self, value): - if value is None: - value = '' - if type(value) == type(u""): - value = value.encode(LOCAL_ENCODING) - self._object.note = value - - note = property(_get_note, _set_note, doc="note") - - def _get_psHints(self): - # get an object representing the postscript zone information - return PostScriptGlyphHintValues(self) - - psHints = property(_get_psHints, doc="postscript hint data") - - # - # necessary evil - # - - def update(self): - """Don't forget to update the glyph when you are done.""" - fl.UpdateGlyph(self._object.parent.FindGlyph(self._object.name)) - - # - # methods to make RGlyph compatible with FL.Glyph - # ##are these still needed? - # - - def GetBoundingRect(self, masterIndex): - """FL compatibility""" - return self._object.GetBoundingRect(masterIndex) - - def GetMetrics(self, masterIndex): - """FL compatibility""" - return self._object.GetMetrics(masterIndex) - - def SetMetrics(self, value, masterIndex): - """FL compatibility""" - return self._object.SetMetrics(value, masterIndex) - - # - # object builders - # - - def _get_anchors(self): - return self.getAnchors() - - anchors = property(_get_anchors, doc="allow for iteration through glyph.anchors") - - def _get_components(self): - return self.getComponents() - - components = property(_get_components, doc="allow for iteration through glyph.components") - - def _get_contours(self): - if self._contours is None: - self._buildContours() - return self._contours - - contours = property(_get_contours, doc="allow for iteration through glyph.contours") - - def getAnchors(self): - """Return a list of wrapped anchors in this RGlyph.""" - anchors=[] - for i in range(len(self._object.anchors)): - a = RAnchor(self._object.anchors[i], i) - a.setParent(self) - anchors.append(a) - return anchors - - def getComponents(self): - """Return a list of wrapped components in this RGlyph.""" - components=[] - for i in range(len(self._object.components)): - c = RComponent(self._object.components[i], i) - c.setParent(self) - components.append(c) - return components - - def getVGuides(self): - """Return a list of wrapped vertical guides in this RGlyph""" - vguides=[] - for i in range(len(self._object.vguides)): - g = RGuide(self._object.vguides[i], i) - g.setParent(self) - vguides.append(g) - return vguides - - def getHGuides(self): - """Return a list of wrapped horizontal guides in this RGlyph""" - hguides=[] - for i in range(len(self._object.hguides)): - g = RGuide(self._object.hguides[i], i) - g.setParent(self) - hguides.append(g) - return hguides - - # - # tools - # - - def getPointPen(self): - self._invalidateContours() - # Now just don't muck with glyph.contours before you're done drawing... - return FLPointPen(self) - - def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): - """Append a component to the glyph. x and y are optional offset values""" - offset = roundPt((offset[0], offset[1])) - p = FLPointPen(self.naked()) - xx, yy = scale - dx, dy = offset - p.addComponent(baseGlyph, (xx, 0, 0, yy, dx, dy)) - - def appendAnchor(self, name, position): - """Append an anchor to the glyph""" - value = roundPt((position[0], position[1])) - anchor = Anchor(name, value[0], value[1]) - self._object.anchors.append(anchor) - - def appendHGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - g = Guide(position, angle) - self._object.hguides.append(g) - - def appendVGuide(self, position, angle=0): - """Append a horizontal guide""" - position = int(round(position)) - g = Guide(position, angle) - self._object.vguides.append(g) - - def clearContours(self): - self._object.Clear() - self._invalidateContours() - - def clearComponents(self): - """Clear all components.""" - self._object.components.clean() - - def clearAnchors(self): - """Clear all anchors.""" - self._object.anchors.clean() - - def clearHGuides(self): - """Clear all horizontal guides.""" - self._object.hguides.clean() - - def clearVGuides(self): - """Clear all vertical guides.""" - self._object.vguides.clean() - - def removeComponent(self, component): - """Remove a specific component from the glyph. This only works - if the glyph does not have duplicate components in the same location.""" - pos = (component.baseGlyph, component.offset, component.scale) - a = self.getComponents() - found = [] - for i in a: - if (i.baseGlyph, i.offset, i.scale) == pos: - found.append(i) - if len(found) > 1: - raise RoboFabError, 'Found more than one possible component to remove' - elif len(found) == 1: - del self._object.components[found[0].index] - else: - raise RoboFabError, 'Component does not exist' - - def removeContour(self, index): - """remove a specific contour from the glyph""" - self._object.DeleteContour(index) - self._invalidateContours() - - def removeAnchor(self, anchor): - """Remove a specific anchor from the glyph. This only works - if the glyph does not have anchors with duplicate names - in exactly the same location with the same mark.""" - pos = (anchor.name, anchor.position, anchor.mark) - a = self.getAnchors() - found = [] - for i in a: - if (i.name, i.position, i.mark) == pos: - found.append(i) - if len(found) > 1: - raise RoboFabError, 'Found more than one possible anchor to remove' - elif len(found) == 1: - del self._object.anchors[found[0].index] - else: - raise RoboFabError, 'Anchor does not exist' - - def removeHGuide(self, guide): - """Remove a horizontal guide.""" - pos = (guide.position, guide.angle) - for g in self.getHGuides(): - if (g.position, g.angle) == pos: - del self._object.hguides[g.index] - break - - def removeVGuide(self, guide): - """Remove a vertical guide.""" - pos = (guide.position, guide.angle) - for g in self.getVGuides(): - if (g.position, g.angle) == pos: - del self._object.vguides[g.index] - break - - def center(self, padding=None): - """Equalise sidebearings, set to padding if wanted.""" - left = self.leftMargin - right = self.rightMargin - if padding: - e_left = e_right = padding - else: - e_left = (left + right)/2 - e_right = (left + right) - e_left - self.leftMargin= e_left - self.rightMargin= e_right - - def removeOverlap(self): - """Remove overlap""" - self._object.RemoveOverlap() - self._invalidateContours() - - def decompose(self): - """Decompose all components""" - self._object.Decompose() - self._invalidateContours() - - ##broken! - #def removeHints(self): - # """Remove the hints.""" - # self._object.RemoveHints() - - def autoHint(self): - """Automatically generate type 1 hints.""" - self._object.Autohint() - - def move(self, (x, y), contours=True, components=True, anchors=True): - """Move a glyph's items that are flagged as True""" - x, y = roundPt((x, y)) - self._object.Shift(Point(x, y)) - for c in self.getComponents(): - c.move((x, y)) - for a in self.getAnchors(): - a.move((x, y)) - - def clear(self, contours=True, components=True, anchors=True, guides=True, hints=True): - """Clear all items marked as true from the glyph""" - if contours: - self._object.Clear() - self._invalidateContours() - if components: - self._object.components.clean() - if anchors: - self._object.anchors.clean() - if guides: - self._object.hguides.clean() - self._object.vguides.clean() - if hints: - # RemoveHints requires an "integer mode" argument - # but it is not documented. from some simple experiments - # i deduced that - # 1 = horizontal hints and links, - # 2 = vertical hints and links - # 3 = all hints and links - self._object.RemoveHints(3) - - # - # special treatment for GlyphMath support in FontLab - # - - def _getMathDestination(self): - from robofab.objects.objectsRF import RGlyph as _RGlyph - return _RGlyph() - - def copy(self, aParent=None): - """Make a copy of this glyph. - Note: the copy is not a duplicate fontlab glyph, but - a RF RGlyph with the same outlines. The new glyph is - not part of the fontlab font in any way. Use font.appendGlyph(glyph) - to get it in a FontLab glyph again.""" - from robofab.objects.objectsRF import RGlyph as _RGlyph - newGlyph = _RGlyph() - newGlyph.appendGlyph(self) - for attr in GLYPH_COPY_ATTRS: - value = getattr(self, attr) - setattr(newGlyph, attr, value) - # hints - doHints = False - parent = self.getParent() - if parent is not None and parent._supportHints: - hintStuff = _glyphHintsToDict(self.naked()) - if hintStuff: - newGlyph.lib[postScriptHintDataLibKey] = hintStuff - if aParent is not None: - newGlyph.setParent(aParent) - elif self.getParent() is not None: - newGlyph.setParent(self.getParent()) - return newGlyph - - def __mul__(self, factor): - return self.copy() *factor - - __rmul__ = __mul__ - - def __sub__(self, other): - return self.copy() - other.copy() - - def __add__(self, other): - return self.copy() + other.copy() - - - -class RContour(BaseContour): - - """RoboFab wrapper for non FL contour object""" - - _title = "FLContour" - - def __init__(self, index): - self._index = index - self._parentGlyph = None - self.segments = [] - - def __len__(self): - return len(self.points) - - def _buildSegments(self): - ####################### - # Notes about FL node contour structure - ####################### - # for TT curves, FL lists them as seperate nodes: - # [move, off, off, off, line, off, off] - # and, this list is sequential. after the last on curve, - # it is possible (and likely) that there will be more offCurves - # in our segment object, these should be associated with the - # first segment in the contour. - # - # for PS curves, it is a very different scenerio. - # curve nodes contain points: - # [on, off, off] - # and the list is not in sequential order. the first point in - # the list is the on curve and the subsequent points are the off - # curve points leading up to that on curve. - # - # it is very important to remember these structures when trying - # to understand the code below - - self.segments = [] - offList = [] - nodes = self._nakedParent.nodes - for index in range(self._nodeLength): - x = index + self._startNodeIndex - node = nodes[x] - # we do have a loose off curve. deal with it. - if node.type == flOFFCURVE: - offList.append(x) - # we are not dealing with a loose off curve - else: - s = RSegment(x) - s.setParent(self) - # but do we have a collection of loose off curves above? - # if so, apply them to the segment, and clear the list - if len(offList) != 0: - s._looseOffCurve = offList - offList = [] - self.segments.append(s) - # do we have some off curves now that the contour is complete? - if len(offList) != 0: - # ugh. apply them to the first segment - self.segments[0]._looseOffCurve = offList - - def setParent(self, parentGlyph): - self._parentGlyph = parentGlyph - - def getParent(self): - return self._parentGlyph - - def _get__nakedParent(self): - return self._parentGlyph.naked() - - _nakedParent = property(_get__nakedParent, doc="") - - def _get__startNodeIndex(self): - return self._nakedParent.GetContourBegin(self._index) - - _startNodeIndex = property(_get__startNodeIndex, doc="") - - def _get__nodeLength(self): - return self._nakedParent.GetContourLength(self._index) - - _nodeLength = property(_get__nodeLength, doc="") - - def _get__lastNodeIndex(self): - return self._startNodeIndex + self._nodeLength - 1 - - _lastNodeIndex = property(_get__lastNodeIndex, doc="") - - def _previousNodeIndex(self, index): - return (index - 1) % self._nodeLength - - def _nextNodeIndex(self, index): - return (index + 1) % self._nodeLength - - def _getNode(self, index): - return self._nodes[index] - - def _get__nodes(self): - nodes = [] - for node in self._nakedParent.nodes[self._startNodeIndex:self._startNodeIndex+self._nodeLength-1]: - nodes.append(node) - return nodes - - _nodes = property(_get__nodes, doc="") - - def _get_points(self): - points = [] - for segment in self.segments: - for point in segment.points: - points.append(point) - return points - - points = property(_get_points, doc="") - - def _get_bPoints(self): - bPoints = [] - for segment in self.segments: - bp = RBPoint(segment.index) - bp.setParent(self) - bPoints.append(bp) - return bPoints - - bPoints = property(_get_bPoints, doc="") - - def _get_index(self): - return self._index - - def _set_index(self, index): - if index != self._index: - self._nakedParent.ReorderContour(self._index, index) - # reorder and set the _index of the existing RContour objects - # this will be a better solution than reconstructing all the objects - # segment objects will still, sadly, have to be reconstructed - contourList = self.getParent().contours - contourList.insert(index, contourList.pop(self._index)) - for i in range(len(contourList)): - contourList[i]._index = i - contourList[i]._buildSegments() - - - index = property(_get_index, _set_index, doc="the index of the contour") - - def _get_selected(self): - selected = 0 - nodes = self._nodes - for node in nodes: - if node.selected == 1: - selected = 1 - break - return selected - - def _set_selected(self, value): - if value == 1: - self._nakedParent.SelectContour(self._index) - else: - for node in self._nodes: - node.selected = value - - selected = property(_get_selected, _set_selected, doc="selection of the contour: 1-selected or 0-unselected") - - def appendSegment(self, segmentType, points, smooth=False): - segment = self.insertSegment(index=self._nodeLength, segmentType=segmentType, points=points, smooth=smooth) - return segment - - def insertSegment(self, index, segmentType, points, smooth=False): - """insert a seggment into the contour""" - # do a qcurve insertion - if segmentType == QCURVE: - count = 0 - for point in points[:-1]: - newNode = Node(flOFFCURVE, Point(point[0], point[1])) - self._nakedParent.Insert(newNode, self._startNodeIndex + index + count) - count = count + 1 - newNode = Node(flLINE, Point(points[-1][0], points[-1][1])) - self._nakedParent.Insert(newNode, self._startNodeIndex + index +len(points) - 1) - # do a regular insertion - else: - onX, onY = points[-1] - newNode = Node(_rfToFLSegmentType(segmentType), Point(onX, onY)) - # fix the off curves in case the user is inserting a curve - # but is not specifying off curve points - if segmentType == CURVE and len(points) == 1: - pSeg = self._prevSegment(index) - pOn = pSeg.onCurve - newNode.points[1].Assign(Point(pOn.x, pOn.y)) - newNode.points[2].Assign(Point(onX, onY)) - for pointIndex in range(len(points[:-1])): - x, y = points[pointIndex] - newNode.points[1 + pointIndex].Assign(Point(x, y)) - if smooth: - newNode.alignment = flSMOOTH - self._nakedParent.Insert(newNode, self._startNodeIndex + index) - self._buildSegments() - return self.segments[index] - - def removeSegment(self, index): - """remove a segment from the contour""" - segment = self.segments[index] - # we have a qcurve. umph. - if segment.type == QCURVE: - indexList = [segment._nodeIndex] + segment._looseOffCurve - indexList.sort() - indexList.reverse() - parent = self._nakedParent - for nodeIndex in indexList: - parent.DeleteNode(nodeIndex) - # we have a more sane structure to follow - else: - # store some info for later - next = self._nextSegment(index) - nextOffA = None - nextOffB = None - nextType = next.type - if nextType != LINE and nextType != MOVE: - pA = next.offCurve[0] - nextOffA = (pA.x, pA.y) - pB = next.offCurve[-1] - nextOffB = (pB.x, pB.y) - nodeIndex = segment._nodeIndex - self._nakedParent.DeleteNode(nodeIndex) - self._buildSegments() - # now we must override FL guessing about offCurves - next = self._nextSegment(index - 1) - nextType = next.type - if nextType != LINE and nextType != MOVE: - pA = next.offCurve[0] - pB = next.offCurve[-1] - pA.x, pA.y = nextOffA - pB.x, pB.y = nextOffB - - def reverseContour(self): - """reverse contour direction""" - self._nakedParent.ReverseContour(self._index) - self._buildSegments() - - def setStartSegment(self, segmentIndex): - """set the first node on the contour""" - self._nakedParent.SetStartNode(self._startNodeIndex + segmentIndex) - self.getParent()._invalidateContours() - self.getParent()._buildContours() - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RContour is not implemented." - - - -class RSegment(BaseSegment): - - _title = "FLSegment" - - def __init__(self, flNodeIndex): - BaseSegment.__init__(self) - self._nodeIndex = flNodeIndex - self._looseOffCurve = [] #a list of indexes to loose off curve nodes - - def _get__node(self): - glyph = self.getParent()._nakedParent - return glyph.nodes[self._nodeIndex] - - _node = property(_get__node, doc="") - - def _get_qOffCurve(self): - nodes = self.getParent()._nakedParent.nodes - off = [] - for x in self._looseOffCurve: - off.append(nodes[x]) - return off - - _qOffCurve = property(_get_qOffCurve, doc="free floating off curve nodes in the segment") - - def _get_index(self): - contour = self.getParent() - return self._nodeIndex - contour._startNodeIndex - - index = property(_get_index, doc="") - - def _isQCurve(self): - # loose off curves only appear in q curves - if len(self._looseOffCurve) != 0: - return True - return False - - def _get_type(self): - if self._isQCurve(): - return QCURVE - return _flToRFSegmentType(self._node.type) - - def _set_type(self, segmentType): - if self._isQCurve(): - raise RoboFabError, 'qcurve point types cannot be changed' - oldNode = self._node - oldType = oldNode.type - oldPointType = _flToRFSegmentType(oldType) - if oldPointType == MOVE: - raise RoboFabError, '%s point types cannot be changed'%oldPointType - if segmentType == MOVE or segmentType == OFFCURVE: - raise RoboFabError, '%s point types cannot be assigned'%oldPointType - if oldPointType == segmentType: - return - oldNode.type = _rfToFLSegmentType(segmentType) - - type = property(_get_type, _set_type, doc="") - - def _get_smooth(self): - alignment = self._node.alignment - if alignment == flSMOOTH or alignment == flFIXED: - return True - return False - - def _set_smooth(self, value): - if value: - self._node.alignment = flSMOOTH - else: - self._node.alignment = flSHARP - - smooth = property(_get_smooth, _set_smooth, doc="") - - def _get_points(self): - points = [] - node = self._node - # gather the off curves - # - # are we dealing with a qCurve? ugh. - # gather the loose off curves - if self._isQCurve(): - off = self._qOffCurve - x = 0 - for n in off: - p = RPoint(0) - p.setParent(self) - p._qOffIndex = x - points.append(p) - x = x + 1 - # otherwise get the points associated with the node - else: - index = 1 - for point in node.points[1:]: - p = RPoint(index) - p.setParent(self) - points.append(p) - index = index + 1 - # the last point should always be the on curve - p = RPoint(0) - p.setParent(self) - points.append(p) - return points - - points = property(_get_points, doc="") - - def _get_selected(self): - return self._node.selected - - def _set_selected(self, value): - self._node.selected = value - - selected = property(_get_selected, _set_selected, doc="") - - def move(self, (x, y)): - x, y = roundPt((x, y)) - self._node.Shift(Point(x, y)) - if self._isQCurve(): - qOff = self._qOffCurve - for node in qOff: - node.Shift(Point(x, y)) - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RSegment is not implemented." - - - -class RPoint(BasePoint): - - _title = "FLPoint" - - def __init__(self, pointIndex): - #BasePoint.__init__(self) - self._pointIndex = pointIndex - self._qOffIndex = None - - def _get__parentGlyph(self): - return self._parentContour.getParent() - - _parentGlyph = property(_get__parentGlyph, doc="") - - def _get__parentContour(self): - return self._parentSegment.getParent() - - _parentContour = property(_get__parentContour, doc="") - - def _get__parentSegment(self): - return self.getParent() - - _parentSegment = property(_get__parentSegment, doc="") - - def _get__node(self): - if self._qOffIndex is not None: - return self.getParent()._qOffCurve[self._qOffIndex] - return self.getParent()._node - - _node = property(_get__node, doc="") - - def _get__point(self): - return self._node.points[self._pointIndex] - - _point = property(_get__point, doc="") - - def _get_x(self): - return self._point.x - - def _set_x(self, value): - value = int(round(value)) - self._point.x = value - - x = property(_get_x, _set_x, doc="") - - def _get_y(self): - return self._point.y - - def _set_y(self, value): - value = int(round(value)) - self._point.y = value - - y = property(_get_y, _set_y, doc="") - - def _get_type(self): - if self._pointIndex == 0: - # FL store quad contour data as a list of off curves and lines - # (see note in RContour._buildSegments). So, we need to do - # a bit of trickery to return a decent point type. - # if the straight FL node type is off curve, it is a loose - # quad off curve. return that. - tp = _flToRFSegmentType(self._node.type) - if tp == OFFCURVE: - return OFFCURVE - # otherwise we are dealing with an on curve. in this case, - # we attempt to get the parent segment type and return it. - segment = self.getParent() - if segment is not None: - return segment.type - # we must not have a segment, fall back to straight conversion - return tp - return OFFCURVE - - type = property(_get_type, doc="") - - def _set_selected(self, value): - if self._pointIndex == 0: - self._node.selected = value - - def _get_selected(self): - if self._pointIndex == 0: - return self._node.selected - return False - - selected = property(_get_selected, _set_selected, doc="") - - def move(self, (x, y)): - x, y = roundPt((x, y)) - self._point.Shift(Point(x, y)) - - def scale(self, (x, y), center=(0, 0)): - centerX, centerY = roundPt(center) - point = self._point - point.x, point.y = _scalePointFromCenter((point.x, point.y), (x, y), (centerX, centerY)) - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RPoint is not implemented." - - -class RBPoint(BaseBPoint): - - _title = "FLBPoint" - - def __init__(self, segmentIndex): - #BaseBPoint.__init__(self) - self._segmentIndex = segmentIndex - - def _get__parentSegment(self): - return self.getParent().segments[self._segmentIndex] - - _parentSegment = property(_get__parentSegment, doc="") - - def _get_index(self): - return self._segmentIndex - - index = property(_get_index, doc="") - - def _get_selected(self): - return self._parentSegment.selected - - def _set_selected(self, value): - self._parentSegment.selected = value - - selected = property(_get_selected, _set_selected, doc="") - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RBPoint is not implemented." - - -class RComponent(BaseComponent): - - """RoboFab wrapper for FL Component object""" - - _title = "FLComponent" - - def __init__(self, flComponent, index): - BaseComponent.__init__(self) - self._object = flComponent - self._index=index - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of component") - - def _get_baseGlyph(self): - return self._object.parent.parent[self._object.index].name - - baseGlyph = property(_get_baseGlyph, doc="") - - def _get_offset(self): - return (int(self._object.delta.x), int(self._object.delta.y)) - - def _set_offset(self, value): - value = roundPt((value[0], value[1])) - self._object.delta=Point(value[0], value[1]) - - offset = property(_get_offset, _set_offset, doc="the offset of the component") - - def _get_scale(self): - return (self._object.scale.x, self._object.scale.y) - - def _set_scale(self, (x, y)): - self._object.scale=Point(x, y) - - scale = property(_get_scale, _set_scale, doc="the scale of the component") - - def move(self, (x, y)): - """Move the component""" - x, y = roundPt((x, y)) - self._object.delta=Point(self._object.delta.x+x, self._object.delta.y+y) - - def decompose(self): - """Decompose the component""" - self._object.Paste() - - def copy(self, aParent=None): - """Copy this object -- result is an ObjectsRF flavored object. - There is no way to make this work using FontLab objects. - Copy is mainly used for glyphmath. - """ - raise RoboFabError, "copy() for objectsFL.RComponent is not implemented." - - - -class RAnchor(BaseAnchor): - """RoboFab wrapper for FL Anchor object""" - - _title = "FLAnchor" - - def __init__(self, flAnchor, index): - BaseAnchor.__init__(self) - self._object = flAnchor - self._index = index - - def _get_y(self): - return self._object.y - - def _set_y(self, value): - self._object.y = int(round(value)) - - y = property(_get_y, _set_y, doc="y") - - def _get_x(self): - return self._object.x - - def _set_x(self, value): - self._object.x = int(round(value)) - - x = property(_get_x, _set_x, doc="x") - - def _get_name(self): - return self._object.name - - def _set_name(self, value): - self._object.name = value - - name = property(_get_name, _set_name, doc="name") - - def _get_mark(self): - return self._object.mark - - def _set_mark(self, value): - self._object.mark = value - - mark = property(_get_mark, _set_mark, doc="mark") - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of the anchor") - - def _get_position(self): - return (self._object.x, self._object.y) - - def _set_position(self, value): - value = roundPt((value[0], value[1])) - self._object.x=value[0] - self._object.y=value[1] - - position = property(_get_position, _set_position, doc="position of the anchor") - - - -class RGuide(BaseGuide): - - """RoboFab wrapper for FL Guide object""" - - _title = "FLGuide" - - def __init__(self, flGuide, index): - BaseGuide.__init__(self) - self._object = flGuide - self._index = index - - def __repr__(self): - # this is a doozy! - parent = "unknown_parent" - parentObject = self.getParent() - if parentObject is not None: - # do we have a font? - try: - parent = parentObject.info.postscriptFullName - except AttributeError: - # or do we have a glyph? - try: - parent = parentObject.name - # we must be an orphan - except AttributeError: pass - return "<Robofab guide wrapper for %s>"%parent - - def _get_position(self): - return self._object.position - - def _set_position(self, value): - self._object.position = value - - position = property(_get_position, _set_position, doc="position") - - def _get_angle(self): - return self._object.angle - - def _set_angle(self, value): - self._object.angle = value - - angle = property(_get_angle, _set_angle, doc="angle") - - def _get_index(self): - return self._index - - index = property(_get_index, doc="index of the guide") - - -class RGroups(BaseGroups): - - """RoboFab wrapper for FL group data""" - - _title = "FLGroups" - - def __init__(self, aDict): - self.update(aDict) - - def __setitem__(self, key, value): - # override baseclass so that data is stored in FL classes - if not isinstance(key, str): - raise RoboFabError, 'key must be a string' - if not isinstance(value, list): - raise RoboFabError, 'group must be a list' - super(RGroups, self).__setitem__(key, value) - self._setFLGroups() - - def __delitem__(self, key): - # override baseclass so that data is stored in FL classes - super(RGroups, self).__delitem__(key) - self._setFLGroups() - - def _setFLGroups(self): - # set the group data into the font. - if self.getParent() is not None: - groups = [] - for i in self.keys(): - value = ' '.join(self[i]) - groups.append(': '.join([i, value])) - groups.sort() - self.getParent().naked().classes = groups - - def update(self, aDict): - # override baseclass so that data is stored in FL classes - super(RGroups, self).update(aDict) - self._setFLGroups() - - def clear(self): - # override baseclass so that data is stored in FL classes - super(RGroups, self).clear() - self._setFLGroups() - - def pop(self, key): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).pop(key) - self._setFLGroups() - return i - - def popitem(self): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).popitem() - self._setFLGroups() - return i - - def setdefault(self, key, value=None): - # override baseclass so that data is stored in FL classes - i = super(RGroups, self).setdefault(key, value) - self._setFLGroups() - return i - - -class RKerning(BaseKerning): - - """RoboFab wrapper for FL Kerning data""" - - _title = "FLKerning" - - def __setitem__(self, pair, value): - if not isinstance(pair, tuple): - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if len(pair) != 2: - raise RoboFabError, 'kerning pair must be a tuple: (left, right)' - else: - if value == 0: - if self._kerning.get(pair) is not None: - #see note about setting kerning values to 0 below - self._setFLKerning(pair, 0) - del self._kerning[pair] - else: - #self._kerning[pair] = value - self._setFLKerning(pair, value) - - def _setFLKerning(self, pair, value): - # write a pair back into the font - # - # this is fairly speedy, but setting a pair to 0 is roughly - # 2-3 times slower than setting a real value. this is because - # of all the hoops that must be jumped through to keep FL - # from storing kerning pairs with a value of 0. - parentFont = self.getParent().naked() - left = parentFont[pair[0]] - right = parentFont.FindGlyph(pair[1]) - # the left glyph doesn not exist - if left is None: - return - # the right glyph doesn not exist - if right == -1: - return - self._kerning[pair] = value - leftName = pair[0] - value = int(round(value)) - # pairs set to 0 need to be handled carefully. FL will allow - # for pairs to have a value of 0 (!?), so we must catch them - # when they pop up and make sure that the pair is actually - # removed from the font. - if value == 0: - foundPair = False - # if the value is 0, we don't need to construct a pair - # we just need to make sure that the pair is not in the list - pairs = [] - # so, go through all the pairs and add them to a new list - for flPair in left.kerning: - # we have found the pair. flag it. - if flPair.key == right: - foundPair = True - # not the pair. add it to the list. - else: - pairs.append((flPair.key, flPair.value)) - # if we found it, write it back to the glyph. - if foundPair: - left.kerning = [] - for p in pairs: - new = KerningPair(p[0], p[1]) - left.kerning.append(new) - else: - # non-zero pairs are a bit easier to handle - # we just need to look to see if the pair exists - # if so, change the value and stop the loop. - # if not, add a new pair to the glyph - self._kerning[pair] = value - foundPair = False - for flPair in left.kerning: - if flPair.key == right: - flPair.value = value - foundPair = True - break - if not foundPair: - p = KerningPair(right, value) - left.kerning.append(p) - - def update(self, kerningDict): - """replace kerning data with the data in the given kerningDict""" - # override base class here for speed - parentFont = self.getParent().naked() - # add existing data to the new kerning dict is not being replaced - for pair in self.keys(): - if not kerningDict.has_key(pair): - kerningDict[pair] = self._kerning[pair] - # now clear the existing kerning to make sure that - # all the kerning in residing in the glyphs is gone - self.clear() - self._kerning = kerningDict - kDict = {} - # nest the pairs into a dict keyed by the left glyph - # {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...} - for left, right in kerningDict.keys(): - value = kerningDict[left, right] - if not left in kDict: - kDict[left] = {} - kDict[left][right] = value - for left in kDict.keys(): - leftGlyph = parentFont[left] - if leftGlyph is not None: - for right in kDict[left].keys(): - value = kDict[left][right] - if value != 0: - rightIndex = parentFont.FindGlyph(right) - if rightIndex != -1: - p = KerningPair(rightIndex, value) - leftGlyph.kerning.append(p) - - def clear(self): - """clear all kerning""" - # override base class here for speed - self._kerning = {} - for glyph in self.getParent().naked().glyphs: - glyph.kerning = [] - - def __add__(self, other): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from sets import Set - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - k = Set(self.keys()) | Set(other.keys()) - for key in k: - new[key] = self.get(key, 0) + other.get(key, 0) - return new - - def __sub__(self, other): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from sets import Set - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - k = Set(self.keys()) | Set(other.keys()) - for key in k: - new[key] = self.get(key, 0) - other.get(key, 0) - return new - - def __mul__(self, factor): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - from robofab.objects.objectsRF import RKerning as _RKerning - new = _RKerning() - for name, value in self.items(): - new[name] = value * factor - return new - - __rmul__ = __mul__ - - def __div__(self, factor): - """Math operations on FL Kerning objects return RF Kerning objects - as they need to be orphaned objects and FL can't deal with that.""" - if factor == 0: - raise ZeroDivisionError - return self.__mul__(1.0/factor) - - -class RLib(BaseLib): - - """RoboFab wrapper for FL lib""" - - # XXX: As of FL 4.6 the customdata field in glyph objects is busted. - # storing anything there causes the glyph to become uneditable. - # however, the customdata field in font objects is stable. - - def __init__(self, aDict): - self.update(aDict) - - def __setitem__(self, key, value): - # override baseclass so that data is stored in customdata field - super(RLib, self).__setitem__(key, value) - self._stashLib() - - def __delitem__(self, key): - # override baseclass so that data is stored in customdata field - super(RLib, self).__delitem__(key) - self._stashLib() - - def _stashLib(self): - # write the plist into the customdata field of the FL object - if self.getParent() is None: - return - if not self: - data = None - elif len(self) == 1 and "org.robofab.fontlab.customdata" in self: - data = self["org.robofab.fontlab.customdata"].data - else: - f = StringIO() - writePlist(self, f) - data = f.getvalue() - f.close() - parent = self.getParent() - parent.naked().customdata = data - - def update(self, aDict): - # override baseclass so that data is stored in customdata field - super(RLib, self).update(aDict) - self._stashLib() - - def clear(self): - # override baseclass so that data is stored in customdata field - super(RLib, self).clear() - self._stashLib() - - def pop(self, key): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).pop(key) - self._stashLib() - return i - - def popitem(self): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).popitem() - self._stashLib() - return i - - def setdefault(self, key, value=None): - # override baseclass so that data is stored in customdata field - i = super(RLib, self).setdefault(key, value) - self._stashLib() - return i - - -def _infoMapDict(**kwargs): - default = dict( - nakedAttribute=None, - type=None, - requiresSetNum=False, - masterSpecific=False, - libLocation=None, - specialGetSet=False - ) - default.update(kwargs) - return default - -def _flipDict(d): - f = {} - for k, v in d.items(): - f[v] = k - return f - -_styleMapStyleName_fromFL = { - 64 : "regular", - 1 : "italic", - 32 : "bold", - 33 : "bold italic" -} -_styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL) - -_postscriptWindowsCharacterSet_fromFL = { - 0 : 1, - 1 : 2, - 2 : 3, - 77 : 4, - 128 : 5, - 129 : 6, - 130 : 7, - 134 : 8, - 136 : 9, - 161 : 10, - 162 : 11, - 163 : 12, - 177 : 13, - 178 : 14, - 186 : 15, - 200 : 16, - 204 : 17, - 222 : 18, - 238 : 19, - 255 : 20, -} -_postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL) - -_openTypeOS2Type_toFL = { - 1 : 0x0002, - 2 : 0x0004, - 3 : 0x0008, - 8 : 0x0100, - 9 : 0x0200, -} -_openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL) - -_openTypeOS2WidthClass_fromFL = { - "Ultra-condensed" : 1, - "Extra-condensed" : 2, - "Condensed" : 3, - "Semi-condensed" : 4, - "Medium (normal)" : 5, - "Semi-expanded" : 6, - "Expanded" : 7, - "Extra-expanded" : 8, - "Ultra-expanded" : 9, -} -_openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL) - -_postscriptHintAttributes = set(( - "postscriptBlueValues", - "postscriptOtherBlues", - "postscriptFamilyBlues", - "postscriptFamilyOtherBlues", - "postscriptStemSnapH", - "postscriptStemSnapV", -)) - - -class RInfo(BaseInfo): - - """RoboFab wrapper for FL Font Info""" - - _title = "FLInfo" - - _ufoToFLAttrMapping = { - "familyName" : _infoMapDict(valueType=str, nakedAttribute="family_name"), - "styleName" : _infoMapDict(valueType=str, nakedAttribute="style_name"), - "styleMapFamilyName" : _infoMapDict(valueType=str, nakedAttribute="menu_name"), - "styleMapStyleName" : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True), - "versionMajor" : _infoMapDict(valueType=int, nakedAttribute="version_major"), - "versionMinor" : _infoMapDict(valueType=int, nakedAttribute="version_minor"), - "year" : _infoMapDict(valueType=int, nakedAttribute="year"), - "copyright" : _infoMapDict(valueType=str, nakedAttribute="copyright"), - "trademark" : _infoMapDict(valueType=str, nakedAttribute="trademark"), - "unitsPerEm" : _infoMapDict(valueType=int, nakedAttribute="upm"), - "descender" : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True), - "xHeight" : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True), - "capHeight" : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True), - "ascender" : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True), - "italicAngle" : _infoMapDict(valueType=float, nakedAttribute="italic_angle"), - "note" : _infoMapDict(valueType=str, nakedAttribute="note"), - "openTypeHeadCreated" : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values - "openTypeHeadLowestRecPPEM" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"), - "openTypeHeadFlags" : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface. - "openTypeHheaAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"), - "openTypeHheaDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"), - "openTypeHheaLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"), - "openTypeHheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeHheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeHheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeNameDesigner" : _infoMapDict(valueType=str, nakedAttribute="designer"), - "openTypeNameDesignerURL" : _infoMapDict(valueType=str, nakedAttribute="designer_url"), - "openTypeNameManufacturer" : _infoMapDict(valueType=str, nakedAttribute="source"), - "openTypeNameManufacturerURL" : _infoMapDict(valueType=str, nakedAttribute="vendor_url"), - "openTypeNameLicense" : _infoMapDict(valueType=str, nakedAttribute="license"), - "openTypeNameLicenseURL" : _infoMapDict(valueType=str, nakedAttribute="license_url"), - "openTypeNameVersion" : _infoMapDict(valueType=str, nakedAttribute="tt_version"), - "openTypeNameUniqueID" : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"), - "openTypeNameDescription" : _infoMapDict(valueType=str, nakedAttribute="notice"), - "openTypeNamePreferredFamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"), - "openTypeNamePreferredSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"), - "openTypeNameCompatibleFullName" : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"), - "openTypeNameSampleText" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeNameWWSFamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeNameWWSSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), - "openTypeOS2WidthClass" : _infoMapDict(valueType=int, nakedAttribute="width"), - "openTypeOS2WeightClass" : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True), - "openTypeOS2Selection" : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0 - "openTypeOS2VendorID" : _infoMapDict(valueType=str, nakedAttribute="vendor"), - "openTypeOS2Panose" : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True), - "openTypeOS2FamilyClass" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True), - "openTypeOS2UnicodeRanges" : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"), - "openTypeOS2CodePageRanges" : _infoMapDict(valueType="intList", nakedAttribute="codepages"), - "openTypeOS2TypoAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"), - "openTypeOS2TypoDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"), - "openTypeOS2TypoLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"), - "openTypeOS2WinAscent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"), - "openTypeOS2WinDescent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True), - "openTypeOS2Type" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True), - "openTypeOS2SubscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"), - "openTypeOS2SubscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"), - "openTypeOS2SubscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"), - "openTypeOS2SubscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"), - "openTypeOS2SuperscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"), - "openTypeOS2SuperscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"), - "openTypeOS2SuperscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"), - "openTypeOS2SuperscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"), - "openTypeOS2StrikeoutSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"), - "openTypeOS2StrikeoutPosition" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"), - "openTypeVheaVertTypoAscender" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaVertTypoDescender" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaVertTypoLineGap" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), - "openTypeVheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), - "postscriptFontName" : _infoMapDict(valueType=str, nakedAttribute="font_name"), - "postscriptFullName" : _infoMapDict(valueType=str, nakedAttribute="full_name"), - "postscriptSlantAngle" : _infoMapDict(valueType=float, nakedAttribute="slant_angle"), - "postscriptUniqueID" : _infoMapDict(valueType=int, nakedAttribute="unique_id"), - "postscriptUnderlineThickness" : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"), - "postscriptUnderlinePosition" : _infoMapDict(valueType=int, nakedAttribute="underline_position"), - "postscriptIsFixedPitch" : _infoMapDict(valueType="boolint", nakedAttribute="is_fixed_pitch"), - "postscriptBlueValues" : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True), - "postscriptOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True), - "postscriptFamilyBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True), - "postscriptFamilyOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True), - "postscriptStemSnapH" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True), - "postscriptStemSnapV" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True), - "postscriptBlueFuzz" : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True), - "postscriptBlueShift" : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True), - "postscriptBlueScale" : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True), - "postscriptForceBold" : _infoMapDict(valueType="boolint", nakedAttribute="force_bold", masterSpecific=True), - "postscriptDefaultWidthX" : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True), - "postscriptNominalWidthX" : _infoMapDict(valueType=int, nakedAttribute=None), - "postscriptWeightName" : _infoMapDict(valueType=str, nakedAttribute="weight"), - "postscriptDefaultCharacter" : _infoMapDict(valueType=str, nakedAttribute="default_character"), - "postscriptWindowsCharacterSet" : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True), - "macintoshFONDFamilyID" : _infoMapDict(valueType=int, nakedAttribute="fond_id"), - "macintoshFONDName" : _infoMapDict(valueType=str, nakedAttribute="apple_name"), - } - _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh. - - def __init__(self, font): - super(RInfo, self).__init__() - self._object = font - - def _environmentSetAttr(self, attr, value): - # special fontlab workarounds - if attr == "width": - warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) - attr = "openTypeOS2WidthClass" - if attr == "openTypeOS2WidthClass": - if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL: - print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value - self._object.width = value - else: - self._object.width = _openTypeOS2WidthClass_toFL[value] - return - # get the attribute data - data = self._ufoToFLAttrMapping[attr] - flAttr = data["nakedAttribute"] - valueType = data["valueType"] - masterSpecific = data["masterSpecific"] - requiresSetNum = data["requiresSetNum"] - specialGetSet = data["specialGetSet"] - # warn about setting attributes not supported by FL - if flAttr is None: - print "The attribute %s is not supported by FontLab. This data will not be set." % attr - return - # make sure that the value is the proper type for FL - if valueType == "intList": - value = [int(i) for i in value] - elif valueType == "boolint": - value = int(bool(value)) - elif valueType == str: - if value is None: - value = "" - value = value.encode(LOCAL_ENCODING) - elif valueType == int and not isinstance(value, int): - value = int(round(value)) - elif not isinstance(value, valueType): - value = valueType(value) - # handle postscript hint bug in FL - if attr in _postscriptHintAttributes: - value = self._handlePSHintBug(attr, value) - # handle special cases - if specialGetSet: - attr = "_set_%s" % attr - method = getattr(self, attr) - return method(value) - # set the value - obj = self._object - if len(flAttr.split(".")) > 1: - flAttrList = flAttr.split(".") - for i in flAttrList[:-1]: - obj = getattr(obj, i) - flAttr = flAttrList[-1] - ## set the foo_num attribute if necessary - if requiresSetNum: - numAttr = flAttr + "_num" - setattr(obj, numAttr, len(value)) - ## set master 0 if the data is master specific - if masterSpecific: - subObj = getattr(obj, flAttr) - if valueType == "intList": - for index, v in enumerate(value): - subObj[0][index] = v - else: - subObj[0] = value - ## otherwise use a regular set - else: - setattr(obj, flAttr, value) - - def _environmentGetAttr(self, attr): - # special fontlab workarounds - if attr == "width": - warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) - attr = "openTypeOS2WidthClass" - if attr == "openTypeOS2WidthClass": - value = self._object.width - if value not in _openTypeOS2WidthClass_fromFL: - print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value - return - else: - return _openTypeOS2WidthClass_fromFL[value] - # get the attribute data - data = self._ufoToFLAttrMapping[attr] - flAttr = data["nakedAttribute"] - valueType = data["valueType"] - masterSpecific = data["masterSpecific"] - specialGetSet = data["specialGetSet"] - # warn about setting attributes not supported by FL - if flAttr is None: - if not _IN_UFO_EXPORT: - print "The attribute %s is not supported by FontLab." % attr - return - # handle special cases - if specialGetSet: - attr = "_get_%s" % attr - method = getattr(self, attr) - return method() - # get the value - if len(flAttr.split(".")) > 1: - flAttrList = flAttr.split(".") - obj = self._object - for i in flAttrList: - obj = getattr(obj, i) - value = obj - else: - value = getattr(self._object, flAttr) - # grab the first master value if necessary - if masterSpecific: - value = value[0] - # convert if necessary - if valueType == "intList": - value = [int(i) for i in value] - elif valueType == "boolint": - value = bool(value) - elif valueType == str: - if value is None: - pass - else: - value = unicode(value, LOCAL_ENCODING) - elif not isinstance(value, valueType): - value = valueType(value) - return value - - # ------------------------------ - # individual attribute overrides - # ------------------------------ - - # styleMapStyleName - - def _get_styleMapStyleName(self): - return _styleMapStyleName_fromFL[self._object.font_style] - - def _set_styleMapStyleName(self, value): - value = _styleMapStyleName_toFL[value] - self._object.font_style = value - -# # openTypeHeadCreated -# -# # fontlab epoch: 1969-12-31 19:00:00 -# -# def _get_openTypeHeadCreated(self): -# value = self._object.ttinfo.head_creation -# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) -# delta = datetime.timedelta(seconds=value[0]) -# t = epoch - delta -# string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2)) -# return string -# -# def _set_openTypeHeadCreated(self, value): -# date, time = value.split(" ") -# year, month, day = [int(i) for i in date.split("-")] -# hour, minute, second = [int(i) for i in time.split(":")] -# value = datetime.datetime(year, month, day, hour, minute, second) -# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) -# delta = epoch - value -# seconds = delta.seconds -# self._object.ttinfo.head_creation[0] = seconds - - # openTypeOS2WeightClass - - def _get_openTypeOS2WeightClass(self): - value = self._object.weight_code - if value == -1: - value = None - return value - - def _set_openTypeOS2WeightClass(self, value): - self._object.weight_code = value - - # openTypeOS2WinDescent - - def _get_openTypeOS2WinDescent(self): - return self._object.ttinfo.os2_us_win_descent - - def _set_openTypeOS2WinDescent(self, value): - if value < 0: - warn("FontLab can only handle positive values for openTypeOS2WinDescent.") - value = abs(value) - self._object.ttinfo.os2_us_win_descent = value - - # openTypeOS2Type - - def _get_openTypeOS2Type(self): - value = self._object.ttinfo.os2_fs_type - intList = [] - for bit, bitNumber in _openTypeOS2Type_fromFL.items(): - if value & bit: - intList.append(bitNumber) - return intList - - def _set_openTypeOS2Type(self, values): - value = 0 - for bitNumber in values: - bit = _openTypeOS2Type_toFL[bitNumber] - value = value | bit - self._object.ttinfo.os2_fs_type = value - - # openTypeOS2Panose - - def _get_openTypeOS2Panose(self): - return [i for i in self._object.panose] - - def _set_openTypeOS2Panose(self, values): - for index, value in enumerate(values): - self._object.panose[index] = value - - # openTypeOS2FamilyClass - - def _get_openTypeOS2FamilyClass(self): - value = self._object.ttinfo.os2_s_family_class - for classID in range(15): - classValue = classID * 256 - if classValue > value: - classID -= 1 - classValue = classID * 256 - break - subclassID = value - classValue - return [classID, subclassID] - - def _set_openTypeOS2FamilyClass(self, values): - classID, subclassID = values - classID = classID * 256 - value = classID + subclassID - self._object.ttinfo.os2_s_family_class = value - - # postscriptWindowsCharacterSet - - def _get_postscriptWindowsCharacterSet(self): - value = self._object.ms_charset - value = _postscriptWindowsCharacterSet_fromFL[value] - return value - - def _set_postscriptWindowsCharacterSet(self, value): - value = _postscriptWindowsCharacterSet_toFL[value] - self._object.ms_charset = value - - # ----------------- - # FL bug workaround - # ----------------- - - def _handlePSHintBug(self, attribute, values): - """Function to handle problems with FontLab not allowing the max number of - alignment zones to be set to the max number. - Input: the name of the zones and the values to be set - Output: a warning when there are too many values to be set - and the max values which FontLab will allow. - """ - originalValues = values - truncatedLength = None - if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"): - if len(values) > 10: - values = values[:10] - truncatedLength = 10 - elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"): - if len(values) > 12: - values = values[:12] - truncatedLength = 12 - elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"): - if len(values) > 8: - values = values[:8] - truncatedLength = 8 - if truncatedLength is not None: - print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:])) - return values - - -class RFeatures(BaseFeatures): - - _title = "FLFeatures" - - def __init__(self, font): - super(RFeatures, self).__init__() - self._object = font - - def _get_text(self): - naked = self._object - features = [] - if naked.ot_classes: - features.append(_normalizeLineEndings(naked.ot_classes)) - for feature in naked.features: - features.append(_normalizeLineEndings(feature.value)) - return "".join(features) - - def _set_text(self, value): - classes, features = splitFeaturesForFontLab(value) - naked = self._object - naked.ot_classes = classes - naked.features.clean() - for featureName, featureText in features: - f = Feature(featureName, featureText) - naked.features.append(f) - - text = property(_get_text, _set_text, doc="raw feature text.") - |