aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/t1Lib/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/t1Lib/__init__.py')
-rw-r--r--Lib/fontTools/t1Lib/__init__.py982
1 files changed, 525 insertions, 457 deletions
diff --git a/Lib/fontTools/t1Lib/__init__.py b/Lib/fontTools/t1Lib/__init__.py
index a74f9a47..a64f7809 100644
--- a/Lib/fontTools/t1Lib/__init__.py
+++ b/Lib/fontTools/t1Lib/__init__.py
@@ -1,4 +1,4 @@
-"""fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts (Python2 only)
+"""fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts.
Functions for reading and writing raw Type 1 data:
@@ -19,7 +19,11 @@ import fontTools
from fontTools.misc import eexec
from fontTools.misc.macCreatorType import getMacCreatorAndType
from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes
-from fontTools.misc.psOperators import _type1_pre_eexec_order, _type1_fontinfo_order, _type1_post_eexec_order
+from fontTools.misc.psOperators import (
+ _type1_pre_eexec_order,
+ _type1_fontinfo_order,
+ _type1_post_eexec_order,
+)
from fontTools.encodings.StandardEncoding import StandardEncoding
import os
import re
@@ -30,260 +34,307 @@ DEBUG = 0
try:
- try:
- from Carbon import Res
- except ImportError:
- import Res # MacPython < 2.2
+ try:
+ from Carbon import Res
+ except ImportError:
+ import Res # MacPython < 2.2
except ImportError:
- haveMacSupport = 0
+ haveMacSupport = 0
else:
- haveMacSupport = 1
+ haveMacSupport = 1
-class T1Error(Exception): pass
+class T1Error(Exception):
+ pass
class T1Font(object):
- """Type 1 font class.
-
- Uses a minimal interpeter that supports just about enough PS to parse
- Type 1 fonts.
- """
-
- def __init__(self, path, encoding="ascii", kind=None):
- if kind is None:
- self.data, _ = read(path)
- elif kind == "LWFN":
- self.data = readLWFN(path)
- elif kind == "PFB":
- self.data = readPFB(path)
- elif kind == "OTHER":
- self.data = readOther(path)
- else:
- raise ValueError(kind)
- self.encoding = encoding
-
- def saveAs(self, path, type, dohex=False):
- write(path, self.getData(), type, dohex)
-
- def getData(self):
- if not hasattr(self, "data"):
- self.data = self.createData()
- return self.data
-
- def getGlyphSet(self):
- """Return a generic GlyphSet, which is a dict-like object
- mapping glyph names to glyph objects. The returned glyph objects
- have a .draw() method that supports the Pen protocol, and will
- have an attribute named 'width', but only *after* the .draw() method
- has been called.
-
- In the case of Type 1, the GlyphSet is simply the CharStrings dict.
- """
- return self["CharStrings"]
-
- def __getitem__(self, key):
- if not hasattr(self, "font"):
- self.parse()
- return self.font[key]
-
- def parse(self):
- from fontTools.misc import psLib
- from fontTools.misc import psCharStrings
- self.font = psLib.suckfont(self.data, self.encoding)
- charStrings = self.font["CharStrings"]
- lenIV = self.font["Private"].get("lenIV", 4)
- assert lenIV >= 0
- subrs = self.font["Private"]["Subrs"]
- for glyphName, charString in charStrings.items():
- charString, R = eexec.decrypt(charString, 4330)
- charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:],
- subrs=subrs)
- for i in range(len(subrs)):
- charString, R = eexec.decrypt(subrs[i], 4330)
- subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
- del self.data
-
- def createData(self):
- sf = self.font
-
- eexec_began = False
- eexec_dict = {}
- lines = []
- lines.extend([self._tobytes(f"%!FontType1-1.1: {sf['FontName']}"),
- self._tobytes(f"%t1Font: ({fontTools.version})"),
- self._tobytes(f"%%BeginResource: font {sf['FontName']}")])
- # follow t1write.c:writeRegNameKeyedFont
- size = 3 # Headroom for new key addition
- size += 1 # FontMatrix is always counted
- size += 1 + 1 # Private, CharStings
- for key in font_dictionary_keys:
- size += int(key in sf)
- lines.append(self._tobytes(f"{size} dict dup begin"))
-
- for key, value in sf.items():
- if eexec_began:
- eexec_dict[key] = value
- continue
-
- if key == "FontInfo":
- fi = sf["FontInfo"]
- # follow t1write.c:writeFontInfoDict
- size = 3 # Headroom for new key addition
- for subkey in FontInfo_dictionary_keys:
- size += int(subkey in fi)
- lines.append(self._tobytes(f"/FontInfo {size} dict dup begin"))
-
- for subkey, subvalue in fi.items():
- lines.extend(self._make_lines(subkey, subvalue))
- lines.append(b"end def")
- elif key in _type1_post_eexec_order: # usually 'Private'
- eexec_dict[key] = value
- eexec_began = True
- else:
- lines.extend(self._make_lines(key, value))
- lines.append(b"end")
- eexec_portion = self.encode_eexec(eexec_dict)
- lines.append(bytesjoin([b"currentfile eexec ", eexec_portion]))
-
- for _ in range(8):
- lines.append(self._tobytes("0"*64))
- lines.extend([b"cleartomark",
- b"%%EndResource",
- b"%%EOF"])
-
- data = bytesjoin(lines, "\n")
- return data
-
- def encode_eexec(self, eexec_dict):
- lines = []
-
- # '-|', '|-', '|'
- RD_key, ND_key, NP_key = None, None, None
-
- for key, value in eexec_dict.items():
- if key == "Private":
- pr = eexec_dict["Private"]
- # follow t1write.c:writePrivateDict
- size = 3 # for RD, ND, NP
- for subkey in Private_dictionary_keys:
- size += int(subkey in pr)
- lines.append(b"dup /Private")
- lines.append(self._tobytes(f"{size} dict dup begin"))
- for subkey, subvalue in pr.items():
- if not RD_key and subvalue == RD_value:
- RD_key = subkey
- elif not ND_key and subvalue == ND_value:
- ND_key = subkey
- elif not NP_key and subvalue == PD_value:
- NP_key = subkey
-
- if subkey == 'OtherSubrs':
- # XXX: assert that no flex hint is used
- lines.append(self._tobytes(hintothers))
- elif subkey == "Subrs":
- # XXX: standard Subrs only
- lines.append(b"/Subrs 5 array")
- for i, subr_bin in enumerate(std_subrs):
- encrypted_subr, R = eexec.encrypt(bytesjoin([char_IV, subr_bin]), 4330)
- lines.append(bytesjoin([self._tobytes(f"dup {i} {len(encrypted_subr)} {RD_key} "), encrypted_subr, self._tobytes(f" {NP_key}")]))
- lines.append(b'def')
-
- lines.append(b"put")
- else:
- lines.extend(self._make_lines(subkey, subvalue))
- elif key == "CharStrings":
- lines.append(b"dup /CharStrings")
- lines.append(self._tobytes(f"{len(eexec_dict['CharStrings'])} dict dup begin"))
- for glyph_name, char_bin in eexec_dict["CharStrings"].items():
- char_bin.compile()
- encrypted_char, R = eexec.encrypt(bytesjoin([char_IV, char_bin.bytecode]), 4330)
- lines.append(bytesjoin([self._tobytes(f"/{glyph_name} {len(encrypted_char)} {RD_key} "), encrypted_char, self._tobytes(f" {ND_key}")]))
- lines.append(b"end put")
- else:
- lines.extend(self._make_lines(key, value))
-
- lines.extend([b"end",
- b"dup /FontName get exch definefont pop",
- b"mark",
- b"currentfile closefile\n"])
-
- eexec_portion = bytesjoin(lines, "\n")
- encrypted_eexec, R = eexec.encrypt(bytesjoin([eexec_IV, eexec_portion]), 55665)
-
- return encrypted_eexec
-
- def _make_lines(self, key, value):
- if key == "FontName":
- return [self._tobytes(f"/{key} /{value} def")]
- if key in ["isFixedPitch", "ForceBold", "RndStemUp"]:
- return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
- elif key == "Encoding":
- if value == StandardEncoding:
- return [self._tobytes(f"/{key} StandardEncoding def")]
- else:
- # follow fontTools.misc.psOperators._type1_Encoding_repr
- lines = []
- lines.append(b"/Encoding 256 array")
- lines.append(b"0 1 255 {1 index exch /.notdef put} for")
- for i in range(256):
- name = value[i]
- if name != ".notdef":
- lines.append(self._tobytes(f"dup {i} /{name} put"))
- lines.append(b"def")
- return lines
- if isinstance(value, str):
- return [self._tobytes(f"/{key} ({value}) def")]
- elif isinstance(value, bool):
- return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
- elif isinstance(value, list):
- return [self._tobytes(f"/{key} [{' '.join(str(v) for v in value)}] def")]
- elif isinstance(value, tuple):
- return [self._tobytes(f"/{key} {{{' '.join(str(v) for v in value)}}} def")]
- else:
- return [self._tobytes(f"/{key} {value} def")]
-
- def _tobytes(self, s, errors="strict"):
- return tobytes(s, self.encoding, errors)
+ """Type 1 font class.
+
+ Uses a minimal interpeter that supports just about enough PS to parse
+ Type 1 fonts.
+ """
+
+ def __init__(self, path, encoding="ascii", kind=None):
+ if kind is None:
+ self.data, _ = read(path)
+ elif kind == "LWFN":
+ self.data = readLWFN(path)
+ elif kind == "PFB":
+ self.data = readPFB(path)
+ elif kind == "OTHER":
+ self.data = readOther(path)
+ else:
+ raise ValueError(kind)
+ self.encoding = encoding
+
+ def saveAs(self, path, type, dohex=False):
+ write(path, self.getData(), type, dohex)
+
+ def getData(self):
+ if not hasattr(self, "data"):
+ self.data = self.createData()
+ return self.data
+
+ def getGlyphSet(self):
+ """Return a generic GlyphSet, which is a dict-like object
+ mapping glyph names to glyph objects. The returned glyph objects
+ have a .draw() method that supports the Pen protocol, and will
+ have an attribute named 'width', but only *after* the .draw() method
+ has been called.
+
+ In the case of Type 1, the GlyphSet is simply the CharStrings dict.
+ """
+ return self["CharStrings"]
+
+ def __getitem__(self, key):
+ if not hasattr(self, "font"):
+ self.parse()
+ return self.font[key]
+
+ def parse(self):
+ from fontTools.misc import psLib
+ from fontTools.misc import psCharStrings
+
+ self.font = psLib.suckfont(self.data, self.encoding)
+ charStrings = self.font["CharStrings"]
+ lenIV = self.font["Private"].get("lenIV", 4)
+ assert lenIV >= 0
+ subrs = self.font["Private"]["Subrs"]
+ for glyphName, charString in charStrings.items():
+ charString, R = eexec.decrypt(charString, 4330)
+ charStrings[glyphName] = psCharStrings.T1CharString(
+ charString[lenIV:], subrs=subrs
+ )
+ for i in range(len(subrs)):
+ charString, R = eexec.decrypt(subrs[i], 4330)
+ subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
+ del self.data
+
+ def createData(self):
+ sf = self.font
+
+ eexec_began = False
+ eexec_dict = {}
+ lines = []
+ lines.extend(
+ [
+ self._tobytes(f"%!FontType1-1.1: {sf['FontName']}"),
+ self._tobytes(f"%t1Font: ({fontTools.version})"),
+ self._tobytes(f"%%BeginResource: font {sf['FontName']}"),
+ ]
+ )
+ # follow t1write.c:writeRegNameKeyedFont
+ size = 3 # Headroom for new key addition
+ size += 1 # FontMatrix is always counted
+ size += 1 + 1 # Private, CharStings
+ for key in font_dictionary_keys:
+ size += int(key in sf)
+ lines.append(self._tobytes(f"{size} dict dup begin"))
+
+ for key, value in sf.items():
+ if eexec_began:
+ eexec_dict[key] = value
+ continue
+
+ if key == "FontInfo":
+ fi = sf["FontInfo"]
+ # follow t1write.c:writeFontInfoDict
+ size = 3 # Headroom for new key addition
+ for subkey in FontInfo_dictionary_keys:
+ size += int(subkey in fi)
+ lines.append(self._tobytes(f"/FontInfo {size} dict dup begin"))
+
+ for subkey, subvalue in fi.items():
+ lines.extend(self._make_lines(subkey, subvalue))
+ lines.append(b"end def")
+ elif key in _type1_post_eexec_order: # usually 'Private'
+ eexec_dict[key] = value
+ eexec_began = True
+ else:
+ lines.extend(self._make_lines(key, value))
+ lines.append(b"end")
+ eexec_portion = self.encode_eexec(eexec_dict)
+ lines.append(bytesjoin([b"currentfile eexec ", eexec_portion]))
+
+ for _ in range(8):
+ lines.append(self._tobytes("0" * 64))
+ lines.extend([b"cleartomark", b"%%EndResource", b"%%EOF"])
+
+ data = bytesjoin(lines, "\n")
+ return data
+
+ def encode_eexec(self, eexec_dict):
+ lines = []
+
+ # '-|', '|-', '|'
+ RD_key, ND_key, NP_key = None, None, None
+ lenIV = 4
+ subrs = std_subrs
+
+ # Ensure we look at Private first, because we need RD_key, ND_key, NP_key and lenIV
+ sortedItems = sorted(eexec_dict.items(), key=lambda item: item[0] != "Private")
+
+ for key, value in sortedItems:
+ if key == "Private":
+ pr = eexec_dict["Private"]
+ # follow t1write.c:writePrivateDict
+ size = 3 # for RD, ND, NP
+ for subkey in Private_dictionary_keys:
+ size += int(subkey in pr)
+ lines.append(b"dup /Private")
+ lines.append(self._tobytes(f"{size} dict dup begin"))
+ for subkey, subvalue in pr.items():
+ if not RD_key and subvalue == RD_value:
+ RD_key = subkey
+ elif not ND_key and subvalue in ND_values:
+ ND_key = subkey
+ elif not NP_key and subvalue in PD_values:
+ NP_key = subkey
+
+ if subkey == "lenIV":
+ lenIV = subvalue
+
+ if subkey == "OtherSubrs":
+ # XXX: assert that no flex hint is used
+ lines.append(self._tobytes(hintothers))
+ elif subkey == "Subrs":
+ for subr_bin in subvalue:
+ subr_bin.compile()
+ subrs = [subr_bin.bytecode for subr_bin in subvalue]
+ lines.append(f"/Subrs {len(subrs)} array".encode("ascii"))
+ for i, subr_bin in enumerate(subrs):
+ encrypted_subr, R = eexec.encrypt(
+ bytesjoin([char_IV[:lenIV], subr_bin]), 4330
+ )
+ lines.append(
+ bytesjoin(
+ [
+ self._tobytes(
+ f"dup {i} {len(encrypted_subr)} {RD_key} "
+ ),
+ encrypted_subr,
+ self._tobytes(f" {NP_key}"),
+ ]
+ )
+ )
+ lines.append(b"def")
+
+ lines.append(b"put")
+ else:
+ lines.extend(self._make_lines(subkey, subvalue))
+ elif key == "CharStrings":
+ lines.append(b"dup /CharStrings")
+ lines.append(
+ self._tobytes(f"{len(eexec_dict['CharStrings'])} dict dup begin")
+ )
+ for glyph_name, char_bin in eexec_dict["CharStrings"].items():
+ char_bin.compile()
+ encrypted_char, R = eexec.encrypt(
+ bytesjoin([char_IV[:lenIV], char_bin.bytecode]), 4330
+ )
+ lines.append(
+ bytesjoin(
+ [
+ self._tobytes(
+ f"/{glyph_name} {len(encrypted_char)} {RD_key} "
+ ),
+ encrypted_char,
+ self._tobytes(f" {ND_key}"),
+ ]
+ )
+ )
+ lines.append(b"end put")
+ else:
+ lines.extend(self._make_lines(key, value))
+
+ lines.extend(
+ [
+ b"end",
+ b"dup /FontName get exch definefont pop",
+ b"mark",
+ b"currentfile closefile\n",
+ ]
+ )
+
+ eexec_portion = bytesjoin(lines, "\n")
+ encrypted_eexec, R = eexec.encrypt(bytesjoin([eexec_IV, eexec_portion]), 55665)
+
+ return encrypted_eexec
+
+ def _make_lines(self, key, value):
+ if key == "FontName":
+ return [self._tobytes(f"/{key} /{value} def")]
+ if key in ["isFixedPitch", "ForceBold", "RndStemUp"]:
+ return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
+ elif key == "Encoding":
+ if value == StandardEncoding:
+ return [self._tobytes(f"/{key} StandardEncoding def")]
+ else:
+ # follow fontTools.misc.psOperators._type1_Encoding_repr
+ lines = []
+ lines.append(b"/Encoding 256 array")
+ lines.append(b"0 1 255 {1 index exch /.notdef put} for")
+ for i in range(256):
+ name = value[i]
+ if name != ".notdef":
+ lines.append(self._tobytes(f"dup {i} /{name} put"))
+ lines.append(b"def")
+ return lines
+ if isinstance(value, str):
+ return [self._tobytes(f"/{key} ({value}) def")]
+ elif isinstance(value, bool):
+ return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
+ elif isinstance(value, list):
+ return [self._tobytes(f"/{key} [{' '.join(str(v) for v in value)}] def")]
+ elif isinstance(value, tuple):
+ return [self._tobytes(f"/{key} {{{' '.join(str(v) for v in value)}}} def")]
+ else:
+ return [self._tobytes(f"/{key} {value} def")]
+
+ def _tobytes(self, s, errors="strict"):
+ return tobytes(s, self.encoding, errors)
# low level T1 data read and write functions
+
def read(path, onlyHeader=False):
- """reads any Type 1 font file, returns raw data"""
- _, ext = os.path.splitext(path)
- ext = ext.lower()
- creator, typ = getMacCreatorAndType(path)
- if typ == 'LWFN':
- return readLWFN(path, onlyHeader), 'LWFN'
- if ext == '.pfb':
- return readPFB(path, onlyHeader), 'PFB'
- else:
- return readOther(path), 'OTHER'
-
-def write(path, data, kind='OTHER', dohex=False):
- assertType1(data)
- kind = kind.upper()
- 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
+ """reads any Type 1 font file, returns raw data"""
+ _, ext = os.path.splitext(path)
+ ext = ext.lower()
+ creator, typ = getMacCreatorAndType(path)
+ if typ == "LWFN":
+ return readLWFN(path, onlyHeader), "LWFN"
+ if ext == ".pfb":
+ return readPFB(path, onlyHeader), "PFB"
+ else:
+ return readOther(path), "OTHER"
+
+
+def write(path, data, kind="OTHER", dohex=False):
+ assertType1(data)
+ kind = kind.upper()
+ 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 --
@@ -293,125 +344,132 @@ HEXLINELENGTH = 80
def readLWFN(path, onlyHeader=False):
- """reads an LWFN font file, returns raw data"""
- from fontTools.misc.macRes import ResourceReader
- reader = ResourceReader(path)
- try:
- data = []
- for res in reader.get('POST', []):
- code = byteord(res.data[0])
- if byteord(res.data[1]) != 0:
- raise T1Error('corrupt LWFN file')
- if code in [1, 2]:
- if onlyHeader and code == 2:
- break
- data.append(res.data[2:])
- elif code in [3, 5]:
- break
- elif code == 4:
- with open(path, "rb") as f:
- data.append(f.read())
- elif code == 0:
- pass # comment, ignore
- else:
- raise T1Error('bad chunk code: ' + repr(code))
- finally:
- reader.close()
- data = bytesjoin(data)
- assertType1(data)
- return data
+ """reads an LWFN font file, returns raw data"""
+ from fontTools.misc.macRes import ResourceReader
+
+ reader = ResourceReader(path)
+ try:
+ data = []
+ for res in reader.get("POST", []):
+ code = byteord(res.data[0])
+ if byteord(res.data[1]) != 0:
+ raise T1Error("corrupt LWFN file")
+ if code in [1, 2]:
+ if onlyHeader and code == 2:
+ break
+ data.append(res.data[2:])
+ elif code in [3, 5]:
+ break
+ elif code == 4:
+ with open(path, "rb") as f:
+ data.append(f.read())
+ elif code == 0:
+ pass # comment, ignore
+ else:
+ raise T1Error("bad chunk code: " + repr(code))
+ finally:
+ reader.close()
+ data = bytesjoin(data)
+ assertType1(data)
+ return data
+
def readPFB(path, onlyHeader=False):
- """reads a PFB font file, returns raw data"""
- data = []
- with open(path, "rb") as f:
- while True:
- if f.read(1) != bytechr(128):
- raise T1Error('corrupt PFB file')
- code = byteord(f.read(1))
- if code in [1, 2]:
- chunklen = stringToLong(f.read(4))
- chunk = f.read(chunklen)
- assert len(chunk) == chunklen
- data.append(chunk)
- elif code == 3:
- break
- else:
- raise T1Error('bad chunk code: ' + repr(code))
- if onlyHeader:
- break
- data = bytesjoin(data)
- assertType1(data)
- return data
+ """reads a PFB font file, returns raw data"""
+ data = []
+ with open(path, "rb") as f:
+ while True:
+ if f.read(1) != bytechr(128):
+ raise T1Error("corrupt PFB file")
+ code = byteord(f.read(1))
+ if code in [1, 2]:
+ chunklen = stringToLong(f.read(4))
+ chunk = f.read(chunklen)
+ assert len(chunk) == chunklen
+ data.append(chunk)
+ elif code == 3:
+ break
+ else:
+ raise T1Error("bad chunk code: " + repr(code))
+ if onlyHeader:
+ break
+ data = bytesjoin(data)
+ assertType1(data)
+ return data
+
def readOther(path):
- """reads any (font) file, returns raw data"""
- with open(path, "rb") as f:
- data = f.read()
- 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 bytesjoin(data)
+ """reads any (font) file, returns raw data"""
+ with open(path, "rb") as f:
+ data = f.read()
+ 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 bytesjoin(data)
+
# 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:
- 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(bytechr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
- res.AddResource('POST', resID, '')
- chunk = chunk[LWFNCHUNKSIZE - 2:]
- resID = resID + 1
- res = Res.Resource(bytechr(5) + '\0')
- res.AddResource('POST', resID, '')
- finally:
- Res.CloseResFile(resRef)
+ # Res.FSpCreateResFile was deprecated in OS X 10.5
+ Res.FSpCreateResFile(path, "just", "LWFN", 0)
+ resRef = Res.FSOpenResFile(path, 2) # write-only
+ 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(bytechr(code) + "\0" + chunk[: LWFNCHUNKSIZE - 2])
+ res.AddResource("POST", resID, "")
+ chunk = chunk[LWFNCHUNKSIZE - 2 :]
+ resID = resID + 1
+ res = Res.Resource(bytechr(5) + "\0")
+ res.AddResource("POST", resID, "")
+ finally:
+ Res.CloseResFile(resRef)
+
def writePFB(path, data):
- chunks = findEncryptedChunks(data)
- with open(path, "wb") as f:
- for isEncrypted, chunk in chunks:
- if isEncrypted:
- code = 2
- else:
- code = 1
- f.write(bytechr(128) + bytechr(code))
- f.write(longToString(len(chunk)))
- f.write(chunk)
- f.write(bytechr(128) + bytechr(3))
+ chunks = findEncryptedChunks(data)
+ with open(path, "wb") as f:
+ for isEncrypted, chunk in chunks:
+ if isEncrypted:
+ code = 2
+ else:
+ code = 1
+ f.write(bytechr(128) + bytechr(code))
+ f.write(longToString(len(chunk)))
+ f.write(chunk)
+ f.write(bytechr(128) + bytechr(3))
+
def writeOther(path, data, dohex=False):
- chunks = findEncryptedChunks(data)
- with open(path, "wb") as f:
- 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(b'\r')
- chunk = chunk[hexlinelen:]
- else:
- f.write(chunk)
+ chunks = findEncryptedChunks(data)
+ with open(path, "wb") as f:
+ 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(b"\r")
+ chunk = chunk[hexlinelen:]
+ else:
+ f.write(chunk)
# decryption tools
@@ -419,99 +477,107 @@ def writeOther(path, data, dohex=False):
EEXECBEGIN = b"currentfile eexec"
# The spec allows for 512 ASCII zeros interrupted by arbitrary whitespace to
# follow eexec
-EEXECEND = re.compile(b'(0[ \t\r\n]*){512}', flags=re.M)
+EEXECEND = re.compile(b"(0[ \t\r\n]*){512}", flags=re.M)
EEXECINTERNALEND = b"currentfile closefile"
EEXECBEGINMARKER = b"%-- eexec start\r"
EEXECENDMARKER = b"%-- eexec end\r"
-_ishexRE = re.compile(b'[0-9A-Fa-f]*$')
+_ishexRE = re.compile(b"[0-9A-Fa-f]*$")
+
def isHex(text):
- return _ishexRE.match(text) is not None
+ 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 T1Error("invalid end of eexec part")
- decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + b'\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 bytesjoin(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 T1Error("invalid end of eexec part")
+ decrypted = decrypted[: -len(EEXECINTERNALEND) - 2] + b"\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 bytesjoin(data)
+
def findEncryptedChunks(data):
- chunks = []
- while True:
- eBegin = data.find(EEXECBEGIN)
- if eBegin < 0:
- break
- eBegin = eBegin + len(EEXECBEGIN) + 1
- endMatch = EEXECEND.search(data, eBegin)
- if endMatch is None:
- raise T1Error("can't find end of eexec part")
- eEnd = endMatch.start()
- cypherText = data[eBegin:eEnd + 2]
- if isHex(cypherText[:4]):
- cypherText = deHexString(cypherText)
- plainText, R = eexec.decrypt(cypherText, 55665)
- eEndLocal = plainText.find(EEXECINTERNALEND)
- if eEndLocal < 0:
- raise T1Error("can't find end of eexec part")
- chunks.append((0, data[:eBegin]))
- chunks.append((1, cypherText[:eEndLocal + len(EEXECINTERNALEND) + 1]))
- data = data[eEnd:]
- chunks.append((0, data))
- return chunks
+ chunks = []
+ while True:
+ eBegin = data.find(EEXECBEGIN)
+ if eBegin < 0:
+ break
+ eBegin = eBegin + len(EEXECBEGIN) + 1
+ endMatch = EEXECEND.search(data, eBegin)
+ if endMatch is None:
+ raise T1Error("can't find end of eexec part")
+ eEnd = endMatch.start()
+ cypherText = data[eBegin : eEnd + 2]
+ if isHex(cypherText[:4]):
+ cypherText = deHexString(cypherText)
+ plainText, R = eexec.decrypt(cypherText, 55665)
+ eEndLocal = plainText.find(EEXECINTERNALEND)
+ if eEndLocal < 0:
+ raise T1Error("can't find end of eexec part")
+ chunks.append((0, data[:eBegin]))
+ chunks.append((1, cypherText[: eEndLocal + len(EEXECINTERNALEND) + 1]))
+ data = data[eEnd:]
+ chunks.append((0, data))
+ return chunks
+
def deHexString(hexstring):
- return eexec.deHexString(bytesjoin(hexstring.split()))
+ return eexec.deHexString(bytesjoin(hexstring.split()))
# Type 1 assertion
-_fontType1RE = re.compile(br"/FontType\s+1\s+def")
+_fontType1RE = re.compile(rb"/FontType\s+1\s+def")
+
def assertType1(data):
- for head in [b'%!PS-AdobeFont', b'%!FontType1']:
- if data[:len(head)] == head:
- break
- else:
- raise T1Error("not a PostScript font")
- if not _fontType1RE.search(data):
- raise T1Error("not a Type 1 font")
- if data.find(b"currentfile eexec") < 0:
- raise T1Error("not an encrypted Type 1 font")
- # XXX what else?
- return data
+ for head in [b"%!PS-AdobeFont", b"%!FontType1"]:
+ if data[: len(head)] == head:
+ break
+ else:
+ raise T1Error("not a PostScript font")
+ if not _fontType1RE.search(data):
+ raise T1Error("not a Type 1 font")
+ if data.find(b"currentfile eexec") < 0:
+ raise T1Error("not an encrypted Type 1 font")
+ # XXX what else?
+ return data
# pfb helpers
+
def longToString(long):
- s = b""
- for i in range(4):
- s += bytechr((long & (0xff << (i * 8))) >> i * 8)
- return s
+ s = b""
+ for i in range(4):
+ s += bytechr((long & (0xFF << (i * 8))) >> i * 8)
+ return s
+
def stringToLong(s):
- if len(s) != 4:
- raise ValueError('string must be 4 bytes long')
- l = 0
- for i in range(4):
- l += byteord(s[i]) << (i * 8)
- return l
+ if len(s) != 4:
+ raise ValueError("string must be 4 bytes long")
+ l = 0
+ for i in range(4):
+ l += byteord(s[i]) << (i * 8)
+ return l
# PS stream helpers
@@ -523,36 +589,38 @@ font_dictionary_keys.remove("FontMatrix")
FontInfo_dictionary_keys = list(_type1_fontinfo_order)
# extend because AFDKO tx may use following keys
-FontInfo_dictionary_keys.extend([
- "FSType",
- "Copyright",
-])
+FontInfo_dictionary_keys.extend(
+ [
+ "FSType",
+ "Copyright",
+ ]
+)
Private_dictionary_keys = [
- # We don't know what names will be actually used.
- # "RD",
- # "ND",
- # "NP",
- "Subrs",
- "OtherSubrs",
- "UniqueID",
- "BlueValues",
- "OtherBlues",
- "FamilyBlues",
- "FamilyOtherBlues",
- "BlueScale",
- "BlueShift",
- "BlueFuzz",
- "StdHW",
- "StdVW",
- "StemSnapH",
- "StemSnapV",
- "ForceBold",
- "LanguageGroup",
- "password",
- "lenIV",
- "MinFeature",
- "RndStemUp",
+ # We don't know what names will be actually used.
+ # "RD",
+ # "ND",
+ # "NP",
+ "Subrs",
+ "OtherSubrs",
+ "UniqueID",
+ "BlueValues",
+ "OtherBlues",
+ "FamilyBlues",
+ "FamilyOtherBlues",
+ "BlueScale",
+ "BlueShift",
+ "BlueFuzz",
+ "StdHW",
+ "StdVW",
+ "StemSnapH",
+ "StemSnapV",
+ "ForceBold",
+ "LanguageGroup",
+ "password",
+ "lenIV",
+ "MinFeature",
+ "RndStemUp",
]
# t1write_hintothers.h
@@ -561,20 +629,20 @@ systemdict/internaldict get exec dup/startlock known{/startlock get exec}{dup
/strtlck known{/strtlck get exec}{pop 3}ifelse}ifelse}ifelse}executeonly]def"""
# t1write.c:saveStdSubrs
std_subrs = [
- # 3 0 callother pop pop setcurrentpoint return
- b"\x8e\x8b\x0c\x10\x0c\x11\x0c\x11\x0c\x21\x0b",
- # 0 1 callother return
- b"\x8b\x8c\x0c\x10\x0b",
- # 0 2 callother return
- b"\x8b\x8d\x0c\x10\x0b",
- # return
- b"\x0b",
- # 3 1 3 callother pop callsubr return
- b"\x8e\x8c\x8e\x0c\x10\x0c\x11\x0a\x0b"
+ # 3 0 callother pop pop setcurrentpoint return
+ b"\x8e\x8b\x0c\x10\x0c\x11\x0c\x11\x0c\x21\x0b",
+ # 0 1 callother return
+ b"\x8b\x8c\x0c\x10\x0b",
+ # 0 2 callother return
+ b"\x8b\x8d\x0c\x10\x0b",
+ # return
+ b"\x0b",
+ # 3 1 3 callother pop callsubr return
+ b"\x8e\x8c\x8e\x0c\x10\x0c\x11\x0a\x0b",
]
# follow t1write.c:writeRegNameKeyedFont
eexec_IV = b"cccc"
char_IV = b"\x0c\x0c\x0c\x0c"
RD_value = ("string", "currentfile", "exch", "readstring", "pop")
-ND_value = ("def",)
-PD_value = ("put",)
+ND_values = [("def",), ("noaccess", "def")]
+PD_values = [("put",), ("noaccess", "put")]