diff --git a/barcode/codex.py b/barcode/codex.py index b82c020..165fc4b 100755 --- a/barcode/codex.py +++ b/barcode/codex.py @@ -64,12 +64,14 @@ def get_fullcode(self) -> str: """:returns: The full code as it will be encoded.""" return self.code - def calculate_checksum(self): + def calculate_checksum(self) -> str: 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 - return None + raise RuntimeError( + "All possible values for the checksum should have been included in the map." + ) def build(self) -> list[str]: chars = [code39.EDGE] @@ -150,7 +152,7 @@ def __init__(self, code: str, writer=None) -> None: self.code = code self.writer = writer or self.default_writer() self._charset = "B" - self._buffer = "" + self._digit_buffer = "" # Accumulate pairs of digits for charset C check_code(self.code, self.name, code128.ALL) def __str__(self) -> str: @@ -194,9 +196,11 @@ def look_next() -> bool: 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 = "" + assert self._charset != "C" + if len(self._digit_buffer) == 1: + # Flush the remaining single digit from the buffer + codes.append(self._convert(self._digit_buffer[0])) + self._digit_buffer = "" elif self._charset == "B": if look_next(): codes = self._new_charset("C") @@ -209,25 +213,45 @@ def look_next() -> bool: codes = self._new_charset("B") return codes - def _convert(self, char: str): + def _convert(self, char: str) -> int: + """Convert a character to a code number for the current charset. + + NOTE: encoding digits with charset C requires buffering and is not supported + here. Use _convert_or_buffer instead. + """ if self._charset == "A": return code128.A[char] if self._charset == "B": return code128.B[char] if self._charset == "C": - if char in code128.C: + if char in ["TO_A", "TO_B"]: return code128.C[char] - if char.isdigit(): - self._buffer += char - if len(self._buffer) == 2: - value = int(self._buffer) - self._buffer = "" - return value - return None + raise RuntimeError("Use _convert_or_buffer for charset C.") raise RuntimeError( f"Character {char} could not be converted in charset {self._charset}." ) + def _convert_or_buffer(self, char: str) -> int | None: + """Convert a character to a code number for the current charset. + + If charset C is active then digits are encoded in pairs. When the first digit + is encountered, it is buffered and None is returned. + """ + if self._charset != "C": + return self._convert(char) + if char in code128.C: + return code128.C[char] + if char.isdigit(): + self._digit_buffer += char + if len(self._digit_buffer) == 1: + # Wait for the second digit to group in pairs + return None + assert len(self._digit_buffer) == 2 + value = int(self._digit_buffer) + self._digit_buffer = "" + return value + raise RuntimeError(f"Character {char} could not be converted in charset C.") + def _try_to_optimize(self, encoded: list[int]) -> list[int]: if encoded[1] in code128.TO: encoded[:2] = [code128.TO[encoded[1]]] @@ -243,14 +267,15 @@ def _build(self) -> list[int]: encoded: list[int] = [code128.START_CODES[self._charset]] for i, char in enumerate(self.code): encoded.extend(self._maybe_switch_charset(i)) - code_num = self._convert(char) + code_num = self._convert_or_buffer(char) if code_num is not None: encoded.append(code_num) - # Finally look in the buffer - if len(self._buffer) == 1: + # If we finish in charset C with a single digit remaining in the buffer, + # switch to charset B and flush out the buffer. + if len(self._digit_buffer) == 1: encoded.extend(self._new_charset("B")) - encoded.append(self._convert(self._buffer[0])) - self._buffer = "" + encoded.append(self._convert(self._digit_buffer[0])) + self._digit_buffer = "" return self._try_to_optimize(encoded) def build(self) -> list[str]: