mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +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);
|
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")
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user