formFields method

Future<List<UPdfFormField>> formFields()

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;
}