# -*- coding: utf-8 -*-
"""Module: barcode.codex
:Provided barcodes: Code 39, Code 128, PZN
"""
from .base import Barcode
from .charsets import code128, code39
from .errors import BarcodeError, IllegalCharacterError
# Sizes
MIN_SIZE = 0.2
MIN_QUIET_ZONE = 2.54
[docs]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 %s: %s' % (name, ', '.join(wrong)))
[docs]class Code39(Barcode):
"""Initializes a new Code39 instance.
Attributes:
code (str):
writer (:py:class:`.writer.BaseWriter`): writer class.
Args:
code (str):
Code 39 string without \* and checksum (added automatically if `add_checksum` is True).
writer (:py:class:`.writer Instance`): instance of writer class to render the bar code.
add_checksum (bool): add the checksum to code or not.
"""
name = 'Code 39'
def __init__(self, code, writer=None):
super(Code39, self).__init__(code.upper(), writer)
[docs] def get_fullcode(self):
return self.code
@staticmethod
[docs] def calculate_checksum(code):
check = sum([code39.MAP[x][0] for x in code]) % 43
for k, v in code39.MAP.items():
if check == v[0]:
return k
@staticmethod
[docs] def validate(code):
check_code(code, Code39.name, code39.REF)
[docs] 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)]
[docs] def render(self, writer_options=None):
options = dict(module_width=MIN_SIZE, quiet_zone=MIN_QUIET_ZONE)
options.update(writer_options or {})
return Barcode.render(self, options)
[docs]class PZN(Code39):
"""Initializes new German number for pharmaceutical products.
Args:
pzn (str): code to render.
writer (:py:class:`.writer.BaseWriter`): instance of writer class to render the bar code.
"""
name = 'Pharmazentralnummer'
digits = 6
def __init__(self, code, writer=None):
super(PZN, self).__init__(code, writer)
[docs] def get_fullcode(self):
return 'PZN-%s' % self.code
@staticmethod
[docs] def calculate_checksum(code):
sum_ = sum([int(x) * int(y) for x, y in enumerate(code, start=2)])
checksum = sum_ % 11
if checksum == 10:
raise BarcodeError('Checksum can not be 10 for PZN.')
else:
return checksum
@staticmethod
[docs] def validate(code):
if not code.isdigit():
raise IllegalCharacterError('[0-9]{%d}' % PZN.digits)
if len(code) != PZN.digits:
raise ValueError('Bar code %s exceeds maximum number of characters/digits (%d)' % (code, PZN.digits))
check_code(code, PZN.name, code39.REF)
[docs] def build(self):
return self.code39().build()
[docs] def code39(self):
return Code39('PZN-%s' % self.code, self.writer)
[docs]class Code128(Barcode):
"""Initializes a new Code128 instance. The checksum is added automatically
when building the bars.
Args:
code (str): code 128 string without checksum (added automatically).
writer (:py:class:`.writer.BaseWriter`): instance of writer class to render the bar code.
"""
name = 'Code 128'
def __init__(self, code, writer=None):
self._charset = 'B'
self._buffer = ''
super(Code128, self).__init__(code, writer)
def __unicode__(self):
return self.code
__str__ = __unicode__
@staticmethod
[docs] def calculate_checksum(code):
return None
@staticmethod
[docs] def validate(code):
check_code(code, Code128.name, code128.ALL)
@property
def encoded(self):
return self._build()
[docs] 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
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 _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 = _try_to_optimize(encoded)
return encoded
[docs] def build(self):
encoded = self._build()
encoded.append(_calculate_checksum(encoded))
code = ''
for code_num in encoded:
code += code128.CODES[code_num]
code += code128.STOP
code += '11'
return [code]
[docs] def render(self, writer_options=None):
options = dict(module_width=MIN_SIZE, quiet_zone=MIN_QUIET_ZONE)
options.update(writer_options or {})
return Barcode.render(self, options)
def _calculate_checksum(encoded):
out = [encoded[0]]
for i, code_num in enumerate(encoded[1:], start=1):
out.append(i * code_num)
return sum(out) % 103
def _try_to_optimize(encoded):
if encoded[1] in code128.TO:
encoded[:2] = [code128.TO[encoded[1]]]
return encoded