Files
Gas-Tank-AR-App/GasTankAR/Services/Signing/DefaultPDFSigningService.swift

77 lines
3.4 KiB
Swift

import Foundation
import PDFKit
import PencilKit
import UIKit
@MainActor
struct DefaultPDFSigningService: PDFSigningService {
var outputDirectory: URL = URL.applicationSupportDirectory
.appending(path: "SignedLeadDocuments", directoryHint: .isDirectory)
func sign(source: URL, drawing: PKDrawing, placement: PDFSignaturePlacement) async throws -> URL {
try Task.checkCancellation()
guard !drawing.strokes.isEmpty else { throw PDFSigningError.emptySignature }
guard let document = PDFDocument(url: source), !document.isLocked else {
throw PDFSigningError.invalidDocument
}
guard placement.pageIndex >= 0,
let page = document.page(at: placement.pageIndex),
placement.bounds.width > 0, placement.bounds.height > 0,
page.bounds(for: .mediaBox).contains(placement.bounds) else {
throw PDFSigningError.invalidPlacement
}
// Fit the handwriting into the configured field without changing its aspect ratio.
// PencilKit uses a top-left origin; PDF page coordinates use a bottom-left origin.
let sourceBounds = drawing.bounds.insetBy(dx: -2, dy: -2)
let target = placement.bounds.insetBy(dx: 4, dy: 4)
guard target.width > 0, target.height > 0,
sourceBounds.width.isFinite, sourceBounds.height.isFinite else {
throw PDFSigningError.invalidPlacement
}
let scale = min(target.width / sourceBounds.width, target.height / sourceBounds.height)
let origin = CGPoint(x: target.midX - sourceBounds.width * scale / 2,
y: target.midY - sourceBounds.height * scale / 2)
let annotation = PDFAnnotation(bounds: placement.bounds, forType: .ink, withProperties: nil)
annotation.color = .black
let border = PDFBorder()
border.lineWidth = max(0.7, 2 * scale)
annotation.border = border
annotation.shouldPrint = true
for stroke in drawing.strokes {
let path = UIBezierPath()
var isFirst = true
for point in stroke.path.interpolatedPoints(by: .distance(1)) {
let location = point.location.applying(stroke.transform)
let converted = CGPoint(
x: origin.x + (location.x - sourceBounds.minX) * scale,
y: origin.y + (sourceBounds.maxY - location.y) * scale
)
if isFirst {
path.move(to: converted)
isFirst = false
} else {
path.addLine(to: converted)
}
}
// A tap is also a valid ink mark.
if path.isEmpty == false {
path.addLine(to: CGPoint(x: path.currentPoint.x + 0.01, y: path.currentPoint.y))
annotation.add(path)
}
}
page.addAnnotation(annotation)
guard let data = document.dataRepresentation() else { throw PDFSigningError.saveFailed }
try Task.checkCancellation()
do {
try FileManager.default.createDirectory(at: outputDirectory, withIntermediateDirectories: true)
let destination = outputDirectory.appending(path: UUID().uuidString + ".pdf")
try data.write(to: destination, options: .atomic)
return destination
} catch {
throw PDFSigningError.saveFailed
}
}
}