Home | History | Annotate | Download | only in ttLib
      1 """ttLib.macUtils.py -- Various Mac-specific stuff."""
      2 
      3 from __future__ import print_function, division, absolute_import
      4 from fontTools.misc.py23 import *
      5 import sys
      6 import os
      7 if sys.platform not in ("mac", "darwin"):
      8 	raise ImportError("This module is Mac-only!")
      9 try:
     10 	from Carbon import Res
     11 except ImportError:
     12 	import Res
     13 
     14 
     15 
     16 def 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 
     26 def 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 
     38 def 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 
     57 class 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 
     77 class 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