Compare commits

..

1 Commits

Author SHA1 Message Date
Eliot Jones
b5d90d1725 allow indirect references in xref streams as an attempt to fix #457 2022-05-29 16:26:56 -04:00
34 changed files with 148 additions and 658 deletions

View File

@@ -10,8 +10,7 @@
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))
@@ -87,7 +86,6 @@
}
Console.WriteLine(sb.ToString());
#endif
}
}
}

View File

@@ -45,14 +45,9 @@
},
{7,
("Advance text extraction using layout analysis algorithms",
() => 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")))
}
};
() => AdvancedTextExtraction.Run(Path.Combine(filesDirectory, "ICML03-081.pdf")))
}
};
var choices = string.Join(Environment.NewLine, examples.Select(x => $"{x.Key}: {x.Value.name}"));

View File

@@ -1,34 +0,0 @@
namespace UglyToad.PdfPig.Fonts
{
using System;
using System.Runtime.Serialization;
/// <summary>
/// Thrown when a PDF contains an invalid compressed data stream.
/// </summary>
[Serializable]
public class CorruptCompressedDataException : Exception
{
/// <inheritdoc />
public CorruptCompressedDataException()
{
}
/// <inheritdoc />
public CorruptCompressedDataException(string message) : base(message)
{
}
/// <inheritdoc />
public CorruptCompressedDataException(string message, Exception inner) : base(message, inner)
{
}
/// <inheritdoc />
protected CorruptCompressedDataException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -1,54 +0,0 @@
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);
}
}

View File

@@ -6,6 +6,7 @@
using Logging;
using PdfPig.Core;
using PdfPig.Graphics;
using PdfPig.Graphics.Core;
using PdfPig.Graphics.Operations.General;
using PdfPig.Graphics.Operations.SpecialGraphicsState;
using PdfPig.Graphics.Operations.TextObjects;
@@ -182,30 +183,6 @@ cm BT 0.0001 Tc 19 0 0 19 0 0 Tm /Tc1 1 Tf ( \(sleep 1; printf ""QUIT\\r\\n""\
Assert.Equal(@" (sleep 1; printf ""QUIT\r\n"") | ", text.Text);
}
[Fact]
public void HandlesWeirdNumber()
{
// Issue 453
const string s = @"/Alpha1
gs
0
0
0
rg
0.00-90
151555.0
m
302399.97
151555.0
l";
var input = StringBytesTestConverter.Convert(s, false);
var result = parser.Parse(1, input.Bytes, log);
Assert.Equal(4, result.Count);
}
private static string LineEndingsToWhiteSpace(string str)
{
return str.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ');

View File

@@ -51,7 +51,7 @@
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
Assert.Equal(1.2m, result.Version);
Assert.Equal(TestEnvironment.IsSingleByteNewLine(input) ? 7 : 9, result.OffsetInFile);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 7 : 9, result.OffsetInFile);
}
[Fact]
@@ -66,42 +66,38 @@
[Fact]
public void HeaderPrecededByJunkNonLenientDoesNotThrow()
{
var input = @"one
%PDF-1.2";
var scanner = StringBytesTestConverter.Scanner(input);
{
var scanner = StringBytesTestConverter.Scanner(@"one
%PDF-1.2");
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
Assert.Equal(1.2m, result.Version);
Assert.Equal(TestEnvironment.IsSingleByteNewLine(input) ? 12 : 13, result.OffsetInFile);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile);
}
[Fact]
public void HeaderPrecededByJunkLenientReads()
{
var input = @"one
%PDF-1.7";
var scanner = StringBytesTestConverter.Scanner(input);
{
var scanner = StringBytesTestConverter.Scanner(@"one
%PDF-1.7");
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log);
Assert.Equal(1.7m, result.Version);
Assert.Equal(TestEnvironment.IsSingleByteNewLine(input) ? 12 : 13, result.OffsetInFile);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile);
}
[Fact]
public void HeaderPrecededByJunkDoesNotThrow()
{
var s = @"one two
three %PDF-1.6";
var scanner = StringBytesTestConverter.Scanner(s);
{
var scanner = StringBytesTestConverter.Scanner(@"one two
three %PDF-1.6");
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log);
Assert.Equal(1.6m, result.Version);
Assert.Equal(TestEnvironment.IsSingleByteNewLine(s) ? 14 : 15, result.OffsetInFile);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 14 : 15, result.OffsetInFile);
}
[Fact]

View File

@@ -4,7 +4,6 @@
public static class TestEnvironment
{
public static bool IsSingleByteNewLine(string s) => s.IndexOf('\r') < 0;
public static readonly bool IsUnixPlatform = Environment.NewLine.Length == 1;
}
}

View File

@@ -155,12 +155,6 @@
default:
if (!decimal.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
{
if (TryParseInvalidNumber(str, out value))
{
token = new NumericToken(value);
return true;
}
return false;
}
@@ -177,34 +171,5 @@
return false;
}
}
private static bool TryParseInvalidNumber(string numeric, out decimal result)
{
result = 0;
if (!numeric.Contains("-") && !numeric.Contains("+"))
{
return false;
}
var parts = numeric.Split(new string[] { "+", "-" }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
{
return false;
}
foreach (var part in parts)
{
if (!decimal.TryParse(part, NumberStyles.Any, CultureInfo.InvariantCulture, out var partNumber))
{
return false;
}
result += partNumber;
}
return true;
}
}
}

View File

