Add lead workflows, PDF signing, AR projects and photo gallery integration
This commit is contained in:
76
GasTankAR/Services/Signing/DefaultPDFSigningService.swift
Normal file
76
GasTankAR/Services/Signing/DefaultPDFSigningService.swift
Normal file
@ -0,0 +1,76 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user