blob: d565528c5530c984d139540df73655c89848a029 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""ttLib.macUtils.py -- Various Mac-specific stuff."""
2
Behdad Esfahbod1ae29592014-01-14 15:07:50 +08003from __future__ import print_function, division, absolute_import
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05004from fontTools.misc.py23 import *
jvrd4079732003-08-22 18:56:01 +00005import sys
Just7842e561999-12-16 21:34:53 +00006import os
jvrd4079732003-08-22 18:56:01 +00007if sys.platform not in ("mac", "darwin"):
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -05008 raise ImportError("This module is Mac-only!")
Behdad Esfahbodb92c0802013-11-27 05:05:46 -05009try:
jvr83eca432002-06-06 19:58:18 +000010 from Carbon import Res
11except ImportError:
12 import Res
Just7842e561999-12-16 21:34:53 +000013
Behdad Esfahbodbb0beb72013-11-27 14:37:28 -050014
Just7842e561999-12-16 21:34:53 +000015
jvrd4079732003-08-22 18:56:01 +000016def MyOpenResFile(path):
17 mode = 1 # read only
18 try:
jvr91bca422012-10-18 12:49:22 +000019 resref = Res.FSOpenResFile(path, mode)
jvrd4079732003-08-22 18:56:01 +000020 except Res.Error:
21 # try data fork
Behdad Esfahbodbb0beb72013-11-27 14:37:28 -050022 resref = Res.FSOpenResourceFile(path, unicode(), mode)
jvrd4079732003-08-22 18:56:01 +000023 return resref
24
25
Just7842e561999-12-16 21:34:53 +000026def getSFNTResIndices(path):
27 """Determine whether a file has a resource fork or not."""
Just7842e561999-12-16 21:34:53 +000028 try:
jvrd4079732003-08-22 18:56:01 +000029 resref = MyOpenResFile(path)
Just7842e561999-12-16 21:34:53 +000030 except Res.Error:
31 return []
32 Res.UseResFile(resref)
33 numSFNTs = Res.Count1Resources('sfnt')
34 Res.CloseResFile(resref)
Behdad Esfahbod97dea0a2013-11-27 03:34:48 -050035 return list(range(1, numSFNTs + 1))
Just7842e561999-12-16 21:34:53 +000036
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:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050053 raise ttLib.TTLibError("no fonts found in file '%s'" % path)
Just7842e561999-12-16 21:34:53 +000054 return fonts
55
56
Behdad Esfahbode388db52013-11-28 14:26:58 -050057class SFNTResourceReader(object):
Just7842e561999-12-16 21:34:53 +000058
59 """Simple (Mac-only) read-only file wrapper for 'sfnt' resources."""
60
61 def __init__(self, path, res_name_or_index):
jvrd4079732003-08-22 18:56:01 +000062 resref = MyOpenResFile(path)
Just7842e561999-12-16 21:34:53 +000063 Res.UseResFile(resref)
Behdad Esfahbodac4672e2013-11-27 16:44:53 -050064 if isinstance(res_name_or_index, basestring):
Just7842e561999-12-16 21:34:53 +000065 res = Res.Get1NamedResource('sfnt', res_name_or_index)
66 else:
67 res = Res.Get1IndResource('sfnt', res_name_or_index)
Behdad Esfahbodb92c0802013-11-27 05:05:46 -050068 self.file = StringIO(res.data)
Just7842e561999-12-16 21:34:53 +000069 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 Esfahbode388db52013-11-28 14:26:58 -050077class SFNTResourceWriter(object):
Just7842e561999-12-16 21:34:53 +000078
79 """Simple (Mac-only) file wrapper for 'sfnt' resources."""
80
81 def __init__(self, path, ttFont, res_id=None):
Behdad Esfahbodb92c0802013-11-27 05:05:46 -050082 self.file = StringIO()
Just7842e561999-12-16 21:34:53 +000083 self.name = path
84 self.closed = 0
Just4ff3ba92000-03-14 22:59:39 +000085 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.
Just7842e561999-12-16 21:34:53 +000088 if fullname is None or fullname is None or psname is None:
89 from fontTools import ttLib
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -050090 raise ttLib.TTLibError("can't make 'sfnt' resource, no Macintosh 'name' table found")
Just7842e561999-12-16 21:34:53 +000091 self.fullname = fullname.string
92 self.familyname = familyname.string
93 self.psname = psname.string
Behdad Esfahbod180ace62013-11-27 02:40:30 -050094 if self.familyname != self.psname[:len(self.familyname)]:
Just7842e561999-12-16 21:34:53 +000095 # 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 Esfahbod180ace62013-11-27 02:40:30 -050098 if self.familyname[i] != self.psname[i]:
Just7842e561999-12-16 21:34:53 +000099 break
100 self.familyname = self.psname[:i]
101
102 self.ttFont = ttFont
103 self.res_id = res_id
Just7842e561999-12-16 21:34:53 +0000104 if os.path.exists(self.name):
105 os.remove(self.name)
jvrd4079732003-08-22 18:56:01 +0000106 # XXX datafork support
107 Res.FSpCreateResFile(self.name, 'DMOV', 'FFIL', 0)
jvr91bca422012-10-18 12:49:22 +0000108 self.resref = Res.FSOpenResFile(self.name, 3) # exclusive read/write permission
Just7842e561999-12-16 21:34:53 +0000109
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 Esfahbod32c10ee2013-11-27 17:46:17 -0500161 scale = 4096 / self.ttFont['head'].unitsPerEm
Just7842e561999-12-16 21:34:53 +0000162 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 Esfahbodbc5e1cb2013-11-27 02:33:03 -0500176 if 'kern' in self.ttFont:
Just7842e561999-12-16 21:34:53 +0000177 kern = self.ttFont['kern'].getkern(0)
178 if kern:
179 fondkerning = []
180 for (left, right), value in kern.kernTable.items():
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500181 if left in names and right in names:
Just7842e561999-12-16 21:34:53 +0000182 fondkerning.append((names[left], names[right], scale * value))
183 fondkerning.sort()
184 fond.kernTables = {0: fondkerning}
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -0500185 if 'hmtx' in self.ttFont:
Just7842e561999-12-16 21:34:53 +0000186 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 Esfahbodbc5e1cb2013-11-27 02:33:03 -0500189 if name in names:
Just7842e561999-12-16 21:34:53 +0000190 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