예제 #1
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());
            }
        }
예제 #2
0
        private void CompleteDocument()
        {
            // write fonts to reserved object numbers
            foreach (var font in fonts)
            {
                font.Value.FontProgram.WriteFont(context, font.Value.FontKey.Reference);
            }

            const int desiredLeafSize = 25; // allow customization at some point?
            var       numLeafs        = (int)Math.Ceiling(Decimal.Divide(Pages.Count, desiredLeafSize));

            var leafRefs     = new List <IndirectReferenceToken>();
            var leafChildren = new List <List <IndirectReferenceToken> >();
            var leafs        = new List <Dictionary <NameToken, IToken> >();

            for (var i = 0; i < numLeafs; i++)
            {
                leafs.Add(new Dictionary <NameToken, IToken>()
                {
                    { NameToken.Type, NameToken.Pages },
                });
                leafChildren.Add(new List <IndirectReferenceToken>());
                leafRefs.Add(context.ReserveObjectNumber());
            }

            int leafNum = 0;

            foreach (var page in pages)
            {
                var pageDictionary = page.Value.pageDictionary;
                pageDictionary[NameToken.Type]    = NameToken.Page;
                pageDictionary[NameToken.Parent]  = leafRefs[leafNum];
                pageDictionary[NameToken.ProcSet] = DefaultProcSet;
                if (!pageDictionary.ContainsKey(NameToken.MediaBox))
                {
                    pageDictionary[NameToken.MediaBox] = RectangleToArray(page.Value.PageSize);
                }

                // Adobe Acrobat errors if content streams ref'd by multiple pages, turn off
                // dedup if on to avoid issues
                var prev = context.AttemptDeduplication;
                context.AttemptDeduplication = false;

                var toWrite = page.Value.contentStreams.Where(x => x.HasContent).ToList();
                if (toWrite.Count == 0)
                {
                    pageDictionary[NameToken.Contents] = new PdfPageBuilder.DefaultContentStream().Write(context);
                }
                else if (toWrite.Count == 1)
                {
                    // write single
                    pageDictionary[NameToken.Contents] = toWrite[0].Write(context);
                }
                else
                {
                    // write array
                    var streams = new List <IToken>();
                    foreach (var stream in toWrite)
                    {
                        streams.Add(stream.Write(context));
                    }
                    pageDictionary[NameToken.Contents] = new ArrayToken(streams);
                }
                context.AttemptDeduplication = prev;;

                leafChildren[leafNum].Add(context.WriteToken(new DictionaryToken(pageDictionary)));

                if (leafChildren[leafNum].Count >= desiredLeafSize)
                {
                    leafNum += 1;
                }
            }

            var dummyName = NameToken.Create("ObjIdToUse");

            for (var i = 0; i < leafs.Count; i++)
            {
                leafs[i][NameToken.Kids]  = new ArrayToken(leafChildren[i]);
                leafs[i][NameToken.Count] = new NumericToken(leafChildren[i].Count);
                leafs[i][dummyName]       = leafRefs[i];
            }

            var catalogDictionary = new Dictionary <NameToken, IToken>
            {
                { NameToken.Type, NameToken.Catalog },
            };

            if (leafs.Count == 1)
            {
                var leaf = leafs[0];
                var id   = leaf[dummyName] as IndirectReferenceToken;
                leaf.Remove(dummyName);
                catalogDictionary[NameToken.Pages] = context.WriteToken(new DictionaryToken(leaf), id);
            }
            else
            {
                var rootPageInfo = CreatePageTree(leafs, null);
                catalogDictionary[NameToken.Pages] = rootPageInfo.Ref;
            }

            if (ArchiveStandard != PdfAStandard.None)
            {
                Func <IToken, IndirectReferenceToken> writerFunc = x => context.WriteToken(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.WriteToken(catalog);

            var informationReference = default(IndirectReferenceToken);

            if (IncludeDocumentInformation)
            {
                var informationDictionary = DocumentInformation.ToDictionary();
                if (informationDictionary.Count > 0)
                {
                    var dictionary = new DictionaryToken(informationDictionary);
                    informationReference = context.WriteToken(dictionary);
                }
            }

            context.CompletePdf(catalogRef, informationReference);

            completed = true;

            (int Count, IndirectReferenceToken Ref) CreatePageTree(List <Dictionary <NameToken, IToken> > pagesNodes, IndirectReferenceToken parent)
            {
                // TODO shorten page tree when there is a single or small number of pages left in a branch
                var count   = 0;
                var thisObj = context.ReserveObjectNumber();

                var children = new List <IndirectReferenceToken>();

                if (pagesNodes.Count > desiredLeafSize)
                {
                    var currentTreeDepth = (int)Math.Ceiling(Math.Log(pagesNodes.Count, desiredLeafSize));
                    var perBranch        = (int)Math.Ceiling(Math.Pow(desiredLeafSize, currentTreeDepth - 1));
                    var branches         = (int)Math.Ceiling(decimal.Divide(pagesNodes.Count, (decimal)perBranch));
                    for (var i = 0; i < branches; i++)
                    {
                        var part   = pagesNodes.Skip(i * perBranch).Take(perBranch).ToList();
                        var result = CreatePageTree(part, thisObj);
                        count += result.Count;
                        children.Add(result.Ref);
                    }
                }
                else
                {
                    foreach (var page in pagesNodes)
                    {
                        page[NameToken.Parent] = thisObj;
                        var id = page[dummyName] as IndirectReferenceToken;
                        page.Remove(dummyName);
                        count += (page[NameToken.Count] as NumericToken).Int;
                        children.Add(context.WriteToken(new DictionaryToken(page), id));
                    }
                }

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

                if (parent != null)
                {
                    node[NameToken.Parent] = parent;
                }
                return(count, context.WriteToken(new DictionaryToken(node), thisObj));
            }
        }