perf improvement for copying lots of pages from large documents

This commit is contained in:
Plaisted
2021-02-06 18:04:13 -06:00
parent 92f9af613f
commit 1db481164c
2 changed files with 411 additions and 391 deletions

View File

@@ -689,76 +689,76 @@
Assert.Equal("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", page2.Text); Assert.Equal("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", page2.Text);
} }
} }
[Fact] [Fact]
public void CanAddHelloWorldToSimplePage() public void CanAddHelloWorldToSimplePage()
{ {
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
var doc = PdfDocument.Open(path); var doc = PdfDocument.Open(path);
var builder = new PdfDocumentBuilder(); var builder = new PdfDocumentBuilder();
var page = builder.AddPage(doc, 1); var page = builder.AddPage(doc, 1);
page.DrawLine(new PdfPoint(30, 520), new PdfPoint(360, 520)); page.DrawLine(new PdfPoint(30, 520), new PdfPoint(360, 520));
page.DrawLine(new PdfPoint(360, 520), new PdfPoint(360, 250)); page.DrawLine(new PdfPoint(360, 520), new PdfPoint(360, 250));
page.SetStrokeColor(250, 132, 131); page.SetStrokeColor(250, 132, 131);
page.DrawLine(new PdfPoint(25, 70), new PdfPoint(100, 70), 3); page.DrawLine(new PdfPoint(25, 70), new PdfPoint(100, 70), 3);
page.ResetColor(); page.ResetColor();
page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m); page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m);
page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m); page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m);
var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf"); var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf");
var font = builder.AddTrueTypeFont(file); var font = builder.AddTrueTypeFont(file);
var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font); var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font);
Assert.NotEmpty(page.CurrentStream.Operations); Assert.NotEmpty(page.CurrentStream.Operations);
var b = builder.Build(); var b = builder.Build();
WriteFile(nameof(CanAddHelloWorldToSimplePage), b); WriteFile(nameof(CanAddHelloWorldToSimplePage), b);
Assert.NotEmpty(b); Assert.NotEmpty(b);
using (var document = PdfDocument.Open(b)) using (var document = PdfDocument.Open(b))
{ {
var page1 = document.GetPage(1); var page1 = document.GetPage(1);
Assert.Equal("I am a simple pdf.Hello World!", page1.Text); Assert.Equal("I am a simple pdf.Hello World!", page1.Text);
var h = page1.Letters[18]; var h = page1.Letters[18];
Assert.Equal("H", h.Value); Assert.Equal("H", h.Value);
Assert.Equal("Andada-Regular", h.FontName); Assert.Equal("Andada-Regular", h.FontName);
var comparer = new DoubleComparer(0.01); var comparer = new DoubleComparer(0.01);
var pointComparer = new PointComparer(comparer); var pointComparer = new PointComparer(comparer);
for (int i = 0; i < letters.Count; i++) for (int i = 0; i < letters.Count; i++)
{ {
var readerLetter = page1.Letters[i+18]; var readerLetter = page1.Letters[i+18];
var writerLetter = letters[i]; var writerLetter = letters[i];
Assert.Equal(readerLetter.Value, writerLetter.Value); Assert.Equal(readerLetter.Value, writerLetter.Value);
Assert.Equal(readerLetter.Location, writerLetter.Location, pointComparer); Assert.Equal(readerLetter.Location, writerLetter.Location, pointComparer);
Assert.Equal(readerLetter.FontSize, writerLetter.FontSize, comparer); Assert.Equal(readerLetter.FontSize, writerLetter.FontSize, comparer);
Assert.Equal(readerLetter.GlyphRectangle.Width, writerLetter.GlyphRectangle.Width, comparer); Assert.Equal(readerLetter.GlyphRectangle.Width, writerLetter.GlyphRectangle.Width, comparer);
Assert.Equal(readerLetter.GlyphRectangle.Height, writerLetter.GlyphRectangle.Height, comparer); Assert.Equal(readerLetter.GlyphRectangle.Height, writerLetter.GlyphRectangle.Height, comparer);
Assert.Equal(readerLetter.GlyphRectangle.BottomLeft, writerLetter.GlyphRectangle.BottomLeft, pointComparer); Assert.Equal(readerLetter.GlyphRectangle.BottomLeft, writerLetter.GlyphRectangle.BottomLeft, pointComparer);
} }
} }
} }
[Fact] [Fact]
public void CanMerge2SimpleDocumentsReversed_Builder() public void CanMerge2SimpleDocumentsReversed_Builder()
{ {
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
using (var docOne = PdfDocument.Open(one)) using (var docOne = PdfDocument.Open(one))
using (var docTwo = PdfDocument.Open(two)) using (var docTwo = PdfDocument.Open(two))
{ {
@@ -768,12 +768,12 @@
var result = builder.Build(); var result = builder.Build();
PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape", false); PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape", false);
} }
} }
[Fact] [Fact]
public void CanMerge2SimpleDocuments_Builder() public void CanMerge2SimpleDocuments_Builder()
{ {
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf"); var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
using (var docOne = PdfDocument.Open(one)) using (var docOne = PdfDocument.Open(one))
@@ -787,12 +787,12 @@
PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.", false); PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.", false);
} }
} }
[Fact] [Fact]
public void CanDedupObjectsFromSameDoc_Builder() public void CanDedupObjectsFromSameDoc_Builder()
{ {
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
using (var doc = PdfDocument.Open(one)) 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 "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use
} }
} }
} }
[Fact] [Fact]
public void CanDedupObjectsFromDifferentDoc_HashBuilder() public void CanDedupObjectsFromDifferentDoc_HashBuilder()
{ {
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
using (var doc = PdfDocument.Open(one)) using (var doc = PdfDocument.Open(one))
using (var doc2 = 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 "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("Single Page Simple - from google drive.pdf")]
[InlineData("Old Gutnish Internet Explorer.pdf")] [InlineData("Old Gutnish Internet Explorer.pdf")]
[InlineData("68-1990-01_A.pdf")] [InlineData("68-1990-01_A.pdf")]
[InlineData("Multiple Page - from Mortality Statistics.pdf")] [InlineData("Multiple Page - from Mortality Statistics.pdf")]
[Theory] [Theory]
public void CopiedPagesResultInSameData(string name) public void CopiedPagesResultInSameData(string name)
{ {
var docPath = IntegrationHelpers.GetDocumentPath(name); var docPath = IntegrationHelpers.GetDocumentPath(name);
using (var doc = PdfDocument.Open(docPath, ParsingOptions.LenientParsingOff)) using (var doc = PdfDocument.Open(docPath, ParsingOptions.LenientParsingOff))
using (var builder = new PdfDocumentBuilder()) using (var builder = new PdfDocumentBuilder())
{ {
var count1 = GetCounts(doc); var count1 = GetCounts(doc);
@@ -859,30 +859,30 @@
var count2 = GetCounts(doc2); var count2 = GetCounts(doc2);
Assert.Equal(count1.Item1, count2.Item1); Assert.Equal(count1.Item1, count2.Item1);
Assert.Equal(count1.Item2, count2.Item2); Assert.Equal(count1.Item2, count2.Item2);
} }
} }
(int, double) GetCounts(PdfDocument toCount) (int, double) GetCounts(PdfDocument toCount)
{ {
int letters = 0; int letters = 0;
double location = 0; double location = 0;
foreach (var page in toCount.GetPages()) foreach (var page in toCount.GetPages())
{ {
foreach (var letter in page.Letters) foreach (var letter in page.Letters)
{ {
unchecked { letters += 1; } unchecked { letters += 1; }
unchecked { unchecked {
location += letter.Location.X; location += letter.Location.X;
location += letter.Location.Y; location += letter.Location.Y;
location += letter.Font.Name.Length; location += letter.Font.Name.Length;
} }
} }
} }
return (letters, location); return (letters, location);
} }
} }
private static void WriteFile(string name, byte[] bytes, string extension = "pdf") private static void WriteFile(string name, byte[] bytes, string extension = "pdf")

View File

@@ -269,217 +269,237 @@ namespace UglyToad.PdfPig.Writer
} }
return WriterUtil.CopyToken(context, token, source, refs); return WriterUtil.CopyToken(context, token, source, refs);
} }
internal class PageInfo
{
public DictionaryToken Page { get; set; }
public List<DictionaryToken> Parents { get; set; }
}
private readonly ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>> existingCopies = private readonly ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>> existingCopies =
new ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>>(); new ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>>();
/// <summary> private readonly ConditionalWeakTable<PdfDocument, Dictionary<int, PageInfo>> existingTrees =
/// Add a new page with the specified size, this page will be included in the output when <see cref="Build"/> is called. new ConditionalWeakTable<PdfDocument, Dictionary<int, PageInfo>>();
/// </summary> /// <summary>
/// <param name="document">Source document.</param> /// Add a new page with the specified size, this page will be included in the output when <see cref="Build"/> is called.
/// <param name="pageNumber">Page to copy.</param> /// </summary>
/// <returns>A builder for editing the page.</returns> /// <param name="document">Source document.</param>
public PdfPageBuilder AddPage(PdfDocument document, int pageNumber) /// <param name="pageNumber">Page to copy.</param>
{ /// <returns>A builder for editing the page.</returns>
if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var refs)) public PdfPageBuilder AddPage(PdfDocument document, int pageNumber)
{ {
refs = new Dictionary<IndirectReference, IndirectReferenceToken>(); if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var refs))
existingCopies.Add(document.Structure.TokenScanner, refs); {
} refs = new Dictionary<IndirectReference, IndirectReferenceToken>();
existingCopies.Add(document.Structure.TokenScanner, refs);
int i = 1; }
foreach (var (pageDict, parents) in WriterUtil.WalkTree(document.Structure.Catalog.PageTree))
{ if (!existingTrees.TryGetValue(document, out var pagesInfos))
if (i == pageNumber) {
{ pagesInfos = new Dictionary<int, PageInfo>();
// copy content streams int i = 1;
var streams = new List<PdfPageBuilder.CopiedContentStream>(); foreach (var (pageDict, parents) in WriterUtil.WalkTree(document.Structure.Catalog.PageTree))
if (pageDict.ContainsKey(NameToken.Contents)) {
{ pagesInfos[i] = new PageInfo
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<NameToken, IToken>();
Dictionary<NameToken, IToken> resources = new Dictionary<NameToken, IToken>();
// 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)
{ {
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<PdfPageBuilder.CopiedContentStream>();
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<NameToken, IToken>();
Dictionary<NameToken, IToken> resources = new Dictionary<NameToken, IToken>();
// 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<NameToken, IToken> 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); destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, document.Structure.TokenScanner.Get(ir.Data).Data, document.Structure.TokenScanner, refs);
continue; }
else
{
destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs);
} }
copiedPageDict[NameToken.Create(kvp.Key)] = continue;
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); var subDict = GetRemoteDict(item.Value);
} var destSubDict = destinationDict[NameToken.Create(item.Key)] as DictionaryToken;
if (destSubDict == null || subDict == null)
pages[builder.PageNumber] = builder; {
return builder; // 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;
i++; }
} foreach (var subItem in subDict.Data)
{
throw new KeyNotFoundException($"Page {pageNumber} was not found in the source document."); // last copied most important important
destinationDict[NameToken.Create(subItem.Key)] = WriterUtil.CopyToken(context, subItem.Value,
void CopyResourceDict(IToken token, Dictionary<NameToken, IToken> destinationDict) document.Structure.TokenScanner, refs);
{ }
DictionaryToken dict = GetRemoteDict(token); }
if (dict == null) }
{
return; DictionaryToken GetRemoteDict(IToken token)
} {
foreach (var item in dict.Data) DictionaryToken dict = null;
{ if (token is IndirectReferenceToken ir)
{
if (!destinationDict.ContainsKey(NameToken.Create(item.Key))) dict = document.Structure.TokenScanner.Get(ir.Data).Data as DictionaryToken;
{ }
if (item.Value is IndirectReferenceToken ir) else if (token is DictionaryToken dt)
{ {
destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, document.Structure.TokenScanner.Get(ir.Data).Data, document.Structure.TokenScanner, refs); dict = dt;
} }
else return dict;
{ }
destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs); }
}
private void CompleteDocument()
continue; {
} var fontsWritten = new Dictionary<Guid, IndirectReferenceToken>();
foreach (var font in fonts)
var subDict = GetRemoteDict(item.Value); {
var destSubDict = destinationDict[NameToken.Create(item.Key)] as DictionaryToken; var fontObj = font.Value.FontProgram.WriteFont(context, font.Value.FontKey.Reference);
if (destSubDict == null || subDict == null) fontsWritten.Add(font.Key, fontObj);
{ }
// 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); var procSet = new List<NameToken>
continue; {
} NameToken.Create("PDF"),
foreach (var subItem in subDict.Data) NameToken.Text,
{ NameToken.ImageB,
// last copied most important important NameToken.ImageC,
destinationDict[NameToken.Create(subItem.Key)] = WriterUtil.CopyToken(context, subItem.Value, NameToken.ImageI
document.Structure.TokenScanner, refs); };
}
} var resources = new Dictionary<NameToken, IToken>
} {
{ NameToken.ProcSet, new ArrayToken(procSet) }
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<Guid, IndirectReferenceToken>();
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>
{
NameToken.Create("PDF"),
NameToken.Text,
NameToken.ImageB,
NameToken.ImageC,
NameToken.ImageI
};
var resources = new Dictionary<NameToken, IToken>
{
{ NameToken.ProcSet, new ArrayToken(procSet) }
};
// var fontDictionary = new DictionaryToken(fontsWritten.Select(x => // var fontDictionary = new DictionaryToken(fontsWritten.Select(x =>
// (fonts[x.Key].FontKey.Name, (IToken)x.Value)) // (fonts[x.Key].FontKey.Name, (IToken)x.Value))
// .ToDictionary(x => x.Item1, x => x.Item2)); // .ToDictionary(x => x.Item1, x => x.Item2));
// var fontsDictionaryRef = context.WriteToken(fontDictionary); // var fontsDictionaryRef = context.WriteToken(fontDictionary);
// if (fontsWritten.Count > 0) // if (fontsWritten.Count > 0)
// { // {
// var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => // var fontsDictionary = new DictionaryToken(fontsWritten.Select(x =>
// (fonts[x.Key].FontKey.Name, (IToken)x.Value)) // (fonts[x.Key].FontKey.Name, (IToken)x.Value))
// .ToDictionary(x => x.Item1, x => x.Item2)); // .ToDictionary(x => x.Item1, x => x.Item2));
// //
// var fontsDictionaryRef = context.WriteToken(fontsDictionary); // var fontsDictionaryRef = context.WriteToken(fontsDictionary);
// //
// resources.Add(NameToken.Font, fontsDictionaryRef); // resources.Add(NameToken.Font, fontsDictionaryRef);
// } // }
var parentIndirect = context.ReserveObjectNumber(); var parentIndirect = context.ReserveObjectNumber();
var pageReferences = new List<IndirectReferenceToken>(); var pageReferences = new List<IndirectReferenceToken>();
foreach (var page in pages) foreach (var page in pages)
{ {
var pageDictionary = page.Value.additionalPageProperties; var pageDictionary = page.Value.additionalPageProperties;
pageDictionary[NameToken.Type] = NameToken.Page; pageDictionary[NameToken.Type] = NameToken.Page;
pageDictionary[NameToken.Parent] = parentIndirect; pageDictionary[NameToken.Parent] = parentIndirect;
pageDictionary[NameToken.ProcSet] = new ArrayToken(procSet); pageDictionary[NameToken.ProcSet] = new ArrayToken(procSet);
if (!pageDictionary.ContainsKey(NameToken.MediaBox)) if (!pageDictionary.ContainsKey(NameToken.MediaBox))
{ {
pageDictionary[NameToken.MediaBox] = RectangleToArray(page.Value.PageSize); pageDictionary[NameToken.MediaBox] = RectangleToArray(page.Value.PageSize);
} }
// combine existing resources (if any) with added // combine existing resources (if any) with added
var pageResources = new Dictionary<NameToken, IToken>(); var pageResources = new Dictionary<NameToken, IToken>();
foreach (var existing in page.Value.Resources) foreach (var existing in page.Value.Resources)
@@ -488,82 +508,82 @@ namespace UglyToad.PdfPig.Writer
} }
pageResources[NameToken.Font] = new DictionaryToken(page.Value.fontDictionary); pageResources[NameToken.Font] = new DictionaryToken(page.Value.fontDictionary);
pageDictionary[NameToken.Resources] = new DictionaryToken(pageResources); pageDictionary[NameToken.Resources] = new DictionaryToken(pageResources);
if (page.Value.contentStreams.Count == 1) if (page.Value.contentStreams.Count == 1)
{ {
pageDictionary[NameToken.Contents] = page.Value.contentStreams[0].Write(context); pageDictionary[NameToken.Contents] = page.Value.contentStreams[0].Write(context);
} }
else else
{ {
var streams = new List<IToken>(); var streams = new List<IToken>();
foreach (var stream in page.Value.contentStreams) foreach (var stream in page.Value.contentStreams)
{ {
streams.Add(stream.Write(context)); streams.Add(stream.Write(context));
} }
pageDictionary[NameToken.Contents] = new ArrayToken(streams); pageDictionary[NameToken.Contents] = new ArrayToken(streams);
} }
var pageRef = context.WriteToken( new DictionaryToken(pageDictionary)); var pageRef = context.WriteToken( new DictionaryToken(pageDictionary));
pageReferences.Add(pageRef); pageReferences.Add(pageRef);
} }
var pagesDictionaryData = new Dictionary<NameToken, IToken> var pagesDictionaryData = new Dictionary<NameToken, IToken>
{ {
{NameToken.Type, NameToken.Pages}, {NameToken.Type, NameToken.Pages},
{NameToken.Kids, new ArrayToken(pageReferences)}, {NameToken.Kids, new ArrayToken(pageReferences)},
{NameToken.Resources, new DictionaryToken(resources)}, {NameToken.Resources, new DictionaryToken(resources)},
{NameToken.Count, new NumericToken(pageReferences.Count)} {NameToken.Count, new NumericToken(pageReferences.Count)}
}; };
var pagesDictionary = new DictionaryToken(pagesDictionaryData); var pagesDictionary = new DictionaryToken(pagesDictionaryData);
var pagesRef = context.WriteToken(pagesDictionary, parentIndirect); var pagesRef = context.WriteToken(pagesDictionary, parentIndirect);
var catalogDictionary = new Dictionary<NameToken, IToken> var catalogDictionary = new Dictionary<NameToken, IToken>
{ {
{NameToken.Type, NameToken.Catalog}, {NameToken.Type, NameToken.Catalog},
{NameToken.Pages, pagesRef} {NameToken.Pages, pagesRef}
}; };
if (ArchiveStandard != PdfAStandard.None) if (ArchiveStandard != PdfAStandard.None)
{ {
Func<IToken, IndirectReferenceToken> writerFunc = x => context.WriteToken(x); Func<IToken, IndirectReferenceToken> writerFunc = x => context.WriteToken(x);
PdfABaselineRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard); PdfABaselineRuleBuilder.Obey(catalogDictionary, writerFunc, DocumentInformation, ArchiveStandard);
switch (ArchiveStandard) switch (ArchiveStandard)
{ {
case PdfAStandard.A1A: case PdfAStandard.A1A:
PdfA1ARuleBuilder.Obey(catalogDictionary); PdfA1ARuleBuilder.Obey(catalogDictionary);
break; break;
case PdfAStandard.A2B: case PdfAStandard.A2B:
break; break;
case PdfAStandard.A2A: case PdfAStandard.A2A:
PdfA1ARuleBuilder.Obey(catalogDictionary); PdfA1ARuleBuilder.Obey(catalogDictionary);
break; break;
} }
} }
var catalog = new DictionaryToken(catalogDictionary); var catalog = new DictionaryToken(catalogDictionary);
var catalogRef = context.WriteToken(catalog); var catalogRef = context.WriteToken(catalog);
var informationReference = default(IndirectReferenceToken); var informationReference = default(IndirectReferenceToken);
if (IncludeDocumentInformation) if (IncludeDocumentInformation)
{ {
var informationDictionary = DocumentInformation.ToDictionary(); var informationDictionary = DocumentInformation.ToDictionary();
if (informationDictionary.Count > 0) if (informationDictionary.Count > 0)
{ {
var dictionary = new DictionaryToken(informationDictionary); var dictionary = new DictionaryToken(informationDictionary);
informationReference = context.WriteToken(dictionary); informationReference = context.WriteToken(dictionary);
} }
} }
context.CompletePdf(catalogRef, informationReference); context.CompletePdf(catalogRef, informationReference);
} }
/// <summary> /// <summary>
@@ -737,12 +757,12 @@ namespace UglyToad.PdfPig.Writer
return result; return result;
} }
} }
/// <summary> /// <summary>
/// Disposes underlying stream if set to do so. /// Disposes underlying stream if set to do so.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
context.Dispose(); context.Dispose();
} }
} }