Just 7842e56b97 Created a new library directory called "FreeLib". All OpenSource RFMKII components will reside there, fontTools being the flagship.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@2 4cde692c-a291-49d1-8350-778aa11640f8
1999-12-16 21:34:53 +00:00

348 lines
8.0 KiB
Python

"""fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts
Functions for reading and writing raw Type 1 data:
read(path)
reads any Type 1 font file, returns the raw data and a type indicator:
'LWFN', 'PFB' or 'OTHER', depending on the format of the file pointed
to by 'path'.
Raises an error when the file does not contain valid Type 1 data.
write(path, data, kind = 'OTHER', dohex = 0)
writes raw Type 1 data to the file pointed to by 'path'.
'kind' can be one of 'LWFN', 'PFB' or 'OTHER'; it defaults to 'OTHER'.
'dohex' is a flag which determines whether the eexec encrypted
part should be written as hexadecimal or binary, but only if kind
is 'LWFN' or 'PFB'.
"""
__author__ = "jvr"
__version__ = "1.0b2"
DEBUG = 0
import eexec
import string
import re
import os
if os.name == 'mac':
import Res
import macfs
error = 't1Lib.error'
# work in progress
class T1Font:
"""Type 1 font class.
XXX This is work in progress! For now just use the read()
and write() functions as described above, they are stable.
"""
def __init__(self, path=None):
if path is not None:
self.data, type = read(path)
else:
pass # XXX
def saveAs(self, path, type):
self.write(path, self.getData(), type)
def getData(self):
return self.data
def __getitem__(self, key):
if not hasattr(self, "font"):
self.parse()
return self.font[key]
else:
return self.font[key]
def parse(self):
import psLib
import psCharStrings
self.font = psLib.suckfont(self.data)
charStrings = self.font["CharStrings"]
lenIV = self.font["Private"].get("lenIV", 4)
assert lenIV >= 0
for glyphName, charString in charStrings.items():
charString, R = eexec.Decrypt(charString, 4330)
charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:])
subrs = self.font["Private"]["Subrs"]
for i in range(len(subrs)):
charString, R = eexec.Decrypt(subrs[i], 4330)
subrs[i] = psCharStrings.T1CharString(charString[lenIV:])
del self.data
# public functions
def read(path):
"""reads any Type 1 font file, returns raw data"""
normpath = string.lower(path)
if os.name == 'mac':
fss = macfs.FSSpec(path)
creator, type = fss.GetCreatorType()
if type == 'LWFN':
return readlwfn(path), 'LWFN'
if normpath[-4:] == '.pfb':
return readpfb(path), 'PFB'
else:
return readother(path), 'OTHER'
def write(path, data, kind='OTHER', dohex=0):
asserttype1(data)
kind = string.upper(kind)
try:
os.remove(path)
except os.error:
pass
err = 1
try:
if kind == 'LWFN':
writelwfn(path, data)
elif kind == 'PFB':
writepfb(path, data)
else:
writeother(path, data, dohex)
err = 0
finally:
if err and not DEBUG:
try:
os.remove(path)
except os.error:
pass
# -- internal --
LWFNCHUNKSIZE = 2000
HEXLINELENGTH = 80
def readlwfn(path):
"""reads an LWFN font file, returns raw data"""
resref = Res.OpenResFile(path)
try:
Res.UseResFile(resref)
n = Res.Count1Resources('POST')
data = []
for i in range(501, 501 + n):
res = Res.Get1Resource('POST', i)
code = ord(res.data[0])
if ord(res.data[1]) <> 0:
raise error, 'corrupt LWFN file'
if code in [1, 2]:
data.append(res.data[2:])
elif code in [3, 5]:
break
elif code == 4:
f = open(path, "rb")
data.append(f.read())
f.close()
elif code == 0:
pass # comment, ignore
else:
raise error, 'bad chunk code: ' + `code`
finally:
Res.CloseResFile(resref)
data = string.join(data, '')
asserttype1(data)
return data
def readpfb(path):
"""reads a PFB font file, returns raw data"""
f = open(path, "rb")
data = []
while 1:
if f.read(1) <> chr(128):
raise error, 'corrupt PFB file'
code = ord(f.read(1))
if code in [1, 2]:
chunklen = string2long(f.read(4))
data.append(f.read(chunklen))
elif code == 3:
break
else:
raise error, 'bad chunk code: ' + `code`
f.close()
data = string.join(data, '')
asserttype1(data)
return data
def readother(path):
"""reads any (font) file, returns raw data"""
f = open(path, "rb")
data = f.read()
f.close()
asserttype1(data)
chunks = findencryptedchunks(data)
data = []
for isencrypted, chunk in chunks:
if isencrypted and ishex(chunk[:4]):
data.append(dehexstring(chunk))
else:
data.append(chunk)
return string.join(data, '')
# file writing tools
def writelwfn(path, data):
Res.CreateResFile(path)
fss = macfs.FSSpec(path)
fss.SetCreatorType('just', 'LWFN')
resref = Res.OpenResFile(path)
try:
Res.UseResFile(resref)
resID = 501
chunks = findencryptedchunks(data)
for isencrypted, chunk in chunks:
if isencrypted:
code = 2
else:
code = 1
while chunk:
res = Res.Resource(chr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
res.AddResource('POST', resID, '')
chunk = chunk[LWFNCHUNKSIZE - 2:]
resID = resID + 1
res = Res.Resource(chr(5) + '\0')
res.AddResource('POST', resID, '')
finally:
Res.CloseResFile(resref)
def writepfb(path, data):
chunks = findencryptedchunks(data)
f = open(dstpath, "wb")
try:
for isencrypted, chunk in chunks:
if isencrypted:
code = 2
else:
code = 1
f.write(chr(128) + chr(code))
f.write(long2string(len(chunk)))
f.write(chunk)
f.write(chr(128) + chr(3))
finally:
f.close()
if os.name == 'mac':
fss = macfs.FSSpec(dstpath)
fss.SetCreatorType('mdos', 'BINA')
def writeother(path, data, dohex = 0):
chunks = findencryptedchunks(data)
f = open(path, "wb")
try:
hexlinelen = HEXLINELENGTH / 2
for isencrypted, chunk in chunks:
if isencrypted:
code = 2
else:
code = 1
if code == 2 and dohex:
while chunk:
f.write(eexec.hexstring(chunk[:hexlinelen]))
f.write('\r')
chunk = chunk[hexlinelen:]
else:
f.write(chunk)
finally:
f.close()
if os.name == 'mac':
fss = macfs.FSSpec(path)
fss.SetCreatorType('R*ch', 'TEXT') # BBEdit text file
# decryption tools
EEXECBEGIN = "currentfile eexec"
EEXECEND = '0' * 64
EEXECINTERNALEND = "currentfile closefile"
EEXECBEGINMARKER = "%-- eexec start\r"
EEXECENDMARKER = "%-- eexec end\r"
_ishexRE = re.compile('[0-9A-Fa-f]*$')
def ishex(text):
return _ishexRE.match(text) is not None
def decrypttype1(data):
chunks = findencryptedchunks(data)
data = []
for isencrypted, chunk in chunks:
if isencrypted:
if ishex(chunk[:4]):
chunk = dehexstring(chunk)
decrypted, R = eexec.Decrypt(chunk, 55665)
decrypted = decrypted[4:]
if decrypted[-len(EEXECINTERNALEND)-1:-1] <> EEXECINTERNALEND \
and decrypted[-len(EEXECINTERNALEND)-2:-2] <> EEXECINTERNALEND:
raise error, "invalid end of eexec part"
decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + '\r'
data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
else:
if chunk[-len(EEXECBEGIN)-1:-1] == EEXECBEGIN:
data.append(chunk[:-len(EEXECBEGIN)-1])
else:
data.append(chunk)
return string.join(data, '')
def findencryptedchunks(data):
chunks = []
while 1:
ebegin = string.find(data, EEXECBEGIN)
if ebegin < 0:
break
eend = string.find(data, EEXECEND, ebegin)
if eend < 0:
raise error, "can't find end of eexec part"
chunks.append((0, data[:ebegin + len(EEXECBEGIN) + 1]))
chunks.append((1, data[ebegin + len(EEXECBEGIN) + 1:eend]))
data = data[eend:]
chunks.append((0, data))
return chunks
def dehexstring(hexstring):
return eexec.dehexstring(string.join(string.split(hexstring), ""))
# Type 1 assertion
_fontType1RE = re.compile(r"/FontType\s+1\s+def")
def asserttype1(data):
for head in ['%!PS-AdobeFont', '%!FontType1-1.0']:
if data[:len(head)] == head:
break
else:
raise error, "not a PostScript font"
if not _fontType1RE.search(data):
raise error, "not a Type 1 font"
if string.find(data, "currentfile eexec") < 0:
raise error, "not an encrypted Type 1 font"
# XXX what else?
return data
# pfb helpers
def long2string(long):
str = ""
for i in range(4):
str = str + chr((long & (0xff << (i * 8))) >> i * 8)
return str
def string2long(str):
if len(str) <> 4:
raise ValueError, 'string must be 4 bytes long'
long = 0
for i in range(4):
long = long + (ord(str[i]) << (i * 8))
return long