mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-23 20:53:39 +08:00
#24 add more information to the choice fields
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user