#24 start adding classes for the acroform api

This commit is contained in:
Eliot Jones
2019-01-01 17:44:46 +00:00
parent 52b925489e
commit 20e843f5ae
9 changed files with 233 additions and 3 deletions

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System;
using Xunit;
public class AcroFormsBasicFieldsTests
{
private static string GetFilename()
{
return IntegrationHelpers.GetDocumentPath("AcroFormsBasicFields");
}
[Fact]
public void GetFormNotNull()
{
using (var document = PdfDocument.Open(GetFilename(), new ParsingOptions { UseLenientParsing = false }))
{
var form = document.GetForm();
Assert.NotNull(form);
}
}
[Fact]
public void GetFormDisposedThrows()
{
var document = PdfDocument.Open(GetFilename());
document.Dispose();
Action action = () => document.GetForm();
Assert.Throws<ObjectDisposedException>(action);
}
}
}

View File

@@ -0,0 +1,49 @@
namespace UglyToad.PdfPig.AcroForms
{
using System;
using Tokens;
using Util.JetBrains.Annotations;
/// <summary>
/// A collection of interactive fields for gathering data from a user through dropdowns, textboxes, checkboxes, etc.
/// Each <see cref="PdfDocument"/> with form functionality contains a single <see cref="AcroForm"/> spread across one or more pages.
/// </summary>
/// <remarks>
/// The name AcroForm distinguishes this from the other form type called form XObjects which act as templates for repeated sections of content.
/// </remarks>
internal class AcroForm
{
/// <summary>
/// The raw PDF dictionary which is the root form object.
/// </summary>
[NotNull]
public DictionaryToken Dictionary { get; }
/// <summary>
/// Document-level characteristics related to signature fields.
/// </summary>
public SignatureFlags SignatureFlags { get; }
/// <summary>
/// Whether all widget annotations need appearance dictionaries and streams.
/// </summary>
public bool NeedAppearances { get; }
/// <summary>
/// Create a new <see cref="AcroForm"/>.
/// </summary>
public AcroForm(DictionaryToken dictionary, SignatureFlags signatureFlags, bool needAppearances)
{
Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
SignatureFlags = signatureFlags;
NeedAppearances = needAppearances;
}
/// <inheritdoc />
public override string ToString()
{
return Dictionary.ToString();
}
}
}

View File

@@ -0,0 +1,90 @@
namespace UglyToad.PdfPig.AcroForms
{
using System;
using Content;
using Exceptions;
using Filters;
using Parser.Parts;
using Tokenization.Scanner;
using Tokens;
using Util;
using Util.JetBrains.Annotations;
/// <summary>
/// Extracts the <see cref="AcroForm"/> from the document, if available.
/// </summary>
internal class AcroFormFactory
{
private readonly IPdfTokenScanner tokenScanner;
private readonly IFilterProvider filterProvider;
public AcroFormFactory(IPdfTokenScanner tokenScanner, IFilterProvider filterProvider)
{
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
}
/// <summary>
/// Retrieve the <see cref="AcroForm"/> from the document, if applicable.
/// </summary>
/// <returns>The <see cref="AcroForm"/> if the document contains one.</returns>
[CanBeNull]
public AcroForm GetAcroForm(Catalog catalog)
{
if (!catalog.CatalogDictionary.TryGet(NameToken.AcroForm, out var acroRawToken) || !DirectObjectFinder.TryGet(acroRawToken, tokenScanner, out DictionaryToken acroDictionary))
{
return null;
}
var signatureFlags = (SignatureFlags)0;
if (acroDictionary.TryGetOptionalTokenDirect(NameToken.SigFlags, tokenScanner, out NumericToken signatureToken))
{
signatureFlags = (SignatureFlags)signatureToken.Int;
}
var needAppearances = false;
if (acroDictionary.TryGetOptionalTokenDirect(NameToken.NeedAppearances, tokenScanner, out BooleanToken appearancesToken))
{
needAppearances = appearancesToken.Data;
}
var calculationOrder = default(ArrayToken);
acroDictionary.TryGetOptionalTokenDirect(NameToken.Co, tokenScanner, out calculationOrder);
var formResources = default(DictionaryToken);
acroDictionary.TryGetOptionalTokenDirect(NameToken.Dr, tokenScanner, out formResources);
var da = default(string);
if (acroDictionary.TryGetOptionalTokenDirect(NameToken.Da, tokenScanner, out StringToken daToken))
{
da = daToken.Data;
}
else if (acroDictionary.TryGetOptionalTokenDirect(NameToken.Da, tokenScanner, out HexToken daHexToken))
{
da = daHexToken.Data;
}
var q = default(int?);
if (acroDictionary.TryGetOptionalTokenDirect(NameToken.Q, tokenScanner, out NumericToken qToken))
{
q = qToken.Int;
}
var fieldsToken = acroDictionary.Data[NameToken.Fields.Data];
if (!DirectObjectFinder.TryGet(fieldsToken, tokenScanner, out ArrayToken fieldsArray))
{
throw new PdfDocumentFormatException($"Could not retrieve the fields array for an AcroForm: {acroDictionary}.");
}
foreach (var fieldToken in fieldsArray.Data)
{
var fieldDictionary = DirectObjectFinder.Get<DictionaryToken>(fieldToken, tokenScanner);
}
return new AcroForm(acroDictionary, signatureFlags, needAppearances);
}
}
}

