blob: 85a375c22a6d9490eeaec07dcbf4b1e295d7dd48 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""ttLib.macUtils.py -- Various Mac-specific stuff."""
2
jvrd4079732003-08-22 18:56:01 +00003import sys
Just7842e561999-12-16 21:34:53 +00004import os
jvrd4079732003-08-22 18:56:01 +00005if sys.platform not in ("mac", "darwin"):
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -05006 raise ImportError("This module is Mac-only!")
Just7842e561999-12-16 21:34:53 +00007
Behdad Esfahbodb92c0802013-11-27 05:05:46 -05008try:
9 from cStringIO import StringIO
10except ImportError:
11 from io import StringIO
jvr83eca432002-06-06 19:58:18 +000012try:
13 from Carbon import Res
14except ImportError:
15 import Res
Just7842e561999-12-16 21:34:53 +000016
17
jvrd4079732003-08-22 18:56:01 +000018def MyOpenResFile(path):
19 mode = 1 # read only
20 try:
jvr91bca422012-10-18 12:49:22 +000021 resref = Res.FSOpenResFile(path, mode)
jvrd4079732003-08-22 18:56:01 +000022 except Res.Error:
23 # try data fork
24 resref = Res.FSOpenResourceFile(path, u'', mode)
25 return resref
26
27
Just7842e561999-12-16 21:34:53 +000028def getSFNTResIndices(path):
29 """Determine whether a file has a resource fork or not."""
Just7842e561999-12-16 21:34:53 +000030 try:
jvrd4079732003-08-22 18:56:01 +000031 resref = MyOpenResFile(path)
Just7842e561999-12-16 21:34:53 +000032 except Res.Error:
33 return []
34 Res.UseResFile(resref)
35 numSFNTs = Res.Count1Resources('sfnt')
36 Res.CloseResFile(resref)
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -050037 return list(range(1, numSFNTs + 1))
Just7842e561999-12-16 21:34:53 +000038
39
40def openTTFonts(path):
41 """Given a pathname, return a list of TTFont objects. In the case
42 of a flat TTF/OTF file, the list will contain just one font object;
43 but in the case of a Mac font suitcase it will contain as many
44 font objects as there are sfnt resources in the file.
45 """
46 from fontTools import ttLib
47 fonts = []
48 sfnts = getSFNTResIndices(path)
49 if not sfnts:
50 fonts.append(ttLib.TTFont(path))
51 else:
52 for index in sfnts:
53 fonts.append(ttLib.TTFont(path, index))
54 if not fonts:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050055 raise ttLib.TTLibError("no fonts found in file '%s'" % path)
Just7842e561999-12-16 21:34:53 +000056 return fonts
57
58
Just7842e561999-12-16 21:34:53 +000059class SFNTResourceReader:
60
61 """Simple (Mac-only) read-only file wrapper for 'sfnt' resources."""
62
63 def __init__(self, path, res_name_or_index):
jvrd4079732003-08-22 18:56:01 +000064 resref = MyOpenResFile(path)
Just7842e561999-12-16 21:34:53 +000065 Res.UseResFile(resref)
Behdad Esfahbodac1b4352013-11-27 04:15:34 -050066 if isinstance(res_name_or_index, type("")):
Just7842e561999-12-16 21:34:53 +000067 res = Res.Get1NamedResource('sfnt', res_name_or_index)
68 else:
69 res = Res.Get1IndResource('sfnt', res_name_or_index)
Behdad Esfahbodb92c0802013-11-27 05:05:46 -050070 self.file = StringIO(res.data)
Just7842e561999-12-16 21:34:53 +000071 Res.CloseResFile(resref)
72 self.name = path
73
74 def __getattr__(self, attr):
75 # cheap inheritance
76 return getattr(self.file, attr)
77
78
79class SFNTResourceWriter:
80
81 """Simple (Mac-only) file wrapper for 'sfnt' resources."""
82
83 def __init__(self, path, ttFont, res_id=None):
Behdad Esfahbodb92c0802013-11-27 05:05:46 -050084 self.file = StringIO()
Just7842e561999-12-16 21:34:53 +000085 self.name = path
86 self.closed = 0
Just4ff3ba92000-03-14 22:59:39 +000087 fullname = ttFont['name'].getName(4, 1, 0) # Full name, mac, default encoding
88 familyname = ttFont['name'].getName(1, 1, 0) # Fam. name, mac, default encoding
89 psname = ttFont['name'].getName(6, 1, 0) # PostScript name, etc.
Just7842e561999-12-16 21:34:53 +000090 if fullname is None or fullname is None or psname is None:
91 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050092 raise ttLib.TTLibError("can't make 'sfnt' resource, no Macintosh 'name' table found")
Just7842e561999-12-16 21:34:53 +000093 self.fullname = fullname.string
94 self.familyname = familyname.string
95 self.psname = psname.string
Behdad Esfahbod180ace62013-11-27 02:40:30 -050096 if self.familyname != self.psname[:len(self.familyname)]:
Just7842e561999-12-16 21:34:53 +000097 # ugh. force fam name to be the same as first part of ps name,
98 # fondLib otherwise barfs.
99 for i in range(min(len(self.psname), len(self.familyname))):
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500100 if self.familyname[i] != self.psname[i]:
Just7842e561999-12-16 21:34:53 +0000101 break
102 self.familyname = self.psname[:i]
103
104 self.ttFont = ttFont
105 self.res_id = res_id
Just7842e561999-12-16 21:34:53 +0000106 if os.path.exists(self.name):
107 os.remove(self.name)
jvrd4079732003-08-22 18:56:01 +0000108 # XXX datafork support
109 Res.FSpCreateResFile(self.name, 'DMOV', 'FFIL', 0)
jvr91bca422012-10-18 12:49:22 +0000110 self.resref = Res.FSOpenResFile(self.name, 3) # exclusive read/write permission
Just7842e561999-12-16 21:34:53 +0000111
112 def close(self):
113 if self.closed:
114 return
115 Res.UseResFile(self.resref)
116 try:
117 res = Res.Get1NamedResource('sfnt', self.fullname)
118 except Res.Error:
119 pass
120 else:
121 res.RemoveResource()
122 res = Res.Resource(self.file.getvalue())
123 if self.res_id is None:
124 self.res_id = Res.Unique1ID('sfnt')
125 res.AddResource('sfnt', self.res_id, self.fullname)
126 res.ChangedResource()
127
128 self.createFond()
129 del self.ttFont
130 Res.CloseResFile(self.resref)
131 self.file.close()
132 self.closed = 1
133
134 def createFond(self):
135 fond_res = Res.Resource("")
136 fond_res.AddResource('FOND', self.res_id, self.fullname)
137
138 from fontTools import fondLib
139 fond = fondLib.FontFamily(fond_res, "w")
140
141 fond.ffFirstChar = 0
142 fond.ffLastChar = 255
143 fond.fondClass = 0
144 fond.fontAssoc = [(0, 0, self.res_id)]
145 fond.ffFlags = 20480 # XXX ???
146 fond.ffIntl = (0, 0)
147 fond.ffLeading = 0
148 fond.ffProperty = (0, 0, 0, 0, 0, 0, 0, 0, 0)
149 fond.ffVersion = 0
150 fond.glyphEncoding = {}
151 if self.familyname == self.psname:
152 fond.styleIndices = (1,) * 48 # uh-oh, fondLib is too dumb.
153 else:
154 fond.styleIndices = (2,) * 48
155 fond.styleStrings = []
156 fond.boundingBoxes = None
157 fond.ffFamID = self.res_id
158 fond.changed = 1
159 fond.glyphTableOffset = 0
160 fond.styleMappingReserved = 0
161
162 # calc:
163 scale = 4096.0 / self.ttFont['head'].unitsPerEm
164 fond.ffAscent = scale * self.ttFont['hhea'].ascent
165 fond.ffDescent = scale * self.ttFont['hhea'].descent
166 fond.ffWidMax = scale * self.ttFont['hhea'].advanceWidthMax
167
168 fond.ffFamilyName = self.familyname
169 fond.psNames = {0: self.psname}
170
171 fond.widthTables = {}
172 fond.kernTables = {}
173 cmap = self.ttFont['cmap'].getcmap(1, 0)
174 if cmap:
175 names = {}
176 for code, name in cmap.cmap.items():
177 names[name] = code
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500178 if 'kern' in self.ttFont:
Just7842e561999-12-16 21:34:53 +0000179 kern = self.ttFont['kern'].getkern(0)
180 if kern:
181 fondkerning = []
182 for (left, right), value in kern.kernTable.items():
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500183 if left in names and right in names:
Just7842e561999-12-16 21:34:53 +0000184 fondkerning.append((names[left], names[right], scale * value))
185 fondkerning.sort()
186 fond.kernTables = {0: fondkerning}
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500187 if 'hmtx' in self.ttFont:
Just7842e561999-12-16 21:34:53 +0000188 hmtx = self.ttFont['hmtx']
189 fondwidths = [2048] * 256 + [0, 0] # default width, + plus two zeros.
190 for name, (width, lsb) in hmtx.metrics.items():
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500191 if name in names:
Just7842e561999-12-16 21:34:53 +0000192 fondwidths[names[name]] = scale * width
193 fond.widthTables = {0: fondwidths}
194 fond.save()
195
196 def __del__(self):
197 if not self.closed:
198 self.close()
199
200 def __getattr__(self, attr):
201 # cheap inheritance
202 return getattr(self.file, attr)
203
204