From 2cd313acfe84d02b117bf9dd025165b6e94a1022 Mon Sep 17 00:00:00 2001 From: Dmitry Alimov Date: Mon, 19 Oct 2020 10:47:39 +0300 Subject: [PATCH 1/2] Fix tests Apply fixes by @dlenski --- test_aztec_code_generator.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test_aztec_code_generator.py b/test_aztec_code_generator.py index 72f06f8..68e78cc 100644 --- a/test_aztec_code_generator.py +++ b/test_aztec_code_generator.py @@ -39,7 +39,7 @@ def test_find_optimal_sequence(self): 'C', 'L/L', 'o', 'd', 'e', 'D/L', ' ', '2', 'U/S', 'D', 'P/S', '!']) self.assertEqual(find_optimal_sequence('a\xff'), ['B/S', 2, 'a', '\xff']) self.assertEqual(find_optimal_sequence('a' + '\xff' * 30), ['B/S', 31, 'a'] + ['\xff'] * 30) - self.assertEqual(find_optimal_sequence('a' + '\xff' * 31), ['B/S', 0, 32, 'a'] + ['\xff'] * 31) + self.assertEqual(find_optimal_sequence('a' + '\xff' * 31), ['B/S', 0, 1, 'a'] + ['\xff'] * 31) self.assertEqual(find_optimal_sequence('!#$%&?'), ['M/L', 'P/L', '!', '#', '$', '%', '&', '?']) self.assertEqual(find_optimal_sequence('!#$%&?\xff'), [ 'M/L', 'P/L', '!', '#', '$', '%', '&', '?', 'U/L', 'B/S', 1, '\xff']) @@ -61,12 +61,11 @@ def test_find_optimal_sequence(self): self.assertEqual(find_optimal_sequence('ABCabc1a2b3eBC'), [ 'A', 'B', 'C', 'L/L', 'a', 'b', 'c', 'B/S', 6, '1', 'a', '2', 'b', '3', 'e', 'M/L', 'U/L', 'B', 'C']) self.assertEqual(find_optimal_sequence('0a|5Tf.l'), [ - 'D/L', '0', 'U/L', 'L/L', 'a', 'M/L', '|', 'U/L', - 'D/L', '5', 'U/L', 'L/L', 'U/S', 'T', 'f', 'P/S', '.', 'l']) + 'B/S', 5, '0', 'a', '|', '5', 'T', 'L/L', 'f', 'P/S', '.', 'l']) self.assertEqual(find_optimal_sequence('*V1\x0c {Pa'), [ - 'P/S', '*', 'V', 'B/S', 2, '1', '\x0c', ' ', 'P/S', '{', 'P', 'L/L', 'a']) + 'P/S', '*', 'V', 'B/S', 5, '1', '\x0c', ' ', '{', 'P', 'L/L', 'a']) self.assertEqual(find_optimal_sequence('~Fxlb"I4'), [ - 'M/L', '~', 'U/L', 'D/L', 'U/S', 'F', 'U/L', 'L/L', 'x', 'l', 'b', 'D/L', 'P/S', '"', 'U/S', 'I', '4']) + 'B/S', 7, '~', 'F', 'x', 'l', 'b', '"', 'I', 'D/L', '4']) self.assertEqual(find_optimal_sequence('\\+=R?1'), [ 'M/L', '\\', 'P/L', '+', '=', 'U/L', 'R', 'D/L', 'P/S', '?', '1']) From 120c4df93a18c112f8024f2c563d3ddcdb6b2779 Mon Sep 17 00:00:00 2001 From: Dmitry Alimov Date: Wed, 14 Sep 2022 22:55:03 +0400 Subject: [PATCH 2/2] Fix '\r\n' in mixed mode --- aztec_code_generator.py | 131 ++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 58 deletions(-) diff --git a/aztec_code_generator.py b/aztec_code_generator.py index 65bbb28..844b8b5 100644 --- a/aztec_code_generator.py +++ b/aztec_code_generator.py @@ -1,12 +1,12 @@ #!/usr/bin/env python -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- """ aztec_code_generator ~~~~~~~~~~~~~~~~~~~~ Aztec code generator. - :copyright: (c) 2016-2018 by Dmitry Alimov. + :copyright: (c) 2016-2022 by Dmitry Alimov. :license: The MIT License (MIT), see LICENSE for more details. """ @@ -25,7 +25,6 @@ except ImportError: from io import StringIO - table = { (15, True): {'layers': 1, 'codewords': 17, 'cw_bits': 6, 'bits': 102, 'digits': 13, 'text': 12, 'bytes': 6}, (19, False): {'layers': 1, 'codewords': 21, 'cw_bits': 6, 'bits': 126, 'digits': 18, 'text': 15, 'bytes': 8}, @@ -160,14 +159,14 @@ def prod(x, y, log, alog, gf): - """ Product x times y """ + """Product x times y.""" if not x or not y: return 0 return alog[(log[x] + log[y]) % (gf - 1)] def reed_solomon(wd, nd, nc, gf, pp): - """ Calculate error correction codewords + """Calculate error correction codewords. Algorithm is based on Aztec Code bar code symbology specification from GOST-R-ISO-MEK-24778-2010 (Russian) @@ -175,11 +174,13 @@ def reed_solomon(wd, nd, nc, gf, pp): codewords, all within GF(gf) where ``gf`` is a power of 2 and ``pp`` is the value of its prime modulus polynomial. - :param wd: data codewords (in/out param) - :param nd: number of data codewords - :param nc: number of error correction codewords - :param gf: Galois Field order - :param pp: prime modulus polynomial value + :param list[int] wd: Data codewords (in/out param). + :param int nd: Number of data codewords. + :param int nc: Number of error correction codewords. + :param int gf: Galois Field order. + :param int pp: Prime modulus polynomial value. + + :return: None. """ # generate log and anti log tables log = {0: 1 - gf} @@ -189,7 +190,7 @@ def reed_solomon(wd, nd, nc, gf, pp): if alog[i] >= gf: alog[i] ^= pp log[alog[i]] = i - # generate polynomial coeffs + # generate polynomial coefficients c = {0: 1} for i in range(1, nc + 1): c[i] = 0 @@ -211,12 +212,13 @@ def reed_solomon(wd, nd, nc, gf, pp): def find_optimal_sequence(data): - """ Find optimal sequence, i.e. with minimum number of bits to encode data. + """Find optimal sequence, i.e. with minimum number of bits to encode data. TODO: add support of FLG(n) processing - :param data: data to encode - :return: optimal sequence + :param list[str|int] data: Data to encode. + + :return: Optimal sequence. """ back_to = { 'upper': 'upper', 'lower': 'upper', 'mixed': 'upper', @@ -329,7 +331,7 @@ def find_optimal_sequence(data): for x in possible_modes: # TODO: review this! if back_to[x] == 'digit' and x == 'lower': - cur_seq[x] = cur_seq[x] + ['U/L', 'L/L'] + cur_seq[x] = cur_seq[x] + ['U/L', 'L/L'] cur_len[x] = cur_len[x] + latch_len[back_to[x]][x] back_to[x] = 'lower' # add char to current sequence @@ -351,7 +353,8 @@ def find_optimal_sequence(data): last_mode = abbr_modes.get(char.replace('/S', '').replace('/L', '')) break if last_mode == 'punct': - if cur_seq[x][-1] + c in punct_2_chars: + # do not use mixed mode for '\r\n' as in mixed mode '\r' and '\n' are separate + if cur_seq[x][-1] + c in punct_2_chars and x != 'mixed': if cur_len[x] < next_len[x]: next_len[x] = cur_len[x] next_seq[x] = cur_seq[x][:-1] + [cur_seq[x][-1] + c] @@ -397,15 +400,16 @@ def find_optimal_sequence(data): if c == 'B/S': is_binary_length = True - + return updated_result_seq def optimal_sequence_to_bits(optimal_sequence): - """ Convert optimal sequence to bits + """Convert optimal sequence to bits. + + :param list[str|int] optimal_sequence: Input optimal sequence. - :param optimal_sequence: input optimal sequence - :return: string with bits + :return: String with bits. """ out_bits = '' mode = 'upper' @@ -466,12 +470,13 @@ def optimal_sequence_to_bits(optimal_sequence): def get_data_codewords(bits, codeword_size): - """ Get codewords stream from data bits sequence - Bit stuffing and padding are used to avoid all-zero and all-ones codewords + """Get codewords stream from data bits sequence. + Bit stuffing and padding are used to avoid all-zero and all-ones codewords. - :param bits: input data bits - :param codeword_size: codeword size in bits - :return: data codewords + :param str bits: Input data bits. + :param int codeword_size: Codeword size in bits. + + :return: Data codewords. """ codewords = [] sub_bits = '' @@ -498,23 +503,26 @@ def get_data_codewords(bits, codeword_size): def get_config_from_table(size, compact): - """ Get config from table with given size and compactness flag + """Get config from table with given size and compactness flag. - :param size: matrix size - :param compact: compactness flag - :return: dict with config + :param int size: Matrix size. + :param bool compact: Compactness flag. + + :return: Dict with config. """ config = table.get((size, compact)) if not config: raise Exception('Failed to find config with size and compactness flag') return config + def find_suitable_matrix_size(data): - """ Find suitable matrix size - Raise an exception if suitable size is not found + """Find suitable matrix size. + Raise an exception if suitable size is not found. + + :param list[str|int] data: Data to encode. - :param data: data to encode - :return: (size, compact) tuple + :return: (size, compact) tuple. """ optimal_sequence = find_optimal_sequence(data) out_bits = optimal_sequence_to_bits(optimal_sequence) @@ -525,24 +533,25 @@ def find_suitable_matrix_size(data): ec_percent = 23 # recommended: 23% of symbol capacity plus 3 codewords # calculate minimum required number of bits required_bits_count = int(math.ceil(len(out_bits) * 100.0 / ( - 100 - ec_percent) + 3 * 100.0 / (100 - ec_percent))) + 100 - ec_percent) + 3 * 100.0 / (100 - ec_percent))) if required_bits_count < bits: return size, compact raise Exception('Data too big to fit in one Aztec code!') + class AztecCode(object): """ - Aztec code generator + Aztec code generator. """ def __init__(self, data, size=None, compact=None): - """ Create Aztec code with given data. + """Create Aztec code with given data. If size and compact parameters are None (by default), an optimal size and compactness calculated based on the data. - :param data: data to encode - :param size: size of matrix - :param compact: compactness flag + :param data: Data to encode. + :param int|None size: Size of matrix. + :param bool|None compact: Compactness flag. """ self.data = data if size is not None and compact is not None: @@ -557,7 +566,7 @@ def __init__(self, data, size=None, compact=None): self.__encode_data() def __create_matrix(self): - """ Create Aztec code matrix with given size """ + """Create Aztec code matrix with given size.""" self.matrix = [] for _ in range(self.size): line = [] @@ -566,10 +575,12 @@ def __create_matrix(self): self.matrix.append(line) def save(self, filename, module_size=1): - """ Save matrix to image file + """Save matrix to image file. + + :param str filename: Output image filename. + :param int module_size: Barcode module size in pixels. - :param filename: output image filename. - :param module_size: barcode module size in pixels. + :return: None. """ if ImageDraw is None: exc = missing_pil[0](missing_pil[1]) @@ -586,12 +597,12 @@ def save(self, filename, module_size=1): image.save(filename) def print_out(self): - """ Print out Aztec code matrix """ + """Print out Aztec code matrix.""" for line in self.matrix: print(''.join(x for x in line)) def __add_finder_pattern(self): - """ Add bulls-eye finder pattern """ + """Add bulls-eye finder pattern.""" center = self.size // 2 ring_radius = 5 if self.compact else 7 for x in range(-ring_radius, ring_radius): @@ -600,7 +611,7 @@ def __add_finder_pattern(self): self.matrix[center + y][center + x] = '#' def __add_orientation_marks(self): - """ Add orientation marks to matrix """ + """Add orientation marks to matrix.""" center = self.size // 2 ring_radius = 5 if self.compact else 7 # add orientation marks @@ -615,7 +626,7 @@ def __add_orientation_marks(self): self.matrix[center + ring_radius - 1][center + ring_radius + 0] = '#' def __add_reference_grid(self): - """ Add reference grid to matrix """ + """Add reference grid to matrix.""" if self.compact: return center = self.size // 2 @@ -631,11 +642,12 @@ def __add_reference_grid(self): self.matrix[center + y][center + x] = val def __get_mode_message(self, layers_count, data_cw_count): - """ Get mode message + """Get mode message. - :param layers_count: number of layers - :param data_cw_count: number of data codewords - :return: mode message codewords + :param int layers_count: Number of layers. + :param int data_cw_count: Number of data codewords. + + :return: Mode message codewords. """ if self.compact: # for compact mode - 2 bits with layers count and 6 bits with data codewords count @@ -657,9 +669,11 @@ def __get_mode_message(self, layers_count, data_cw_count): return codewords def __add_mode_info(self, data_cw_count): - """ Add mode info to matrix + """Add mode info to matrix. + + :param int data_cw_count: Number of data codewords. - :param data_cw_count: number of data codewords. + :return: None. """ config = get_config_from_table(self.size, self.compact) layers_count = config.get('layers') @@ -704,10 +718,11 @@ def __add_mode_info(self, data_cw_count): index += 1 def __add_data(self, data): - """ Add data to encode to the matrix + """Add data to encode to the matrix. + + :param list[str|int] data: data to encode. - :param data: data to encode - :return: number of data codewords + :return: number of data codewords. """ optimal_sequence = find_optimal_sequence(data) out_bits = optimal_sequence_to_bits(optimal_sequence) @@ -721,7 +736,7 @@ def __add_data(self, data): ec_percent = 23 # recommended # calculate minimum required number of bits required_bits_count = int(math.ceil(len(out_bits) * 100.0 / ( - 100 - ec_percent) + 3 * 100.0 / (100 - ec_percent))) + 100 - ec_percent) + 3 * 100.0 / (100 - ec_percent))) data_codewords = get_data_codewords(out_bits, cw_bits) if required_bits_count > bits: raise Exception('Data too big to fit in Aztec code with current size!') @@ -818,7 +833,7 @@ def __add_data(self, data): return data_cw_count def __encode_data(self): - """ Encode data """ + """Encode data.""" self.__add_finder_pattern() self.__add_orientation_marks() self.__add_reference_grid()