From 73b7bb61bcf7bf89d5274dc40066f1f6558b27e3 Mon Sep 17 00:00:00 2001 From: Michael Plaisted Date: Mon, 2 Aug 2021 11:17:49 -0500 Subject: [PATCH] AddPage fixes for streams incorrectly being copied to page dict, add inheritance for media/crop box and rotation --- .../Documents/inherited_mediabox.pdf | 61 + .../Documents/steam_in_page_dict.pdf | Bin 0 -> 18374 bytes .../Writer/PdfDocumentBuilderTests.cs | 1936 +++++++++-------- .../Writer/PdfMergerTests.cs | 5 +- .../Writer/PdfDocumentBuilder.cs | 56 +- 5 files changed, 1094 insertions(+), 964 deletions(-) create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/inherited_mediabox.pdf create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/steam_in_page_dict.pdf diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/inherited_mediabox.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/inherited_mediabox.pdf new file mode 100644 index 00000000..2d4468c4 --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Integration/Documents/inherited_mediabox.pdf @@ -0,0 +1,61 @@ +%PDF-1.1 +1 0 obj +<> +endobj +2 0 obj +<> +endobj +3 0 obj +<< + /Type/Page + /Parent 2 0 R + /Resources << + /XObject << /SomeImage 4 0 R >> + >> + /Contents 5 0 R +>> +endobj +4 0 obj +<< + /Type/XObject + /Subtype/Image + /Width 200 % The width or height directly affects the image's file size. + /Height 100 + /ColorSpace/DeviceRGB + /DecodeParms [] % Forces NativeImageDecoder.isSupported to return false. + /BitsPerComponent 8 + /Length 580 + /Filter [ /ASCIIHexDecode /DCTDecode ] +>> +% convert -size 1x1 xc:red jpeg:- | xxd -p -c40 +stream +ffd8ffe000104a46494600010100000100010000ffdb004300030202020202030202020303030304 +060404040404080606050609080a0a090809090a0c0f0c0a0b0e0b09090d110d0e0f101011100a0c +12131210130f101010ffdb00430103030304030408040408100b090b101010101010101010101010 +1010101010101010101010101010101010101010101010101010101010101010101010101010ffc0 +0011080001000103011100021101031101ffc40014000100000000000000000000000000000008ff +c40014100100000000000000000000000000000000ffc40015010101000000000000000000000000 +00000709ffc40014110100000000000000000000000000000000ffda000c03010002110311003f00 +3a03154dffd9 +endstream +endobj +5 0 obj +<> +stream +500 0 0 400 0 0 cm +/SomeImage Do +endstream +endobj +xref +0 6 +0000000000 65535 f +0000000008 00000 n +0000000054 00000 n +0000000128 00000 n +0000000246 00000 n +0000001201 00000 n +trailer +<> +startxref +1281 +%%EOF diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/steam_in_page_dict.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/steam_in_page_dict.pdf new file mode 100644 index 0000000000000000000000000000000000000000..721018a716eb75c0769446fe1ecd852b05c4f5f9 GIT binary patch literal 18374 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 x.Text)); + + byte[] results = null; + using (var existing = PdfDocument.Open(contents, ParsingOptions.LenientParsingOff)) + using (var output = new PdfDocumentBuilder()) + { + output.AddPage(existing, 1); + results = output.Build(); + } + + using (var rewritted = PdfDocument.Open(results, ParsingOptions.LenientParsingOff)) + { + // really just checking for no exception... + var pg = rewritted.GetPage(1); + Assert.NotNull(pg.Content); } - } - - [Fact] - public void CanWriteSinglePageHelloWorld() - { - var builder = new PdfDocumentBuilder(); - - var page = builder.AddPage(PageSize.A4); - - 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(CanWriteSinglePageHelloWorld), b); - - Assert.NotEmpty(b); - - using (var document = PdfDocument.Open(b)) - { - var page1 = document.GetPage(1); - - Assert.Equal("Hello World!", page1.Text); - - var h = page1.Letters[0]; - - 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 < page1.Letters.Count; i++) - { - var readerLetter = page1.Letters[i]; - 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 CanWriteRobotoAccentedCharacters() - { - var builder = new PdfDocumentBuilder(); - - builder.DocumentInformation.Title = "Hello Roboto!"; - - var page = builder.AddPage(PageSize.A4); - - var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); - - page.AddText("eé", 12, new PdfPoint(30, 520), font); - - Assert.NotEmpty(page.CurrentStream.Operations); - - var b = builder.Build(); - - WriteFile(nameof(CanWriteRobotoAccentedCharacters), b); - - Assert.NotEmpty(b); - - using (var document = PdfDocument.Open(b)) - { - var page1 = document.GetPage(1); - - Assert.Equal("eé", page1.Text); - } - } - - [Fact] - public void WindowsOnlyCanWriteSinglePageAccentedCharactersSystemFont() - { - var builder = new PdfDocumentBuilder(); - - builder.DocumentInformation.Title = "Hello Windows!"; - - var page = builder.AddPage(PageSize.A4); - - var file = @"C:\Windows\Fonts\Calibri.ttf"; - - if (!File.Exists(file)) - { - return; - } - - byte[] bytes; - try - { - bytes = File.ReadAllBytes(file); - } - catch - { - return; - } - - var font = builder.AddTrueTypeFont(bytes); - - page.AddText("eé", 12, new PdfPoint(30, 520), font); - - Assert.NotEmpty(page.CurrentStream.Operations); - - var b = builder.Build(); - - WriteFile(nameof(WindowsOnlyCanWriteSinglePageAccentedCharactersSystemFont), b); - - Assert.NotEmpty(b); - - using (var document = PdfDocument.Open(b)) - { - var page1 = document.GetPage(1); - - Assert.Equal("eé", page1.Text); - } - } - - [Fact] - public void WindowsOnlyCanWriteSinglePageHelloWorldSystemFont() - { - var builder = new PdfDocumentBuilder(); - - builder.DocumentInformation.Title = "Hello Windows!"; - - var page = builder.AddPage(PageSize.A4); - - var file = @"C:\Windows\Fonts\BASKVILL.TTF"; - - if (!File.Exists(file)) - { - return; - } - - byte[] bytes; - try - { - bytes = File.ReadAllBytes(file); - } - catch - { - return; - } - - var font = builder.AddTrueTypeFont(bytes); - - 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); - - Assert.NotEmpty(page.CurrentStream.Operations); - - var b = builder.Build(); - - WriteFile(nameof(WindowsOnlyCanWriteSinglePageHelloWorldSystemFont), b); - - Assert.NotEmpty(b); - - using (var document = PdfDocument.Open(b)) - { - var page1 = document.GetPage(1); - - Assert.StartsWith("Hello World!", page1.Text); - - var h = page1.Letters[0]; - - Assert.Equal("H", h.Value); - Assert.Equal("BaskOldFace", 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]; - 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 CanWriteSinglePageWithAccentedCharacters() - { - var builder = new PdfDocumentBuilder(); - var page = builder.AddPage(PageSize.A4); - - var file = TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf"); - - var font = builder.AddTrueTypeFont(file); - - page.AddText("é (lower case, upper case É).", 9, - new PdfPoint(30, page.PageSize.Height - 50), font); - - var bytes = builder.Build(); - WriteFile(nameof(CanWriteSinglePageWithAccentedCharacters), bytes); - - using (var document = PdfDocument.Open(bytes)) - { - var page1 = document.GetPage(1); - - Assert.Equal("é (lower case, upper case É).", page1.Text); - } - } - - [Fact] - public void CanWriteTwoPageDocument() - { - var builder = new PdfDocumentBuilder(); - var page1 = builder.AddPage(PageSize.A4); - var page2 = builder.AddPage(PageSize.A4); - - var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); - - var topLine = new PdfPoint(30, page1.PageSize.Height - 60); - var letters = page1.AddText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor", 9, topLine, font); - page1.AddText("incididunt ut labore et dolore magna aliqua.", 9, new PdfPoint(30, topLine.Y - letters.Max(x => x.GlyphRectangle.Height) - 5), font); - - var page2Letters = page2.AddText("The very hungry caterpillar ate all the apples in the garden.", 12, topLine, font); - var left = (decimal)page2Letters[0].GlyphRectangle.Left; - var bottom = (decimal)page2Letters.Min(x => x.GlyphRectangle.Bottom); - var right = (decimal)page2Letters[page2Letters.Count - 1].GlyphRectangle.Right; - var top = (decimal)page2Letters.Max(x => x.GlyphRectangle.Top); - page2.SetStrokeColor(10, 250, 69); - page2.DrawRectangle(new PdfPoint(left, bottom), right - left, top - bottom); - - var bytes = builder.Build(); - WriteFile(nameof(CanWriteTwoPageDocument), bytes); - - using (var document = PdfDocument.Open(bytes)) - { - var page1Out = document.GetPage(1); - - Assert.StartsWith("Lorem ipsum dolor sit", page1Out.Text); - - var page2Out = document.GetPage(2); - - Assert.StartsWith("The very hungry caterpillar", page2Out.Text); - } - } - - [Fact] - public void CanWriteSinglePageWithCzechCharacters() - { - var builder = new PdfDocumentBuilder(); - var page = builder.AddPage(PageSize.A4); - - var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); - - page.AddText("Hello: řó", 9, - new PdfPoint(30, page.PageSize.Height - 50), font); - - var bytes = builder.Build(); - WriteFile(nameof(CanWriteSinglePageWithCzechCharacters), bytes); - - using (var document = PdfDocument.Open(bytes)) - { - var page1 = document.GetPage(1); - - Assert.Equal("Hello: řó", page1.Text); - } - } - - [Fact] - public void CanWriteSinglePageWithJpeg() - { - var builder = new PdfDocumentBuilder(); - var page = builder.AddPage(PageSize.A4); - - var font = builder.AddStandard14Font(Standard14Font.Helvetica); - - page.AddText("Smile", 12, new PdfPoint(25, page.PageSize.Height - 52), font); - - var img = IntegrationHelpers.GetDocumentPath("smile-250-by-160.jpg", false); - - var expectedBounds = new PdfRectangle(25, page.PageSize.Height - 300, 200, page.PageSize.Height - 200); - - var imageBytes = File.ReadAllBytes(img); - - page.AddJpeg(imageBytes, expectedBounds); - - var bytes = builder.Build(); - WriteFile(nameof(CanWriteSinglePageWithJpeg), bytes); - - using (var document = PdfDocument.Open(bytes)) - { - var page1 = document.GetPage(1); - - Assert.Equal("Smile", page1.Text); - - var image = Assert.Single(page1.GetImages()); - - Assert.NotNull(image); - - Assert.Equal(expectedBounds.BottomLeft, image.Bounds.BottomLeft); - Assert.Equal(expectedBounds.TopRight, image.Bounds.TopRight); - - Assert.Equal(imageBytes, image.RawBytes); - } - } - - [Fact] - public void CanWrite2PagesSharingJpeg() - { - var builder = new PdfDocumentBuilder(); - var page = builder.AddPage(PageSize.A4); - - var font = builder.AddStandard14Font(Standard14Font.Helvetica); - - page.AddText("Smile", 12, new PdfPoint(25, page.PageSize.Height - 52), font); - - var img = IntegrationHelpers.GetDocumentPath("smile-250-by-160.jpg", false); - - var expectedBounds1 = new PdfRectangle(25, page.PageSize.Height - 300, 200, page.PageSize.Height - 200); - - var imageBytes = File.ReadAllBytes(img); - - var expectedBounds2 = new PdfRectangle(25, 600, 75, 650); - - var jpeg = page.AddJpeg(imageBytes, expectedBounds1); - page.AddJpeg(jpeg, expectedBounds2); - - var expectedBounds3 = new PdfRectangle(30, 500, 130, 550); - - var page2 = builder.AddPage(PageSize.A4); - page2.AddJpeg(jpeg, expectedBounds3); - - var bytes = builder.Build(); - WriteFile(nameof(CanWrite2PagesSharingJpeg), bytes); - - using (var document = PdfDocument.Open(bytes)) - { - var page1 = document.GetPage(1); - - Assert.Equal("Smile", page1.Text); - - var page1Images = page1.GetImages().ToList(); - Assert.Equal(2, page1Images.Count); - - var image1 = page1Images[0]; - Assert.Equal(expectedBounds1, image1.Bounds); - - var image2 = page1Images[1]; - Assert.Equal(expectedBounds2, image2.Bounds); - - var page2Doc = document.GetPage(2); - - var image3 = Assert.Single(page2Doc.GetImages()); - - Assert.NotNull(image3); - - Assert.Equal(expectedBounds3, image3.Bounds); - - Assert.Equal(imageBytes, image1.RawBytes); - Assert.Equal(imageBytes, image2.RawBytes); - Assert.Equal(imageBytes, image3.RawBytes); - } - } - - [Theory] - [InlineData(PdfAStandard.A1B)] - [InlineData(PdfAStandard.A1A)] - [InlineData(PdfAStandard.A2B)] - [InlineData(PdfAStandard.A2A)] - public void CanGeneratePdfAFile(PdfAStandard standard) - { - var builder = new PdfDocumentBuilder - { - ArchiveStandard = standard - }; - - var page = builder.AddPage(PageSize.A4); - - var imgBytes = File.ReadAllBytes(IntegrationHelpers.GetDocumentPath("smile-250-by-160.jpg", false)); - page.AddJpeg(imgBytes, new PdfRectangle(50, 70, 150, 130)); - - var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); - - page.AddText($"Howdy PDF/{standard}!", 10, new PdfPoint(25, 700), font); - - var bytes = builder.Build(); - - WriteFile(nameof(CanGeneratePdfAFile) + standard, bytes); - - using (var pdf = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(1, pdf.NumberOfPages); - - Assert.True(pdf.TryGetXmpMetadata(out var xmp)); - - Assert.NotNull(xmp.GetXDocument()); - } - } - - [Fact] - public void CanWriteSinglePageWithPng() - { - var builder = new PdfDocumentBuilder(); - var page = builder.AddPage(PageSize.A4); - - var font = builder.AddStandard14Font(Standard14Font.Helvetica); - - page.AddText("Piggy", 12, new PdfPoint(25, page.PageSize.Height - 52), font); - - var img = IntegrationHelpers.GetDocumentPath("pdfpig.png", false); - - var expectedBounds = new PdfRectangle(25, page.PageSize.Height - 300, 200, page.PageSize.Height - 200); - - var imageBytes = File.ReadAllBytes(img); - - page.AddPng(imageBytes, expectedBounds); - - var bytes = builder.Build(); - WriteFile(nameof(CanWriteSinglePageWithPng), bytes); - - using (var document = PdfDocument.Open(bytes)) - { - var page1 = document.GetPage(1); - - Assert.Equal("Piggy", page1.Text); - - var image = Assert.Single(page1.GetImages()); - - Assert.NotNull(image); - - Assert.Equal(expectedBounds.BottomLeft, image.Bounds.BottomLeft); - Assert.Equal(expectedBounds.TopRight, image.Bounds.TopRight); - - Assert.True(image.TryGetPng(out var png)); - Assert.NotNull(png); - - WriteFile(nameof(CanWriteSinglePageWithPng) + "out", png, "png"); - } - } - - [Fact] - public void CanCreateDocumentInformationDictionaryWithNonAsciiCharacters() - { - const string littlePig = "маленький поросенок"; - var builder = new PdfDocumentBuilder(); - builder.DocumentInformation.Title = littlePig; - var page = builder.AddPage(PageSize.A4); - var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); - page.AddText(littlePig, 12, new PdfPoint(120, 600), font); - - var file = builder.Build(); - WriteFile(nameof(CanCreateDocumentInformationDictionaryWithNonAsciiCharacters), file); - using (var document = PdfDocument.Open(file)) - { - Assert.Equal(littlePig, document.Information.Title); - } - } - - [Fact] - public void CanCreateDocumentWithFilledRectangle() - { - var builder = new PdfDocumentBuilder(); - var page = builder.AddPage(PageSize.A4); - - page.SetTextAndFillColor(255, 0, 0); - page.SetStrokeColor(0, 0, 255); - - page.DrawRectangle(new PdfPoint(20, 100), 200, 100, 1.5m, true); - - var file = builder.Build(); - 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); - } - } - - [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)) - { - var builder = new PdfDocumentBuilder(); - builder.AddPage(docOne, 1); - builder.AddPage(docTwo, 1); - 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"); - var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); - - using (var docOne = PdfDocument.Open(one)) - using (var docTwo = PdfDocument.Open(two)) - using (var builder = new PdfDocumentBuilder()) - { - - builder.AddPage(docOne, 1); - builder.AddPage(docTwo, 1); - var result = builder.Build(); - PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.", false); - } - - - } - - [Fact] - public void CanDedupObjectsFromSameDoc_Builder() - { - var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); - - using (var doc = PdfDocument.Open(one)) - { - var builder = new PdfDocumentBuilder(); - builder.AddPage(doc, 1); - builder.AddPage(doc, 1); - - var result = builder.Build(); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(2, document.NumberOfPages); - Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29, - "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use - } - } - } - - [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)) - using (var builder = new PdfDocumentBuilder(new MemoryStream(), true, PdfWriterType.ObjectInMemoryDedup)) - { - builder.AddPage(doc, 1); - builder.AddPage(doc2, 1); - - var result = builder.Build(); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(2, document.NumberOfPages); - Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29, - "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use - } - } - } - - [Fact] - public void CanCreatePageTree() - { - var count = 25 * 25 * 25 + 1; - using (var builder = new PdfDocumentBuilder()) - { - for (var i = 0; i < count;i++) - { - builder.AddPage(PageSize.A4); - } - var result = builder.Build(); - WriteFile(nameof(CanCreatePageTree), result); - - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(count, document.NumberOfPages); - } - } - } - - [Fact] - public void CanWriteEmptyContentStream() - { - using (var builder = new PdfDocumentBuilder()) - { - builder.AddPage(PageSize.A4); - var result = builder.Build(); - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(1, document.NumberOfPages); - var pg = document.GetPage(1); - // single empty page should result in single content stream - Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken); - } - } - } - - [Fact] - public void CanWriteSingleContentStream() - { - using (var builder = new PdfDocumentBuilder()) - { - var pb = builder.AddPage(PageSize.A4); - pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); - var result = builder.Build(); - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(1, document.NumberOfPages); - var pg = document.GetPage(1); - // single empty page should result in single content stream - Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken); - } - } - } - - [Fact] - public void CanWriteAndIgnoreEmptyContentStream() - { - using (var builder = new PdfDocumentBuilder()) - { - var pb = builder.AddPage(PageSize.A4); - pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); - pb.NewContentStreamAfter(); - var result = builder.Build(); - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(1, document.NumberOfPages); - var pg = document.GetPage(1); - // empty stream should be ignored and resulting single stream should be written - Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken); - } - } - } - - [Fact] - public void CanWriteMultipleContentStream() - { - using (var builder = new PdfDocumentBuilder()) - { - var pb = builder.AddPage(PageSize.A4); - pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); - pb.NewContentStreamAfter(); - pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); - var result = builder.Build(); - using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - Assert.Equal(1, document.NumberOfPages); - var pg = document.GetPage(1); - // multiple streams should be written to array - var streams = pg.Dictionary.Data[NameToken.Contents] as ArrayToken; - Assert.NotNull(streams); - Assert.Equal(2, streams.Length); - } - } - } - - [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()) - { - var count1 = GetCounts(doc); - - for (var i = 1; i <= doc.NumberOfPages; i++) - { - builder.AddPage(doc, i); - } - var result = builder.Build(); - WriteFile(nameof(CopiedPagesResultInSameData) + "_" + name, result); - - using (var doc2 = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) - { - 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; - location += letter.Location.Y; - location += letter.Font.Name.Length; - } - } - } - - return (letters, location); - } - } - - private static void WriteFile(string name, byte[] bytes, string extension = "pdf") - { - try - { - if (!Directory.Exists("Builder")) - { - Directory.CreateDirectory("Builder"); - } - - var output = Path.Combine("Builder", $"{name}.{extension}"); - - File.WriteAllBytes(output, bytes); - } - catch - { - // ignored. - } - } - } + } + + + [Fact] + public void CanReadSingleBlankPage() + { + var result = CreateSingleBlankPage(); + + using (var document = PdfDocument.Open(result, new ParsingOptions { UseLenientParsing = false })) + { + Assert.Equal(1, document.NumberOfPages); + + var page = document.GetPage(1); + + Assert.Equal(PageSize.A4, page.Size); + + Assert.Empty(page.Letters); + + Assert.NotNull(document.Structure.Catalog); + + foreach (var offset in document.Structure.CrossReferenceTable.ObjectOffsets) + { + var obj = document.Structure.GetObject(offset.Key); + + Assert.NotNull(obj); + } + } + } + + private static byte[] CreateSingleBlankPage() + { + var builder = new PdfDocumentBuilder(); + + builder.AddPage(PageSize.A4); + + var result = builder.Build(); + + return result; + } + + [Fact] + public void CanWriteSinglePageStandard14FontHelloWorld() + { + var builder = new PdfDocumentBuilder(); + + PdfPageBuilder page = builder.AddPage(PageSize.A4); + + PdfDocumentBuilder.AddedFont font = builder.AddStandard14Font(Standard14Font.Helvetica); + + page.AddText("Hello World!", 12, new PdfPoint(25, 520), font); + + var b = builder.Build(); + + WriteFile(nameof(CanWriteSinglePageStandard14FontHelloWorld), b); + + using (var document = PdfDocument.Open(b)) + { + var page1 = document.GetPage(1); + + Assert.Equal(new[] { "Hello", "World!" }, page1.GetWords().Select(x => x.Text)); + } + } + + [Fact] + public void CanWriteSinglePageHelloWorld() + { + var builder = new PdfDocumentBuilder(); + + var page = builder.AddPage(PageSize.A4); + + 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(CanWriteSinglePageHelloWorld), b); + + Assert.NotEmpty(b); + + using (var document = PdfDocument.Open(b)) + { + var page1 = document.GetPage(1); + + Assert.Equal("Hello World!", page1.Text); + + var h = page1.Letters[0]; + + 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 < page1.Letters.Count; i++) + { + var readerLetter = page1.Letters[i]; + 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 CanWriteRobotoAccentedCharacters() + { + var builder = new PdfDocumentBuilder(); + + builder.DocumentInformation.Title = "Hello Roboto!"; + + var page = builder.AddPage(PageSize.A4); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); + + page.AddText("eé", 12, new PdfPoint(30, 520), font); + + Assert.NotEmpty(page.CurrentStream.Operations); + + var b = builder.Build(); + + WriteFile(nameof(CanWriteRobotoAccentedCharacters), b); + + Assert.NotEmpty(b); + + using (var document = PdfDocument.Open(b)) + { + var page1 = document.GetPage(1); + + Assert.Equal("eé", page1.Text); + } + } + + [Fact] + public void WindowsOnlyCanWriteSinglePageAccentedCharactersSystemFont() + { + var builder = new PdfDocumentBuilder(); + + builder.DocumentInformation.Title = "Hello Windows!"; + + var page = builder.AddPage(PageSize.A4); + + var file = @"C:\Windows\Fonts\Calibri.ttf"; + + if (!File.Exists(file)) + { + return; + } + + byte[] bytes; + try + { + bytes = File.ReadAllBytes(file); + } + catch + { + return; + } + + var font = builder.AddTrueTypeFont(bytes); + + page.AddText("eé", 12, new PdfPoint(30, 520), font); + + Assert.NotEmpty(page.CurrentStream.Operations); + + var b = builder.Build(); + + WriteFile(nameof(WindowsOnlyCanWriteSinglePageAccentedCharactersSystemFont), b); + + Assert.NotEmpty(b); + + using (var document = PdfDocument.Open(b)) + { + var page1 = document.GetPage(1); + + Assert.Equal("eé", page1.Text); + } + } + + [Fact] + public void WindowsOnlyCanWriteSinglePageHelloWorldSystemFont() + { + var builder = new PdfDocumentBuilder(); + + builder.DocumentInformation.Title = "Hello Windows!"; + + var page = builder.AddPage(PageSize.A4); + + var file = @"C:\Windows\Fonts\BASKVILL.TTF"; + + if (!File.Exists(file)) + { + return; + } + + byte[] bytes; + try + { + bytes = File.ReadAllBytes(file); + } + catch + { + return; + } + + var font = builder.AddTrueTypeFont(bytes); + + 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); + + Assert.NotEmpty(page.CurrentStream.Operations); + + var b = builder.Build(); + + WriteFile(nameof(WindowsOnlyCanWriteSinglePageHelloWorldSystemFont), b); + + Assert.NotEmpty(b); + + using (var document = PdfDocument.Open(b)) + { + var page1 = document.GetPage(1); + + Assert.StartsWith("Hello World!", page1.Text); + + var h = page1.Letters[0]; + + Assert.Equal("H", h.Value); + Assert.Equal("BaskOldFace", 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]; + 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 CanWriteSinglePageWithAccentedCharacters() + { + var builder = new PdfDocumentBuilder(); + var page = builder.AddPage(PageSize.A4); + + var file = TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf"); + + var font = builder.AddTrueTypeFont(file); + + page.AddText("é (lower case, upper case É).", 9, + new PdfPoint(30, page.PageSize.Height - 50), font); + + var bytes = builder.Build(); + WriteFile(nameof(CanWriteSinglePageWithAccentedCharacters), bytes); + + using (var document = PdfDocument.Open(bytes)) + { + var page1 = document.GetPage(1); + + Assert.Equal("é (lower case, upper case É).", page1.Text); + } + } + + [Fact] + public void CanWriteTwoPageDocument() + { + var builder = new PdfDocumentBuilder(); + var page1 = builder.AddPage(PageSize.A4); + var page2 = builder.AddPage(PageSize.A4); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); + + var topLine = new PdfPoint(30, page1.PageSize.Height - 60); + var letters = page1.AddText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor", 9, topLine, font); + page1.AddText("incididunt ut labore et dolore magna aliqua.", 9, new PdfPoint(30, topLine.Y - letters.Max(x => x.GlyphRectangle.Height) - 5), font); + + var page2Letters = page2.AddText("The very hungry caterpillar ate all the apples in the garden.", 12, topLine, font); + var left = (decimal)page2Letters[0].GlyphRectangle.Left; + var bottom = (decimal)page2Letters.Min(x => x.GlyphRectangle.Bottom); + var right = (decimal)page2Letters[page2Letters.Count - 1].GlyphRectangle.Right; + var top = (decimal)page2Letters.Max(x => x.GlyphRectangle.Top); + page2.SetStrokeColor(10, 250, 69); + page2.DrawRectangle(new PdfPoint(left, bottom), right - left, top - bottom); + + var bytes = builder.Build(); + WriteFile(nameof(CanWriteTwoPageDocument), bytes); + + using (var document = PdfDocument.Open(bytes)) + { + var page1Out = document.GetPage(1); + + Assert.StartsWith("Lorem ipsum dolor sit", page1Out.Text); + + var page2Out = document.GetPage(2); + + Assert.StartsWith("The very hungry caterpillar", page2Out.Text); + } + } + + [Fact] + public void CanWriteSinglePageWithCzechCharacters() + { + var builder = new PdfDocumentBuilder(); + var page = builder.AddPage(PageSize.A4); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); + + page.AddText("Hello: řó", 9, + new PdfPoint(30, page.PageSize.Height - 50), font); + + var bytes = builder.Build(); + WriteFile(nameof(CanWriteSinglePageWithCzechCharacters), bytes); + + using (var document = PdfDocument.Open(bytes)) + { + var page1 = document.GetPage(1); + + Assert.Equal("Hello: řó", page1.Text); + } + } + + [Fact] + public void CanWriteSinglePageWithJpeg() + { + var builder = new PdfDocumentBuilder(); + var page = builder.AddPage(PageSize.A4); + + var font = builder.AddStandard14Font(Standard14Font.Helvetica); + + page.AddText("Smile", 12, new PdfPoint(25, page.PageSize.Height - 52), font); + + var img = IntegrationHelpers.GetDocumentPath("smile-250-by-160.jpg", false); + + var expectedBounds = new PdfRectangle(25, page.PageSize.Height - 300, 200, page.PageSize.Height - 200); + + var imageBytes = File.ReadAllBytes(img); + + page.AddJpeg(imageBytes, expectedBounds); + + var bytes = builder.Build(); + WriteFile(nameof(CanWriteSinglePageWithJpeg), bytes); + + using (var document = PdfDocument.Open(bytes)) + { + var page1 = document.GetPage(1); + + Assert.Equal("Smile", page1.Text); + + var image = Assert.Single(page1.GetImages()); + + Assert.NotNull(image); + + Assert.Equal(expectedBounds.BottomLeft, image.Bounds.BottomLeft); + Assert.Equal(expectedBounds.TopRight, image.Bounds.TopRight); + + Assert.Equal(imageBytes, image.RawBytes); + } + } + + [Fact] + public void CanWrite2PagesSharingJpeg() + { + var builder = new PdfDocumentBuilder(); + var page = builder.AddPage(PageSize.A4); + + var font = builder.AddStandard14Font(Standard14Font.Helvetica); + + page.AddText("Smile", 12, new PdfPoint(25, page.PageSize.Height - 52), font); + + var img = IntegrationHelpers.GetDocumentPath("smile-250-by-160.jpg", false); + + var expectedBounds1 = new PdfRectangle(25, page.PageSize.Height - 300, 200, page.PageSize.Height - 200); + + var imageBytes = File.ReadAllBytes(img); + + var expectedBounds2 = new PdfRectangle(25, 600, 75, 650); + + var jpeg = page.AddJpeg(imageBytes, expectedBounds1); + page.AddJpeg(jpeg, expectedBounds2); + + var expectedBounds3 = new PdfRectangle(30, 500, 130, 550); + + var page2 = builder.AddPage(PageSize.A4); + page2.AddJpeg(jpeg, expectedBounds3); + + var bytes = builder.Build(); + WriteFile(nameof(CanWrite2PagesSharingJpeg), bytes); + + using (var document = PdfDocument.Open(bytes)) + { + var page1 = document.GetPage(1); + + Assert.Equal("Smile", page1.Text); + + var page1Images = page1.GetImages().ToList(); + Assert.Equal(2, page1Images.Count); + + var image1 = page1Images[0]; + Assert.Equal(expectedBounds1, image1.Bounds); + + var image2 = page1Images[1]; + Assert.Equal(expectedBounds2, image2.Bounds); + + var page2Doc = document.GetPage(2); + + var image3 = Assert.Single(page2Doc.GetImages()); + + Assert.NotNull(image3); + + Assert.Equal(expectedBounds3, image3.Bounds); + + Assert.Equal(imageBytes, image1.RawBytes); + Assert.Equal(imageBytes, image2.RawBytes); + Assert.Equal(imageBytes, image3.RawBytes); + } + } + + [Theory] + [InlineData(PdfAStandard.A1B)] + [InlineData(PdfAStandard.A1A)] + [InlineData(PdfAStandard.A2B)] + [InlineData(PdfAStandard.A2A)] + public void CanGeneratePdfAFile(PdfAStandard standard) + { + var builder = new PdfDocumentBuilder + { + ArchiveStandard = standard + }; + + var page = builder.AddPage(PageSize.A4); + + var imgBytes = File.ReadAllBytes(IntegrationHelpers.GetDocumentPath("smile-250-by-160.jpg", false)); + page.AddJpeg(imgBytes, new PdfRectangle(50, 70, 150, 130)); + + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); + + page.AddText($"Howdy PDF/{standard}!", 10, new PdfPoint(25, 700), font); + + var bytes = builder.Build(); + + WriteFile(nameof(CanGeneratePdfAFile) + standard, bytes); + + using (var pdf = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, pdf.NumberOfPages); + + Assert.True(pdf.TryGetXmpMetadata(out var xmp)); + + Assert.NotNull(xmp.GetXDocument()); + } + } + + [Fact] + public void CanWriteSinglePageWithPng() + { + var builder = new PdfDocumentBuilder(); + var page = builder.AddPage(PageSize.A4); + + var font = builder.AddStandard14Font(Standard14Font.Helvetica); + + page.AddText("Piggy", 12, new PdfPoint(25, page.PageSize.Height - 52), font); + + var img = IntegrationHelpers.GetDocumentPath("pdfpig.png", false); + + var expectedBounds = new PdfRectangle(25, page.PageSize.Height - 300, 200, page.PageSize.Height - 200); + + var imageBytes = File.ReadAllBytes(img); + + page.AddPng(imageBytes, expectedBounds); + + var bytes = builder.Build(); + WriteFile(nameof(CanWriteSinglePageWithPng), bytes); + + using (var document = PdfDocument.Open(bytes)) + { + var page1 = document.GetPage(1); + + Assert.Equal("Piggy", page1.Text); + + var image = Assert.Single(page1.GetImages()); + + Assert.NotNull(image); + + Assert.Equal(expectedBounds.BottomLeft, image.Bounds.BottomLeft); + Assert.Equal(expectedBounds.TopRight, image.Bounds.TopRight); + + Assert.True(image.TryGetPng(out var png)); + Assert.NotNull(png); + + WriteFile(nameof(CanWriteSinglePageWithPng) + "out", png, "png"); + } + } + + [Fact] + public void CanCreateDocumentInformationDictionaryWithNonAsciiCharacters() + { + const string littlePig = "маленький поросенок"; + var builder = new PdfDocumentBuilder(); + builder.DocumentInformation.Title = littlePig; + var page = builder.AddPage(PageSize.A4); + var font = builder.AddTrueTypeFont(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.ttf")); + page.AddText(littlePig, 12, new PdfPoint(120, 600), font); + + var file = builder.Build(); + WriteFile(nameof(CanCreateDocumentInformationDictionaryWithNonAsciiCharacters), file); + using (var document = PdfDocument.Open(file)) + { + Assert.Equal(littlePig, document.Information.Title); + } + } + + [Fact] + public void CanCreateDocumentWithFilledRectangle() + { + var builder = new PdfDocumentBuilder(); + var page = builder.AddPage(PageSize.A4); + + page.SetTextAndFillColor(255, 0, 0); + page.SetStrokeColor(0, 0, 255); + + page.DrawRectangle(new PdfPoint(20, 100), 200, 100, 1.5m, true); + + var file = builder.Build(); + 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); + } + } + + [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)) + { + var builder = new PdfDocumentBuilder(); + builder.AddPage(docOne, 1); + builder.AddPage(docTwo, 1); + 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"); + var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf"); + + using (var docOne = PdfDocument.Open(one)) + using (var docTwo = PdfDocument.Open(two)) + using (var builder = new PdfDocumentBuilder()) + { + + builder.AddPage(docOne, 1); + builder.AddPage(docTwo, 1); + var result = builder.Build(); + PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.", false); + } + + + } + + [Fact] + public void CanDedupObjectsFromSameDoc_Builder() + { + var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf"); + + using (var doc = PdfDocument.Open(one)) + { + var builder = new PdfDocumentBuilder(); + builder.AddPage(doc, 1); + builder.AddPage(doc, 1); + + var result = builder.Build(); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(2, document.NumberOfPages); + Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29, + "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use + } + } + } + + [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)) + using (var builder = new PdfDocumentBuilder(new MemoryStream(), true, PdfWriterType.ObjectInMemoryDedup)) + { + builder.AddPage(doc, 1); + builder.AddPage(doc2, 1); + + var result = builder.Build(); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(2, document.NumberOfPages); + Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29, + "Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use + } + } + } + + [Fact] + public void CanCreatePageTree() + { + var count = 25 * 25 * 25 + 1; + using (var builder = new PdfDocumentBuilder()) + { + for (var i = 0; i < count; i++) + { + builder.AddPage(PageSize.A4); + } + var result = builder.Build(); + WriteFile(nameof(CanCreatePageTree), result); + + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(count, document.NumberOfPages); + } + } + } + + [Fact] + public void CanWriteEmptyContentStream() + { + using (var builder = new PdfDocumentBuilder()) + { + builder.AddPage(PageSize.A4); + var result = builder.Build(); + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, document.NumberOfPages); + var pg = document.GetPage(1); + // single empty page should result in single content stream + Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken); + } + } + } + + [Fact] + public void CanWriteSingleContentStream() + { + using (var builder = new PdfDocumentBuilder()) + { + var pb = builder.AddPage(PageSize.A4); + pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); + var result = builder.Build(); + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, document.NumberOfPages); + var pg = document.GetPage(1); + // single empty page should result in single content stream + Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken); + } + } + } + + [Fact] + public void CanWriteAndIgnoreEmptyContentStream() + { + using (var builder = new PdfDocumentBuilder()) + { + var pb = builder.AddPage(PageSize.A4); + pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); + pb.NewContentStreamAfter(); + var result = builder.Build(); + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, document.NumberOfPages); + var pg = document.GetPage(1); + // empty stream should be ignored and resulting single stream should be written + Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken); + } + } + } + + [Fact] + public void CanWriteMultipleContentStream() + { + using (var builder = new PdfDocumentBuilder()) + { + var pb = builder.AddPage(PageSize.A4); + pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); + pb.NewContentStreamAfter(); + pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2)); + var result = builder.Build(); + using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + Assert.Equal(1, document.NumberOfPages); + var pg = document.GetPage(1); + // multiple streams should be written to array + var streams = pg.Dictionary.Data[NameToken.Contents] as ArrayToken; + Assert.NotNull(streams); + Assert.Equal(2, streams.Length); + } + } + } + + [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()) + { + var count1 = GetCounts(doc); + + for (var i = 1; i <= doc.NumberOfPages; i++) + { + builder.AddPage(doc, i); + } + var result = builder.Build(); + WriteFile(nameof(CopiedPagesResultInSameData) + "_" + name, result); + + using (var doc2 = PdfDocument.Open(result, ParsingOptions.LenientParsingOff)) + { + 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; + location += letter.Location.Y; + location += letter.Font.Name.Length; + } + } + } + + return (letters, location); + } + } + + private static void WriteFile(string name, byte[] bytes, string extension = "pdf") + { + try + { + if (!Directory.Exists("Builder")) + { + Directory.CreateDirectory("Builder"); + } + + var output = Path.Combine("Builder", $"{name}.{extension}"); + + File.WriteAllBytes(output, bytes); + } + catch + { + // ignored. + } + } + } } diff --git a/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs b/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs index ec7cee85..74a906e4 100644 --- a/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs +++ b/src/UglyToad.PdfPig.Tests/Writer/PdfMergerTests.cs @@ -165,9 +165,8 @@ ); } } - } - - + } + [Fact] public void CanMergeMultipleWithSelection() { diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index 138be36b..0682cceb 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -90,7 +90,7 @@ namespace UglyToad.PdfPig.Writer /// If stream should be disposed when builder is. /// Type of pdf stream writer to use /// Pdf version to use in header. - public PdfDocumentBuilder(Stream stream, bool disposeStream=false, PdfWriterType type=PdfWriterType.Default, decimal version=1.7m) + public PdfDocumentBuilder(Stream stream, bool disposeStream = false, PdfWriterType type = PdfWriterType.Default, decimal version = 1.7m) { switch (type) { @@ -286,10 +286,10 @@ namespace UglyToad.PdfPig.Writer public DictionaryToken Page { get; set; } public IReadOnlyList Parents { get; set; } } - private readonly ConditionalWeakTable> existingCopies = + private readonly ConditionalWeakTable> existingCopies = new ConditionalWeakTable>(); - private readonly ConditionalWeakTable> existingTrees = - new ConditionalWeakTable>(); + private readonly ConditionalWeakTable> existingTrees = + new ConditionalWeakTable>(); /// /// Add a new page with the specified size, this page will be included in the output when is called. /// @@ -312,7 +312,8 @@ namespace UglyToad.PdfPig.Writer { pagesInfos[i] = new PageInfo { - Page = pageDict, Parents = parents + Page = pageDict, + Parents = parents }; i++; } @@ -350,7 +351,7 @@ namespace UglyToad.PdfPig.Writer // manually copy page dict / resources as we need to modify some var copiedPageDict = new Dictionary(); - Dictionary resources = new Dictionary(); + Dictionary resources = new Dictionary(); // just put all parent resources into new page foreach (var dict in pageInfo.Parents) @@ -358,6 +359,18 @@ namespace UglyToad.PdfPig.Writer if (dict.TryGet(NameToken.Resources, out var resourceToken)) { CopyResourceDict(resourceToken, resources); + } + if (dict.TryGet(NameToken.MediaBox, out var mb)) + { + copiedPageDict[NameToken.MediaBox] = WriterUtil.CopyToken(context, mb, document.Structure.TokenScanner, refs); + } + if (dict.TryGet(NameToken.CropBox, out var cb)) + { + copiedPageDict[NameToken.CropBox] = WriterUtil.CopyToken(context, cb, document.Structure.TokenScanner, refs); + } + if (dict.TryGet(NameToken.Rotate, out var rt)) + { + copiedPageDict[NameToken.Rotate] = WriterUtil.CopyToken(context, rt, document.Structure.TokenScanner, refs); } } @@ -396,10 +409,19 @@ namespace UglyToad.PdfPig.Writer { if (!destinationDict.ContainsKey(NameToken.Create(item.Key))) { - if (item.Value is IndirectReferenceToken ir) + if (item.Value is IndirectReferenceToken ir) { - // convert indirect to direct as PdfPageBuilder needs to modify resource entries - destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, document.Structure.TokenScanner.Get(ir.Data).Data, document.Structure.TokenScanner, refs); + // convert indirect to direct as PdfPageBuilder needs to modify resource entries + var obj = document.Structure.TokenScanner.Get(ir.Data); + if (obj.Data is StreamToken) + { + // rare case, have seen /SubType as stream token, can't make direct + destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, item.Value, document.Structure.TokenScanner, refs); + } + else + { + destinationDict[NameToken.Create(item.Key)] = WriterUtil.CopyToken(context, obj.Data, document.Structure.TokenScanner, refs); + } } else { @@ -458,7 +480,7 @@ namespace UglyToad.PdfPig.Writer } const int desiredLeafSize = 25; // allow customization at some point? - var numLeafs = (int) Math.Ceiling(Decimal.Divide(Pages.Count, desiredLeafSize)); + var numLeafs = (int)Math.Ceiling(Decimal.Divide(Pages.Count, desiredLeafSize)); var leafRefs = new List(); var leafChildren = new List>(); @@ -584,17 +606,17 @@ namespace UglyToad.PdfPig.Writer { // TODO shorten page tree when there is a single or small number of pages left in a branch var count = 0; - var thisObj = context.ReserveObjectNumber(); - + var thisObj = context.ReserveObjectNumber(); + var children = new List(); if (pagesNodes.Count > desiredLeafSize) { - var currentTreeDepth = (int) Math.Ceiling(Math.Log(pagesNodes.Count, desiredLeafSize)); - var perBranch = (int) Math.Ceiling(Math.Pow(desiredLeafSize, currentTreeDepth - 1)); + var currentTreeDepth = (int)Math.Ceiling(Math.Log(pagesNodes.Count, desiredLeafSize)); + var perBranch = (int)Math.Ceiling(Math.Pow(desiredLeafSize, currentTreeDepth - 1)); var branches = (int)Math.Ceiling(decimal.Divide(pagesNodes.Count, (decimal)perBranch)); for (var i = 0; i < branches; i++) { - var part = pagesNodes.Skip(i*perBranch).Take(perBranch).ToList(); + var part = pagesNodes.Skip(i * perBranch).Take(perBranch).ToList(); var result = CreatePageTree(part, thisObj); count += result.Count; children.Add(result.Ref); @@ -787,8 +809,8 @@ namespace UglyToad.PdfPig.Writer if (!completed) { CompleteDocument(); - } - + } + context.Dispose(); } }