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