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