Source code for pybarcodes.ean

from collections import namedtuple
from typing import Union

from .barcode import Barcode, BarcodeInput
from .codings import ean as EANCoding
from .exceptions import IncorrectFormat

Size = namedtuple("Size", "width height")
Weights = namedtuple("Weights", "ODD EVEN")


[docs] class EAN(Barcode): """Base class for EAN type barcodes Shouldn't be used directly and it's subclasses are preferred """ def __init__(self, barcode: BarcodeInput): super().__init__(barcode)
[docs] @classmethod def validate(cls, barcode: BarcodeInput) -> None: code = str(barcode) if not code.isdigit(): raise IncorrectFormat("Barcode can't contain non-digit characters.") if len(code) < cls.BARCODE_LENGTH: classname = cls.__name__ error = ( f"{classname} should be at least {cls.BARCODE_LENGTH} digits long, " f"not {len(code)}." ) raise IncorrectFormat(error)
[docs] @classmethod def normalize(cls, barcode: BarcodeInput) -> str: cls.validate(barcode) code = str(barcode)[: cls.BARCODE_LENGTH] check_digit = cls.calculate_checksum(code) return code + str(check_digit)
@property def get_binary_string(self) -> str: """ Converts the code to the binary string that it produces The binary string contains the left, center and right guards, and also the binary values of each digit. Returns ------- The return string contains 1's and 0's that represent the barcode. This string is used to iterate over, to create the barcode. """ # Find the structure of the first section # This is determined by the first digit if self.HAS_STRUCTURE: # We find the structure of the first section using the first digit structure = EANCoding.STRUCTURE[self.code[0]] # The first digit is removed code = self.code[1:] else: # If there is no structure then all digits should be in `L` coding structure = "L" * (self.FIRST_SECTION[1]) # In EAN8 barcodes the first digit is accounted for code = self.code # Convert the barcode to a binary string with the CodeNumbers class # Add the left guard binary_string = EANCoding.LEFT_GUARD # Add the 6 digits after the left guard for i in range(*self.FIRST_SECTION): digit = int(code[i]) coding = structure[i] binary_string += EANCoding.CODES[coding][digit] # Add the center guard binary_string += EANCoding.CENTER_GUARD # Add the 6 digits after the center guard for i in range(*self.SECOND_SECTION): digit = int(code[i]) binary_string += EANCoding.CODES["R"][digit] binary_string += EANCoding.RIGHT_GUARD return binary_string
[docs] @classmethod def calculate_checksum(cls, barcode: Union[str, "EAN13", "EAN8", "EAN14"]) -> int: """ Calculate the checksum from the barcode given This is a class method because it can only be used just to calculate any barcode of the same type, not only the instance's checksum Parameters ---------- barcode: Union[str, "EAN13"] The barcode to calculate the check digit of. Returns ------- A single digit integer that helps determine if the barcode is correct Raises ------ TypeError Raised when the barcode is not an acceptable type IncorrectFormat Raised when the barcode is not in the format expected """ if isinstance(barcode, cls): barcode = barcode.code elif isinstance(barcode, str): pass else: raise TypeError(f"Can't accept type {type(barcode)}") cls.validate(barcode) barcode = barcode[: cls.BARCODE_LENGTH] # Here there is no check digit so it's calculated digits = list(map(int, list(barcode))) # Get even and odd indeces of the digits weighted_odd = digits[1::2] weighted_even = digits[::2] # Calculate the checksum checksum = ( sum(weighted_odd) * cls.WEIGHTS.ODD + sum(weighted_even) * cls.WEIGHTS.EVEN ) return (10 - checksum % 10) % 10
def _get_column_size(self) -> int: """Finds and returns what the width of each column should be Returns ------- Returns an integer with the width of the bar """ return self.BARCODE_SIZE[0] // self.BARCODE_COLUMN_NUMBER def _clean_code(self) -> str: """ Tries to correct the barcode given Returns ------- A new barcode is returned that has the correct length and the check digit is calculated if not given """ if len(self.code) >= self.BARCODE_LENGTH: return self.normalize(self.code)
[docs] class EAN14(EAN): """The class to represent an EAN14 barcode Attributes ---------- BARCODE_LENGTH: int The number of digits in an EAN14 barcode BARCODE_SIZE: Tuple[int, int] The barcode's size and not the output image's size BARCODE_FONT_SIZE: int The size of the font under the barcode BARCODE_COLUMN_NUMBER: int How many binary columns the barcode consists of BARCODE_PADDING: Tuple[int, int] The padding around the actual barcode """ BARCODE_LENGTH = 13 BARCODE_SIZE = 720, 360 BARCODE_FONT_SIZE = 46 BARCODE_COLUMN_NUMBER = 108 BARCODE_PADDING = Size(100, 200) FIRST_SECTION = (0, 6) SECOND_SECTION = (6, BARCODE_LENGTH) WEIGHTS = Weights(1, 3) HAS_STRUCTURE = True def __init__(self, barcode: BarcodeInput): super().__init__(barcode)
[docs] class EAN13(EAN): """The class to represent an EAN13 barcode Attributes ---------- BARCODE_LENGTH: int The number of digits in an EAN13 barcode BARCODE_SIZE: Tuple[int, int] The barcode's size and not the output image's size BARCODE_FONT_SIZE: int The size of the font under the barcode BARCODE_COLUMN_NUMBER: int How many binary columns the barcode consists of BARCODE_PADDING: Tuple[int, int] The padding around the actual barcode """ BARCODE_LENGTH = 12 BARCODE_SIZE = 720, 360 BARCODE_FONT_SIZE = 46 BARCODE_COLUMN_NUMBER = 110 BARCODE_PADDING = Size(100, 200) FIRST_SECTION = (0, 6) SECOND_SECTION = (6, BARCODE_LENGTH) WEIGHTS = Weights(3, 1) HAS_STRUCTURE = True def __init__(self, barcode: BarcodeInput): super().__init__(barcode)
[docs] class EAN8(EAN): """The class to represent an EAN8 barcode Attributes ---------- BARCODE_LENGTH: int The number of digits of the barcode BARCODE_SIZE: Tuple[int, int] The barcode's size and not the output image's size BARCODE_FONT_SIZE: int The size of the font under the barcode BARCODE_COLUMN_NUMBER: int How many binary columns the barcode consists of BARCODE_PADDING: Tuple[int, int] The padding around the actual barcode """ BARCODE_LENGTH = 7 BARCODE_SIZE = 480, 240 BARCODE_FONT_SIZE = 40 BARCODE_COLUMN_NUMBER = 75 BARCODE_PADDING = Size(0, 200) FIRST_SECTION = (0, 4) SECOND_SECTION = (4, BARCODE_LENGTH + 1) WEIGHTS = Weights(1, 3) HAS_STRUCTURE = False def __init__(self, barcode: BarcodeInput): super().__init__(barcode)
[docs] class JAN(EAN13, EAN): """The class to represent an EAN13 barcode Attributes ---------- BARCODE_LENGTH: int The number of digits in an EAN13 barcode BARCODE_SIZE: Tuple[int, int] The barcode's size and not the output image's size BARCODE_FONT_SIZE: int The size of the font under the barcode BARCODE_COLUMN_NUMBER: int How many binary columns the barcode consists of BARCODE_PADDING: Tuple[int, int] The padding around the actual barcode """
[docs] @classmethod def validate(cls, barcode: BarcodeInput) -> None: super().validate(barcode) code = str(barcode) if code[:2] not in ("45", "49"): raise IncorrectFormat( "JAN type barcodes need to start with country code 45 or 49." )
def __init__(self, barcode: BarcodeInput): super().__init__(barcode) if self.code[:2] not in ("45", "49"): raise IncorrectFormat( "JAN type barcodes need to start with country code 45 or 49." )