Compare commits

...

4 Commits

Author SHA1 Message Date
BobLd
0754e7f003 Implement clipping in ProcessFormXObject()
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-03-23 21:18:29 +00:00
BobLd
306642a234 Add SetStrokeDetails() and SetFillDetails() to PdfPath and tidy up ContentStreamProcessor 2025-03-23 20:07:43 +00:00
BobLd
204f488ebf Improve Jpeg2000Helper to support J2K codec and add test
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-03-09 14:05:05 +00:00
BobLd
a4a0fe220a Bump version to 0.1.11-alpha001
Some checks are pending
Build and test / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-03-08 13:42:57 +00:00
17 changed files with 147 additions and 58 deletions

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject> <IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject> <IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject> <IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

View File

@ -2,12 +2,13 @@
{ {
using PdfPig.Images; using PdfPig.Images;
using System; using System;
using UglyToad.PdfPig.Tests.Integration;
public class Jpeg2000HelperTests public class Jpeg2000HelperTests
{ {
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files", "Jpx"))); private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files", "Jpx")));
public static IEnumerable<object[]> GetAllDocuments public static IEnumerable<object[]> GetAllJp2Files
{ {
get get
{ {
@ -15,24 +16,40 @@
} }
} }
[Theory]
[MemberData(nameof(GetAllJp2Files))]
public void GetJp2BitsPerComponent_ReturnsCorrectBitsPerComponent_WhenValidInput(string path)
{
byte[] image = File.ReadAllBytes(Path.Combine(DocumentFolder.Value, path));
Assert.Equal(8, Jpeg2000Helper.GetBitsPerComponent(image));
}
[Fact] [Fact]
public void GetJp2BitsPerComponent_ThrowsException_WhenInputIsTooShort() public void GetJp2BitsPerComponent_ThrowsException_WhenInputIsTooShort()
{ {
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetJp2BitsPerComponent(new byte[11])); Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetBitsPerComponent(new byte[11]));
} }
[Fact] [Fact]
public void GetJp2BitsPerComponent_ThrowsException_WhenSignatureBoxIsInvalid() public void GetJp2BitsPerComponent_ThrowsException_WhenSignatureBoxIsInvalid()
{ {
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetJp2BitsPerComponent(new byte[12])); Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetBitsPerComponent(new byte[12]));
} }
[Theory] [Fact]
[MemberData(nameof(GetAllDocuments))] public void GetJp2BitsPerComponentJ2K()
public void GetJp2BitsPerComponent_ReturnsCorrectBitsPerComponent_WhenValidInput(string path)
{ {
byte[] image = File.ReadAllBytes(Path.Combine(DocumentFolder.Value, path)); string path = IntegrationHelpers.GetSpecificTestDocumentPath("GHOSTSCRIPT-688999-2.pdf");
Assert.Equal(8, Jpeg2000Helper.GetJp2BitsPerComponent(image));
using (var document = PdfDocument.Open(path))
{
var page1 = document.GetPage(1);
var jpxImage = page1.GetImages().Single();
var bpc = Jpeg2000Helper.GetBitsPerComponent(jpxImage.RawBytes);
Assert.Equal(8, bpc);
}
} }
} }
} }

View File

@ -231,6 +231,11 @@
// No op // No op
} }
protected override void ClipToRectangle(PdfRectangle rectangle, FillingRule clippingRule)
{
// No op
}
public override void PaintShading(NameToken shadingName) public override void PaintShading(NameToken shadingName)
{ {
// No op // No op

View File

@ -206,6 +206,11 @@
// No op // No op
} }
protected override void ClipToRectangle(PdfRectangle rectangle, FillingRule clippingRule)
{
// No op
}
public override void PaintShading(NameToken shadingName) public override void PaintShading(NameToken shadingName)
{ {
// No op // No op

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject> <IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject> <IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

View File

@ -381,6 +381,19 @@
#endregion #endregion
#region PdfRectangle #region PdfRectangle
/// <summary>
/// Converts a <see cref="PdfRectangle"/> into its <see cref="PdfPath"/> representation.
/// </summary>
public static PdfPath ToPdfPath(this PdfRectangle rectangle)
{
var clippingSubpath = new PdfSubpath();
clippingSubpath.Rectangle(rectangle.BottomLeft.X,
rectangle.BottomLeft.Y,
rectangle.Width,
rectangle.Height);
return new PdfPath() { clippingSubpath };
}
/// <summary> /// <summary>
/// Whether the point is located inside the rectangle. /// Whether the point is located inside the rectangle.
/// </summary> /// </summary>

View File

@ -145,15 +145,8 @@
/// </summary> /// </summary>
protected static PdfPath GetInitialClipping(CropBox cropBox) protected static PdfPath GetInitialClipping(CropBox cropBox)
{ {
var cropBoxBounds = cropBox.Bounds; // Initiate CurrentClippingPath to cropBox
var clippingPath = cropBox.Bounds.ToPdfPath();
// initiate CurrentClippingPath to cropBox
var clippingSubpath = new PdfSubpath();
clippingSubpath.Rectangle(cropBoxBounds.BottomLeft.X,
cropBoxBounds.BottomLeft.Y,
cropBoxBounds.Width,
cropBoxBounds.Height);
var clippingPath = new PdfPath() { clippingSubpath };
clippingPath.SetClipping(FillingRule.EvenOdd); clippingPath.SetClipping(FillingRule.EvenOdd);
return clippingPath; return clippingPath;
} }
@ -581,7 +574,14 @@
new MemoryInputBytes(contentStream), new MemoryInputBytes(contentStream),
ParsingOptions.Logger); ParsingOptions.Logger);
// 3. We don't respect clipping currently. // 3. Clip according to the form dictionary's BBox entry.
if (formStream.StreamDictionary.TryGet<ArrayToken>(NameToken.Bbox, PdfScanner, out var bboxToken))
{
var points = bboxToken.Data.OfType<NumericToken>().Select(x => x.Double).ToArray();
PdfRectangle bbox = new PdfRectangle(points[0], points[1], points[2], points[3]);
PdfRectangle transformedBox = startState.CurrentTransformationMatrix.Transform(bbox);
ClipToRectangle(transformedBox, FillingRule.EvenOdd); // TODO - Check that Even Odd is valid
}
// 4. Paint the objects. // 4. Paint the objects.
bool hasCircularReference = HasFormXObjectCircularReference(formStream, xObjectName, operations); bool hasCircularReference = HasFormXObjectCircularReference(formStream, xObjectName, operations);
@ -668,6 +668,9 @@
/// <inheritdoc/> /// <inheritdoc/>
public abstract void ModifyClippingIntersect(FillingRule clippingRule); public abstract void ModifyClippingIntersect(FillingRule clippingRule);
/// <inheritdoc/>
protected abstract void ClipToRectangle(PdfRectangle rectangle, FillingRule clippingRule);
/// <inheritdoc/> /// <inheritdoc/>
public virtual void SetNamedGraphicsState(NameToken stateName) public virtual void SetNamedGraphicsState(NameToken stateName)
{ {

View File

@ -4,7 +4,6 @@ namespace UglyToad.PdfPig.Graphics
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Colors;
using Content; using Content;
using Filters; using Filters;
using Geometry; using Geometry;
@ -212,7 +211,7 @@ namespace UglyToad.PdfPig.Graphics
return point; return point;
} }
public void AddCurrentSubpath() // Not an override private void AddCurrentSubpath()
{ {
if (CurrentSubpath is null) if (CurrentSubpath is null)
{ {
@ -385,16 +384,12 @@ namespace UglyToad.PdfPig.Graphics
var currentState = GetCurrentState(); var currentState = GetCurrentState();
if (CurrentPath.IsStroked) if (CurrentPath.IsStroked)
{ {
CurrentPath.LineDashPattern = currentState.LineDashPattern; CurrentPath.SetStrokeDetails(currentState);
CurrentPath.StrokeColor = currentState.CurrentStrokingColor;
CurrentPath.LineWidth = currentState.LineWidth;
CurrentPath.LineCapStyle = currentState.CapStyle;
CurrentPath.LineJoinStyle = currentState.JoinStyle;
} }
if (CurrentPath.IsFilled) if (CurrentPath.IsFilled)
{ {
CurrentPath.FillColor = currentState.CurrentNonStrokingColor; CurrentPath.SetFillDetails(currentState);
} }
if (ParsingOptions.ClipPaths) if (ParsingOptions.ClipPaths)
@ -427,7 +422,8 @@ namespace UglyToad.PdfPig.Graphics
if (ParsingOptions.ClipPaths) if (ParsingOptions.ClipPaths)
{ {
var currentClipping = GetCurrentState().CurrentClippingPath!; var graphicsState = GetCurrentState();
var currentClipping = graphicsState.CurrentClippingPath!;
currentClipping.SetClipping(clippingRule); currentClipping.SetClipping(clippingRule);
var newClippings = CurrentPath.Clip(currentClipping, ParsingOptions.Logger); var newClippings = CurrentPath.Clip(currentClipping, ParsingOptions.Logger);
@ -437,11 +433,32 @@ namespace UglyToad.PdfPig.Graphics
} }
else else
{ {
GetCurrentState().CurrentClippingPath = newClippings; graphicsState.CurrentClippingPath = newClippings;
} }
} }
} }
protected override void ClipToRectangle(PdfRectangle rectangle, FillingRule clippingRule)
{
// https://github.com/apache/pdfbox/blob/f4bfe47de37f6fe69e8f98b164c3546facfd5e91/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDFStreamEngine.java#L611
var graphicsState = GetCurrentState();
var clip = rectangle.ToPdfPath();
clip.SetClipping(clippingRule);
var currentClipping = graphicsState.CurrentClippingPath!;
currentClipping.SetClipping(clippingRule);
var newClippings = clip.Clip(currentClipping, ParsingOptions.Logger);
if (newClippings is null)
{
ParsingOptions.Logger.Warn("Empty clipping path found. Clipping path not updated.");
}
else
{
graphicsState.CurrentClippingPath = newClippings;
}
}
protected override void RenderInlineImage(InlineImage inlineImage) protected override void RenderInlineImage(InlineImage inlineImage)
{ {
images.Add(Union<XObjectContentRecord, InlineImage>.Two(inlineImage)); images.Add(Union<XObjectContentRecord, InlineImage>.Two(inlineImage));

View File

@ -6,7 +6,9 @@
using UglyToad.PdfPig.Graphics.Core; using UglyToad.PdfPig.Graphics.Core;
/// <summary> /// <summary>
/// A path is made up of one or more disconnected subpaths, each comprising a sequence of connected segments. The topology of the path is unrestricted: it may be concave or convex, may contain multiple subpaths representing disjoint areas, and may intersect itself in arbitrary ways. /// A path is made up of one or more disconnected subpaths, each comprising a sequence of connected segments.
/// The topology of the path is unrestricted: it may be concave or convex, may contain multiple subpaths representing
/// disjoint areas, and may intersect itself in arbitrary ways.
/// <para>A path shall be composed of straight and curved line segments, which may connect to one another or may be disconnected.</para> /// <para>A path shall be composed of straight and curved line segments, which may connect to one another or may be disconnected.</para>
/// </summary> /// </summary>
public class PdfPath : List<PdfSubpath> public class PdfPath : List<PdfSubpath>
@ -29,7 +31,7 @@
/// <summary> /// <summary>
/// The fill color. /// The fill color.
/// </summary> /// </summary>
public IColor? FillColor { get; internal set; } public IColor? FillColor { get; private set; }
/// <summary> /// <summary>
/// Returns true if the path is stroked. /// Returns true if the path is stroked.
@ -39,31 +41,31 @@
/// <summary> /// <summary>
/// The stroke color. /// The stroke color.
/// </summary> /// </summary>
public IColor? StrokeColor { get; internal set; } public IColor? StrokeColor { get; private set; }
/// <summary> /// <summary>
/// Thickness in user space units of path to be stroked. /// Thickness in user space units of path to be stroked.
/// </summary> /// </summary>
public double LineWidth { get; internal set; } public double LineWidth { get; private set; }
/// <summary> /// <summary>
/// The pattern to be used for stroked lines. /// The pattern to be used for stroked lines.
/// </summary> /// </summary>
public LineDashPattern? LineDashPattern { get; internal set; } public LineDashPattern? LineDashPattern { get; private set; }
/// <summary> /// <summary>
/// The cap style to be used for stroked lines. /// The cap style to be used for stroked lines.
/// </summary> /// </summary>
public LineCapStyle LineCapStyle { get; internal set; } public LineCapStyle LineCapStyle { get; private set; }
/// <summary> /// <summary>
/// The join style to be used for stroked lines. /// The join style to be used for stroked lines.
/// </summary> /// </summary>
public LineJoinStyle LineJoinStyle { get; internal set; } public LineJoinStyle LineJoinStyle { get; private set; }
/// <summary> /// <summary>
/// Set the clipping mode for this path and IsClipping to true. /// Set the clipping mode for this path and <c>IsClipping</c> to <c>true</c>.
/// <para>IsFilled and IsStroked flags will be set to false.</para> /// <para><c>IsFilled</c> and <c>IsStroked</c> flags will be set to <c>false</c>.</para>
/// </summary> /// </summary>
public void SetClipping(FillingRule fillingRule) public void SetClipping(FillingRule fillingRule)
{ {
@ -74,7 +76,7 @@
} }
/// <summary> /// <summary>
/// Set the filling rule for this path and IsFilled to true. /// Set the filling rule for this path and <c>IsFilled</c> to <c>true</c>.
/// </summary> /// </summary>
public void SetFilled(FillingRule fillingRule) public void SetFilled(FillingRule fillingRule)
{ {
@ -83,13 +85,35 @@
} }
/// <summary> /// <summary>
/// Set IsStroked to true. /// Set <c>IsStroked</c> to <c>true</c>.
/// </summary> /// </summary>
public void SetStroked() public void SetStroked()
{ {
IsStroked = true; IsStroked = true;
} }
/// <summary>
/// Set the path stroke details, i.e. <c>LineDashPattern</c>, <c>StrokeColor</c>, <c>LineWidth</c>, <c>LineCapStyle</c> and <c>LineJoinStyle</c>.
/// </summary>
/// <param name="graphicsState">The current graphics state.</param>
public void SetStrokeDetails(CurrentGraphicsState graphicsState)
{
LineDashPattern = graphicsState.LineDashPattern;
StrokeColor = graphicsState.CurrentStrokingColor;
LineWidth = graphicsState.LineWidth;
LineCapStyle = graphicsState.CapStyle;
LineJoinStyle = graphicsState.JoinStyle;
}
/// <summary>
/// Set the path fill details, i.e. <c>FillColor</c>.
/// </summary>
/// <param name="graphicsState">The current graphics state.</param>
public void SetFillDetails(CurrentGraphicsState graphicsState)
{
FillColor = graphicsState.CurrentNonStrokingColor;
}
/// <summary> /// <summary>
/// Create a clone with no Subpaths. /// Create a clone with no Subpaths.
/// </summary> /// </summary>

View File

@ -8,7 +8,7 @@
/// <summary> /// <summary>
/// Get bits per component values for Jp2 (Jpx) encoded images (first component). /// Get bits per component values for Jp2 (Jpx) encoded images (first component).
/// </summary> /// </summary>
public static byte GetJp2BitsPerComponent(ReadOnlySpan<byte> jp2Bytes) public static byte GetBitsPerComponent(ReadOnlySpan<byte> jp2Bytes)
{ {
// Ensure the input has at least 12 bytes for the signature box // Ensure the input has at least 12 bytes for the signature box
if (jp2Bytes.Length < 12) if (jp2Bytes.Length < 12)
@ -18,16 +18,21 @@
// Verify the JP2 signature box // Verify the JP2 signature box
uint length = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(0, 4)); uint length = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(0, 4));
uint type = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(4, 4)); if (length == 0xFF4FFF51)
uint magic = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(8, 4));
if (length != 0x0000000C || type != 0x6A502020 || magic != 0x0D0A870A)
{ {
throw new InvalidOperationException("Invalid JP2 signature box."); // J2K format detected (SOC marker) (See GHOSTSCRIPT-688999-2.pdf)
return ParseCodestream(jp2Bytes);
} }
// Proceed to parse JP2 boxes uint type = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(4, 4));
return ParseBoxes(jp2Bytes.Slice(12)); uint magic = BinaryPrimitives.ReadUInt32BigEndian(jp2Bytes.Slice(8, 4));
if (length == 0x0000000C && type == 0x6A502020 && magic == 0x0D0A870A)
{
// JP2 format detected
return ParseBoxes(jp2Bytes.Slice(12));
}
throw new InvalidOperationException("Invalid JP2 or J2K signature.");
} }
private static byte ParseBoxes(ReadOnlySpan<byte> jp2Bytes) private static byte ParseBoxes(ReadOnlySpan<byte> jp2Bytes)
@ -37,7 +42,7 @@
{ {
if (offset + 8 > jp2Bytes.Length) if (offset + 8 > jp2Bytes.Length)
{ {
throw new InvalidOperationException("Invalid JP2 box structure."); throw new InvalidOperationException("Invalid JP2 or J2K box structure.");
} }
// Read box length and type // Read box length and type
@ -55,7 +60,7 @@
offset += (int)(boxLength > 0 ? boxLength : 8); // Box length of 0 means the rest of the file offset += (int)(boxLength > 0 ? boxLength : 8); // Box length of 0 means the rest of the file
} }
throw new InvalidOperationException("Codestream box not found in JP2 file."); throw new InvalidOperationException("Codestream box not found in JP2 or J2K file.");
} }
private static byte ParseCodestream(ReadOnlySpan<byte> codestream) private static byte ParseCodestream(ReadOnlySpan<byte> codestream)

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject> <IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

