summaryrefslogtreecommitdiffstats
path: root/share/extensions/render_barcode_datamatrix.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/render_barcode_datamatrix.py
parentInitial commit. (diff)
downloadinkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.tar.xz
inkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/render_barcode_datamatrix.py')
-rwxr-xr-xshare/extensions/render_barcode_datamatrix.py588
1 files changed, 588 insertions, 0 deletions
diff --git a/share/extensions/render_barcode_datamatrix.py b/share/extensions/render_barcode_datamatrix.py
new file mode 100755
index 0000000..5b3906c
--- /dev/null
+++ b/share/extensions/render_barcode_datamatrix.py
@@ -0,0 +1,588 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+#
+# Copyright (C) 2009 John Beard john.j.beard@gmail.com
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+"""
+This extension renders a DataMatrix 2D barcode, as specified in
+BS ISO/IEC 16022:2006. Only ECC200 codes are considered, as these are the only
+ones recommended for an "open" system.
+
+The size of the DataMatrix is variable between 10x10 to 144x144
+
+The absolute size of the DataMatrix modules (the little squares) is also
+variable.
+
+If more data is given than can be contained in one DataMatrix,
+more than one DataMatrices will be produced.
+
+Text is encoded as ASCII (the standard provides for other options, but these are
+not implemented). Consecutive digits are encoded in a compressed form, halving
+the space required to store them.
+
+The basis processing flow is;
+ * Convert input string to codewords (modified ASCII and compressed digits)
+ * Split codewords into blocks of the right size for Reed-Solomon coding
+ * Interleave the blocks if required
+ * Apply Reed-Solomon coding
+ * De-interleave the blocks if required
+ * Place the codewords into the matrix bit by bit
+ * Render the modules in the matrix as squares
+"""
+
+import inkex
+from inkex import Rectangle
+from inkex.localization import inkex_gettext as _
+
+INVALID_BIT = 2
+
+# return parameters for the selected datamatrix size
+# drow number of rows in each data region
+# dcol number of cols in each data region
+# reg_row number of rows of data regions
+# reg_col number of cols of data regions
+# nd number of data codewords per reed-solomon block
+# nc number of ECC codewords per reed-solomon block
+# inter number of interleaved Reed-Solomon blocks
+SYMBOLS = {
+ # 'id': (nrow, ncol, drow, dcol, reg_row, reg_col, nd, nc, inter)
+ "sq10": (10, 10, 8, 8, 1, 1, 3, 5, 1),
+ "sq12": (12, 12, 10, 10, 1, 1, 5, 7, 1),
+ "sq14": (14, 14, 12, 12, 1, 1, 8, 10, 1),
+ "sq16": (16, 16, 14, 14, 1, 1, 12, 12, 1),
+ "sq18": (18, 18, 16, 16, 1, 1, 18, 14, 1),
+ "sq20": (20, 20, 18, 18, 1, 1, 22, 18, 1),
+ "sq22": (22, 22, 20, 20, 1, 1, 30, 20, 1),
+ "sq24": (24, 24, 22, 22, 1, 1, 36, 24, 1),
+ "sq26": (26, 26, 24, 24, 1, 1, 44, 28, 1),
+ "sq32": (32, 32, 14, 14, 2, 2, 62, 36, 1),
+ "sq36": (36, 36, 16, 16, 2, 2, 86, 42, 1),
+ "sq40": (40, 40, 18, 18, 2, 2, 114, 48, 1),
+ "sq44": (44, 44, 20, 20, 2, 2, 144, 56, 1),
+ "sq48": (48, 48, 22, 22, 2, 2, 174, 68, 1),
+ "sq52": (52, 52, 24, 24, 2, 2, 102, 42, 2),
+ "sq64": (64, 64, 14, 14, 4, 4, 140, 56, 2),
+ "sq72": (72, 72, 16, 16, 4, 4, 92, 36, 4),
+ "sq80": (80, 80, 18, 18, 4, 4, 114, 48, 4),
+ "sq88": (88, 88, 20, 20, 4, 4, 144, 56, 4),
+ "sq96": (96, 96, 22, 22, 4, 4, 174, 68, 4),
+ "sq104": (104, 104, 24, 24, 4, 4, 136, 56, 6),
+ "sq120": (120, 120, 18, 18, 6, 6, 175, 68, 6),
+ "sq132": (132, 132, 20, 20, 6, 6, 163, 62, 8),
+ # there are two separate sections of the data matrix with different interleaving
+ # and reed-solomon parameters. this will be handled separately.
+ "sq144": (144, 144, 22, 22, 6, 6, 0, 0, 0),
+ "rect8x18": (8, 18, 6, 16, 1, 1, 5, 7, 1),
+ "rect8x32": (8, 32, 6, 14, 1, 2, 10, 11, 1),
+ "rect12x26": (12, 26, 10, 24, 1, 1, 16, 14, 1),
+ "rect12x36": (12, 36, 10, 16, 1, 2, 22, 18, 1),
+ "rect16x36": (16, 36, 14, 16, 1, 2, 32, 24, 1),
+ "rect16x48": (16, 48, 14, 22, 1, 2, 49, 28, 1),
+}
+
+
+# CODEWORD STREAM GENERATION =========================================
+# take the text input and return the codewords,
+# including the Reed-Solomon error-correcting codes.
+# =====================================================================
+
+
+def get_codewords(text, nd, nc, inter, size144):
+ # convert the data to the codewords
+ data = list(encode_to_ascii(text))
+
+ if not size144: # render a "normal" datamatrix
+ data_blocks = partition_data(
+ data, nd * inter
+ ) # partition into data blocks of length nd*inter -> inter Reed-Solomon block
+
+ data_blocks = interleave(
+ data_blocks, inter
+ ) # interleave consecutive inter blocks if required
+
+ data_blocks = reed_solomon(
+ data_blocks, nd, nc
+ ) # generate and append the Reed-Solomon codewords
+
+ data_blocks = combine_interleaved(
+ data_blocks, inter, nd, nc, False
+ ) # concatenate Reed-Solomon blocks bound for the same datamatrix
+
+ else: # we have a 144x144 datamatrix
+ data_blocks = partition_data(
+ data, 1558
+ ) # partition the data into datamtrix-sized chunks (1558 =156*8 + 155*2 )
+
+ for i in range(len(data_blocks)): # for each datamtrix
+
+ inter = 8
+ nd = 156
+ nc = 62
+ block1 = data_blocks[i][0 : 156 * 8]
+ block1 = interleave([block1], inter) # interleave into 8 blocks
+ block1 = reed_solomon(
+ block1, nd, nc
+ ) # generate and append the Reed-Solomon codewords
+
+ inter = 2
+ nd = 155
+ nc = 62
+ block2 = data_blocks[i][156 * 8 :]
+ block2 = interleave([block2], inter) # interleave into 2 blocks
+ block2 = reed_solomon(
+ block2, nd, nc
+ ) # generate and append the Reed-Solomon codewords
+
+ blocks = block1
+ blocks.extend(block2)
+
+ blocks = combine_interleaved(blocks, 10, nd, nc, True)
+
+ data_blocks[i] = blocks[0]
+
+ return data_blocks
+
+
+# Takes a codeword stream and splits up into "inter" blocks.
+# eg interleave( [1,2,3,4,5,6], 2 ) -> [1,3,5], [2,4,6]
+def interleave(blocks, inter):
+ if inter == 1: # if we don't have to interleave, just return the blocks
+ return blocks
+ else:
+ result = []
+ for block in blocks: # for each codeword block in the stream
+ block_length = int(len(block) / inter) # length of each interleaved block
+ inter_blocks = [
+ [0] * block_length for i in range(inter)
+ ] # the interleaved blocks
+
+ for i in range(block_length): # for each element in the interleaved blocks
+ for j in range(inter): # for each interleaved block
+ inter_blocks[j][i] = block[i * inter + j]
+
+ result.extend(inter_blocks) # add the interleaved blocks to the output
+
+ return result
+
+
+# Combine interleaved blocks into the groups for the same datamatrix
+#
+# e.g combine_interleaved( [[d1, d3, d5, e1, e3, e5], [d2, d4, d6, e2, e4, e6]], 2, 3, 3 )
+# --> [[d1, d2, d3, d4, d5, d6, e1, e2, e3, e4, e5, e6]]
+def combine_interleaved(blocks, inter, nd, nc, size144):
+ if inter == 1: # the blocks aren't interleaved
+ return blocks
+ else:
+ result = []
+ for i in range(
+ len(blocks) // inter
+ ): # for each group of "inter" blocks -> one full datamatrix
+ data_codewords = [] # interleaved data blocks
+
+ if size144:
+ nd_range = 1558 # 1558 = 156*8 + 155*2
+ nc_range = 620 # 620 = 62*8 + 62*2
+ else:
+ nd_range = nd * inter
+ nc_range = nc * inter
+
+ for j in range(nd_range): # for each codeword in the final list
+ data_codewords.append(blocks[i * inter + j % inter][j // inter])
+
+ for j in range(nc_range): # for each block, add the ecc codewords
+ data_codewords.append(blocks[i * inter + j % inter][nd + j // inter])
+
+ result.append(data_codewords)
+ return result
+
+
+def encode_to_ascii(text):
+ """Encode this text into chunks, ascii or digits"""
+ i = 0
+ while i < len(text):
+ # check for double digits, if the next char is also a digit
+ if text[i].isdigit() and (i < len(text) - 1) and text[i + 1].isdigit():
+ yield int(text[i] + text[i + 1]) + 130
+ i += 2 # move on 2 characters
+ else: # encode as a normal ascii,
+ yield ord(text[i]) + 1 # codeword is ASCII value + 1 (ISO 16022:2006 5.2.3)
+ i += 1 # next character
+
+
+# partition data into blocks of the appropriate size to suit the
+# Reed-Solomon block being used.
+# e.g. partition_data([1,2,3,4,5], 3) -> [[1,2,3],[4,5,PAD]]
+def partition_data(data, rs_data):
+ PAD_VAL = 129 # PAD codeword (ISO 16022:2006 5.2.3)
+ data_blocks = []
+ i = 0
+ while i < len(data):
+ if len(data) >= i + rs_data: # we have a whole block in our data
+ data_blocks.append(data[i : i + rs_data])
+ i = i + rs_data
+ else: # pad out with the pad codeword
+ data_block = data[i : len(data)] # add any remaining data
+ pad_pos = len(data)
+ padded = False
+ while (
+ len(data_block) < rs_data
+ ): # and then pad with randomised pad codewords
+ if not padded:
+ data_block.append(PAD_VAL) # add a normal pad codeword
+ padded = True
+ else:
+ data_block.append(randomise_pad_253(PAD_VAL, pad_pos))
+ pad_pos += 1
+ data_blocks.append(data_block)
+ break
+
+ return data_blocks
+
+
+# Pad character randomisation, to prevent regular patterns appearing
+# in the data matrix
+def randomise_pad_253(pad_value, pad_position):
+ pseudo_random_number = ((149 * pad_position) % 253) + 1
+ randomised = pad_value + pseudo_random_number
+ if randomised <= 254:
+ return randomised
+ else:
+ return randomised - 254
+
+
+# REED-SOLOMON ENCODING ROUTINES =====================================
+
+# "prod(x,y,log,alog,gf)" returns the product "x" times "y"
+def prod(x, y, log, alog, gf):
+ if x == 0 or y == 0:
+ return 0
+ else:
+ result = alog[(log[x] + log[y]) % (gf - 1)]
+ return result
+
+
+# generate the log & antilog lists:
+def gen_log_alog(gf, pp):
+ log = [0] * gf
+ alog = [0] * gf
+
+ log[0] = 1 - gf
+ alog[0] = 1
+
+ for i in range(1, gf):
+ alog[i] = alog[i - 1] * 2
+
+ if alog[i] >= gf:
+ alog[i] = alog[i] ^ pp
+
+ log[alog[i]] = i
+
+ return log, alog
+
+
+# generate the generator polynomial coefficients:
+def gen_poly_coeffs(nc, log, alog, gf):
+ c = [0] * (nc + 1)
+ c[0] = 1
+
+ for i in range(1, nc + 1):
+ c[i] = c[i - 1]
+
+ j = i - 1
+ while j >= 1:
+ c[j] = c[j - 1] ^ prod(c[j], alog[i], log, alog, gf)
+ j -= 1
+
+ c[0] = prod(c[0], alog[i], log, alog, gf)
+
+ return c
+
+
+# "ReedSolomon(wd,nd,nc)" takes "nd" data codeword values in wd[]
+# and adds on "nc" check codewords, all within GF(gf) where "gf" is a
+# power of 2 and "pp" is the value of its prime modulus polynomial */
+def reed_solomon(data, nd, nc):
+ # parameters of the polynomial arithmetic
+ gf = 256 # operating on 8-bit codewords -> Galois field = 2^8 = 256
+ pp = 301 # prime modulus polynomial for ECC-200 is 0b100101101 = 301 (ISO 16022:2006 5.7.1)
+
+ log, alog = gen_log_alog(gf, pp)
+ c = gen_poly_coeffs(nc, log, alog, gf)
+
+ for block in data: # for each block of data codewords
+
+ block.extend([0] * (nc + 1)) # extend to make space for the error codewords
+
+ # generate "nc" checkwords in the list block
+ for i in range(0, nd):
+ k = block[nd] ^ block[i]
+
+ for j in range(0, nc):
+ block[nd + j] = block[nd + j + 1] ^ prod(
+ k, c[nc - j - 1], log, alog, gf
+ )
+
+ block.pop()
+
+ return data
+
+
+# MODULE PLACEMENT ROUTINES===========================================
+# These routines take a steam of codewords, and place them into the
+# DataMatrix in accordance with Annex F of BS ISO/IEC 16022:2006
+
+
+def bit(byte, bit_ch):
+ """bit() returns the bit'th bit of the byte"""
+ # the MSB is bit 1, LSB is bit 8
+ return (byte >> (8 - bit_ch)) % 2
+
+
+def module(array, nrow, ncol, row, col, bit_ch):
+ """place a given bit with appropriate wrapping within array"""
+ if row < 0:
+ row = row + nrow
+ col = col + 4 - ((nrow + 4) % 8)
+
+ if col < 0:
+ col = col + ncol
+ row = row + 4 - ((ncol + 4) % 8)
+
+ array[row][col] = bit_ch
+
+
+def place_square(case, array, nrow, ncol, row, col, char):
+ """Populate corner cases (0-3) and utah case (-1)"""
+ for i in range(8):
+ x, y = [
+ [
+ (row - 1, 0),
+ (row - 1, 1),
+ (row - 1, 2),
+ (0, col - 2),
+ (0, col - 1),
+ (1, col - 1),
+ (2, col - 1),
+ (3, col - 1),
+ ],
+ [
+ (row - 3, 0),
+ (row - 2, 0),
+ (row - 1, 0),
+ (0, col - 4),
+ (0, col - 3),
+ (0, col - 2),
+ (0, col - 1),
+ (1, col - 1),
+ ],
+ [
+ (row - 3, 0),
+ (row - 2, 0),
+ (row - 1, 0),
+ (0, col - 2),
+ (0, col - 1),
+ (1, col - 1),
+ (2, col - 1),
+ (3, col - 1),
+ ],
+ [
+ (row - 1, 0),
+ (row - 1, col - 1),
+ (0, col - 3),
+ (0, col - 2),
+ (0, col - 1),
+ (1, col - 3),
+ (1, col - 2),
+ (1, col - 1),
+ ],
+ # "utah" places the 8 bits of a utah-shaped symbol character in ECC200
+ [
+ (row - 2, col - 2),
+ (row - 2, col - 1),
+ (row - 1, col - 2),
+ (row - 1, col - 1),
+ (row - 1, col),
+ (row, col - 2),
+ (row, col - 1),
+ (row, col),
+ ],
+ ][case][i]
+ module(array, nrow, ncol, x, y, bit(char, i + 1))
+ return 1
+
+
+def place_bits(data, nrow, ncol):
+ """fill an nrow x ncol array with the bits from the codewords in data."""
+ # initialise and fill with -1's (invalid value)
+ array = [[INVALID_BIT] * ncol for i in range(nrow)]
+ # Starting in the correct location for character #1, bit 8,...
+ char = 0
+ row = 4
+ col = 0
+ while True:
+
+ # first check for one of the special corner cases, then...
+ if (row == nrow) and (col == 0):
+ char += place_square(0, array, nrow, ncol, nrow, ncol, data[char])
+ elif (row == nrow - 2) and (col == 0) and (ncol % 4):
+ char += place_square(1, array, nrow, ncol, nrow, ncol, data[char])
+ elif (row == nrow - 2) and (col == 0) and (ncol % 8 == 4):
+ char += place_square(2, array, nrow, ncol, nrow, ncol, data[char])
+ elif (row == nrow + 4) and (col == 2) and ((ncol % 8) == 0):
+ char += place_square(3, array, nrow, ncol, nrow, ncol, data[char])
+
+ # sweep upward diagonally, inserting successive characters,...
+ while (row >= 0) and (col < ncol):
+ if (row < nrow) and (col >= 0) and (array[row][col] == INVALID_BIT):
+ char += place_square(-1, array, nrow, ncol, row, col, data[char])
+ row -= 2
+ col += 2
+
+ row += 1
+ col += 3
+
+ # & then sweep downward diagonally, inserting successive characters,...
+ while (row < nrow) and (col >= 0):
+ if (row >= 0) and (col < ncol) and (array[row][col] == INVALID_BIT):
+ char += place_square(-1, array, nrow, ncol, row, col, data[char])
+ row += 2
+ col -= 2
+
+ row += 3
+ col += 1
+
+ # ... until the entire array is scanned
+ if not ((row < nrow) or (col < ncol)):
+ break
+
+ # Lastly, if the lower righthand corner is untouched, fill in fixed pattern */
+ if array[nrow - 1][ncol - 1] == INVALID_BIT:
+ array[nrow - 1][ncol - 2] = 0
+ array[nrow - 1][ncol - 1] = 1
+ array[nrow - 2][ncol - 1] = 0
+ array[nrow - 2][ncol - 2] = 1
+
+ return array # return the array of 1's and 0's
+
+
+def add_finder_pattern(array, data_nrow, data_ncol, reg_row, reg_col):
+ # get the total size of the datamatrix
+ nrow = (data_nrow + 2) * reg_row
+ ncol = (data_ncol + 2) * reg_col
+
+ datamatrix = [[0] * ncol for i in range(nrow)] # initialise and fill with 0's
+
+ for i in range(reg_col): # for each column of data regions
+ for j in range(nrow):
+ datamatrix[j][i * (data_ncol + 2)] = 1 # vertical black bar on left
+ datamatrix[j][i * (data_ncol + 2) + data_ncol + 1] = (
+ j % 2
+ ) # alternating blocks
+
+ for i in range(reg_row): # for each row of data regions
+ for j in range(ncol):
+ datamatrix[i * (data_nrow + 2) + data_nrow + 1][
+ j
+ ] = 1 # horizontal black bar at bottom
+ datamatrix[i * (data_nrow + 2)][j] = (j + 1) % 2 # alternating blocks
+
+ for i in range(data_nrow * reg_row):
+ for j in range(data_ncol * reg_col):
+ # offset by 1, plus two for every addition block
+ dest_col = j + 1 + 2 * (j // data_ncol)
+ dest_row = i + 1 + 2 * (i // data_nrow)
+
+ datamatrix[dest_row][dest_col] = array[i][
+ j
+ ] # transfer from the plain bit array
+
+ return datamatrix
+
+
+class DataMatrix(inkex.GenerateExtension):
+ container_label = "DataMatrix"
+
+ def add_arguments(self, pars):
+ pars.add_argument("--text", default="Inkscape")
+ pars.add_argument("--symbol", type=self.arg_symbols, required=True)
+ pars.add_argument("--size", type=int, default=4)
+
+ @staticmethod
+ def arg_symbols(value):
+ """Turn a symbol key into matrix metrics"""
+ try:
+ return SYMBOLS[value]
+ except KeyError:
+ raise inkex.AbortExtension(_("Invalid symbol size."))
+
+ def generate(self):
+ size = str(self.options.size)
+ style = inkex.Style({"stroke": "none", "stroke-width": "1", "fill": "#000000"})
+ attribs = {"style": str(style), "height": size, "width": size}
+
+ if not self.options.text:
+ raise inkex.AbortExtension(_("Please enter an input string."))
+
+ # create a 2d list corresponding to the 1's and 0s of the DataMatrix
+ encoded = self.encode(self.options.text, *self.options.symbol)
+ for x, y in self.render_data_matrix(encoded):
+ attribs.update({"x": str(x), "y": str(y)})
+ yield Rectangle(**attribs)
+
+ def encode(
+ self, text, nrow, ncol, data_nrow, data_ncol, reg_row, reg_col, nd, nc, inter
+ ):
+ """
+ Take an input string and convert it to a sequence (or sequences)
+ of codewords as specified in ISO/IEC 16022:2006 (section 5.2.3)
+ """
+ # generate the codewords including padding and ECC
+ codewords = get_codewords(text, nd, nc, inter, nrow == 144)
+
+ # break up into separate arrays if more than one DataMatrix is needed
+ module_arrays = []
+ for codeword_stream in codewords: # for each datamatrix
+ # place the codewords' bits across the array as modules
+ bit_array = place_bits(
+ codeword_stream, data_nrow * reg_row, data_ncol * reg_col
+ )
+ # add finder patterns around the modules
+ module_arrays.append(
+ add_finder_pattern(bit_array, data_nrow, data_ncol, reg_row, reg_col)
+ )
+
+ return module_arrays
+
+ def render_data_matrix(self, module_arrays):
+ """turn a 2D array of 1's and 0's into a set of black squares"""
+ ncol = self.options.symbol[1]
+ size = self.options.size
+ spacing = ncol * size * 1.5
+ for i, line in enumerate(module_arrays):
+ height = len(line)
+ width = len(line[0])
+
+ for y in range(height): # loop over all the modules in the datamatrix
+ for x in range(width):
+ if line[y][x] == 1: # A binary 1 is a filled square
+ yield (x * size + i * spacing, y * size)
+ elif line[y][x] == INVALID_BIT: # we have an invalid bit value
+ inkex.errormsg(_("Invalid bit value, {}!").format(line[y][x]))
+
+
+if __name__ == "__main__":
+ DataMatrix().run()