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