|
|
|
@ -22,6 +22,7 @@
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
import collections, itertools, re
|
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
@ -79,7 +80,7 @@ class QrCode:
|
|
|
|
|
# ---- Static factory functions (high level) ----
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def encode_text(text, ecl):
|
|
|
|
|
def encode_text(text: str, ecl: QrCode.Ecc) -> QrCode:
|
|
|
|
|
"""Returns a QR Code representing the given Unicode text string at the given error correction level.
|
|
|
|
|
As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
|
|
|
|
|
Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
|
|
|
|
@ -90,7 +91,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def encode_binary(data, ecl):
|
|
|
|
|
def encode_binary(data: bytes, ecl: QrCode.Ecc) -> QrCode:
|
|
|
|
|
"""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.
|
|
|
|
@ -103,7 +104,7 @@ class QrCode:
|
|
|
|
|
# ---- Static factory functions (mid level) ----
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def encode_segments(segs, ecl, minversion=1, maxversion=40, mask=-1, boostecl=True):
|
|
|
|
|
def encode_segments(segs: List[QrSegment], ecl: QrCode.Ecc, minversion: int = 1, maxversion: int = 40, mask: int = -1, boostecl: bool = True) -> QrCode:
|
|
|
|
|
"""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
|
|
|
|
@ -168,7 +169,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
# ---- Constructor (low level) ----
|
|
|
|
|
|
|
|
|
|
def __init__(self, version, errcorlvl, datacodewords, mask):
|
|
|
|
|
def __init__(self, version: int, errcorlvl: QrCode.Ecc, datacodewords: List[int], mask: int) -> None:
|
|
|
|
|
"""Creates a new QR Code with the given version number,
|
|
|
|
|
error correction level, data codeword bytes, and mask number.
|
|
|
|
|
This is a low-level API that most users should not use directly.
|
|
|
|
@ -230,23 +231,23 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
# ---- Accessor methods ----
|
|
|
|
|
|
|
|
|
|
def get_version(self):
|
|
|
|
|
def get_version(self) -> int:
|
|
|
|
|
"""Returns this QR Code's version number, in the range [1, 40]."""
|
|
|
|
|
return self._version
|
|
|
|
|
|
|
|
|
|
def get_size(self):
|
|
|
|
|
def get_size(self) -> int:
|
|
|
|
|
"""Returns this QR Code's size, in the range [21, 177]."""
|
|
|
|
|
return self._size
|
|
|
|
|
|
|
|
|
|
def get_error_correction_level(self):
|
|
|
|
|
def get_error_correction_level(self) -> QrCode.Ecc:
|
|
|
|
|
"""Returns this QR Code's error correction level."""
|
|
|
|
|
return self._errcorlvl
|
|
|
|
|
|
|
|
|
|
def get_mask(self):
|
|
|
|
|
def get_mask(self) -> int:
|
|
|
|
|
"""Returns this QR Code's mask, in the range [0, 7]."""
|
|
|
|
|
return self._mask
|
|
|
|
|
|
|
|
|
|
def get_module(self, x, y):
|
|
|
|
|
def get_module(self, x, y) -> bool:
|
|
|
|
|
"""Returns the color of the module (pixel) at the given coordinates, which is False
|
|
|
|
|
for white or True for black. The top left corner has the coordinates (x=0, y=0).
|
|
|
|
|
If the given coordinates are out of bounds, then False (white) is returned."""
|
|
|
|
@ -255,7 +256,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
# ---- Public instance methods ----
|
|
|
|
|
|
|
|
|
|
def to_svg_str(self, border):
|
|
|
|
|
def to_svg_str(self, border: int) -> str:
|
|
|
|
|
"""Returns a string of SVG code for an image depicting this QR Code, with the given number
|
|
|
|
|
of border modules. The string always uses Unix newlines (\n), regardless of the platform."""
|
|
|
|
|
if border < 0:
|
|
|
|
@ -276,7 +277,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
# ---- Private helper methods for constructor: Drawing function modules ----
|
|
|
|
|
|
|
|
|
|
def _draw_function_patterns(self):
|
|
|
|
|
def _draw_function_patterns(self) -> None:
|
|
|
|
|
"""Reads this object's version field, and draws and marks all function modules."""
|
|
|
|
|
# Draw horizontal and vertical timing patterns
|
|
|
|
|
for i in range(self._size):
|
|
|
|
@ -302,7 +303,7 @@ class QrCode:
|
|
|
|
|
self._draw_version()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_format_bits(self, mask):
|
|
|
|
|
def _draw_format_bits(self, mask) -> None:
|
|
|
|
|
"""Draws two copies of the format bits (with its own error correction code)
|
|
|
|
|
based on the given mask and this object's error correction level field."""
|
|
|
|
|
# Calculate error correction code and pack bits
|
|
|
|
@ -330,7 +331,7 @@ class QrCode:
|
|
|
|
|
self._set_function_module(8, self._size - 8, True) # Always black
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_version(self):
|
|
|
|
|
def _draw_version(self) -> None:
|
|
|
|
|
"""Draws two copies of the version bits (with its own error correction code),
|
|
|
|
|
based on this object's version field, iff 7 <= version <= 40."""
|
|
|
|
|
if self._version < 7:
|
|
|
|
@ -352,7 +353,7 @@ class QrCode:
|
|
|
|
|
self._set_function_module(b, a, bit)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_finder_pattern(self, x, y):
|
|
|
|
|
def _draw_finder_pattern(self, x, y) -> None:
|
|
|
|
|
"""Draws a 9*9 finder pattern including the border separator,
|
|
|
|
|
with the center module at (x, y). Modules can be out of bounds."""
|
|
|
|
|
for dy in range(-4, 5):
|
|
|
|
@ -363,7 +364,7 @@ class QrCode:
|
|
|
|
|
self._set_function_module(xx, yy, max(abs(dx), abs(dy)) not in (2, 4))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_alignment_pattern(self, x, y):
|
|
|
|
|
def _draw_alignment_pattern(self, x, y) -> None:
|
|
|
|
|
"""Draws a 5*5 alignment pattern, with the center module
|
|
|
|
|
at (x, y). All modules must be in bounds."""
|
|
|
|
|
for dy in range(-2, 3):
|
|
|
|
@ -371,7 +372,7 @@ class QrCode:
|
|
|
|
|
self._set_function_module(x + dx, y + dy, max(abs(dx), abs(dy)) != 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_function_module(self, x, y, isblack):
|
|
|
|
|
def _set_function_module(self, x: int, y: int, isblack: bool) -> None:
|
|
|
|
|
"""Sets the color of a module and marks it as a function module.
|
|
|
|
|
Only used by the constructor. Coordinates must be in bounds."""
|
|
|
|
|
assert type(isblack) is bool
|
|
|
|
@ -381,7 +382,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
# ---- Private helper methods for constructor: Codewords and masking ----
|
|
|
|
|
|
|
|
|
|
def _add_ecc_and_interleave(self, data):
|
|
|
|
|
def _add_ecc_and_interleave(self, data: List[int]) -> List[int]:
|
|
|
|
|
"""Returns a new byte string representing the given data with the appropriate error correction
|
|
|
|
|
codewords appended to it, based on this object's version and error correction level."""
|
|
|
|
|
version = self._version
|
|
|
|
@ -418,7 +419,7 @@ class QrCode:
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_codewords(self, data):
|
|
|
|
|
def _draw_codewords(self, data: List[int]) -> None:
|
|
|
|
|
"""Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
|
|
|
|
data area of this QR Code. Function modules need to be marked off before this is called."""
|
|
|
|
|
assert len(data) == QrCode._get_num_raw_data_modules(self._version) // 8
|
|
|
|
@ -441,7 +442,7 @@ class QrCode:
|
|
|
|
|
assert i == len(data) * 8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_mask(self, mask):
|
|
|
|
|
def _apply_mask(self, mask: int) -> None:
|
|
|
|
|
"""XORs the codeword modules in this QR Code with the given mask pattern.
|
|
|
|
|
The function modules must be marked and the codeword bits must be drawn
|
|
|
|
|
before masking. Due to the arithmetic of XOR, calling applyMask() with
|
|
|
|
@ -455,7 +456,7 @@ class QrCode:
|
|
|
|
|
self._modules[y][x] ^= (masker(x, y) == 0) and (not self._isfunction[y][x])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_penalty_score(self):
|
|
|
|
|
def _get_penalty_score(self) -> int:
|
|
|
|
|
"""Calculates and returns the penalty score based on state of this QR Code's current modules.
|
|
|
|
|
This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score."""
|
|
|
|
|
result = 0
|
|
|
|
@ -518,7 +519,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
# ---- Private helper functions ----
|
|
|
|
|
|
|
|
|
|
def _get_alignment_pattern_positions(self):
|
|
|
|
|
def _get_alignment_pattern_positions(self) -> List[int]:
|
|
|
|
|
"""Returns an ascending list of positions of alignment patterns for this version number.
|
|
|
|
|
Each position is in the range [0,177), and are used on both the x and y axes.
|
|
|
|
|
This could be implemented as lookup table of 40 variable-length lists of integers."""
|
|
|
|
@ -534,7 +535,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _get_num_raw_data_modules(ver):
|
|
|
|
|
def _get_num_raw_data_modules(ver) -> int:
|
|
|
|
|
"""Returns the number of data bits that can be stored in a QR Code of the given version number, after
|
|
|
|
|
all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
|
|
|
|
|
The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table."""
|
|
|
|
@ -551,7 +552,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _get_num_data_codewords(ver, ecl):
|
|
|
|
|
def _get_num_data_codewords(ver, ecl: QrCode.Ecc) -> int:
|
|
|
|
|
"""Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
|
|
|
|
|
QR Code of the given version number and error correction level, with remainder bits discarded.
|
|
|
|
|
This stateless pure function could be implemented as a (40*4)-cell lookup table."""
|
|
|
|
@ -561,7 +562,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _reed_solomon_compute_divisor(degree):
|
|
|
|
|
def _reed_solomon_compute_divisor(degree: int) -> List[int]:
|
|
|
|
|
"""Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be
|
|
|
|
|
implemented as a lookup table over all possible parameter values, instead of as an algorithm."""
|
|
|
|
|
if not (1 <= degree <= 255):
|
|
|
|
@ -585,7 +586,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _reed_solomon_compute_remainder(data, divisor):
|
|
|
|
|
def _reed_solomon_compute_remainder(data: List[int], divisor: List[int]) -> List[int]:
|
|
|
|
|
"""Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials."""
|
|
|
|
|
result = [0] * len(divisor)
|
|
|
|
|
for b in data: # Polynomial division
|
|
|
|
@ -597,7 +598,7 @@ class QrCode:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _reed_solomon_multiply(x, y):
|
|
|
|
|
def _reed_solomon_multiply(x: int, y: int) -> int:
|
|
|
|
|
"""Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result
|
|
|
|
|
are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8."""
|
|
|
|
|
if x >> 8 != 0 or y >> 8 != 0:
|
|
|
|
@ -611,7 +612,7 @@ class QrCode:
|
|
|
|
|
return z
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _finder_penalty_count_patterns(self, runhistory):
|
|
|
|
|
def _finder_penalty_count_patterns(self, runhistory: collections.deque) -> int:
|
|
|
|
|
"""Can only be called immediately after a white run is added, and
|
|
|
|
|
returns either 0, 1, or 2. A helper function for _get_penalty_score()."""
|
|
|
|
|
n = runhistory[1]
|
|
|
|
@ -621,7 +622,7 @@ class QrCode:
|
|
|
|
|
+ (1 if (core and runhistory[6] >= n * 4 and runhistory[0] >= n) else 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _finder_penalty_terminate_and_count(self, currentruncolor, currentrunlength, runhistory):
|
|
|
|
|
def _finder_penalty_terminate_and_count(self, currentruncolor: bool, currentrunlength: int, runhistory: collections.deque) -> int:
|
|
|
|
|
"""Must be called at the end of a line (row or column) of modules. A helper function for _get_penalty_score()."""
|
|
|
|
|
if currentruncolor: # Terminate black run
|
|
|
|
|
self._finder_penalty_add_history(currentrunlength, runhistory)
|
|
|
|
@ -631,7 +632,7 @@ class QrCode:
|
|
|
|
|
return self._finder_penalty_count_patterns(runhistory)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _finder_penalty_add_history(self, currentrunlength, runhistory):
|
|
|
|
|
def _finder_penalty_add_history(self, currentrunlength: int, runhistory: collections.deque) -> None:
|
|
|
|
|
if runhistory[0] == 0:
|
|
|
|
|
currentrunlength += self._size # Add white border to initial run
|
|
|
|
|
runhistory.appendleft(currentrunlength)
|
|
|
|
@ -681,7 +682,7 @@ class QrCode:
|
|
|
|
|
class Ecc:
|
|
|
|
|
"""The error correction level in a QR Code symbol. Immutable."""
|
|
|
|
|
# Private constructor
|
|
|
|
|
def __init__(self, i, fb):
|
|
|
|
|
def __init__(self, i: int, fb: int) -> None:
|
|
|
|
|
self.ordinal = i # (Public) In the range 0 to 3 (unsigned 2-bit integer)
|
|
|
|
|
self.formatbits = fb # (Package-private) In the range 0 to 3 (unsigned 2-bit integer)
|
|
|
|
|
|
|
|
|
@ -715,7 +716,7 @@ class QrSegment:
|
|
|
|
|
# ---- Static factory functions (mid level) ----
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def make_bytes(data):
|
|
|
|
|
def make_bytes(data) -> QrSegment:
|
|
|
|
|
"""Returns a segment representing the given binary data encoded in byte mode.
|
|
|
|
|
All input byte lists are acceptable. Any text string can be converted to
|
|
|
|
|
UTF-8 bytes (s.encode("UTF-8")) and encoded as a byte mode segment."""
|
|
|
|
@ -728,7 +729,7 @@ class QrSegment:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def make_numeric(digits):
|
|
|
|
|
def make_numeric(digits: str) -> QrSegment:
|
|
|
|
|
"""Returns a segment representing the given string of decimal digits encoded in numeric mode."""
|
|
|
|
|
if QrSegment.NUMERIC_REGEX.match(digits) is None:
|
|
|
|
|
raise ValueError("String contains non-numeric characters")
|
|
|
|
@ -742,7 +743,7 @@ class QrSegment:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def make_alphanumeric(text):
|
|
|
|
|
def make_alphanumeric(text: str) -> QrSegment:
|
|
|
|
|
"""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."""
|
|
|
|
@ -759,7 +760,7 @@ class QrSegment:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def make_segments(text):
|
|
|
|
|
def make_segments(text: str) -> List[QrSegment]:
|
|
|
|
|
"""Returns a new mutable 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."""
|
|
|
|
|
if not isinstance(text, str):
|
|
|
|
@ -777,7 +778,7 @@ class QrSegment:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def make_eci(assignval):
|
|
|
|
|
def make_eci(assignval: int) -> QrSegment:
|
|
|
|
|
"""Returns a segment representing an Extended Channel Interpretation
|
|
|
|
|
(ECI) designator with the given assignment value."""
|
|
|
|
|
bb = _BitBuffer()
|
|
|
|
@ -798,7 +799,7 @@ class QrSegment:
|
|
|
|
|
|
|
|
|
|
# ---- Constructor (low level) ----
|
|
|
|
|
|
|
|
|
|
def __init__(self, mode, numch, bitdata):
|
|
|
|
|
def __init__(self, mode: QrSegment.Mode, numch: int, bitdata: List[int]) -> None:
|
|
|
|
|
"""Creates a new QR Code segment with the given attributes and data.
|
|
|
|
|
The character count (numch) must agree with the mode and the bit buffer length,
|
|
|
|
|
but the constraint isn't checked. The given bit buffer is cloned and stored."""
|
|
|
|
@ -822,22 +823,22 @@ class QrSegment:
|
|
|
|
|
|
|
|
|
|
# ---- Accessor methods ----
|
|
|
|
|
|
|
|
|
|
def get_mode(self):
|
|
|
|
|
def get_mode(self) -> QrSegment.Mode:
|
|
|
|
|
"""Returns the mode field of this segment."""
|
|
|
|
|
return self._mode
|
|
|
|
|
|
|
|
|
|
def get_num_chars(self):
|
|
|
|
|
def get_num_chars(self) -> int:
|
|
|
|
|
"""Returns the character count field of this segment."""
|
|
|
|
|
return self._numchars
|
|
|
|
|
|
|
|
|
|
def get_data(self):
|
|
|
|
|
def get_data(self) -> List[int]:
|
|
|
|
|
"""Returns a new copy of the data bits of this segment."""
|
|
|
|
|
return list(self._bitdata) # Make defensive copy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Package-private function
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_total_bits(segs, version):
|
|
|
|
|
def get_total_bits(segs, version: int) -> Optional[int]:
|
|
|
|
|
"""Calculates the number of bits needed to encode the given segments at
|
|
|
|
|
the given version. Returns a non-negative number if successful. Otherwise
|
|
|
|
|
returns None if a segment has too many characters to fit its length field."""
|
|
|
|
@ -874,17 +875,17 @@ class QrSegment:
|
|
|
|
|
"""Describes how a segment's data bits are interpreted. Immutable."""
|
|
|
|
|
|
|
|
|
|
# Private constructor
|
|
|
|
|
def __init__(self, modebits, charcounts):
|
|
|
|
|
def __init__(self, modebits: int, charcounts: Tuple[int,int,int]):
|
|
|
|
|
self._modebits = modebits # The mode indicator bits, which is a uint4 value (range 0 to 15)
|
|
|
|
|
self._charcounts = charcounts # Number of character count bits for three different version ranges
|
|
|
|
|
|
|
|
|
|
# Package-private method
|
|
|
|
|
def get_mode_bits(self):
|
|
|
|
|
def get_mode_bits(self) -> int:
|
|
|
|
|
"""Returns an unsigned 4-bit integer value (range 0 to 15) representing the mode indicator bits for this mode object."""
|
|
|
|
|
return self._modebits
|
|
|
|
|
|
|
|
|
|
# Package-private method
|
|
|
|
|
def num_char_count_bits(self, ver):
|
|
|
|
|
def num_char_count_bits(self, ver: int) -> int:
|
|
|
|
|
"""Returns the bit width of the character count field for a segment in this mode
|
|
|
|
|
in a QR Code at the given version number. The result is in the range [0, 16]."""
|
|
|
|
|
return self._charcounts[(ver + 7) // 17]
|
|
|
|
@ -910,7 +911,7 @@ class QrSegment:
|
|
|
|
|
class _BitBuffer(list):
|
|
|
|
|
"""An appendable sequence of bits (0s and 1s). Mainly used by QrSegment."""
|
|
|
|
|
|
|
|
|
|
def append_bits(self, val, n):
|
|
|
|
|
def append_bits(self, val: int, n: int) -> None:
|
|
|
|
|
"""Appends the given number of low-order bits of the given
|
|
|
|
|
value to this buffer. Requires n >= 0 and 0 <= val < 2^n."""
|
|
|
|
|
if n < 0 or val >> n != 0:
|
|
|
|
@ -918,7 +919,7 @@ class _BitBuffer(list):
|
|
|
|
|
self.extend(((val >> i) & 1) for i in reversed(range(n)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_bit(x, i):
|
|
|
|
|
def _get_bit(x: int, i: int) -> bool:
|
|
|
|
|
"""Returns true iff the i'th bit of x is set to 1."""
|
|
|
|
|
return (x >> i) & 1 != 0
|
|
|
|
|
|
|
|
|
|