"""ttLib.macUtils.py -- Various Mac-specific stuff.""" from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * import sys import os if sys.platform not in ("mac", "darwin"): raise ImportError("This module is Mac-only!") try: from Carbon import Res except ImportError: import Res def MyOpenResFile(path): mode = 1 # read only try: resref = Res.FSOpenResFile(path, mode) except Res.Error: # try data fork resref = Res.FSOpenResourceFile(path, unicode(), mode) return resref def getSFNTResIndices(path): """Determine whether a file has a resource fork or not.""" try: resref = MyOpenResFile(path) except Res.Error: return [] Res.UseResFile(resref) numSFNTs = Res.Count1Resources('sfnt') Res.CloseResFile(resref) return list(range(1, numSFNTs + 1)) def openTTFonts(path): """Given a pathname, return a list of TTFont objects. In the case of a flat TTF/OTF file, the list will contain just one font object; but in the case of a Mac font suitcase it will contain as many font objects as there are sfnt resources in the file. """ from fontTools import ttLib fonts = [] sfnts = getSFNTResIndices(path) if not sfnts: fonts.append(ttLib.TTFont(path)) else: for index in sfnts: fonts.append(ttLib.TTFont(path, index)) if not fonts: raise ttLib.TTLibError("no fonts found in file '%s'" % path) return fonts class SFNTResourceReader(object): """Simple (Mac-only) read-only file wrapper for 'sfnt' resources.""" def __init__(self, path, res_name_or_index): resref = MyOpenResFile(path) Res.UseResFile(resref) if isinstance(res_name_or_index, basestring): res = Res.Get1NamedResource('sfnt', res_name_or_index) else: res = Res.Get1IndResource('sfnt', res_name_or_index) self.file = StringIO(res.data) Res.CloseResFile(resref) self.name = path def __getattr__(self, attr): # cheap inheritance return getattr(self.file, attr) class SFNTResourceWriter(object): """Simple (Mac-only) file wrapper for 'sfnt' resources.""" def __init__(self, path, ttFont, res_id=None): self.file = StringIO() self.name = path self.closed = 0 fullname = ttFont['name'].getName(4, 1, 0) # Full name, mac, default encoding familyname = ttFont['name'].getName(1, 1, 0) # Fam. name, mac, default encoding psname = ttFont['name'].getName(6, 1, 0) # PostScript name, etc. if fullname is None or fullname is None or psname is None: from fontTools import ttLib raise ttLib.TTLibError("can't make 'sfnt' resource, no Macintosh 'name' table found") self.fullname = fullname.string self.familyname = familyname.string self.psname = psname.string if self.familyname != self.psname[:len(self.familyname)]: # ugh. force fam name to be the same as first part of ps name, # fondLib otherwise barfs. for i in range(min(len(self.psname), len(self.familyname))): if self.familyname[i] != self.psname[i]: break self.familyname = self.psname[:i] self.ttFont = ttFont self.res_id = res_id if os.path.exists(self.name): os.remove(self.name) # XXX datafork support Res.FSpCreateResFile(self.name, 'DMOV', 'FFIL', 0) self.resref = Res.FSOpenResFile(self.name, 3) # exclusive read/write permission def close(self): if self.closed: return Res.UseResFile(self.resref) try: res = Res.Get1NamedResource('sfnt', self.fullname) except Res.Error: pass else: res.RemoveResource() res = Res.Resource(self.file.getvalue()) if self.res_id is None: self.res_id = Res.Unique1ID('sfnt') res.AddResource('sfnt', self.res_id, self.fullname) res.ChangedResource() self.createFond() del self.ttFont Res.CloseResFile(self.resref) self.file.close() self.closed = 1 def createFond(self): fond_res = Res.Resource("") fond_res.AddResource('FOND', self.res_id, self.fullname) from fontTools import fondLib fond = fondLib.FontFamily(fond_res, "w") fond.ffFirstChar = 0 fond.ffLastChar = 255 fond.fondClass = 0 fond.fontAssoc = [(0, 0, self.res_id)] fond.ffFlags = 20480 # XXX ??? fond.ffIntl = (0, 0) fond.ffLeading = 0 fond.ffProperty = (0, 0, 0, 0, 0, 0, 0, 0, 0) fond.ffVersion = 0 fond.glyphEncoding = {} if self.familyname == self.psname: fond.styleIndices = (1,) * 48 # uh-oh, fondLib is too dumb. else: fond.styleIndices = (2,) * 48 fond.styleStrings = [] fond.boundingBoxes = None fond.ffFamID = self.res_id fond.changed = 1 fond.glyphTableOffset = 0 fond.styleMappingReserved = 0 # calc: scale = 4096 / self.ttFont['head'].unitsPerEm fond.ffAscent = scale * self.ttFont['hhea'].ascent fond.ffDescent = scale * self.ttFont['hhea'].descent fond.ffWidMax = scale * self.ttFont['hhea'].advanceWidthMax fond.ffFamilyName = self.familyname fond.psNames = {0: self.psname} fond.widthTables = {} fond.kernTables = {} cmap = self.ttFont['cmap'].getcmap(1, 0) if cmap: names = {} for code, name in cmap.cmap.items(): names[name] = code if 'kern' in self.ttFont: kern = self.ttFont['kern'].getkern(0) if kern: fondkerning = [] for (left, right), value in kern.kernTable.items(): if left in names and right in names: fondkerning.append((names[left], names[right], scale * value)) fondkerning.sort() fond.kernTables = {0: fondkerning} if 'hmtx' in self.ttFont: hmtx = self.ttFont['hmtx'] fondwidths = [2048] * 256 + [0, 0] # default width, + plus two zeros. for name, (width, lsb) in hmtx.metrics.items(): if name in names: fondwidths[names[name]] = scale * width fond.widthTables = {0: fondwidths} fond.save() def __del__(self): if not self.closed: self.close() def __getattr__(self, attr): # cheap inheritance return getattr(self.file, attr)