From 5217af7cf69c02f77c88bf489e1304676035bf2d Mon Sep 17 00:00:00 2001 From: Thomas Klausner Date: Sat, 8 Feb 2020 18:48:32 +0100 Subject: [PATCH 01/22] Honor LDFLAGS when building executables. --- c/Makefile | 4 ++-- cpp/Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/c/Makefile b/c/Makefile index fd0c367..314d175 100644 --- a/c/Makefile +++ b/c/Makefile @@ -65,11 +65,11 @@ clean: # Executable files %: %.o $(LIBFILE) - $(CC) $(CFLAGS) -o $@ $< -L . -l $(LIB) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -L . -l $(LIB) # Special executable qrcodegen-test: qrcodegen-test.c $(LIBOBJ:%.o=%.c) - $(CC) $(CFLAGS) -DQRCODEGEN_TEST -o $@ $^ + $(CC) $(CFLAGS) $(LDFLAGS) -DQRCODEGEN_TEST -o $@ $^ # The library $(LIBFILE): $(LIBOBJ) diff --git a/cpp/Makefile b/cpp/Makefile index f83c512..374bfe9 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -65,7 +65,7 @@ clean: # Executable files %: %.o $(LIBFILE) - $(CXX) $(CXXFLAGS) -o $@ $< -L . -l $(LIB) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $< -L . -l $(LIB) # The library $(LIBFILE): $(LIBOBJ) From 98963e5cbaa8a643c484e9d3c18b9b0b86c6ef24 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 14 Oct 2020 01:27:54 +0000 Subject: [PATCH 02/22] Tweaked Rust code to narrow the bit width of QrCodeEcc.format_bits(). --- rust/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6a90c81..66d354c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -438,7 +438,7 @@ impl QrCode { // Calculate error correction code and pack bits let bits: u32 = { // errcorrlvl is uint2, mask is uint3 - let data: u32 = self.errorcorrectionlevel.format_bits() << 3 | u32::from(mask.value()); + let data: u32 = u32::from(self.errorcorrectionlevel.format_bits() << 3 | mask.value()); let mut rem: u32 = data; for _ in 0 .. 10 { rem = (rem << 1) ^ ((rem >> 9) * 0x537); @@ -949,7 +949,7 @@ impl QrCodeEcc { // Returns an unsigned 2-bit integer (in the range 0 to 3). - fn format_bits(self) -> u32 { + fn format_bits(self) -> u8 { use QrCodeEcc::*; match self { Low => 1, From 705ce44efd233cfdf23fa983d88d95ebb90b13b4 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 14 Oct 2020 01:34:20 +0000 Subject: [PATCH 03/22] Fixed arithmetic overflow in Rust code on platforms where usize is 16 bits wide. --- rust/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 66d354c..b247258 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1141,8 +1141,11 @@ impl QrSegment { let mut result: usize = 0; for seg in segs { let ccbits = seg.mode.num_char_count_bits(version); - if seg.numchars >= 1 << ccbits { - return None; // The segment's length doesn't fit the field's bit width + // ccbits can be as large as 16, but usize can be as small as 16 + if let Some(limit) = 1usize.checked_shl(u32::from(ccbits)) { + if seg.numchars >= limit { + return None; // The segment's length doesn't fit the field's bit width + } } result = result.checked_add(4 + usize::from(ccbits) + seg.data.len())?; } From 43020cbd67847b7c2726cb8fc8dcc28aa0e47b68 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 14 Oct 2020 01:38:21 +0000 Subject: [PATCH 04/22] Fixed Rust code to avoid arithmetic overflow when a segment's bit length is very near usize::MAX. --- rust/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b247258..88340a1 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1147,7 +1147,8 @@ impl QrSegment { return None; // The segment's length doesn't fit the field's bit width } } - result = result.checked_add(4 + usize::from(ccbits) + seg.data.len())?; + result = result.checked_add(4 + usize::from(ccbits))?; + result = result.checked_add(seg.data.len())?; } Some(result) } From d00cbd35857b26d987f40417b8716f69c0cd7f36 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 14 Oct 2020 01:58:56 +0000 Subject: [PATCH 05/22] Added static types to some variables in Rust code. --- rust/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 88340a1..17da2fc 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -207,7 +207,7 @@ impl QrCode { assert!(minversion.value() <= maxversion.value(), "Invalid value"); // Find the minimal version number to use - let mut version = minversion; + let mut version: Version = minversion; let datausedbits: usize = loop { // Number of data bits available let datacapacitybits: usize = QrCode::get_num_data_codewords(version, ecl) * 8; @@ -245,9 +245,9 @@ impl QrCode { // Add terminator and pad up to a byte if applicable let datacapacitybits: usize = QrCode::get_num_data_codewords(version, ecl) * 8; assert!(bb.0.len() <= datacapacitybits); - let numzerobits = std::cmp::min(4, datacapacitybits - bb.0.len()); + let numzerobits: usize = std::cmp::min(4, datacapacitybits - bb.0.len()); bb.append_bits(0, numzerobits as u8); - let numzerobits = bb.0.len().wrapping_neg() & 7; + let numzerobits: usize = bb.0.len().wrapping_neg() & 7; bb.append_bits(0, numzerobits as u8); assert_eq!(bb.0.len() % 8, 0, "Assertion error"); @@ -539,8 +539,8 @@ impl QrCode { // 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. fn add_ecc_and_interleave(&self, data: &[u8]) -> Vec { - let ver = self.version; - let ecl = self.errorcorrectionlevel; + let ver: Version = self.version; + let ecl: QrCodeEcc = self.errorcorrectionlevel; assert_eq!(data.len(), QrCode::get_num_data_codewords(ver, ecl), "Illegal argument"); // Calculate parameter numbers @@ -720,7 +720,7 @@ impl QrCode { // 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 unsigned bytes. fn get_alignment_pattern_positions(&self) -> Vec { - let ver = self.version.value(); + let ver: u8 = self.version.value(); if ver == 1 { vec![] } else { @@ -1047,7 +1047,7 @@ impl QrSegment { let mut accumdata: u32 = 0; let mut accumcount: u32 = 0; for &c in text { - let i = ALPHANUMERIC_CHARSET.iter().position(|&x| x == c) + let i: usize = ALPHANUMERIC_CHARSET.iter().position(|&x| x == c) .expect("String contains unencodable characters in alphanumeric mode"); accumdata = accumdata * 45 + (i as u32); accumcount += 1; @@ -1140,7 +1140,7 @@ impl QrSegment { fn get_total_bits(segs: &[Self], version: Version) -> Option { let mut result: usize = 0; for seg in segs { - let ccbits = seg.mode.num_char_count_bits(version); + let ccbits: u8 = seg.mode.num_char_count_bits(version); // ccbits can be as large as 16, but usize can be as small as 16 if let Some(limit) = 1usize.checked_shl(u32::from(ccbits)) { if seg.numchars >= limit { From bafd258293031bcff605932a39eed0bbe8041734 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 14 Oct 2020 02:02:06 +0000 Subject: [PATCH 06/22] Clarified a few pieces of Rust code. --- rust/examples/qrcodegen-demo.rs | 4 ++-- rust/src/lib.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rust/examples/qrcodegen-demo.rs b/rust/examples/qrcodegen-demo.rs index 80b2b7a..67665f8 100644 --- a/rust/examples/qrcodegen-demo.rs +++ b/rust/examples/qrcodegen-demo.rs @@ -128,8 +128,8 @@ fn do_segment_demo() { 0x0000, 0x0208, 0x01FF, 0x0008, ]; let mut bb = qrcodegen::BitBuffer(Vec::new()); - for c in &kanjichars { - bb.append_bits(*c, 13); + for &c in &kanjichars { + bb.append_bits(c, 13); } let segs = vec![ QrSegment::new(qrcodegen::QrSegmentMode::Kanji, kanjichars.len(), bb.0), diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 17da2fc..d342e32 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -296,7 +296,7 @@ impl QrCode { // Do masking if mask.is_none() { // Automatically choose best mask - let mut minpenalty: i32 = std::i32::MAX; + let mut minpenalty = std::i32::MAX; for i in 0u8 .. 8 { let newmask = Mask::new(i); result.apply_mask(newmask); @@ -617,10 +617,9 @@ impl QrCode { // the same mask value a second time will undo the mask. A final well-formed // QR Code needs exactly one (not zero, two, etc.) mask applied. fn apply_mask(&mut self, mask: Mask) { - let mask: u8 = mask.value(); for y in 0 .. self.size { for x in 0 .. self.size { - let invert: bool = match mask { + let invert: bool = match mask.value() { 0 => (x + y) % 2 == 0, 1 => y % 2 == 0, 2 => x % 3 == 0, From 8cbd1f506ac59406b18d13a199187c2756f2e06f Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Wed, 14 Oct 2020 02:09:27 +0000 Subject: [PATCH 07/22] Changed Rust API to move the version min/max values into associated constants for its type. --- rust/examples/qrcodegen-demo.rs | 15 +++++++-------- rust/examples/qrcodegen-worker.rs | 4 ++-- rust/src/lib.rs | 21 ++++++++++----------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/rust/examples/qrcodegen-demo.rs b/rust/examples/qrcodegen-demo.rs index 67665f8..91fee9d 100644 --- a/rust/examples/qrcodegen-demo.rs +++ b/rust/examples/qrcodegen-demo.rs @@ -29,8 +29,7 @@ use qrcodegen::Mask; use qrcodegen::QrCode; use qrcodegen::QrCodeEcc; use qrcodegen::QrSegment; -use qrcodegen::QrCode_MAX_VERSION; -use qrcodegen::QrCode_MIN_VERSION; +use qrcodegen::Version; // The main application program. @@ -143,20 +142,20 @@ fn do_segment_demo() { fn do_mask_demo() { // Project Nayuki URL let segs = QrSegment::make_segments(&to_chars("https://www.nayuki.io/")); - let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::High, QrCode_MIN_VERSION, QrCode_MAX_VERSION, None, true).unwrap(); // Automatic mask + let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::High, Version::MIN, Version::MAX, None, true).unwrap(); // Automatic mask print_qr(&qr); - let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::High, QrCode_MIN_VERSION, QrCode_MAX_VERSION, Some(Mask::new(3)), true).unwrap(); // Force mask 3 + let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::High, Version::MIN, Version::MAX, Some(Mask::new(3)), true).unwrap(); // Force mask 3 print_qr(&qr); // Chinese text as UTF-8 let segs = QrSegment::make_segments(&to_chars("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫")); - let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, QrCode_MIN_VERSION, QrCode_MAX_VERSION, Some(Mask::new(0)), true).unwrap(); // Force mask 0 + let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, Version::MIN, Version::MAX, Some(Mask::new(0)), true).unwrap(); // Force mask 0 print_qr(&qr); - let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, QrCode_MIN_VERSION, QrCode_MAX_VERSION, Some(Mask::new(1)), true).unwrap(); // Force mask 1 + let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, Version::MIN, Version::MAX, Some(Mask::new(1)), true).unwrap(); // Force mask 1 print_qr(&qr); - let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, QrCode_MIN_VERSION, QrCode_MAX_VERSION, Some(Mask::new(5)), true).unwrap(); // Force mask 5 + let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, Version::MIN, Version::MAX, Some(Mask::new(5)), true).unwrap(); // Force mask 5 print_qr(&qr); - let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, QrCode_MIN_VERSION, QrCode_MAX_VERSION, Some(Mask::new(7)), true).unwrap(); // Force mask 7 + let qr = QrCode::encode_segments_advanced(&segs, QrCodeEcc::Medium, Version::MIN, Version::MAX, Some(Mask::new(7)), true).unwrap(); // Force mask 7 print_qr(&qr); } diff --git a/rust/examples/qrcodegen-worker.rs b/rust/examples/qrcodegen-worker.rs index da9a0a3..7238392 100644 --- a/rust/examples/qrcodegen-worker.rs +++ b/rust/examples/qrcodegen-worker.rs @@ -59,9 +59,9 @@ fn main() { let mask = read_int(); let boostecl = read_int(); assert!(0 <= errcorlvl && errcorlvl <= 3); - assert!(i16::from(qrcodegen::QrCode_MIN_VERSION.value()) <= minversion + assert!(i16::from(Version::MIN.value()) <= minversion && minversion <= maxversion - && maxversion <= i16::from(qrcodegen::QrCode_MAX_VERSION.value())); + && maxversion <= i16::from(Version::MAX.value())); assert!(-1 <= mask && mask <= 7); assert!(boostecl >> 1 == 0); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index d342e32..a2c4d76 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -184,7 +184,7 @@ impl QrCode { /// Returns a wrapped `QrCode` if successful, or `Err` if the /// data is too long to fit in any version at the given ECC level. pub fn encode_segments(segs: &[QrSegment], ecl: QrCodeEcc) -> Result { - QrCode::encode_segments_advanced(segs, ecl, QrCode_MIN_VERSION, QrCode_MAX_VERSION, None, true) + QrCode::encode_segments_advanced(segs, ecl, Version::MIN, Version::MAX, None, true) } @@ -883,13 +883,6 @@ impl FinderPenalty { /*---- Constants and tables ----*/ -/// The minimum version number supported in the QR Code Model 2 standard. -pub const QrCode_MIN_VERSION: Version = Version( 1); - -/// The maximum version number supported in the QR Code Model 2 standard. -pub const QrCode_MAX_VERSION: Version = Version(40); - - // For use in get_penalty_score(), when evaluating which mask is best. const PENALTY_N1: i32 = 3; const PENALTY_N2: i32 = 3; @@ -1252,8 +1245,8 @@ impl BitBuffer { /// /// - Decrease the error correction level if it was greater than `QrCodeEcc::Low`. /// - If the `encode_segments_advanced()` function was called, then increase the maxversion -/// argument if it was less than `QrCode_MAX_VERSION`. (This advice does not apply to the -/// other factory functions because they search all versions up to `QrCode_MAX_VERSION`.) +/// argument if it was less than `Version::MAX`. (This advice does not apply to the +/// other factory functions because they search all versions up to `Version::MAX`.) /// - Split the text data into better or optimal segments in order to reduce the number of bits required. /// - Change the text or binary data to be shorter. /// - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). @@ -1279,11 +1272,17 @@ impl std::fmt::Display for DataTooLong { pub struct Version(u8); impl Version { + /// The minimum version number supported in the QR Code Model 2 standard. + pub const MIN: Version = Version( 1); + + /// The maximum version number supported in the QR Code Model 2 standard. + pub const MAX: Version = Version(40); + /// Creates a version object from the given number. /// /// Panics if the number is outside the range [1, 40]. pub fn new(ver: u8) -> Self { - assert!(QrCode_MIN_VERSION.value() <= ver && ver <= QrCode_MAX_VERSION.value(), "Version number out of range"); + assert!(Version::MIN.value() <= ver && ver <= Version::MAX.value(), "Version number out of range"); Self(ver) } From f9d1172e2983bf4cf72e6b0db7b12c0172fd3606 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 25 Jan 2021 23:11:54 +0000 Subject: [PATCH 08/22] Fixed the names of some methods in comments. --- python/qrcodegen.py | 4 ++-- rust/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 967d5ae..66f84b8 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -446,7 +446,7 @@ class QrCode: 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 + before masking. Due to the arithmetic of XOR, calling _apply_mask() with the same mask value a second time will undo the mask. A final well-formed QR Code needs exactly one (not zero, two, etc.) mask applied.""" if not (0 <= mask <= 7): @@ -644,7 +644,7 @@ class QrCode: MIN_VERSION = 1 # The minimum version number supported in the QR Code Model 2 standard MAX_VERSION = 40 # The maximum version number supported in the QR Code Model 2 standard - # For use in getPenaltyScore(), when evaluating which mask is best. + # For use in _get_penalty_score(), when evaluating which mask is best. _PENALTY_N1 = 3 _PENALTY_N2 = 3 _PENALTY_N3 = 40 diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a2c4d76..64f563c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -613,7 +613,7 @@ impl QrCode { // 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 + // before masking. Due to the arithmetic of XOR, calling apply_mask() with // the same mask value a second time will undo the mask. A final well-formed // QR Code needs exactly one (not zero, two, etc.) mask applied. fn apply_mask(&mut self, mask: Mask) { From a807ee27db5e1724aa47ea70c83d738c0a0f0b42 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 25 Jan 2021 23:15:49 +0000 Subject: [PATCH 09/22] Added type annotations to instance fields in Python code. --- python/qrcodegen.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 66f84b8..f311f1a 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -168,6 +168,16 @@ class QrCode: return QrCode(version, ecl, datacodewords, mask) + # ---- Private fields ---- + + _version: int + _size: int + _errcorlvl: QrCode.Ecc + _mask: int + _modules: List[List[bool]] + _isfunction: List[List[bool]] + + # ---- Constructor (low level) ---- def __init__(self, version: int, errcorlvl: QrCode.Ecc, datacodewords: List[int], mask: int) -> None: @@ -681,6 +691,9 @@ class QrCode: # ---- Public helper enumeration ---- class Ecc: + ordinal: int + formatbits: int + """The error correction level in a QR Code symbol. Immutable.""" # Private constructor def __init__(self, i: int, fb: int) -> None: @@ -798,6 +811,13 @@ class QrSegment: return QrSegment(QrSegment.Mode.ECI, 0, bb) + # ---- Private fields ---- + + _mode: QrSegment.Mode + _numchars: int + _bitdata: List[int] + + # ---- Constructor (low level) ---- def __init__(self, mode: QrSegment.Mode, numch: int, bitdata: List[int]) -> None: @@ -875,6 +895,9 @@ class QrSegment: class Mode: """Describes how a segment's data bits are interpreted. Immutable.""" + _modebits: int + _charcounts: Tuple[int,int,int] + # Private constructor 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) From 8518684c0f33d004fa93971be2c6a8eca3167d1e Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Mon, 25 Jan 2021 23:23:00 +0000 Subject: [PATCH 10/22] Moved comments in Python code from field assignments to field declarations. --- python/qrcodegen.py | 63 +++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index f311f1a..d4ac1eb 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -170,11 +170,27 @@ class QrCode: # ---- Private fields ---- + # The version number of this QR Code, which is between 1 and 40 (inclusive). + # This determines the size of this barcode. _version: int + + # The width and height of this QR Code, measured in modules, between + # 21 and 177 (inclusive). This is equal to version * 4 + 17. _size: int + + # The error correction level used in this QR Code. _errcorlvl: QrCode.Ecc + + # The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + # Even if a QR Code is created with automatic masking requested (mask = -1), + # the resulting object still has a mask value between 0 and 7. _mask: int + + # The modules of this QR Code (False = white, True = black). + # Immutable after constructor finishes. Accessed through get_module(). _modules: List[List[bool]] + + # Indicates function modules that are not subjected to masking. Discarded when constructor finishes. _isfunction: List[List[bool]] @@ -194,22 +210,12 @@ class QrCode: if not isinstance(errcorlvl, QrCode.Ecc): raise TypeError("QrCode.Ecc expected") - # The version number of this QR Code, which is between 1 and 40 (inclusive). - # This determines the size of this barcode. self._version = version - - # The width and height of this QR Code, measured in modules, between - # 21 and 177 (inclusive). This is equal to version * 4 + 17. self._size = version * 4 + 17 - - # The error correction level used in this QR Code. self._errcorlvl = errcorlvl # Initialize both grids to be size*size arrays of Boolean false - # The modules of this QR Code (False = white, True = black). - # Immutable after constructor finishes. Accessed through get_module(). self._modules = [[False] * self._size for _ in range(self._size)] # Initially all white - # Indicates function modules that are not subjected to masking. Discarded when constructor finishes self._isfunction = [[False] * self._size for _ in range(self._size)] # Compute ECC, draw modules @@ -231,10 +237,6 @@ class QrCode: assert 0 <= mask <= 7 self._apply_mask(mask) # Apply the final choice of mask self._draw_format_bits(mask) # Overwrite old format bits - - # The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). - # Even if a QR Code is created with automatic masking requested (mask = -1), - # the resulting object still has a mask value between 0 and 7. self._mask = mask del self._isfunction @@ -691,14 +693,14 @@ class QrCode: # ---- Public helper enumeration ---- class Ecc: - ordinal: int - formatbits: int + ordinal: int # (Public) In the range 0 to 3 (unsigned 2-bit integer) + formatbits: int # (Package-private) In the range 0 to 3 (unsigned 2-bit integer) """The error correction level in a QR Code symbol. Immutable.""" # Private constructor 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) + self.ordinal = i + self.formatbits = fb # Placeholders LOW : QrCode.Ecc @@ -813,8 +815,16 @@ class QrSegment: # ---- Private fields ---- + # The mode indicator of this segment. Accessed through get_mode(). _mode: QrSegment.Mode + + # The length of this segment's unencoded data. Measured in characters for + # numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + # Always zero or positive. Not the same as the data's bit length. + # Accessed through get_num_chars(). _numchars: int + + # The data bits of this segment. Accessed through get_data(). _bitdata: List[int] @@ -828,17 +838,8 @@ class QrSegment: raise TypeError("QrSegment.Mode expected") if numch < 0: raise ValueError() - - # The mode indicator of this segment. Accessed through get_mode(). self._mode = mode - - # The length of this segment's unencoded data. Measured in characters for - # numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. - # Always zero or positive. Not the same as the data's bit length. - # Accessed through get_num_chars(). self._numchars = numch - - # The data bits of this segment. Accessed through get_data(). self._bitdata = list(bitdata) # Make defensive copy @@ -895,13 +896,13 @@ class QrSegment: class Mode: """Describes how a segment's data bits are interpreted. Immutable.""" - _modebits: int - _charcounts: Tuple[int,int,int] + _modebits: int # The mode indicator bits, which is a uint4 value (range 0 to 15) + _charcounts: Tuple[int,int,int] # Number of character count bits for three different version ranges # Private constructor 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 + self._modebits = modebits + self._charcounts = charcounts # Package-private method def get_mode_bits(self) -> int: From a999dca15f3a1184402bf57b9ce15047b80dbf1b Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Thu, 1 Jul 2021 04:35:23 +0000 Subject: [PATCH 11/22] Simplified an expression because C++11 natively supports for-each over a braced list, without needing to construct a typed object. --- cpp/QrCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/QrCode.cpp b/cpp/QrCode.cpp index b9de862..79890a5 100644 --- a/cpp/QrCode.cpp +++ b/cpp/QrCode.cpp @@ -279,7 +279,7 @@ QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, throw std::logic_error("Assertion error"); // Increase the error correction level while the data still fits in the current version number - for (Ecc newEcl : vector{Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high + for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) ecl = newEcl; } From 68b2b7782b39a5557bc976b57742256b6f1195e3 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sat, 24 Jul 2021 21:54:42 +0000 Subject: [PATCH 12/22] Simplified Java and C++ code to remove unnecessary `this`, also improving consistency with other field assignments, enabled by a local variable renaming in commit 67c62461d380. --- cpp/QrCode.cpp | 2 +- java/src/main/java/io/nayuki/qrcodegen/QrCode.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/QrCode.cpp b/cpp/QrCode.cpp index 79890a5..60cfd67 100644 --- a/cpp/QrCode.cpp +++ b/cpp/QrCode.cpp @@ -351,7 +351,7 @@ QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) } if (msk < 0 || msk > 7) throw std::logic_error("Assertion error"); - this->mask = msk; + mask = msk; applyMask(msk); // Apply the final choice of mask drawFormatBits(msk); // Overwrite old format bits diff --git a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java index ea0196c..1b4c7d0 100644 --- a/java/src/main/java/io/nayuki/qrcodegen/QrCode.java +++ b/java/src/main/java/io/nayuki/qrcodegen/QrCode.java @@ -263,7 +263,7 @@ public final class QrCode { drawFunctionPatterns(); byte[] allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(allCodewords); - this.mask = handleConstructorMasking(msk); + mask = handleConstructorMasking(msk); isFunction = null; } From 772a311c564f8f2c4371f2693dc0bf67974e9ffb Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Sun, 25 Jul 2021 03:30:37 +0000 Subject: [PATCH 13/22] Tweaked a bit of C++ code to use strict bounds checking for consistency. --- cpp/QrCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/QrCode.cpp b/cpp/QrCode.cpp index 60cfd67..90a3989 100644 --- a/cpp/QrCode.cpp +++ b/cpp/QrCode.cpp @@ -310,7 +310,7 @@ QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, // Pack bits into bytes in big endian vector dataCodewords(bb.size() / 8); for (size_t i = 0; i < bb.size(); i++) - dataCodewords[i >> 3] |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); + dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); // Create the QR Code object return QrCode(version, ecl, dataCodewords, mask); From d11eb098cb1a6e84bd3734e849842cc1fac9bf0a Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:18:32 +0000 Subject: [PATCH 14/22] Added more type annotations to class-level members in Python library code, continuing the work of commit 55dd3c881ea1. --- python/qrcodegen.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index d4ac1eb..529cb3b 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -23,7 +23,8 @@ from __future__ import annotations import collections, itertools, re -from typing import List, Optional, Tuple +from collections.abc import Sequence +from typing import Callable, Dict, List, Optional, Tuple """ @@ -260,7 +261,7 @@ class QrCode: """Returns this QR Code's mask, in the range [0, 7].""" return self._mask - def get_module(self, x, y) -> bool: + def get_module(self, x: int, y: int) -> 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.""" @@ -316,7 +317,7 @@ class QrCode: self._draw_version() - def _draw_format_bits(self, mask) -> None: + def _draw_format_bits(self, mask: int) -> 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 @@ -366,7 +367,7 @@ class QrCode: self._set_function_module(b, a, bit) - def _draw_finder_pattern(self, x, y) -> None: + def _draw_finder_pattern(self, x: int, y: int) -> 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): @@ -377,7 +378,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) -> None: + def _draw_alignment_pattern(self, x: int, y: int) -> 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): @@ -548,7 +549,7 @@ class QrCode: @staticmethod - def _get_num_raw_data_modules(ver) -> int: + def _get_num_raw_data_modules(ver: int) -> 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.""" @@ -565,7 +566,7 @@ class QrCode: @staticmethod - def _get_num_data_codewords(ver, ecl: QrCode.Ecc) -> int: + def _get_num_data_codewords(ver: int, 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.""" @@ -653,16 +654,16 @@ class QrCode: # ---- Constants and tables ---- - MIN_VERSION = 1 # The minimum version number supported in the QR Code Model 2 standard - MAX_VERSION = 40 # The maximum version number supported in the QR Code Model 2 standard + MIN_VERSION: int = 1 # The minimum version number supported in the QR Code Model 2 standard + MAX_VERSION: int = 40 # The maximum version number supported in the QR Code Model 2 standard # For use in _get_penalty_score(), when evaluating which mask is best. - _PENALTY_N1 = 3 - _PENALTY_N2 = 3 - _PENALTY_N3 = 40 - _PENALTY_N4 = 10 + _PENALTY_N1: int = 3 + _PENALTY_N2: int = 3 + _PENALTY_N3: int = 40 + _PENALTY_N4: int = 10 - _ECC_CODEWORDS_PER_BLOCK = ( + _ECC_CODEWORDS_PER_BLOCK: Sequence[Sequence[int]] = ( # Version: (note that index 0 is for padding, and is set to an illegal value) # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level (-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30), # Low @@ -670,7 +671,7 @@ class QrCode: (-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30), # Quartile (-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30)) # High - _NUM_ERROR_CORRECTION_BLOCKS = ( + _NUM_ERROR_CORRECTION_BLOCKS: Sequence[Sequence[int]] = ( # Version: (note that index 0 is for padding, and is set to an illegal value) # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level (-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25), # Low @@ -678,7 +679,7 @@ class QrCode: (-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68), # Quartile (-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81)) # High - _MASK_PATTERNS = ( + _MASK_PATTERNS: Sequence[Callable[[int,int],int]] = ( (lambda x, y: (x + y) % 2 ), (lambda x, y: y % 2 ), (lambda x, y: x % 3 ), @@ -878,17 +879,16 @@ class QrSegment: # (Public) Describes precisely all strings that are encodable in numeric mode. # To test whether a string s is encodable: ok = NUMERIC_REGEX.fullmatch(s) is not None # A string is encodable iff each character is in the range 0 to 9. - NUMERIC_REGEX = re.compile(r"[0-9]*") + NUMERIC_REGEX: re.Pattern = re.compile(r"[0-9]*") # (Public) Describes precisely all strings that are encodable in alphanumeric mode. # To test whether a string s is encodable: ok = ALPHANUMERIC_REGEX.fullmatch(s) is not None # A string is encodable iff each character is in the following set: 0 to 9, A to Z # (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. - - ALPHANUMERIC_REGEX = re.compile(r"[A-Z0-9 $%*+./:-]*") + ALPHANUMERIC_REGEX: re.Pattern = re.compile(r"[A-Z0-9 $%*+./:-]*") # (Private) Dictionary of "0"->0, "A"->10, "$"->37, etc. - _ALPHANUMERIC_ENCODING_TABLE = {ch: i for (i, ch) in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:")} + _ALPHANUMERIC_ENCODING_TABLE: Dict[str,int] = {ch: i for (i, ch) in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:")} # ---- Public helper enumeration ---- From e5d21aee09d1517c59e876671719c77b279c3aaf Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:27:50 +0000 Subject: [PATCH 15/22] Updated Python code so that public functions accept bytes or sequences of integers, removed some run-time type checks. --- python/qrcodegen.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 529cb3b..3106b8e 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -24,7 +24,7 @@ from __future__ import annotations import collections, itertools, re from collections.abc import Sequence -from typing import Callable, Dict, List, Optional, Tuple +from typing import Callable, Dict, List, Optional, Tuple, Union """ @@ -93,13 +93,11 @@ class QrCode: @staticmethod - def encode_binary(data: bytes, ecl: QrCode.Ecc) -> QrCode: + def encode_binary(data: Union[bytes,Sequence[int]], 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. The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.""" - if not isinstance(data, (bytes, bytearray)): - raise TypeError("Byte string/list expected") return QrCode.encode_segments([QrSegment.make_bytes(data)], ecl) @@ -197,7 +195,7 @@ class QrCode: # ---- Constructor (low level) ---- - def __init__(self, version: int, errcorlvl: QrCode.Ecc, datacodewords: List[int], mask: int) -> None: + def __init__(self, version: int, errcorlvl: QrCode.Ecc, datacodewords: Union[bytes,Sequence[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. @@ -221,7 +219,7 @@ class QrCode: # Compute ECC, draw modules self._draw_function_patterns() - allcodewords = self._add_ecc_and_interleave(datacodewords) + allcodewords = self._add_ecc_and_interleave(list(datacodewords)) self._draw_codewords(allcodewords) # Do masking @@ -733,12 +731,10 @@ class QrSegment: # ---- Static factory functions (mid level) ---- @staticmethod - def make_bytes(data) -> QrSegment: + def make_bytes(data: Union[bytes,Sequence[int]]) -> 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.""" - if isinstance(data, str): - raise TypeError("Byte string/list expected") bb = _BitBuffer() for b in data: bb.append_bits(b, 8) From 83300fd61925771e1aa880089039d1a28b566339 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:33:09 +0000 Subject: [PATCH 16/22] Updated private Python functions to operate on bytes instead of lists of integers, thus conveying the constrained value range more clearly. --- python/qrcodegen.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 3106b8e..652eeae 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -159,7 +159,7 @@ class QrCode: bb.append_bits(padbyte, 8) # Pack bits into bytes in big endian - datacodewords = [0] * (len(bb) // 8) + datacodewords = bytearray([0] * (len(bb) // 8)) for (i, bit) in enumerate(bb): datacodewords[i >> 3] |= bit << (7 - (i & 7)) @@ -219,7 +219,7 @@ class QrCode: # Compute ECC, draw modules self._draw_function_patterns() - allcodewords = self._add_ecc_and_interleave(list(datacodewords)) + allcodewords = self._add_ecc_and_interleave(bytearray(datacodewords)) self._draw_codewords(allcodewords) # Do masking @@ -394,7 +394,7 @@ class QrCode: # ---- Private helper methods for constructor: Codewords and masking ---- - def _add_ecc_and_interleave(self, data: List[int]) -> List[int]: + def _add_ecc_and_interleave(self, data: bytearray) -> bytes: """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 @@ -421,7 +421,7 @@ class QrCode: assert k == len(data) # Interleave (not concatenate) the bytes from every block into a single sequence - result = [] + result = bytearray() for i in range(len(blocks[0])): for (j, blk) in enumerate(blocks): # Skip the padding byte in short blocks @@ -431,7 +431,7 @@ class QrCode: return result - def _draw_codewords(self, data: List[int]) -> None: + def _draw_codewords(self, data: bytes) -> 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 @@ -574,14 +574,14 @@ class QrCode: @staticmethod - def _reed_solomon_compute_divisor(degree: int) -> List[int]: + def _reed_solomon_compute_divisor(degree: int) -> bytes: """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): raise ValueError("Degree out of range") # Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. # For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array [255, 8, 93]. - result = [0] * (degree - 1) + [1] # Start off with the monomial x^0 + result = bytearray([0] * (degree - 1) + [1]) # Start off with the monomial x^0 # Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), # and drop the highest monomial term which is always 1x^degree. @@ -598,9 +598,9 @@ class QrCode: @staticmethod - def _reed_solomon_compute_remainder(data: List[int], divisor: List[int]) -> List[int]: + def _reed_solomon_compute_remainder(data: bytes, divisor: bytes) -> bytes: """Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.""" - result = [0] * len(divisor) + result = bytearray([0] * len(divisor)) for b in data: # Polynomial division factor = b ^ result.pop(0) result.append(0) From 3c3aec6b9c2fdd76b4d5cedade960ca0b55c19d0 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:34:31 +0000 Subject: [PATCH 17/22] Relaxed some Python function parameters from List[int] to Sequence[int]. --- python/qrcodegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 652eeae..c51478e 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -104,7 +104,7 @@ class QrCode: # ---- Static factory functions (mid level) ---- @staticmethod - def encode_segments(segs: List[QrSegment], ecl: QrCode.Ecc, minversion: int = 1, maxversion: int = 40, mask: int = -1, boostecl: bool = True) -> QrCode: + def encode_segments(segs: Sequence[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 @@ -827,7 +827,7 @@ class QrSegment: # ---- Constructor (low level) ---- - def __init__(self, mode: QrSegment.Mode, numch: int, bitdata: List[int]) -> None: + def __init__(self, mode: QrSegment.Mode, numch: int, bitdata: Sequence[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.""" From 0ff7b57a81bc4d518ad0df74b4b5c5d83aeab293 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:48:26 +0000 Subject: [PATCH 18/22] Added type annotations to nearly all local variables in Python library code. --- python/qrcodegen.py | 114 ++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index c51478e..83d67cc 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -88,7 +88,7 @@ class QrCode: Unicode code points (not UTF-16 code units) if the low error correction level is used. 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.""" - segs = QrSegment.make_segments(text) + segs: List[QrSegment] = QrSegment.make_segments(text) return QrCode.encode_segments(segs, ecl) @@ -120,12 +120,12 @@ class QrCode: # Find the minimal version number to use for version in range(minversion, maxversion + 1): - datacapacitybits = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available - datausedbits = QrSegment.get_total_bits(segs, version) + datacapacitybits: int = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available + datausedbits: Optional[int] = QrSegment.get_total_bits(segs, version) if datausedbits is not None and datausedbits <= datacapacitybits: break # This version number is found to be suitable if version >= maxversion: # All versions in the range could not fit the given data - msg = "Segment too long" + msg: str = "Segment too long" if datausedbits is not None: msg = "Data length = {} bits, Max capacity = {} bits".format(datausedbits, datacapacitybits) raise DataTooLongError(msg) @@ -219,12 +219,12 @@ class QrCode: # Compute ECC, draw modules self._draw_function_patterns() - allcodewords = self._add_ecc_and_interleave(bytearray(datacodewords)) + allcodewords: bytes = self._add_ecc_and_interleave(bytearray(datacodewords)) self._draw_codewords(allcodewords) # Do masking if mask == -1: # Automatically choose best mask - minpenalty = 1 << 32 + minpenalty: int = 1 << 32 for i in range(8): self._apply_mask(i) self._draw_format_bits(i) @@ -273,7 +273,7 @@ class QrCode: of border modules. The string always uses Unix newlines (\n), regardless of the platform.""" if border < 0: raise ValueError("Border must be non-negative") - parts = [] + parts: List[str] = [] for y in range(self._size): for x in range(self._size): if self.get_module(x, y): @@ -302,9 +302,9 @@ class QrCode: self._draw_finder_pattern(3, self._size - 4) # Draw numerous alignment patterns - alignpatpos = self._get_alignment_pattern_positions() - numalign = len(alignpatpos) - skips = ((0, 0), (0, numalign - 1), (numalign - 1, 0)) + alignpatpos: List[int] = self._get_alignment_pattern_positions() + numalign: int = len(alignpatpos) + skips: Sequence[Tuple[int,int]] = ((0, 0), (0, numalign - 1), (numalign - 1, 0)) for i in range(numalign): for j in range(numalign): if (i, j) not in skips: # Don't draw on the three finder corners @@ -319,11 +319,11 @@ class QrCode: """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 - data = self._errcorlvl.formatbits << 3 | mask # errCorrLvl is uint2, mask is uint3 - rem = data + data: int = self._errcorlvl.formatbits << 3 | mask # errCorrLvl is uint2, mask is uint3 + rem: int = data for _ in range(10): rem = (rem << 1) ^ ((rem >> 9) * 0x537) - bits = (data << 10 | rem) ^ 0x5412 # uint15 + bits: int = (data << 10 | rem) ^ 0x5412 # uint15 assert bits >> 15 == 0 # Draw first copy @@ -350,17 +350,17 @@ class QrCode: return # Calculate error correction code and pack bits - rem = self._version # version is uint6, in the range [7, 40] + rem: int = self._version # version is uint6, in the range [7, 40] for _ in range(12): rem = (rem << 1) ^ ((rem >> 11) * 0x1F25) - bits = self._version << 12 | rem # uint18 + bits: int = self._version << 12 | rem # uint18 assert bits >> 18 == 0 # Draw two copies for i in range(18): - bit = _get_bit(bits, i) - a = self._size - 11 + i % 3 - b = i // 3 + bit: bool = _get_bit(bits, i) + a: int = self._size - 11 + i % 3 + b: int = i // 3 self._set_function_module(a, b, bit) self._set_function_module(b, a, bit) @@ -397,24 +397,24 @@ class QrCode: def _add_ecc_and_interleave(self, data: bytearray) -> bytes: """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 + version: int = self._version assert len(data) == QrCode._get_num_data_codewords(version, self._errcorlvl) # Calculate parameter numbers - numblocks = QrCode._NUM_ERROR_CORRECTION_BLOCKS[self._errcorlvl.ordinal][version] - blockecclen = QrCode._ECC_CODEWORDS_PER_BLOCK [self._errcorlvl.ordinal][version] - rawcodewords = QrCode._get_num_raw_data_modules(version) // 8 - numshortblocks = numblocks - rawcodewords % numblocks - shortblocklen = rawcodewords // numblocks + numblocks: int = QrCode._NUM_ERROR_CORRECTION_BLOCKS[self._errcorlvl.ordinal][version] + blockecclen: int = QrCode._ECC_CODEWORDS_PER_BLOCK [self._errcorlvl.ordinal][version] + rawcodewords: int = QrCode._get_num_raw_data_modules(version) // 8 + numshortblocks: int = numblocks - rawcodewords % numblocks + shortblocklen: int = rawcodewords // numblocks # Split data into blocks and append ECC to each block - blocks = [] - rsdiv = QrCode._reed_solomon_compute_divisor(blockecclen) - k = 0 + blocks: List[bytes] = [] + rsdiv: bytes = QrCode._reed_solomon_compute_divisor(blockecclen) + k: int = 0 for i in range(numblocks): - dat = data[k : k + shortblocklen - blockecclen + (0 if i < numshortblocks else 1)] + dat: bytearray = data[k : k + shortblocklen - blockecclen + (0 if i < numshortblocks else 1)] k += len(dat) - ecc = QrCode._reed_solomon_compute_remainder(dat, rsdiv) + ecc: bytes = QrCode._reed_solomon_compute_remainder(dat, rsdiv) if i < numshortblocks: dat.append(0) blocks.append(dat + ecc) @@ -436,16 +436,16 @@ class QrCode: 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 - i = 0 # Bit index into the data + i: int = 0 # Bit index into the data # Do the funny zigzag scan for right in range(self._size - 1, 0, -2): # Index of right column in each column pair if right <= 6: right -= 1 for vert in range(self._size): # Vertical counter for j in range(2): - x = right - j # Actual x coordinate - upward = (right + 1) & 2 == 0 - y = (self._size - 1 - vert) if upward else vert # Actual y coordinate + x: int = right - j # Actual x coordinate + upward: bool = (right + 1) & 2 == 0 + y: int = (self._size - 1 - vert) if upward else vert # Actual y coordinate if not self._isfunction[y][x] and i < len(data) * 8: self._modules[y][x] = _get_bit(data[i >> 3], 7 - (i & 7)) i += 1 @@ -462,7 +462,7 @@ class QrCode: QR Code needs exactly one (not zero, two, etc.) mask applied.""" if not (0 <= mask <= 7): raise ValueError("Mask value out of range") - masker = QrCode._MASK_PATTERNS[mask] + masker: Callable[[int,int],int] = QrCode._MASK_PATTERNS[mask] for y in range(self._size): for x in range(self._size): self._modules[y][x] ^= (masker(x, y) == 0) and (not self._isfunction[y][x]) @@ -471,14 +471,14 @@ class QrCode: 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 - size = self._size - modules = self._modules + result: int = 0 + size: int = self._size + modules: List[List[bool]] = self._modules # Adjacent modules in row having same color, and finder-like patterns for y in range(size): - runcolor = False - runx = 0 + runcolor: bool = False + runx: int = 0 runhistory = collections.deque([0] * 7, 7) for x in range(size): if modules[y][x] == runcolor: @@ -521,10 +521,10 @@ class QrCode: result += QrCode._PENALTY_N2 # Balance of black and white modules - black = sum((1 if cell else 0) for row in modules for cell in row) - total = size**2 # Note that size is odd, so black/total != 1/2 + black: int = sum((1 if cell else 0) for row in modules for cell in row) + total: int = size**2 # Note that size is odd, so black/total != 1/2 # Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% - k = (abs(black * 20 - total * 10) + total - 1) // total - 1 + k: int = (abs(black * 20 - total * 10) + total - 1) // total - 1 result += k * QrCode._PENALTY_N4 return result @@ -535,14 +535,14 @@ class QrCode: """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.""" - ver = self._version + ver: int = self._version if ver == 1: return [] else: - numalign = ver // 7 + 2 - step = 26 if (ver == 32) else \ + numalign: int = ver // 7 + 2 + step: int = 26 if (ver == 32) else \ (ver*4 + numalign*2 + 1) // (numalign*2 - 2) * 2 - result = [(self._size - 7 - i * step) for i in range(numalign - 1)] + [6] + result: List[int] = [(self._size - 7 - i * step) for i in range(numalign - 1)] + [6] return list(reversed(result)) @@ -553,9 +553,9 @@ class QrCode: The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.""" if not (QrCode.MIN_VERSION <= ver <= QrCode.MAX_VERSION): raise ValueError("Version number out of range") - result = (16 * ver + 128) * ver + 64 + result: int = (16 * ver + 128) * ver + 64 if ver >= 2: - numalign = ver // 7 + 2 + numalign: int = ver // 7 + 2 result -= (25 * numalign - 10) * numalign - 55 if ver >= 7: result -= 36 @@ -586,7 +586,7 @@ class QrCode: # Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), # and drop the highest monomial term which is always 1x^degree. # Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - root = 1 + root: int = 1 for _ in range(degree): # Unused variable i # Multiply the current product by (x - r^i) for j in range(degree): @@ -602,7 +602,7 @@ class QrCode: """Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.""" result = bytearray([0] * len(divisor)) for b in data: # Polynomial division - factor = b ^ result.pop(0) + factor: int = b ^ result.pop(0) result.append(0) for (i, coef) in enumerate(divisor): result[i] ^= QrCode._reed_solomon_multiply(coef, factor) @@ -616,7 +616,7 @@ class QrCode: if x >> 8 != 0 or y >> 8 != 0: raise ValueError("Byte out of range") # Russian peasant multiplication - z = 0 + z: int = 0 for i in reversed(range(8)): z = (z << 1) ^ ((z >> 7) * 0x11D) z ^= ((y >> i) & 1) * x @@ -627,9 +627,9 @@ class QrCode: 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] + n: int = runhistory[1] assert n <= self._size * 3 - core = n > 0 and (runhistory[2] == runhistory[4] == runhistory[5] == n) and runhistory[3] == n * 3 + core: bool = n > 0 and (runhistory[2] == runhistory[4] == runhistory[5] == n) and runhistory[3] == n * 3 return (1 if (core and runhistory[0] >= n * 4 and runhistory[6] >= n) else 0) \ + (1 if (core and runhistory[6] >= n * 4 and runhistory[0] >= n) else 0) @@ -747,9 +747,9 @@ class QrSegment: if QrSegment.NUMERIC_REGEX.fullmatch(digits) is None: raise ValueError("String contains non-numeric characters") bb = _BitBuffer() - i = 0 + i: int = 0 while i < len(digits): # Consume up to 3 digits per iteration - n = min(len(digits) - i, 3) + n: int = min(len(digits) - i, 3) bb.append_bits(int(digits[i : i + n]), n * 3 + 1) i += n return QrSegment(QrSegment.Mode.NUMERIC, len(digits), bb) @@ -764,7 +764,7 @@ class QrSegment: raise ValueError("String contains unencodable characters in alphanumeric mode") bb = _BitBuffer() for i in range(0, len(text) - 1, 2): # Process groups of 2 - temp = QrSegment._ALPHANUMERIC_ENCODING_TABLE[text[i]] * 45 + temp: int = QrSegment._ALPHANUMERIC_ENCODING_TABLE[text[i]] * 45 temp += QrSegment._ALPHANUMERIC_ENCODING_TABLE[text[i + 1]] bb.append_bits(temp, 11) if len(text) % 2 > 0: # 1 character remaining @@ -863,7 +863,7 @@ class QrSegment: returns None if a segment has too many characters to fit its length field.""" result = 0 for seg in segs: - ccbits = seg.get_mode().num_char_count_bits(version) + ccbits: int = seg.get_mode().num_char_count_bits(version) if seg.get_num_chars() >= (1 << ccbits): return None # The segment's length doesn't fit the field's bit width result += 4 + ccbits + len(seg._bitdata) From 1ca1d43f1ccd8817e7f632ef3c5ee32caa331b3b Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:49:42 +0000 Subject: [PATCH 19/22] Deleted Python module summary comment because static type hints make it redundant. --- python/qrcodegen.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 83d67cc..535639f 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -27,40 +27,6 @@ from collections.abc import Sequence from typing import Callable, Dict, List, Optional, Tuple, Union -""" -This module "qrcodegen", public members: -- Class QrCode: - - Function encode_text(str text, QrCode.Ecc ecl) -> QrCode - - Function encode_binary(bytes data, QrCode.Ecc ecl) -> QrCode - - Function encode_segments(list segs, QrCode.Ecc ecl, - int minversion=1, int maxversion=40, mask=-1, boostecl=true) -> QrCode - - Constants int MIN_VERSION, MAX_VERSION - - Constructor QrCode(int version, QrCode.Ecc ecl, bytes datacodewords, int mask) - - Method get_version() -> int - - Method get_size() -> int - - Method get_error_correction_level() -> QrCode.Ecc - - Method get_mask() -> int - - Method get_module(int x, int y) -> bool - - Method to_svg_str(int border) -> str - - Enum Ecc: - - Constants LOW, MEDIUM, QUARTILE, HIGH - - Field int ordinal -- Class QrSegment: - - Function make_bytes(bytes data) -> QrSegment - - Function make_numeric(str digits) -> QrSegment - - Function make_alphanumeric(str text) -> QrSegment - - Function make_segments(str text) -> list - - Function make_eci(int assignval) -> QrSegment - - Constructor QrSegment(QrSegment.Mode mode, int numch, list bitdata) - - Method get_mode() -> QrSegment.Mode - - Method get_num_chars() -> int - - Method get_data() -> list - - Constants regex NUMERIC_REGEX, ALPHANUMERIC_REGEX - - Enum Mode: - - Constants NUMERIC, ALPHANUMERIC, BYTE, KANJI, ECI -""" - - # ---- QR Code symbol class ---- class QrCode: From 27dd722961b2f82aea1cb28d79769be0ac5fa80c Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 22:54:44 +0000 Subject: [PATCH 20/22] Parenthesized most clauses of `and`/`or` expressions in Python code for clarity. --- python/qrcodegen.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/qrcodegen.py b/python/qrcodegen.py index 535639f..c0924c9 100644 --- a/python/qrcodegen.py +++ b/python/qrcodegen.py @@ -88,7 +88,7 @@ class QrCode: for version in range(minversion, maxversion + 1): datacapacitybits: int = QrCode._get_num_data_codewords(version, ecl) * 8 # Number of data bits available datausedbits: Optional[int] = QrSegment.get_total_bits(segs, version) - if datausedbits is not None and datausedbits <= datacapacitybits: + if (datausedbits is not None) and (datausedbits <= datacapacitybits): break # This version number is found to be suitable if version >= maxversion: # All versions in the range could not fit the given data msg: str = "Segment too long" @@ -100,7 +100,7 @@ class QrCode: # Increase the error correction level while the data still fits in the current version number for newecl in (QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH): # From low to high - if boostecl and datausedbits <= QrCode._get_num_data_codewords(version, newecl) * 8: + if boostecl and (datausedbits <= QrCode._get_num_data_codewords(version, newecl) * 8): ecl = newecl # Concatenate all segments to create the data bit string @@ -391,7 +391,7 @@ class QrCode: for i in range(len(blocks[0])): for (j, blk) in enumerate(blocks): # Skip the padding byte in short blocks - if i != shortblocklen - blockecclen or j >= numshortblocks: + if (i != shortblocklen - blockecclen) or (j >= numshortblocks): result.append(blk[i]) assert len(result) == rawcodewords return result @@ -412,7 +412,7 @@ class QrCode: x: int = right - j # Actual x coordinate upward: bool = (right + 1) & 2 == 0 y: int = (self._size - 1 - vert) if upward else vert # Actual y coordinate - if not self._isfunction[y][x] and i < len(data) * 8: + if (not self._isfunction[y][x]) and (i < len(data) * 8): self._modules[y][x] = _get_bit(data[i >> 3], 7 - (i & 7)) i += 1 # If this QR Code has any remainder bits (0 to 7), they were assigned as @@ -579,7 +579,7 @@ class QrCode: 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: + if (x >> 8 != 0) or (y >> 8 != 0): raise ValueError("Byte out of range") # Russian peasant multiplication z: int = 0 @@ -901,7 +901,7 @@ class _BitBuffer(list): 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: + if (n < 0) or (val >> n != 0): raise ValueError("Value out of range") self.extend(((val >> i) & 1) for i in reversed(range(n))) From aa32fe12359ad660730a888cca4af851edd975ec Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 23:00:52 +0000 Subject: [PATCH 21/22] Added more type annotations to Python tester programs. --- python/qrcodegen-batch-test.py | 18 ++++++++++-------- python/qrcodegen-worker.py | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/python/qrcodegen-batch-test.py b/python/qrcodegen-batch-test.py index b4279e0..babd9d8 100644 --- a/python/qrcodegen-batch-test.py +++ b/python/qrcodegen-batch-test.py @@ -68,7 +68,9 @@ def main() -> None: def do_trial() -> None: - mode = random.randrange(4) + mode: int = random.randrange(4) + length: int + data: List[int] if mode == 0: # Numeric length = round((2 * 7089) ** random.random()) data = random.choices(b"0123456789", k=length) @@ -88,12 +90,12 @@ def do_trial() -> None: for b in data: write_all(b) - errcorlvl = random.randrange(4) - minversion = random.randint(1, 40) - maxversion = random.randint(1, 40) + errcorlvl: int = random.randrange(4) + minversion: int = random.randint(1, 40) + maxversion: int = random.randint(1, 40) if minversion > maxversion: minversion, maxversion = maxversion, minversion - mask = -1 + mask: int = -1 if random.random() < 0.5: mask = random.randrange(8) boostecl = int(random.random() < 0.2) @@ -106,11 +108,11 @@ def do_trial() -> None: write_all(boostecl) flush_all() - version = read_verify() + version: int = read_verify() print(" version={}".format(version), end="") if version == -1: return - size = version * 4 + 17 + size: int = version * 4 + 17 for _ in range(size**2): read_verify() @@ -124,7 +126,7 @@ def flush_all() -> None: not_none(proc.stdin).flush() def read_verify() -> int: - val = not_none(subprocs[0].stdout).readline().rstrip("\r\n") + val: str = not_none(subprocs[0].stdout).readline().rstrip("\r\n") for proc in subprocs[1 : ]: if not_none(proc.stdout).readline().rstrip("\r\n") != val: raise ValueError("Mismatch") diff --git a/python/qrcodegen-worker.py b/python/qrcodegen-worker.py index a7b0ce6..7ea4a65 100644 --- a/python/qrcodegen-worker.py +++ b/python/qrcodegen-worker.py @@ -27,6 +27,7 @@ # import sys +from typing import List, Sequence import qrcodegen @@ -38,26 +39,26 @@ def main() -> None: while True: # Read data or exit - length = read_int() + length: int = read_int() if length == -1: break data = bytearray(read_int() for _ in range(length)) # Read encoding parameters - errcorlvl = read_int() - minversion = read_int() - maxversion = read_int() - mask = read_int() - boostecl = read_int() + errcorlvl : int = read_int() + minversion: int = read_int() + maxversion: int = read_int() + mask : int = read_int() + boostecl : int = read_int() # Make segments for encoding if all((b < 128) for b in data): # Is ASCII - segs = qrcodegen.QrSegment.make_segments(data.decode("ASCII")) + segs: List[qrcodegen.QrSegment] = qrcodegen.QrSegment.make_segments(data.decode("ASCII")) else: segs = [qrcodegen.QrSegment.make_bytes(data)] try: # Try to make QR Code symbol - qr = qrcodegen.QrCode.encode_segments(segs, ECC_LEVELS[errcorlvl], minversion, maxversion, mask, boostecl != 0) + qr: qrcodegen.QrCode = qrcodegen.QrCode.encode_segments(segs, ECC_LEVELS[errcorlvl], minversion, maxversion, mask, boostecl != 0) # Print grid of modules print(qr.get_version()) for y in range(qr.get_size()): @@ -69,7 +70,7 @@ def main() -> None: sys.stdout.flush() -ECC_LEVELS = ( +ECC_LEVELS: Sequence[qrcodegen.QrCode.Ecc] = ( qrcodegen.QrCode.Ecc.LOW, qrcodegen.QrCode.Ecc.MEDIUM, qrcodegen.QrCode.Ecc.QUARTILE, From 2c76b0a17047754218305c7d5516fc58ccb56698 Mon Sep 17 00:00:00 2001 From: Project Nayuki Date: Tue, 27 Jul 2021 23:02:54 +0000 Subject: [PATCH 22/22] Updated copyright year in readme document. --- Readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.markdown b/Readme.markdown index d28e31c..7e8b15f 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -175,7 +175,7 @@ Rust language: License ------- -Copyright © 2020 Project Nayuki. (MIT License) +Copyright © 2021 Project Nayuki. (MIT License) [https://www.nayuki.io/page/qr-code-generator-library](https://www.nayuki.io/page/qr-code-generator-library) Permission is hereby granted, free of charge, to any person obtaining a copy of