mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-21 04:17:57 +08:00
handle checked state of radio buttons and checkboxes
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AcroForms.Fields;
|
||||
using Xunit;
|
||||
|
||||
public class AcroFormsBasicFieldsTests
|
||||
@@ -53,5 +54,38 @@
|
||||
Assert.Equal(18, fields.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetsRadioButtonState()
|
||||
{
|
||||
using (var document = PdfDocument.Open(GetFilename(), ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
var form = document.GetForm();
|
||||
var radioButtons = form.Fields.OfType<AcroRadioButtonsField>().ToList();
|
||||
|
||||
Assert.Equal(2, radioButtons.Count);
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
var ordered = radioButtons.OrderBy(x => x.Children.Min(y => y.Bounds.Value.Left)).ToList();
|
||||
|
||||
var left = ordered[0];
|
||||
|
||||
Assert.Equal(2, left.Children.Count);
|
||||
foreach (var acroFieldBase in left.Children)
|
||||
{
|
||||
var button = Assert.IsType<AcroRadioButtonField>(acroFieldBase);
|
||||
Assert.False(button.IsSelected);
|
||||
}
|
||||
|
||||
var right = ordered[1];
|
||||
Assert.Equal(2, right.Children.Count);
|
||||
|
||||
var buttonOn = Assert.IsType<AcroRadioButtonField>(right.Children[0]);
|
||||
Assert.True(buttonOn.IsSelected);
|
||||
|
||||
var buttonOff = Assert.IsType<AcroRadioButtonField>(right.Children[1]);
|
||||
Assert.False(buttonOff.IsSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@
|
||||
/// </remarks>
|
||||
internal class AcroForm
|
||||
{
|
||||
private readonly IReadOnlyDictionary<IndirectReference, AcroFieldBase> fieldsWithReferences;
|
||||
|
||||
/// <summary>
|
||||
/// The raw PDF dictionary which is the root form object.
|
||||
/// </summary>
|
||||
@@ -33,20 +35,21 @@
|
||||
public bool NeedAppearances { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All root fields in this form with their corresponding references.
|
||||
/// All root fields in this form.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<IndirectReference, AcroFieldBase> Fields { get; }
|
||||
public IReadOnlyList<AcroFieldBase> Fields { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AcroForm"/>.
|
||||
/// </summary>
|
||||
public AcroForm(DictionaryToken dictionary, SignatureFlags signatureFlags, bool needAppearances,
|
||||
IReadOnlyDictionary<IndirectReference, AcroFieldBase> fields)
|
||||
IReadOnlyDictionary<IndirectReference, AcroFieldBase> fieldsWithReferences)
|
||||
{
|
||||
Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
|
||||
SignatureFlags = signatureFlags;
|
||||
NeedAppearances = needAppearances;
|
||||
Fields = fields ?? throw new ArgumentNullException(nameof(fields));
|
||||
this.fieldsWithReferences = fieldsWithReferences ?? throw new ArgumentNullException(nameof(fieldsWithReferences));
|
||||
Fields = fieldsWithReferences.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,14 +64,14 @@
|
||||
|
||||
foreach (var field in Fields)
|
||||
{
|
||||
if (field.Value.PageNumber == pageNumber)
|
||||
if (field.PageNumber == pageNumber)
|
||||
{
|
||||
yield return field.Value;
|
||||
yield return field;
|
||||
}
|
||||
else if (field.Value is AcroNonTerminalField parent
|
||||
else if (field is AcroNonTerminalField parent
|
||||
&& parent.Children.Any(x => x.PageNumber == pageNumber))
|
||||
{
|
||||
yield return field.Value;
|
||||
yield return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -112,7 +112,9 @@
|
||||
private AcroFieldBase GetAcroField(DictionaryToken fieldDictionary, Catalog catalog,
|
||||
IReadOnlyList<DictionaryToken> parentDictionaries)
|
||||
{
|
||||
fieldDictionary = CreateInheritedDictionary(fieldDictionary, parentDictionaries);
|
||||
var (combinedFieldDictionary, inheritsValue) = CreateInheritedDictionary(fieldDictionary, parentDictionaries);
|
||||
|
||||
fieldDictionary = combinedFieldDictionary;
|
||||
|
||||
fieldDictionary.TryGet(NameToken.Ft, tokenScanner, out NameToken fieldType);
|
||||
fieldDictionary.TryGet(NameToken.Ff, tokenScanner, out NumericToken fieldFlagsToken);
|
||||
@@ -194,9 +196,13 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
var (isChecked, valueToken) = GetCheckedState(fieldDictionary, inheritsValue);
|
||||
|
||||
var field = new AcroRadioButtonField(fieldDictionary, fieldType, buttonFlags, information,
|
||||
pageNumber,
|
||||
bounds);
|
||||
bounds,
|
||||
valueToken,
|
||||
isChecked);
|
||||
|
||||
result = field;
|
||||
}
|
||||
@@ -210,16 +216,6 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
var isChecked = false;
|
||||
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
|
||||
{
|
||||
valueToken = NameToken.Off;
|
||||
}
|
||||
else
|
||||
{
|
||||
isChecked = !string.Equals(valueToken.Data, NameToken.Off, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (children.Count > 0)
|
||||
{
|
||||
result = new AcroCheckboxesField(fieldDictionary, fieldType, buttonFlags, information,
|
||||
@@ -227,6 +223,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
var (isChecked, valueToken) = GetCheckedState(fieldDictionary, inheritsValue);
|
||||
var field = new AcroCheckboxField(fieldDictionary, fieldType, buttonFlags, information,
|
||||
valueToken,
|
||||
isChecked,
|
||||
@@ -441,13 +438,38 @@
|
||||
bounds);
|
||||
}
|
||||
|
||||
private static DictionaryToken CreateInheritedDictionary(DictionaryToken fieldDictionary, IReadOnlyList<DictionaryToken> parents)
|
||||
private (bool isChecked, NameToken stateName) GetCheckedState(DictionaryToken fieldDictionary, bool inheritsValue)
|
||||
{
|
||||
var isChecked = false;
|
||||
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
|
||||
{
|
||||
valueToken = NameToken.Off;
|
||||
}
|
||||
else if (inheritsValue && fieldDictionary.TryGet(NameToken.As, tokenScanner, out NameToken appearanceStateName))
|
||||
{
|
||||
// The parent field's V entry holds a name object corresponding to the
|
||||
// appearance state of whichever child field is currently in the on state.
|
||||
isChecked = appearanceStateName.Equals(valueToken);
|
||||
valueToken = appearanceStateName;
|
||||
}
|
||||
else
|
||||
{
|
||||
isChecked = !string.Equals(valueToken.Data, NameToken.Off, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return (isChecked, valueToken);
|
||||
}
|
||||
|
||||
private static (DictionaryToken dictionary, bool inheritsValue) CreateInheritedDictionary(DictionaryToken fieldDictionary,
|
||||
IReadOnlyList<DictionaryToken> parents)
|
||||
{
|
||||
if (parents.Count == 0)
|
||||
{
|
||||
return fieldDictionary;
|
||||
return (fieldDictionary, false);
|
||||
}
|
||||
|
||||
var inheritsValue = false;
|
||||
|
||||
var inheritedDictionary = new Dictionary<NameToken, IToken>();
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
@@ -457,16 +479,26 @@
|
||||
if (InheritableFields.Contains(key))
|
||||
{
|
||||
inheritedDictionary[key] = kvp.Value;
|
||||
|
||||
if (NameToken.V.Equals(key))
|
||||
{
|
||||
inheritsValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in fieldDictionary.Data)
|
||||
{
|
||||
inheritedDictionary[NameToken.Create(kvp.Key)] = kvp.Value;
|
||||
var key = NameToken.Create(kvp.Key);
|
||||
inheritedDictionary[key] = kvp.Value;
|
||||
if (NameToken.V.Equals(key))
|
||||
{
|
||||
inheritsValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
return new DictionaryToken(inheritedDictionary);
|
||||
return (new DictionaryToken(inheritedDictionary), inheritsValue);
|
||||
}
|
||||
|
||||
private static bool IsChoiceSelected(IReadOnlyList<string> selectedOptionNames, IReadOnlyList<int> selectedOptionIndices, int index, string name)
|
||||
|
@@ -14,6 +14,16 @@
|
||||
/// </summary>
|
||||
public AcroButtonFieldFlags Flags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current value of this radio button.
|
||||
/// </summary>
|
||||
public NameToken CurrentValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the radio button is currently on/active.
|
||||
/// </summary>
|
||||
public bool IsSelected { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AcroRadioButtonField"/>.
|
||||
@@ -21,10 +31,14 @@
|
||||
public AcroRadioButtonField(DictionaryToken dictionary, string fieldType, AcroButtonFieldFlags fieldFlags,
|
||||
AcroFieldCommonInformation information,
|
||||
int? pageNumber,
|
||||
PdfRectangle? bounds) :
|
||||
PdfRectangle? bounds,
|
||||
NameToken currentValue,
|
||||
bool isSelected) :
|
||||
base(dictionary, fieldType, (uint)fieldFlags, AcroFieldType.RadioButton, information, pageNumber, bounds)
|
||||
{
|
||||
Flags = fieldFlags;
|
||||
CurrentValue = currentValue;
|
||||
IsSelected = isSelected;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user