@@ -29,12 +29,7 @@
/// <summary>
/// The page tree for this document containing all pages, page numbers and their dictionaries.
/// </summary>
public PageTreeNode PageTree { get; }
/// <summary>
/// Number of discovered pages.
/// </summary>
public int? NumberOfDiscoveredPages => pagesByNumber?.Count;
public PageTreeNode PageTree { get; }
/// <summary>
/// Create a new <see cref="CatalogDictionary"/>.

View File

@@ -4,10 +4,6 @@
internal interface IPageFactory
{
Page Create(
int number,
DictionaryToken dictionary,
PageTreeMembers pageTreeMembers,
InternalParsingOptions parsingOptions);
Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, bool clipPaths);
}
}

View File

@@ -6,7 +6,7 @@
internal interface IResourceStore
{
void LoadResourceDictionary(DictionaryToken resourceDictionary, InternalParsingOptions parsingOptions);
void LoadResourceDictionary(DictionaryToken resourceDictionary);
/// <summary>
/// Remove any named resources and associated state for the last resource dictionary loaded.

View File

@@ -44,7 +44,7 @@
/// <summary>
/// Create a <see cref="PageRotationDegrees"/>.
/// </summary>
/// <param name="rotation">Rotation in degrees clockwise, must be a multiple of 90.</param>
/// <param name="rotation">Rotation in degrees clockwise.</param>
public PageRotationDegrees(int rotation)
{
if (rotation < 0)

View File

@@ -32,7 +32,7 @@
/// <summary>
/// The number of this page if <see cref="IsPage"/> is <see langword="true"/>.
/// </summary>
public int? PageNumber { get; internal set; }
public int? PageNumber { get; }
/// <summary>
/// The child nodes of this node if <see cref="IsPage"/> is <see langword="false" />

View File

@@ -21,21 +21,12 @@
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)
public Page GetPage(int pageNumber, bool clipPaths)
{
if (pageNumber <= 0 || pageNumber > Count)
{
parsingOptions.Logger.Error($"Page {pageNumber} requested but is out of range.");
throw new ArgumentOutOfRangeException(nameof(pageNumber),
$"Page number {pageNumber} invalid, must be between 1 and {Count}.");
}
@@ -72,11 +63,7 @@
}
}
var page = pageFactory.Create(
pageNumber,
pageNode.NodeDictionary,
pageTreeMembers,
parsingOptions);
var page = pageFactory.Create(pageNumber, pageNode.NodeDictionary, pageTreeMembers, clipPaths);
return page;
}

View File

