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