Coverage for adhoc-cicd-odoo-odoo / odoo / tools / barcode.py: 19%
48 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:22 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:22 +0000
1# Part of Odoo. See LICENSE file for full copyright and licensing details.
2import functools
3import re
4from threading import RLock
6__all__ = ['check_barcode_encoding', 'createBarcodeDrawing', 'get_barcode_font']
7_barcode_init_lock = RLock()
10# A lock occurs when the user wants to print a report having multiple barcode while the server is
11# started in threaded-mode. The reason is that reportlab has to build a cache of the T1 fonts
12# before rendering a barcode (done in a C extension) and this part is not thread safe.
13# This cached functions allows to lazily initialize the T1 fonts cache need for rendering of
14# barcodes in a thread-safe way.
15@functools.lru_cache(1)
16def _init_barcode():
17 with _barcode_init_lock:
18 try:
19 from reportlab.graphics import barcode # noqa: PLC0415
20 from reportlab.pdfbase.pdfmetrics import TypeFace, getFont # noqa: PLC0415
21 font_name = 'Courier'
22 available = TypeFace(font_name).findT1File()
23 if not available:
24 substitution_font = 'NimbusMonoPS-Regular'
25 fnt = getFont(substitution_font)
26 if fnt:
27 font_name = substitution_font
28 fnt.ascent = 629
29 fnt.descent = -157
30 barcode.createBarcodeDrawing('Code128', value='foo', format='png', width=100, height=100, humanReadable=1, fontName=font_name).asString('png')
31 except ImportError:
32 raise
33 except Exception: # noqa: BLE001
34 font_name = 'Courier'
35 return barcode, font_name
38def createBarcodeDrawing(codeName: str, **options):
39 barcode, _font = _init_barcode()
40 return barcode.createBarcodeDrawing(codeName, **options)
43def get_barcode_font():
44 """Get the barcode font for rendering."""
45 _barcode, font = _init_barcode()
46 return font
49def get_barcode_check_digit(numeric_barcode: str) -> int:
50 """ Computes and returns the barcode check digit. The used algorithm
51 follows the GTIN specifications and can be used by all compatible
52 barcode nomenclature, like as EAN-8, EAN-12 (UPC-A) or EAN-13.
53 https://www.gs1.org/sites/default/files/docs/barcodes/GS1_General_Specifications.pdf
54 https://www.gs1.org/services/how-calculate-check-digit-manually
55 :param numeric_barcode: the barcode to verify/recompute the check digit
56 :return: the number corresponding to the right check digit
57 """
58 # Multiply value of each position by
59 # N1 N2 N3 N4 N5 N6 N7 N8 N9 N10 N11 N12 N13 N14 N15 N16 N17 N18
60 # x3 X1 x3 x1 x3 x1 x3 x1 x3 x1 x3 x1 x3 x1 x3 x1 x3 CHECKSUM
61 oddsum = evensum = 0
62 code = numeric_barcode[-2::-1] # Remove the check digit and reverse the barcode.
63 # The CHECKSUM digit is removed because it will be recomputed and it must not interfer with
64 # the computation. Also, the barcode is inverted, so the barcode length doesn't matter.
65 # Otherwise, the digits' group (even or odd) could be different according to the barcode length.
66 for i, digit in enumerate(code):
67 if i % 2 == 0:
68 evensum += int(digit)
69 else:
70 oddsum += int(digit)
71 total = evensum * 3 + oddsum
72 return (10 - total % 10) % 10
75def check_barcode_encoding(barcode: str, encoding: str) -> bool:
76 """ Checks if the given barcode is correctly encoded.
77 :return: True if the barcode string is encoded with the provided encoding.
78 """
79 encoding = encoding.lower()
80 if encoding == "any":
81 return True
82 barcode_sizes = {
83 'ean8': 8,
84 'ean13': 13,
85 'gtin14': 14,
86 'upca': 12,
87 'sscc': 18,
88 }
89 barcode_size = barcode_sizes[encoding]
90 return (encoding != 'ean13' or barcode[0] != '0') \
91 and len(barcode) == barcode_size \
92 and re.match(r"^\d+$", barcode) \
93 and get_barcode_check_digit(barcode) == int(barcode[-1])