@@ -33,7 +33,7 @@
this.fontFactory = fontFactory;
}
public void LoadResourceDictionary(DictionaryToken resourceDictionary, InternalParsingOptions parsingOptions)
public void LoadResourceDictionary(DictionaryToken resourceDictionary)
{
lastLoadedFont = (null, null);
@@ -43,7 +43,7 @@
{
var fontDictionary = DirectObjectFinder.Get<DictionaryToken>(fontBase, scanner);
LoadFontDictionary(fontDictionary, parsingOptions);
LoadFontDictionary(fontDictionary);
}
if (resourceDictionary.TryGet(NameToken.Xobject, out var xobjectBase))
@@ -52,11 +52,6 @@
foreach (var pair in xobjectDictionary.Data)
{
if (pair.Value is NullToken)
{
continue;
}
if (!(pair.Value is IndirectReferenceToken reference))
{
throw new InvalidOperationException($"Expected the XObject dictionary value for key /{pair.Key} to be an indirect reference, instead got: {pair.Value}.");
@@ -132,7 +127,7 @@
currentResourceState.Pop();
}
private void LoadFontDictionary(DictionaryToken fontDictionary, InternalParsingOptions parsingOptions)
private void LoadFontDictionary(DictionaryToken fontDictionary)
{
lastLoadedFont = (null, null);
@@ -157,18 +152,7 @@
continue;
}
try
{
loadedFonts[reference] = fontFactory.Get(fontObject);
}
catch
{
if (!parsingOptions.SkipMissingFonts)
{
throw;
}
}
loadedFonts[reference] = fontFactory.Get(fontObject);
}
else if (pair.Value is DictionaryToken fd)
{

View File

@@ -1,11 +1,11 @@
namespace UglyToad.PdfPig.Filters
{
using Core;
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
using Tokens;
using UglyToad.PdfPig.Util;
using Util;
/// <inheritdoc />
/// <summary>

View File

@@ -1,6 +1,5 @@
namespace UglyToad.PdfPig.Filters
{
using Fonts;
using System;
using System.Collections.Generic;
using System.IO;
@@ -80,17 +79,10 @@
memoryStream.ReadByte();
memoryStream.ReadByte();
try
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
{
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);
deflate.CopyTo(output);
return output.ToArray();
}
}
}

View File

@@ -5,6 +5,7 @@
using Core;
using Filters;
using Geometry;
using Logging;
using Operations;
using Parser;
using PdfFonts;
@@ -48,8 +49,9 @@
private readonly IPdfTokenScanner pdfScanner;
private readonly IPageContentParser pageContentParser;
private readonly ILookupFilterProvider filterProvider;
private readonly ILog log;
private readonly bool clipPaths;
private readonly PdfVector pageSize;
private readonly InternalParsingOptions parsingOptions;
private readonly MarkedContentStack markedContentStack = new MarkedContentStack();
private Stack<CurrentGraphicsState> graphicsStack = new Stack<CurrentGraphicsState>();
@@ -88,8 +90,9 @@
IPdfTokenScanner pdfScanner,
IPageContentParser pageContentParser,
ILookupFilterProvider filterProvider,
PdfVector pageSize,
InternalParsingOptions parsingOptions)
ILog log,
bool clipPaths,
PdfVector pageSize)
{
this.resourceStore = resourceStore;
this.userSpaceUnit = userSpaceUnit;
@@ -97,8 +100,9 @@
this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner));
this.pageContentParser = pageContentParser ?? throw new ArgumentNullException(nameof(pageContentParser));
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
this.log = log;
this.clipPaths = clipPaths;
this.pageSize = pageSize;
this.parsingOptions = parsingOptions;
// initiate CurrentClippingPath to cropBox
var clippingSubpath = new PdfSubpath();
@@ -226,15 +230,6 @@
if (font == null)
{
if (parsingOptions.SkipMissingFonts)
{
parsingOptions.Logger.Warn($"Skipping a missing font with name {currentState.FontState.FontName} " +
$"since it is not present in the document and {nameof(InternalParsingOptions.SkipMissingFonts)} " +
"is set to true. This may result in some text being skipped and not included in the output.");
return;
}
throw new InvalidOperationException($"Could not find the font with name {currentState.FontState.FontName} in the resource store. It has not been loaded yet.");
}
@@ -258,8 +253,7 @@
if (!foundUnicode || unicode == null)
{
parsingOptions.Logger.Warn($"We could not find the corresponding character with code {code} in font {font.Name}.");
log.Warn($"We could not find the corresponding character with code {code} in font {font.Name}.");
// Try casting directly to string as in PDFBox 1.8.
unicode = new string((char)code, 1);
}
@@ -479,7 +473,7 @@
var hasResources = formStream.StreamDictionary.TryGet<DictionaryToken>(NameToken.Resources, pdfScanner, out var formResources);
if (hasResources)
{
resourceStore.LoadResourceDictionary(formResources, parsingOptions);
resourceStore.LoadResourceDictionary(formResources);
}
// 1. Save current state.
@@ -500,7 +494,7 @@
var contentStream = formStream.Decode(filterProvider, pdfScanner);
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentStream), parsingOptions.Logger);
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentStream), log);
// 3. We don't respect clipping currently.
@@ -683,7 +677,7 @@
if (CurrentPath.IsClipping)
{
if (!parsingOptions.ClipPaths)
if (!clipPaths)
{
// if we don't clip paths, add clipping path to paths
paths.Add(CurrentPath);
@@ -723,9 +717,9 @@
CurrentPath.FillColor = currentState.CurrentNonStrokingColor;
}
if (parsingOptions.ClipPaths)
if (clipPaths)
{
var clippedPath = currentState.CurrentClippingPath.Clip(CurrentPath, parsingOptions.Logger);
var clippedPath = currentState.CurrentClippingPath.Clip(CurrentPath, log);
if (clippedPath != null)
{
paths.Add(clippedPath);
@@ -751,15 +745,15 @@
AddCurrentSubpath();
CurrentPath.SetClipping(clippingRule);
if (parsingOptions.ClipPaths)
if (clipPaths)
{
var currentClipping = GetCurrentState().CurrentClippingPath;
currentClipping.SetClipping(clippingRule);
var newClippings = CurrentPath.Clip(currentClipping, parsingOptions.Logger);
var newClippings = CurrentPath.Clip(currentClipping, log);
if (newClippings == null)
{
parsingOptions.Logger.Warn("Empty clipping path found. Clipping path not updated.");
log.Warn("Empty clipping path found. Clipping path not updated.");
}
else
{
@@ -802,7 +796,7 @@
{
if (inlineImageBuilder != null)
{
parsingOptions.Logger.Error("Begin inline image (BI) command encountered while another inline image was active.");
log?.Error("Begin inline image (BI) command encountered while another inline image was active.");
}
inlineImageBuilder = new InlineImageBuilder();
@@ -812,7 +806,7 @@
{
if (inlineImageBuilder == null)
{
parsingOptions.Logger.Error("Begin inline image data (ID) command encountered without a corresponding begin inline image (BI) command.");
log?.Error("Begin inline image data (ID) command encountered without a corresponding begin inline image (BI) command.");
return;
}
@@ -823,7 +817,7 @@
{
if (inlineImageBuilder == null)
{
parsingOptions.Logger.Error("End inline image (EI) command encountered without a corresponding begin inline image (BI) command.");
log?.Error("End inline image (EI) command encountered without a corresponding begin inline image (BI) command.");
return;
}

View File

@@ -1,35 +0,0 @@
namespace UglyToad.PdfPig
{
using Logging;
using System.Collections.Generic;
/// <summary>
/// <see cref="ParsingOptions"/> but without being a public API/
/// </summary>
internal class InternalParsingOptions
{
public IReadOnlyList<string> Passwords { get; }
public bool UseLenientParsing { get; }
public bool ClipPaths { get; }
public bool SkipMissingFonts { get; }
public ILog Logger { get; }
public InternalParsingOptions(
IReadOnlyList<string> passwords,
bool useLenientParsing,
bool clipPaths,
bool skipMissingFonts,
ILog logger)
{
Passwords = passwords;
UseLenientParsing = useLenientParsing;
ClipPaths = clipPaths;
SkipMissingFonts = skipMissingFonts;
Logger = logger;
}
}
}

View File

@@ -5,10 +5,8 @@
using Content;
using Core;
using Parts;
using System.Linq;
using Tokenization.Scanner;
using Tokens;
using Util;
internal static class CatalogFactory
{
@@ -81,13 +79,9 @@
pageNumber.Increment();
return new PageTreeNode(nodeDictionaryInput, referenceInput, true, pageNumber.PageCount).WithChildren(EmptyArray<PageTreeNode>.Instance);
}
//If we got here, we have to iterate till we manage to exit
HashSet<int> visitedTokens = new HashSet<int>(); // As we visit each token add to this list (the hashcode of the indirect reference)
}
//If we got here, we have to iterate till we manage to exit
var toProcess =
new Queue<(PageTreeNode thisPage, IndirectReference reference, DictionaryToken nodeDictionary, IndirectReference parentReference,
@@ -104,16 +98,8 @@
do
{
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);
}
var current = toProcess.Dequeue();
if (!current.nodeDictionary.TryGet(NameToken.Kids, pdfTokenScanner, out ArrayToken kids))
{
if (!isLenientParsing)
@@ -140,6 +126,8 @@
if (isChildPage)
{
pageNumber.Increment();
var kidPageNode =
new PageTreeNode(kidDictionaryToken, kidRef.Data, true, pageNumber.PageCount).WithChildren(EmptyArray<PageTreeNode>.Instance);
current.nodeChildren.Add(kidPageNode);
@@ -164,12 +152,6 @@
action();
}
foreach (var child in firstPage.Children.ToRecursiveOrderList(x=>x.Children).Where(child => child.IsPage))
{
pageNumber.Increment();
child.PageNumber = pageNumber.PageCount;
}
return firstPage;
}

View File

@@ -269,7 +269,7 @@
return false;
}
xrefTablePart = crossReferenceStreamParser.Parse(objByteOffset, fromTableAtOffset, objectStream);
xrefTablePart = crossReferenceStreamParser.Parse(objByteOffset, fromTableAtOffset, objectStream, pdfScanner);
return true;
}

View File

@@ -94,7 +94,7 @@
throw new InvalidOperationException($"Failed to read expected buffer length {gap} on page {pageNumber} " +
$"when reading inline image at offset in content: {lastEndImageOffset.Value}.");
}
// Replace the last end image operator with one containing the full set of data.
graphicsStateOperations.Remove(lastEndImage);
graphicsStateOperations.Add(new EndInlineImage(lastEndImage.ImageData.Concat(missingData).ToArray()));
@@ -142,13 +142,6 @@
graphicsStateOperations.Add(newEndInlineImage);
lastEndImageOffset = scanner.CurrentPosition - 3;
}
else if (op.Data == "inf")
{
// Value representing infinity in broken file from #467.
// Treat as zero.
precedingTokens.Add(NumericToken.Zero);
continue;
}
else
{
log.Warn($"Operator which was not understood encountered. Values was {op.Data}. Ignoring.");

View File

@@ -21,20 +21,20 @@
private readonly IResourceStore resourceStore;
private readonly ILookupFilterProvider filterProvider;
private readonly IPageContentParser pageContentParser;
private readonly ILog log;
public PageFactory(
IPdfTokenScanner pdfScanner,
IResourceStore resourceStore,
ILookupFilterProvider filterProvider,
IPageContentParser pageContentParser)
public PageFactory(IPdfTokenScanner pdfScanner, IResourceStore resourceStore, ILookupFilterProvider filterProvider,
IPageContentParser pageContentParser,
ILog log)
{
this.resourceStore = resourceStore;
this.filterProvider = filterProvider;
this.pageContentParser = pageContentParser;
this.log = log;
this.pdfScanner = pdfScanner;
}
public Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, InternalParsingOptions parsingOptions)
public Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, bool clipPaths)
{
if (dictionary == null)
{
@@ -45,11 +45,11 @@
if (type != null && !type.Equals(NameToken.Page))
{
parsingOptions.Logger.Error($"Page {number} had its type specified as {type} rather than 'Page'.");
log?.Error($"Page {number} had its type specified as {type} rather than 'Page'.");
}
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, parsingOptions.Logger);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox, parsingOptions.Logger);
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
var rotation = new PageRotationDegrees(pageTreeMembers.Rotation);
if (dictionary.TryGet(NameToken.Rotate, pdfScanner, out NumericToken rotateToken))
@@ -63,13 +63,13 @@
{
var resource = pageTreeMembers.ParentResources.Dequeue();
resourceStore.LoadResourceDictionary(resource, parsingOptions);
resourceStore.LoadResourceDictionary(resource);
stackDepth++;
}
if (dictionary.TryGet(NameToken.Resources, pdfScanner, out DictionaryToken resources))
{
resourceStore.LoadResourceDictionary(resources, parsingOptions);
resourceStore.LoadResourceDictionary(resources);
stackDepth++;
}
@@ -130,7 +130,7 @@
}
}
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
}
else
{
@@ -143,7 +143,7 @@
var bytes = contentStream.Decode(filterProvider, pdfScanner);
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
}
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
@@ -158,28 +158,18 @@
return page;
}
private PageContent GetContent(
int pageNumber,
IReadOnlyList<byte> contentBytes,
CropBox cropBox,
UserSpaceUnit userSpaceUnit,
PageRotationDegrees rotation,
MediaBox mediaBox,
InternalParsingOptions parsingOptions)
private PageContent GetContent(int pageNumber, IReadOnlyList<byte> contentBytes, CropBox cropBox, UserSpaceUnit userSpaceUnit,
PageRotationDegrees rotation, bool clipPaths, MediaBox mediaBox)
{
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentBytes),
parsingOptions.Logger);
log);
var context = new ContentStreamProcessor(
cropBox.Bounds,
resourceStore,
userSpaceUnit,
rotation,
pdfScanner,
var context = new ContentStreamProcessor(cropBox.Bounds, resourceStore, userSpaceUnit, rotation, pdfScanner,
pageContentParser,
filterProvider,
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height),
parsingOptions);
log,
clipPaths,
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height));
return context.Process(pageNumber, operations);
}
@@ -195,11 +185,7 @@
return spaceUnits;
}
private CropBox GetCropBox(
DictionaryToken dictionary,
PageTreeMembers pageTreeMembers,
MediaBox mediaBox,
ILog log)
private CropBox GetCropBox(DictionaryToken dictionary, PageTreeMembers pageTreeMembers, MediaBox mediaBox)
{
CropBox cropBox;
if (dictionary.TryGet(NameToken.CropBox, out var cropBoxObject) &&
@@ -224,11 +210,7 @@
return cropBox;
}
private MediaBox GetMediaBox(
int number,
DictionaryToken dictionary,
PageTreeMembers pageTreeMembers,
ILog log)
private MediaBox GetMediaBox(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers)
{
MediaBox mediaBox;
if (dictionary.TryGet(NameToken.MediaBox, out var mediaboxObject)

View File

@@ -176,7 +176,7 @@
const string searchTerm = "%%EOF";
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
var minimumEndOffset = bytes.Length - searchTerm.Length;
bytes.Seek(minimumEndOffset);

View File

@@ -4,6 +4,7 @@
using Core;
using Filters;
using PdfPig.CrossReference;
using Tokenization.Scanner;
using Tokens;
using Util;
@@ -19,9 +20,9 @@
/// <summary>
/// Parses through the unfiltered stream and populates the xrefTable HashMap.
/// </summary>
public CrossReferenceTablePart Parse(long streamOffset, long? fromTableAtOffset, StreamToken stream)
public CrossReferenceTablePart Parse(long streamOffset, long? fromTableAtOffset, StreamToken stream, IPdfTokenScanner pdfTokenScanner)
{
var decoded = stream.Decode(filterProvider);
var decoded = stream.DecodeWithLookup(filterProvider, pdfTokenScanner);
var fieldSizes = new CrossReferenceStreamFieldSize(stream.StreamDictionary);

View File

@@ -45,25 +45,9 @@
internal static PdfDocument Open(Stream stream, ParsingOptions options)
{
var initialPosition = stream.Position;
var streamInput = new StreamInputBytes(stream, false);
try
{
return Open(streamInput, options);
}
catch (Exception ex)
{
if (initialPosition != 0)
{
throw new InvalidOperationException(
"Could not parse document due to an error, the input stream was not at position zero when provided to the Open method.",
ex);
}
throw;
}
return Open(streamInput, options);
}
private static PdfDocument Open(IInputBytes inputBytes, ParsingOptions options = null)
@@ -91,28 +75,19 @@
passwords.Add(string.Empty);
}
var finalOptions = new InternalParsingOptions(
passwords,
isLenientParsing,
clipPaths,
options?.SkipMissingFonts ?? false,
options?.Logger ?? new NoOpLog());
var document = OpenDocument(inputBytes, tokenScanner, finalOptions);
var document = OpenDocument(inputBytes, tokenScanner, options?.Logger ?? new NoOpLog(), isLenientParsing, passwords, clipPaths);
return document;
}
private static PdfDocument OpenDocument(
IInputBytes inputBytes,
ISeekableTokenScanner scanner,
InternalParsingOptions parsingOptions)
private static PdfDocument OpenDocument(IInputBytes inputBytes, ISeekableTokenScanner scanner, ILog log, bool isLenientParsing,
IReadOnlyList<string> passwords, bool clipPaths)
{
var filterProvider = new FilterProviderWithLookup(DefaultFilterProvider.Instance);
CrossReferenceTable crossReferenceTable = null;
var xrefValidator = new XrefOffsetValidator(parsingOptions.Logger);
var xrefValidator = new XrefOffsetValidator(log);
// We're ok with this since our intent is to lazily load the cross reference table.
// ReSharper disable once AccessToModifiedClosure
@@ -120,39 +95,30 @@
var pdfScanner = new PdfTokenScanner(inputBytes, locationProvider, filterProvider, NoOpEncryptionHandler.Instance);
var crossReferenceStreamParser = new CrossReferenceStreamParser(filterProvider);
var crossReferenceParser = new CrossReferenceParser(parsingOptions.Logger, xrefValidator, crossReferenceStreamParser);
var crossReferenceParser = new CrossReferenceParser(log, xrefValidator, crossReferenceStreamParser);
var version = FileHeaderParser.Parse(scanner, inputBytes, parsingOptions.UseLenientParsing, parsingOptions.Logger);
var version = FileHeaderParser.Parse(scanner, inputBytes, isLenientParsing, log);
var crossReferenceOffset = FileTrailerParser.GetFirstCrossReferenceOffset(
inputBytes,
scanner,
parsingOptions.UseLenientParsing) + version.OffsetInFile;
var crossReferenceOffset = FileTrailerParser.GetFirstCrossReferenceOffset(inputBytes, scanner,
isLenientParsing) + version.OffsetInFile;
// TODO: make this use the scanner.
var validator = new CrossReferenceOffsetValidator(xrefValidator);
crossReferenceOffset = validator.Validate(crossReferenceOffset, scanner, inputBytes, parsingOptions.UseLenientParsing);
crossReferenceOffset = validator.Validate(crossReferenceOffset, scanner, inputBytes, isLenientParsing);
crossReferenceTable = crossReferenceParser.Parse(
inputBytes,
parsingOptions.UseLenientParsing,
crossReferenceTable = crossReferenceParser.Parse(inputBytes, isLenientParsing,
crossReferenceOffset,
version.OffsetInFile,
pdfScanner,
scanner);
var (rootReference, rootDictionary) = ParseTrailer(
crossReferenceTable,
parsingOptions.UseLenientParsing,
var (rootReference, rootDictionary) = ParseTrailer(crossReferenceTable, isLenientParsing,
pdfScanner,
out var encryptionDictionary);
var encryptionHandler = encryptionDictionary != null ?
(IEncryptionHandler)new EncryptionHandler(
encryptionDictionary,
crossReferenceTable.Trailer,
parsingOptions.Passwords)
(IEncryptionHandler)new EncryptionHandler(encryptionDictionary, crossReferenceTable.Trailer, passwords)
: NoOpEncryptionHandler.Instance;
pdfScanner.UpdateEncryptionHandler(encryptionHandler);
@@ -162,45 +128,35 @@
var type1Handler = new Type1FontHandler(pdfScanner, filterProvider, encodingReader);
var fontFactory = new FontFactory(parsingOptions.Logger, new Type0FontHandler(cidFontFactory,
var fontFactory = new FontFactory(log, new Type0FontHandler(cidFontFactory,
filterProvider, pdfScanner),
new TrueTypeFontHandler(parsingOptions.Logger, pdfScanner, filterProvider, encodingReader, SystemFontFinder.Instance,
new TrueTypeFontHandler(log, pdfScanner, filterProvider, encodingReader, SystemFontFinder.Instance,
type1Handler),
type1Handler,
new Type3FontHandler(pdfScanner, filterProvider, encodingReader));
var resourceContainer = new ResourceStore(pdfScanner, fontFactory);
var information = DocumentInformationFactory.Create(
pdfScanner,
crossReferenceTable.Trailer,
parsingOptions.UseLenientParsing);
var information = DocumentInformationFactory.Create(pdfScanner, crossReferenceTable.Trailer, isLenientParsing);
var catalog = CatalogFactory.Create(
rootReference,
rootDictionary,
pdfScanner,
parsingOptions.UseLenientParsing);
var catalog = CatalogFactory.Create(rootReference, rootDictionary, pdfScanner, isLenientParsing);
var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider,
new PageContentParser(new ReflectionGraphicsStateOperationFactory()));
new PageContentParser(new ReflectionGraphicsStateOperationFactory()),
log);
var caching = new ParsingCachingProviders(resourceContainer);
var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider, crossReferenceTable);
var bookmarksProvider = new BookmarksProvider(parsingOptions.Logger, pdfScanner);
var bookmarksProvider = new BookmarksProvider(log, pdfScanner);
return new PdfDocument(
inputBytes,
version,
crossReferenceTable,
pageFactory,
catalog,
information,
return new PdfDocument(log, inputBytes, version, crossReferenceTable, caching, pageFactory, catalog, information,
encryptionDictionary,
pdfScanner,
filterProvider,
acroFormFactory,
bookmarksProvider,
parsingOptions);
clipPaths);
}
private static (IndirectReference, DictionaryToken) ParseTrailer(CrossReferenceTable crossReferenceTable, bool isLenientParsing, IPdfTokenScanner pdfTokenScanner,

View File

@@ -48,11 +48,5 @@
/// All passwords to try when opening this document, will include any values set for <see cref="Password"/>.
/// </summary>
public List<string> Passwords { get; set; } = new List<string>();
/// <summary>
/// Skip extracting content where the font could not be found, will result in some letters being skipped/missed
/// but will prevent the library throwing where the source PDF has some corrupted text.
/// </summary>
public bool SkipMissingFonts { get; set; } = false;
}
}

View File

@@ -10,6 +10,7 @@
using Encryption;
using Exceptions;
using Filters;
using Logging;
using Parser;
using Tokenization.Scanner;
using Tokens;
@@ -27,9 +28,16 @@
[NotNull]
private readonly HeaderVersion version;
private readonly ILog log;
private readonly IInputBytes inputBytes;
private readonly bool clipPaths;
[NotNull]
private readonly ParsingCachingProviders cachingProviders;
[CanBeNull]
private readonly EncryptionDictionary encryptionDictionary;
@@ -38,7 +46,6 @@
private readonly ILookupFilterProvider filterProvider;
private readonly BookmarksProvider bookmarksProvider;
private readonly InternalParsingOptions parsingOptions;
[NotNull]
private readonly Pages pages;
@@ -75,10 +82,11 @@
/// </summary>
public bool IsEncrypted => encryptionDictionary != null;
internal PdfDocument(
internal PdfDocument(ILog log,
IInputBytes inputBytes,
HeaderVersion version,
CrossReferenceTable crossReferenceTable,
ParsingCachingProviders cachingProviders,
IPageFactory pageFactory,
Catalog catalog,
DocumentInformation information,
@@ -87,16 +95,17 @@
ILookupFilterProvider filterProvider,
AcroFormFactory acroFormFactory,
BookmarksProvider bookmarksProvider,
InternalParsingOptions parsingOptions)
bool clipPaths)
{
this.log = log;
this.inputBytes = inputBytes;
this.version = version ?? throw new ArgumentNullException(nameof(version));
this.cachingProviders = cachingProviders ?? throw new ArgumentNullException(nameof(cachingProviders));
this.encryptionDictionary = encryptionDictionary;
this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner));
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
this.bookmarksProvider = bookmarksProvider ?? throw new ArgumentNullException(nameof(bookmarksProvider));
this.parsingOptions = parsingOptions;
this.clipPaths = clipPaths;
Information = information ?? throw new ArgumentNullException(nameof(information));
pages = new Pages(catalog, pageFactory, pdfScanner);
Structure = new Structure(catalog, crossReferenceTable, pdfScanner);
@@ -144,11 +153,11 @@
throw new ObjectDisposedException("Cannot access page after the document is disposed.");
}
parsingOptions.Logger.Debug($"Accessing page {pageNumber}.");
log.Debug($"Accessing page {pageNumber}.");
try
{
return pages.GetPage(pageNumber, parsingOptions);
return pages.GetPage(pageNumber, clipPaths);
}
catch (Exception ex)
{
@@ -249,7 +258,7 @@
}
catch (Exception ex)
{
parsingOptions.Logger.Error("Failed disposing the PdfDocument due to an error.", ex);
log.Error("Failed disposing the PdfDocument due to an error.", ex);
}
finally
{

View File

@@ -45,14 +45,18 @@
}
return typedToken;
}
}
/// <summary>
/// Get the decoded data from this stream.
/// </summary>
public static IReadOnlyList<byte> Decode(this StreamToken stream, IFilterProvider filterProvider)
=> DecodeWithLookup(stream, filterProvider, null);
internal static IReadOnlyList<byte> DecodeWithLookup(this StreamToken stream, IFilterProvider filterProvider, IPdfTokenScanner pdfTokenScanner)
{
var filters = filterProvider.GetFilters(stream.StreamDictionary);
var filters = pdfTokenScanner != null
? new FilterProviderWithLookup(filterProvider).GetFilters(stream.StreamDictionary, pdfTokenScanner)
: filterProvider.GetFilters(stream.StreamDictionary);
var transform = stream.Data;
for (var i = 0; i < filters.Count; i++)

View File

@@ -1,44 +0,0 @@
namespace UglyToad.PdfPig.Util
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
internal static class EnumerableExtensions
{
public static List<T> ToRecursiveOrderList<T>(this IEnumerable<T> collection,
Expression<Func<T, IEnumerable<T>>> childCollection)
{
var resultList = new List<T>();
var currentItems = new Queue<(int Index, T Item, int Depth)>(collection.Select(i => (0, i, 0)));
var depthItemCounter = 0;
var previousItemDepth = 0;
var childProperty = (PropertyInfo)((MemberExpression)childCollection.Body).Member;
while (currentItems.Count > 0)
{
var currentItem = currentItems.Dequeue();
// Reset counter for number of items at this depth when the depth changes.
if (currentItem.Depth != previousItemDepth)
{
depthItemCounter = 0;
}
var resultIndex = currentItem.Index + depthItemCounter++;
resultList.Insert(resultIndex, currentItem.Item);
var childItems = childProperty.GetValue(currentItem.Item) as IEnumerable<T> ?? Enumerable.Empty<T>();
foreach (var childItem in childItems)
{
currentItems.Enqueue((resultIndex + 1, childItem, currentItem.Depth + 1));
}
previousItemDepth = currentItem.Depth;
}
return resultList;
}
}
}

