"""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