#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,76 +200,7 @@
} }
else if (fieldType == NameToken.Ch) else if (fieldType == NameToken.Ch)
{ {
var options = new List<AcroChoiceOption>(); result = GetChoiceField(fieldDictionary, fieldType, fieldFlags, information);
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Opt, tokenScanner, out ArrayToken optionsArrayToken))
{
for (var i = 0; i < optionsArrayToken.Data.Count; i++)
{
var optionToken = optionsArrayToken.Data[i];
if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out StringToken optionStringToken))
{
options.Add(new AcroChoiceOption(i, optionStringToken.Data));
}
else if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out HexToken optionHexToken))
{
options.Add(new AcroChoiceOption(i, optionHexToken.Data));
}
else if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out ArrayToken optionArrayToken))
{
if (optionArrayToken.Length != 2)
{
throw new PdfDocumentFormatException($"An option array containing array elements should contain 2 strings, instead got: {optionArrayToken}.");
}
string exportValue;
if (DirectObjectFinder.TryGet(optionArrayToken.Data[0], tokenScanner, out StringToken exportValueStringToken))
{
exportValue = exportValueStringToken.Data;
}
else if (DirectObjectFinder.TryGet(optionArrayToken.Data[0], tokenScanner, out HexToken exportValueHexToken))
{
exportValue = exportValueHexToken.Data;
}
else
{
throw new PdfDocumentFormatException($"An option array array element's first value should be the export value string, instead got: {optionArrayToken.Data[0]}.");
}
string name;
if (DirectObjectFinder.TryGet(optionArrayToken.Data[1], tokenScanner, out StringToken nameStringToken))
{
name = nameStringToken.Data;
}
else if (DirectObjectFinder.TryGet(optionArrayToken.Data[1], tokenScanner, out HexToken nameHexToken))
{
name = nameHexToken.Data;
}
else
{
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));
}
else
{
throw new PdfDocumentFormatException($"An option array should contain either strings or 2 element arrays, instead got: {optionToken}.");
}
}
}
var choiceFlags = (AcroChoiceFieldFlags)fieldFlags;
if (choiceFlags.HasFlag(AcroChoiceFieldFlags.Combo))
{
var field = new AcroComboBoxField(fieldDictionary, fieldType, choiceFlags, information);
result = field;
}
else
{
var field = new AcroListBoxField(fieldDictionary, fieldType, choiceFlags, information, options);
result = field;
}
} }
else if (fieldType == NameToken.Sig) else if (fieldType == NameToken.Sig)
{ {
@@ -282,5 +214,162 @@
return result; 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>();
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.Opt, tokenScanner, out ArrayToken optionsArrayToken))
{
for (var i = 0; i < optionsArrayToken.Data.Count; i++)
{
var optionToken = optionsArrayToken.Data[i];
if (DirectObjectFinder.TryGet(optionToken, tokenScanner, out StringToken optionStringToken))
{
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))
{
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))
{
if (optionArrayToken.Length != 2)
{
throw new PdfDocumentFormatException($"An option array containing array elements should contain 2 strings, instead got: {optionArrayToken}.");
}
string exportValue;
if (DirectObjectFinder.TryGet(optionArrayToken.Data[0], tokenScanner, out StringToken exportValueStringToken))
{
exportValue = exportValueStringToken.Data;
}
else if (DirectObjectFinder.TryGet(optionArrayToken.Data[0], tokenScanner, out HexToken exportValueHexToken))
{
exportValue = exportValueHexToken.Data;
}
else
{
throw new PdfDocumentFormatException($"An option array array element's first value should be the export value string, instead got: {optionArrayToken.Data[0]}.");
}
string name;
if (DirectObjectFinder.TryGet(optionArrayToken.Data[1], tokenScanner, out StringToken nameStringToken))
{
name = nameStringToken.Data;
}
else if (DirectObjectFinder.TryGet(optionArrayToken.Data[1], tokenScanner, out HexToken nameHexToken))
{
name = nameHexToken.Data;
}
else
{
throw new PdfDocumentFormatException($"An option array array element's second value should be the option name string, instead got: {optionArrayToken.Data[1]}.");
}
var isSelected = IsChoiceSelected(selectedOptions, selectedIndices, i, name);
options.Add(new AcroChoiceOption(i, isSelected, name, exportValue));
}
else
{
throw new PdfDocumentFormatException($"An option array should contain either strings or 2 element arrays, instead got: {optionToken}.");
}
}
}
var choiceFlags = (AcroChoiceFieldFlags)fieldFlags;
if (choiceFlags.HasFlag(AcroChoiceFieldFlags.Combo))
{
var field = new AcroComboBoxField(fieldDictionary, fieldType, choiceFlags, information, options, selectedOptions, selectedIndices);
return field;
}
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; }
public AcroComboBoxField(DictionaryToken dictionary, string fieldType, AcroChoiceFieldFlags fieldFlags, /// <summary>
AcroFieldCommonInformation information) : /// 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,
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;
} }