public static ArrayToken GetOutputIntentsArray(Func <IToken, ObjectToken> objectWriter)
        {
            var rgbColorCondition = new StringToken(SrgbIec61966OutputCondition);

            var profileBytes = ProfileStreamReader.GetSRgb2014();

            var compressedBytes = DataCompresser.CompressBytes(profileBytes);

            var profileStreamDictionary = new Dictionary <NameToken, IToken>
            {
                { NameToken.Length, new NumericToken(compressedBytes.Length) },
                { NameToken.N, new NumericToken(3) },
                { NameToken.Filter, NameToken.FlateDecode }
            };

            var stream = new StreamToken(new DictionaryToken(profileStreamDictionary), compressedBytes);

            var written = objectWriter(stream);

            return(new ArrayToken(new IToken[]
            {
                new DictionaryToken(new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.OutputIntent },
                    { NameToken.S, NameToken.GtsPdfa1 },
                    { NameToken.OutputCondition, rgbColorCondition },
                    { NameToken.OutputConditionIdentifier, rgbColorCondition },
                    { NameToken.RegistryName, new StringToken(RegistryName) },
                    { NameToken.Info, rgbColorCondition },
                    { NameToken.DestOutputProfile, new IndirectReferenceToken(written.Number) }
                }),
            }));
        }
        public void ApplyXObject(StreamToken xObjectStream)
        {
            // For now we will determine the type and store the object with the graphics state information preceding it.
            // Then consumers of the page can request the object/s to be retrieved by type.
            var subType = (NameToken)xObjectStream.StreamDictionary.Data[NameToken.Subtype.Data];

            var state = GetCurrentState();

            var matrix = state.CurrentTransformationMatrix;

            if (subType.Equals(NameToken.Ps))
            {
                xObjects[XObjectType.PostScript].Add(new XObjectContentRecord(XObjectType.PostScript, xObjectStream, matrix));
            }
            else if (subType.Equals(NameToken.Image))
            {
                xObjects[XObjectType.Image].Add(new XObjectContentRecord(XObjectType.Image, xObjectStream, matrix));
            }
            else if (subType.Equals(NameToken.Form))
            {
                xObjects[XObjectType.Form].Add(new XObjectContentRecord(XObjectType.Form, xObjectStream, matrix));
            }
            else
            {
                throw new InvalidOperationException($"XObject encountered with unexpected SubType {subType}. {xObjectStream.StreamDictionary}.");
            }
        }
Exemple #3
0
 internal EmbeddedFile(string name, string fileSpecification, IReadOnlyList <byte> bytes, StreamToken stream)
 {
     Name = name ?? throw new ArgumentNullException(nameof(name));
     FileSpecification = fileSpecification;
     Bytes             = bytes ?? throw new ArgumentNullException(nameof(bytes));
     Stream            = stream ?? throw new ArgumentNullException(nameof(stream));
 }
 public XObjectContentRecord(XObjectType type, StreamToken stream, TransformationMatrix appliedTransformation,
                             RenderingIntent defaultRenderingIntent)
 {
     Type   = type;
     Stream = stream ?? throw new ArgumentNullException(nameof(stream));
     AppliedTransformation  = appliedTransformation;
     DefaultRenderingIntent = defaultRenderingIntent;
 }
Exemple #5
0
 private static void WriteStream(StreamToken streamToken, Stream outputStream)
 {
     WriteDictionary(streamToken.StreamDictionary, outputStream);
     WriteLineBreak(outputStream);
     outputStream.Write(StreamStart, 0, StreamStart.Length);
     WriteLineBreak(outputStream);
     outputStream.Write(streamToken.Data.ToArray(), 0, streamToken.Data.Count);
     WriteLineBreak(outputStream);
     outputStream.Write(StreamEnd, 0, StreamEnd.Length);
 }
        public StreamToken GetToken()
        {
            RemoveExpiredTokens();
            var token = new StreamToken()
            {
                Token     = Guid.NewGuid().ToString(),
                ExpiresOn = DateTime.Now.AddHours(1)
            };

            _tokens.Add(token);
            return(token);
        }
