276 lines
8.1 KiB
Python
Executable File
276 lines
8.1 KiB
Python
Executable File
"""Module: barcode.codex
|
|
|
|
:Provided barcodes: Code 39, Code 128, PZN
|
|
"""
|
|
from barcode.base import Barcode
|
|
from barcode.charsets import code128
|
|
from barcode.charsets import code39
|
|
from barcode.errors import BarcodeError
|
|
from barcode.errors import IllegalCharacterError
|
|
from barcode.errors import NumberOfDigitsError
|
|
|
|
__docformat__ = "restructuredtext en"
|
|
|
|
# Sizes
|
|
MIN_SIZE = 0.2
|
|
MIN_QUIET_ZONE = 2.54
|
|
|
|
|
|
def check_code(code, name, allowed):
|
|
wrong = []
|
|
for char in code:
|
|
if char not in allowed:
|
|
wrong.append(char)
|
|
if wrong:
|
|
raise IllegalCharacterError(
|
|
"The following characters are not valid for {name}: {wrong}".format(
|
|
name=name, wrong=", ".join(wrong)
|
|
)
|
|
)
|
|
|
|
|
|
class Code39(Barcode):
|
|
"""A Code39 barcode implementation"""
|
|
|
|
name = "Code 39"
|
|
|
|
def __init__(self, code: str, writer=None, add_checksum: bool = True):
|
|
r"""
|
|
:param code: Code 39 string without \* and without checksum.
|
|
:param writer: A ``barcode.writer`` instance used to render the barcode
|
|
(default: SVGWriter).
|
|
:param add_checksum: Add the checksum to code or not
|
|
"""
|
|
|
|
self.code = code.upper()
|
|
if add_checksum:
|
|
self.code += self.calculate_checksum()
|
|
self.writer = writer or Barcode.default_writer()
|
|
check_code(self.code, self.name, code39.REF)
|
|
|
|
def __str__(self):
|
|
return self.code
|
|
|
|
def get_fullcode(self) -> str:
|
|
""":returns: The full code as it will be encoded."""
|
|
return self.code
|
|
|
|
def calculate_checksum(self):
|
|
check = sum(code39.MAP[x][0] for x in self.code) % 43
|
|
for k, v in code39.MAP.items():
|
|
if check == v[0]:
|
|
return k
|
|
|
|
def build(self):
|
|
chars = [code39.EDGE]
|
|
for char in self.code:
|
|
chars.append(code39.MAP[char][1])
|
|
chars.append(code39.EDGE)
|
|
return [code39.MIDDLE.join(chars)]
|
|
|
|
def render(self, writer_options=None, text=None):
|
|
options = {"module_width": MIN_SIZE, "quiet_zone": MIN_QUIET_ZONE}
|
|
options.update(writer_options or {})
|
|
return Barcode.render(self, options, text)
|
|
|
|
|
|
class PZN7(Code39):
|
|
"""Initializes new German number for pharmaceutical products.
|
|
|
|
:parameters:
|
|
pzn : String
|
|
Code to render.
|
|
writer : barcode.writer Instance
|
|
The writer to render the barcode (default: SVGWriter).
|
|
"""
|
|
|
|
name = "Pharmazentralnummer"
|
|
|
|
digits = 6
|
|
|
|
def __init__(self, pzn, writer=None):
|
|
pzn = pzn[: self.digits]
|
|
if not pzn.isdigit():
|
|
raise IllegalCharacterError("PZN can only contain numbers.")
|
|
if len(pzn) != self.digits:
|
|
raise NumberOfDigitsError(
|
|
"PZN must have {} digits, not {}.".format(self.digits, len(pzn))
|
|
)
|
|
self.pzn = pzn
|
|
self.pzn = "{}{}".format(pzn, self.calculate_checksum())
|
|
Code39.__init__(self, "PZN-{}".format(self.pzn), writer, add_checksum=False)
|
|
|
|
def get_fullcode(self):
|
|
return "PZN-{}".format(self.pzn)
|
|
|
|
def calculate_checksum(self):
|
|
sum_ = sum(int(x) * int(y) for x, y in enumerate(self.pzn, start=2))
|
|
checksum = sum_ % 11
|
|
if checksum == 10:
|
|
raise BarcodeError("Checksum can not be 10 for PZN.")
|
|
else:
|
|
return checksum
|
|
|
|
|
|
class PZN8(PZN7):
|
|
"""Will be fully added in v0.9."""
|
|
|
|
digits = 7
|
|
|
|
|
|
class Code128(Barcode):
|
|
"""Initializes a new Code128 instance. The checksum is added automatically
|
|
when building the bars.
|
|
|
|
:parameters:
|
|
code : String
|
|
Code 128 string without checksum (added automatically).
|
|
writer : barcode.writer Instance
|
|
The writer to render the barcode (default: SVGWriter).
|
|
"""
|
|
|
|
name = "Code 128"
|
|
|
|
def __init__(self, code, writer=None):
|
|
self.code = code
|
|
self.writer = writer or Barcode.default_writer()
|
|
self._charset = "B"
|
|
self._buffer = ""
|
|
check_code(self.code, self.name, code128.ALL)
|
|
|
|
def __str__(self):
|
|
return self.code
|
|
|
|
@property
|
|
def encoded(self):
|
|
return self._build()
|
|
|
|
def get_fullcode(self):
|
|
return self.code
|
|
|
|
def _new_charset(self, which):
|
|
if which == "A":
|
|
code = self._convert("TO_A")
|
|
elif which == "B":
|
|
code = self._convert("TO_B")
|
|
elif which == "C":
|
|
code = self._convert("TO_C")
|
|
self._charset = which
|
|
return [code]
|
|
|
|
def _maybe_switch_charset(self, pos):
|
|
char = self.code[pos]
|
|
next_ = self.code[pos : pos + 10]
|
|
|
|
def look_next():
|
|
digits = 0
|
|
for c in next_:
|
|
if c.isdigit():
|
|
digits += 1
|
|
else:
|
|
break
|
|
return digits > 3 and (digits % 2) == 0
|
|
|
|
codes = []
|
|
if self._charset == "C" and not char.isdigit():
|
|
if char in code128.B:
|
|
codes = self._new_charset("B")
|
|
elif char in code128.A:
|
|
codes = self._new_charset("A")
|
|
if len(self._buffer) == 1:
|
|
codes.append(self._convert(self._buffer[0]))
|
|
self._buffer = ""
|
|
elif self._charset == "B":
|
|
if look_next():
|
|
codes = self._new_charset("C")
|
|
elif char not in code128.B:
|
|
if char in code128.A:
|
|
codes = self._new_charset("A")
|
|
elif self._charset == "A":
|
|
if look_next():
|
|
codes = self._new_charset("C")
|
|
elif char not in code128.A:
|
|
if char in code128.B:
|
|
codes = self._new_charset("B")
|
|
return codes
|
|
|
|
def _convert(self, char):
|
|
if self._charset == "A":
|
|
return code128.A[char]
|
|
elif self._charset == "B":
|
|
return code128.B[char]
|
|
elif self._charset == "C":
|
|
if char in code128.C:
|
|
return code128.C[char]
|
|
elif char.isdigit():
|
|
self._buffer += char
|
|
if len(self._buffer) == 2:
|
|
value = int(self._buffer)
|
|
self._buffer = ""
|
|
return value
|
|
|
|
def _try_to_optimize(self, encoded):
|
|
if encoded[1] in code128.TO:
|
|
encoded[:2] = [code128.TO[encoded[1]]]
|
|
return encoded
|
|
|
|
def _calculate_checksum(self, encoded):
|
|
cs = [encoded[0]]
|
|
for i, code_num in enumerate(encoded[1:], start=1):
|
|
cs.append(i * code_num)
|
|
return sum(cs) % 103
|
|
|
|
def _build(self):
|
|
encoded = [code128.START_CODES[self._charset]]
|
|
for i, char in enumerate(self.code):
|
|
encoded.extend(self._maybe_switch_charset(i))
|
|
code_num = self._convert(char)
|
|
if code_num is not None:
|
|
encoded.append(code_num)
|
|
# Finally look in the buffer
|
|
if len(self._buffer) == 1:
|
|
encoded.extend(self._new_charset("B"))
|
|
encoded.append(self._convert(self._buffer[0]))
|
|
self._buffer = ""
|
|
encoded = self._try_to_optimize(encoded)
|
|
return encoded
|
|
|
|
def build(self):
|
|
encoded = self._build()
|
|
encoded.append(self._calculate_checksum(encoded))
|
|
code = ""
|
|
for code_num in encoded:
|
|
code += code128.CODES[code_num]
|
|
code += code128.STOP
|
|
code += "11"
|
|
return [code]
|
|
|
|
def render(self, writer_options=None, text=None):
|
|
options = {"module_width": MIN_SIZE, "quiet_zone": MIN_QUIET_ZONE}
|
|
options.update(writer_options or {})
|
|
return Barcode.render(self, options, text)
|
|
|
|
|
|
class Gs1_128(Code128):
|
|
"""
|
|
following the norm, a gs1-128 barcode is a subset of code 128 barcode,
|
|
it can be generated by prepending the code with the FNC1 character
|
|
https://en.wikipedia.org/wiki/GS1-128
|
|
https://www.gs1-128.info/
|
|
"""
|
|
|
|
name = "GS1-128"
|
|
|
|
FNC1_CHAR = "\xf1"
|
|
|
|
def __init__(self, code, writer=None):
|
|
code = self.FNC1_CHAR + code
|
|
super().__init__(code, writer)
|
|
|
|
def get_fullcode(self):
|
|
return super().get_fullcode()[1:]
|
|
|
|
|
|
# For pre 0.8 compatibility
|
|
PZN = PZN7
|