macUtils.py revision 7842e56b97ce677b83bdab09cda48bc2d89ac75a
1"""ttLib.macUtils.py -- Various Mac-specific stuff.""" 2 3 4import os 5if os.name <> "mac": 6 raise ImportError, "This module is Mac-only!" 7 8import Res, macfs 9import cStringIO 10 11 12def getSFNTResIndices(path): 13 """Determine whether a file has a resource fork or not.""" 14 fss = macfs.FSSpec(path) 15 try: 16 resref = Res.FSpOpenResFile(fss, 1) # read only 17 except Res.Error: 18 return [] 19 Res.UseResFile(resref) 20 numSFNTs = Res.Count1Resources('sfnt') 21 Res.CloseResFile(resref) 22 return range(1, numSFNTs + 1) 23 24 25def openTTFonts(path): 26 """Given a pathname, return a list of TTFont objects. In the case 27 of a flat TTF/OTF file, the list will contain just one font object; 28 but in the case of a Mac font suitcase it will contain as many 29 font objects as there are sfnt resources in the file. 30 """ 31 from fontTools import ttLib 32 fonts = [] 33 sfnts = getSFNTResIndices(path) 34 if not sfnts: 35 fonts.append(ttLib.TTFont(path)) 36 else: 37 for index in sfnts: 38 fonts.append(ttLib.TTFont(path, index)) 39 if not fonts: 40 raise ttLib.TTLibError, "no fonts found in file '%s'" % path 41 return fonts 42 43 44class ProgressBar: 45 46 def __init__(self, title, maxval=100): 47 import EasyDialogs 48 self.bar = EasyDialogs.ProgressBar(title, maxval=maxval) 49 50 def set(self, val, maxval=None): 51 if maxval <> None: 52 self.bar.maxval = maxval 53 self.bar.set(val) 54 55 def increment(self, val=1): 56 self.bar.inc(val) 57 58 def setlabel(self, text): 59 self.bar.label(text) 60 61 def close(self): 62 self.bar.d.HideWindow() 63 del self.bar 64 65 66class SFNTResourceReader: 67 68 """Simple (Mac-only) read-only file wrapper for 'sfnt' resources.""" 69 70 def __init__(self, path, res_name_or_index): 71 fss = macfs.FSSpec(path) 72 resref = Res.FSpOpenResFile(fss, 1) # read-only 73 Res.UseResFile(resref) 74 if type(res_name_or_index) == type(""): 75 res = Res.Get1NamedResource('sfnt', res_name_or_index) 76 else: 77 res = Res.Get1IndResource('sfnt', res_name_or_index) 78 self.file = cStringIO.StringIO(res.data) 79 Res.CloseResFile(resref) 80 self.name = path 81 82 def __getattr__(self, attr): 83 # cheap inheritance 84 return getattr(self.file, attr) 85 86 87class SFNTResourceWriter: 88 89 """Simple (Mac-only) file wrapper for 'sfnt' resources.""" 90 91 def __init__(self, path, ttFont, res_id=None): 92 self.file = cStringIO.StringIO() 93 self.name = path 94 self.closed = 0 95 fullname = ttFont['name'].getname(4, 1, 0) # Full name, mac, default encoding 96 familyname = ttFont['name'].getname(1, 1, 0) # Fam. name, mac, default encoding 97 psname = ttFont['name'].getname(6, 1, 0) # PostScript name, etc. 98 if fullname is None or fullname is None or psname is None: 99 from fontTools import ttLib 100 raise ttLib.TTLibError, "can't make 'sfnt' resource, no Macintosh 'name' table found" 101 self.fullname = fullname.string 102 self.familyname = familyname.string 103 self.psname = psname.string 104 if self.familyname <> self.psname[:len(self.familyname)]: 105 # ugh. force fam name to be the same as first part of ps name, 106 # fondLib otherwise barfs. 107 for i in range(min(len(self.psname), len(self.familyname))): 108 if self.familyname[i] <> self.psname[i]: 109 break 110 self.familyname = self.psname[:i] 111 112 self.ttFont = ttFont 113 self.res_id = res_id 114 fss = macfs.FSSpec(self.name) 115 if os.path.exists(self.name): 116 os.remove(self.name) 117 Res.FSpCreateResFile(fss, 'DMOV', 'FFIL', 0) 118 self.resref = Res.FSpOpenResFile(fss, 3) # exclusive read/write permission 119 120 def close(self): 121 if self.closed: 122 return 123 Res.UseResFile(self.resref) 124 try: 125 res = Res.Get1NamedResource('sfnt', self.fullname) 126 except Res.Error: 127 pass 128 else: 129 res.RemoveResource() 130 res = Res.Resource(self.file.getvalue()) 131 if self.res_id is None: 132 self.res_id = Res.Unique1ID('sfnt') 133 res.AddResource('sfnt', self.res_id, self.fullname) 134 res.ChangedResource() 135 136 self.createFond() 137 del self.ttFont 138 Res.CloseResFile(self.resref) 139 self.file.close() 140 self.closed = 1 141 142 def createFond(self): 143 fond_res = Res.Resource("") 144 fond_res.AddResource('FOND', self.res_id, self.fullname) 145 146 from fontTools import fondLib 147 fond = fondLib.FontFamily(fond_res, "w") 148 149 fond.ffFirstChar = 0 150 fond.ffLastChar = 255 151 fond.fondClass = 0 152 fond.fontAssoc = [(0, 0, self.res_id)] 153 fond.ffFlags = 20480 # XXX ??? 154 fond.ffIntl = (0, 0) 155 fond.ffLeading = 0 156 fond.ffProperty = (0, 0, 0, 0, 0, 0, 0, 0, 0) 157 fond.ffVersion = 0 158 fond.glyphEncoding = {} 159 if self.familyname == self.psname: 160 fond.styleIndices = (1,) * 48 # uh-oh, fondLib is too dumb. 161 else: 162 fond.styleIndices = (2,) * 48 163 fond.styleStrings = [] 164 fond.boundingBoxes = None 165 fond.ffFamID = self.res_id 166 fond.changed = 1 167 fond.glyphTableOffset = 0 168 fond.styleMappingReserved = 0 169 170 # calc: 171 scale = 4096.0 / self.ttFont['head'].unitsPerEm 172 fond.ffAscent = scale * self.ttFont['hhea'].ascent 173 fond.ffDescent = scale * self.ttFont['hhea'].descent 174 fond.ffWidMax = scale * self.ttFont['hhea'].advanceWidthMax 175 176 fond.ffFamilyName = self.familyname 177 fond.psNames = {0: self.psname} 178 179 fond.widthTables = {} 180 fond.kernTables = {} 181 cmap = self.ttFont['cmap'].getcmap(1, 0) 182 if cmap: 183 names = {} 184 for code, name in cmap.cmap.items(): 185 names[name] = code 186 if self.ttFont.has_key('kern'): 187 kern = self.ttFont['kern'].getkern(0) 188 if kern: 189 fondkerning = [] 190 for (left, right), value in kern.kernTable.items(): 191 if names.has_key(left) and names.has_key(right): 192 fondkerning.append((names[left], names[right], scale * value)) 193 fondkerning.sort() 194 fond.kernTables = {0: fondkerning} 195 if self.ttFont.has_key('hmtx'): 196 hmtx = self.ttFont['hmtx'] 197 fondwidths = [2048] * 256 + [0, 0] # default width, + plus two zeros. 198 for name, (width, lsb) in hmtx.metrics.items(): 199 if names.has_key(name): 200 fondwidths[names[name]] = scale * width 201 fond.widthTables = {0: fondwidths} 202 fond.save() 203 204 def __del__(self): 205 if not self.closed: 206 self.close() 207 208 def __getattr__(self, attr): 209 # cheap inheritance 210 return getattr(self.file, attr) 211 212 213