@@ -46,10 +46,12 @@ class Type1Font:
4646 parts : tuple
4747 A 3-tuple of the cleartext part, the encrypted part, and the finale of
4848 zeros.
49+ decrypted : bytes
50+ The decrypted form of parts[1].
4951 prop : dict[str, Any]
5052 A dictionary of font properties.
5153 """
52- __slots__ = ('parts' , 'prop' )
54+ __slots__ = ('parts' , 'decrypted' , ' prop' )
5355
5456 def __init__ (self , input ):
5557 """
@@ -68,6 +70,7 @@ def __init__(self, input):
6870 data = self ._read (file )
6971 self .parts = self ._split (data )
7072
73+ self .decrypted = self ._decrypt (self .parts [1 ], 'eexec' )
7174 self ._parse ()
7275
7376 def _read (self , file ):
@@ -139,6 +142,57 @@ def _split(self, data):
139142 _token_re = re .compile (br'/{0,2}[^]\0\t\r\v\n ()<>{}/%[]+' )
140143 _instring_re = re .compile (br'[()\\]' )
141144
145+ @staticmethod
146+ def _decrypt (ciphertext , key , ndiscard = 4 ):
147+ """
148+ Decrypt ciphertext using the Type-1 font algorithm
149+
150+ The key argument can be an integer, or one of the strings
151+ 'eexec' and 'charstring', which map to the key specified for the
152+ corresponding part of Type-1 fonts.
153+
154+ The ndiscard argument should be an integer, usually 4.
155+ That number of bytes is discarded from the beginning of plaintext.
156+ """
157+
158+ key = {'eexec' : 55665 , 'charstring' : 4330 }.get (key , key )
159+ if not isinstance (key , int ):
160+ raise ValueError (f'Invalid decryption key { key !r} ' )
161+
162+ plaintext = bytearray (len (ciphertext ))
163+ for i , byte in enumerate (ciphertext ):
164+ plaintext [i ] = byte ^ (key >> 8 )
165+ key = ((key + byte ) * 52845 + 22719 ) & 0xffff
166+
167+ return bytes (plaintext [ndiscard :])
168+
169+ @staticmethod
170+ def _encrypt (plaintext , key , ndiscard = 4 ):
171+ """
172+ Encrypt plaintext using the Type-1 font algorithm
173+
174+ The key argument can be an integer, or one of the strings
175+ 'eexec' and 'charstring', which map to the key specified for the
176+ corresponding part of Type-1 fonts.
177+
178+ The ndiscard argument should be an integer, usually 4. That
179+ number of bytes is prepended to the plaintext before encryption.
180+ This function prepends NUL bytes for reproducibility, even though
181+ the original algorithm uses random bytes, presumably to avoid
182+ cryptanalysis.
183+ """
184+
185+ key = {'eexec' : 55665 , 'charstring' : 4330 }.get (key , key )
186+ if not isinstance (key , int ):
187+ raise ValueError (f'Invalid encryption key { key !r} ' )
188+
189+ ciphertext = bytearray (len (plaintext ) + ndiscard )
190+ for i , byte in enumerate (b'\0 ' * ndiscard + plaintext ):
191+ ciphertext [i ] = byte ^ (key >> 8 )
192+ key = ((key + ciphertext [i ]) * 52845 + 22719 ) & 0xffff
193+
194+ return bytes (ciphertext )
195+
142196 @classmethod
143197 def _tokens (cls , text ):
144198 """
0 commit comments