pdftract/swift-sdk/Sources/Pdftract/Models/Attachment.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

218 lines
6.1 KiB
Swift

//
// Attachment.swift
// Pdftract
//
// Attachment models.
//
import Foundation
/// An embedded file attachment extracted from a PDF.
public struct Attachment: Codable, Equatable {
/// Attachment filename from /UF (Unicode, preferred) or /F (system-independent).
public let name: String
/// Description from /Desc (None if absent, not empty string).
public var description: String?
/// MIME type from stream /Subtype (None if absent, no guessing from extension).
public var mimeType: String?
/// Original decoded size in bytes (always populated, even when truncated).
public let size: UInt64
/// Creation date from /Params /CreationDate as ISO 8601 string (None if absent).
public var created: String?
/// Modification date from /Params /ModDate as ISO 8601 string (None if absent).
public var modified: String?
/// MD5 checksum from /Params /CheckSum as hex string (None if absent).
public var checksumMd5: String?
/// Base64-encoded attachment content (null if truncated or empty).
public var data: String?
/// Whether the attachment content was truncated due to the 50 MB size limit.
public let truncated: Bool
/// Coding keys for custom serialization
enum CodingKeys: String, CodingKey {
case name
case description
case mimeType = "mime_type"
case size
case created
case modified
case checksumMd5 = "checksum_md5"
case data
case truncated
}
/// Create a new Attachment structure.
public init(
name: String,
description: String? = nil,
mimeType: String? = nil,
size: UInt64,
created: String? = nil,
modified: String? = nil,
checksumMd5: String? = nil,
data: String? = nil,
truncated: Bool = false
) {
self.name = name
self.description = description
self.mimeType = mimeType
self.size = size
self.created = created
self.modified = modified
self.checksumMd5 = checksumMd5
self.data = data
self.truncated = truncated
}
}
/// An article thread extracted from the PDF's /Threads array.
public struct Thread: Codable, Equatable {
/// Thread title from /I/Title.
public var title: String?
/// Thread author from /I/Author.
public var author: String?
/// Thread subject from /I/Subject.
public var subject: String?
/// Thread keywords from /I/Keywords.
public var keywords: String?
/// Beads in this thread chain, in traversal order.
public var beads: [Bead]
/// Create a new Thread structure.
public init(
title: String? = nil,
author: String? = nil,
subject: String? = nil,
keywords: String? = nil,
beads: [Bead] = []
) {
self.title = title
self.author = author
self.subject = subject
self.keywords = keywords
self.beads = beads
}
}
/// A single bead in an article thread chain.
public struct Bead: Codable, Equatable {
/// 0-based page index where this bead is located.
public let pageIndex: UInt
/// Bounding rectangle in PDF user-space coordinates [x0, y0, x1, y1].
public let rect: [Float]
/// Coding keys for custom serialization
enum CodingKeys: String, CodingKey {
case pageIndex = "page_index"
case rect
}
/// Create a new Bead structure.
public init(pageIndex: UInt, rect: [Float]) {
self.pageIndex = pageIndex
self.rect = rect
}
}
/// An outline node (bookmark) from the document's outline hierarchy.
public struct OutlineNode: Codable, Equatable {
/// The outline title text (decoded to UTF-8).
public let title: String
/// Hierarchical level in the outline tree (0-based, root is 0).
public let level: UInt8
/// Zero-based page index this outline points to, if resolved.
public var pageIndex: UInt32?
/// Destination type and coordinates within the page.
public var destination: Destination?
/// Nested child outlines (empty array for leaf nodes).
public var children: [OutlineNode]
/// Coding keys for custom serialization
enum CodingKeys: String, CodingKey {
case title
case level
case pageIndex = "page_index"
case destination = "dest"
case children
}
/// Create a new OutlineNode structure.
public init(
title: String,
level: UInt8 = 0,
pageIndex: UInt32? = nil,
destination: Destination? = nil,
children: [OutlineNode] = []
) {
self.title = title
self.level = level
self.pageIndex = pageIndex
self.destination = destination
self.children = children
}
}
/// A destination anchor describing a specific location within a PDF page.
public struct Destination: Codable, Equatable {
/// Destination type: "xyz", "fit", "fith", "fitv", "fitr", "fitb", "fitbh", "fitbv".
public let destType: String
/// Left coordinate (user-space points), present for "xyz", "fitv", "fitr", "fitbv".
public var left: Double?
/// Top coordinate (user-space points), present for "xyz", "fith", "fitr", "fitbh".
public var top: Double?
/// Right coordinate (user-space points), present only for "fitr".
public var right: Double?
/// Bottom coordinate (user-space points), present only for "fitr".
public var bottom: Double?
/// Zoom factor, present only for "xyz".
public var zoom: Double?
/// Coding keys for custom serialization
enum CodingKeys: String, CodingKey {
case destType = "type"
case left
case top
case right
case bottom
case zoom
}
/// Create a new Destination structure.
public init(
destType: String,
left: Double? = nil,
top: Double? = nil,
right: Double? = nil,
bottom: Double? = nil,
zoom: Double? = nil
) {
self.destType = destType
self.left = left
self.top = top
self.right = right
self.bottom = bottom
self.zoom = zoom
}
}