You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import Foundation
import UIKit
import CoreImage.CIFilterBuiltins
// MARK: -
class BarcodeGenerator {
// MARK: -
static let shared = BarcodeGenerator()
private init() {}
// MARK: -
///
/// - Parameters:
/// - content:
/// - type:
/// - size: CGSize(width: 300, height: 120)
/// - Returns:
func generateBarcode(from content: String, type: String, size: CGSize = CGSize(width: 300, height: 120), showText: Bool = true) -> UIImage? {
guard !content.isEmpty else { return nil }
//
switch type.lowercased() {
case let t where t.contains("ean13"):
return generateEAN13Barcode(from: content, size: size, showText: showText)
case let t where t.contains("ean8"):
return generateEAN8Barcode(from: content, size: size, showText: showText)
case let t where t.contains("upce"):
return generateUPCEBarcode(from: content, size: size, showText: showText)
case let t where t.contains("code39"):
return generateCode39Barcode(from: content, size: size, showText: showText)
case let t where t.contains("code128"):
return generateCode128Barcode(from: content, size: size, showText: showText)
case let t where t.contains("pdf417"):
return generatePDF417Barcode(from: content, size: size, showText: showText)
case let t where t.contains("itf14"):
return generateITF14Barcode(from: content, size: size, showText: showText)
default:
// 使 Code 128
return generateCode128Barcode(from: content, size: size, showText: showText)
}
}
// MARK: - EAN-13
private func generateEAN13Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.code128BarcodeGenerator()
// EAN-13 13
let paddedContent = content.padding(toLength: 13, withPad: "0", startingAt: 0)
filter.message = Data(paddedContent.utf8)
filter.quietSpace = 7.0
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
}
// MARK: - EAN-8
private func generateEAN8Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.code128BarcodeGenerator()
// EAN-8 8
let paddedContent = content.padding(toLength: 8, withPad: "0", startingAt: 0)
filter.message = Data(paddedContent.utf8)
filter.quietSpace = 7.0
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
}
// MARK: - UPC-E
private func generateUPCEBarcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.code128BarcodeGenerator()
// UPC-E 8
let paddedContent = content.padding(toLength: 8, withPad: "0", startingAt: 0)
filter.message = Data(paddedContent.utf8)
filter.quietSpace = 7.0
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
}
// MARK: - Code 39
private func generateCode39Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.code128BarcodeGenerator()
// Code 39
filter.message = Data(content.utf8)
filter.quietSpace = 7.0
return processBarcodeFilter(filter, context: context, size: size, content: content, showText: showText)
}
// MARK: - Code 128
private func generateCode128Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.code128BarcodeGenerator()
filter.message = Data(content.utf8)
filter.quietSpace = 7.0
return processBarcodeFilter(filter, context: context, size: size, content: content, showText: showText)
}
// MARK: - PDF417
private func generatePDF417Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.pdf417BarcodeGenerator()
filter.message = Data(content.utf8)
filter.minWidth = 3
filter.maxWidth = 3
filter.minHeight = 3
filter.maxHeight = 3
return processBarcodeFilter(filter, context: context, size: size, content: content, showText: showText)
}
// MARK: - ITF-14
private func generateITF14Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
let context = CIContext()
let filter = CIFilter.code128BarcodeGenerator()
// ITF-14 14
let paddedContent = content.padding(toLength: 14, withPad: "0", startingAt: 0)
filter.message = Data(paddedContent.utf8)
filter.quietSpace = 7.0
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
}
// MARK: -
private func processBarcodeFilter(_ filter: CIFilter, context: CIContext, size: CGSize, content: String, showText: Bool) -> UIImage? {
guard let outputImage = filter.outputImage else { return nil }
//
let scaleX = size.width / outputImage.extent.width
let scaleY = size.height / outputImage.extent.height
//
let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
let scaledImage = outputImage.transformed(by: transform)
// UIImage
guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil }
let baseImage = UIImage(cgImage: cgImage)
//
if showText {
return addTextLabel(to: baseImage, content: content, size: size)
}
return baseImage
}
// MARK: -
private func addTextLabel(to image: UIImage, content: String, size: CGSize) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
//
image.draw(in: CGRect(origin: .zero, size: size))
//
let textAttributes: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 12, weight: .medium),
.foregroundColor: UIColor.black
]
//
let charWidth = size.width / CGFloat(content.count)
let textY = size.height - 20 // 20
//
for (index, char) in content.enumerated() {
let charString = String(char)
let charSize = charString.size(withAttributes: textAttributes)
// X
let charX = CGFloat(index) * charWidth + (charWidth - charSize.width) / 2
//
let backgroundRect = CGRect(
x: charX - 2,
y: textY - 2,
width: charSize.width + 4,
height: charSize.height + 4
)
UIColor.white.setFill()
context.fill(backgroundRect)
//
charString.draw(at: CGPoint(x: charX, y: textY), withAttributes: textAttributes)
}
}
}
// MARK: -
///
/// - Parameter type:
/// - Returns:
func getBarcodeIcon(for type: String) -> String {
let lowercasedType = type.lowercased()
if lowercasedType.contains("ean") || lowercasedType.contains("upc") {
return "barcode"
} else if lowercasedType.contains("code39") || lowercasedType.contains("code128") {
return "barcode.viewfinder"
} else if lowercasedType.contains("pdf417") {
return "qrcode.viewfinder"
} else if lowercasedType.contains("itf14") {
return "barcode"
} else {
return "barcode"
}
}
// MARK: -
///
/// - Parameters:
/// - content:
/// - type:
/// - Returns:
func validateBarcodeContent(_ content: String, type: String) -> Bool {
guard !content.isEmpty else { return false }
let lowercasedType = type.lowercased()
switch lowercasedType {
case let t where t.contains("ean13"):
return content.count == 13 && content.allSatisfy { $0.isNumber }
case let t where t.contains("ean8"):
return content.count == 8 && content.allSatisfy { $0.isNumber }
case let t where t.contains("upce"):
return content.count == 8 && content.allSatisfy { $0.isNumber }
case let t where t.contains("itf14"):
return content.count == 14 && content.allSatisfy { $0.isNumber }
case let t where t.contains("code39"):
// Code 39
return content.allSatisfy { $0.isLetter || $0.isNumber || " -.$/+%".contains($0) }
case let t where t.contains("code128"):
// Code 128 ASCII
return content.allSatisfy { $0.asciiValue != nil }
case let t where t.contains("pdf417"):
// PDF417
return true
default:
return true
}
}
}
// MARK: - String
extension String {
///
/// - Parameters:
/// - length:
/// - pad:
/// - startingAt:
/// - Returns:
func padding(toLength length: Int, withPad pad: String, startingAt startIndex: Int) -> String {
if self.count >= length {
return String(self.prefix(length))
}
let paddingLength = length - self.count
let padding = String(repeating: pad, count: paddingLength)
return self + padding
}
}