@@ -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,59 @@ 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 algorithm is described in Adobe's "Adobe Type 1 Font Format".
151+ The key argument can be an integer, or one of the strings
152+ 'eexec' and 'charstring', which map to the key specified for the
153+ corresponding part of Type-1 fonts.
154+
155+ The ndiscard argument should be an integer, usually 4.
156+ That number of bytes is discarded from the beginning of plaintext.
157+ """
158+
159+ key = {'eexec' : 55665 , 'charstring' : 4330 }.get (key , key )
160+ if not isinstance (key , int ):
161+ raise ValueError (f'Invalid decryption key { key !r} ' )
162+
163+ plaintext = bytearray (len (ciphertext ))
164+ for i , byte in enumerate (ciphertext ):
165+ plaintext [i ] = byte ^ (key >> 8 )
166+ key = ((key + byte ) * 52845 + 22719 ) & 0xffff
167+
168+ return bytes (plaintext [ndiscard :])
169+
170+ @staticmethod
171+ def _encrypt (plaintext , key , ndiscard = 4 ):
172+ """
173+ Encrypt plaintext using the Type-1 font algorithm
174+
175+ The algorithm is described in Adobe's "Adobe Type 1 Font Format".
176+ The key argument can be an integer, or one of the strings
177+ 'eexec' and 'charstring', which map to the key specified for the
178+ corresponding part of Type-1 fonts.
179+
180+ The ndiscard argument should be an integer, usually 4. That
181+ number of bytes is prepended to the plaintext before encryption.
182+ This function prepends NUL bytes for reproducibility, even though
183+ the original algorithm uses random bytes, presumably to avoid
184+ cryptanalysis.
185+ """
186+
187+ key = {'eexec' : 55665 , 'charstring' : 4330 }.get (key , key )
188+ if not isinstance (key , int ):
189+ raise ValueError (f'Invalid encryption key { key !r} ' )
190+
191+ ciphertext = bytearray (len (plaintext ) + ndiscard )
192+ for i , byte in enumerate (b'\0 ' * ndiscard + plaintext ):
193+ ciphertext [i ] = byte ^ (key >> 8 )
194+ key = ((key + ciphertext [i ]) * 52845 + 22719 ) & 0xffff
195+
196+ return bytes (ciphertext )
197+
142198 @classmethod
143199 def _tokens (cls , text ):
144200 """
0 commit comments