From 1db481164cf4862de750296a9fef29971ec31004 Mon Sep 17 00:00:00 2001 From: Plaisted Date: Sat, 6 Feb 2021 18:04:13 -0600 Subject: [PATCH] perf improvement for copying lots of pages from large documents --- .../Writer/PdfDocumentBuilderTests.cs | 242 ++++---- .../Writer/PdfDocumentBuilder.cs | 560 +++++++++--------- 2 files changed, 411 insertions(+), 391 deletions(-) diff --git a/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs b/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs index e1f57ead..ad9f5d98 100644 --- a/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs +++ b/src/UglyToad.PdfPig.Tests/Writer/PdfDocumentBuilderTests.cs @@ -689,76 +689,76 @@ Assert.Equal("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", page2.Text); } - } - - [Fact] - public void CanAddHelloWorldToSimplePage() - { - var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - var doc = PdfDocument.Open(path); - var builder = new PdfDocumentBuilder(); - - var page = builder.AddPage(doc, 1); - - page.DrawLine(new PdfPoint(30, 520), new PdfPoint(360, 520)); - page.DrawLine(new PdfPoint(360, 520), new PdfPoint(360, 250)); - - page.SetStrokeColor(250, 132, 131); - page.DrawLine(new PdfPoint(25, 70), new PdfPoint(100, 70), 3); - page.ResetColor(); - page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m); - page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m); - - var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf"); - - var font = builder.AddTrueTypeFont(file); - - var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font); - - Assert.NotEmpty(page.CurrentStream.Operations); - - var b = builder.Build(); - - WriteFile(nameof(CanAddHelloWorldToSimplePage), b); - - Assert.NotEmpty(b); - - using (var document = PdfDocument.Open(b)) - { - var page1 = document.GetPage(1); - - Assert.Equal("I am a simple pdf.Hello World!", page1.Text); - - var h = page1.Letters[18]; - - Assert.Equal("H", h.Value); - Assert.Equal("Andada-Regular", h.FontName); - - var comparer = new DoubleComparer(0.01); - var pointComparer = new PointComparer(comparer); - - for (int i = 0; i < letters.Count; i++) - { - var readerLetter = page1.Letters[i+18]; - var writerLetter = letters[i]; - - Assert.Equal(readerLetter.Value, writerLetter.Value); - Assert.Equal(readerLetter.Location, writerLetter.Location, pointComparer); - Assert.Equal(readerLetter.FontSize, writerLetter.FontSize, comparer); - Assert.Equal(readerLetter.GlyphRectangle.Width, writerLetter.GlyphRectangle.Width, comparer); - Assert.Equal(readerLetter.GlyphRectangle.Height, writerLetter.GlyphRectangle.Height, comparer); - Assert.Equal(readerLetter.GlyphRectangle.BottomLeft, writerLetter.GlyphRectangle.BottomLeft, pointComparer); - } - } - } - - [Fact] - public void CanMerge2SimpleDocumentsReversed_Builder() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); - - + } + + [Fact] + public void CanAddHelloWorldToSimplePage() + { + var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + var doc = PdfDocument.Open(path); + var builder = new PdfDocumentBuilder(); + + var page = builder.AddPage(doc, 1); + + page.DrawLine(new PdfPoint(30, 520), new PdfPoint(360, 520)); + page.DrawLine(new PdfPoint(360, 520), new PdfPoint(360, 250)); + + page.SetStrokeColor(250, 132, 131); + page.DrawLine(new PdfPoint(25, 70), new PdfPoint(100, 70), 3); + page.ResetColor(); + page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m); + page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m); + + var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf"); + + var font = builder.AddTrueTypeFont(file); + + var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font); + + Assert.NotEmpty(page.CurrentStream.Operations); + + var b = builder.Build(); + + WriteFile(nameof(CanAddHelloWorldToSimplePage), b); + + Assert.NotEmpty(b); + + using (var document = PdfDocument.Open(b)) + { + var page1 = document.GetPage(1); + + Assert.Equal("I am a simple pdf.Hello World!", page1.Text); + + var h = page1.Letters[18]; + + Assert.Equal("H", h.Value); + Assert.Equal("Andada-Regular", h.FontName); + + var comparer = new DoubleComparer(0.01); + var pointComparer = new PointComparer(comparer); + + for (int i = 0; i < letters.Count; i++) + { + var readerLetter = page1.Letters[i+18]; + var writerLetter = letters[i]; + + Assert.Equal(readerLetter.Value, writerLetter.Value); + Assert.Equal(readerLetter.Location, writerLetter.Location, pointComparer); + Assert.Equal(readerLetter.FontSize, writerLetter.FontSize, comparer); + Assert.Equal(readerLetter.GlyphRectangle.Width, writerLetter.GlyphRectangle.Width, comparer); + Assert.Equal(readerLetter.GlyphRectangle.Height, writerLetter.GlyphRectangle.Height, comparer); + Assert.Equal(readerLetter.GlyphRectangle.BottomLeft, writerLetter.GlyphRectangle.BottomLeft, pointComparer); + } + } + } + + [Fact] + public void CanMerge2SimpleDocumentsReversed_Builder() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + + using (var docOne = PdfDocument.Open(one)) using (var docTwo = PdfDocument.Open(two)) { @@ -768,12 +768,12 @@ var result = builder.Build(); PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape", false); } - } - - [Fact] - public void CanMerge2SimpleDocuments_Builder() - { - var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); + } + + [Fact] + public void CanMerge2SimpleDocuments_Builder() + { + var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); using (var docOne = PdfDocument.Open(one)) @@ -787,12 +787,12 @@ PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.", false); } - - } - - [Fact] - public void CanDedupObjectsFromSameDoc_Builder() - { + + } + + [Fact] + public void CanDedupObjectsFromSameDoc_Builder() + { var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); using (var doc = PdfDocument.Open(one)) @@ -810,11 +810,11 @@ "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use } } - } - - [Fact] - public void CanDedupObjectsFromDifferentDoc_HashBuilder() - { + } + + [Fact] + public void CanDedupObjectsFromDifferentDoc_HashBuilder() + { var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); using (var doc = PdfDocument.Open(one)) using (var doc2 = PdfDocument.Open(one)) @@ -832,19 +832,19 @@ "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use } } - } - - [InlineData("Single Page Simple - from google drive.pdf")] - [InlineData("Old Gutnish Internet Explorer.pdf")] - [InlineData("68-1990-01_A.pdf")] - [InlineData("Multiple Page - from Mortality Statistics.pdf")] - [Theory] - public void CopiedPagesResultInSameData(string name) - { - var docPath = IntegrationHelpers.GetDocumentPath(name); - + } + + [InlineData("Single Page Simple - from google drive.pdf")] + [InlineData("Old Gutnish Internet Explorer.pdf")] + [InlineData("68-1990-01_A.pdf")] + [InlineData("Multiple Page - from Mortality Statistics.pdf")] + [Theory] + public void CopiedPagesResultInSameData(string name) + { + var docPath = IntegrationHelpers.GetDocumentPath(name); + using (var doc = PdfDocument.Open(docPath, ParsingOptions.LenientParsingOff)) - using (var builder = new PdfDocumentBuilder()) + using (var builder = new PdfDocumentBuilder()) { var count1 = GetCounts(doc); @@ -859,30 +859,30 @@ var count2 = GetCounts(doc2); Assert.Equal(count1.Item1, count2.Item1); Assert.Equal(count1.Item2, count2.Item2); - } - } - - - (int, double) GetCounts(PdfDocument toCount) - { - int letters = 0; - double location = 0; - foreach (var page in toCount.GetPages()) - { - foreach (var letter in page.Letters) - { - - unchecked { letters += 1; } - unchecked { - location += letter.Location.X; + } + } + + + (int, double) GetCounts(PdfDocument toCount) + { + int letters = 0; + double location = 0; + foreach (var page in toCount.GetPages()) + { + foreach (var letter in page.Letters) + { + + unchecked { letters += 1; } + unchecked { + location += letter.Location.X; location += letter.Location.Y; location += letter.Font.Name.Length; - } - } - } - - return (letters, location); - } + } + } + } + + return (letters, location); + } } private static void WriteFile(string name, byte[] bytes, string extension = "pdf") diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index 805665d9..d1dda73a 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -269,217 +269,237 @@ namespace UglyToad.PdfPig.Writer } return WriterUtil.CopyToken(context, token, source, refs); - } + } + internal class PageInfo + { + public DictionaryToken Page { get; set; } + public List Parents { get; set; } + } private readonly ConditionalWeakTable> existingCopies = - new ConditionalWeakTable>(); - /// - /// Add a new page with the specified size, this page will be included in the output when is called. - /// - /// Source document. - /// Page to copy. - /// A builder for editing the page. - public PdfPageBuilder AddPage(PdfDocument document, int pageNumber) - { - if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var refs)) - { - refs = new Dictionary(); - existingCopies.Add(document.Structure.TokenScanner, refs); - } - - int i = 1; - foreach (var (pageDict, parents) in WriterUtil.WalkTree(document.Structure.Catalog.PageTree)) - { - if (i == pageNumber) - { - // copy content streams - var streams = new List(); - if (pageDict.ContainsKey(NameToken.Contents)) - { - var token = pageDict.Data[NameToken.Contents]; - if (token is ArrayToken array) - { - foreach (var item in array.Data) - { - if (item is IndirectReferenceToken ir) - { - streams.Add(new PdfPageBuilder.CopiedContentStream( - WriterUtil.CopyToken(context, ir, document.Structure.TokenScanner, refs) as IndirectReferenceToken)); - } - - } - } - else if (token is IndirectReferenceToken ir) - { - streams.Add(new PdfPageBuilder.CopiedContentStream( - WriterUtil.CopyToken(context, ir, document.Structure.TokenScanner, refs) as IndirectReferenceToken)); - } - } - - // manually copy page dict / resources as we need to modify some - var copiedPageDict = new Dictionary(); - Dictionary resources = new Dictionary(); - - // just put all parent resources into new page - foreach (var dict in parents) - { - if (dict.TryGet(NameToken.Resources, out var token)) - { - CopyResourceDict(token, resources); - } - } - - - foreach (var kvp in pageDict.Data) + new ConditionalWeakTable>(); + private readonly ConditionalWeakTable> existingTrees = + new ConditionalWeakTable>(); + /// + /// Add a new page with the specified size, this page will be included in the output when is called. + /// + /// Source document. + /// Page to copy. + /// A builder for editing the page. + public PdfPageBuilder AddPage(PdfDocument document, int pageNumber) + { + if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var refs)) + { + refs = new Dictionary(); + existingCopies.Add(document.Structure.TokenScanner, refs); + } + + if (!existingTrees.TryGetValue(document, out var pagesInfos)) + { + pagesInfos = new Dictionary(); + int i = 1; + foreach (var (pageDict, parents) in WriterUtil.WalkTree(document.Structure.Catalog.PageTree)) + { + pagesInfos[i] = new PageInfo { - if (kvp.Key == NameToken.Contents || kvp.Key == NameToken.Parent || kvp.Key == NameToken.Type) + Page = pageDict, Parents = parents + }; + i++; + } + + existingTrees.Add(document, pagesInfos); + } + + if (!pagesInfos.ContainsKey(pageNumber)) + { + throw new KeyNotFoundException($"Page {pageNumber} was not found in the source document."); + } + + var pageInfo = pagesInfos[pageNumber]; + + // copy content streams + var streams = new List(); + if (pageInfo.Page.ContainsKey(NameToken.Contents)) + { + var token = pageInfo.Page.Data[NameToken.Contents]; + if (token is ArrayToken array) + { + foreach (var item in array.Data) + { + if (item is IndirectReferenceToken ir) { - continue; + streams.Add(new PdfPageBuilder.CopiedContentStream( + WriterUtil.CopyToken(context, ir, document.Structure.TokenScanner, refs) as IndirectReferenceToken)); } - if (kvp.Key == NameToken.Resources) + } + } + else if (token is IndirectReferenceToken ir) + { + streams.Add(new PdfPageBuilder.CopiedContentStream( + WriterUtil.CopyToken(context, ir, document.Structure.TokenScanner, refs) as IndirectReferenceToken)); + } + } + + // manually copy page dict / resources as we need to modify some + var copiedPageDict = new Dictionary(); + Dictionary resources = new Dictionary(); + + // just put all parent resources into new page + foreach (var dict in pageInfo.Parents) + { + if (dict.TryGet(NameToken.Resources, out var token)) + { + CopyResourceDict(token, resources); + } + } + + + foreach (var kvp in pageInfo.Page.Data) + { + if (kvp.Key == NameToken.Contents || kvp.Key == NameToken.Parent || kvp.Key == NameToken.Type) + { + continue; + } + + if (kvp.Key == NameToken.Resources) + { + CopyResourceDict(kvp.Value, resources); + continue; + } + + copiedPageDict[NameToken.Create(kvp.Key)] = + WriterUtil.CopyToken(context, kvp.Value, document.Structure.TokenScanner, refs); + } + + var builder = new PdfPageBuilder(pages.Count + 1, this, streams, resources, copiedPageDict); + if (resources.TryGetValue(NameToken.Font, out var fonts)) + { + var existingFontDict = fonts as DictionaryToken; + foreach (var item in existingFontDict.Data) + { + var key = NameToken.Create(item.Key); + builder.fontDictionary[key] = item.Value; + } + + resources.Remove(NameToken.Font); + } + + pages[builder.PageNumber] = builder; + return builder; + + + void CopyResourceDict(IToken token, Dictionary destinationDict) + { + DictionaryToken dict = GetRemoteDict(token); + if (dict == null) + { + return; + } + foreach (var item in dict.Data) + { + + if (!destinationDict.ContainsKey(NameToken.Create(item.Key))) + { + if (item.Value is IndirectReferenceToken ir) { - CopyResourceDict(kvp.Value, resources); - continue; + destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, document.Structure.TokenScanner.Get(ir.Data).Data, document.Structure.TokenScanner, refs); + } + else + { + destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs); } - copiedPageDict[NameToken.Create(kvp.Key)] = - WriterUtil.CopyToken(context, kvp.Value, document.Structure.TokenScanner, refs); + continue; } - var builder = new PdfPageBuilder(pages.Count + 1, this, streams, resources, copiedPageDict); - if (resources.TryGetValue(NameToken.Font, out var fonts)) - { - var existingFontDict = fonts as DictionaryToken; - foreach (var item in existingFontDict.Data) - { - var key = NameToken.Create(item.Key); - builder.fontDictionary[key] = item.Value; - } - resources.Remove(NameToken.Font); - } - - pages[builder.PageNumber] = builder; - return builder; - } - - i++; - } - - throw new KeyNotFoundException($"Page {pageNumber} was not found in the source document."); - - void CopyResourceDict(IToken token, Dictionary destinationDict) - { - DictionaryToken dict = GetRemoteDict(token); - if (dict == null) - { - return; - } - foreach (var item in dict.Data) - { - - if (!destinationDict.ContainsKey(NameToken.Create(item.Key))) - { - if (item.Value is IndirectReferenceToken ir) - { - destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, document.Structure.TokenScanner.Get(ir.Data).Data, document.Structure.TokenScanner, refs); - } - else - { - destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs); - } - - continue; - } - - - var subDict = GetRemoteDict(item.Value); - var destSubDict = destinationDict[NameToken.Create(item.Key)] as DictionaryToken; - if (destSubDict == null || subDict == null) - { - // not a dict.. just overwrite with more important one? should maybe check arrays? - destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs); - continue; - } - foreach (var subItem in subDict.Data) - { - // last copied most important important - destinationDict[NameToken.Create(subItem.Key)] = WriterUtil.CopyToken(context, subItem.Value, - document.Structure.TokenScanner, refs); - } - } - } - - DictionaryToken GetRemoteDict(IToken token) - { - DictionaryToken dict = null; - if (token is IndirectReferenceToken ir) - { - dict = document.Structure.TokenScanner.Get(ir.Data).Data as DictionaryToken; - } - else if (token is DictionaryToken dt) - { - dict = dt; - } - return dict; - } - } - - private void CompleteDocument() - { - var fontsWritten = new Dictionary(); - - foreach (var font in fonts) - { - var fontObj = font.Value.FontProgram.WriteFont(context, font.Value.FontKey.Reference); - fontsWritten.Add(font.Key, fontObj); - } - - var procSet = new List - { - NameToken.Create("PDF"), - NameToken.Text, - NameToken.ImageB, - NameToken.ImageC, - NameToken.ImageI - }; - - var resources = new Dictionary - { - { NameToken.ProcSet, new ArrayToken(procSet) } - }; - + var subDict = GetRemoteDict(item.Value); + var destSubDict = destinationDict[NameToken.Create(item.Key)] as DictionaryToken; + if (destSubDict == null || subDict == null) + { + // not a dict.. just overwrite with more important one? should maybe check arrays? + destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs); + continue; + } + foreach (var subItem in subDict.Data) + { + // last copied most important important + destinationDict[NameToken.Create(subItem.Key)] = WriterUtil.CopyToken(context, subItem.Value, + document.Structure.TokenScanner, refs); + } + } + } + + DictionaryToken GetRemoteDict(IToken token) + { + DictionaryToken dict = null; + if (token is IndirectReferenceToken ir) + { + dict = document.Structure.TokenScanner.Get(ir.Data).Data as DictionaryToken; + } + else if (token is DictionaryToken dt) + { + dict = dt; + } + return dict; + } + } + + private void CompleteDocument() + { + var fontsWritten = new Dictionary(); + + foreach (var font in fonts) + { + var fontObj = font.Value.FontProgram.WriteFont(context, font.Value.FontKey.Reference); + fontsWritten.Add(font.Key, fontObj); + } + + var procSet = new List + { + NameToken.Create("PDF"), + NameToken.Text, + NameToken.ImageB, + NameToken.ImageC, + NameToken.ImageI + }; + + var resources = new Dictionary + { + { NameToken.ProcSet, new ArrayToken(procSet) } + }; + // var fontDictionary = new DictionaryToken(fontsWritten.Select(x => // (fonts[x.Key].FontKey.Name, (IToken)x.Value)) // .ToDictionary(x => x.Item1, x => x.Item2)); - // var fontsDictionaryRef = context.WriteToken(fontDictionary); - // if (fontsWritten.Count > 0) - // { - // var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => - // (fonts[x.Key].FontKey.Name, (IToken)x.Value)) - // .ToDictionary(x => x.Item1, x => x.Item2)); - // - // var fontsDictionaryRef = context.WriteToken(fontsDictionary); - // - // resources.Add(NameToken.Font, fontsDictionaryRef); - // } - - var parentIndirect = context.ReserveObjectNumber(); - - var pageReferences = new List(); - foreach (var page in pages) - { - var pageDictionary = page.Value.additionalPageProperties; - pageDictionary[NameToken.Type] = NameToken.Page; + // var fontsDictionaryRef = context.WriteToken(fontDictionary); + // if (fontsWritten.Count > 0) + // { + // var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => + // (fonts[x.Key].FontKey.Name, (IToken)x.Value)) + // .ToDictionary(x => x.Item1, x => x.Item2)); + // + // var fontsDictionaryRef = context.WriteToken(fontsDictionary); + // + // resources.Add(NameToken.Font, fontsDictionaryRef); + // } + + var parentIndirect = context.ReserveObjectNumber(); + + var pageReferences = new List(); + foreach (var page in pages) + { + var pageDictionary = page.Value.additionalPageProperties; + pageDictionary[NameToken.Type] = NameToken.Page; pageDictionary[NameToken.Parent] = parentIndirect; - pageDictionary[NameToken.ProcSet] = new ArrayToken(procSet); + pageDictionary[NameToken.ProcSet] = new ArrayToken(procSet); if (!pageDictionary.ContainsKey(NameToken.MediaBox)) { pageDictionary[NameToken.MediaBox] = RectangleToArray(page.Value.PageSize); } - + // combine existing resources (if any) with added var pageResources = new Dictionary(); foreach (var existing in page.Value.Resources) @@ -488,82 +508,82 @@ namespace UglyToad.PdfPig.Writer } pageResources[NameToken.Font] = new DictionaryToken(page.Value.fontDictionary); - pageDictionary[NameToken.Resources] = new DictionaryToken(pageResources); - - if (page.Value.contentStreams.Count == 1) - { - pageDictionary[NameToken.Contents] = page.Value.contentStreams[0].Write(context); - } - else - { - var streams = new List(); - foreach (var stream in page.Value.contentStreams) - { - streams.Add(stream.Write(context)); - } - - pageDictionary[NameToken.Contents] = new ArrayToken(streams); - } - - - var pageRef = context.WriteToken( new DictionaryToken(pageDictionary)); - - pageReferences.Add(pageRef); - } - - var pagesDictionaryData = new Dictionary - { - {NameToken.Type, NameToken.Pages}, - {NameToken.Kids, new ArrayToken(pageReferences)}, - {NameToken.Resources, new DictionaryToken(resources)}, - {NameToken.Count, new NumericToken(pageReferences.Count)} - }; - - var pagesDictionary = new DictionaryToken(pagesDictionaryData); - - var pagesRef = context.WriteToken(pagesDictionary, parentIndirect); - - var catalogDictionary = new Dictionary - { - {NameToken.Type, NameToken.Catalog}, - {NameToken.Pages, pagesRef} - }; - - if (ArchiveStandard != PdfAStandard.None) - { - Func 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); + pageDictionary[NameToken.Resources] = new DictionaryToken(pageResources); + + if (page.Value.contentStreams.Count == 1) + { + pageDictionary[NameToken.Contents] = page.Value.contentStreams[0].Write(context); + } + else + { + var streams = new List(); + foreach (var stream in page.Value.contentStreams) + { + streams.Add(stream.Write(context)); + } + + pageDictionary[NameToken.Contents] = new ArrayToken(streams); + } + + + var pageRef = context.WriteToken( new DictionaryToken(pageDictionary)); + + pageReferences.Add(pageRef); + } + + var pagesDictionaryData = new Dictionary + { + {NameToken.Type, NameToken.Pages}, + {NameToken.Kids, new ArrayToken(pageReferences)}, + {NameToken.Resources, new DictionaryToken(resources)}, + {NameToken.Count, new NumericToken(pageReferences.Count)} + }; + + var pagesDictionary = new DictionaryToken(pagesDictionaryData); + + var pagesRef = context.WriteToken(pagesDictionary, parentIndirect); + + var catalogDictionary = new Dictionary + { + {NameToken.Type, NameToken.Catalog}, + {NameToken.Pages, pagesRef} + }; + + if (ArchiveStandard != PdfAStandard.None) + { + Func 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); } /// @@ -737,12 +757,12 @@ namespace UglyToad.PdfPig.Writer return result; } } - - /// - /// Disposes underlying stream if set to do so. + + /// + /// Disposes underlying stream if set to do so. /// public void Dispose() - { + { context.Dispose(); } }