mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8874c5984 | ||
|
|
2aed996319 | ||
|
|
060c7bc728 | ||
|
|
6764d81958 | ||
|
|
95df15996b | ||
|
|
998e768bba | ||
|
|
f5fe39b285 | ||
|
|
9ef07b0176 | ||
|
|
29adece983 | ||
|
|
a7f64ec64b | ||
|
|
afe473e10e | ||
|
|
9c9c7c99ea | ||
|
|
e2246a88bb | ||
|
|
c643facee0 | ||
|
|
2f9a9ace9a | ||
|
|
19962af011 | ||
|
|
545d1a0793 | ||
|
|
3cd81297c1 | ||
|
|
fc71a91dfa | ||
|
|
f2188729a3 | ||
|
|
ae83861c5f | ||
|
|
21dfa3e985 | ||
|
|
559f3af5f3 | ||
|
|
5eed9fd1bc | ||
|
|
ddab53e456 | ||
|
|
4e490d63be | ||
|
|
03692cf42f |
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"));
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
{
|
||||
return new PdfPoint(X + dx, Y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PdfPoint"/> which is the current point moved in the y direction relative to its current position by a value.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
34
src/UglyToad.PdfPig.Fonts/CorruptCompressedDataException.cs
Normal file
34
src/UglyToad.PdfPig.Fonts/CorruptCompressedDataException.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -528,6 +528,52 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static IEnumerable<object[]> Issue458Data => new[]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
new PdfPoint[]
|
||||
{
|
||||
new PdfPoint(134.74199999999985, 1611.657),
|
||||
new PdfPoint(277.58043749999985, 1611.657),
|
||||
new PdfPoint(314.74199999999985, 1611.657),
|
||||
new PdfPoint(507.0248828124999, 1611.657),
|
||||
new PdfPoint(545.1419999999998, 1611.657),
|
||||
new PdfPoint(632.9658281249997, 1611.657),
|
||||
new PdfPoint(668.5709999999998, 1611.657),
|
||||
new PdfPoint(831.3395078125002, 1611.657),
|
||||
new PdfPoint(868.1139999999998, 1611.657),
|
||||
new PdfPoint(892.8907187499999, 1611.657),
|
||||
new PdfPoint(1010.0569999999999, 1611.6569999999997),
|
||||
new PdfPoint(1046.2174003906248, 1611.7654812011715),
|
||||
new PdfPoint(1010.0569999999999, 1611.657),
|
||||
new PdfPoint(1046.2174003906248, 1611.7654812011717),
|
||||
new PdfPoint(1085.145, 1611.8822639999996),
|
||||
new PdfPoint(1255.484941406251, 1612.3932838242179),
|
||||
new PdfPoint(1301.144, 1612.5302609999994),
|
||||
new PdfPoint(1359.0006406250002, 1612.7038309218747),
|
||||
new PdfPoint(1301.144, 1612.5302609999997),
|
||||
new PdfPoint(1359.0006406250002, 1612.703830921875),
|
||||
new PdfPoint(1400.915, 1612.8295739999996),
|
||||
new PdfPoint(1505.379062499999, 1613.1429661874993),
|
||||
new PdfPoint(1400.915, 1612.8295739999999),
|
||||
new PdfPoint(1505.379062499999, 1613.1429661874995),
|
||||
new PdfPoint(1543.886, 1613.2584869999996),
|
||||
new PdfPoint(1600.9401015625003, 1613.4296493046872),
|
||||
new PdfPoint(1641.597, 1613.5516199999997),
|
||||
new PdfPoint(1764.5577421874998, 1613.9205022265612),
|
||||
new PdfPoint(1641.597, 1613.55162),
|
||||
new PdfPoint(1764.5577421874998, 1613.9205022265614)
|
||||
},
|
||||
new PdfPoint[]
|
||||
{
|
||||
new PdfPoint(134.74199999999985, 1611.657),
|
||||
new PdfPoint(1010.0569999999999, 1611.6569999999997),
|
||||
new PdfPoint(1764.5577421874998, 1613.9205022265614),
|
||||
}
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
[Fact]
|
||||
@@ -570,7 +616,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MinimumAreaRectangleData))]
|
||||
public void MinimumAreaRectangle(PdfPoint[] points, PdfPoint[] expected)
|
||||
@@ -585,5 +630,51 @@
|
||||
Assert.Equal(expected[i], mar[i], PointComparer);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Issue458Data))]
|
||||
public void Issue458(PdfPoint[] points, PdfPoint[] expected)
|
||||
{
|
||||
/*
|
||||
* https://github.com/UglyToad/PdfPig/issues/458
|
||||
* An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Linq.dll: 'Specified argument was out of the range of valid values.'
|
||||
* at System.Linq.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument)
|
||||
* at System.Linq.Enumerable.ElementAt[TSource](IEnumerable`1 source, Int32 index)
|
||||
*/
|
||||
|
||||
var result = GeometryExtensions.GrahamScan(points);
|
||||
|
||||
// Data is noisy so we just check it does not throw an exception
|
||||
// and that key points are present (other points might be present,
|
||||
// e.g. 'not enough equal dupplicates' or 'not collinear enough' points)
|
||||
foreach (var point in expected)
|
||||
{
|
||||
Assert.Contains(point, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Issue458Data))]
|
||||
public void Issue458_Inv(PdfPoint[] points, PdfPoint[] expected)
|
||||
{
|
||||
/*
|
||||
* https://github.com/UglyToad/PdfPig/issues/458
|
||||
* An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Linq.dll: 'Specified argument was out of the range of valid values.'
|
||||
* at System.Linq.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument)
|
||||
* at System.Linq.Enumerable.ElementAt[TSource](IEnumerable`1 source, Int32 index)
|
||||
*/
|
||||
var pointsInv = points.Select(p => new PdfPoint(p.Y, p.X)).ToArray();
|
||||
var expectedInv = expected.Select(p => new PdfPoint(p.Y, p.X)).ToArray();
|
||||
|
||||
var result = GeometryExtensions.GrahamScan(pointsInv);
|
||||
|
||||
// Data is noisy so we just check it does not throw an exception
|
||||
// and that key points are present (other points might be present,
|
||||
// e.g. 'not enough equal dupplicates' or 'not collinear enough' points)
|
||||
foreach (var point in expectedInv)
|
||||
{
|
||||
Assert.Contains(point, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
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;
|
||||
@@ -183,6 +182,30 @@ 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', ' ');
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,12 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -171,5 +177,34 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -29,7 +29,12 @@
|
||||
/// <summary>
|
||||
/// The page tree for this document containing all pages, page numbers and their dictionaries.
|
||||
/// </summary>
|
||||
public PageTreeNode PageTree { get; }
|
||||
public PageTreeNode PageTree { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of discovered pages.
|
||||
/// </summary>
|
||||
public int? NumberOfDiscoveredPages => pagesByNumber?.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="CatalogDictionary"/>.
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
internal interface IPageFactory
|
||||
{
|
||||
Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, bool clipPaths);
|
||||
Page Create(
|
||||
int number,
|
||||
DictionaryToken dictionary,
|
||||
PageTreeMembers pageTreeMembers,
|
||||
InternalParsingOptions parsingOptions);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
internal interface IResourceStore
|
||||
{
|
||||
void LoadResourceDictionary(DictionaryToken resourceDictionary);
|
||||
void LoadResourceDictionary(DictionaryToken resourceDictionary, InternalParsingOptions parsingOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Remove any named resources and associated state for the last resource dictionary loaded.
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
/// <summary>
|
||||
/// Create a <see cref="PageRotationDegrees"/>.
|
||||
/// </summary>
|
||||
/// <param name="rotation">Rotation in degrees clockwise.</param>
|
||||
/// <param name="rotation">Rotation in degrees clockwise, must be a multiple of 90.</param>
|
||||
public PageRotationDegrees(int rotation)
|
||||
{
|
||||
if (rotation < 0)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
/// <summary>
|
||||
/// The number of this page if <see cref="IsPage"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public int? PageNumber { get; }
|
||||
public int? PageNumber { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The child nodes of this node if <see cref="IsPage"/> is <see langword="false" />
|
||||
|
||||
@@ -21,12 +21,21 @@
|
||||
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, bool clipPaths)
|
||||
public Page GetPage(int pageNumber, InternalParsingOptions parsingOptions)
|
||||
{
|
||||
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}.");
|
||||
}
|
||||
@@ -63,7 +72,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
var page = pageFactory.Create(pageNumber, pageNode.NodeDictionary, pageTreeMembers, clipPaths);
|
||||
var page = pageFactory.Create(
|
||||
pageNumber,
|
||||
pageNode.NodeDictionary,
|
||||
pageTreeMembers,
|
||||
parsingOptions);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -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<DictionaryToken>(fontBase, scanner);
|
||||
|
||||
LoadFontDictionary(fontDictionary);
|
||||
LoadFontDictionary(fontDictionary, parsingOptions);
|
||||
}
|
||||
|
||||
if (resourceDictionary.TryGet(NameToken.Xobject, out var xobjectBase))
|
||||
@@ -52,6 +52,11 @@
|
||||
|
||||
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}.");
|
||||
@@ -127,7 +132,7 @@
|
||||
currentResourceState.Pop();
|
||||
}
|
||||
|
||||
private void LoadFontDictionary(DictionaryToken fontDictionary)
|
||||
private void LoadFontDictionary(DictionaryToken fontDictionary, InternalParsingOptions parsingOptions)
|
||||
{
|
||||
lastLoadedFont = (null, null);
|
||||
|
||||
@@ -152,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)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
@@ -29,11 +28,6 @@
|
||||
|
||||
var buffer = new byte[256];
|
||||
|
||||
if (data.Length > 256)
|
||||
{
|
||||
Debugger.Break();
|
||||
}
|
||||
|
||||
using (var decryptor = rijndael.CreateDecryptor(rijndael.Key, rijndael.IV))
|
||||
using (var input = new MemoryStream(data))
|
||||
using (var output = new MemoryStream())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,11 +269,12 @@
|
||||
|
||||
double polarAngle(PdfPoint point1, PdfPoint point2)
|
||||
{
|
||||
// This is used for grouping, we could use Math.Round()
|
||||
return Math.Atan2(point2.Y - point1.Y, point2.X - point1.X) % Math.PI;
|
||||
}
|
||||
|
||||
Stack<PdfPoint> stack = new Stack<PdfPoint>();
|
||||
var sortedPoints = points.OrderBy(p => p.Y).ThenBy(p => p.X).ToList();
|
||||
var stack = new Stack<PdfPoint>();
|
||||
var sortedPoints = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
|
||||
var P0 = sortedPoints[0];
|
||||
var groups = sortedPoints.Skip(1).GroupBy(p => polarAngle(P0, p)).OrderBy(g => g.Key);
|
||||
|
||||
@@ -309,7 +310,7 @@
|
||||
for (int i = 2; i < sortedPoints.Count; i++)
|
||||
{
|
||||
var point = sortedPoints[i];
|
||||
while (!ccw(stack.ElementAt(1), stack.Peek(), point))
|
||||
while (stack.Count > 1 && !ccw(stack.ElementAt(1), stack.Peek(), point))
|
||||
{
|
||||
stack.Pop();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
using Core;
|
||||
using Filters;
|
||||
using Geometry;
|
||||
using Logging;
|
||||
using Operations;
|
||||
using Parser;
|
||||
using PdfFonts;
|
||||
@@ -49,9 +48,8 @@
|
||||
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>();
|
||||
@@ -90,9 +88,8 @@
|
||||
IPdfTokenScanner pdfScanner,
|
||||
IPageContentParser pageContentParser,
|
||||
ILookupFilterProvider filterProvider,
|
||||
ILog log,
|
||||
bool clipPaths,
|
||||
PdfVector pageSize)
|
||||
PdfVector pageSize,
|
||||
InternalParsingOptions parsingOptions)
|
||||
{
|
||||
this.resourceStore = resourceStore;
|
||||
this.userSpaceUnit = userSpaceUnit;
|
||||
@@ -100,9 +97,8 @@
|
||||
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();
|
||||
@@ -230,6 +226,15 @@
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
@@ -253,7 +258,8 @@
|
||||
|
||||
if (!foundUnicode || unicode == null)
|
||||
{
|
||||
log.Warn($"We could not find the corresponding character with code {code} in font {font.Name}.");
|
||||
parsingOptions.Logger.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);
|
||||
}
|
||||
@@ -473,7 +479,7 @@
|
||||
var hasResources = formStream.StreamDictionary.TryGet<DictionaryToken>(NameToken.Resources, pdfScanner, out var formResources);
|
||||
if (hasResources)
|
||||
{
|
||||
resourceStore.LoadResourceDictionary(formResources);
|
||||
resourceStore.LoadResourceDictionary(formResources, parsingOptions);
|
||||
}
|
||||
|
||||
// 1. Save current state.
|
||||
@@ -494,7 +500,7 @@
|
||||
|
||||
var contentStream = formStream.Decode(filterProvider, pdfScanner);
|
||||
|
||||
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentStream), log);
|
||||
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentStream), parsingOptions.Logger);
|
||||
|
||||
// 3. We don't respect clipping currently.
|
||||
|
||||
@@ -677,7 +683,7 @@
|
||||
|
||||
if (CurrentPath.IsClipping)
|
||||
{
|
||||
if (!clipPaths)
|
||||
if (!parsingOptions.ClipPaths)
|
||||
{
|
||||
// if we don't clip paths, add clipping path to paths
|
||||
paths.Add(CurrentPath);
|
||||
@@ -717,9 +723,9 @@
|
||||
CurrentPath.FillColor = currentState.CurrentNonStrokingColor;
|
||||
}
|
||||
|
||||
if (clipPaths)
|
||||
if (parsingOptions.ClipPaths)
|
||||
{
|
||||
var clippedPath = currentState.CurrentClippingPath.Clip(CurrentPath, log);
|
||||
var clippedPath = currentState.CurrentClippingPath.Clip(CurrentPath, parsingOptions.Logger);
|
||||
if (clippedPath != null)
|
||||
{
|
||||
paths.Add(clippedPath);
|
||||
@@ -745,15 +751,15 @@
|
||||
AddCurrentSubpath();
|
||||
CurrentPath.SetClipping(clippingRule);
|
||||
|
||||
if (clipPaths)
|
||||
if (parsingOptions.ClipPaths)
|
||||
{
|
||||
var currentClipping = GetCurrentState().CurrentClippingPath;
|
||||
currentClipping.SetClipping(clippingRule);
|
||||
|
||||
var newClippings = CurrentPath.Clip(currentClipping, log);
|
||||
var newClippings = CurrentPath.Clip(currentClipping, parsingOptions.Logger);
|
||||
if (newClippings == null)
|
||||
{
|
||||
log.Warn("Empty clipping path found. Clipping path not updated.");
|
||||
parsingOptions.Logger.Warn("Empty clipping path found. Clipping path not updated.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -796,7 +802,7 @@
|
||||
{
|
||||
if (inlineImageBuilder != null)
|
||||
{
|
||||
log?.Error("Begin inline image (BI) command encountered while another inline image was active.");
|
||||
parsingOptions.Logger.Error("Begin inline image (BI) command encountered while another inline image was active.");
|
||||
}
|
||||
|
||||
inlineImageBuilder = new InlineImageBuilder();
|
||||
@@ -806,7 +812,7 @@
|
||||
{
|
||||
if (inlineImageBuilder == null)
|
||||
{
|
||||
log?.Error("Begin inline image data (ID) command encountered without a corresponding begin inline image (BI) command.");
|
||||
parsingOptions.Logger.Error("Begin inline image data (ID) command encountered without a corresponding begin inline image (BI) command.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -817,7 +823,7 @@
|
||||
{
|
||||
if (inlineImageBuilder == null)
|
||||
{
|
||||
log?.Error("End inline image (EI) command encountered without a corresponding begin inline image (BI) command.");
|
||||
parsingOptions.Logger.Error("End inline image (EI) command encountered without a corresponding begin inline image (BI) command.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
35
src/UglyToad.PdfPig/InternalParsingOptions.cs
Normal file
35
src/UglyToad.PdfPig/InternalParsingOptions.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
using Content;
|
||||
using Core;
|
||||
using Parts;
|
||||
using System.Linq;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using Util;
|
||||
|
||||
internal static class CatalogFactory
|
||||
{
|
||||
@@ -79,9 +81,13 @@
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
//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)
|
||||
|
||||
var toProcess =
|
||||
new Queue<(PageTreeNode thisPage, IndirectReference reference, DictionaryToken nodeDictionary, IndirectReference parentReference,
|
||||
@@ -98,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)
|
||||
@@ -126,8 +140,6 @@
|
||||
|
||||
if (isChildPage)
|
||||
{
|
||||
pageNumber.Increment();
|
||||
|
||||
var kidPageNode =
|
||||
new PageTreeNode(kidDictionaryToken, kidRef.Data, true, pageNumber.PageCount).WithChildren(EmptyArray<PageTreeNode>.Instance);
|
||||
current.nodeChildren.Add(kidPageNode);
|
||||
@@ -152,6 +164,12 @@
|
||||
action();
|
||||
}
|
||||
|
||||
foreach (var child in firstPage.Children.ToRecursiveOrderList(x=>x.Children).Where(child => child.IsPage))
|
||||
{
|
||||
pageNumber.Increment();
|
||||
child.PageNumber = pageNumber.PageCount;
|
||||
}
|
||||
|
||||
return firstPage;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 +142,13 @@
|
||||
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.");
|
||||
|
||||
@@ -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,
|
||||
ILog log)
|
||||
public PageFactory(
|
||||
IPdfTokenScanner pdfScanner,
|
||||
IResourceStore resourceStore,
|
||||
ILookupFilterProvider filterProvider,
|
||||
IPageContentParser pageContentParser)
|
||||
{
|
||||
this.resourceStore = resourceStore;
|
||||
this.filterProvider = filterProvider;
|
||||
this.pageContentParser = pageContentParser;
|
||||
this.log = log;
|
||||
this.pdfScanner = pdfScanner;
|
||||
}
|
||||
|
||||
public Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, bool clipPaths)
|
||||
public Page Create(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers, InternalParsingOptions parsingOptions)
|
||||
{
|
||||
if (dictionary == null)
|
||||
{
|
||||
@@ -45,11 +45,11 @@
|
||||
|
||||
if (type != null && !type.Equals(NameToken.Page))
|
||||
{
|
||||
log?.Error($"Page {number} had its type specified as {type} rather than 'Page'.");
|
||||
parsingOptions.Logger.Error($"Page {number} had its type specified as {type} rather than 'Page'.");
|
||||
}
|
||||
|
||||
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers);
|
||||
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox);
|
||||
MediaBox mediaBox = GetMediaBox(number, dictionary, pageTreeMembers, parsingOptions.Logger);
|
||||
CropBox cropBox = GetCropBox(dictionary, pageTreeMembers, mediaBox, parsingOptions.Logger);
|
||||
|
||||
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);
|
||||
resourceStore.LoadResourceDictionary(resource, parsingOptions);
|
||||
stackDepth++;
|
||||
}
|
||||
|
||||
if (dictionary.TryGet(NameToken.Resources, pdfScanner, out DictionaryToken resources))
|
||||
{
|
||||
resourceStore.LoadResourceDictionary(resources);
|
||||
resourceStore.LoadResourceDictionary(resources, parsingOptions);
|
||||
stackDepth++;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -143,7 +143,7 @@
|
||||
|
||||
var bytes = contentStream.Decode(filterProvider, pdfScanner);
|
||||
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, clipPaths, mediaBox);
|
||||
content = GetContent(number, bytes, cropBox, userSpaceUnit, rotation, mediaBox, parsingOptions);
|
||||
}
|
||||
|
||||
var page = new Page(number, dictionary, mediaBox, cropBox, rotation, content,
|
||||
@@ -158,18 +158,28 @@
|
||||
return page;
|
||||
}
|
||||
|
||||
private PageContent GetContent(int pageNumber, IReadOnlyList<byte> contentBytes, CropBox cropBox, UserSpaceUnit userSpaceUnit,
|
||||
PageRotationDegrees rotation, bool clipPaths, MediaBox mediaBox)
|
||||
private PageContent GetContent(
|
||||
int pageNumber,
|
||||
IReadOnlyList<byte> contentBytes,
|
||||
CropBox cropBox,
|
||||
UserSpaceUnit userSpaceUnit,
|
||||
PageRotationDegrees rotation,
|
||||
MediaBox mediaBox,
|
||||
InternalParsingOptions parsingOptions)
|
||||
{
|
||||
var operations = pageContentParser.Parse(pageNumber, new ByteArrayInputBytes(contentBytes),
|
||||
log);
|
||||
parsingOptions.Logger);
|
||||
|
||||
var context = new ContentStreamProcessor(cropBox.Bounds, resourceStore, userSpaceUnit, rotation, pdfScanner,
|
||||
var context = new ContentStreamProcessor(
|
||||
cropBox.Bounds,
|
||||
resourceStore,
|
||||
userSpaceUnit,
|
||||
rotation,
|
||||
pdfScanner,
|
||||
pageContentParser,
|
||||
filterProvider,
|
||||
log,
|
||||
clipPaths,
|
||||
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height));
|
||||
new PdfVector(mediaBox.Bounds.Width, mediaBox.Bounds.Height),
|
||||
parsingOptions);
|
||||
|
||||
return context.Process(pageNumber, operations);
|
||||
}
|
||||
@@ -185,7 +195,11 @@
|
||||
return spaceUnits;
|
||||
}
|
||||
|
||||
private CropBox GetCropBox(DictionaryToken dictionary, PageTreeMembers pageTreeMembers, MediaBox mediaBox)
|
||||
private CropBox GetCropBox(
|
||||
DictionaryToken dictionary,
|
||||
PageTreeMembers pageTreeMembers,
|
||||
MediaBox mediaBox,
|
||||
ILog log)
|
||||
{
|
||||
CropBox cropBox;
|
||||
if (dictionary.TryGet(NameToken.CropBox, out var cropBoxObject) &&
|
||||
@@ -210,7 +224,11 @@
|
||||
return cropBox;
|
||||
}
|
||||
|
||||
private MediaBox GetMediaBox(int number, DictionaryToken dictionary, PageTreeMembers pageTreeMembers)
|
||||
private MediaBox GetMediaBox(
|
||||
int number,
|
||||
DictionaryToken dictionary,
|
||||
PageTreeMembers pageTreeMembers,
|
||||
ILog log)
|
||||
{
|
||||
MediaBox mediaBox;
|
||||
if (dictionary.TryGet(NameToken.MediaBox, out var mediaboxObject)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -45,9 +45,25 @@
|
||||
|
||||
internal static PdfDocument Open(Stream stream, ParsingOptions options)
|
||||
{
|
||||
var initialPosition = stream.Position;
|
||||
|
||||
var streamInput = new StreamInputBytes(stream, false);
|
||||
|
||||
return Open(streamInput, options);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static PdfDocument Open(IInputBytes inputBytes, ParsingOptions options = null)
|
||||
@@ -75,19 +91,28 @@
|
||||
passwords.Add(string.Empty);
|
||||
}
|
||||
|
||||
var document = OpenDocument(inputBytes, tokenScanner, options?.Logger ?? new NoOpLog(), isLenientParsing, passwords, clipPaths);
|
||||
var finalOptions = new InternalParsingOptions(
|
||||
passwords,
|
||||
isLenientParsing,
|
||||
clipPaths,
|
||||
options?.SkipMissingFonts ?? false,
|
||||
options?.Logger ?? new NoOpLog());
|
||||
|
||||
var document = OpenDocument(inputBytes, tokenScanner, finalOptions);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private static PdfDocument OpenDocument(IInputBytes inputBytes, ISeekableTokenScanner scanner, ILog log, bool isLenientParsing,
|
||||
IReadOnlyList<string> passwords, bool clipPaths)
|
||||
private static PdfDocument OpenDocument(
|
||||
IInputBytes inputBytes,
|
||||
ISeekableTokenScanner scanner,
|
||||
InternalParsingOptions parsingOptions)
|
||||
{
|
||||
var filterProvider = new FilterProviderWithLookup(DefaultFilterProvider.Instance);
|
||||
|
||||
CrossReferenceTable crossReferenceTable = null;
|
||||
|
||||
var xrefValidator = new XrefOffsetValidator(log);
|
||||
var xrefValidator = new XrefOffsetValidator(parsingOptions.Logger);
|
||||
|
||||
// We're ok with this since our intent is to lazily load the cross reference table.
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
@@ -95,30 +120,39 @@
|
||||
var pdfScanner = new PdfTokenScanner(inputBytes, locationProvider, filterProvider, NoOpEncryptionHandler.Instance);
|
||||
|
||||
var crossReferenceStreamParser = new CrossReferenceStreamParser(filterProvider);
|
||||
var crossReferenceParser = new CrossReferenceParser(log, xrefValidator, crossReferenceStreamParser);
|
||||
var crossReferenceParser = new CrossReferenceParser(parsingOptions.Logger, xrefValidator, crossReferenceStreamParser);
|
||||
|
||||
var version = FileHeaderParser.Parse(scanner, inputBytes, isLenientParsing, log);
|
||||
var version = FileHeaderParser.Parse(scanner, inputBytes, parsingOptions.UseLenientParsing, parsingOptions.Logger);
|
||||
|
||||
var crossReferenceOffset = FileTrailerParser.GetFirstCrossReferenceOffset(inputBytes, scanner,
|
||||
isLenientParsing) + version.OffsetInFile;
|
||||
var crossReferenceOffset = FileTrailerParser.GetFirstCrossReferenceOffset(
|
||||
inputBytes,
|
||||
scanner,
|
||||
parsingOptions.UseLenientParsing) + version.OffsetInFile;
|
||||
|
||||
// TODO: make this use the scanner.
|
||||
var validator = new CrossReferenceOffsetValidator(xrefValidator);
|
||||
|
||||
crossReferenceOffset = validator.Validate(crossReferenceOffset, scanner, inputBytes, isLenientParsing);
|
||||
crossReferenceOffset = validator.Validate(crossReferenceOffset, scanner, inputBytes, parsingOptions.UseLenientParsing);
|
||||
|
||||
crossReferenceTable = crossReferenceParser.Parse(inputBytes, isLenientParsing,
|
||||
crossReferenceTable = crossReferenceParser.Parse(
|
||||
inputBytes,
|
||||
parsingOptions.UseLenientParsing,
|
||||
crossReferenceOffset,
|
||||
version.OffsetInFile,
|
||||
pdfScanner,
|
||||
scanner);
|
||||
|
||||
var (rootReference, rootDictionary) = ParseTrailer(crossReferenceTable, isLenientParsing,
|
||||
var (rootReference, rootDictionary) = ParseTrailer(
|
||||
crossReferenceTable,
|
||||
parsingOptions.UseLenientParsing,
|
||||
pdfScanner,
|
||||
out var encryptionDictionary);
|
||||
|
||||
var encryptionHandler = encryptionDictionary != null ?
|
||||
(IEncryptionHandler)new EncryptionHandler(encryptionDictionary, crossReferenceTable.Trailer, passwords)
|
||||
(IEncryptionHandler)new EncryptionHandler(
|
||||
encryptionDictionary,
|
||||
crossReferenceTable.Trailer,
|
||||
parsingOptions.Passwords)
|
||||
: NoOpEncryptionHandler.Instance;
|
||||
|
||||
pdfScanner.UpdateEncryptionHandler(encryptionHandler);
|
||||
@@ -128,35 +162,45 @@
|
||||
|
||||
var type1Handler = new Type1FontHandler(pdfScanner, filterProvider, encodingReader);
|
||||
|
||||
var fontFactory = new FontFactory(log, new Type0FontHandler(cidFontFactory,
|
||||
var fontFactory = new FontFactory(parsingOptions.Logger, new Type0FontHandler(cidFontFactory,
|
||||
filterProvider, pdfScanner),
|
||||
new TrueTypeFontHandler(log, pdfScanner, filterProvider, encodingReader, SystemFontFinder.Instance,
|
||||
new TrueTypeFontHandler(parsingOptions.Logger, 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, isLenientParsing);
|
||||
var information = DocumentInformationFactory.Create(
|
||||
pdfScanner,
|
||||
crossReferenceTable.Trailer,
|
||||
parsingOptions.UseLenientParsing);
|
||||
|
||||
var catalog = CatalogFactory.Create(rootReference, rootDictionary, pdfScanner, isLenientParsing);
|
||||
var catalog = CatalogFactory.Create(
|
||||
rootReference,
|
||||
rootDictionary,
|
||||
pdfScanner,
|
||||
parsingOptions.UseLenientParsing);
|
||||
|
||||
var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider,
|
||||
new PageContentParser(new ReflectionGraphicsStateOperationFactory()),
|
||||
log);
|
||||
|
||||
var caching = new ParsingCachingProviders(resourceContainer);
|
||||
new PageContentParser(new ReflectionGraphicsStateOperationFactory()));
|
||||
|
||||
var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider, crossReferenceTable);
|
||||
var bookmarksProvider = new BookmarksProvider(log, pdfScanner);
|
||||
var bookmarksProvider = new BookmarksProvider(parsingOptions.Logger, pdfScanner);
|
||||
|
||||
return new PdfDocument(log, inputBytes, version, crossReferenceTable, caching, pageFactory, catalog, information,
|
||||
return new PdfDocument(
|
||||
inputBytes,
|
||||
version,
|
||||
crossReferenceTable,
|
||||
pageFactory,
|
||||
catalog,
|
||||
information,
|
||||
encryptionDictionary,
|
||||
pdfScanner,
|
||||
filterProvider,
|
||||
acroFormFactory,
|
||||
bookmarksProvider,
|
||||
clipPaths);
|
||||
parsingOptions);
|
||||
}
|
||||
|
||||
private static (IndirectReference, DictionaryToken) ParseTrailer(CrossReferenceTable crossReferenceTable, bool isLenientParsing, IPdfTokenScanner pdfTokenScanner,
|
||||
|
||||
@@ -48,5 +48,11 @@
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
using Encryption;
|
||||
using Exceptions;
|
||||
using Filters;
|
||||
using Logging;
|
||||
using Parser;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
@@ -28,16 +27,9 @@
|
||||
|
||||
[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;
|
||||
|
||||
@@ -46,6 +38,7 @@
|
||||
|
||||
private readonly ILookupFilterProvider filterProvider;
|
||||
private readonly BookmarksProvider bookmarksProvider;
|
||||
private readonly InternalParsingOptions parsingOptions;
|
||||
|
||||
[NotNull]
|
||||
private readonly Pages pages;
|
||||
@@ -82,11 +75,10 @@
|
||||
/// </summary>
|
||||
public bool IsEncrypted => encryptionDictionary != null;
|
||||
|
||||
internal PdfDocument(ILog log,
|
||||
internal PdfDocument(
|
||||
IInputBytes inputBytes,
|
||||
HeaderVersion version,
|
||||
CrossReferenceTable crossReferenceTable,
|
||||
ParsingCachingProviders cachingProviders,
|
||||
IPageFactory pageFactory,
|
||||
Catalog catalog,
|
||||
DocumentInformation information,
|
||||
@@ -95,17 +87,16 @@
|
||||
ILookupFilterProvider filterProvider,
|
||||
AcroFormFactory acroFormFactory,
|
||||
BookmarksProvider bookmarksProvider,
|
||||
bool clipPaths)
|
||||
InternalParsingOptions parsingOptions)
|
||||
{
|
||||
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.clipPaths = clipPaths;
|
||||
this.parsingOptions = parsingOptions;
|
||||
|
||||
Information = information ?? throw new ArgumentNullException(nameof(information));
|
||||
pages = new Pages(catalog, pageFactory, pdfScanner);
|
||||
Structure = new Structure(catalog, crossReferenceTable, pdfScanner);
|
||||
@@ -153,11 +144,11 @@
|
||||
throw new ObjectDisposedException("Cannot access page after the document is disposed.");
|
||||
}
|
||||
|
||||
log.Debug($"Accessing page {pageNumber}.");
|
||||
parsingOptions.Logger.Debug($"Accessing page {pageNumber}.");
|
||||
|
||||
try
|
||||
{
|
||||
return pages.GetPage(pageNumber, clipPaths);
|
||||
return pages.GetPage(pageNumber, parsingOptions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -258,7 +249,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed disposing the PdfDocument due to an error.", ex);
|
||||
parsingOptions.Logger.Error("Failed disposing the PdfDocument due to an error.", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -107,18 +107,25 @@
|
||||
CMap toUnicodeCMap = null;
|
||||
if (dictionary.TryGet(NameToken.ToUnicode, out var toUnicodeObj))
|
||||
{
|
||||
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, pdfScanner);
|
||||
|
||||
var decodedUnicodeCMap = toUnicode.Decode(filterProvider, pdfScanner);
|
||||
|
||||
if (decodedUnicodeCMap != null)
|
||||
try
|
||||
{
|
||||
toUnicodeCMap = CMapCache.Parse(new ByteArrayInputBytes(decodedUnicodeCMap));
|
||||
var toUnicode = DirectObjectFinder.Get<StreamToken>(toUnicodeObj, pdfScanner);
|
||||
|
||||
var decodedUnicodeCMap = toUnicode.Decode(filterProvider, pdfScanner);
|
||||
|
||||
if (decodedUnicodeCMap != null)
|
||||
{
|
||||
toUnicodeCMap = CMapCache.Parse(new ByteArrayInputBytes(decodedUnicodeCMap));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Failed to decode ToUnicode CMap for a TrueType font in file due to an exception.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
Encoding encoding = encodingReader.Read(dictionary, descriptor);
|
||||
|
||||
|
||||
if (encoding == null && font?.TableRegister?.CMapTable != null
|
||||
&& font.TableRegister.PostScriptTable?.GlyphNames != null)
|
||||
{
|
||||
@@ -199,7 +206,7 @@
|
||||
$"Expected a TrueType font in the TrueType font descriptor, instead it was {descriptor.FontFile.FileType}.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var font = TrueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile)));
|
||||
|
||||
return font;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
44
src/UglyToad.PdfPig/Util/EnumerableExtensions.cs
Normal file
44
src/UglyToad.PdfPig/Util/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ namespace UglyToad.PdfPig.Writer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content;
|
||||
@@ -559,6 +558,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;
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
|
||||
private int imageKey = 1;
|
||||
|
||||
internal int? rotation;
|
||||
|
||||
internal IReadOnlyDictionary<string, IToken> Resources => pageDictionary.GetOrCreateDict(NameToken.Resources);
|
||||
|
||||
/// <summary>
|
||||
@@ -131,7 +133,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 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -156,7 +160,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 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,128 @@
|
||||
{
|
||||
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>
|
||||
@@ -186,10 +312,12 @@
|
||||
/// <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 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,11 +326,13 @@
|
||||
/// <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 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,18 +341,22 @@
|
||||
/// <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 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the stroke, text and fill color to default (black).
|
||||
/// </summary>
|
||||
public void ResetColor()
|
||||
public PdfPageBuilder ResetColor()
|
||||
{
|
||||
currentStream.Add(Pop.Value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -346,9 +480,11 @@
|
||||
/// 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 void SetTextRenderingMode(TextRenderingMode mode)
|
||||
public PdfPageBuilder SetTextRenderingMode(TextRenderingMode mode)
|
||||
{
|
||||
currentStream.Add(new SetTextRenderingMode(mode));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private NameToken GetAddedFont(PdfDocumentBuilder.AddedFont font)
|
||||
@@ -585,7 +721,7 @@
|
||||
/// Copy a page from unknown source to this page
|
||||
/// </summary>
|
||||
/// <param name="srcPage">Page to be copied</param>
|
||||
public void CopyFrom(Page srcPage)
|
||||
public PdfPageBuilder CopyFrom(Page srcPage)
|
||||
{
|
||||
if (currentStream.Operations.Count > 0)
|
||||
{
|
||||
@@ -599,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,
|
||||
@@ -723,6 +859,8 @@
|
||||
}
|
||||
|
||||
destinationStream.Operations.AddRange(operations);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private List<Letter> DrawLetters(NameToken name, string text, IWritingFont font, TransformationMatrix fontMatrix, decimal fontSize, TransformationMatrix textMatrix)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<PackageTags>PDF;Reader;Document;Adobe;PDFBox;PdfPig;pdf-extract;pdf-to-text;pdf;file;text;C#;dotnet;.NET</PackageTags>
|
||||
<RepositoryUrl>https://github.com/UglyToad/PdfPig</RepositoryUrl>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Version>0.1.6</Version>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<AssemblyVersion>0.1.5.0</AssemblyVersion>
|
||||
<FileVersion>0.1.5.0</FileVersion>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/UglyToad/PdfPig/master/documentation/pdfpig.png</PackageIconUrl>
|
||||
|
||||
Reference in New Issue
Block a user