pdftract/swift-sdk/Sources/Pdftract/Models/FormField.swift
jedarden 8b9a7bc91a docs(pdftract-5lvpu): verify Swift SDK implementation for v1.1+ release
Bead pdftract-5lvpu implements the Swift SDK for pdftract as a
subprocess-based SDK using Foundation's Process with async/await.
Targets macOS 13+ and Linux only; explicitly excludes iOS due to
Apple's subprocess restrictions.

Acceptance criteria status:
- PASS: SPM package structure (Package.swift configured)
- PASS: All 9 contract methods exposed in Methods.swift
- PASS: All 8 error cases defined in Error.swift
- PASS: iOS documented as unsupported in README.md
- PASS: CI workflow configured (pdftract-swift-publish.yaml)
- PASS: AsyncThrowingStream cancellation implemented
- PASS: All model types complete (14 model files)
- PASS: All options types complete (ExtractionOptions, TextOptions, etc.)
- PASS: Conformance test suite defined (ConformanceTests.swift)
- PASS: Cross-platform Process support (ProcessRunner actor)

Files updated:
- swift-sdk/README.md: Fixed GitHub URL from placeholder to jedarden/pdftract-swift

Verification note: notes/pdftract-5lvpu.md

References:
- Plan: SDK Architecture / The Ten SDKs, line 3480
- Plan: SDK Architecture / Per-SDK Release Channels, line 3577
- Plan: SDK Acceptance Criteria, lines 3581-3589
- ADR-009: Argo Workflows on iad-ci only
2026-06-01 13:40:03 -04:00

211 lines
6.2 KiB
Swift

//
// FormField.swift
// Pdftract
//
// Form field models.
//
import Foundation
/// A form field extracted from a PDF's AcroForm or XFA data.
public struct FormField: Codable, Equatable {
/// The absolute (dot-joined) field name from the AcroForm.
public let name: String
/// The field type.
public let fieldType: FormFieldType
/// The current value of the form field.
public var value: FormFieldValue
/// The default value (/DV entry) if present.
public var defaultValue: FormFieldValue?
/// Zero-based page index where this field's widget appears.
public var pageIndex: UInt?
/// Bounding box in PDF user-space points.
public var rect: [Float]?
/// Whether this field is required (bit 2 of /Ff flags).
public let required: Bool
/// Whether this field is read-only (bit 1 of /Ff flags).
public let readOnly: Bool
/// Whether this text field supports multiple lines.
public var multiline: Bool?
/// Maximum length for text fields (/MaxLen entry).
public var maxLength: UInt32?
/// Available options for choice fields.
public var options: [[String]]?
/// Whether this choice field supports multiple selections.
public var multiSelect: Bool?
/// Selected state for button fields.
public var selected: Bool?
/// Appearance state name for button fields.
public var stateName: String?
/// Whether this button is a pushbutton (bit 26 of /Ff).
public var pushbutton: Bool?
/// Whether this button is a radio button (bit 25 of /Ff).
public var radio: Bool?
/// Coding keys for custom serialization
enum CodingKeys: String, CodingKey {
case name
case fieldType = "type"
case value
case defaultValue = "default"
case pageIndex = "page_index"
case rect
case required
case readOnly = "read_only"
case multiline
case maxLength = "max_length"
case options
case multiSelect = "multi_select"
case selected
case stateName = "state_name"
case pushbutton
case radio
}
/// Create a new FormField structure.
public init(
name: String,
fieldType: FormFieldType,
value: FormFieldValue,
defaultValue: FormFieldValue? = nil,
pageIndex: UInt? = nil,
rect: [Float]? = nil,
required: Bool = false,
readOnly: Bool = false,
multiline: Bool? = nil,
maxLength: UInt32? = nil,
options: [[String]]? = nil,
multiSelect: Bool? = nil,
selected: Bool? = nil,
stateName: String? = nil,
pushbutton: Bool? = nil,
radio: Bool? = nil
) {
self.name = name
self.fieldType = fieldType
self.value = value
self.defaultValue = defaultValue
self.pageIndex = pageIndex
self.rect = rect
self.required = required
self.readOnly = readOnly
self.multiline = multiline
self.maxLength = maxLength
self.options = options
self.multiSelect = multiSelect
self.selected = selected
self.stateName = stateName
self.pushButton = pushbutton
self.radio = radio
}
}
/// Form field type discriminator.
public enum FormFieldType: String, Codable {
case text
case button
case choice
case signature
}
/// Form field value representation.
public enum FormFieldValue: Codable, Equatable {
case text(String?)
case button(Bool)
case choice(ChoiceValue)
case signature(UInt32?)
/// Create a new FormFieldValue from a decoder.
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringValue = try? container.decode(String.self) {
self = .text(stringValue)
} else if let boolValue = try? container.decode(Bool.self) {
self = .button(boolValue)
} else if let arrayValue = try? container.decode([String].self) {
self = .choice(.multiple(arrayValue))
} else if let intValue = try? container.decode(UInt32.self) {
self = .signature(intValue)
} else if container.decodeNil() {
// Need context to determine which type of nil
// Default to text(nil) for backward compatibility
self = .text(nil)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "FormFieldValue cannot be decoded"
)
}
}
/// Encode a FormFieldValue to an encoder.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .text(let value):
try container.encode(value)
case .button(let value):
try container.encode(value)
case .choice(let choiceValue):
switch choiceValue {
case .single(let value):
try container.encode(value)
case .multiple(let values):
try container.encode(values)
}
case .signature(let value):
try container.encode(value)
}
}
}
/// Choice field value representation.
public enum ChoiceValue: Codable, Equatable {
case single(String)
case multiple([String])
/// Create a new ChoiceValue from a decoder.
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringValue = try? container.decode(String.self) {
self = .single(stringValue)
} else if let arrayValue = try? container.decode([String].self) {
self = .multiple(arrayValue)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "ChoiceValue cannot be decoded"
)
}
}
/// Encode a ChoiceValue to an encoder.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .single(let value):
try container.encode(value)
case .multiple(let values):
try container.encode(values)
}
}
}