View File

@ -60,11 +60,11 @@
if (dictionary.TryGet(NameToken.BitsPerComponent, out NumericToken? bitsPerComponentToken)) if (dictionary.TryGet(NameToken.BitsPerComponent, out NumericToken? bitsPerComponentToken))
{ {
bitsPerComponent = bitsPerComponentToken.Int; bitsPerComponent = bitsPerComponentToken.Int;
System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span)); System.Diagnostics.Debug.Assert(bitsPerComponent == Jpeg2000Helper.GetBitsPerComponent(xObject.Stream.Data.Span));
} }
else else
{ {
bitsPerComponent = Jpeg2000Helper.GetJp2BitsPerComponent(xObject.Stream.Data.Span); bitsPerComponent = Jpeg2000Helper.GetBitsPerComponent(xObject.Stream.Data.Span);
System.Diagnostics.Debug.Assert(new int[] { 1, 2, 4, 8, 16 }.Contains(bitsPerComponent)); System.Diagnostics.Debug.Assert(new int[] { 1, 2, 4, 8, 16 }.Contains(bitsPerComponent));
} }
} }

View File

@ -11,7 +11,7 @@
<PackageTags>PDF;Reader;Document;Adobe;PDFBox;PdfPig;pdf-extract;pdf-to-text;pdf;file;text;C#;dotnet;.NET</PackageTags> <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> <RepositoryUrl>https://github.com/UglyToad/PdfPig</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>0.1.10</Version> <Version>0.1.11-alpha001</Version>
<AssemblyVersion>0.1.10.0</AssemblyVersion> <AssemblyVersion>0.1.10.0</AssemblyVersion>
<FileVersion>0.1.10.0</FileVersion> <FileVersion>0.1.10.0</FileVersion>
<PackageIconUrl>https://raw.githubusercontent.com/UglyToad/PdfPig/master/documentation/pdfpig.png</PackageIconUrl> <PackageIconUrl>https://raw.githubusercontent.com/UglyToad/PdfPig/master/documentation/pdfpig.png</PackageIconUrl>