Exemple #7
0
        public static StreamToken CompressToStream(byte[] bytes)
        {
            var compressed = CompressBytes(bytes);
            var stream     = new StreamToken(new DictionaryToken(new Dictionary <NameToken, IToken>
            {
                { NameToken.Length, new NumericToken(compressed.Length) },
                { NameToken.Length1, new NumericToken(bytes.Length) },
                { NameToken.Filter, new ArrayToken(new [] { NameToken.FlateDecode }) }
            }), compressed);

            return(stream);
        }
        private void ProcessFormXObject(StreamToken formStream)
        {
            /*
             * When a form XObject is invoked the following should happen:
             *
             * 1. Save the current graphics state, as if by invoking the q operator.
             * 2. Concatenate the matrix from the form dictionary's Matrix entry with the current transformation matrix.
             * 3. Clip according to the form dictionary's BBox entry.
             * 4. Paint the graphics objects specified in the form's content stream.
             * 5. Restore the saved graphics state, as if by invoking the Q operator.
             */

            var hasResources = formStream.StreamDictionary.TryGet <DictionaryToken>(NameToken.Resources, pdfScanner, out var formResources);

            if (hasResources)
            {
                resourceStore.LoadResourceDictionary(formResources, isLenientParsing);
            }

            // 1. Save current state.
            PushState();

            var startState = GetCurrentState();

            var formMatrix = TransformationMatrix.Identity;

            if (formStream.StreamDictionary.TryGet <ArrayToken>(NameToken.Matrix, pdfScanner, out var formMatrixToken))
            {
                formMatrix = TransformationMatrix.FromArray(formMatrixToken.Data.OfType <NumericToken>().Select(x => (double)x.Data).ToArray());
            }

            // 2. Update current transformation matrix.
            var resultingTransformationMatrix = startState.CurrentTransformationMatrix.Multiply(formMatrix);

            startState.CurrentTransformationMatrix = resultingTransformationMatrix;

            var contentStream = formStream.Decode(filterProvider);

            var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentStream));

            // 3. We don't respect clipping currently.

            // 4. Paint the objects.
            ProcessOperations(operations);

            // 5. Restore saved state.
            PopState();

            if (hasResources)
            {
                resourceStore.UnloadResourceDictionary();
            }
        }
Exemple #9
0
        internal static IReadOnlyList <byte> Decode(this StreamToken stream, IFilterProvider filterProvider)
        {
            var filters = filterProvider.GetFilters(stream.StreamDictionary);

            var transform = stream.Data;

            for (var i = 0; i < filters.Count; i++)
            {
                transform = filters[i].Decode(transform, stream.StreamDictionary, i);
            }

            return(transform);
        }
        public void ReplacesObjects()
        {
            var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");

            using (var document = PdfDocument.Open(path))
            {
                var dict = new Dictionary <NameToken, IToken>();
                dict[NameToken.Length] = new NumericToken(0);
                var replacement = new StreamToken(new DictionaryToken(dict), new List <byte>());

                var pg       = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
                var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
                document.Advanced.ReplaceIndirectObject(contents.Data, replacement);

                var page = document.GetPage(1);
                Assert.Empty(page.Letters);
            }
        }
Exemple #11
0
        private static StreamToken WriteContentStream(IReadOnlyList <IGraphicsStateOperation> content)
        {
            using (var memoryStream = new MemoryStream())
            {
                foreach (var operation in content)
                {
                    operation.Write(memoryStream);
                }

                var bytes = memoryStream.ToArray();

                var streamDictionary = new Dictionary <NameToken, IToken>
                {
                    { NameToken.Length, new NumericToken(bytes.Length) }
                };

                var stream = new StreamToken(new DictionaryToken(streamDictionary), bytes);

                return(stream);
            }
        }
Exemple #12
0
        internal IndirectReferenceToken AddImage(DictionaryToken dictionary, byte[] bytes)
        {
            var streamToken = new StreamToken(dictionary, bytes);

            return(context.WriteToken(streamToken));
        }
