Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 1 | """ttLib.macUtils.py -- Various Mac-specific stuff.""" |
| 2 | |
Behdad Esfahbod | 1ae2959 | 2014-01-14 15:07:50 +0800 | [diff] [blame^] | 3 | from __future__ import print_function, division, absolute_import |
Behdad Esfahbod | 30e691e | 2013-11-27 17:27:45 -0500 | [diff] [blame] | 4 | from fontTools.misc.py23 import * |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 5 | import sys |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 6 | import os |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 7 | if sys.platform not in ("mac", "darwin"): |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 8 | raise ImportError("This module is Mac-only!") |
Behdad Esfahbod | b92c080 | 2013-11-27 05:05:46 -0500 | [diff] [blame] | 9 | try: |
jvr | 83eca43 | 2002-06-06 19:58:18 +0000 | [diff] [blame] | 10 | from Carbon import Res |
| 11 | except ImportError: |
| 12 | import Res |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 13 | |
Behdad Esfahbod | bb0beb7 | 2013-11-27 14:37:28 -0500 | [diff] [blame] | 14 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 15 | |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 16 | def MyOpenResFile(path): |
| 17 | mode = 1 # read only |
| 18 | try: |
jvr | 91bca42 | 2012-10-18 12:49:22 +0000 | [diff] [blame] | 19 | resref = Res.FSOpenResFile(path, mode) |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 20 | except Res.Error: |
| 21 | # try data fork |
Behdad Esfahbod | bb0beb7 | 2013-11-27 14:37:28 -0500 | [diff] [blame] | 22 | resref = Res.FSOpenResourceFile(path, unicode(), mode) |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 23 | return resref |
| 24 | |
| 25 | |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 26 | def getSFNTResIndices(path): |
| 27 | """Determine whether a file has a resource fork or not.""" |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 28 | try: |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 29 | resref = MyOpenResFile(path) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 30 | except Res.Error: |
| 31 | return [] |
| 32 | Res.UseResFile(resref) |
| 33 | numSFNTs = Res.Count1Resources('sfnt') |
| 34 | Res.CloseResFile(resref) |
Behdad Esfahbod | 97dea0a | 2013-11-27 03:34:48 -0500 | [diff] [blame] | 35 | return list(range(1, numSFNTs + 1)) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 36 | |
| 37 | |
| 38 | def 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: |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 53 | raise ttLib.TTLibError("no fonts found in file '%s'" % path) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 54 | return fonts |
| 55 | |
| 56 | |
Behdad Esfahbod | e388db5 | 2013-11-28 14:26:58 -0500 | [diff] [blame] | 57 | class SFNTResourceReader(object): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 58 | |
| 59 | """Simple (Mac-only) read-only file wrapper for 'sfnt' resources.""" |
| 60 | |
| 61 | def __init__(self, path, res_name_or_index): |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 62 | resref = MyOpenResFile(path) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 63 | Res.UseResFile(resref) |
Behdad Esfahbod | ac4672e | 2013-11-27 16:44:53 -0500 | [diff] [blame] | 64 | if isinstance(res_name_or_index, basestring): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 65 | res = Res.Get1NamedResource('sfnt', res_name_or_index) |
| 66 | else: |
| 67 | res = Res.Get1IndResource('sfnt', res_name_or_index) |
Behdad Esfahbod | b92c080 | 2013-11-27 05:05:46 -0500 | [diff] [blame] | 68 | self.file = StringIO(res.data) |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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 | |
Behdad Esfahbod | e388db5 | 2013-11-28 14:26:58 -0500 | [diff] [blame] | 77 | class SFNTResourceWriter(object): |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 78 | |
| 79 | """Simple (Mac-only) file wrapper for 'sfnt' resources.""" |
| 80 | |
| 81 | def __init__(self, path, ttFont, res_id=None): |
Behdad Esfahbod | b92c080 | 2013-11-27 05:05:46 -0500 | [diff] [blame] | 82 | self.file = StringIO() |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 83 | self.name = path |
| 84 | self.closed = 0 |
Just | 4ff3ba9 | 2000-03-14 22:59:39 +0000 | [diff] [blame] | 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. |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 88 | if fullname is None or fullname is None or psname is None: |
| 89 | from fontTools import ttLib |
Behdad Esfahbod | cd5aad9 | 2013-11-27 02:42:28 -0500 | [diff] [blame] | 90 | raise ttLib.TTLibError("can't make 'sfnt' resource, no Macintosh 'name' table found") |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 91 | self.fullname = fullname.string |
| 92 | self.familyname = familyname.string |
| 93 | self.psname = psname.string |
Behdad Esfahbod | 180ace6 | 2013-11-27 02:40:30 -0500 | [diff] [blame] | 94 | if self.familyname != self.psname[:len(self.familyname)]: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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))): |
Behdad Esfahbod | 180ace6 | 2013-11-27 02:40:30 -0500 | [diff] [blame] | 98 | if self.familyname[i] != self.psname[i]: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 99 | break |
| 100 | self.familyname = self.psname[:i] |
| 101 | |
| 102 | self.ttFont = ttFont |
| 103 | self.res_id = res_id |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 104 | if os.path.exists(self.name): |
| 105 | os.remove(self.name) |
jvr | d407973 | 2003-08-22 18:56:01 +0000 | [diff] [blame] | 106 | # XXX datafork support |
| 107 | Res.FSpCreateResFile(self.name, 'DMOV', 'FFIL', 0) |
jvr | 91bca42 | 2012-10-18 12:49:22 +0000 | [diff] [blame] | 108 | self.resref = Res.FSOpenResFile(self.name, 3) # exclusive read/write permission |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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: |
Behdad Esfahbod | 32c10ee | 2013-11-27 17:46:17 -0500 | [diff] [blame] | 161 | scale = 4096 / self.ttFont['head'].unitsPerEm |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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 |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 176 | if 'kern' in self.ttFont: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 177 | kern = self.ttFont['kern'].getkern(0) |
| 178 | if kern: |
| 179 | fondkerning = [] |
| 180 | for (left, right), value in kern.kernTable.items(): |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 181 | if left in names and right in names: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 182 | fondkerning.append((names[left], names[right], scale * value)) |
| 183 | fondkerning.sort() |
| 184 | fond.kernTables = {0: fondkerning} |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 185 | if 'hmtx' in self.ttFont: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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(): |
Behdad Esfahbod | bc5e1cb | 2013-11-27 02:33:03 -0500 | [diff] [blame] | 189 | if name in names: |
Just | 7842e56 | 1999-12-16 21:34:53 +0000 | [diff] [blame] | 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 | |