From ac4189f9eab866ffcff7b0b1b3e27d3fdc3f235c Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 24 Feb 2020 23:05:11 +0100 Subject: [PATCH] Port BitBuffer and mid-level QR encoding methods to Swift --- swift/Package.swift | 4 +- swift/Sources/QRCodeGenerator/BitBuffer.swift | 40 +++++ swift/Sources/QRCodeGenerator/QRCode.swift | 143 +++++++++++++++++- 3 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 swift/Sources/QRCodeGenerator/BitBuffer.swift diff --git a/swift/Package.swift b/swift/Package.swift index 9491e38..c53b7bf 100644 --- a/swift/Package.swift +++ b/swift/Package.swift @@ -10,7 +10,7 @@ let package = Package( .library( name: "QRCodeGenerator", targets: ["QRCodeGenerator"] - ), + ) ], dependencies: [ // Dependencies declare other packages that this package depends on. @@ -26,6 +26,6 @@ let package = Package( .testTarget( name: "QRCodeGeneratorTests", dependencies: ["QRCodeGenerator"] - ), + ) ] ) diff --git a/swift/Sources/QRCodeGenerator/BitBuffer.swift b/swift/Sources/QRCodeGenerator/BitBuffer.swift new file mode 100644 index 0000000..b710178 --- /dev/null +++ b/swift/Sources/QRCodeGenerator/BitBuffer.swift @@ -0,0 +1,40 @@ +/* + * QR Code generator library (Swift) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +/// An appendable sequence of bits (0s and 1s). +public struct BitBuffer { + public var bits: [Bool] + public var count: UInt { UInt(bits.count) } + + public init(bits: [Bool]) { + self.bits = bits + } + + /// 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) { + assert(length <= 31 && (value >> length) == 0, "Value out of range") + bits.append((0.. Self { + let chrs = Array(text) + let segs = QRSegment.makeSegments(chrs) + return try QRCode.encode(segments: segs, ecl: ecl) + } + + /// Returns a QR Code representing the given binary data at the given error correction level. + /// + /// This function always encodes using the binary segment mode, not any text mode. The maximum number of + /// bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + /// The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. + /// + /// Returns a wrapped `QrCode` if successful, or `Err` if the + /// data is too long to fit in any version at the given ECC level. + public static func encode(binary data: [UInt8], ecl: QRCodeECC) throws -> Self { + let segs = [QRSegment.make(bytes: data)] + return try QRCode.encode(segments: segs, ecl: ecl) + } + + /*---- Static factory functions (mid level) ----*/ + + /// Returns a QR Code representing the given segments at the given error correction level. + /// + /// The smallest possible QR Code version is automatically chosen for the output. The ECC level + /// of the result may be higher than the ecl argument if it can be done without increasing the version. + /// + /// This function allows the user to create a custom sequence of segments that switches + /// between modes (such as alphanumeric and byte) to encode text in less space. + /// This is a mid-level API; the high-level API is `encode_text()` and `encode_binary()`. + /// + /// Returns a wrapped `QrCode` if successful, or `Err` if the + /// data is too long to fit in any version at the given ECC level. + public static func encode(segments: [QRSegment], ecl: QRCodeECC) throws -> Self { + try QRCode.encodeAdvanced(segments: segments, ecl: ecl, minVersion: qrCodeMinVersion, maxVersion: qrCodeMaxVersion, boostECL: true) + } + + /// Returns a QR Code representing the given segments with the given encoding parameters. + /// + /// The smallest possible QR Code version within the given range is automatically + /// chosen for the output. Iff boostecl is `true`, then the ECC level of the result + /// may be higher than the ecl argument if it can be done without increasing the + /// version. The mask number is either between 0 to 7 (inclusive) to force that + /// mask, or `None` to automatically choose an appropriate mask (which may be slow). + /// + /// This function allows the user to create a custom sequence of segments that switches + /// between modes (such as alphanumeric and byte) to encode text in less space. + /// This is a mid-level API; the high-level API is `encode_text()` and `encode_binary()`. + /// + /// Returns a wrapped `QrCode` if successful, or `Err` if the data is too + /// long to fit in any version in the given range at the given ECC level. + public static func encodeAdvanced(segments: [QRSegment], ecl: QRCodeECC, minVersion: QRCodeVersion, maxVersion: QRCodeVersion, mask: QRCodeMask? = nil, boostECL: Bool) throws -> Self { + assert(minVersion <= maxVersion, "Invalid value") + + // Find the minimal version number to use + var version = minVersion + var dataUsedBits: UInt! + while true { + // Number of data bits available + let dataCapacityBits: UInt = QRCode.getNumDataCodewords(version: version, ecl: ecl) * 8 + let dataUsed: UInt? = QRSegment.getTotalBits(segments: segments, version: version) + if let used = dataUsed, used <= dataCapacityBits { + // The version number is found to be suitable + dataUsedBits = used + break + } else if version >= maxVersion { + let msg: String + if let used = dataUsed { + msg = "Data length = \(used) bits, Max capacity = \(dataCapacityBits) bits" + } else { + msg = "Segment too long" + } + } else { + version = QRCodeVersion(version.value + 1) + } + } + + // Increase error correction level while the data still fits in the current version number + for newECL in [QRCodeECC.medium, QRCodeECC.quartile, QRCodeECC.high] { + if boostECL && dataUsedBits <= QRCode.getNumDataCodewords(version: version, ecl: newECL) * 8 { + ecl = newECL + } + } + + // Concatenate all segments to create the data bit string + var bb = BitBuffer() + for seg in segments { + bb.appendBits(seg.mode.modeBits(), 4) + bb.appendBits(UInt32(seg.numChars), seg.mode.numCharCountBits(version: version)) + bb.values += seg.data + } + + assert(bb.count == dataUsedBits) + + // Add terminator and pad up to a byte if applicable + let dataCapacityBits: UInt = QRCode.getNumDataCodeWords(version: version, ecl: ecl) + assert(bb.count <= dataCapacityBits) + var numZeroBits = min(4, dataCapacityBits - bb.count) + bb.appendBits(0, UInt8(numZeroBits)) + numZeroBits = (0 &- bb.count) & 7 + bb.appendBits(0, UInt8(numZeroBits)) + assert(bb.count % 8 == 0) + + // Pad with alternating bytes until data capacity is reached + let padBytes = [0xEC, 0x11] + var i = 0 + while bb.count < dataCapacityBits { + bb.appendBits(padBytes[i], 8) + i += 1 + if i >= padBytes.count { + i = 0 + } + } + + // Pack bits into bytes in big endian + var dataCodeWords = [UInt8](repeating: 0, bb.count / 8) + for (i, bit) in bb.values.enumerated() { + dataCodeWords[i >> 3] |= UInt8(bit) << (7 - (i & 7)) + } + + // Create the QRCode object + return QRCode.encodeCodewords(version: version, ecl: ecl, dataCodeWords: dataCodeWords, mask: mask) + } + + /*---- Constructor (low level) ----*/ + + // TODO }