@@ -25,7 +25,13 @@ class UniversalProductCodeA(Barcode):
2525
2626 digits = 11
2727
28- def __init__ (self , upc , writer = None , make_ean = False ) -> None :
28+ def __init__ (
29+ self ,
30+ upc : str ,
31+ writer = None ,
32+ make_ean : bool = False ,
33+ addon : str | None = None ,
34+ ) -> None :
2935 """Initializes new UPC-A barcode.
3036
3137 :param str upc: The upc number as string.
@@ -34,6 +40,7 @@ def __init__(self, upc, writer=None, make_ean=False) -> None:
3440 :param bool make_ean: Indicates if a leading zero should be added to
3541 the barcode. This converts the UPC into a valid European Article
3642 Number (EAN).
43+ :param addon: Optional 2 or 5 digit addon (EAN-2 or EAN-5).
3744 """
3845 self .ean = make_ean
3946 upc = upc [: self .digits ]
@@ -45,19 +52,35 @@ def __init__(self, upc, writer=None, make_ean=False) -> None:
4552 )
4653 self .upc = upc
4754 self .upc = f"{ upc } { self .calculate_checksum ()} "
55+
56+ # Validate and store addon
57+ self .addon : str | None = None
58+ if addon is not None :
59+ addon = addon .strip ()
60+ if addon :
61+ if not addon .isdigit ():
62+ raise IllegalCharacterError (
63+ f"Addon can only contain numbers, got { addon } ."
64+ )
65+ if len (addon ) not in (2 , 5 ):
66+ raise NumberOfDigitsError (
67+ f"Addon must be 2 or 5 digits, received { len (addon )} ."
68+ )
69+ self .addon = addon
70+
4871 self .writer = writer or self .default_writer ()
4972
5073 def __str__ (self ) -> str :
51- if self .ean :
52- return "0" + self .upc
53-
54- return self . upc
74+ base = "0" + self . upc if self .ean else self . upc
75+ if self .addon :
76+ return f" { base } { self . addon } "
77+ return base
5578
5679 def get_fullcode (self ):
57- if self .ean :
58- return "0" + self .upc
59-
60- return self . upc
80+ base = "0" + self . upc if self .ean else self . upc
81+ if self .addon :
82+ return f" { base } { self . addon } "
83+ return base
6184
6285 def calculate_checksum (self ):
6386 """Calculates the checksum for UPCA/UPC codes
@@ -86,7 +109,7 @@ def build(self) -> list[str]:
86109 """
87110 code = _upc .EDGE [:]
88111
89- for _i , number in enumerate ( self .upc [0 :6 ]) :
112+ for number in self .upc [0 :6 ]:
90113 code += _upc .CODES ["L" ][int (number )]
91114
92115 code += _upc .MIDDLE
@@ -96,8 +119,59 @@ def build(self) -> list[str]:
96119
97120 code += _upc .EDGE
98121
122+ # Add addon if present
123+ if self .addon :
124+ code += self ._build_addon ()
125+
99126 return [code ]
100127
128+ def _build_addon (self ) -> str :
129+ """Builds the addon barcode pattern (EAN-2 or EAN-5).
130+
131+ :returns: The addon pattern as string
132+ """
133+ if not self .addon :
134+ return ""
135+
136+ if len (self .addon ) == 2 :
137+ return self ._build_addon2 ()
138+ return self ._build_addon5 ()
139+
140+ def _build_addon2 (self ) -> str :
141+ """Builds EAN-2 addon pattern.
142+
143+ Parity is determined by the 2-digit value mod 4.
144+ """
145+ value = int (self .addon )
146+ parity = _upc .ADDON2_PARITY [value % 4 ]
147+
148+ code = _upc .ADDON_START
149+ for i , digit in enumerate (self .addon ):
150+ if i > 0 :
151+ code += _upc .ADDON_SEPARATOR
152+ code += _upc .ADDON_CODES [parity [i ]][int (digit )]
153+ return code
154+
155+ def _build_addon5 (self ) -> str :
156+ """Builds EAN-5 addon pattern.
157+
158+ Parity is determined by a checksum calculation.
159+ """
160+ # Calculate checksum for parity pattern
161+ checksum = 0
162+ for i , digit in enumerate (self .addon ):
163+ weight = 3 if i % 2 == 0 else 9
164+ checksum += int (digit ) * weight
165+ checksum %= 10
166+ parity = _upc .ADDON5_PARITY [checksum ]
167+
168+ code = _upc .ADDON_START
169+ for i , digit in enumerate (self .addon ):
170+ if i > 0 :
171+ code += _upc .ADDON_SEPARATOR
172+ code += _upc .ADDON_CODES [parity [i ]][int (digit )]
173+ return code
174+
101175 def to_ascii (self ) -> str :
102176 """Returns an ascii representation of the barcode.
103177
0 commit comments