diff options
Diffstat (limited to 'Lib/fontTools/t1Lib/__init__.py')
-rw-r--r-- | Lib/fontTools/t1Lib/__init__.py | 219 |
1 files changed, 215 insertions, 4 deletions
diff --git a/Lib/fontTools/t1Lib/__init__.py b/Lib/fontTools/t1Lib/__init__.py index e1d94d35..a74f9a47 100644 --- a/Lib/fontTools/t1Lib/__init__.py +++ b/Lib/fontTools/t1Lib/__init__.py @@ -15,14 +15,17 @@ write(path, data, kind='OTHER', dohex=False) part should be written as hexadecimal or binary, but only if kind is 'OTHER'. """ -from fontTools.misc.py23 import bytechr, byteord, bytesjoin +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.encodings.StandardEncoding import StandardEncoding import os import re __author__ = "jvr" -__version__ = "1.0b2" +__version__ = "1.0b3" DEBUG = 0 @@ -65,8 +68,8 @@ class T1Font(object): write(path, self.getData(), type, dohex) def getData(self): - # XXX Todo: if the data has been converted to Python object, - # recreate the PS stream + if not hasattr(self, "data"): + self.data = self.createData() return self.data def getGlyphSet(self): @@ -102,6 +105,148 @@ class T1Font(object): 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) + # low level T1 data read and write functions @@ -367,3 +512,69 @@ def stringToLong(s): for i in range(4): l += byteord(s[i]) << (i * 8) return l + + +# PS stream helpers + +font_dictionary_keys = list(_type1_pre_eexec_order) +# t1write.c:writeRegNameKeyedFont +# always counts following keys +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", +]) + +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", +] + +# t1write_hintothers.h +hintothers = """/OtherSubrs[{}{}{}{systemdict/internaldict known not{pop 3}{1183615869 +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" +] +# 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",) |