diff options
Diffstat (limited to 'Lib/fontTools/t1Lib/__init__.py')
-rw-r--r-- | Lib/fontTools/t1Lib/__init__.py | 982 |
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")] |