# SPDX-FileCopyrightText: 2009 Kazuhiko Arase # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # # SPDX-License-Identifier: MIT # Ported from the Javascript library by Sam Curren # QRCode for Javascript # http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/qrcode.js # # Minimized for CircuitPython by ladyada for adafruit industries # # The word "QR Code" is registered trademark of # DENSO WAVE INCORPORATED # http://www.denso-wave.com/qrcode/faqpatent-e.html """ `adafruit_miniqr` ==================================================== A non-hardware dependant miniature QR generator library. All native Python! * Author(s): ladyada Implementation Notes -------------------- **Hardware:** * Any! **Software and Dependencies:** * Python 3 """ # imports import math try: from typing import Dict, List, Optional, Tuple except ImportError: pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_miniQR.git" # Consts! M = 0 L = 1 H = 2 Q = 3 _MODE_8BIT_BYTE = 1 << 2 _PAD0 = 0xEC _PAD1 = 0x11 # Optimized polynomial helpers def _glog(n: int) -> int: """Lookup log(n) from pre-calculated byte table""" if n < 1: raise ValueError("glog(" + n + ")") return LOG_TABLE[n] def _gexp(n: int) -> int: """Lookup exp(n) from pre-calculated byte table""" while n < 0: n += 255 while n >= 256: n -= 255 return EXP_TABLE[n] EXP_TABLE = b"\x01\x02\x04\x08\x10 @\x80\x1d:t\xe8\xcd\x87\x13&L\x98-Z\xb4u\xea\xc9\x8f\x03\x06\x0c\x180`\xc0\x9d'N\x9c%J\x945j\xd4\xb5w\xee\xc1\x9f#F\x8c\x05\n\x14(P\xa0]\xbai\xd2\xb9o\xde\xa1_\xbea\xc2\x99/^\xbce\xca\x89\x0f\x1e|\xf8\xed\xc7\x93;v\xec\xc5\x973f\xcc\x85\x17.\\\xb8m\xda\xa9O\x9e!B\x84\x15*T\xa8M\x9a)R\xa4U\xaaI\x929r\xe4\xd5\xb7s\xe6\xd1\xbfc\xc6\x91?~\xfc\xe5\xd7\xb3{\xf6\xf1\xff\xe3\xdb\xabK\x961b\xc4\x957n\xdc\xa5W\xaeA\x82\x192d\xc8\x8d\x07\x0e\x1c8p\xe0\xdd\xa7S\xa6Q\xa2Y\xb2y\xf2\xf9\xef\xc3\x9b+V\xacE\x8a\t\x12$H\x90=z\xf4\xf5\xf7\xf3\xfb\xeb\xcb\x8b\x0b\x16,X\xb0}\xfa\xe9\xcf\x83\x1b6l\xd8\xadG\x8e\x01" # noqa: E501 LOG_TABLE = b"\x00\x00\x01\x19\x022\x1a\xc6\x03\xdf3\xee\x1bh\xc7K\x04d\xe0\x0e4\x8d\xef\x81\x1c\xc1i\xf8\xc8\x08Lq\x05\x8ae/\xe1$\x0f!5\x93\x8e\xda\xf0\x12\x82E\x1d\xb5\xc2}j'\xf9\xb9\xc9\x9a\txM\xe4r\xa6\x06\xbf\x8bbf\xdd0\xfd\xe2\x98%\xb3\x10\x91\"\x886\xd0\x94\xce\x8f\x96\xdb\xbd\xf1\xd2\x13\\\x838F@\x1eB\xb6\xa3\xc3H~nk:(T\xfa\x85\xba=\xca^\x9b\x9f\n\x15y+N\xd4\xe5\xacs\xf3\xa7W\x07p\xc0\xf7\x8c\x80c\rgJ\xde\xed1\xc5\xfe\x18\xe3\xa5\x99w&\xb8\xb4|\x11D\x92\xd9# \x89.7?\xd1[\x95\xbc\xcf\xcd\x90\x87\x97\xb2\xdc\xfc\xbea\xf2V\xd3\xab\x14*]\x9e\x84<9SGmA\xa2\x1f-C\xd8\xb7{\xa4v\xc4\x17I\xec\x7f\x0co\xf6l\xa1;R)\x9dU\xaa\xfb`\x86\xb1\xbb\xcc>Z\xcbY_\xb0\x9c\xa9\xa0Q\x0b\xf5\x16\xebzu,\xd7O\xae\xd5\xe9\xe6\xe7\xad\xe8t\xd6\xf4\xea\xa8PX\xaf" # noqa: E501 class QRCode: """The generator class for QR code matrices""" def __init__(self, *, qr_type: Optional[int] = None, error_correct: int = L): """Initialize an empty QR code. You can define the `qr_type` (size) of the code matrix, or have the libary auto-select the smallest match. Default `error_correct` is type L (7%), but you can select M, Q or H.""" self.type = qr_type self.ECC = error_correct self.matrix = None self.module_count = 0 self.data_cache = None self.data_list = [] def add_data(self, data: bytes) -> None: """Add more data to the QR code, must be bytestring stype""" self.data_list.append(data) datalen = sum(len(x) for x in self.data_list) if not self.type: for qr_type in range(1, 10): rs_blocks = _get_rs_blocks(qr_type, self.ECC) total_data_count = 0 for block in rs_blocks: total_data_count += block["data"] if total_data_count > datalen: self.type = qr_type break self.data_cache = None def make(self, *, test: bool = False, mask_pattern: int = 0) -> None: """Perform the actual generation of the QR matrix. To keep things small and speedy we don't generate all 8 mask patterns and pick the best. Instead, please pass in a desired mask_pattern, the default mask is 0.""" self.module_count = self.type * 4 + 17 self.matrix = QRBitMatrix(self.module_count, self.module_count) self._setup_position_probe_pattern(0, 0) self._setup_position_probe_pattern(self.module_count - 7, 0) self._setup_position_probe_pattern(0, self.module_count - 7) self._setup_position_adjust_pattern() self._setup_timing_pattern() self._setup_type_info(test, mask_pattern) if self.type >= 7: self._setup_type_number(test) if self.data_cache is None: self.data_cache = QRCode._create_data(self.type, self.ECC, self.data_list) self._map_data(self.data_cache, mask_pattern) def _setup_position_probe_pattern(self, row: int, col: int) -> None: """Add the positition probe data pixels to the matrix""" for r in range(-1, 8): if row + r <= -1 or self.module_count <= row + r: continue for c in range(-1, 8): if col + c <= -1 or self.module_count <= col + c: continue test = ( (0 <= r <= 6 and (c in (0, 6))) or (0 <= c <= 6 and (r in (0, 6))) or (2 <= r <= 4 and 2 <= c <= 4) ) self.matrix[row + r, col + c] = test def _setup_timing_pattern(self) -> None: """Add the timing data pixels to the matrix""" for r in range(8, self.module_count - 8): if self.matrix[r, 6] is not None: continue self.matrix[r, 6] = r % 2 == 0 for c in range(8, self.module_count - 8): if self.matrix[6, c] is not None: continue self.matrix[6, c] = c % 2 == 0 def _setup_position_adjust_pattern(self) -> None: """Add the position adjust data pixels to the matrix""" pos = QRUtil.get_pattern_position(self.type) for row in pos: for col in pos: if self.matrix[row, col] is not None: continue for r in range(-2, 3): for c in range(-2, 3): test = abs(r) == 2 or abs(c) == 2 or (r == 0 and c == 0) self.matrix[row + r, col + c] = test def _setup_type_number(self, test: bool) -> None: """Add the type number pixels to the matrix""" bits = QRUtil.get_BCH_type_number(self.type) for i in range(18): mod = not test and ((bits >> i) & 1) == 1 self.matrix[i // 3, i % 3 + self.module_count - 8 - 3] = mod for i in range(18): mod = not test and ((bits >> i) & 1) == 1 self.matrix[i % 3 + self.module_count - 8 - 3, i // 3] = mod def _setup_type_info(self, test: bool, mask_pattern: int) -> None: """Add the type info pixels to the matrix""" data = (self.ECC << 3) | mask_pattern bits = QRUtil.get_BCH_type_info(data) # // vertical for i in range(15): mod = not test and ((bits >> i) & 1) == 1 if i < 6: self.matrix[i, 8] = mod elif i < 8: self.matrix[i + 1, 8] = mod else: self.matrix[self.module_count - 15 + i, 8] = mod # // horizontal for i in range(15): mod = not test and ((bits >> i) & 1) == 1 if i < 8: self.matrix[8, self.module_count - i - 1] = mod elif i < 9: self.matrix[8, 15 - i - 1 + 1] = mod else: self.matrix[8, 15 - i - 1] = mod # // fixed module self.matrix[self.module_count - 8, 8] = not test def _map_data(self, data: bytes, mask_pattern: int) -> None: """Map the data onto the QR code""" inc = -1 row = self.module_count - 1 bit_idx = 7 byte_idx = 0 for col in range(self.module_count - 1, 0, -2): if col == 6: col -= 1 # noqa: PLW2901 loop variable overwritten while True: for c in range(2): if self.matrix[row, col - c] is None: dark = False if byte_idx < len(data): dark = ((data[byte_idx] >> bit_idx) & 1) == 1 mask = QRUtil.get_mask(mask_pattern, row, col - c) if mask: dark = not dark self.matrix[row, col - c] = dark bit_idx -= 1 if bit_idx == -1: byte_idx += 1 bit_idx = 7 row += inc if row < 0 or self.module_count <= row: row -= inc inc = -inc break @staticmethod def _create_data(qr_type: int, ecc: int, data_list: list) -> bytes: """Check and format data into bit buffer""" rs_blocks = _get_rs_blocks(qr_type, ecc) buffer = QRBitBuffer() for data in data_list: if isinstance(data, str): data = str.encode(data) # noqa: PLW2901 loop variable overwritten buffer.put(_MODE_8BIT_BYTE, 4) buffer.put(len(data), 8) for byte in data: buffer.put(byte, 8) # // calc num max data. total_data_count = 0 for block in rs_blocks: total_data_count += block["data"] if buffer.get_length_bits() > total_data_count * 8: raise RuntimeError( "Code length overflow: %d > %d" % (buffer.get_length_bits(), total_data_count * 8) ) # // end code if buffer.get_length_bits() + 4 <= total_data_count * 8: buffer.put(0, 4) # // padding while buffer.get_length_bits() % 8 != 0: buffer.put_bit(False) # // padding while True: if buffer.get_length_bits() >= total_data_count * 8: break buffer.put(_PAD0, 8) if buffer.get_length_bits() >= total_data_count * 8: break buffer.put(_PAD1, 8) return QRCode._create_bytes(buffer, rs_blocks) @staticmethod def _create_bytes(buffer: bytes, rs_blocks: List[Dict]) -> bytes: # noqa: PLR0912 Too many branches """Perform error calculation math on bit buffer""" offset = 0 max_dc_count = 0 max_ec_count = 0 dcdata = [0] * len(rs_blocks) ecdata = [0] * len(rs_blocks) for r, block in enumerate(rs_blocks): dc_count = block["data"] ec_count = block["total"] - dc_count max_dc_count = max(max_dc_count, dc_count) max_ec_count = max(max_ec_count, ec_count) dcdata[r] = [0] * dc_count for i in range(len(dcdata[r])): dcdata[r][i] = 0xFF & buffer.buffer[i + offset] offset += dc_count rs_poly = QRUtil.get_error_correct_polynomial(ec_count) mod_poly = QRPolynomial(dcdata[r], rs_poly.get_length() - 1) while True: if mod_poly.get_length() - rs_poly.get_length() < 0: break ratio = _glog(mod_poly.get(0)) - _glog(rs_poly.get(0)) num = [0 for x in range(mod_poly.get_length())] for i in range(mod_poly.get_length()): num[i] = mod_poly.get(i) for i in range(rs_poly.get_length()): num[i] ^= _gexp(_glog(rs_poly.get(i)) + ratio) mod_poly = QRPolynomial(num, 0) ecdata[r] = [0 for x in range(rs_poly.get_length() - 1)] for i in range(len(ecdata[r])): mod_index = i + mod_poly.get_length() - len(ecdata[r]) if mod_index >= 0: ecdata[r][i] = mod_poly.get(mod_index) else: ecdata[r][i] = 0 total_code_count = 0 for block in rs_blocks: total_code_count += block["total"] data = [None] * total_code_count index = 0 for i in range(max_dc_count): for r in range(len(rs_blocks)): if i < len(dcdata[r]): data[index] = dcdata[r][i] index += 1 for i in range(max_ec_count): for r in range(len(rs_blocks)): if i < len(ecdata[r]): data[index] = ecdata[r][i] index += 1 return data class QRUtil: """A selection of bit manipulation tools for QR generation and BCH encoding""" PATTERN_POSITION_TABLE = [ b"", b"\x06\x12", b"\x06\x16", b"\x06\x1a", b"\x06\x1e", b'\x06"', b"\x06\x16&", b"\x06\x18*", b"\x06\x1a.", b"\x06\x1c2", ] G15 = 0b10100110111 G18 = 0b1111100100101 G15_MASK = 0b101010000010010 @staticmethod def get_BCH_type_info(data: int) -> int: """Encode with G15 BCH mask""" d = data << 10 while QRUtil.get_BCH_digit(d) - QRUtil.get_BCH_digit(QRUtil.G15) >= 0: d ^= QRUtil.G15 << (QRUtil.get_BCH_digit(d) - QRUtil.get_BCH_digit(QRUtil.G15)) return ((data << 10) | d) ^ QRUtil.G15_MASK @staticmethod def get_BCH_type_number(data: int) -> int: """Encode with G18 BCH mask""" d = data << 12 while QRUtil.get_BCH_digit(d) - QRUtil.get_BCH_digit(QRUtil.G18) >= 0: d ^= QRUtil.G18 << (QRUtil.get_BCH_digit(d) - QRUtil.get_BCH_digit(QRUtil.G18)) return (data << 12) | d @staticmethod def get_BCH_digit(data: int) -> int: """Count digits in data""" digit = 0 while data != 0: digit += 1 data >>= 1 return digit @staticmethod def get_pattern_position(qr_type: int) -> bytes: """The mask pattern position array for this QR type""" return QRUtil.PATTERN_POSITION_TABLE[qr_type - 1] @staticmethod def get_mask(mask: int, i: int, j: int) -> int: # noqa: PLR0911 Too many return statements """Perform matching calculation on two vals for given pattern mask""" if mask == 0: return (i + j) % 2 == 0 if mask == 1: return i % 2 == 0 if mask == 2: return j % 3 == 0 if mask == 3: return (i + j) % 3 == 0 if mask == 4: return (math.floor(i / 2) + math.floor(j / 3)) % 2 == 0 if mask == 5: return (i * j) % 2 + (i * j) % 3 == 0 if mask == 6: return ((i * j) % 2 + (i * j) % 3) % 2 == 0 if mask == 7: return ((i * j) % 3 + (i + j) % 2) % 2 == 0 raise ValueError("Bad mask pattern:" + mask) @staticmethod def get_error_correct_polynomial(ecc_length: int) -> "QRPolynomial": """Generate a ecc polynomial""" poly = QRPolynomial([1], 0) for i in range(ecc_length): poly = poly.multiply(QRPolynomial([1, _gexp(i)], 0)) return poly class QRPolynomial: """Structure for creating and manipulating error code polynomials""" def __init__(self, num: int, shift: int): """Create a QR polynomial""" if not num: raise ValueError(num.length + "/" + shift) offset = 0 while offset < len(num) and num[offset] == 0: offset += 1 self.num = [0 for x in range(len(num) - offset + shift)] for i in range(len(num) - offset): self.num[i] = num[i + offset] def get(self, index: int) -> int: """The exponent at the index location""" return self.num[index] def get_length(self) -> int: """Length of the poly""" return len(self.num) def multiply(self, other_polynomial: "QRPolynomial") -> "QRPolynomial": """Multiply two polynomials, returns a new one""" num = [0 for x in range(self.get_length() + other_polynomial.get_length() - 1)] for i in range(self.get_length()): for j in range(other_polynomial.get_length()): num[i + j] ^= _gexp(_glog(self.get(i)) + _glog(other_polynomial.get(j))) return QRPolynomial(num, 0) _QRRS_BLOCK_TABLE = ( b"\x01\x1a\x10", b"\x01\x1a\x13", b"\x01\x1a\t", b"\x01\x1a\r", b"\x01,\x1c", b'\x01,"', b"\x01,\x10", b"\x01,\x16", b"\x01F,", b"\x01F7", b"\x02#\r", b"\x02#\x11", b"\x022 ", b"\x01dP", b"\x04\x19\t", b"\x022\x18", b"\x02C+", b"\x01\x86l", b'\x02!\x0b\x02"\x0c', b'\x02!\x0f\x02"\x10', b"\x04+\x1b", b"\x02VD", b"\x04+\x0f", b"\x04+\x13", b"\x041\x1f", b"\x02bN", b"\x04'\r\x01(\x0e", b"\x02 \x0e\x04!\x0f", b"\x02<&\x02='", b"\x02ya", b"\x04(\x0e\x02)\x0f", b"\x04(\x12\x02)\x13", b"\x03:$\x02;%", b"\x02\x92t", b"\x04$\x0c\x04%\r", b"\x04$\x10\x04%\x11", ) def _get_rs_blocks(qr_type: int, ecc: int) -> List[Dict]: rs_block = _QRRS_BLOCK_TABLE[(qr_type - 1) * 4 + ecc] length = len(rs_block) // 3 blocks = [] for i in range(length): count = rs_block[i * 3 + 0] total = rs_block[i * 3 + 1] data = rs_block[i * 3 + 2] block = {"total": total, "data": data} for _ in range(count): blocks.append(block) return blocks class QRBitMatrix: """A bit-packed storage class for matrices""" def __init__(self, width: int, height: int): self.width = width self.height = height if width > 60: raise ValueError("Max 60 bits wide:", width) self.buffer = [0] * self.height * 2 self.used = [0] * self.height * 2 def __repr__(self) -> str: b = "" for y in range(self.height): for x in range(self.width): if self[x, y]: b += "X" else: b += "." b += "\n" return b def __getitem__(self, key: Tuple[int, int]) -> int: x, y = key if y > self.width: raise ValueError() i = 2 * x + (y // 30) j = y % 30 if not self.used[i] & (1 << j): return None return self.buffer[i] & (1 << j) def __setitem__(self, key: Tuple[int, int], value: int) -> None: x, y = key if y > self.width: raise ValueError() i = 2 * x + (y // 30) j = y % 30 if value: self.buffer[i] |= 1 << j else: self.buffer[i] &= ~(1 << j) self.used[i] |= 1 << j # buffer item was set class QRBitBuffer: """Storage class for a length of individual bits""" def __init__(self): self.buffer = [] self.length = 0 def __repr__(self) -> str: return ".".join([str(n) for n in self.buffer]) def get(self, index: int) -> int: """The bit value at a location""" i = index // 8 return self.buffer[i] & (1 << (7 - index % 8)) def put(self, num: int, length: int) -> None: """Add a number of bits from a single integer value""" for i in range(length): self.put_bit(num & (1 << (length - i - 1))) def get_length_bits(self) -> int: """Size of bit buffer""" return self.length def put_bit(self, bit: int) -> None: """Insert one bit at the end of the bit buffer""" i = self.length // 8 if len(self.buffer) <= i: self.buffer.append(0) if bit: self.buffer[i] |= 0x80 >> (self.length % 8) self.length += 1