#24 add more information to the choice fields

This commit is contained in:
Eliot Jones
2019-01-04 21:58:03 +00:00
parent 4a67b51d68
commit b9f5a6da8c
2 changed files with 245 additions and 74 deletions

View File

@@ -2,6 +2,7 @@
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content; using Content;
using Exceptions; using Exceptions;
using Fields; using Fields;
@@ -199,6 +200,66 @@
} }
else if (fieldType == NameToken.Ch) else if (fieldType == NameToken.Ch)
{ {
result = GetChoiceField(fieldDictionary, fieldType, fieldFlags, information);
}
else if (fieldType == NameToken.Sig)
{
var field = new AcroSignatureField(fieldDictionary, fieldType, fieldFlags, information);
result = field;
}
else
{
throw new PdfDocumentFormatException($"Unexpected type for field in AcroForm: {fieldType}.");
}
return result;
}
private AcroFieldBase GetChoiceField(DictionaryToken fieldDictionary, NameToken fieldType, uint fieldFlags, AcroFieldCommonInformation information)
{
var selectedOptions = Array.Empty<string>();
if (fieldDictionary.TryGet(NameToken.V, out var valueToken))
{
if (DirectObjectFinder.TryGet(valueToken, tokenScanner, out StringToken valueString))
{
selectedOptions = new[] {valueString.Data};
}
else if (DirectObjectFinder.TryGet(valueToken, tokenScanner, out HexToken valueHex))
{
selectedOptions = new[] {valueHex.Data};
}
else if (DirectObjectFinder.TryGet(valueToken, tokenScanner, out ArrayToken valueArray))
{
selectedOptions = new string[valueArray.Length];
for (var i = 0; i < valueArray.Length; i++)
{
var valueOptToken = valueArray.Data[i];
if (DirectObjectFinder.TryGet(valueOptToken, tokenScanner, out StringToken valueOptString))
{
selectedOptions[i] = valueOptString.Data;
}
else if (DirectObjectFinder.TryGet(valueOptToken, tokenScanner, out HexToken valueOptHex))
{
selectedOptions[i] = valueOptHex.Data;
}
}
}
}
var selectedIndices = default(int[]);
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.I, tokenScanner, out ArrayToken indicesArray))
{
selectedIndices = new int[indicesArray.Length];
for (var i = 0; i < indicesArray.Data.Count; i++)
{
var token = indicesArray.Data[i];
var numericToken = DirectObjectFinder.Get<NumericToken>(token, tokenScanner);
selectedIndices[i] = numericToken.Int;
}
}
var options = new List<AcroChoiceOption>(); var options = new List<AcroChoiceOption>();
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Opt, tokenScanner, out ArrayToken optionsArrayToken)) if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Opt, tokenScanner, out ArrayToken optionsArrayToken))
{ {
@@ -207,11 +268,15 @@
var optionToken = optionsArrayToken.Data[i]; var optionToken = optionsArrayToken.Data[i];
if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out StringToken optionStringToken)) if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out StringToken optionStringToken))
{ {
options.Add(new AcroChoiceOption(i, optionStringToken.Data)); var name = optionStringToken.Data;
var isSelected = IsChoiceSelected(selectedOptions, selectedIndices, i, name);
options.Add(new AcroChoiceOption(i, isSelected, optionStringToken.Data));
} }
else if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out HexToken optionHexToken)) else if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out HexToken optionHexToken))
{ {
options.Add(new AcroChoiceOption(i, optionHexToken.Data)); var name = optionHexToken.Data;
var isSelected = IsChoiceSelected(selectedOptions, selectedIndices, i, name);
options.Add(new AcroChoiceOption(i, isSelected, optionHexToken.Data));
} }
else if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out ArrayToken optionArrayToken)) else if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out ArrayToken optionArrayToken))
{ {
@@ -248,7 +313,8 @@
throw new PdfDocumentFormatException($"An option array array element's second value should be the option name string, instead got: {optionArrayToken.Data[1]}."); throw new PdfDocumentFormatException($"An option array array element's second value should be the option name string, instead got: {optionArrayToken.Data[1]}.");
} }
options.Add(new AcroChoiceOption(i, name, exportValue)); var isSelected = IsChoiceSelected(selectedOptions, selectedIndices, i, name);
options.Add(new AcroChoiceOption(i, isSelected, name, exportValue));
} }
else else
{ {
@@ -261,26 +327,49 @@
if (choiceFlags.HasFlag(AcroChoiceFieldFlags.Combo)) if (choiceFlags.HasFlag(AcroChoiceFieldFlags.Combo))
{ {
var field = new AcroComboBoxField(fieldDictionary, fieldType, choiceFlags, information); var field = new AcroComboBoxField(fieldDictionary, fieldType, choiceFlags, information, options, selectedOptions, selectedIndices);
result = field; return field;
}
else
{
var field = new AcroListBoxField(fieldDictionary, fieldType, choiceFlags, information, options);
result = field;
}
}
else if (fieldType == NameToken.Sig)
{
var field = new AcroSignatureField(fieldDictionary, fieldType, fieldFlags, information);
result = field;
}
else
{
throw new PdfDocumentFormatException($"Unexpected type for field in AcroForm: {fieldType}.");
} }
return result; var topIndex = default(int?);
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Ti, tokenScanner, out NumericToken topIndexToken))
{
topIndex = topIndexToken.Int;
}
return new AcroListBoxField(fieldDictionary, fieldType, choiceFlags, information, options, selectedOptions, selectedIndices, topIndex);
}
private static bool IsChoiceSelected(IReadOnlyList<string> selectedOptionNames, IReadOnlyList<int> selectedOptionIndices, int index, string name)
{
if (selectedOptionNames.Count == 0)
{
return false;
}
for (var i = 0; i < selectedOptionNames.Count; i++)
{
var optionName = selectedOptionNames[i];
if (optionName != name)
{
continue;
}
if (selectedOptionIndices == null)
{
return true;
}
if (selectedOptionIndices.Contains(index))
{
return true;
}
return false;
}
return false;
} }
} }
} }

View File

@@ -110,30 +110,106 @@
} }
} }
/// <inheritdoc />
/// <summary>
/// A scrollable list box field.
/// </summary>
internal class AcroListBoxField : AcroFieldBase internal class AcroListBoxField : AcroFieldBase
{ {
/// <summary>
/// The flags specifying the behaviour of this field.
/// </summary>
public AcroChoiceFieldFlags Flags { get; } public AcroChoiceFieldFlags Flags { get; }
/// <summary>
/// The options to be presented to the user.
/// </summary>
[NotNull]
public IReadOnlyList<AcroChoiceOption> Options { get; } public IReadOnlyList<AcroChoiceOption> Options { get; }
/// <summary>
/// The names of any currently selected options.
/// </summary>
[NotNull]
public IReadOnlyList<string> SelectedOptions { get; }
/// <summary>
/// For multiple select lists with duplicate names gives the indices of the selected options.
/// </summary>
[CanBeNull]
public IReadOnlyList<int> SelectedOptionIndices { get; }
/// <summary>
/// For scrollable list boxes gives the index of the first visible option.
/// </summary>
public int TopIndex { get; }
/// <summary>
/// Whether the field allows multiple selections.
/// </summary>
public bool SupportsMultiSelect => Flags.Equals(AcroChoiceFieldFlags.MultiSelect);
/// <summary>
/// Create a new <see cref="AcroListBoxField"/>.
/// </summary>
/// <param name="dictionary">The dictionary for this field.</param>
/// <param name="fieldType">The type of this field, must be <see cref="NameToken.Ch"/>.</param>
/// <param name="fieldFlags">The flags specifying behaviour for this field.</param>
/// <param name="information">Additional information for this field.</param>
/// <param name="options">The options in this field.</param>
/// <param name="selectedOptionIndices"></param>
/// <param name="topIndex">The first visible option index.</param>
/// <param name="selectedOptions"></param>
public AcroListBoxField(DictionaryToken dictionary, string fieldType, AcroChoiceFieldFlags fieldFlags, public AcroListBoxField(DictionaryToken dictionary, string fieldType, AcroChoiceFieldFlags fieldFlags,
AcroFieldCommonInformation information, IReadOnlyList<AcroChoiceOption> options) : AcroFieldCommonInformation information, IReadOnlyList<AcroChoiceOption> options,
IReadOnlyList<string> selectedOptions,
IReadOnlyList<int> selectedOptionIndices,
int? topIndex) :
base(dictionary, fieldType, (uint)fieldFlags, information) base(dictionary, fieldType, (uint)fieldFlags, information)
{ {
Flags = fieldFlags; Flags = fieldFlags;
Options = options ?? throw new ArgumentNullException(nameof(options)); Options = options ?? throw new ArgumentNullException(nameof(options));
SelectedOptions = selectedOptions ?? throw new ArgumentNullException(nameof(selectedOptions));
SelectedOptionIndices = selectedOptionIndices;
TopIndex = topIndex ?? 0;
} }
} }
internal class AcroComboBoxField : AcroFieldBase internal class AcroComboBoxField : AcroFieldBase
{ {
/// <summary>
/// The flags specifying the behaviour of this field.
/// </summary>
public AcroChoiceFieldFlags Flags { get; } public AcroChoiceFieldFlags Flags { get; }
/// <summary>
/// The options to be presented to the user.
/// </summary>
[NotNull]
public IReadOnlyList<AcroChoiceOption> Options { get; }
/// <summary>
/// The names of any currently selected options.
/// </summary>
[NotNull]
public IReadOnlyList<string> SelectedOptions { get; }
/// <summary>
/// For multiple select lists with duplicate names gives the indices of the selected options.
/// </summary>
[CanBeNull]
public IReadOnlyList<int> SelectedOptionIndices { get; }
public AcroComboBoxField(DictionaryToken dictionary, string fieldType, AcroChoiceFieldFlags fieldFlags, public AcroComboBoxField(DictionaryToken dictionary, string fieldType, AcroChoiceFieldFlags fieldFlags,
AcroFieldCommonInformation information) : AcroFieldCommonInformation information, IReadOnlyList<AcroChoiceOption> options,
IReadOnlyList<string> selectedOptions,
IReadOnlyList<int> selectedOptionIndices) :
base(dictionary, fieldType, (uint)fieldFlags, information) base(dictionary, fieldType, (uint)fieldFlags, information)
{ {
Flags = fieldFlags; Flags = fieldFlags;
Options = options ?? throw new ArgumentNullException(nameof(options));
SelectedOptions = selectedOptions ?? throw new ArgumentNullException(nameof(selectedOptions));
SelectedOptionIndices = selectedOptionIndices;
} }
} }
@@ -147,6 +223,11 @@
/// </summary> /// </summary>
public int Index { get; } public int Index { get; }
/// <summary>
/// Whether this option is selected.
/// </summary>
public bool IsSelected { get; }
/// <summary> /// <summary>
/// The text of the option. /// The text of the option.
/// </summary> /// </summary>
@@ -160,9 +241,10 @@
/// <summary> /// <summary>
/// Create a new <see cref="AcroChoiceOption"/>. /// Create a new <see cref="AcroChoiceOption"/>.
/// </summary> /// </summary>
public AcroChoiceOption(int index, string name, string exportValue = null) public AcroChoiceOption(int index, bool isSelected, string name, string exportValue = null)
{ {
Index = index; Index = index;
IsSelected = isSelected;
Name = name; Name = name;
ExportValue = exportValue; ExportValue = exportValue;
} }