diff --git a/Sources/Bedrock/Utilities/QRCodeGenerator.swift b/Sources/Bedrock/Utilities/QRCodeGenerator.swift new file mode 100644 index 0000000..9f8de60 --- /dev/null +++ b/Sources/Bedrock/Utilities/QRCodeGenerator.swift @@ -0,0 +1,122 @@ +// +// QRCodeGenerator.swift +// Bedrock +// +// Generates QR codes from string payloads. +// + +import SwiftUI +import CoreImage +import CoreImage.CIFilterBuiltins + +/// Generates QR codes from string data. +/// +/// Use this to create QR codes for URLs, vCards, or any text payload. +/// +/// ## Example +/// +/// ```swift +/// if let qrImage = QRCodeGenerator.generate(from: "https://example.com") { +/// Image(decorative: qrImage, scale: 1.0) +/// } +/// ``` +public enum QRCodeGenerator { + + /// The CIContext used for rendering. + private static let context = CIContext() + + /// Error correction levels for QR codes. + public enum CorrectionLevel: String, Sendable { + /// Low (7% recovery) + case low = "L" + /// Medium (15% recovery) + case medium = "M" + /// Quartile (25% recovery) + case quartile = "Q" + /// High (30% recovery) + case high = "H" + } + + /// Generates a QR code image from a string payload. + /// - Parameters: + /// - payload: The string to encode. + /// - correctionLevel: Error correction level (default: medium). + /// - scale: Scale factor for the output image (default: 10). + /// - Returns: A CGImage of the QR code, or nil if generation fails. + public static func generate( + from payload: String, + correctionLevel: CorrectionLevel = .medium, + scale: CGFloat = 10 + ) -> CGImage? { + let data = Data(payload.utf8) + let filter = CIFilter.qrCodeGenerator() + filter.setValue(data, forKey: "inputMessage") + filter.correctionLevel = correctionLevel.rawValue + + guard let outputImage = filter.outputImage else { return nil } + + let scaledImage = outputImage.transformed( + by: CGAffineTransform(scaleX: scale, y: scale) + ) + + return context.createCGImage(scaledImage, from: scaledImage.extent) + } +} + +// MARK: - SwiftUI View + +/// A SwiftUI view that displays a QR code. +/// +/// ## Example +/// +/// ```swift +/// QRCodeImageView(payload: card.vCardPayload) +/// .frame(width: 200, height: 200) +/// ``` +public struct QRCodeImageView: View { + /// The string to encode as a QR code. + public let payload: String + + /// Error correction level. + public let correctionLevel: QRCodeGenerator.CorrectionLevel + + /// Creates a QR code view. + /// - Parameters: + /// - payload: The string to encode. + /// - correctionLevel: Error correction level (default: medium). + public init( + payload: String, + correctionLevel: QRCodeGenerator.CorrectionLevel = .medium + ) { + self.payload = payload + self.correctionLevel = correctionLevel + } + + public var body: some View { + if let cgImage = QRCodeGenerator.generate( + from: payload, + correctionLevel: correctionLevel + ) { + Image(decorative: cgImage, scale: 1.0) + .interpolation(.none) + .resizable() + .scaledToFit() + .accessibilityLabel("QR code") + } else { + Image(systemName: "qrcode") + .font(.largeTitle) + .foregroundStyle(.secondary) + } + } +} + +#Preview { + VStack(spacing: Design.Spacing.large) { + QRCodeImageView(payload: "https://example.com") + .frame(width: 150, height: 150) + + QRCodeImageView(payload: "BEGIN:VCARD\nFN:John Doe\nEND:VCARD") + .frame(width: 150, height: 150) + } + .padding() +}