Exemple #13
0
        /// <summary>
        /// Builds a PDF document from the current content of this builder and its pages.
        /// </summary>
        /// <returns>The bytes of the resulting PDF document.</returns>
        public byte[] Build()
        {
            var fontsWritten = new Dictionary <Guid, ObjectToken>();

            using (var memory = new MemoryStream())
            {
                // Header
                WriteString("%PDF-1.7", memory);

                // Files with binary data should contain a 2nd comment line followed by 4 bytes with values > 127
                memory.WriteText("%");
                memory.WriteByte(169);
                memory.WriteByte(205);
                memory.WriteByte(196);
                memory.WriteByte(210);
                memory.WriteNewLine();

                // Body
                foreach (var font in fonts)
                {
                    var fontObj = font.Value.FontProgram.WriteFont(font.Value.FontKey.Name, memory, context);
                    fontsWritten.Add(font.Key, fontObj);
                }

                foreach (var image in images)
                {
                    var streamToken = new StreamToken(image.Value.StreamDictionary, image.Value.StreamData);

                    context.WriteObject(memory, streamToken, image.Value.ObjectNumber);
                }

                var procSet = new List <NameToken>
                {
                    NameToken.Create("PDF"),
                    NameToken.Text,
                    NameToken.ImageB,
                    NameToken.ImageC,
                    NameToken.ImageI
                };

                var resources = new Dictionary <NameToken, IToken>
                {
                    { NameToken.ProcSet, new ArrayToken(procSet) }
                };

                if (fontsWritten.Count > 0)
                {
                    var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => (fonts[x.Key].FontKey.Name, (IToken) new IndirectReferenceToken(x.Value.Number)))
                                                              .ToDictionary(x => x.Item1, x => x.Item2));

                    var fontsDictionaryRef = context.WriteObject(memory, fontsDictionary);

                    resources.Add(NameToken.Font, new IndirectReferenceToken(fontsDictionaryRef.Number));
                }

                var reserved       = context.ReserveNumber();
                var parentIndirect = new IndirectReferenceToken(new IndirectReference(reserved, 0));

                var pageReferences = new List <IndirectReferenceToken>();
                foreach (var page in pages)
                {
                    var individualResources = new Dictionary <NameToken, IToken>(resources);
                    var pageDictionary      = new Dictionary <NameToken, IToken>
                    {
                        { NameToken.Type, NameToken.Page },
                        { NameToken.MediaBox, RectangleToArray(page.Value.PageSize) },
                        { NameToken.Parent, parentIndirect }
                    };

                    if (page.Value.Resources.Count > 0)
                    {
                        foreach (var kvp in page.Value.Resources)
                        {
                            // TODO: combine resources if value is dictionary or array, otherwise overwrite.
                            individualResources[kvp.Key] = kvp.Value;
                        }
                    }

                    pageDictionary[NameToken.Resources] = new DictionaryToken(individualResources);

                    if (page.Value.Operations.Count > 0)
                    {
                        var contentStream = WriteContentStream(page.Value.Operations);

                        var contentStreamObj = context.WriteObject(memory, contentStream);

                        pageDictionary[NameToken.Contents] = new IndirectReferenceToken(contentStreamObj.Number);
                    }

                    var pageRef = context.WriteObject(memory, new DictionaryToken(pageDictionary));

                    pageReferences.Add(new IndirectReferenceToken(pageRef.Number));
                }

                var pagesDictionaryData = new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.Pages },
                    { NameToken.Kids, new ArrayToken(pageReferences) },
                    { NameToken.Count, new NumericToken(pageReferences.Count) }
                };

                var pagesDictionary = new DictionaryToken(pagesDictionaryData);

                var pagesRef = context.WriteObject(memory, pagesDictionary, reserved);

                var catalogDictionary = new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.Catalog },
                    { NameToken.Pages, new IndirectReferenceToken(pagesRef.Number) }
                };

                if (ArchiveStandard != PdfAStandard.None)
                {
                    Func <IToken, ObjectToken> writerFunc = x => context.WriteObject(memory, x);

                    PdfABaselineRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard);

                    switch (ArchiveStandard)
                    {
                    case PdfAStandard.A1A:
                        PdfA1ARuleBuilder.Obey(catalogDictionary);
                        break;

                    case PdfAStandard.A2B:
                        break;

                    case PdfAStandard.A2A:
                        PdfA1ARuleBuilder.Obey(catalogDictionary);
                        break;
                    }
                }

                var catalog = new DictionaryToken(catalogDictionary);

                var catalogRef = context.WriteObject(memory, catalog);

                var informationReference = default(IndirectReference?);
                if (IncludeDocumentInformation)
                {
                    var informationDictionary = DocumentInformation.ToDictionary();
                    if (informationDictionary.Count > 0)
                    {
                        var dictionary = new DictionaryToken(informationDictionary);
                        informationReference = context.WriteObject(memory, dictionary).Number;
                    }
                }

                TokenWriter.WriteCrossReferenceTable(context.ObjectOffsets, catalogRef, memory, informationReference);

                return(memory.ToArray());
            }
        }
Exemple #14
0
 internal XmpMetadata(StreamToken stream, IFilterProvider filterProvider)
 {
     this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
     MetadataStreamToken = stream ?? throw new ArgumentNullException(nameof(stream));
 }
