2424import binascii
2525import enum
2626import itertools
27+ import logging
2728import re
2829import struct
2930
3031import numpy as np
3132
3233from matplotlib .cbook import _format_approx
34+ from . import _api
3335
36+ _log = logging .getLogger (__name__ )
3437
3538# token types
3639_TokenType = enum .Enum ('_TokenType' ,
@@ -46,10 +49,12 @@ class Type1Font:
4649 parts : tuple
4750 A 3-tuple of the cleartext part, the encrypted part, and the finale of
4851 zeros.
52+ decrypted : bytes
53+ The decrypted form of parts[1].
4954 prop : dict[str, Any]
5055 A dictionary of font properties.
5156 """
52- __slots__ = ('parts' , 'prop' )
57+ __slots__ = ('parts' , 'decrypted' , ' prop' )
5358
5459 def __init__ (self , input ):
5560 """
@@ -68,6 +73,7 @@ def __init__(self, input):
6873 data = self ._read (file )
6974 self .parts = self ._split (data )
7075
76+ self .decrypted = self ._decrypt (self .parts [1 ], 'eexec' )
7177 self ._parse ()
7278
7379 def _read (self , file ):
@@ -125,20 +131,71 @@ def _split(self, data):
125131 zeros -= 1
126132 idx -= 1
127133 if zeros :
128- raise RuntimeError ('Insufficiently many zeros in Type 1 font' )
134+ # this may have been a problem on old implementations that
135+ # used the zeros as necessary padding
136+ _log .info ('Insufficiently many zeros in Type 1 font' )
129137
130138 # Convert encrypted part to binary (if we read a pfb file, we may end
131139 # up converting binary to hexadecimal to binary again; but if we read
132140 # a pfa file, this part is already in hex, and I am not quite sure if
133141 # even the pfb format guarantees that it will be in binary).
134- binary = binascii .unhexlify (data [len1 :idx + 1 ])
142+ idx1 = len1 + ((idx - len1 + 2 ) & ~ 1 ) # ensure an even number of bytes
143+ binary = binascii .unhexlify (data [len1 :idx1 ])
135144
136145 return data [:len1 ], binary , data [idx + 1 :]
137146
138147 _whitespace_or_comment_re = re .compile (br'[\0\t\r\014\n ]+|%[^\r\n\v]*' )
139148 _token_re = re .compile (br'/{0,2}[^]\0\t\r\v\n ()<>{}/%[]+' )
140149 _instring_re = re .compile (br'[()\\]' )
141150
151+ @staticmethod
152+ def _decrypt (ciphertext , key , ndiscard = 4 ):
153+ """
154+ Decrypt ciphertext using the Type-1 font algorithm
155+
156+ The algorithm is described in Adobe's "Adobe Type 1 Font Format".
157+ The key argument can be an integer, or one of the strings
158+ 'eexec' and 'charstring', which map to the key specified for the
159+ corresponding part of Type-1 fonts.
160+
161+ The ndiscard argument should be an integer, usually 4.
162+ That number of bytes is discarded from the beginning of plaintext.
163+ """
164+
165+ key = _api .check_getitem ({'eexec' : 55665 , 'charstring' : 4330 }, key = key )
166+ plaintext = []
167+ for byte in ciphertext :
168+ plaintext .append (byte ^ (key >> 8 ))
169+ key = ((key + byte ) * 52845 + 22719 ) & 0xffff
170+
171+ return bytes (plaintext [ndiscard :])
172+
173+ @staticmethod
174+ def _encrypt (plaintext , key , ndiscard = 4 ):
175+ """
176+ Encrypt plaintext using the Type-1 font algorithm
177+
178+ The algorithm is described in Adobe's "Adobe Type 1 Font Format".
179+ The key argument can be an integer, or one of the strings
180+ 'eexec' and 'charstring', which map to the key specified for the
181+ corresponding part of Type-1 fonts.
182+
183+ The ndiscard argument should be an integer, usually 4. That
184+ number of bytes is prepended to the plaintext before encryption.
185+ This function prepends NUL bytes for reproducibility, even though
186+ the original algorithm uses random bytes, presumably to avoid
187+ cryptanalysis.
188+ """
189+
190+ key = _api .check_getitem ({'eexec' : 55665 , 'charstring' : 4330 }, key = key )
191+ ciphertext = []
192+ for byte in b'\0 ' * ndiscard + plaintext :
193+ c = byte ^ (key >> 8 )
194+ ciphertext .append (c )
195+ key = ((key + c ) * 52845 + 22719 ) & 0xffff
196+
197+ return bytes (ciphertext )
198+
142199 @classmethod
143200 def _tokens (cls , text ):
144201 """
0 commit comments