mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-06-28 15:30:17 +08:00
wrap checkboxes and radiobuttons in their own form field types with access to the child collections
This commit is contained in:
parent
677d2b5e8f
commit
910e22a4e9
@ -43,16 +43,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Fact]
|
[Fact]
|
||||||
//public void GetFormFieldsByPage()
|
public void GetFormFieldsByPage()
|
||||||
//{
|
{
|
||||||
// using (var document = PdfDocument.Open(GetFilename(), ParsingOptions.LenientParsingOff))
|
using (var document = PdfDocument.Open(GetFilename(), ParsingOptions.LenientParsingOff))
|
||||||
// {
|
{
|
||||||
// var form = document.GetForm();
|
var form = document.GetForm();
|
||||||
// var fields = form.GetFieldsForPage(1).ToList();
|
var fields = form.GetFieldsForPage(1).ToList();
|
||||||
// var page = document.GetPage(1).ExperimentalAccess.GetAnnotations().ToList();
|
Assert.Equal(18, fields.Count);
|
||||||
// Assert.Equal(16, fields.Count);
|
}
|
||||||
// }
|
}
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
{
|
{
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroButtonFieldFlags",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroButtonFieldFlags",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroCheckboxField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroCheckboxField",
|
||||||
|
"UglyToad.PdfPig.AcroForms.Fields.AcroCheckboxesField",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroChoiceFieldFlags",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroChoiceFieldFlags",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroChoiceOption",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroChoiceOption",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroComboBoxField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroComboBoxField",
|
||||||
@ -50,6 +51,7 @@
|
|||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroListBoxField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroListBoxField",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroNonTerminalField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroNonTerminalField",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroPushButtonField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroPushButtonField",
|
||||||
|
"UglyToad.PdfPig.AcroForms.Fields.AcroRadioButtonField",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroRadioButtonsField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroRadioButtonsField",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroSignatureField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroSignatureField",
|
||||||
"UglyToad.PdfPig.AcroForms.Fields.AcroTextField",
|
"UglyToad.PdfPig.AcroForms.Fields.AcroTextField",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Fields;
|
using Fields;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
using Util.JetBrains.Annotations;
|
using Util.JetBrains.Annotations;
|
||||||
@ -39,7 +40,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="AcroForm"/>.
|
/// Create a new <see cref="AcroForm"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AcroForm(DictionaryToken dictionary, SignatureFlags signatureFlags, bool needAppearances,
|
public AcroForm(DictionaryToken dictionary, SignatureFlags signatureFlags, bool needAppearances,
|
||||||
IReadOnlyDictionary<IndirectReference, AcroFieldBase> fields)
|
IReadOnlyDictionary<IndirectReference, AcroFieldBase> fields)
|
||||||
{
|
{
|
||||||
Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
|
Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
|
||||||
@ -64,6 +65,11 @@
|
|||||||
{
|
{
|
||||||
yield return field.Value;
|
yield return field.Value;
|
||||||
}
|
}
|
||||||
|
else if (field.Value is AcroNonTerminalField parent
|
||||||
|
&& parent.Children.Any(x => x.PageNumber == pageNumber))
|
||||||
|
{
|
||||||
|
yield return field.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,15 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class AcroFormFactory
|
internal class AcroFormFactory
|
||||||
{
|
{
|
||||||
|
private static readonly HashSet<NameToken> InheritableFields = new HashSet<NameToken>
|
||||||
|
{
|
||||||
|
NameToken.Ft,
|
||||||
|
NameToken.Ff,
|
||||||
|
NameToken.V,
|
||||||
|
NameToken.Dv,
|
||||||
|
NameToken.Aa
|
||||||
|
};
|
||||||
|
|
||||||
private readonly IPdfTokenScanner tokenScanner;
|
private readonly IPdfTokenScanner tokenScanner;
|
||||||
private readonly IFilterProvider filterProvider;
|
private readonly IFilterProvider filterProvider;
|
||||||
|
|
||||||
@ -92,7 +101,7 @@
|
|||||||
|
|
||||||
var fieldDictionary = DirectObjectFinder.Get<DictionaryToken>(fieldToken, tokenScanner);
|
var fieldDictionary = DirectObjectFinder.Get<DictionaryToken>(fieldToken, tokenScanner);
|
||||||
|
|
||||||
var field = GetAcroField(fieldDictionary, catalog);
|
var field = GetAcroField(fieldDictionary, catalog, new List<DictionaryToken>(0));
|
||||||
|
|
||||||
fields[fieldReferenceToken.Data] = field;
|
fields[fieldReferenceToken.Data] = field;
|
||||||
}
|
}
|
||||||
@ -100,12 +109,15 @@
|
|||||||
return new AcroForm(acroDictionary, signatureFlags, needAppearances, fields);
|
return new AcroForm(acroDictionary, signatureFlags, needAppearances, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AcroFieldBase GetAcroField(DictionaryToken fieldDictionary, Catalog catalog)
|
private AcroFieldBase GetAcroField(DictionaryToken fieldDictionary, Catalog catalog,
|
||||||
|
IReadOnlyList<DictionaryToken> parentDictionaries)
|
||||||
{
|
{
|
||||||
fieldDictionary.TryGet(NameToken.Ft, out NameToken fieldType);
|
fieldDictionary = CreateInheritedDictionary(fieldDictionary, parentDictionaries);
|
||||||
fieldDictionary.TryGet(NameToken.Ff, out NumericToken fieldFlagsToken);
|
|
||||||
|
|
||||||
var kids = new List<DictionaryToken>();
|
fieldDictionary.TryGet(NameToken.Ft, tokenScanner, out NameToken fieldType);
|
||||||
|
fieldDictionary.TryGet(NameToken.Ff, tokenScanner, out NumericToken fieldFlagsToken);
|
||||||
|
|
||||||
|
var kids = new List<(bool hasParent, DictionaryToken dictionary)>();
|
||||||
|
|
||||||
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Kids, tokenScanner, out ArrayToken kidsToken))
|
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Kids, tokenScanner, out ArrayToken kidsToken))
|
||||||
{
|
{
|
||||||
@ -120,7 +132,8 @@
|
|||||||
|
|
||||||
if (kidObject.Data is DictionaryToken kidDictionaryToken)
|
if (kidObject.Data is DictionaryToken kidDictionaryToken)
|
||||||
{
|
{
|
||||||
kids.Add(kidDictionaryToken);
|
var hasParent = kidDictionaryToken.TryGet(NameToken.Parent, out IndirectReferenceToken _);
|
||||||
|
kids.Add((hasParent, kidDictionaryToken));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -147,19 +160,26 @@
|
|||||||
bounds = rectArray.ToRectangle();
|
bounds = rectArray.ToRectangle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var newParentDictionaries = new List<DictionaryToken>(parentDictionaries) {fieldDictionary};
|
||||||
|
|
||||||
|
var children = new List<AcroFieldBase>(kids.Count);
|
||||||
|
foreach (var kid in kids)
|
||||||
|
{
|
||||||
|
if (!kid.hasParent)
|
||||||
|
{
|
||||||
|
// Is a widget annotation dictionary.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
children.Add(GetAcroField(kid.dictionary, catalog, newParentDictionaries));
|
||||||
|
}
|
||||||
|
|
||||||
var fieldFlags = (uint) (fieldFlagsToken?.Long ?? 0);
|
var fieldFlags = (uint) (fieldFlagsToken?.Long ?? 0);
|
||||||
|
|
||||||
AcroFieldBase result;
|
AcroFieldBase result;
|
||||||
if (fieldType == null)
|
if (fieldType == null)
|
||||||
{
|
{
|
||||||
var children = new List<AcroFieldBase>();
|
result = new AcroNonTerminalField(fieldDictionary, "Non-Terminal Field", fieldFlags, information, AcroFieldType.Unknown, children);
|
||||||
foreach (var kid in kids)
|
|
||||||
{
|
|
||||||
var kidField = GetAcroField(kid, catalog);
|
|
||||||
children.Add(kidField);
|
|
||||||
}
|
|
||||||
|
|
||||||
result = new AcroNonTerminalField(fieldDictionary, "Non-Terminal Field", fieldFlags, information, children);
|
|
||||||
}
|
}
|
||||||
else if (fieldType == NameToken.Btn)
|
else if (fieldType == NameToken.Btn)
|
||||||
{
|
{
|
||||||
@ -167,10 +187,19 @@
|
|||||||
|
|
||||||
if (buttonFlags.HasFlag(AcroButtonFieldFlags.Radio))
|
if (buttonFlags.HasFlag(AcroButtonFieldFlags.Radio))
|
||||||
{
|
{
|
||||||
var field = new AcroRadioButtonsField(fieldDictionary, fieldType, buttonFlags, information,
|
if (children.Count > 0)
|
||||||
pageNumber,
|
{
|
||||||
bounds);
|
result = new AcroRadioButtonsField(fieldDictionary, fieldType, buttonFlags, information,
|
||||||
result = field;
|
children);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var field = new AcroRadioButtonField(fieldDictionary, fieldType, buttonFlags, information,
|
||||||
|
pageNumber,
|
||||||
|
bounds);
|
||||||
|
|
||||||
|
result = field;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (buttonFlags.HasFlag(AcroButtonFieldFlags.PushButton))
|
else if (buttonFlags.HasFlag(AcroButtonFieldFlags.PushButton))
|
||||||
{
|
{
|
||||||
@ -191,13 +220,21 @@
|
|||||||
isChecked = !string.Equals(valueToken.Data, NameToken.Off, StringComparison.OrdinalIgnoreCase);
|
isChecked = !string.Equals(valueToken.Data, NameToken.Off, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
var field = new AcroCheckboxField(fieldDictionary, fieldType, buttonFlags, information,
|
if (children.Count > 0)
|
||||||
valueToken,
|
{
|
||||||
isChecked,
|
result = new AcroCheckboxesField(fieldDictionary, fieldType, buttonFlags, information,
|
||||||
pageNumber,
|
children);
|
||||||
bounds);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var field = new AcroCheckboxField(fieldDictionary, fieldType, buttonFlags, information,
|
||||||
|
valueToken,
|
||||||
|
isChecked,
|
||||||
|
pageNumber,
|
||||||
|
bounds);
|
||||||
|
|
||||||
result = field;
|
result = field;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (fieldType == NameToken.Tx)
|
else if (fieldType == NameToken.Tx)
|
||||||
@ -403,6 +440,34 @@
|
|||||||
pageNumber,
|
pageNumber,
|
||||||
bounds);
|
bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DictionaryToken CreateInheritedDictionary(DictionaryToken fieldDictionary, IReadOnlyList<DictionaryToken> parents)
|
||||||
|
{
|
||||||
|
if (parents.Count == 0)
|
||||||
|
{
|
||||||
|
return fieldDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inheritedDictionary = new Dictionary<NameToken, IToken>();
|
||||||
|
foreach (var parent in parents)
|
||||||
|
{
|
||||||
|
foreach (var kvp in parent.Data)
|
||||||
|
{
|
||||||
|
var key = NameToken.Create(kvp.Key);
|
||||||
|
if (InheritableFields.Contains(key))
|
||||||
|
{
|
||||||
|
inheritedDictionary[key] = kvp.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in fieldDictionary.Data)
|
||||||
|
{
|
||||||
|
inheritedDictionary[NameToken.Create(kvp.Key)] = kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DictionaryToken(inheritedDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsChoiceSelected(IReadOnlyList<string> selectedOptionNames, IReadOnlyList<int> selectedOptionIndices, int index, string name)
|
private static bool IsChoiceSelected(IReadOnlyList<string> selectedOptionNames, IReadOnlyList<int> selectedOptionIndices, int index, string name)
|
||||||
{
|
{
|
||||||
|
24
src/UglyToad.PdfPig/AcroForms/Fields/AcroCheckboxesField.cs
Normal file
24
src/UglyToad.PdfPig/AcroForms/Fields/AcroCheckboxesField.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
namespace UglyToad.PdfPig.AcroForms.Fields
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Tokens;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// A set of related checkboxes.
|
||||||
|
/// </summary>
|
||||||
|
public class AcroCheckboxesField : AcroNonTerminalField
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="AcroCheckboxesField"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal AcroCheckboxesField(DictionaryToken dictionary, string fieldType, AcroButtonFieldFlags fieldFlags,
|
||||||
|
AcroFieldCommonInformation information,
|
||||||
|
IReadOnlyList<AcroFieldBase> children) :
|
||||||
|
base(dictionary, fieldType, (uint)fieldFlags, information,
|
||||||
|
AcroFieldType.Checkboxes, children)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,12 +10,20 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
PushButton,
|
PushButton,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// A set of checkboxes.
|
||||||
|
/// </summary>
|
||||||
|
Checkboxes,
|
||||||
|
/// <summary>
|
||||||
/// A checkbox which toggles between on and off states.
|
/// A checkbox which toggles between on and off states.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Checkbox,
|
Checkbox,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A set of radio buttons.
|
/// A set of radio buttons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
RadioButtons,
|
||||||
|
/// <summary>
|
||||||
|
/// A single radio button, as part of a set or on its own.
|
||||||
|
/// </summary>
|
||||||
RadioButton,
|
RadioButton,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A textbox allowing user input through the keyboard.
|
/// A textbox allowing user input through the keyboard.
|
||||||
@ -34,8 +42,8 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Signature,
|
Signature,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A field which acts as a container for other fields.
|
/// The field type wasn't specified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NonTerminal
|
Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,11 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="AcroNonTerminalField"/>.
|
/// Create a new <see cref="AcroNonTerminalField"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal AcroNonTerminalField(DictionaryToken dictionary, string fieldType, uint fieldFlags, AcroFieldCommonInformation information,
|
internal AcroNonTerminalField(DictionaryToken dictionary, string fieldType, uint fieldFlags,
|
||||||
|
AcroFieldCommonInformation information,
|
||||||
|
AcroFieldType acroFieldType,
|
||||||
IReadOnlyList<AcroFieldBase> children) :
|
IReadOnlyList<AcroFieldBase> children) :
|
||||||
base(dictionary, fieldType, fieldFlags, AcroFieldType.NonTerminal, information,
|
base(dictionary, fieldType, fieldFlags, acroFieldType, information,
|
||||||
null, null)
|
null, null)
|
||||||
{
|
{
|
||||||
Children = children ?? throw new ArgumentNullException(nameof(children));
|
Children = children ?? throw new ArgumentNullException(nameof(children));
|
||||||
|
30
src/UglyToad.PdfPig/AcroForms/Fields/AcroRadioButtonField.cs
Normal file
30
src/UglyToad.PdfPig/AcroForms/Fields/AcroRadioButtonField.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
namespace UglyToad.PdfPig.AcroForms.Fields
|
||||||
|
{
|
||||||
|
using Geometry;
|
||||||
|
using Tokens;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// A single radio button.
|
||||||
|
/// </summary>
|
||||||
|
public class AcroRadioButtonField : AcroFieldBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="AcroButtonFieldFlags"/> which define the behaviour of this button type.
|
||||||
|
/// </summary>
|
||||||
|
public AcroButtonFieldFlags Flags { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="AcroRadioButtonField"/>.
|
||||||
|
/// </summary>
|
||||||
|
public AcroRadioButtonField(DictionaryToken dictionary, string fieldType, AcroButtonFieldFlags fieldFlags,
|
||||||
|
AcroFieldCommonInformation information,
|
||||||
|
int? pageNumber,
|
||||||
|
PdfRectangle? bounds) :
|
||||||
|
base(dictionary, fieldType, (uint)fieldFlags, AcroFieldType.RadioButton, information, pageNumber, bounds)
|
||||||
|
{
|
||||||
|
Flags = fieldFlags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
namespace UglyToad.PdfPig.AcroForms.Fields
|
namespace UglyToad.PdfPig.AcroForms.Fields
|
||||||
{
|
{
|
||||||
using Geometry;
|
using System.Collections.Generic;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A set of radio buttons.
|
/// A set of radio buttons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AcroRadioButtonsField : AcroFieldBase
|
public class AcroRadioButtonsField : AcroNonTerminalField
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="AcroButtonFieldFlags"/> which define the behaviour of this button type.
|
/// The <see cref="AcroButtonFieldFlags"/> which define the behaviour of this button type.
|
||||||
@ -20,9 +20,8 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AcroRadioButtonsField(DictionaryToken dictionary, string fieldType, AcroButtonFieldFlags fieldFlags,
|
public AcroRadioButtonsField(DictionaryToken dictionary, string fieldType, AcroButtonFieldFlags fieldFlags,
|
||||||
AcroFieldCommonInformation information,
|
AcroFieldCommonInformation information,
|
||||||
int? pageNumber,
|
IReadOnlyList<AcroFieldBase> children) :
|
||||||
PdfRectangle? bounds) :
|
base(dictionary, fieldType, (uint)fieldFlags, information, AcroFieldType.RadioButtons, children)
|
||||||
base(dictionary, fieldType, (uint)fieldFlags, AcroFieldType.RadioButton, information, pageNumber, bounds)
|
|
||||||
{
|
{
|
||||||
Flags = fieldFlags;
|
Flags = fieldFlags;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user