View File

@@ -3,6 +3,7 @@ namespace UglyToad.PdfPig.Writer
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Content;
@@ -558,11 +559,6 @@ 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;

View File

@@ -46,8 +46,6 @@
private int imageKey = 1;
internal int? rotation;
internal IReadOnlyDictionary<string, IToken> Resources => pageDictionary.GetOrCreateDict(NameToken.Resources);
/// <summary>
@@ -133,7 +131,7 @@
/// <param name="from">The first point on the line.</param>
/// <param name="to">The last point on the line.</param>
/// <param name="lineWidth">The width of the line in user space units.</param>
public PdfPageBuilder DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1)
public void DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1)
{
if (lineWidth != 1)
{
@@ -148,8 +146,6 @@
{
currentStream.Add(new SetLineWidth(1));
}
return this;
}
/// <summary>
@@ -160,7 +156,7 @@
/// <param name="height">The height of the rectangle.</param>
/// <param name="lineWidth">The width of the line border of the rectangle.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
public PdfPageBuilder DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1, bool fill = false)
public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1, bool fill = false)
{
if (lineWidth != 1)
{
@@ -182,128 +178,6 @@
{
currentStream.Add(new SetLineWidth(lineWidth));
}
return this;
}
/// <summary>
/// Set the number of degrees by which the page is rotated clockwise when displayed or printed.
/// </summary>
public PdfPageBuilder SetRotation(PageRotationDegrees degrees)
{
rotation = degrees.Value;
return this;
}
/// <summary>
/// Draws a triangle on the current page with the specified points and line width.
/// </summary>
/// <param name="point1">Position of the first corner of the triangle.</param>
/// <param name="point2">Position of the second corner of the triangle.</param>
/// <param name="point3">Position of the third corner of the triangle.</param>
/// <param name="lineWidth">The width of the line border of the triangle.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
public PdfPageBuilder DrawTriangle(PdfPoint point1, PdfPoint point2, PdfPoint point3, decimal lineWidth = 1, bool fill = false)
{
if (lineWidth != 1)
{
currentStream.Add(new SetLineWidth(lineWidth));
}
currentStream.Add(new BeginNewSubpath((decimal)point1.X, (decimal)point1.Y));
currentStream.Add(new AppendStraightLineSegment((decimal)point2.X, (decimal)point2.Y));
currentStream.Add(new AppendStraightLineSegment((decimal)point3.X, (decimal)point3.Y));
currentStream.Add(new AppendStraightLineSegment((decimal)point1.X, (decimal)point1.Y));
if (fill)
{
currentStream.Add(FillPathEvenOddRuleAndStroke.Value);
}
else
{
currentStream.Add(StrokePath.Value);
}
if (lineWidth != 1)
{
currentStream.Add(new SetLineWidth(lineWidth));
}
return this;
}
/// <summary>
/// Draws a circle on the current page centering at the specified point with the given diameter and line width.
/// </summary>
/// <param name="center">The center position of the circle.</param>
/// <param name="diameter">The diameter of the circle.</param>
/// <param name="lineWidth">The width of the line border of the circle.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
public PdfPageBuilder DrawCircle(PdfPoint center, decimal diameter, decimal lineWidth = 1, bool fill = false)
{
DrawEllipsis(center, diameter, diameter, lineWidth, fill);
return this;
}
/// <summary>
/// Draws an ellipsis on the current page centering at the specified point with the given width, height and line width.
/// </summary>
/// <param name="center">The center position of the ellipsis.</param>
/// <param name="width">The width of the ellipsis.</param>
/// <param name="height">The height of the ellipsis.</param>
/// <param name="lineWidth">The width of the line border of the ellipsis.</param>
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
public PdfPageBuilder DrawEllipsis(PdfPoint center, decimal width, decimal height, decimal lineWidth = 1, bool fill = false)
{
width /= 2;
height /= 2;
// See here: https://spencermortensen.com/articles/bezier-circle/
decimal cc = 0.55228474983079m;
if (lineWidth != 1)
{
currentStream.Add(new SetLineWidth(lineWidth));
}
currentStream.Add(new BeginNewSubpath((decimal)center.X - width, (decimal)center.Y));
currentStream.Add(new AppendDualControlPointBezierCurve(
(decimal)center.X - width, (decimal)center.Y + height * cc,
(decimal)center.X - width * cc, (decimal)center.Y + height,
(decimal)center.X, (decimal)center.Y + height
));
currentStream.Add(new AppendDualControlPointBezierCurve(
(decimal)center.X + width * cc, (decimal)center.Y + height,
(decimal)center.X + width, (decimal)center.Y + height * cc,
(decimal)center.X + width, (decimal)center.Y
));
currentStream.Add(new AppendDualControlPointBezierCurve(
(decimal)center.X + width, (decimal)center.Y - height * cc,
(decimal)center.X + width * cc, (decimal)center.Y - height,
(decimal)center.X, (decimal)center.Y - height
));
currentStream.Add(new AppendDualControlPointBezierCurve(
(decimal)center.X - width * cc, (decimal)center.Y - height,
(decimal)center.X - width, (decimal)center.Y - height * cc,
(decimal)center.X - width, (decimal)center.Y
));
if (fill)
{
currentStream.Add(FillPathEvenOddRuleAndStroke.Value);
}
else
{
currentStream.Add(StrokePath.Value);
}
if (lineWidth != 1)
{
currentStream.Add(new SetLineWidth(lineWidth));
}
return this;
}
/// <summary>
@@ -312,12 +186,10 @@
/// <param name="r">Red - 0 to 255</param>
/// <param name="g">Green - 0 to 255</param>
/// <param name="b">Blue - 0 to 255</param>
public PdfPageBuilder SetStrokeColor(byte r, byte g, byte b)
public void SetStrokeColor(byte r, byte g, byte b)
{
currentStream.Add(Push.Value);
currentStream.Add(new SetStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b)));
return this;
}
/// <summary>
@@ -326,13 +198,11 @@
/// <param name="r">Red - 0 to 1</param>
/// <param name="g">Green - 0 to 1</param>
/// <param name="b">Blue - 0 to 1</param>
internal PdfPageBuilder SetStrokeColorExact(decimal r, decimal g, decimal b)
internal void 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;
}
/// <summary>
@@ -341,22 +211,18 @@
/// <param name="r">Red - 0 to 255</param>
/// <param name="g">Green - 0 to 255</param>
/// <param name="b">Blue - 0 to 255</param>
public PdfPageBuilder SetTextAndFillColor(byte r, byte g, byte b)
public void SetTextAndFillColor(byte r, byte g, byte b)
{
currentStream.Add(Push.Value);
currentStream.Add(new SetNonStrokeColorDeviceRgb(RgbToDecimal(r), RgbToDecimal(g), RgbToDecimal(b)));
return this;
}
/// <summary>
/// Restores the stroke, text and fill color to default (black).
/// </summary>
public PdfPageBuilder ResetColor()
public void ResetColor()
{
currentStream.Add(Pop.Value);
return this;
}
/// <summary>
@@ -480,11 +346,9 @@
/// To insert invisible text, for example output of OCR, use <c>TextRenderingMode.Neither</c>.
/// </summary>
/// <param name="mode">Text rendering mode to set.</param>
public PdfPageBuilder SetTextRenderingMode(TextRenderingMode mode)
public void SetTextRenderingMode(TextRenderingMode mode)
{
currentStream.Add(new SetTextRenderingMode(mode));
return this;
}
private NameToken GetAddedFont(PdfDocumentBuilder.AddedFont font)
@@ -721,7 +585,7 @@
/// Copy a page from unknown source to this page
/// </summary>
/// <param name="srcPage">Page to be copied</param>
public PdfPageBuilder CopyFrom(Page srcPage)
public void CopyFrom(Page srcPage)
{
if (currentStream.Operations.Count > 0)
{
@@ -735,7 +599,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 this;
return;
}
// TODO: How should we handle any other token in the page dictionary (Eg. LastModified, MediaBox, CropBox, BleedBox, TrimBox, ArtBox,
@@ -859,8 +723,6 @@
}
destinationStream.Operations.AddRange(operations);
return this;
}
private List<Letter> DrawLetters(NameToken name, string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix)