mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-11-28 17:47:12 +08:00
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
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.Operations);
|
Assert.NotEmpty(page.CurrentStream.Operations);
|
||||||
|
|
||||||
var b = builder.Build();
|
var b = builder.Build();
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
|
|
||||||
page.AddText("eé", 12, new PdfPoint(30, 520), font);
|
page.AddText("eé", 12, new PdfPoint(30, 520), font);
|
||||||
|
|
||||||
Assert.NotEmpty(page.Operations);
|
Assert.NotEmpty(page.CurrentStream.Operations);
|
||||||
|
|
||||||
var b = builder.Build();
|
var b = builder.Build();
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
|
|
||||||
page.AddText("eé", 12, new PdfPoint(30, 520), font);
|
page.AddText("eé", 12, new PdfPoint(30, 520), font);
|
||||||
|
|
||||||
Assert.NotEmpty(page.Operations);
|
Assert.NotEmpty(page.CurrentStream.Operations);
|
||||||
|
|
||||||
var b = builder.Build();
|
var b = builder.Build();
|
||||||
|
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
var letters = page.AddText("Hello World!", 16, new PdfPoint(30, 520), font);
|
var letters = page.AddText("Hello World!", 16, new PdfPoint(30, 520), font);
|
||||||
page.AddText("This is some further text continuing to write", 12, new PdfPoint(30, 500), font);
|
page.AddText("This is some further text continuing to write", 12, new PdfPoint(30, 500), font);
|
||||||
|
|
||||||
Assert.NotEmpty(page.Operations);
|
Assert.NotEmpty(page.CurrentStream.Operations);
|
||||||
|
|
||||||
var b = builder.Build();
|
var b = builder.Build();
|
||||||
|
|
||||||
@@ -603,6 +603,94 @@
|
|||||||
WriteFile(nameof(CanCreateDocumentWithFilledRectangle), file);
|
WriteFile(nameof(CanCreateDocumentWithFilledRectangle), file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanGeneratePageWithMultipleStream()
|
||||||
|
{
|
||||||
|
var builder = new PdfDocumentBuilder();
|
||||||
|
|
||||||
|
var page = builder.AddPage(PageSize.A4);
|
||||||
|
|
||||||
|
var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf");
|
||||||
|
|
||||||
|
var font = builder.AddTrueTypeFont(file);
|
||||||
|
|
||||||
|
var letters = page.AddText("Hello", 12, new PdfPoint(30, 50), font);
|
||||||
|
|
||||||
|
Assert.NotEmpty(page.CurrentStream.Operations);
|
||||||
|
|
||||||
|
page.NewContentStreamAfter();
|
||||||
|
|
||||||
|
page.AddText("World!", 12, new PdfPoint(50, 50), font);
|
||||||
|
|
||||||
|
Assert.NotEmpty(page.CurrentStream.Operations);
|
||||||
|
|
||||||
|
|
||||||
|
var b = builder.Build();
|
||||||
|
|
||||||
|
WriteFile(nameof(CanGeneratePageWithMultipleStream), b);
|
||||||
|
|
||||||
|
Assert.NotEmpty(b);
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(b))
|
||||||
|
{
|
||||||
|
var page1 = document.GetPage(1);
|
||||||
|
|
||||||
|
Assert.Equal("HelloWorld!", page1.Text);
|
||||||
|
|
||||||
|
var h = page1.Letters[0];
|
||||||
|
|
||||||
|
Assert.Equal("H", h.Value);
|
||||||
|
Assert.Equal("Andada-Regular", h.FontName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCopyPage()
|
||||||
|
{
|
||||||
|
|
||||||
|
byte[] b;
|
||||||
|
{
|
||||||
|
var builder = new PdfDocumentBuilder();
|
||||||
|
|
||||||
|
var page1 = builder.AddPage(PageSize.A4);
|
||||||
|
|
||||||
|
var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf");
|
||||||
|
|
||||||
|
var font = builder.AddTrueTypeFont(file);
|
||||||
|
|
||||||
|
page1.AddText("Hello", 12, new PdfPoint(30, 50), font);
|
||||||
|
|
||||||
|
Assert.NotEmpty(page1.CurrentStream.Operations);
|
||||||
|
|
||||||
|
|
||||||
|
using (var readDocument = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("bold-italic.pdf")))
|
||||||
|
{
|
||||||
|
var rpage = readDocument.GetPage(1);
|
||||||
|
|
||||||
|
var page2 = builder.AddPage(PageSize.A4);
|
||||||
|
page2.CopyFrom(rpage);
|
||||||
|
}
|
||||||
|
|
||||||
|
b = builder.Build();
|
||||||
|
Assert.NotEmpty(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteFile(nameof(CanCopyPage), b);
|
||||||
|
|
||||||
|
using (var document = PdfDocument.Open(b))
|
||||||
|
{
|
||||||
|
Assert.Equal( 2, document.NumberOfPages);
|
||||||
|
|
||||||
|
var page1 = document.GetPage(1);
|
||||||
|
|
||||||
|
Assert.Equal("Hello", page1.Text);
|
||||||
|
|
||||||
|
var page2 = document.GetPage(2);
|
||||||
|
|
||||||
|
Assert.Equal("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", page2.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void WriteFile(string name, byte[] bytes, string extension = "pdf")
|
private static void WriteFile(string name, byte[] bytes, string extension = "pdf")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -343,6 +343,7 @@
|
|||||||
public static readonly NameToken MaxWidth = new NameToken("MaxWidth");
|
public static readonly NameToken MaxWidth = new NameToken("MaxWidth");
|
||||||
public static readonly NameToken Mcid = new NameToken("MCID");
|
public static readonly NameToken Mcid = new NameToken("MCID");
|
||||||
public static readonly NameToken Mdp = new NameToken("MDP");
|
public static readonly NameToken Mdp = new NameToken("MDP");
|
||||||
|
public static readonly NameToken Measure = new NameToken("Measure");
|
||||||
public static readonly NameToken MediaBox = new NameToken("MediaBox");
|
public static readonly NameToken MediaBox = new NameToken("MediaBox");
|
||||||
public static readonly NameToken Metadata = new NameToken("Metadata");
|
public static readonly NameToken Metadata = new NameToken("Metadata");
|
||||||
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
|
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
|
||||||
@@ -562,6 +563,7 @@
|
|||||||
public static readonly NameToken ViewClip = new NameToken("ViewClip");
|
public static readonly NameToken ViewClip = new NameToken("ViewClip");
|
||||||
public static readonly NameToken ViewerPreferences = new NameToken("ViewerPreferences");
|
public static readonly NameToken ViewerPreferences = new NameToken("ViewerPreferences");
|
||||||
public static readonly NameToken Volume = new NameToken("Volume");
|
public static readonly NameToken Volume = new NameToken("Volume");
|
||||||
|
public static readonly NameToken Vp = new NameToken("VP");
|
||||||
// W
|
// W
|
||||||
public static readonly NameToken W = new NameToken("W");
|
public static readonly NameToken W = new NameToken("W");
|
||||||
public static readonly NameToken W2 = new NameToken("W2");
|
public static readonly NameToken W2 = new NameToken("W2");
|
||||||
|
|||||||
@@ -478,6 +478,17 @@
|
|||||||
var isChecked = false;
|
var isChecked = false;
|
||||||
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
|
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
|
||||||
{
|
{
|
||||||
|
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.As, tokenScanner, out NameToken appearanceStateName)
|
||||||
|
&& fieldDictionary.TryGetOptionalTokenDirect(NameToken.Ap, tokenScanner, out DictionaryToken _))
|
||||||
|
{
|
||||||
|
// Issue #267 - Use the set appearance instead, this might not work for 3 state checkboxes.
|
||||||
|
isChecked = !string.Equals(
|
||||||
|
appearanceStateName.Data,
|
||||||
|
NameToken.Off,
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
valueToken = appearanceStateName;
|
||||||
|
return (isChecked, valueToken);
|
||||||
|
}
|
||||||
valueToken = NameToken.Off;
|
valueToken = NameToken.Off;
|
||||||
}
|
}
|
||||||
else if (inheritsValue && fieldDictionary.TryGet(NameToken.As, tokenScanner, out NameToken appearanceStateName))
|
else if (inheritsValue && fieldDictionary.TryGet(NameToken.As, tokenScanner, out NameToken appearanceStateName))
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
public class Page
|
public class Page
|
||||||
{
|
{
|
||||||
private readonly AnnotationProvider annotationProvider;
|
private readonly AnnotationProvider annotationProvider;
|
||||||
private readonly IPdfTokenScanner pdfScanner;
|
internal readonly IPdfTokenScanner pdfScanner;
|
||||||
private readonly Lazy<string> textLazy;
|
private readonly Lazy<string> textLazy;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content;
|
using Content;
|
||||||
@@ -10,8 +11,10 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
using Fonts;
|
using Fonts;
|
||||||
using PdfPig.Fonts.TrueType;
|
using PdfPig.Fonts.TrueType;
|
||||||
using Graphics.Operations;
|
using Graphics.Operations;
|
||||||
|
using Parser.Parts;
|
||||||
using PdfPig.Fonts.Standard14Fonts;
|
using PdfPig.Fonts.Standard14Fonts;
|
||||||
using PdfPig.Fonts.TrueType.Parser;
|
using PdfPig.Fonts.TrueType.Parser;
|
||||||
|
using Tokenization.Scanner;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
using Util.JetBrains.Annotations;
|
using Util.JetBrains.Annotations;
|
||||||
@@ -25,6 +28,9 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
private readonly Dictionary<int, PdfPageBuilder> pages = new Dictionary<int, PdfPageBuilder>();
|
private readonly Dictionary<int, PdfPageBuilder> pages = new Dictionary<int, PdfPageBuilder>();
|
||||||
private readonly Dictionary<Guid, FontStored> fonts = new Dictionary<Guid, FontStored>();
|
private readonly Dictionary<Guid, FontStored> fonts = new Dictionary<Guid, FontStored>();
|
||||||
private readonly Dictionary<Guid, ImageStored> images = new Dictionary<Guid, ImageStored>();
|
private readonly Dictionary<Guid, ImageStored> images = new Dictionary<Guid, ImageStored>();
|
||||||
|
private readonly Dictionary<IndirectReferenceToken, IToken> unwrittenTokens = new Dictionary<IndirectReferenceToken, IToken>();
|
||||||
|
|
||||||
|
internal int fontId = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The standard of PDF/A compliance of the generated document. Defaults to <see cref="PdfAStandard.None"/>.
|
/// The standard of PDF/A compliance of the generated document. Defaults to <see cref="PdfAStandard.None"/>.
|
||||||
@@ -50,7 +56,12 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The fonts currently available in the document builder added via <see cref="AddTrueTypeFont"/> or <see cref="AddStandard14Font"/>. Keyed by id for internal purposes.
|
/// The fonts currently available in the document builder added via <see cref="AddTrueTypeFont"/> or <see cref="AddStandard14Font"/>. Keyed by id for internal purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IReadOnlyDictionary<Guid, IWritingFont> Fonts => fonts.ToDictionary(x => x.Key, x => x.Value.FontProgram);
|
internal IReadOnlyDictionary<Guid, FontStored> Fonts => fonts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The images currently available in the document builder added via <see cref="AddImage"/>. Keyed by id for internal purposes.
|
||||||
|
/// </summary>
|
||||||
|
internal IReadOnlyDictionary<Guid, ImageStored> Images => images;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the bytes of the TrueType font file provided can be used in a PDF document.
|
/// Determines whether the bytes of the TrueType font file provided can be used in a PDF document.
|
||||||
@@ -116,8 +127,7 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
{
|
{
|
||||||
var font = TrueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontFileBytes)));
|
var font = TrueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontFileBytes)));
|
||||||
var id = Guid.NewGuid();
|
var id = Guid.NewGuid();
|
||||||
var i = fonts.Count;
|
var added = new AddedFont(id, NameToken.Create($"F{fontId++}"));
|
||||||
var added = new AddedFont(id, NameToken.Create($"F{i}"));
|
|
||||||
fonts[id] = new FontStored(added, new TrueTypeWritingFont(font, fontFileBytes));
|
fonts[id] = new FontStored(added, new TrueTypeWritingFont(font, fontFileBytes));
|
||||||
|
|
||||||
return added;
|
return added;
|
||||||
@@ -141,7 +151,7 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
var id = Guid.NewGuid();
|
var id = Guid.NewGuid();
|
||||||
var name = NameToken.Create($"F{fonts.Count}");
|
var name = NameToken.Create($"F{fontId++}");
|
||||||
var added = new AddedFont(id, name);
|
var added = new AddedFont(id, name);
|
||||||
fonts[id] = new FontStored(added, new Standard14WritingFont(Standard14.GetAdobeFontMetrics(type)));
|
fonts[id] = new FontStored(added, new Standard14WritingFont(Standard14.GetAdobeFontMetrics(type)));
|
||||||
|
|
||||||
@@ -259,6 +269,11 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
context.WriteObject(memory, streamToken, image.Value.ObjectNumber);
|
context.WriteObject(memory, streamToken, image.Value.ObjectNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var tokenSet in unwrittenTokens)
|
||||||
|
{
|
||||||
|
context.WriteObject(memory, tokenSet.Value, (int)tokenSet.Key.Data.ObjectNumber);
|
||||||
|
}
|
||||||
|
|
||||||
var procSet = new List<NameToken>
|
var procSet = new List<NameToken>
|
||||||
{
|
{
|
||||||
NameToken.Create("PDF"),
|
NameToken.Create("PDF"),
|
||||||
@@ -278,9 +293,7 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => (fonts[x.Key].FontKey.Name, (IToken)new IndirectReferenceToken(x.Value.Number)))
|
var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => (fonts[x.Key].FontKey.Name, (IToken)new IndirectReferenceToken(x.Value.Number)))
|
||||||
.ToDictionary(x => x.Item1, x => x.Item2));
|
.ToDictionary(x => x.Item1, x => x.Item2));
|
||||||
|
|
||||||
var fontsDictionaryRef = context.WriteObject(memory, fontsDictionary);
|
resources.Add(NameToken.Font, fontsDictionary);
|
||||||
|
|
||||||
resources.Add(NameToken.Font, new IndirectReferenceToken(fontsDictionaryRef.Number));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var reserved = context.ReserveNumber();
|
var reserved = context.ReserveNumber();
|
||||||
@@ -301,21 +314,50 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
{
|
{
|
||||||
foreach (var kvp in page.Value.Resources)
|
foreach (var kvp in page.Value.Resources)
|
||||||
{
|
{
|
||||||
// TODO: combine resources if value is dictionary or array, otherwise overwrite.
|
var value = kvp.Value;
|
||||||
individualResources[kvp.Key] = kvp.Value;
|
if (individualResources.TryGetValue(kvp.Key, out var pageToken))
|
||||||
|
{
|
||||||
|
if (pageToken is DictionaryToken leftDictionary && value is DictionaryToken rightDictionary)
|
||||||
|
{
|
||||||
|
var merged = leftDictionary.Data.ToDictionary(k => NameToken.Create(k.Key), v => v.Value);
|
||||||
|
foreach (var set in rightDictionary.Data)
|
||||||
|
{
|
||||||
|
merged[NameToken.Create(set.Key)] = set.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = new DictionaryToken(merged);
|
||||||
|
|
||||||
|
}
|
||||||
|
// Else override
|
||||||
|
}
|
||||||
|
|
||||||
|
individualResources[kvp.Key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pageDictionary[NameToken.Resources] = new DictionaryToken(individualResources);
|
pageDictionary[NameToken.Resources] = new DictionaryToken(individualResources);
|
||||||
|
|
||||||
if (page.Value.Operations.Count > 0)
|
if (page.Value.ContentStreams.Count == 1)
|
||||||
{
|
{
|
||||||
var contentStream = WriteContentStream(page.Value.Operations);
|
var contentStream = WriteContentStream(page.Value.CurrentStream.Operations);
|
||||||
|
|
||||||
var contentStreamObj = context.WriteObject(memory, contentStream);
|
var contentStreamObj = context.WriteObject(memory, contentStream);
|
||||||
|
|
||||||
pageDictionary[NameToken.Contents] = new IndirectReferenceToken(contentStreamObj.Number);
|
pageDictionary[NameToken.Contents] = new IndirectReferenceToken(contentStreamObj.Number);
|
||||||
}
|
}
|
||||||
|
else if (page.Value.ContentStreams.Count > 1)
|
||||||
|
{
|
||||||
|
var streamTokens = page.Value.ContentStreams.Select(contentStream =>
|
||||||
|
{
|
||||||
|
var streamToken = WriteContentStream(contentStream.Operations);
|
||||||
|
|
||||||
|
var contentStreamObj = context.WriteObject(memory, streamToken);
|
||||||
|
|
||||||
|
return new IndirectReferenceToken(contentStreamObj.Number);
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
pageDictionary[NameToken.Contents] = new ArrayToken(streamTokens);
|
||||||
|
}
|
||||||
|
|
||||||
var pageRef = context.WriteObject(memory, new DictionaryToken(pageDictionary));
|
var pageRef = context.WriteObject(memory, new DictionaryToken(pageDictionary));
|
||||||
|
|
||||||
@@ -379,6 +421,75 @@ namespace UglyToad.PdfPig.Writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The purpose of this method is to resolve indirect reference. That mean copy the reference's content to the new document's stream
|
||||||
|
/// and replace the indirect reference with the correct/new one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tokenToCopy">Token to inspect for reference</param>
|
||||||
|
/// <param name="tokenScanner">scanner get the content from the original document</param>
|
||||||
|
/// <returns>A reference of the token that was copied. With all the reference updated</returns>
|
||||||
|
internal IToken CopyToken(IToken tokenToCopy, IPdfTokenScanner tokenScanner)
|
||||||
|
{
|
||||||
|
// This token need to be deep copied, because they could contain reference. So we have to update them.
|
||||||
|
switch (tokenToCopy)
|
||||||
|
{
|
||||||
|
case DictionaryToken dictionaryToken:
|
||||||
|
{
|
||||||
|
var newContent = new Dictionary<NameToken, IToken>();
|
||||||
|
foreach (var setPair in dictionaryToken.Data)
|
||||||
|
{
|
||||||
|
var name = setPair.Key;
|
||||||
|
var token = setPair.Value;
|
||||||
|
newContent.Add(NameToken.Create(name), CopyToken(token, tokenScanner));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DictionaryToken(newContent);
|
||||||
|
}
|
||||||
|
case ArrayToken arrayToken:
|
||||||
|
{
|
||||||
|
var newArray = new List<IToken>(arrayToken.Length);
|
||||||
|
foreach (var token in arrayToken.Data)
|
||||||
|
{
|
||||||
|
newArray.Add(CopyToken(token, tokenScanner));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayToken(newArray);
|
||||||
|
}
|
||||||
|
case IndirectReferenceToken referenceToken:
|
||||||
|
{
|
||||||
|
var tokenObject = DirectObjectFinder.Get<IToken>(referenceToken.Data, tokenScanner);
|
||||||
|
|
||||||
|
Debug.Assert(!(tokenObject is IndirectReferenceToken));
|
||||||
|
|
||||||
|
var newToken = CopyToken(tokenObject, tokenScanner);
|
||||||
|
|
||||||
|
var reserved = context.ReserveNumber();
|
||||||
|
var newReference = new IndirectReferenceToken(new IndirectReference(reserved, 0));
|
||||||
|
|
||||||
|
unwrittenTokens.Add(newReference, newToken);
|
||||||
|
|
||||||
|
return newReference;
|
||||||
|
}
|
||||||
|
case StreamToken streamToken:
|
||||||
|
{
|
||||||
|
var properties = CopyToken(streamToken.StreamDictionary, tokenScanner) as DictionaryToken;
|
||||||
|
Debug.Assert(properties != null);
|
||||||
|
|
||||||
|
var bytes = streamToken.Data;
|
||||||
|
return new StreamToken(properties, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
case ObjectToken _:
|
||||||
|
{
|
||||||
|
// Since we don't write token directly to the stream.
|
||||||
|
// We can't know the offset. Therefore the token would be invalid
|
||||||
|
throw new NotSupportedException("Copying a Object token is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenToCopy;
|
||||||
|
}
|
||||||
|
|
||||||
private static StreamToken WriteContentStream(IReadOnlyList<IGraphicsStateOperation> content)
|
private static StreamToken WriteContentStream(IReadOnlyList<IGraphicsStateOperation> content)
|
||||||
{
|
{
|
||||||
using (var memoryStream = new MemoryStream())
|
using (var memoryStream = new MemoryStream())
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
using Images;
|
using Images;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using PdfFonts;
|
using PdfFonts;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
using Graphics.Operations.PathPainting;
|
using Graphics.Operations.PathPainting;
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
public class PdfPageBuilder
|
public class PdfPageBuilder
|
||||||
{
|
{
|
||||||
private readonly PdfDocumentBuilder documentBuilder;
|
private readonly PdfDocumentBuilder documentBuilder;
|
||||||
private readonly List<IGraphicsStateOperation> operations = new List<IGraphicsStateOperation>();
|
private readonly List<ContentStream> contentStreams;
|
||||||
private readonly Dictionary<NameToken, IToken> resourcesDictionary = new Dictionary<NameToken, IToken>();
|
private readonly Dictionary<NameToken, IToken> resourcesDictionary = new Dictionary<NameToken, IToken>();
|
||||||
|
|
||||||
//a sequence number of ShowText operation to determine whether letters belong to same operation or not (letters that belong to different operations have less changes to belong to same word)
|
//a sequence number of ShowText operation to determine whether letters belong to same operation or not (letters that belong to different operations have less changes to belong to same word)
|
||||||
@@ -35,8 +37,6 @@
|
|||||||
|
|
||||||
private int imageKey = 1;
|
private int imageKey = 1;
|
||||||
|
|
||||||
internal IReadOnlyList<IGraphicsStateOperation> Operations => operations;
|
|
||||||
|
|
||||||
internal IReadOnlyDictionary<NameToken, IToken> Resources => resourcesDictionary;
|
internal IReadOnlyDictionary<NameToken, IToken> Resources => resourcesDictionary;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,13 +52,59 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access to the underlying data structures for advanced use cases.
|
/// Access to the underlying data structures for advanced use cases.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AdvancedEditing Advanced { get; }
|
public ContentStream CurrentStream { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Access to
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ContentStream> ContentStreams { get; }
|
||||||
|
|
||||||
internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder)
|
internal PdfPageBuilder(int number, PdfDocumentBuilder documentBuilder)
|
||||||
{
|
{
|
||||||
this.documentBuilder = documentBuilder ?? throw new ArgumentNullException(nameof(documentBuilder));
|
this.documentBuilder = documentBuilder ?? throw new ArgumentNullException(nameof(documentBuilder));
|
||||||
PageNumber = number;
|
PageNumber = number;
|
||||||
Advanced = new AdvancedEditing(operations);
|
|
||||||
|
CurrentStream = new ContentStream();
|
||||||
|
ContentStreams = contentStreams = new List<ContentStream>()
|
||||||
|
{
|
||||||
|
CurrentStream
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allow to append a new content stream before the current one and select it
|
||||||
|
/// </summary>
|
||||||
|
public void NewContentStreamBefore()
|
||||||
|
{
|
||||||
|
var index = Math.Max(contentStreams.IndexOf(CurrentStream) - 1, 0);
|
||||||
|
|
||||||
|
CurrentStream = new ContentStream();
|
||||||
|
contentStreams.Insert(index, CurrentStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allow to append a new content stream after the current one and select it
|
||||||
|
/// </summary>
|
||||||
|
public void NewContentStreamAfter()
|
||||||
|
{
|
||||||
|
var index = Math.Min(contentStreams.IndexOf(CurrentStream) + 1, contentStreams.Count);
|
||||||
|
|
||||||
|
CurrentStream = new ContentStream();
|
||||||
|
contentStreams.Insert(index, CurrentStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select a content stream from the list, by his index
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">index of the content stream to be selected</param>
|
||||||
|
public void SelectContentStream(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= ContentStreams.Count)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentStream = ContentStreams[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -71,16 +117,16 @@
|
|||||||
{
|
{
|
||||||
if (lineWidth != 1)
|
if (lineWidth != 1)
|
||||||
{
|
{
|
||||||
operations.Add(new SetLineWidth(lineWidth));
|
CurrentStream.Add(new SetLineWidth(lineWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
operations.Add(new BeginNewSubpath((decimal)from.X, (decimal)from.Y));
|
CurrentStream.Add(new BeginNewSubpath((decimal)from.X, (decimal)from.Y));
|
||||||
operations.Add(new AppendStraightLineSegment((decimal)to.X, (decimal)to.Y));
|
CurrentStream.Add(new AppendStraightLineSegment((decimal)to.X, (decimal)to.Y));
|
||||||
operations.Add(StrokePath.Value);
|
CurrentStream.Add(StrokePath.Value);
|
||||||
|
|
||||||
if (lineWidth != 1)
|
if (lineWidth != 1)
|
||||||
{
|
{
|
||||||
operations.Add(new SetLineWidth(1));
|
CurrentStream.Add(new SetLineWidth(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,23 +142,23 @@
|
|||||||
{
|
{
|
||||||
if (lineWidth != 1)
|
if (lineWidth != 1)
|
||||||
{
|
{
|
||||||
operations.Add(new SetLineWidth(lineWidth));
|
CurrentStream.Add(new SetLineWidth(lineWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
operations.Add(new AppendRectangle((decimal)position.X, (decimal)position.Y, width, height));
|
CurrentStream.Add(new AppendRectangle((decimal)position.X, (decimal)position.Y, width, height));
|
||||||
|
|
||||||
if (fill)
|
if (fill)
|
||||||
{
|
{
|
||||||
operations.Add(FillPathEvenOddRuleAndStroke.Value);
|
CurrentStream.Add(FillPathEvenOddRuleAndStroke.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
operations.Add(StrokePath.Value);
|
CurrentStream.Add(StrokePath.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineWidth != 1)
|
if (lineWidth != 1)
|
||||||
{
|
{
|
||||||
operations.Add(new SetLineWidth(lineWidth));
|
CurrentStream.Add(new SetLineWidth(lineWidth));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +170,8 @@
|
|||||||
/// <param name="b">Blue - 0 to 255</param>
|
/// <param name="b">Blue - 0 to 255</param>
|
||||||
public void SetStrokeColor(byte r, byte g, byte b)
|
public void SetStrokeColor(byte r, byte g, byte b)
|
||||||
{
|
{
|
||||||
operations.Add(Push.Value);
|
CurrentStream.Add(Push.Value);
|
||||||
operations.Add(new SetStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b)));
|
CurrentStream.Add(new SetStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -136,8 +182,8 @@
|
|||||||
/// <param name="b">Blue - 0 to 1</param>
|
/// <param name="b">Blue - 0 to 1</param>
|
||||||
internal void SetStrokeColorExact(decimal r, decimal g, decimal b)
|
internal void SetStrokeColorExact(decimal r, decimal g, decimal b)
|
||||||
{
|
{
|
||||||
operations.Add(Push.Value);
|
CurrentStream.Add(Push.Value);
|
||||||
operations.Add(new SetStrokeColorDeviceRgb(CheckRgbDecimal(r, nameof(r)),
|
CurrentStream.Add(new SetStrokeColorDeviceRgb(CheckRgbDecimal(r, nameof(r)),
|
||||||
CheckRgbDecimal(g, nameof(g)), CheckRgbDecimal(b, nameof(b))));
|
CheckRgbDecimal(g, nameof(g)), CheckRgbDecimal(b, nameof(b))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +195,8 @@
|
|||||||
/// <param name="b">Blue - 0 to 255</param>
|
/// <param name="b">Blue - 0 to 255</param>
|
||||||
public void SetTextAndFillColor(byte r, byte g, byte b)
|
public void SetTextAndFillColor(byte r, byte g, byte b)
|
||||||
{
|
{
|
||||||
operations.Add(Push.Value);
|
CurrentStream.Add(Push.Value);
|
||||||
operations.Add(new SetNonStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b)));
|
CurrentStream.Add(new SetNonStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -158,7 +204,7 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ResetColor()
|
public void ResetColor()
|
||||||
{
|
{
|
||||||
operations.Add(Pop.Value);
|
CurrentStream.Add(Pop.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -184,7 +230,7 @@
|
|||||||
throw new ArgumentNullException(nameof(text));
|
throw new ArgumentNullException(nameof(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontProgram))
|
if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontStore))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " +
|
throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " +
|
||||||
$"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font));
|
$"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font));
|
||||||
@@ -195,6 +241,8 @@
|
|||||||
throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0");
|
throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fontProgram = fontStore.FontProgram;
|
||||||
|
|
||||||
var fm = fontProgram.GetFontMatrix();
|
var fm = fontProgram.GetFontMatrix();
|
||||||
|
|
||||||
var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y);
|
var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y);
|
||||||
@@ -227,7 +275,7 @@
|
|||||||
throw new ArgumentNullException(nameof(text));
|
throw new ArgumentNullException(nameof(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontProgram))
|
if (!documentBuilder.Fonts.TryGetValue(font.Id, out var fontStore))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " +
|
throw new ArgumentException($"No font has been added to the PdfDocumentBuilder with Id: {font.Id}. " +
|
||||||
$"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font));
|
$"Use {nameof(documentBuilder.AddTrueTypeFont)} to register a font.", nameof(font));
|
||||||
@@ -238,21 +286,23 @@
|
|||||||
throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0");
|
throw new ArgumentOutOfRangeException(nameof(fontSize), "Font size must be greater than 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fontProgram = fontStore.FontProgram;
|
||||||
|
|
||||||
var fm = fontProgram.GetFontMatrix();
|
var fm = fontProgram.GetFontMatrix();
|
||||||
|
|
||||||
var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y);
|
var textMatrix = TransformationMatrix.FromValues(1, 0, 0, 1, position.X, position.Y);
|
||||||
|
|
||||||
var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix);
|
var letters = DrawLetters(text, fontProgram, fm, fontSize, textMatrix);
|
||||||
|
|
||||||
operations.Add(BeginText.Value);
|
CurrentStream.Add(BeginText.Value);
|
||||||
operations.Add(new SetFontAndSize(font.Name, fontSize));
|
CurrentStream.Add(new SetFontAndSize(font.Name, fontSize));
|
||||||
operations.Add(new MoveToNextLineWithOffset((decimal)position.X, (decimal)position.Y));
|
CurrentStream.Add(new MoveToNextLineWithOffset((decimal)position.X, (decimal)position.Y));
|
||||||
var bytesPerShow = new List<byte>();
|
var bytesPerShow = new List<byte>();
|
||||||
foreach (var letter in text)
|
foreach (var letter in text)
|
||||||
{
|
{
|
||||||
if (char.IsWhiteSpace(letter))
|
if (char.IsWhiteSpace(letter))
|
||||||
{
|
{
|
||||||
operations.Add(new ShowText(bytesPerShow.ToArray()));
|
CurrentStream.Add(new ShowText(bytesPerShow.ToArray()));
|
||||||
bytesPerShow.Clear();
|
bytesPerShow.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,10 +312,10 @@
|
|||||||
|
|
||||||
if (bytesPerShow.Count > 0)
|
if (bytesPerShow.Count > 0)
|
||||||
{
|
{
|
||||||
operations.Add(new ShowText(bytesPerShow.ToArray()));
|
CurrentStream.Add(new ShowText(bytesPerShow.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
operations.Add(EndText.Value);
|
CurrentStream.Add(EndText.Value);
|
||||||
|
|
||||||
return letters;
|
return letters;
|
||||||
}
|
}
|
||||||
@@ -322,16 +372,16 @@
|
|||||||
|
|
||||||
resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(reference));
|
resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(reference));
|
||||||
|
|
||||||
operations.Add(Push.Value);
|
CurrentStream.Add(Push.Value);
|
||||||
// This needs to be the placement rectangle.
|
// This needs to be the placement rectangle.
|
||||||
operations.Add(new ModifyCurrentTransformationMatrix(new []
|
CurrentStream.Add(new ModifyCurrentTransformationMatrix(new []
|
||||||
{
|
{
|
||||||
(decimal)placementRectangle.Width, 0,
|
(decimal)placementRectangle.Width, 0,
|
||||||
0, (decimal)placementRectangle.Height,
|
0, (decimal)placementRectangle.Height,
|
||||||
(decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y
|
(decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y
|
||||||
}));
|
}));
|
||||||
operations.Add(new InvokeNamedXObject(key));
|
CurrentStream.Add(new InvokeNamedXObject(key));
|
||||||
operations.Add(Pop.Value);
|
CurrentStream.Add(Pop.Value);
|
||||||
|
|
||||||
return new AddedImage(reference, info.Width, info.Height);
|
return new AddedImage(reference, info.Width, info.Height);
|
||||||
}
|
}
|
||||||
@@ -361,16 +411,16 @@
|
|||||||
|
|
||||||
resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(image.Reference));
|
resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(image.Reference));
|
||||||
|
|
||||||
operations.Add(Push.Value);
|
CurrentStream.Add(Push.Value);
|
||||||
// This needs to be the placement rectangle.
|
// This needs to be the placement rectangle.
|
||||||
operations.Add(new ModifyCurrentTransformationMatrix(new[]
|
CurrentStream.Add(new ModifyCurrentTransformationMatrix(new[]
|
||||||
{
|
{
|
||||||
(decimal)placementRectangle.Width, 0,
|
(decimal)placementRectangle.Width, 0,
|
||||||
0, (decimal)placementRectangle.Height,
|
0, (decimal)placementRectangle.Height,
|
||||||
(decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y
|
(decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y
|
||||||
}));
|
}));
|
||||||
operations.Add(new InvokeNamedXObject(key));
|
CurrentStream.Add(new InvokeNamedXObject(key));
|
||||||
operations.Add(Pop.Value);
|
CurrentStream.Add(Pop.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -439,20 +489,183 @@
|
|||||||
|
|
||||||
resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(reference));
|
resourcesDictionary[NameToken.Xobject] = xobjects.With(key, new IndirectReferenceToken(reference));
|
||||||
|
|
||||||
operations.Add(Push.Value);
|
CurrentStream.Add(Push.Value);
|
||||||
// This needs to be the placement rectangle.
|
// This needs to be the placement rectangle.
|
||||||
operations.Add(new ModifyCurrentTransformationMatrix(new[]
|
CurrentStream.Add(new ModifyCurrentTransformationMatrix(new[]
|
||||||
{
|
{
|
||||||
(decimal)placementRectangle.Width, 0,
|
(decimal)placementRectangle.Width, 0,
|
||||||
0, (decimal)placementRectangle.Height,
|
0, (decimal)placementRectangle.Height,
|
||||||
(decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y
|
(decimal)placementRectangle.BottomLeft.X, (decimal)placementRectangle.BottomLeft.Y
|
||||||
}));
|
}));
|
||||||
operations.Add(new InvokeNamedXObject(key));
|
CurrentStream.Add(new InvokeNamedXObject(key));
|
||||||
operations.Add(Pop.Value);
|
CurrentStream.Add(Pop.Value);
|
||||||
|
|
||||||
return new AddedImage(reference, png.Width, png.Height);
|
return new AddedImage(reference, png.Width, png.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy a page from unknown source to this page
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="srcPage">Page to be copied</param>
|
||||||
|
public void CopyFrom(Page srcPage)
|
||||||
|
{
|
||||||
|
ContentStream destinationStream = null;
|
||||||
|
if (CurrentStream.Operations.Count > 0)
|
||||||
|
{
|
||||||
|
NewContentStreamAfter();
|
||||||
|
}
|
||||||
|
|
||||||
|
destinationStream = CurrentStream;
|
||||||
|
|
||||||
|
if (!srcPage.Dictionary.TryGet(NameToken.Resources, srcPage.pdfScanner, out DictionaryToken srcResourceDictionary))
|
||||||
|
{
|
||||||
|
// If the page doesn't have resources, then we copy the entire content stream, since not operation would collide
|
||||||
|
// with the ones already written
|
||||||
|
destinationStream.Operations.AddRange(srcPage.Operations);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: How should we handle any other token in the page dictionary (Eg. LastModified, MediaBox, CropBox, BleedBox, TrimBox, ArtBox,
|
||||||
|
// BoxColorInfo, Rotate, Group, Thumb, B, Dur, Trans, Annots, AA, Metadata, PieceInfo, StructParents, ID, PZ, SeparationInfo, Tabs,
|
||||||
|
// TemplateInstantiated, PresSteps, UserUnit, VP)
|
||||||
|
|
||||||
|
var operations = new List<IGraphicsStateOperation>(srcPage.Operations);
|
||||||
|
|
||||||
|
// We need to relocate the resources, and we have to make sure that none of the resources collide with
|
||||||
|
// the already written operation's resources
|
||||||
|
|
||||||
|
foreach (var set in srcResourceDictionary.Data)
|
||||||
|
{
|
||||||
|
var nameToken = NameToken.Create(set.Key);
|
||||||
|
if (nameToken == NameToken.Font || nameToken == NameToken.Xobject)
|
||||||
|
{
|
||||||
|
// We have to skip this two because we have a separate dictionary for them
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resourcesDictionary.TryGetValue(nameToken, out var currentToken))
|
||||||
|
{
|
||||||
|
// It means that this type of resources doesn't currently exist in the page, so we can copy it
|
||||||
|
// with no problem
|
||||||
|
resourcesDictionary[nameToken] = documentBuilder.CopyToken(set.Value, srcPage.pdfScanner);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: I need to find a test case
|
||||||
|
// It would have ExtendedGraphics or colorspaces, etc...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special cases
|
||||||
|
// Since we don't directly add font's to the pages resources, we have to go look at the document's font
|
||||||
|
if(srcResourceDictionary.TryGet(NameToken.Font, srcPage.pdfScanner, out DictionaryToken fontsDictionary))
|
||||||
|
{
|
||||||
|
Dictionary<NameToken, IToken> pageFontsDictionary = null;
|
||||||
|
if (resourcesDictionary.TryGetValue(NameToken.Font, out var pageFontsToken))
|
||||||
|
{
|
||||||
|
pageFontsDictionary = (pageFontsToken as DictionaryToken)?.Data.ToDictionary(k => NameToken.Create(k.Key), v => v.Value);
|
||||||
|
Debug.Assert(pageFontsDictionary != null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageFontsDictionary = new Dictionary<NameToken, IToken>();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var fontSet in fontsDictionary.Data)
|
||||||
|
{
|
||||||
|
var fontName = fontSet.Key;
|
||||||
|
var addedFont = documentBuilder.Fonts.Values.FirstOrDefault(f => f.FontKey.Name.Data == fontName);
|
||||||
|
if (addedFont != default)
|
||||||
|
{
|
||||||
|
// This would mean that the imported font collide with one of the added font. so we have to rename it
|
||||||
|
|
||||||
|
var newName = $"F{documentBuilder.fontId++}";
|
||||||
|
|
||||||
|
// Set all the pertinent SetFontAndSize operations with the new name
|
||||||
|
operations = operations.Select(op =>
|
||||||
|
{
|
||||||
|
if (!(op is SetFontAndSize fontAndSizeOperation))
|
||||||
|
{
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontAndSizeOperation.Font.Data == fontName)
|
||||||
|
{
|
||||||
|
return new SetFontAndSize(NameToken.Create(newName), fontAndSizeOperation.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return op;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
fontName = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(fontSet.Value is IndirectReferenceToken fontReferenceToken))
|
||||||
|
{
|
||||||
|
throw new PdfDocumentFormatException($"Expected a IndirectReferenceToken for the font, got a {fontSet.Value.GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pageFontsDictionary.Add(NameToken.Create(fontName), documentBuilder.CopyToken(fontReferenceToken, srcPage.pdfScanner));
|
||||||
|
}
|
||||||
|
|
||||||
|
resourcesDictionary[NameToken.Font] = new DictionaryToken(pageFontsDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we don't directly add xobjects's to the pages resources, we have to go look at the document's xobjects
|
||||||
|
if (srcResourceDictionary.TryGet(NameToken.Xobject, srcPage.pdfScanner, out DictionaryToken xobjectsDictionary))
|
||||||
|
{
|
||||||
|
Dictionary<NameToken, IToken> pageXobjectsDictionary = null;
|
||||||
|
if (resourcesDictionary.TryGetValue(NameToken.Xobject, out var pageXobjectToken))
|
||||||
|
{
|
||||||
|
pageXobjectsDictionary = (pageXobjectToken as DictionaryToken)?.Data.ToDictionary(k => NameToken.Create(k.Key), v => v.Value);
|
||||||
|
Debug.Assert(pageXobjectsDictionary != null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageXobjectsDictionary = new Dictionary<NameToken, IToken>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var xobjectNamesUsed = Enumerable.Range(0, imageKey).Select(i => $"I{i}");
|
||||||
|
foreach (var xobjectSet in xobjectsDictionary.Data)
|
||||||
|
{
|
||||||
|
var xobjectName = xobjectSet.Key;
|
||||||
|
if (xobjectName[0] == 'I' && xobjectNamesUsed.Any(s => s == xobjectName))
|
||||||
|
{
|
||||||
|
// This would mean that the imported xobject collide with one of the added image. so we have to rename it
|
||||||
|
var newName = $"I{imageKey++}";
|
||||||
|
|
||||||
|
// Set all the pertinent SetFontAndSize operations with the new name
|
||||||
|
operations = operations.Select(op =>
|
||||||
|
{
|
||||||
|
if (!(op is InvokeNamedXObject invokeNamedOperation))
|
||||||
|
{
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invokeNamedOperation.Name.Data == xobjectName)
|
||||||
|
{
|
||||||
|
return new InvokeNamedXObject(NameToken.Create(newName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return op;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
xobjectName = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(xobjectSet.Value is IndirectReferenceToken fontReferenceToken))
|
||||||
|
{
|
||||||
|
throw new PdfDocumentFormatException($"Expected a IndirectReferenceToken for the XObject, got a {xobjectSet.Value.GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pageXobjectsDictionary.Add(NameToken.Create(xobjectName), documentBuilder.CopyToken(fontReferenceToken, srcPage.pdfScanner));
|
||||||
|
}
|
||||||
|
|
||||||
|
resourcesDictionary[NameToken.Xobject] = new DictionaryToken(pageXobjectsDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
destinationStream.Operations.AddRange(operations);
|
||||||
|
}
|
||||||
|
|
||||||
private List<Letter> DrawLetters(string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix)
|
private List<Letter> DrawLetters(string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix)
|
||||||
{
|
{
|
||||||
var horizontalScaling = 1;
|
var horizontalScaling = 1;
|
||||||
@@ -531,7 +744,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to the raw page data structures for advanced editing use cases.
|
/// Provides access to the raw page data structures for advanced editing use cases.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AdvancedEditing
|
public class ContentStream
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The operations making up the page content stream.
|
/// The operations making up the page content stream.
|
||||||
@@ -539,11 +752,16 @@
|
|||||||
public List<IGraphicsStateOperation> Operations { get; }
|
public List<IGraphicsStateOperation> Operations { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="AdvancedEditing"/>.
|
/// Create a new <see cref="ContentStream"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal AdvancedEditing(List<IGraphicsStateOperation> operations)
|
internal ContentStream()
|
||||||
{
|
{
|
||||||
Operations = operations;
|
Operations = new List<IGraphicsStateOperation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Add(IGraphicsStateOperation newOperation)
|
||||||
|
{
|
||||||
|
Operations.Add(newOperation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user