mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-11-28 17:47:12 +08:00
perf improvement for copying lots of pages from large documents
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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<DictionaryToken> Parents { get; set; }
|
||||
}
|
||||
private readonly ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>> existingCopies =
|
||||
new ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>>();
|
||||
/// <summary>
|
||||
/// Add a new page with the specified size, this page will be included in the output when <see cref="Build"/> is called.
|
||||
/// </summary>
|
||||
/// <param name="document">Source document.</param>
|
||||
/// <param name="pageNumber">Page to copy.</param>
|
||||
/// <returns>A builder for editing the page.</returns>
|
||||
public PdfPageBuilder AddPage(PdfDocument document, int pageNumber)
|
||||
{
|
||||
if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var 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 (i == pageNumber)
|
||||
{
|
||||
// copy content streams
|
||||
var streams = new List<PdfPageBuilder.CopiedContentStream>();
|
||||
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<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)
|
||||
new ConditionalWeakTable<IPdfTokenScanner, Dictionary<IndirectReference, IndirectReferenceToken>>();
|
||||
private readonly ConditionalWeakTable<PdfDocument, Dictionary<int, PageInfo>> existingTrees =
|
||||
new ConditionalWeakTable<PdfDocument, Dictionary<int, PageInfo>>();
|
||||
/// <summary>
|
||||
/// Add a new page with the specified size, this page will be included in the output when <see cref="Build"/> is called.
|
||||
/// </summary>
|
||||
/// <param name="document">Source document.</param>
|
||||
/// <param name="pageNumber">Page to copy.</param>
|
||||
/// <returns>A builder for editing the page.</returns>
|
||||
public PdfPageBuilder AddPage(PdfDocument document, int pageNumber)
|
||||
{
|
||||
if (!existingCopies.TryGetValue(document.Structure.TokenScanner, out var refs))
|
||||
{
|
||||
refs = new Dictionary<IndirectReference, IndirectReferenceToken>();
|
||||
existingCopies.Add(document.Structure.TokenScanner, refs);
|
||||
}
|
||||
|
||||
if (!existingTrees.TryGetValue(document, out var pagesInfos))
|
||||
{
|
||||
pagesInfos = new Dictionary<int, PageInfo>();
|
||||
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<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);
|
||||
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<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)
|
||||
{
|
||||
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<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 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<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 =>
|
||||
// (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<IndirectReferenceToken>();
|
||||
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<IndirectReferenceToken>();
|
||||
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<NameToken, IToken>();
|
||||
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<IToken>();
|
||||
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, IToken>
|
||||
{
|
||||
{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, IToken>
|
||||
{
|
||||
{NameToken.Type, NameToken.Catalog},
|
||||
{NameToken.Pages, pagesRef}
|
||||
};
|
||||
|
||||
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);
|
||||
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<IToken>();
|
||||
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, IToken>
|
||||
{
|
||||
{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, IToken>
|
||||
{
|
||||
{NameToken.Type, NameToken.Catalog},
|
||||
{NameToken.Pages, pagesRef}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -737,12 +757,12 @@ namespace UglyToad.PdfPig.Writer
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes underlying stream if set to do so.
|
||||
|
||||
/// <summary>
|
||||
/// Disposes underlying stream if set to do so.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user