Exemple #15
0
 internal XmpMetadata(StreamToken stream, ILookupFilterProvider filterProvider, IPdfTokenScanner pdfTokenScanner)
 {
     this.filterProvider  = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
     this.pdfTokenScanner = pdfTokenScanner;
     MetadataStreamToken  = stream ?? throw new ArgumentNullException(nameof(stream));
 }
Exemple #16
0
        public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
        {
            var bytes        = CompressBytes();
            var embeddedFile = new StreamToken(new DictionaryToken(new Dictionary <NameToken, IToken>
            {
                { NameToken.Length, new NumericToken(bytes.Length) },
                { NameToken.Length1, new NumericToken(fontFileBytes.Count) },
                { NameToken.Filter, new ArrayToken(new [] { NameToken.FlateDecode }) }
            }), bytes);

            var fileRef = context.WriteObject(outputStream, embeddedFile);

            var baseFont = NameToken.Create(font.TableRegister.NameTable.GetPostscriptName());

            var charCodeToGlyphId = new CharacterCodeToGlyphIdMapper(font);

            var postscript = font.TableRegister.PostScriptTable;
            var hhead      = font.TableRegister.HorizontalHeaderTable;

            var bbox = font.TableRegister.HeaderTable.Bounds;

            var scaling = 1000m / font.TableRegister.HeaderTable.UnitsPerEm;
            var descriptorDictionary = new Dictionary <NameToken, IToken>
            {
                { NameToken.Type, NameToken.FontDescriptor },
                { NameToken.FontName, baseFont },
                // TODO: get flags TrueTypeEmbedder.java
                { NameToken.Flags, new NumericToken((int)FontDescriptorFlags.Symbolic) },
                { NameToken.FontBbox, GetBoundingBox(bbox, scaling) },
                { NameToken.ItalicAngle, new NumericToken(postscript.ItalicAngle) },
                { NameToken.Ascent, new NumericToken(hhead.Ascender * scaling) },
                { NameToken.Descent, new NumericToken(hhead.Descender * scaling) },
                { NameToken.CapHeight, new NumericToken(90) },
                { NameToken.StemV, new NumericToken(90) },
                { NameToken.FontFile2, new IndirectReferenceToken(fileRef.Number) }
            };

            var os2 = font.TableRegister.Os2Table;

            if (os2 == null)
            {
                throw new InvalidFontFormatException("Embedding TrueType font requires OS/2 table.");
            }

            if (os2 is Os2Version2To4OpenTypeTable twoPlus)
            {
                descriptorDictionary[NameToken.CapHeight] = new NumericToken(twoPlus.CapHeight);
                descriptorDictionary[NameToken.Xheight]   = new NumericToken(twoPlus.XHeight);
            }

            descriptorDictionary[NameToken.StemV] = new NumericToken(bbox.Width * scaling * 0.13m);

            var metrics = charCodeToGlyphId.GetMetrics(scaling);

            var widthsRef = context.WriteObject(outputStream, metrics.Widths);

            var descriptor = context.WriteObject(outputStream, new DictionaryToken(descriptorDictionary));

            var dictionary = new Dictionary <NameToken, IToken>
            {
                { NameToken.Type, NameToken.Font },
                { NameToken.Subtype, NameToken.TrueType },
                { NameToken.BaseFont, baseFont },
                { NameToken.FontDescriptor, new IndirectReferenceToken(descriptor.Number) },
                { NameToken.FirstChar, metrics.FirstChar },
                { NameToken.LastChar, metrics.LastChar },
                { NameToken.Widths, new IndirectReferenceToken(widthsRef.Number) },
                { NameToken.Encoding, NameToken.WinAnsiEncoding }
            };

            var token = new DictionaryToken(dictionary);

            var result = context.WriteObject(outputStream, token);

            return(result);
        }
        /// <summary>
        /// Parses through the unfiltered stream and populates the xrefTable HashMap.
        /// </summary>
        public CrossReferenceTablePart Parse(long streamOffset, StreamToken stream)
        {
            var decoded = stream.Decode(filterProvider);

            var fieldSizes = new CrossReferenceStreamFieldSize(stream.StreamDictionary);

            var lineCount = decoded.Count / fieldSizes.LineLength;

            long previousOffset = -1;

            if (stream.StreamDictionary.TryGet(NameToken.Prev, out var prevToken) && prevToken is NumericToken prevNumeric)
            {
                previousOffset = prevNumeric.Long;
            }

            var builder = new CrossReferenceTablePartBuilder
            {
                Offset     = streamOffset,
                Previous   = previousOffset,
                Dictionary = stream.StreamDictionary,
                XRefType   = CrossReferenceType.Stream
            };

            var objectNumbers = GetObjectNumbers(stream.StreamDictionary);

            var lineNumber = 0;
            var lineBuffer = new byte[fieldSizes.LineLength];

            foreach (var objectNumber in objectNumbers)
            {
                if (lineNumber >= lineCount)
                {
                    break;
                }

                var byteOffset = lineNumber * fieldSizes.LineLength;

                for (var i = 0; i < fieldSizes.LineLength; i++)
                {
                    lineBuffer[i] = decoded[byteOffset + i];
                }

                int type;
                if (fieldSizes.Field1Size == 0)
                {
                    type = 1;
                }
                else
                {
                    type = 0;

                    for (var i = 0; i < fieldSizes.Field1Size; i++)
                    {
                        type += (lineBuffer[i] & 0x00ff) << ((fieldSizes.Field1Size - i - 1) * 8);
                    }
                }

                ReadNextStreamObject(type, objectNumber, fieldSizes, builder, lineBuffer);

                lineNumber++;
            }

            return(builder.Build());
        }
