diff --git a/examples/AdvancedTextExtraction.cs b/examples/AdvancedTextExtraction.cs index ff605fb5..036ed1aa 100644 --- a/examples/AdvancedTextExtraction.cs +++ b/examples/AdvancedTextExtraction.cs @@ -10,7 +10,8 @@ public static class AdvancedTextExtraction { public static void Run(string filePath) - { + { +#if YET_TO_BE_DONE var sb = new StringBuilder(); using (var document = PdfDocument.Open(filePath)) @@ -86,6 +87,7 @@ } Console.WriteLine(sb.ToString()); +#endif } } } diff --git a/examples/Program.cs b/examples/Program.cs index 5297a657..108128d6 100644 --- a/examples/Program.cs +++ b/examples/Program.cs @@ -45,9 +45,14 @@ }, {7, ("Advance text extraction using layout analysis algorithms", - () => AdvancedTextExtraction.Run(Path.Combine(filesDirectory, "ICML03-081.pdf"))) - } - }; + () => AdvancedTextExtraction.Run(Path.Combine(filesDirectory, "ICML03-081.pdf"))) + }, + { + 8, + ("Extract Words with newline detection (example with algorithm). Issue 512", + () => OpenDocumentAndExtractWords.Run(Path.Combine(filesDirectory, "OPEN.RABBIT.ENGLISH.LOP.pdf"))) + } + }; var choices = string.Join(Environment.NewLine, examples.Select(x => $"{x.Key}: {x.Value.name}")); diff --git a/src/UglyToad.PdfPig.Fonts/CorruptCompressedDataException.cs b/src/UglyToad.PdfPig.Fonts/CorruptCompressedDataException.cs new file mode 100644 index 00000000..9cb4b394 --- /dev/null +++ b/src/UglyToad.PdfPig.Fonts/CorruptCompressedDataException.cs @@ -0,0 +1,34 @@ +namespace UglyToad.PdfPig.Fonts +{ + using System; + using System.Runtime.Serialization; + + /// + /// Thrown when a PDF contains an invalid compressed data stream. + /// + [Serializable] + public class CorruptCompressedDataException : Exception + { + /// + public CorruptCompressedDataException() + { + } + + /// + public CorruptCompressedDataException(string message) : base(message) + { + } + + /// + public CorruptCompressedDataException(string message, Exception inner) : base(message, inner) + { + } + + /// + protected CorruptCompressedDataException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/FICTIF_TABLE_INDEX.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/FICTIF_TABLE_INDEX.pdf new file mode 100644 index 00000000..db5f2305 Binary files /dev/null and b/src/UglyToad.PdfPig.Tests/Integration/Documents/FICTIF_TABLE_INDEX.pdf differ diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/OPEN.RABBIT.ENGLISH.LOP.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/OPEN.RABBIT.ENGLISH.LOP.pdf new file mode 100644 index 00000000..0ca6c9a6 Binary files /dev/null and b/src/UglyToad.PdfPig.Tests/Integration/Documents/OPEN.RABBIT.ENGLISH.LOP.pdf differ diff --git a/src/UglyToad.PdfPig.Tests/Integration/IndexedPageSummaryFileTests.cs b/src/UglyToad.PdfPig.Tests/Integration/IndexedPageSummaryFileTests.cs new file mode 100644 index 00000000..beeea4b8 --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Integration/IndexedPageSummaryFileTests.cs @@ -0,0 +1,54 @@ +namespace UglyToad.PdfPig.Tests.Integration; + +using System.Linq; +using Xunit; + +public class IndexedPageSummaryFileTests +{ + private static string GetFilename() + { + return IntegrationHelpers.GetDocumentPath("FICTIF_TABLE_INDEX.pdf"); + } + + [Fact] + public void HasCorrectNumberOfPages() + { + using (var document = PdfDocument.Open(GetFilename())) + { + Assert.Equal(14, document.NumberOfPages); + } + } + + [Fact] + public void GetPagesWorks() + { + using (var document = PdfDocument.Open(GetFilename())) + { + var pageCount = document.GetPages().Count(); + + Assert.Equal(14, pageCount); + } + } + + [Theory] + [InlineData("M. HERNANDEZ DANIEL", 1)] + [InlineData("M. HERNANDEZ DANIEL", 2)] + [InlineData("Mme ALIBERT CHLOE AA", 3)] + [InlineData("Mme ALIBERT CHLOE AA", 4)] + [InlineData("M. SIMPSON BART AAA", 5)] + [InlineData("M. SIMPSON BART AAA", 6)] + [InlineData("M. BOND JAMES A", 7)] + [InlineData("M. BOND JAMES A", 8)] + [InlineData("M. DE BALZAC HONORE", 9)] + [InlineData("M. DE BALZAC HONORE", 10)] + [InlineData("M. STALLONE SILVESTER", 11)] + [InlineData("M. STALLONE SILVESTER", 12)] + [InlineData("M. SCOTT MICHAEL", 13)] + [InlineData("M. SCOTT MICHAEL", 14)] + public void CheckSpecificNamesPresence_InIndexedPageNumbersFile(string searchedName, int pageNumber) + { + using var document = PdfDocument.Open(GetFilename()); + var page = document.GetPage(pageNumber); + Assert.Contains(searchedName, page.Text); + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs b/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs index 374b8396..6a50d0c6 100644 --- a/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs @@ -51,7 +51,7 @@ var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log); Assert.Equal(1.2m, result.Version); - Assert.Equal(TestEnvironment.IsUnixPlatform ? 7 : 9, result.OffsetInFile); + Assert.Equal(TestEnvironment.IsSingleByteNewLine(input) ? 7 : 9, result.OffsetInFile); } [Fact] @@ -66,38 +66,42 @@ [Fact] public void HeaderPrecededByJunkNonLenientDoesNotThrow() - { - var scanner = StringBytesTestConverter.Scanner(@"one - %PDF-1.2"); + { + var input = @"one + %PDF-1.2"; + var scanner = StringBytesTestConverter.Scanner(input); var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log); Assert.Equal(1.2m, result.Version); - Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile); + Assert.Equal(TestEnvironment.IsSingleByteNewLine(input) ? 12 : 13, result.OffsetInFile); } [Fact] public void HeaderPrecededByJunkLenientReads() - { - var scanner = StringBytesTestConverter.Scanner(@"one - %PDF-1.7"); + { + var input = @"one + %PDF-1.7"; + var scanner = StringBytesTestConverter.Scanner(input); var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log); Assert.Equal(1.7m, result.Version); - Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile); + Assert.Equal(TestEnvironment.IsSingleByteNewLine(input) ? 12 : 13, result.OffsetInFile); } [Fact] public void HeaderPrecededByJunkDoesNotThrow() - { - var scanner = StringBytesTestConverter.Scanner(@"one two -three %PDF-1.6"); + { + var s = @"one two +three %PDF-1.6"; + + var scanner = StringBytesTestConverter.Scanner(s); var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log); Assert.Equal(1.6m, result.Version); - Assert.Equal(TestEnvironment.IsUnixPlatform ? 14 : 15, result.OffsetInFile); + Assert.Equal(TestEnvironment.IsSingleByteNewLine(s) ? 14 : 15, result.OffsetInFile); } [Fact] diff --git a/src/UglyToad.PdfPig.Tests/TestEnvironment.cs b/src/UglyToad.PdfPig.Tests/TestEnvironment.cs index abc44914..17545a27 100644 --- a/src/UglyToad.PdfPig.Tests/TestEnvironment.cs +++ b/src/UglyToad.PdfPig.Tests/TestEnvironment.cs @@ -4,6 +4,7 @@ public static class TestEnvironment { - public static readonly bool IsUnixPlatform = Environment.NewLine.Length == 1; + public static bool IsSingleByteNewLine(string s) => s.IndexOf('\r') < 0; + } } diff --git a/src/UglyToad.PdfPig/Content/Catalog.cs b/src/UglyToad.PdfPig/Content/Catalog.cs index 61906602..9bbdd71f 100644 --- a/src/UglyToad.PdfPig/Content/Catalog.cs +++ b/src/UglyToad.PdfPig/Content/Catalog.cs @@ -29,7 +29,12 @@ /// /// The page tree for this document containing all pages, page numbers and their dictionaries. /// - public PageTreeNode PageTree { get; } + public PageTreeNode PageTree { get; } + + /// + /// Number of discovered pages. + /// + public int? NumberOfDiscoveredPages => pagesByNumber?.Count; /// /// Create a new . diff --git a/src/UglyToad.PdfPig/Content/IResourceStore.cs b/src/UglyToad.PdfPig/Content/IResourceStore.cs index 26984a6c..5cbfa8fc 100644 --- a/src/UglyToad.PdfPig/Content/IResourceStore.cs +++ b/src/UglyToad.PdfPig/Content/IResourceStore.cs @@ -6,7 +6,7 @@ internal interface IResourceStore { - void LoadResourceDictionary(DictionaryToken resourceDictionary); + void LoadResourceDictionary(DictionaryToken resourceDictionary, InternalParsingOptions parsingOptions); /// /// Remove any named resources and associated state for the last resource dictionary loaded. diff --git a/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs b/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs index 261e9b8f..38bd219e 100644 --- a/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs +++ b/src/UglyToad.PdfPig/Content/PageRotationDegrees.cs @@ -44,7 +44,7 @@ /// /// Create a . /// - /// Rotation in degrees clockwise. + /// Rotation in degrees clockwise, must be a multiple of 90. public PageRotationDegrees(int rotation) { if (rotation < 0) diff --git a/src/UglyToad.PdfPig/Content/Pages.cs b/src/UglyToad.PdfPig/Content/Pages.cs index 93f60603..3bd55c09 100644 --- a/src/UglyToad.PdfPig/Content/Pages.cs +++ b/src/UglyToad.PdfPig/Content/Pages.cs @@ -21,6 +21,13 @@ this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner)); Count = catalog.PagesDictionary.GetIntOrDefault(NameToken.Count); + var CountOfPagesByPagesTree = catalog.PageTree.Children.Count; + var numberOfDiscoveredPages = catalog.NumberOfDiscoveredPages; + if (numberOfDiscoveredPages is null == false && Count != numberOfDiscoveredPages) + { + //log.Warning($"Dictionary Page Count {Count} different to discovered pages {numberOfDiscoveredPages}. Using {numberOfDiscoveredPages}."); + Count = numberOfDiscoveredPages.Value; + } } public Page GetPage(int pageNumber, InternalParsingOptions parsingOptions) diff --git a/src/UglyToad.PdfPig/Content/ResourceStore.cs b/src/UglyToad.PdfPig/Content/ResourceStore.cs index de743aee..fa6e146e 100644 --- a/src/UglyToad.PdfPig/Content/ResourceStore.cs +++ b/src/UglyToad.PdfPig/Content/ResourceStore.cs @@ -33,7 +33,7 @@ this.fontFactory = fontFactory; } - public void LoadResourceDictionary(DictionaryToken resourceDictionary) + public void LoadResourceDictionary(DictionaryToken resourceDictionary, InternalParsingOptions parsingOptions) { lastLoadedFont = (null, null); @@ -43,7 +43,7 @@ { var fontDictionary = DirectObjectFinder.Get(fontBase, scanner); - LoadFontDictionary(fontDictionary); + LoadFontDictionary(fontDictionary, parsingOptions); } if (resourceDictionary.TryGet(NameToken.Xobject, out var xobjectBase)) @@ -132,7 +132,7 @@ currentResourceState.Pop(); } - private void LoadFontDictionary(DictionaryToken fontDictionary) + private void LoadFontDictionary(DictionaryToken fontDictionary, InternalParsingOptions parsingOptions) { lastLoadedFont = (null, null); @@ -157,7 +157,18 @@ continue; } - loadedFonts[reference] = fontFactory.Get(fontObject); + try + { + loadedFonts[reference] = fontFactory.Get(fontObject); + } + catch + { + if (!parsingOptions.SkipMissingFonts) + { + throw; + } + } + } else if (pair.Value is DictionaryToken fd) { diff --git a/src/UglyToad.PdfPig/Filters/FlateFilter.cs b/src/UglyToad.PdfPig/Filters/FlateFilter.cs index b67b2010..511080b3 100644 --- a/src/UglyToad.PdfPig/Filters/FlateFilter.cs +++ b/src/UglyToad.PdfPig/Filters/FlateFilter.cs @@ -1,5 +1,6 @@ namespace UglyToad.PdfPig.Filters { + using Fonts; using System; using System.Collections.Generic; using System.IO; @@ -79,10 +80,17 @@ memoryStream.ReadByte(); memoryStream.ReadByte(); - using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress)) + try { - deflate.CopyTo(output); - return output.ToArray(); + using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress)) + { + deflate.CopyTo(output); + return output.ToArray(); + } + } + catch (InvalidDataException ex) + { + throw new CorruptCompressedDataException("Invalid Flate compressed stream encountered", ex); } } } diff --git a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs index 8f6d5440..1e5efffd 100644 --- a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs @@ -479,7 +479,7 @@ var hasResources = formStream.StreamDictionary.TryGet(NameToken.Resources, pdfScanner, out var formResources); if (hasResources) { - resourceStore.LoadResourceDictionary(formResources); + resourceStore.LoadResourceDictionary(formResources, parsingOptions); } // 1. Save current state. diff --git a/src/UglyToad.PdfPig/Parser/CatalogFactory.cs b/src/UglyToad.PdfPig/Parser/CatalogFactory.cs index da2bb2cc..26095d8a 100644 --- a/src/UglyToad.PdfPig/Parser/CatalogFactory.cs +++ b/src/UglyToad.PdfPig/Parser/CatalogFactory.cs @@ -81,11 +81,13 @@ pageNumber.Increment(); return new PageTreeNode(nodeDictionaryInput, referenceInput, true, pageNumber.PageCount).WithChildren(EmptyArray.Instance); - } - - - - //If we got here, we have to iterate till we manage to exit + } + + + + //If we got here, we have to iterate till we manage to exit + + HashSet visitedTokens = new HashSet(); // As we visit each token add to this list (the hashcode of the indirect reference) var toProcess = new Queue<(PageTreeNode thisPage, IndirectReference reference, DictionaryToken nodeDictionary, IndirectReference parentReference, @@ -102,8 +104,16 @@ do { - var current = toProcess.Dequeue(); - + var current = toProcess.Dequeue(); + var currentReferenceHash = current.reference.GetHashCode(); + if (visitedTokens.Contains(currentReferenceHash)) + { + continue; // don't revisit token already processed. break infinite loop. Issue #512 + } + else + { + visitedTokens.Add(currentReferenceHash); + } if (!current.nodeDictionary.TryGet(NameToken.Kids, pdfTokenScanner, out ArrayToken kids)) { if (!isLenientParsing) diff --git a/src/UglyToad.PdfPig/Parser/PageFactory.cs b/src/UglyToad.PdfPig/Parser/PageFactory.cs index 61e54289..591500b2 100644 --- a/src/UglyToad.PdfPig/Parser/PageFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PageFactory.cs @@ -63,13 +63,13 @@ { var resource = pageTreeMembers.ParentResources.Dequeue(); - resourceStore.LoadResourceDictionary(resource); + resourceStore.LoadResourceDictionary(resource, parsingOptions); stackDepth++; } if (dictionary.TryGet(NameToken.Resources, pdfScanner, out DictionaryToken resources)) { - resourceStore.LoadResourceDictionary(resources); + resourceStore.LoadResourceDictionary(resources, parsingOptions); stackDepth++; } diff --git a/src/UglyToad.PdfPig/Parser/Parts/BruteForceSearcher.cs b/src/UglyToad.PdfPig/Parser/Parts/BruteForceSearcher.cs index 2dda4ecc..ae91268c 100644 --- a/src/UglyToad.PdfPig/Parser/Parts/BruteForceSearcher.cs +++ b/src/UglyToad.PdfPig/Parser/Parts/BruteForceSearcher.cs @@ -176,7 +176,7 @@ const string searchTerm = "%%EOF"; - var minimumEndOffset = bytes.Length - searchTerm.Length; + var minimumEndOffset = bytes.Length - searchTerm.Length + 1; // Issue #512 - Unable to open PDF - BruteForceScan starts from earlier of two EOF marker due to min end offset off by 1 bytes.Seek(minimumEndOffset); diff --git a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs index 56b102a3..a13ea14d 100644 --- a/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfDocumentBuilder.cs @@ -559,6 +559,11 @@ namespace UglyToad.PdfPig.Writer pageDictionary[NameToken.MediaBox] = RectangleToArray(page.Value.PageSize); } + if (page.Value.rotation.HasValue) + { + pageDictionary[NameToken.Rotate] = new NumericToken(page.Value.rotation.Value); + } + // Adobe Acrobat errors if content streams ref'd by multiple pages, turn off // dedup if on to avoid issues var prev = context.AttemptDeduplication; diff --git a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs index 872ccb4a..7c6f4cd3 100644 --- a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs @@ -46,6 +46,8 @@ private int imageKey = 1; + internal int? rotation; + internal IReadOnlyDictionary Resources => pageDictionary.GetOrCreateDict(NameToken.Resources); /// @@ -131,7 +133,7 @@ /// The first point on the line. /// The last point on the line. /// The width of the line in user space units. - public void DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1) + public PdfPageBuilder DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1) { if (lineWidth != 1) { @@ -146,6 +148,8 @@ { currentStream.Add(new SetLineWidth(1)); } + + return this; } /// @@ -156,7 +160,7 @@ /// The height of the rectangle. /// The width of the line border of the rectangle. /// Whether to fill with the color set by . - public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1, bool fill = false) + public PdfPageBuilder DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1, bool fill = false) { if (lineWidth != 1) { @@ -178,6 +182,17 @@ { currentStream.Add(new SetLineWidth(lineWidth)); } + + return this; + } + + /// + /// Set the number of degrees by which the page is rotated clockwise when displayed or printed. + /// + public PdfPageBuilder SetRotation(PageRotationDegrees degrees) + { + rotation = degrees.Value; + return this; } /// @@ -188,7 +203,7 @@ /// Position of the third corner of the triangle. /// The width of the line border of the triangle. /// Whether to fill with the color set by . - public void DrawTriangle(PdfPoint point1, PdfPoint point2, PdfPoint point3, decimal lineWidth = 1, bool fill = false) + public PdfPageBuilder DrawTriangle(PdfPoint point1, PdfPoint point2, PdfPoint point3, decimal lineWidth = 1, bool fill = false) { if (lineWidth != 1) { @@ -213,6 +228,8 @@ { currentStream.Add(new SetLineWidth(lineWidth)); } + + return this; } /// @@ -222,9 +239,11 @@ /// The diameter of the circle. /// The width of the line border of the circle. /// Whether to fill with the color set by . - public void DrawCircle(PdfPoint center, decimal diameter, decimal lineWidth = 1, bool fill = false) + public PdfPageBuilder DrawCircle(PdfPoint center, decimal diameter, decimal lineWidth = 1, bool fill = false) { DrawEllipsis(center, diameter, diameter, lineWidth, fill); + + return this; } /// @@ -235,7 +254,7 @@ /// The height of the ellipsis. /// The width of the line border of the ellipsis. /// Whether to fill with the color set by . - public void DrawEllipsis(PdfPoint center, decimal width, decimal height, decimal lineWidth = 1, bool fill = false) + public PdfPageBuilder DrawEllipsis(PdfPoint center, decimal width, decimal height, decimal lineWidth = 1, bool fill = false) { width /= 2; height /= 2; @@ -283,6 +302,8 @@ { currentStream.Add(new SetLineWidth(lineWidth)); } + + return this; } /// @@ -291,10 +312,12 @@ /// Red - 0 to 255 /// Green - 0 to 255 /// Blue - 0 to 255 - public void SetStrokeColor(byte r, byte g, byte b) + public PdfPageBuilder SetStrokeColor(byte r, byte g, byte b) { currentStream.Add(Push.Value); currentStream.Add(new SetStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b))); + + return this; } /// @@ -303,11 +326,13 @@ /// Red - 0 to 1 /// Green - 0 to 1 /// Blue - 0 to 1 - internal void SetStrokeColorExact(decimal r, decimal g, decimal b) + internal PdfPageBuilder SetStrokeColorExact(decimal r, decimal g, decimal b) { currentStream.Add(Push.Value); currentStream.Add(new SetStrokeColorDeviceRgb(CheckRgbDecimal(r, nameof(r)), CheckRgbDecimal(g, nameof(g)), CheckRgbDecimal(b, nameof(b)))); + + return this; } /// @@ -316,18 +341,22 @@ /// Red - 0 to 255 /// Green - 0 to 255 /// Blue - 0 to 255 - public void SetTextAndFillColor(byte r, byte g, byte b) + public PdfPageBuilder SetTextAndFillColor(byte r, byte g, byte b) { currentStream.Add(Push.Value); currentStream.Add(new SetNonStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b))); + + return this; } /// /// Restores the stroke, text and fill color to default (black). /// - public void ResetColor() + public PdfPageBuilder ResetColor() { currentStream.Add(Pop.Value); + + return this; } /// @@ -451,9 +480,11 @@ /// To insert invisible text, for example output of OCR, use TextRenderingMode.Neither. /// /// Text rendering mode to set. - public void SetTextRenderingMode(TextRenderingMode mode) + public PdfPageBuilder SetTextRenderingMode(TextRenderingMode mode) { currentStream.Add(new SetTextRenderingMode(mode)); + + return this; } private NameToken GetAddedFont(PdfDocumentBuilder.AddedFont font) @@ -690,7 +721,7 @@ /// Copy a page from unknown source to this page /// /// Page to be copied - public void CopyFrom(Page srcPage) + public PdfPageBuilder CopyFrom(Page srcPage) { if (currentStream.Operations.Count > 0) { @@ -704,7 +735,7 @@ // 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; + return this; } // TODO: How should we handle any other token in the page dictionary (Eg. LastModified, MediaBox, CropBox, BleedBox, TrimBox, ArtBox, @@ -828,6 +859,8 @@ } destinationStream.Operations.AddRange(operations); + + return this; } private List DrawLetters(NameToken name, string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix)