blob: 5e1f0cbd133ef636c399d8b93bca0980a9e62e7e [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"):
Just7842e561999-12-16 21:34:53 +00006 raise ImportError, "This module is Mac-only!"
7
Just7842e561999-12-16 21:34:53 +00008import cStringIO
jvr83eca432002-06-06 19:58:18 +00009try:
10 from Carbon import Res
11except ImportError:
12 import Res
Just7842e561999-12-16 21:34:53 +000013
14
jvrd4079732003-08-22 18:56:01 +000015def 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
Just7842e561999-12-16 21:34:53 +000025def getSFNTResIndices(path):
26 """Determine whether a file has a resource fork or not."""
Just7842e561999-12-16 21:34:53 +000027 try:
jvrd4079732003-08-22 18:56:01 +000028 resref = MyOpenResFile(path)
Just7842e561999-12-16 21:34:53 +000029 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
37def 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
Just7842e561999-12-16 21:34:53 +000056class SFNTResourceReader:
57
58 """Simple (Mac-only) read-only file wrapper for 'sfnt' resources."""
59
60 def __init__(self, path, res_name_or_index):
jvrd4079732003-08-22 18:56:01 +000061 resref = MyOpenResFile(path)
Just7842e561999-12-16 21:34:53 +000062 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
76class 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
Just4ff3ba92000-03-14 22:59:39 +000084 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.
Just7842e561999-12-16 21:34:53 +000087 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
Just7842e561999-12-16 21:34:53 +0000103 if os.path.exists(self.name):
104 os.remove(self.name)
jvrd4079732003-08-22 18:56:01 +0000105 # XXX datafork support
106 Res.FSpCreateResFile(self.name, 'DMOV', 'FFIL', 0)
107 self.resref = Res.FSpOpenResFile(self.name, 3) # exclusive read/write permission
Just7842e561999-12-16 21:34:53 +0000108
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