Exemple #18
0
        public static XObjectImage ReadImage(XObjectContentRecord xObject, IPdfTokenScanner pdfScanner,
                                             ILookupFilterProvider filterProvider,
                                             IResourceStore resourceStore)
        {
            if (xObject == null)
            {
                throw new ArgumentNullException(nameof(xObject));
            }

            if (xObject.Type != XObjectType.Image)
            {
                throw new InvalidOperationException($"Cannot create an image from an XObject with type: {xObject.Type}.");
            }

            var dictionary = xObject.Stream.StreamDictionary;

            var bounds = xObject.AppliedTransformation.Transform(new PdfRectangle(new PdfPoint(0, 0), new PdfPoint(1, 1)));

            var width  = dictionary.Get <NumericToken>(NameToken.Width, pdfScanner).Int;
            var height = dictionary.Get <NumericToken>(NameToken.Height, pdfScanner).Int;

            var isImageMask = dictionary.TryGet(NameToken.ImageMask, pdfScanner, out BooleanToken isMaskToken) &&
                              isMaskToken.Data;

            var isJpxDecode = dictionary.TryGet(NameToken.Filter, out var token) &&
                              token is NameToken filterName &&
                              filterName.Equals(NameToken.JpxDecode);

            int bitsPerComponent = 0;

            if (!isImageMask && !isJpxDecode)
            {
                if (!dictionary.TryGet(NameToken.BitsPerComponent, pdfScanner, out NumericToken bitsPerComponentToken))
                {
                    throw new PdfDocumentFormatException($"No bits per component defined for image: {dictionary}.");
                }

                bitsPerComponent = bitsPerComponentToken.Int;
            }
            else if (isImageMask)
            {
                bitsPerComponent = 1;
            }

            var intent = xObject.DefaultRenderingIntent;

            if (dictionary.TryGet(NameToken.Intent, out NameToken renderingIntentToken))
            {
                intent = renderingIntentToken.Data.ToRenderingIntent();
            }

            var interpolate = dictionary.TryGet(NameToken.Interpolate, pdfScanner, out BooleanToken interpolateToken) &&
                              interpolateToken.Data;

            DictionaryToken filterDictionary = xObject.Stream.StreamDictionary;

            if (xObject.Stream.StreamDictionary.TryGet(NameToken.Filter, out var filterToken) &&
                filterToken is IndirectReferenceToken)
            {
                if (filterDictionary.TryGet(NameToken.Filter, pdfScanner, out ArrayToken filterArray))
                {
                    filterDictionary = filterDictionary.With(NameToken.Filter, filterArray);
                }
                else if (filterDictionary.TryGet(NameToken.Filter, pdfScanner, out NameToken filterNameToken))
                {
                    filterDictionary = filterDictionary.With(NameToken.Filter, filterNameToken);
                }
                else
                {
                    filterDictionary = null;
                }
            }

            var supportsFilters = filterDictionary != null;

            if (filterDictionary != null)
            {
                var filters = filterProvider.GetFilters(filterDictionary, pdfScanner);
                foreach (var filter in filters)
                {
                    if (!filter.IsSupported)
                    {
                        supportsFilters = false;
                        break;
                    }
                }
            }

            var decodeParams = dictionary.GetObjectOrDefault(NameToken.DecodeParms, NameToken.Dp);

            if (decodeParams is IndirectReferenceToken refToken)
            {
                dictionary = dictionary.With(NameToken.DecodeParms, pdfScanner.Get(refToken.Data).Data);
            }

            var streamToken = new StreamToken(dictionary, xObject.Stream.Data);

            var decodedBytes = supportsFilters ? new Lazy <IReadOnlyList <byte> >(() => streamToken.Decode(filterProvider, pdfScanner))
                : null;

            var decode = EmptyArray <decimal> .Instance;

            if (dictionary.TryGet(NameToken.Decode, pdfScanner, out ArrayToken decodeArrayToken))
            {
                decode = decodeArrayToken.Data.OfType <NumericToken>()
                         .Select(x => x.Data)
                         .ToArray();
            }

            var colorSpace = default(ColorSpace?);

            if (!isImageMask)
            {
                if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out NameToken colorSpaceNameToken) &&
                    TryMapColorSpace(colorSpaceNameToken, resourceStore, out var colorSpaceResult))
                {
                    colorSpace = colorSpaceResult;
                }
                else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken colorSpaceArrayToken) &&
                         colorSpaceArrayToken.Length > 0)
                {
                    var first = colorSpaceArrayToken.Data[0];

                    if ((first is NameToken firstColorSpaceName) && TryMapColorSpace(firstColorSpaceName, resourceStore, out colorSpaceResult))
                    {
                        colorSpace = colorSpaceResult;
                    }
                }
                else if (!isJpxDecode)
                {
                    colorSpace = xObject.DefaultColorSpace;
                }
            }

            var details = ColorSpaceDetailsParser.GetColorSpaceDetails(colorSpace, dictionary, pdfScanner, resourceStore, filterProvider);

            return(new XObjectImage(
                       bounds,
                       width,
                       height,
                       bitsPerComponent,
                       colorSpace,
                       isJpxDecode,
                       isImageMask,
                       intent,
                       interpolate,
                       decode,
                       dictionary,
                       xObject.Stream.Data,
                       decodedBytes,
                       details));
        }
 public void ApplyXObject(StreamToken xObjectStream)
 {
 }
