Merge pull request #400 from anthrotype/rsrc-fork
add mac resource fork reader (py23 compatible); drop mac classic support
This commit is contained in:
commit
4374cf2be0
@ -1,97 +0,0 @@
|
||||
"""Mac-only module to find the home file of a resource."""
|
||||
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
from fontTools.misc import sstruct
|
||||
import array
|
||||
import calldll
|
||||
import macfs
|
||||
import Res
|
||||
|
||||
|
||||
def HomeResFile(res):
|
||||
"""Return a path to the file in which resource 'res' lives."""
|
||||
return GetFileLocation(res.HomeResFile())
|
||||
|
||||
|
||||
def GetFileLocation(refNum):
|
||||
"""Return a path to the open file identified with refNum."""
|
||||
pb = ParamBlock(refNum)
|
||||
return pb.getPath()
|
||||
|
||||
#
|
||||
# Internal cruft, adapted from MoreFiles
|
||||
#
|
||||
|
||||
_InterfaceLib = calldll.getlibrary("InterfaceLib")
|
||||
GetVRefNum = calldll.newcall(_InterfaceLib.GetVRefNum, "None", "InShort", "OutShort")
|
||||
_getInfo = calldll.newcall(_InterfaceLib.PBGetFCBInfoSync, "Short", "InLong")
|
||||
|
||||
|
||||
_FCBPBFormat = """
|
||||
qLink: l
|
||||
qType: h
|
||||
ioTrap: h
|
||||
ioCmdAddr: l
|
||||
ioCompletion: l
|
||||
ioResult: h
|
||||
ioNamePtr: l
|
||||
ioVRefNum: h
|
||||
ioRefNum: h
|
||||
filler: h
|
||||
ioFCBIndx: h
|
||||
filler1: h
|
||||
ioFCBFINm: l
|
||||
ioFCBFlags: h
|
||||
ioFCBStBlk: h
|
||||
ioFCBEOF: l
|
||||
ioFCBPLen: l
|
||||
ioFCBCrPs: l
|
||||
ioFCBVRefNum: h
|
||||
ioFCBClpSiz: l
|
||||
ioFCBParID: l
|
||||
"""
|
||||
|
||||
class ParamBlock(object):
|
||||
|
||||
"""Wrapper for the very low level FCBPB record."""
|
||||
|
||||
def __init__(self, refNum):
|
||||
self.__fileName = array.array("c", "\0" * 64)
|
||||
sstruct.unpack(_FCBPBFormat,
|
||||
"\0" * sstruct.calcsize(_FCBPBFormat), self)
|
||||
self.ioNamePtr = self.__fileName.buffer_info()[0]
|
||||
self.ioRefNum = refNum
|
||||
self.ioVRefNum = GetVRefNum(refNum)
|
||||
self.__haveInfo = 0
|
||||
|
||||
def getInfo(self):
|
||||
if self.__haveInfo:
|
||||
return
|
||||
data = sstruct.pack(_FCBPBFormat, self)
|
||||
buf = array.array("c", data)
|
||||
ptr = buf.buffer_info()[0]
|
||||
err = _getInfo(ptr)
|
||||
if err:
|
||||
raise Res.Error("can't get file info", err)
|
||||
sstruct.unpack(_FCBPBFormat, buf.tostring(), self)
|
||||
self.__haveInfo = 1
|
||||
|
||||
def getFileName(self):
|
||||
self.getInfo()
|
||||
data = self.__fileName.tostring()
|
||||
return data[1:byteord(data[0])+1]
|
||||
|
||||
def getFSSpec(self):
|
||||
self.getInfo()
|
||||
vRefNum = self.ioVRefNum
|
||||
parID = self.ioFCBParID
|
||||
return macfs.FSSpec((vRefNum, parID, self.getFileName()))
|
||||
|
||||
def getPath(self):
|
||||
return self.getFSSpec().as_pathname()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fond = Res.GetNamedResource("FOND", "Helvetica")
|
||||
print(HomeResFile(fond))
|
@ -1,11 +1,15 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
import sys
|
||||
try:
|
||||
import xattr
|
||||
except ImportError:
|
||||
xattr = None
|
||||
try:
|
||||
import MacOS
|
||||
except ImportError:
|
||||
MacOS = None
|
||||
from .py23 import *
|
||||
|
||||
|
||||
def _reverseString(s):
|
||||
s = list(s)
|
||||
@ -14,6 +18,15 @@ def _reverseString(s):
|
||||
|
||||
|
||||
def getMacCreatorAndType(path):
|
||||
if xattr is not None:
|
||||
try:
|
||||
finderInfo = xattr.getxattr(path, 'com.apple.FinderInfo')
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
fileType = Tag(finderInfo[:4])
|
||||
fileCreator = Tag(finderInfo[4:8])
|
||||
return fileCreator, fileType
|
||||
if MacOS is not None:
|
||||
fileCreator, fileType = MacOS.GetCreatorAndType(path)
|
||||
if sys.version_info[:2] < (2, 7) and sys.byteorder == "little":
|
||||
@ -28,5 +41,11 @@ def getMacCreatorAndType(path):
|
||||
|
||||
|
||||
def setMacCreatorAndType(path, fileCreator, fileType):
|
||||
if xattr is not None:
|
||||
from fontTools.misc.textTools import pad
|
||||
if not all(len(s) == 4 for s in (fileCreator, fileType)):
|
||||
raise TypeError('arg must be string of 4 chars')
|
||||
finderInfo = pad(bytesjoin([fileType, fileCreator]), 32)
|
||||
xattr.setxattr(path, 'com.apple.FinderInfo', finderInfo)
|
||||
if MacOS is not None:
|
||||
MacOS.SetCreatorAndType(path, fileCreator, fileType)
|
||||
|
227
Lib/fontTools/misc/macRes.py
Normal file
227
Lib/fontTools/misc/macRes.py
Normal file
@ -0,0 +1,227 @@
|
||||
""" Tools for reading Mac resource forks. """
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
import struct
|
||||
from fontTools.misc import sstruct
|
||||
from collections import OrderedDict
|
||||
try:
|
||||
from collections.abc import MutableMapping
|
||||
except ImportError:
|
||||
from UserDict import DictMixin as MutableMapping
|
||||
|
||||
|
||||
class ResourceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceReader(MutableMapping):
|
||||
|
||||
def __init__(self, fileOrPath):
|
||||
self._resources = OrderedDict()
|
||||
if hasattr(fileOrPath, 'read'):
|
||||
self.file = fileOrPath
|
||||
else:
|
||||
try:
|
||||
# try reading from the resource fork (only works on OS X)
|
||||
self.file = self.openResourceFork(fileOrPath)
|
||||
self._readFile()
|
||||
return
|
||||
except (ResourceError, IOError):
|
||||
# if it fails, use the data fork
|
||||
self.file = self.openDataFork(fileOrPath)
|
||||
self._readFile()
|
||||
|
||||
@staticmethod
|
||||
def openResourceFork(path):
|
||||
with open(path + '/..namedfork/rsrc', 'rb') as resfork:
|
||||
data = resfork.read()
|
||||
infile = BytesIO(data)
|
||||
infile.name = path
|
||||
return infile
|
||||
|
||||
@staticmethod
|
||||
def openDataFork(path):
|
||||
with open(path, 'rb') as datafork:
|
||||
data = datafork.read()
|
||||
infile = BytesIO(data)
|
||||
infile.name = path
|
||||
return infile
|
||||
|
||||
def _readFile(self):
|
||||
self._readHeaderAndMap()
|
||||
self._readTypeList()
|
||||
|
||||
def _read(self, numBytes, offset=None):
|
||||
if offset is not None:
|
||||
self.file.seek(offset)
|
||||
if self.file.tell() != offset:
|
||||
raise ResourceError('Failed to seek offset (reached EOF)')
|
||||
data = self.file.read(numBytes)
|
||||
if len(data) != numBytes:
|
||||
raise ResourceError('Cannot read resource (not enough data)')
|
||||
return data
|
||||
|
||||
def _readHeaderAndMap(self):
|
||||
self.file.seek(0)
|
||||
headerData = self._read(ResourceForkHeaderSize)
|
||||
sstruct.unpack(ResourceForkHeader, headerData, self)
|
||||
# seek to resource map, skip reserved
|
||||
mapOffset = self.mapOffset + 22
|
||||
resourceMapData = self._read(ResourceMapHeaderSize, mapOffset)
|
||||
sstruct.unpack(ResourceMapHeader, resourceMapData, self)
|
||||
self.absTypeListOffset = self.mapOffset + self.typeListOffset
|
||||
self.absNameListOffset = self.mapOffset + self.nameListOffset
|
||||
|
||||
def _readTypeList(self):
|
||||
absTypeListOffset = self.absTypeListOffset
|
||||
numTypesData = self._read(2, absTypeListOffset)
|
||||
self.numTypes, = struct.unpack('>H', numTypesData)
|
||||
absTypeListOffset2 = absTypeListOffset + 2
|
||||
for i in range(self.numTypes + 1):
|
||||
resTypeItemOffset = absTypeListOffset2 + ResourceTypeItemSize * i
|
||||
resTypeItemData = self._read(ResourceTypeItemSize, resTypeItemOffset)
|
||||
item = sstruct.unpack(ResourceTypeItem, resTypeItemData)
|
||||
resType = tostr(item['type'], encoding='mac-roman')
|
||||
refListOffset = absTypeListOffset + item['refListOffset']
|
||||
numRes = item['numRes'] + 1
|
||||
resources = self._readReferenceList(resType, refListOffset, numRes)
|
||||
self._resources[resType] = resources
|
||||
|
||||
def _readReferenceList(self, resType, refListOffset, numRes):
|
||||
resources = []
|
||||
for i in range(numRes):
|
||||
refOffset = refListOffset + ResourceRefItemSize * i
|
||||
refData = self._read(ResourceRefItemSize, refOffset)
|
||||
res = Resource(resType)
|
||||
res.decompile(refData, self)
|
||||
resources.append(res)
|
||||
return resources
|
||||
|
||||
def __getitem__(self, resType):
|
||||
return self._resources[resType]
|
||||
|
||||
def __delitem__(self, resType):
|
||||
del self._resources[resType]
|
||||
|
||||
def __setitem__(self, resType, resources):
|
||||
self._resources[resType] = resources
|
||||
|
||||
def __len__(self):
|
||||
return len(self._resources)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._resources)
|
||||
|
||||
def keys(self):
|
||||
return self._resources.keys()
|
||||
|
||||
@property
|
||||
def types(self):
|
||||
return list(self._resources.keys())
|
||||
|
||||
def countResources(self, resType):
|
||||
"""Return the number of resources of a given type."""
|
||||
try:
|
||||
return len(self[resType])
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
def getIndices(self, resType):
|
||||
numRes = self.countResources(resType)
|
||||
if numRes:
|
||||
return list(range(1, numRes+1))
|
||||
else:
|
||||
return []
|
||||
|
||||
def getNames(self, resType):
|
||||
"""Return list of names of all resources of a given type."""
|
||||
return [res.name for res in self.get(resType, []) if res.name is not None]
|
||||
|
||||
def getIndResource(self, resType, index):
|
||||
"""Return resource of given type located at an index ranging from 1
|
||||
to the number of resources for that type, or None if not found.
|
||||
"""
|
||||
if index < 1:
|
||||
return None
|
||||
try:
|
||||
res = self[resType][index-1]
|
||||
except (KeyError, IndexError):
|
||||
return None
|
||||
return res
|
||||
|
||||
def getNamedResource(self, resType, name):
|
||||
"""Return the named resource of given type, else return None."""
|
||||
name = tostr(name, encoding='mac-roman')
|
||||
for res in self.get(resType, []):
|
||||
if res.name == name:
|
||||
return res
|
||||
return None
|
||||
|
||||
def close(self):
|
||||
if not self.file.closed:
|
||||
self.file.close()
|
||||
|
||||
|
||||
class Resource(object):
|
||||
|
||||
def __init__(self, resType=None, resData=None, resID=None, resName=None,
|
||||
resAttr=None):
|
||||
self.type = resType
|
||||
self.data = resData
|
||||
self.id = resID
|
||||
self.name = resName
|
||||
self.attr = resAttr
|
||||
|
||||
def decompile(self, refData, reader):
|
||||
sstruct.unpack(ResourceRefItem, refData, self)
|
||||
# interpret 3-byte dataOffset as (padded) ULONG to unpack it with struct
|
||||
self.dataOffset, = struct.unpack('>L', bytesjoin([b"\0", self.dataOffset]))
|
||||
absDataOffset = reader.dataOffset + self.dataOffset
|
||||
dataLength, = struct.unpack(">L", reader._read(4, absDataOffset))
|
||||
self.data = reader._read(dataLength)
|
||||
if self.nameOffset == -1:
|
||||
return
|
||||
absNameOffset = reader.absNameListOffset + self.nameOffset
|
||||
nameLength, = struct.unpack('B', reader._read(1, absNameOffset))
|
||||
name, = struct.unpack('>%ss' % nameLength, reader._read(nameLength))
|
||||
self.name = tostr(name, encoding='mac-roman')
|
||||
|
||||
|
||||
ResourceForkHeader = """
|
||||
> # big endian
|
||||
dataOffset: L
|
||||
mapOffset: L
|
||||
dataLen: L
|
||||
mapLen: L
|
||||
"""
|
||||
|
||||
ResourceForkHeaderSize = sstruct.calcsize(ResourceForkHeader)
|
||||
|
||||
ResourceMapHeader = """
|
||||
> # big endian
|
||||
attr: H
|
||||
typeListOffset: H
|
||||
nameListOffset: H
|
||||
"""
|
||||
|
||||
ResourceMapHeaderSize = sstruct.calcsize(ResourceMapHeader)
|
||||
|
||||
ResourceTypeItem = """
|
||||
> # big endian
|
||||
type: 4s
|
||||
numRes: H
|
||||
refListOffset: H
|
||||
"""
|
||||
|
||||
ResourceTypeItemSize = sstruct.calcsize(ResourceTypeItem)
|
||||
|
||||
ResourceRefItem = """
|
||||
> # big endian
|
||||
id: h
|
||||
nameOffset: h
|
||||
attr: B
|
||||
dataOffset: 3s
|
||||
reserved: L
|
||||
"""
|
||||
|
||||
ResourceRefItemSize = sstruct.calcsize(ResourceRefItem)
|
97
Lib/fontTools/misc/macRes_test.py
Normal file
97
Lib/fontTools/misc/macRes_test.py
Normal file
@ -0,0 +1,97 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.misc.py23 import *
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from fontTools.misc.textTools import deHexStr
|
||||
from .macRes import ResourceReader
|
||||
|
||||
|
||||
# test resource data in DeRez notation
|
||||
"""
|
||||
data 'TEST' (128, "name1") { $"4865 6C6C 6F" }; /* Hello */
|
||||
data 'TEST' (129, "name2") { $"576F 726C 64" }; /* World */
|
||||
data 'test' (130, "name3") { $"486F 7720 6172 6520 796F 753F" }; /* How are you? */
|
||||
"""
|
||||
# the same data, compiled using Rez
|
||||
# $ /usr/bin/Rez testdata.rez -o compiled
|
||||
# $ hexdump -v compiled/..namedfork/rsrc
|
||||
TEST_RSRC_FORK = deHexStr(
|
||||
"00 00 01 00 00 00 01 22 00 00 00 22 00 00 00 64 " # 0x00000000
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000010
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000020
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000030
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000040
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000050
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000060
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000070
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000080
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000090
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000A0
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000B0
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000C0
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000D0
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000E0
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000F0
|
||||
"00 00 00 05 48 65 6c 6c 6f 00 00 00 05 57 6f 72 " # 0x00000100
|
||||
"6c 64 00 00 00 0c 48 6f 77 20 61 72 65 20 79 6f " # 0x00000110
|
||||
"75 3f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000120
|
||||
"00 00 00 00 00 00 00 00 00 00 00 1c 00 52 00 01 " # 0x00000130
|
||||
"54 45 53 54 00 01 00 12 74 65 73 74 00 00 00 2a " # 0x00000140
|
||||
"00 80 00 00 00 00 00 00 00 00 00 00 00 81 00 06 " # 0x00000150
|
||||
"00 00 00 09 00 00 00 00 00 82 00 0c 00 00 00 12 " # 0x00000160
|
||||
"00 00 00 00 05 6e 61 6d 65 31 05 6e 61 6d 65 32 " # 0x00000170
|
||||
"05 6e 61 6d 65 33 " # 0x00000180
|
||||
)
|
||||
|
||||
|
||||
class ResourceReaderTest(unittest.TestCase):
|
||||
|
||||
def test_read_file(self):
|
||||
infile = BytesIO(TEST_RSRC_FORK)
|
||||
reader = ResourceReader(infile)
|
||||
resources = [res for typ in reader.keys() for res in reader[typ]]
|
||||
self.assertExpected(resources)
|
||||
|
||||
def test_read_datafork(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||
tmp.write(TEST_RSRC_FORK)
|
||||
try:
|
||||
reader = ResourceReader(tmp.name)
|
||||
resources = [res for typ in reader.keys() for res in reader[typ]]
|
||||
reader.close()
|
||||
self.assertExpected(resources)
|
||||
finally:
|
||||
os.remove(tmp.name)
|
||||
|
||||
def test_read_namedfork_rsrc(self):
|
||||
if sys.platform != 'darwin':
|
||||
self.skipTest('Not supported on "%s"' % sys.platform)
|
||||
tmp = tempfile.NamedTemporaryFile(delete=False)
|
||||
tmp.close()
|
||||
try:
|
||||
with open(tmp.name + '/..namedfork/rsrc', 'wb') as fork:
|
||||
fork.write(TEST_RSRC_FORK)
|
||||
reader = ResourceReader(tmp.name)
|
||||
resources = [res for typ in reader.keys() for res in reader[typ]]
|
||||
reader.close()
|
||||
self.assertExpected(resources)
|
||||
finally:
|
||||
os.remove(tmp.name)
|
||||
|
||||
def assertExpected(self, resources):
|
||||
self.assertRezEqual(resources[0], 'TEST', b'Hello', 128, 'name1')
|
||||
self.assertRezEqual(resources[1], 'TEST', b'World', 129, 'name2')
|
||||
self.assertRezEqual(
|
||||
resources[2], 'test', b'How are you?', 130, 'name3')
|
||||
|
||||
def assertRezEqual(self, res, type_, data, id, name):
|
||||
self.assertEqual(res.type, type_)
|
||||
self.assertEqual(res.data, data)
|
||||
self.assertEqual(res.id, id)
|
||||
self.assertEqual(res.name, name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -142,13 +142,11 @@ HEXLINELENGTH = 80
|
||||
|
||||
def readLWFN(path, onlyHeader=False):
|
||||
"""reads an LWFN font file, returns raw data"""
|
||||
resRef = Res.FSOpenResFile(path, 1) # read-only
|
||||
from fontTools.misc.macRes import ResourceReader
|
||||
reader = ResourceReader(path)
|
||||
try:
|
||||
Res.UseResFile(resRef)
|
||||
n = Res.Count1Resources('POST')
|
||||
data = []
|
||||
for i in range(501, 501 + n):
|
||||
res = Res.Get1Resource('POST', i)
|
||||
for res in reader.get('POST', []):
|
||||
code = byteord(res.data[0])
|
||||
if byteord(res.data[1]) != 0:
|
||||
raise T1Error('corrupt LWFN file')
|
||||
@ -167,7 +165,7 @@ def readLWFN(path, onlyHeader=False):
|
||||
else:
|
||||
raise T1Error('bad chunk code: ' + repr(code))
|
||||
finally:
|
||||
Res.CloseResFile(resRef)
|
||||
reader.close()
|
||||
data = bytesjoin(data)
|
||||
assertType1(data)
|
||||
return data
|
||||
@ -215,6 +213,7 @@ def readOther(path):
|
||||
# file writing tools
|
||||
|
||||
def writeLWFN(path, data):
|
||||
# Res.FSpCreateResFile was deprecated in OS X 10.5
|
||||
Res.FSpCreateResFile(path, "just", "LWFN", 0)
|
||||
resRef = Res.FSOpenResFile(path, 2) # write-only
|
||||
try:
|
||||
|
@ -9,6 +9,8 @@ import random
|
||||
|
||||
CWD = os.path.abspath(os.path.dirname(__file__))
|
||||
DATADIR = os.path.join(CWD, 'testdata')
|
||||
# I used `tx` to convert PFA to LWFN (stored in the data fork)
|
||||
LWFN = os.path.join(DATADIR, 'TestT1-Regular.lwfn')
|
||||
PFA = os.path.join(DATADIR, 'TestT1-Regular.pfa')
|
||||
PFB = os.path.join(DATADIR, 'TestT1-Regular.pfb')
|
||||
|
||||
@ -63,6 +65,14 @@ class ReadWriteTest(unittest.TestCase):
|
||||
|
||||
class T1FontTest(unittest.TestCase):
|
||||
|
||||
def test_parse_lwfn(self):
|
||||
# the extended attrs are lost on git so we can't auto-detect 'LWFN'
|
||||
font = t1Lib.T1Font()
|
||||
font.data = t1Lib.readLWFN(LWFN)
|
||||
font.parse()
|
||||
self.assertEqual(font['FontName'], 'TestT1-Regular')
|
||||
self.assertTrue('Subrs' in font['Private'])
|
||||
|
||||
def test_parse_pfa(self):
|
||||
font = t1Lib.T1Font(PFA)
|
||||
font.parse()
|
||||
@ -84,3 +94,7 @@ class T1FontTest(unittest.TestCase):
|
||||
self.assertFalse(hasattr(aglyph, 'width'))
|
||||
aglyph.draw(NullPen())
|
||||
self.assertTrue(hasattr(aglyph, 'width'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
BIN
Lib/fontTools/t1Lib/testdata/TestT1-Regular.lwfn
vendored
Normal file
BIN
Lib/fontTools/t1Lib/testdata/TestT1-Regular.lwfn
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
@ -46,15 +46,6 @@ from fontTools.misc.py23 import *
|
||||
import os
|
||||
import sys
|
||||
|
||||
haveMacSupport = 0
|
||||
if sys.platform == "mac":
|
||||
haveMacSupport = 1
|
||||
elif sys.platform == "darwin":
|
||||
if sys.version_info[:3] != (2, 2, 0) and sys.version_info[:1] < (3,):
|
||||
# Python 2.2's Mac support is broken, so don't enable it there.
|
||||
# Python 3 does not have Res used by macUtils
|
||||
haveMacSupport = 1
|
||||
|
||||
|
||||
class TTLibError(Exception): pass
|
||||
|
||||
@ -151,8 +142,8 @@ class TTFont(object):
|
||||
if not hasattr(file, "read"):
|
||||
closeStream = True
|
||||
# assume file is a string
|
||||
if haveMacSupport and res_name_or_index is not None:
|
||||
# on the mac, we deal with sfnt resources as well as flat files
|
||||
if res_name_or_index is not None:
|
||||
# see if it contains 'sfnt' resources in the resource or data fork
|
||||
from . import macUtils
|
||||
if res_name_or_index == 0:
|
||||
if macUtils.getSFNTResIndices(file):
|
||||
@ -185,25 +176,15 @@ class TTFont(object):
|
||||
if self.reader is not None:
|
||||
self.reader.close()
|
||||
|
||||
def save(self, file, makeSuitcase=False, reorderTables=True):
|
||||
def save(self, file, reorderTables=True):
|
||||
"""Save the font to disk. Similarly to the constructor,
|
||||
the 'file' argument can be either a pathname or a writable
|
||||
file object.
|
||||
|
||||
On the Mac, if makeSuitcase is true, a suitcase (resource fork)
|
||||
file will we made instead of a flat .ttf file.
|
||||
"""
|
||||
from fontTools.ttLib import sfnt
|
||||
if not hasattr(file, "write"):
|
||||
closeStream = 1
|
||||
if os.name == "mac" and makeSuitcase:
|
||||
from . import macUtils
|
||||
file = macUtils.SFNTResourceWriter(file, self)
|
||||
else:
|
||||
file = open(file, "wb")
|
||||
if os.name == "mac":
|
||||
from fontTools.misc.macCreator import setMacCreatorAndType
|
||||
setMacCreatorAndType(file.name, 'mdos', 'BINA')
|
||||
file = open(file, "wb")
|
||||
else:
|
||||
# assume "file" is a writable file object
|
||||
closeStream = 0
|
||||
|
@ -1,37 +1,18 @@
|
||||
"""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
|
||||
from fontTools.misc.macRes import ResourceReader, ResourceError
|
||||
|
||||
|
||||
def getSFNTResIndices(path):
|
||||
"""Determine whether a file has a resource fork or not."""
|
||||
"""Determine whether a file has a 'sfnt' resource fork or not."""
|
||||
try:
|
||||
resref = MyOpenResFile(path)
|
||||
except Res.Error:
|
||||
reader = ResourceReader(path)
|
||||
indices = reader.getIndices('sfnt')
|
||||
reader.close()
|
||||
return indices
|
||||
except ResourceError:
|
||||
return []
|
||||
Res.UseResFile(resref)
|
||||
numSFNTs = Res.Count1Resources('sfnt')
|
||||
Res.CloseResFile(resref)
|
||||
return list(range(1, numSFNTs + 1))
|
||||
|
||||
|
||||
def openTTFonts(path):
|
||||
@ -53,21 +34,19 @@ def openTTFonts(path):
|
||||
return fonts
|
||||
|
||||
|
||||
class SFNTResourceReader(object):
|
||||
class SFNTResourceReader(BytesIO):
|
||||
|
||||
"""Simple (Mac-only) read-only file wrapper for 'sfnt' resources."""
|
||||
"""Simple read-only file wrapper for 'sfnt' resources."""
|
||||
|
||||
def __init__(self, path, res_name_or_index):
|
||||
resref = MyOpenResFile(path)
|
||||
Res.UseResFile(resref)
|
||||
reader = ResourceReader(path)
|
||||
if isinstance(res_name_or_index, basestring):
|
||||
res = Res.Get1NamedResource('sfnt', res_name_or_index)
|
||||
rsrc = reader.getNamedResource('sfnt', res_name_or_index)
|
||||
else:
|
||||
res = Res.Get1IndResource('sfnt', res_name_or_index)
|
||||
self.file = BytesIO(res.data)
|
||||
Res.CloseResFile(resref)
|
||||
rsrc = reader.getIndResource('sfnt', res_name_or_index)
|
||||
if rsrc is None:
|
||||
raise TTLibError("sfnt resource not found: %s" % res_name_or_index)
|
||||
reader.close()
|
||||
self.rsrc = rsrc
|
||||
super(SFNTResourceReader, self).__init__(rsrc.data)
|
||||
self.name = path
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# cheap inheritance
|
||||
return getattr(self.file, attr)
|
||||
|
Loading…
x
Reference in New Issue
Block a user