"""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)