Exemple #20
0
        private IToken DecryptInternal(IndirectReference reference, IToken token)
        {
            switch (token)
            {
            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))
                    {
                        return(token);
                    }

                    if (!encryptionDictionary.EncryptMetadata && NameToken.Metadata.Equals(typeName))
                    {
                        return(token);
                    }

                    // TODO: check unencrypted metadata
                }

                var streamDictionary = (DictionaryToken)DecryptInternal(reference, stream.StreamDictionary);

                var decrypted = DecryptData(stream.Data.ToArray(), reference);

                token = new StreamToken(streamDictionary, decrypted);

                break;
            }

            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);

                token = GetStringTokenFromDecryptedData(decrypted);

                break;
            }

            case HexToken hexToken:
            {
                var data = hexToken.Bytes.ToArray();

                var decrypted = DecryptData(data, reference);

                token = new HexToken(Hex.GetString(decrypted).ToCharArray());

                break;
            }

            case DictionaryToken dictionary:
            {
                // PDFBOX-2936: avoid orphan /CF dictionaries found in US govt "I-" files
                if (dictionary.TryGet(NameToken.Cf, out _))
                {
                    return(token);
                }

                var isSignatureDictionary = dictionary.TryGet(NameToken.Type, out NameToken typeName) &&
                                            (typeName.Equals(NameToken.Sig) || typeName.Equals(NameToken.DocTimeStamp));

                foreach (var keyValuePair in dictionary.Data)
                {
                    if (isSignatureDictionary && keyValuePair.Key == NameToken.Contents.Data)
                    {
                        continue;
                    }

                    if (keyValuePair.Value is StringToken || keyValuePair.Value is ArrayToken ||
                        keyValuePair.Value is DictionaryToken ||
                        keyValuePair.Value is HexToken)
                    {
                        var inner = DecryptInternal(reference, keyValuePair.Value);
                        dictionary = dictionary.With(keyValuePair.Key, inner);
                    }
                }

                token = dictionary;

                break;
            }

            case ArrayToken array:
            {
                var result = new IToken[array.Length];

                for (var i = 0; i < array.Length; i++)
                {
                    result[i] = DecryptInternal(reference, array.Data[i]);
                }

                token = new ArrayToken(result);

                break;
            }
            }

            return(token);
        }