View File

@@ -0,0 +1,21 @@
namespace UglyToad.PdfPig.AcroForms
{
using System;
/// <summary>
/// Specifies document level characteristics for any signature fields in the document's <see cref="AcroForm"/>.
/// </summary>
[Flags]
internal enum SignatureFlags
{
/// <summary>
/// The document contains at least one signature field.
/// </summary>
SignaturesExist = 1 << 0,
/// <summary>
/// The document contains signatures which may be invalidated if the file is saved
/// in a way which alters its previous content rather than simply appending new content.
/// </summary>
AppendOnly = 1 << 1
}
}

View File

@@ -30,7 +30,7 @@
{
var type = dictionary.GetNameOrDefault(NameToken.Type);
if (!type.Equals(NameToken.Font))
if (type != null && !type.Equals(NameToken.Font))
{
var message = "The font dictionary did not have type 'Font'. " + dictionary;

View File

@@ -2,6 +2,7 @@
{
using System;
using System.IO;
using AcroForms;
using Content;
using CrossReference;
using FileStructure;
@@ -127,9 +128,11 @@
var catalog = catalogFactory.Create(pdfScanner, rootDictionary);
var caching = new ParsingCachingProviders(bruteForceSearcher, resourceContainer);
var acroFormFactory = new AcroFormFactory(pdfScanner, filterProvider);
return new PdfDocument(log, inputBytes, version, crossReferenceTable, isLenientParsing, caching, pageFactory, catalog, information,
pdfScanner);
pdfScanner, acroFormFactory);
}
private static DictionaryToken ParseTrailer(CrossReferenceTable crossReferenceTable, bool isLenientParsing, IPdfTokenScanner pdfTokenScanner)

View File

@@ -2,6 +2,7 @@
{
using System;
using System.IO;
using AcroForms;
using Content;
using CrossReference;
using IO;
@@ -17,6 +18,7 @@
public class PdfDocument : IDisposable
{
private bool isDisposed;
private readonly Lazy<AcroForm> documentForm;
private readonly bool isLenientParsing;
@@ -65,7 +67,8 @@
ParsingCachingProviders cachingProviders,
IPageFactory pageFactory,
Catalog catalog,
DocumentInformation information, IPdfTokenScanner pdfScanner)
DocumentInformation information, IPdfTokenScanner pdfScanner,
AcroFormFactory acroFormFactory)
{
this.log = log;
this.inputBytes = inputBytes;
@@ -76,6 +79,7 @@
Information = information ?? throw new ArgumentNullException(nameof(information));
pages = new Pages(log, catalog, pageFactory, isLenientParsing, pdfScanner);
Structure = new Structure(catalog, crossReferenceTable, pdfScanner);
documentForm = new Lazy<AcroForm>(() => acroFormFactory.GetAcroForm(catalog));
}
/// <summary>
@@ -122,6 +126,20 @@
return pages.GetPage(pageNumber);
}
/// <summary>
/// Gets the form if this document contains one.
/// </summary>
/// <returns>An <see cref="AcroForm"/> from the document or <see langword="null"/> if not present.</returns>
internal AcroForm GetForm()
{
if (isDisposed)
{
throw new ObjectDisposedException("Cannot access the form after the document is disposed.");
}
return documentForm.Value;
}
/// <inheritdoc />
/// <summary>

View File

@@ -4,6 +4,8 @@
using Exceptions;
using Geometry;
using JetBrains.Annotations;
using Parser.Parts;
using Tokenization.Scanner;
using Tokens;
internal static class DictionaryTokenExtensions
@@ -68,6 +70,18 @@
return result;
}
public static bool TryGetOptionalTokenDirect<T>(this DictionaryToken token, NameToken name, IPdfTokenScanner scanner, out T result) where T : IToken
{
result = default(T);
if (token.TryGet(name, out var appearancesToken) && DirectObjectFinder.TryGet(appearancesToken, scanner, out T innerResult))
{
result = innerResult;
return true;
}
return false;
}
}
internal static class ArrayTokenExtensions