formFields method
Implementation
Future<List<UPdfFormField>> formFields() async {
final Object? acroForm = await document.resolve(document.catalog?["AcroForm"]);
if (acroForm is! UPdfDict) return const <UPdfFormField>[];
final Object? fields = await document.resolve(acroForm["Fields"]);
if (fields is! List<Object?>) return const <UPdfFormField>[];
final List<UPdfFormField> out = <UPdfFormField>[];
final Map<int, int> pageOf = <int, int>{};
for (int index = 0; index < document.pageCount && index < 4096; index++) {
final UPdfPage? page = await document.page(index);
final Object? annots = await document.resolve(page?.dict["Annots"]);
if (annots is! List<Object?>) continue;
for (final Object? entry in annots) {
if (entry is UPdfRef) pageOf[entry.number] = index;
}
}
Future<void> walk(Object? entry, String prefix, int depth) async {
if (depth > 12 || out.length > 4096) return;
final Object? field = entry is UPdfRef ? await current(entry) : await document.resolve(entry);
if (field is! UPdfDict) return;
final Object? partial = await document.resolve(field["T"]);
final String name = partial is UPdfString ? (prefix.isEmpty ? partial.text : "$prefix.${partial.text}") : prefix;
final Object? kids = await document.resolve(field["Kids"]);
if (kids is List<Object?> && kids.isNotEmpty) {
for (final Object? kid in kids) {
await walk(kid, name, depth + 1);
}
return;
}
final Object? typeObject = await document.resolve(field["FT"]);
final String type = typeObject is UPdfName ? typeObject.value : "";
final Object? flagsObject = await document.resolve(field["Ff"]);
final int flags = flagsObject is num ? flagsObject.toInt() : 0;
UDocFieldKind kind = UDocFieldKind.unknown;
if (type == "Tx") kind = UDocFieldKind.text;
if (type == "Btn") kind = flags & 65536 != 0 ? UDocFieldKind.pushButton : (flags & 32768 != 0 ? UDocFieldKind.radioButton : UDocFieldKind.checkBox);
if (type == "Ch") kind = flags & 131072 != 0 ? UDocFieldKind.comboBox : UDocFieldKind.listBox;
if (type == "Sig") kind = UDocFieldKind.signature;
final Object? rectObject = await document.resolve(field["Rect"]);
final List<double> numbers = <double>[];
if (rectObject is List<Object?>) {
for (final Object? value in rectObject) {
if (value is num) numbers.add(value.toDouble());
}
}
final Object? valueObject = await document.resolve(field["V"]);
Object? value;
if (valueObject is UPdfString) value = valueObject.text;
if (valueObject is UPdfName) value = valueObject.value;
if (valueObject is num) value = valueObject;
final List<String> options = <String>[];
final Object? optionsObject = await document.resolve(field["Opt"]);
if (optionsObject is List<Object?>) {
for (final Object? option in optionsObject) {
final Object? resolved = await document.resolve(option);
if (resolved is UPdfString) options.add(resolved.text);
if (resolved is List<Object?> && resolved.isNotEmpty) {
final Object? label = await document.resolve(resolved.last);
if (label is UPdfString) options.add(label.text);
}
}
}
final Object? maxLengthObject = await document.resolve(field["MaxLen"]);
out.add(
UPdfFormField(
objectNumber: entry is UPdfRef ? entry.number : -1,
name: name,
kind: kind,
pageIndex: entry is UPdfRef ? (pageOf[entry.number] ?? 0) : 0,
rect: numbers.length >= 4 ? Rect.fromLTRB(min(numbers[0], numbers[2]), min(numbers[1], numbers[3]), max(numbers[0], numbers[2]), max(numbers[1], numbers[3])) : Rect.zero,
value: value,
options: options,
readOnly: flags & 1 != 0,
required: flags & 2 != 0,
maxLength: maxLengthObject is num ? maxLengthObject.toInt() : 0,
),
);
}
for (final Object? entry in fields) {
await walk(entry, "", 0);
}
return out;
}