Source code for pybarcodes.codes

from collections import namedtuple
from typing import Optional, Union

from PIL import Image, ImageDraw

from .barcode import Barcode, BarcodeInput
from .codings import codex as CODEXCoding
from .exceptions import IncorrectFormat

Size = namedtuple("Size", "width height")


[docs] class Code(Barcode): def __init__(self, barcode: BarcodeInput): super().__init__(barcode) self.checksum = self.code[-1] # Calculate the variable width of the barcod # 6 pixels for each character if self.BARCODE_SIZE[0] == -1: self.BARCODE_SIZE = ( (len(self.code) * 6 + len(CODEXCoding.GUARD) * 2) * 6, self.BARCODE_SIZE[1], ) column_size = 0 for char in self.get_binary_string: if char == "0": column_size += 1 elif char in ("1", " "): column_size += 3 column_size += 1 # Calculate how many character will have to be written self.BARCODE_COLUMN_NUMBER = column_size
[docs] @classmethod def validate(cls, barcode: BarcodeInput) -> None: code = str(barcode).upper() for char in code: if char not in CODEXCoding.CODES or char == "_": raise IncorrectFormat( f"Character {char} is not supported by {cls.__name__}" )
[docs] @classmethod def normalize(cls, barcode: BarcodeInput) -> str: cls.validate(barcode) code = str(barcode).upper() reference = cls._calculate_checksum(code) checkchar = CODEXCoding.REFERENCE_NUMBERS[reference] return code + checkchar
@classmethod def _calculate_checksum(cls, barcode: str) -> int: code = barcode.upper() for char in code: if char not in CODEXCoding.REFERENCE_DIGITS: raise IncorrectFormat( f"Character {char} is not supported by {cls.__name__}" ) if len(code) == 1: return CODEXCoding.REFERENCE_DIGITS[code] numbers = [CODEXCoding.REFERENCE_DIGITS[char] for char in list(code)] return sum(numbers) % 43 @property def get_binary_string(self) -> str: """Converts the code to the binary string that it produces. The binary string contains the start and stop characters and the actual characters themselves, encoded in binary. Returns ------- str: The return string contains 1's and 0's that represent the barcode. This string is used to iterate over, to create the barcode. """ # Replace all characters with valid reference characters (N, W, S and _) code = "".join([CODEXCoding.CODES[char] for char in self.code]) # Add the start character code = CODEXCoding.GUARD + code # Add the stop character code += CODEXCoding.GUARD return self._convert_to_binary(code)
[docs] def calculate_checksum(self, barcode: Optional[Union[str, "CODE39"]] = None) -> int: """Calculate the checksum of the barcode Parameters ---------- barcode: Union[str, "CODE39"] 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, self.__class__): barcode = barcode.code elif isinstance(barcode, str): pass elif barcode is None: return CODEXCoding.REFERENCE_DIGITS[self.checksum] else: raise TypeError(f"Can't accept type {type(barcode)}") return self._calculate_checksum(barcode)
def _get_barcode_image( self, module_width: Optional[int] = None, bar_height: Optional[int] = None, quiet_zone: Optional[int] = None, font_size: Optional[int] = None, draw_text: bool = True, ) -> Image.Image: """Creates a PIL Image from the binary string of the barcode. Returns ------- A PIL Image with the barcode is returned to the caller. """ module_width, bar_height, quiet_zone, font_size, text_padding = ( self._get_render_options( module_width=module_width, bar_height=bar_height, quiet_zone=quiet_zone, font_size=font_size, draw_text=draw_text, ) ) # Create the image to write the columns img = Image.new( "RGB", (module_width * self.BARCODE_COLUMN_NUMBER, bar_height), (255, 255, 255), ) # This is the spacing we are going to add after each digit space = Image.new("RGB", (module_width, img.height), (255, 255, 255)) # Create a binary string representation of the barcode digits binary_string = self.get_binary_string index = 0 for digit in binary_string: color = (0, 0, 0) # If the character is a `1`, then we write a bar with ratio 3:1 # So the bar needs to be 3 times the size of the bar we write in the `0` situation # If the character is a ` ` (space) then we write 3 times the size on the spacing # We also need to add a single width column of spacing after each digit if digit == "1": column_width = module_width * 3 elif digit == " ": column_width = module_width * 3 color = (255, 255, 255) else: column_width = module_width # First paste the column column = Image.new("RGB", (column_width, img.height), color) img.paste(column, (index, 0)) index += column_width if not (color[0] == 255): # Then paste the spacing img.paste(space, (index, 0)) index += space.width # Crop redundant whitespace after barcode img = img.crop((0, 0, index, img.height)) base = Image.new( "RGB", (img.width + quiet_zone * 2, bar_height + text_padding), (255, 255, 255), ) # Paste the barcode on the center of the padded base Point = namedtuple("Point", "x y") base_center = Point(base.width // 2, base.height // 2) base.paste(img, (quiet_zone, text_padding // 2)) if not draw_text: return base draw = ImageDraw.Draw(base) font = self._get_default_font(font_size) text_width = draw.textlength(self.code, font) x = base_center.x - text_width // 2 y = text_padding // 2 + img.height draw.text((x, y), self.code, (0, 0, 0), font=font) return base def _convert_to_binary(self, string: str) -> str: """Renders the string from `get_binary_string` into binary The string given would be in some form of `NSWNWN` etc. The output of this example would be `0 1010`. Returns ------- The binary string + spaces is returned to the caller. """ references = {"N": "0", "W": "1", "S": " "} out = "" for char in string: out += references[char] return out 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 """ return self.normalize(self.code) def _trim(self, code: str) -> str: """Removes the start and stop characters from the barcode Parameters ---------- code: str The code of type `Code` to trim Returns ------- str: The barcode is returned without the start and stop characters """ return code[5:-6] 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
[docs] class CODE39(Code): """The class to represent Code39 barcodes Attributes ---------- 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_PADDING: Tuple[int, int] The padding around the actual barcode """ BARCODE_SIZE = -1, 240 BARCODE_FONT_SIZE = 30 BARCODE_PADDING = Size(50, 100) def __init__(self, barcode: BarcodeInput): super().__init__(barcode)