/// <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()); } }
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)); } }