diff --git a/src/UglyToad.PdfPig/Encryption/CryptDictionary.cs b/src/UglyToad.PdfPig/Encryption/CryptDictionary.cs
new file mode 100644
index 00000000..0cedd1f1
--- /dev/null
+++ b/src/UglyToad.PdfPig/Encryption/CryptDictionary.cs
@@ -0,0 +1,67 @@
+namespace UglyToad.PdfPig.Encryption
+{
+ internal class CryptDictionary
+ {
+ public static CryptDictionary Identity { get; } = new CryptDictionary();
+
+ public Method Name { get; }
+
+ public TriggerEvent Event { get; }
+
+ public int Length { get; }
+
+ public bool IsIdentity { get; }
+
+ public CryptDictionary(Method name, TriggerEvent @event, int length)
+ {
+ Name = name;
+ Event = @event;
+ Length = length;
+ IsIdentity = false;
+ }
+
+ private CryptDictionary()
+ {
+ Name = Method.None;
+ IsIdentity = true;
+ }
+
+ ///
+ /// The method used by the consumer application to decrypt data.
+ ///
+ public enum Method
+ {
+ ///
+ /// The application does not decrypt data but directs the input stream
+ /// to the security handler for decryption.
+ ///
+ None,
+ ///
+ /// The application asks the security handler for the encryption key
+ /// and implicitly decrypts data using the RC4 algorithm.
+ ///
+ V2,
+ ///
+ /// (PDF 1.6) The application asks the security handler for the encryption key and implicitly decrypts data using the AES algorithm in Cipher Block Chaining (CBC) mode
+ /// with a 16-byte block size and an initialization vector that is randomly generated and placed as the first 16 bytes in the stream or string.
+ ///
+ AesV2
+ }
+
+ ///
+ /// The event to be used to trigger the authorization that is required
+ /// to access encryption keys used by this filter.
+ ///
+ public enum TriggerEvent
+ {
+ ///
+ /// Authorization is required when a document is opened.
+ ///
+ DocumentOpen,
+ ///
+ /// Authorization is required when accessing embedded files.
+ ///
+ EmbeddedFileOpen
+ }
+ }
+}
diff --git a/src/UglyToad.PdfPig/Encryption/CryptHandler.cs b/src/UglyToad.PdfPig/Encryption/CryptHandler.cs
new file mode 100644
index 00000000..18147f51
--- /dev/null
+++ b/src/UglyToad.PdfPig/Encryption/CryptHandler.cs
@@ -0,0 +1,101 @@
+namespace UglyToad.PdfPig.Encryption
+{
+ using System;
+ using Exceptions;
+ using Tokens;
+
+ internal class CryptHandler
+ {
+ private readonly DictionaryToken cryptDictionary;
+
+ public CryptDictionary StreamDictionary { get; }
+
+ public CryptDictionary StringDictionary { get; }
+
+ public CryptHandler(DictionaryToken cryptDictionary,
+ NameToken streamName, NameToken stringName)
+ {
+ if (streamName == null)
+ {
+ throw new ArgumentNullException(nameof(streamName));
+ }
+
+ if (stringName == null)
+ {
+ throw new ArgumentNullException(nameof(stringName));
+ }
+
+ this.cryptDictionary = cryptDictionary ?? throw new ArgumentNullException(nameof(cryptDictionary));
+ StreamDictionary = ParseCryptDictionary(cryptDictionary, streamName);
+ StringDictionary = ParseCryptDictionary(cryptDictionary, stringName);
+ }
+
+ public CryptDictionary GetNamedCryptDictionary(NameToken name)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ return ParseCryptDictionary(cryptDictionary, name);
+ }
+
+ private static CryptDictionary ParseCryptDictionary(DictionaryToken cryptDictionary, NameToken name)
+ {
+ if (name == NameToken.Identity)
+ {
+ return CryptDictionary.Identity;
+ }
+
+ if (!cryptDictionary.TryGet(name, out DictionaryToken cryptDictionaryToken))
+ {
+ throw new PdfDocumentEncryptedException($"Could not find named crypt filter {name} for decryption in crypt dictionary: {cryptDictionaryToken}.");
+ }
+
+ if (cryptDictionaryToken.TryGet(NameToken.Type, out NameToken typeName) && typeName != NameToken.CryptFilter)
+ {
+ throw new PdfDocumentEncryptedException($"Invalid crypt dictionary type {typeName} for crypt filter {name}: {cryptDictionaryToken}.");
+ }
+
+ var cfmName = cryptDictionaryToken.TryGet(NameToken.Cfm, out NameToken cfm) ? cfm : NameToken.None;
+
+ CryptDictionary.Method method;
+ if (cfmName == NameToken.None)
+ {
+ method = CryptDictionary.Method.None;
+ }
+ else if (cfmName == NameToken.V2)
+ {
+ method = CryptDictionary.Method.V2;
+ }
+ else if (cfmName == NameToken.Aesv2)
+ {
+ method = CryptDictionary.Method.AesV2;
+ }
+ else
+ {
+ throw new PdfDocumentEncryptedException($"Unrecognized CFM option for crypt filter {cfm}: {cryptDictionaryToken}.");
+ }
+
+ var eventName = cryptDictionaryToken.TryGet(NameToken.AuthEvent, out NameToken auth) ? auth : NameToken.DocOpen;
+
+ CryptDictionary.TriggerEvent @event;
+ if (eventName == NameToken.DocOpen)
+ {
+ @event = CryptDictionary.TriggerEvent.DocumentOpen;
+ }
+ else if (eventName == NameToken.EfOpen)
+ {
+ @event = CryptDictionary.TriggerEvent.EmbeddedFileOpen;
+ }
+ else
+ {
+ throw new PdfDocumentEncryptedException($"Unrecognized AuthEvent option for crypt filter {eventName}: {cryptDictionaryToken}.");
+ }
+
+ var length = cryptDictionaryToken.TryGet(NameToken.Length, out NumericToken lengthNumeric) ? lengthNumeric.Int : 0;
+
+ return new CryptDictionary(method, @event, length);
+ }
+ }
+}
diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionAlgorithmCode.cs b/src/UglyToad.PdfPig/Encryption/EncryptionAlgorithmCode.cs
new file mode 100644
index 00000000..d53e4ff5
--- /dev/null
+++ b/src/UglyToad.PdfPig/Encryption/EncryptionAlgorithmCode.cs
@@ -0,0 +1,29 @@
+namespace UglyToad.PdfPig.Encryption
+{
+ ///
+ /// A code specifying the algorithm to be used in encrypting and decrypting the document.
+ ///
+ internal enum EncryptionAlgorithmCode
+ {
+ ///
+ /// An algorithm that is undocumented and no longer supported.
+ ///
+ Unrecognized = 0,
+ ///
+ /// RC4 or AES encryption using a key of 40 bits.
+ ///
+ Rc4OrAes40BitKey = 1,
+ ///
+ /// RC4 or AES encryption using a key of more than 40 bits.
+ ///
+ Rc4OrAesGreaterThan40BitKey = 2,
+ ///
+ /// An unpublished algorithm that permits encryption key lengths ranging from 40 to 128 bits.
+ ///
+ UnpublishedAlgorithm40To128BitKey = 3,
+ ///
+ /// The security handler defines the use of encryption and decryption in the document.
+ ///
+ SecurityHandlerInDocument
+ }
+}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs b/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs
index 8ae32e55..37ea691d 100644
--- a/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs
+++ b/src/UglyToad.PdfPig/Encryption/EncryptionDictionary.cs
@@ -1,7 +1,7 @@
namespace UglyToad.PdfPig.Encryption
{
using System;
- using Tokenization.Scanner;
+ using Exceptions;
using Tokens;
using Util;
@@ -53,129 +53,40 @@
OwnerBytes = OtherEncodings.StringAsLatin1Bytes(ownerPasswordCheck);
UserBytes = OtherEncodings.StringAsLatin1Bytes(userPasswordCheck);
}
- }
- internal static class EncryptionDictionaryFactory
- {
- public static EncryptionDictionary Read(DictionaryToken encryptionDictionary, IPdfTokenScanner tokenScanner)
+ public bool TryGetCryptHandler(out CryptHandler cryptHandler)
{
- if (encryptionDictionary == null)
- {
- throw new ArgumentNullException(nameof(encryptionDictionary));
- }
-
- var filter = encryptionDictionary.Get(NameToken.Filter, tokenScanner);
+ cryptHandler = null;
- var code = EncryptionAlgorithmCode.Unrecognized;
-
- if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NumericToken vNum))
+ if (EncryptionAlgorithmCode != EncryptionAlgorithmCode.SecurityHandlerInDocument)
{
- code = (EncryptionAlgorithmCode) vNum.Int;
+ return false;
}
- var length = default(int?);
-
- if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.Length, tokenScanner, out NumericToken lengthToken))
+ if (!Dictionary.TryGet(NameToken.Cf, out DictionaryToken cryptFilterDictionary))
{
- length = lengthToken.Int;
+ return false;
}
- var revision = default(int);
- if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.R, tokenScanner, out NumericToken revisionToken))
+ var namedFilters = cryptFilterDictionary;
+
+ var streamFilterName = Dictionary.TryGet(NameToken.StmF, out NameToken streamFilterToken) ? streamFilterToken : NameToken.Identity;
+ var stringFilterName = Dictionary.TryGet(NameToken.StrF, out NameToken stringFilterToken) ? stringFilterToken : NameToken.Identity;
+
+ if (streamFilterName != NameToken.Identity && !namedFilters.TryGet(streamFilterName, out _))
{
- revision = revisionToken.Int;
+ throw new PdfDocumentEncryptedException($"Stream filter {streamFilterName} not found in crypt dictionary: {cryptFilterDictionary}.");
}
- encryptionDictionary.TryGetOptionalStringDirect(NameToken.O, tokenScanner, out var ownerString);
- encryptionDictionary.TryGetOptionalStringDirect(NameToken.U, tokenScanner, out var userString);
-
- var access = default(UserAccessPermissions);
-
- if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.P, tokenScanner, out NumericToken accessToken))
+ if (stringFilterName != NameToken.Identity && !namedFilters.TryGet(stringFilterName, out _))
{
- access = (UserAccessPermissions) accessToken.Int;
+ throw new PdfDocumentEncryptedException($"String filter {stringFilterName} not found in crypt dictionary: {cryptFilterDictionary}.");
}
- encryptionDictionary.TryGetOptionalTokenDirect(NameToken.EncryptMetaData, tokenScanner, out BooleanToken encryptMetadata);
+ cryptHandler = new CryptHandler(namedFilters, streamFilterName, stringFilterName);
- return new EncryptionDictionary(filter.Data, code, length, revision, ownerString, userString, access, encryptionDictionary,
- encryptMetadata?.Data ?? false);
+ return true;
}
}
-
- ///
- /// A code specifying the algorithm to be used in encrypting and decrypting the document.
- ///
- internal enum EncryptionAlgorithmCode
- {
- ///
- /// An algorithm that is undocumented and no longer supported.
- ///
- Unrecognized = 0,
- ///
- /// RC4 or AES encryption using a key of 40 bits.
- ///
- Rc4OrAes40BitKey = 1,
- ///
- /// RC4 or AES encryption using a key of more than 40 bits.
- ///
- Rc4OrAesGreaterThan40BitKey = 2,
- ///
- /// An unpublished algorithm that permits encryption key lengths ranging from 40 to 128 bits.
- ///
- UnpublishedAlgorithm40To128BitKey = 3,
- ///
- /// The security handler defines the use of encryption and decryption in the document.
- ///
- SecurityHandlerInDocument
- }
-
- [Flags]
- internal enum UserAccessPermissions
- {
- ///
- /// (Revision 2) Print the document.
- /// (Revision 3 or greater) Print the document (possibly not at the highest quality level, see ).
- ///
- Print = 1 << 2,
- ///
- /// Modify the contents of the document by operations other than those
- /// controlled by , and .
- ///
- Modify = 1 << 3,
- ///
- /// (Revision 2) Copy or otherwise extract text and graphics from the document, including extracting text and graphics
- /// (in support of accessibility to users with disabilities or for other purposes).
- /// (Revision 3 or greater) Copy or otherwise extract text and graphics from the document by operations other
- /// than that controlled by .
- ///
- CopyTextAndGraphics = 1 << 4,
- ///
- /// Add or modify text annotations, fill in interactive form fields, and, if is also set,
- /// create or modify interactive form fields (including signature fields).
- ///
- AddOrModifyTextAnnotationsAndFillFormFields = 1 << 5,
- ///
- /// (Revision 3 or greater) Fill in existing interactive form fields (including signature fields),
- /// even if is clear.
- ///
- FillExistingFormFields = 1 << 8,
- ///
- /// (Revision 3 or greater) Extract text and graphics (in support of accessibility to users with disabilities or for other purposes).
- ///
- ExtractTextAndGraphics = 1 << 9,
- ///
- /// (Revision 3 or greater) Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images),
- /// even if is clear.
- ///
- AssembleDocument = 1 << 10,
- ///
- /// (Revision 3 or greater) Print the document to a representation from which a faithful digital copy of the PDF content could be generated.
- /// When this is clear (and is set), printing is limited to a low-level representation of the appearance,
- /// possibly of degraded quality.
- ///
- PrintHighQuality = 1 << 12
- }
-
}
diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionDictionaryFactory.cs b/src/UglyToad.PdfPig/Encryption/EncryptionDictionaryFactory.cs
new file mode 100644
index 00000000..0065eb9b
--- /dev/null
+++ b/src/UglyToad.PdfPig/Encryption/EncryptionDictionaryFactory.cs
@@ -0,0 +1,55 @@
+namespace UglyToad.PdfPig.Encryption
+{
+ using System;
+ using Tokenization.Scanner;
+ using Tokens;
+ using Util;
+
+ internal static class EncryptionDictionaryFactory
+ {
+ public static EncryptionDictionary Read(DictionaryToken encryptionDictionary, IPdfTokenScanner tokenScanner)
+ {
+ if (encryptionDictionary == null)
+ {
+ throw new ArgumentNullException(nameof(encryptionDictionary));
+ }
+
+ var filter = encryptionDictionary.Get(NameToken.Filter, tokenScanner);
+
+ var code = EncryptionAlgorithmCode.Unrecognized;
+
+ if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NumericToken vNum))
+ {
+ code = (EncryptionAlgorithmCode) vNum.Int;
+ }
+
+ var length = default(int?);
+
+ if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.Length, tokenScanner, out NumericToken lengthToken))
+ {
+ length = lengthToken.Int;
+ }
+
+ var revision = default(int);
+ if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.R, tokenScanner, out NumericToken revisionToken))
+ {
+ revision = revisionToken.Int;
+ }
+
+ encryptionDictionary.TryGetOptionalStringDirect(NameToken.O, tokenScanner, out var ownerString);
+ encryptionDictionary.TryGetOptionalStringDirect(NameToken.U, tokenScanner, out var userString);
+
+ var access = default(UserAccessPermissions);
+
+ if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.P, tokenScanner, out NumericToken accessToken))
+ {
+ access = (UserAccessPermissions) accessToken.Int;
+ }
+
+ encryptionDictionary.TryGetOptionalTokenDirect(NameToken.EncryptMetaData, tokenScanner, out BooleanToken encryptMetadata);
+
+ return new EncryptionDictionary(filter.Data, code, length, revision, ownerString, userString, access, encryptionDictionary,
+ encryptMetadata?.Data ?? true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs
index e29952c6..180586d9 100644
--- a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs
+++ b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs
@@ -29,7 +29,10 @@
[CanBeNull]
private readonly EncryptionDictionary encryptionDictionary;
-
+
+ [CanBeNull]
+ private readonly CryptHandler cryptHandler;
+
private readonly byte[] encryptionKey;
private readonly bool useAes;
@@ -49,9 +52,18 @@
return;
}
+ useAes = false;
+
if (encryptionDictionary.EncryptionAlgorithmCode == EncryptionAlgorithmCode.SecurityHandlerInDocument)
{
- throw new PdfDocumentEncryptedException("Document encrypted with unsupported algorithm.", encryptionDictionary);
+ if (!encryptionDictionary.TryGetCryptHandler(out var cryptHandlerLocal))
+ {
+ throw new PdfDocumentEncryptedException("Document encrypted with security handler in document but no crypt dictionary found.", encryptionDictionary);
+ }
+
+ cryptHandler = cryptHandlerLocal;
+
+ useAes = cryptHandlerLocal?.StreamDictionary?.Name == CryptDictionary.Method.AesV2;
}
var charset = OtherEncodings.Iso88591;
@@ -93,19 +105,15 @@
throw new PdfDocumentEncryptedException("The document was encrypted and the provided password was neither the user or owner password.", encryptionDictionary);
}
- encryptionKey = CalculateKeyRevisions2To4(decryptionPasswordBytes, encryptionDictionary.OwnerBytes, (int)encryptionDictionary.UserAccessPermissions,
- encryptionDictionary.StandardSecurityHandlerRevision,
+ encryptionKey = CalculateKeyRevisions2To4(decryptionPasswordBytes, encryptionDictionary,
length,
documentIdBytes);
-
- useAes = false;
}
private static bool IsUserPassword(byte[] passwordBytes, EncryptionDictionary encryptionDictionary, int length, byte[] documentIdBytes)
{
// 1. Create an encryption key based on the user password string.
- var calculatedEncryptionKey = CalculateKeyRevisions2To4(passwordBytes, encryptionDictionary.OwnerBytes, (int)encryptionDictionary.UserAccessPermissions,
- encryptionDictionary.StandardSecurityHandlerRevision, length, documentIdBytes);
+ var calculatedEncryptionKey = CalculateKeyRevisions2To4(passwordBytes, encryptionDictionary, length, documentIdBytes);
byte[] output;
@@ -245,6 +253,13 @@
{
case StreamToken stream:
{
+ if (cryptHandler?.StreamDictionary?.IsIdentity == true
+ || cryptHandler?.StreamDictionary?.Name == CryptDictionary.Method.None)
+ {
+ // TODO: No idea if this is right.
+ return token;
+ }
+
if (stream.StreamDictionary.TryGet(NameToken.Type, out NameToken typeName))
{
if (NameToken.Xref.Equals(typeName))
@@ -270,6 +285,13 @@
}
case StringToken stringToken:
{
+ if (cryptHandler?.StringDictionary?.IsIdentity == true
+ || cryptHandler?.StringDictionary?.Name == CryptDictionary.Method.None)
+ {
+ // TODO: No idea if this is right.
+ return token;
+ }
+
var data = OtherEncodings.StringAsLatin1Bytes(stringToken.Data);
var decrypted = DecryptData(data, reference);
@@ -386,22 +408,23 @@
}
}
- private static byte[] CalculateKeyRevisions2To4(byte[] password, byte[] ownerKey,
- int permissions, int revision, int length, byte[] documentId)
+ private static byte[] CalculateKeyRevisions2To4(byte[] password, EncryptionDictionary encryptionDictionary, int length, byte[] documentId)
{
// 1. Pad or truncate the password string to exactly 32 bytes.
var passwordFull = GetPaddedPassword(password);
+ var revision = encryptionDictionary.StandardSecurityHandlerRevision;
+
using (var md5 = MD5.Create())
{
// 2. Initialize the MD5 hash function and pass the result of step 1 as input to this function.
UpdateMd5(md5, passwordFull);
// 3. Pass the value of the encryption dictionary's owner key entry to the MD5 hash function.
- UpdateMd5(md5, ownerKey);
+ UpdateMd5(md5, encryptionDictionary.OwnerBytes);
// 4. Treat the value of the P entry as an unsigned 4-byte integer.
- var unsigned = (uint)permissions;
+ var unsigned = (uint)encryptionDictionary.UserAccessPermissions;
// 4. Pass these bytes to the MD5 hash function, low-order byte first.
UpdateMd5(md5, new[] { (byte)(unsigned) });
@@ -414,7 +437,7 @@
// 6. (Revision 4 or greater) If document metadata is not being encrypted, pass 4 bytes
// with the value 0xFFFFFFFF to the MD5 hash function.
- if (revision >= 4)
+ if (revision >= 4 && !encryptionDictionary.EncryptMetadata)
{
UpdateMd5(md5, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
}
@@ -427,22 +450,33 @@
{
var n = length;
+ md5.TransformFinalBlock(EmptyArray.Instance, 0, 0);
+
var input = md5.Hash;
-
- for (var i = 0; i < 50; i++)
+ using (var newMd5 = MD5.Create())
{
- UpdateMd5(md5, input.Take(n).ToArray());
- input = md5.Hash;
+ for (var i = 0; i < 50; i++)
+ {
+ input = newMd5.ComputeHash(input.Take(n).ToArray());
+ }
}
+
+ var result = new byte[length];
+
+ Array.Copy(input, result, length);
+
+ return result;
}
+ else
+ {
+ md5.TransformFinalBlock(EmptyArray.Instance, 0, 0);
- md5.TransformFinalBlock(EmptyArray.Instance, 0, 0);
+ var result = new byte[length];
- var result = new byte[length];
+ Array.Copy(md5.Hash, result, length);
- Array.Copy(md5.Hash, result, length);
-
- return result;
+ return result;
+ }
}
}
diff --git a/src/UglyToad.PdfPig/Encryption/UserAccessPermissions.cs b/src/UglyToad.PdfPig/Encryption/UserAccessPermissions.cs
new file mode 100644
index 00000000..a8ba2b4a
--- /dev/null
+++ b/src/UglyToad.PdfPig/Encryption/UserAccessPermissions.cs
@@ -0,0 +1,51 @@
+namespace UglyToad.PdfPig.Encryption
+{
+ using System;
+
+ [Flags]
+ internal enum UserAccessPermissions
+ {
+ ///
+ /// (Revision 2) Print the document.
+ /// (Revision 3 or greater) Print the document (possibly not at the highest quality level, see ).
+ ///
+ Print = 1 << 2,
+ ///
+ /// Modify the contents of the document by operations other than those
+ /// controlled by , and .
+ ///
+ Modify = 1 << 3,
+ ///
+ /// (Revision 2) Copy or otherwise extract text and graphics from the document, including extracting text and graphics
+ /// (in support of accessibility to users with disabilities or for other purposes).
+ /// (Revision 3 or greater) Copy or otherwise extract text and graphics from the document by operations other
+ /// than that controlled by .
+ ///
+ CopyTextAndGraphics = 1 << 4,
+ ///
+ /// Add or modify text annotations, fill in interactive form fields, and, if is also set,
+ /// create or modify interactive form fields (including signature fields).
+ ///
+ AddOrModifyTextAnnotationsAndFillFormFields = 1 << 5,
+ ///
+ /// (Revision 3 or greater) Fill in existing interactive form fields (including signature fields),
+ /// even if is clear.
+ ///
+ FillExistingFormFields = 1 << 8,
+ ///
+ /// (Revision 3 or greater) Extract text and graphics (in support of accessibility to users with disabilities or for other purposes).
+ ///
+ ExtractTextAndGraphics = 1 << 9,
+ ///
+ /// (Revision 3 or greater) Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images),
+ /// even if is clear.
+ ///
+ AssembleDocument = 1 << 10,
+ ///
+ /// (Revision 3 or greater) Print the document to a representation from which a faithful digital copy of the PDF content could be generated.
+ /// When this is clear (and is set), printing is limited to a low-level representation of the appearance,
+ /// possibly of degraded quality.
+ ///
+ PrintHighQuality = 1 << 12
+ }
+}
\ No newline at end of file
diff --git a/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs b/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs
index 0e298b24..4d87064b 100644
--- a/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs
+++ b/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs
@@ -39,6 +39,7 @@
public static readonly NameToken Ascii85Decode = new NameToken("ASCII85Decode");
public static readonly NameToken Ascii85DecodeAbbreviation = new NameToken("A85");
public static readonly NameToken Attached = new NameToken("Attached");
+ public static readonly NameToken AuthEvent = new NameToken("AuthEvent");
public static readonly NameToken Author = new NameToken("Author");
public static readonly NameToken AvgWidth = new NameToken("AvgWidth");
#endregion
@@ -126,6 +127,7 @@
public static readonly NameToken Creator = new NameToken("Creator");
public static readonly NameToken CropBox = new NameToken("CropBox");
public static readonly NameToken Crypt = new NameToken("Crypt");
+ public static readonly NameToken CryptFilter = new NameToken("CryptFilter");
public static readonly NameToken Cs = new NameToken("CS");
// D
public static readonly NameToken D = new NameToken("D");
@@ -164,6 +166,7 @@
public static readonly NameToken Dl = new NameToken("DL");
public static readonly NameToken Dm = new NameToken("Dm");
public static readonly NameToken Doc = new NameToken("Doc");
+ public static readonly NameToken DocOpen = new NameToken("DocOpen");
public static readonly NameToken DocChecksum = new NameToken("DocChecksum");
public static readonly NameToken DocTimeStamp = new NameToken("DocTimeStamp");
public static readonly NameToken Docmdp = new NameToken("DocMDP");
@@ -181,6 +184,7 @@
public static readonly NameToken E = new NameToken("E");
public static readonly NameToken EarlyChange = new NameToken("EarlyChange");
public static readonly NameToken Ef = new NameToken("EF");
+ public static readonly NameToken EfOpen = new NameToken("EFOpen");
public static readonly NameToken EmbeddedFdfs = new NameToken("EmbeddedFDFs");
public static readonly NameToken EmbeddedFiles = new NameToken("EmbeddedFiles");
public static readonly NameToken Empty = new NameToken("");
@@ -527,6 +531,7 @@
public static readonly NameToken UserUnit = new NameToken("UserUnit");
// V
public static readonly NameToken V = new NameToken("V");
+ public static readonly NameToken V2 = new NameToken("V2");
public static readonly NameToken VerisignPpkvs = new NameToken("VeriSign.PPKVS");
public static readonly NameToken Version = new NameToken("Version");
public static readonly NameToken Vertices = new NameToken("Vertices");