From c72f536b08c96ba45ab28295719b5da7d05c3bbb Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 25 Feb 2020 00:57:13 +0100 Subject: [PATCH] Port QRSegment and mid-level factories to Swift --- swift/Sources/QRCodeGenerator/BitBuffer.swift | 2 +- swift/Sources/QRCodeGenerator/QRSegment.swift | 148 ++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 swift/Sources/QRCodeGenerator/QRSegment.swift diff --git a/swift/Sources/QRCodeGenerator/BitBuffer.swift b/swift/Sources/QRCodeGenerator/BitBuffer.swift index b710178..ea648e2 100644 --- a/swift/Sources/QRCodeGenerator/BitBuffer.swift +++ b/swift/Sources/QRCodeGenerator/BitBuffer.swift @@ -33,7 +33,7 @@ public struct BitBuffer { /// Appends the given number of low-order bits of the given value to this buffer. /// /// Requires len ≤ 31 and val < 2len. - public mutating func writeBits(_ value: UInt32, _ length: Int) { + public mutating func appendBits(_ value: UInt32, _ length: Int) { assert(length <= 31 && (value >> length) == 0, "Value out of range") bits.append((0.. Self { + var bb = BitBuffer([]) + for b in data { + bb.appendBits(UInt32(b), 8) + } + return QRSegment(mode: .byte, numChars: data.count, data: bb.bits) + } + + /// Returns a segment representing the given string of decimal digits encoded in numeric mode. + /// + /// Panics if the string contains non-digit characters. + public static func makeNumeric(text: [Character]) -> Self { + var bb = BitBuffer([]) + var accumData: UInt32 = 0 + var accumCount: UInt8 = 0 + for c in text { + assert(c.isNumber && c.isASCII, "String contains non-numeric characters") + accumData = accumData * 10 + (UInt32(c.asciiValue!) - UInt32("0".asciiValue!)) + accumCount += 1 + if accumCount == 3 { + bb.appendBits(accumData, 10) + accumData = 0 + accumCount = 0 + } + } + if accumCount > 0 { // 1 or 2 digits remaining + bb.appendBits(accumData, accumCount * 3 + 1) + } + return QRSegment(mode: .numeric, numChars: text.count, data: bb.bits) + } + + /// Returns a segment representing the given text string encoded in alphanumeric mode. + /// + /// The characters allowed are: 0 to 9, A to Z (uppercase only), space, + /// dollar, percent, asterisk, plus, hyphen, period, slash, colon. + /// + /// Panics if the string contains non-encodable characters. + public static func makeAlphanumeric(text: [Character]) -> Self { + var bb = BitBuffer([]) + var accumData: UInt32 = 0 + var accumCount: UInt32 = 0 + for c in text { + guard let i = alphanumericCharset.firstIndex(of: c) else { + fatalError("String contains unencodable characters in alphanumeric mode") + } + accumData = accumData * 45 + UInt32(i) + accumCount += 1 + if accumCount == 2 { + bb.appendBits(accumData, 11) + accumData = 0 + accumCount = 0 + } + } + if accumCount > 0 { // 1 character remaining + bb.appendBits(accumData, 6) + } + return QRSegment(mode: .alphanumeric, numChars: text.count, data: bb.bits) + } + + /// Returns a list of zero or more segments to represent the given Unicode text string. + /// + /// The result may use various segment modes and switch + /// modes to optimize the length of the bit stream. + public static func makeSegments(text: [Character]) -> Self { + if text.isEmpty { + return [] + } else if QRSegment.isNumeric(text) { + return [QRSegment.makeNumeric(text)] + } else if QRSegment.isAlphanumeric(text) { + return [QRSegment.makeAlphanumeric(text)] + } else { + let s = String(text) + return [QRSegment.makeBytes([UInt8](s.data(using: .utf8)!))] + } + } + + /// Returns a segment representing an Extended Channel Interpretation + /// (ECI) designator with the given assignment value. + public static func makeECI(assignVal: UInt32) -> Self { + var bb = BitBuffer([]) + if assignVal < (1 << 7) { + bb.appendBits(assignVal, 8) + } else if assignVal < (1 << 14) { + bb.appendBits(2, 2) + bb.appendBits(assignVal, 14) + } else if assignVal < 1_000_000 { + bb.appendBits(6, 3) + bb.appendBits(assignVal, 21) + } else { + fatalError("ECI assignment value out of range") + } + return QRSegment(mode: .eci, numChars: 0, data: bb.bits) + } +}