Last active
October 26, 2022 13:24
-
-
Save yasoob/f970de335a99a542461977033b8b0a02 to your computer and use it in GitHub Desktop.
A pure Python Universal Product Code A barcode generator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import xml.dom | |
import os | |
class UPC: | |
""" | |
The all-in-one class that represents the UPC-A | |
barcode. | |
""" | |
EDGE = "101" | |
MIDDLE = "01010" | |
CODES = { | |
"L": ( | |
"0001101", | |
"0011001", | |
"0010011", | |
"0111101", | |
"0100011", | |
"0110001", | |
"0101111", | |
"0111011", | |
"0110111", | |
"0001011", | |
), | |
"R": ( | |
"1110010", | |
"1100110", | |
"1101100", | |
"1000010", | |
"1011100", | |
"1001110", | |
"1010000", | |
"1000100", | |
"1001000", | |
"1110100", | |
), | |
} | |
SIZE = "{0:.3f}mm" | |
MODULE_WIDTH = 0.33 | |
MODULE_HEIGHT = 25.9 | |
EXTENDED_MODULE_HEIGHT = MODULE_HEIGHT + 5 * MODULE_WIDTH | |
BARCODE_HEIGHT = EXTENDED_MODULE_HEIGHT | |
TOTAL_MODULES = 113 | |
def __init__(self, upc): | |
self.upc = list(str(upc))[:11] | |
if len(self.upc) < 11: | |
raise Exception(f"The UPC has to be of length 11 or 12 (with check digit)") | |
self.upc = self.upc + self.calculate_check_digit() | |
encoded_code = self.encode() | |
self.create_barcode() | |
self.save("upc_custom.svg") | |
def calculate_check_digit(self): | |
""" | |
Calculate the check digit | |
""" | |
upc = [int(digit) for digit in self.upc[:11]] | |
oddsum = sum(upc[::2]) | |
evensum = sum(upc[1::2]) | |
check = (evensum + oddsum * 3) % 10 | |
if check == 0: | |
return [0] | |
else: | |
return [10 - check] | |
def encode(self): | |
""" | |
Encode the upc based on the mapping defined above | |
""" | |
code = self.EDGE[:] | |
for _i, number in enumerate(self.upc[0:6]): | |
code += self.CODES["L"][int(number)] | |
code += self.MIDDLE | |
for number in self.upc[6:]: | |
code += self.CODES["R"][int(number)] | |
code += self.EDGE | |
self.encoded_upc = code | |
def create_barcode(self): | |
self.prepare_svg() | |
# Quiet zone is automatically added as the background is white We will | |
# simply skip the space for 9 modules and start the guard from there | |
x_position = 9 * self.MODULE_WIDTH | |
for count, extended in self.packed(self.encoded_upc): | |
if count < 0: | |
color = "white" | |
else: | |
color = "black" | |
config = { | |
"xpos": x_position, | |
"ypos": 1, | |
"color": color, | |
"width": abs(count) * self.MODULE_WIDTH, | |
"height": self.EXTENDED_MODULE_HEIGHT if extended else self.MODULE_HEIGHT, | |
} | |
self.create_module(**config) | |
x_position += abs(count) * self.MODULE_WIDTH | |
def packed(self, encoded_upc): | |
""" | |
Pack the encoded UPC to a list. Ex: | |
"001101" -> [-2, 2, -1, 1] | |
""" | |
encoded_upc += " " | |
extended_bars = [1,2,3, 4,5,6,7, 28,29,30,31,32, 53,54,55,56, 57,58,59] | |
count = 1 | |
bar_count = 1 | |
for i in range(0, len(encoded_upc) - 1): | |
if encoded_upc[i] == encoded_upc[i + 1]: | |
count += 1 | |
else: | |
if encoded_upc[i] == "1": | |
yield count, bar_count in extended_bars | |
else: | |
yield -count, bar_count in extended_bars | |
bar_count += 1 | |
count = 1 | |
def prepare_svg(self): | |
""" | |
Create the complete boilerplate SVG for the barcode | |
""" | |
self._document = self.create_svg_object() | |
self._root = self._document.documentElement | |
group = self._document.createElement("g") | |
group.setAttribute("id", "barcode_group") | |
self._group = self._root.appendChild(group) | |
background = self._document.createElement("rect") | |
background.setAttribute("width", "100%") | |
background.setAttribute("height", "100%") | |
background.setAttribute("style", "fill: white") | |
self._group.appendChild(background) | |
def create_svg_object(self): | |
""" | |
Create a SVG object | |
""" | |
imp = xml.dom.getDOMImplementation() | |
doctype = imp.createDocumentType( | |
"svg", | |
"-//W3C//DTD SVG 1.1//EN", | |
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", | |
) | |
document = imp.createDocument(None, "svg", doctype) | |
document.documentElement.setAttribute("version", "1.1") | |
document.documentElement.setAttribute("xmlns", "http://www.w3.org/2000/svg") | |
document.documentElement.setAttribute( | |
"width", self.SIZE.format(self.TOTAL_MODULES * self.MODULE_WIDTH) | |
) | |
document.documentElement.setAttribute( | |
"height", self.SIZE.format(self.BARCODE_HEIGHT) | |
) | |
return document | |
def create_module(self, xpos, ypos, width, height, color): | |
""" | |
Create a module and append a corresponding rect to the SVG group | |
""" | |
element = self._document.createElement("rect") | |
element.setAttribute("x", self.SIZE.format(xpos)) | |
element.setAttribute("y", self.SIZE.format(ypos)) | |
element.setAttribute("width", self.SIZE.format(width)) | |
element.setAttribute("height", self.SIZE.format(height)) | |
element.setAttribute("style", "fill:{};".format(color)) | |
self._group.appendChild(element) | |
def save(self, filename): | |
""" | |
Dump the final SVG XML code to a file | |
""" | |
output = self._document.toprettyxml( | |
indent=4 * " ", newl=os.linesep, encoding="UTF-8" | |
) | |
with open(filename, "wb") as f: | |
f.write(output) | |
if __name__ == "__main__": | |
upc = UPC(12345678910) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With ideas and code from https://github.com/WhyNotHugo/python-barcode