Skip to content

Commit d82c72f

Browse files
author
Hugo Barrera
authored
Merge pull request #100 from Governa/feature/longer_guard_bar
Adds support for longer guard bar to EAN13 and EAN8 Fixes #11
2 parents ba2a034 + a806aa3 commit d82c72f

6 files changed

Lines changed: 128 additions & 18 deletions

File tree

barcode/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
from barcode.codex import Gs1_128
1414
from barcode.codex import PZN
1515
from barcode.ean import EAN13
16+
from barcode.ean import EAN13_GUARD
1617
from barcode.ean import EAN14
1718
from barcode.ean import EAN8
19+
from barcode.ean import EAN8_GUARD
1820
from barcode.ean import JAN
1921
from barcode.errors import BarcodeNotFoundError
2022
from barcode.isxn import ISBN10
@@ -26,7 +28,9 @@
2628

2729
__BARCODE_MAP = {
2830
"ean8": EAN8,
31+
"ean8-guard": EAN8_GUARD,
2932
"ean13": EAN13,
33+
"ean13-guard": EAN13_GUARD,
3034
"ean": EAN13,
3135
"gtin": EAN14,
3236
"ean14": EAN14,

barcode/ean.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class EuropeanArticleNumber13(Barcode):
4343

4444
digits = 12
4545

46-
def __init__(self, ean, writer=None, no_checksum=False):
46+
def __init__(self, ean, writer=None, no_checksum=False, guardbar=False):
4747
ean = ean[: self.digits]
4848
if not ean.isdigit():
4949
raise IllegalCharacterError("EAN code can only contain numbers.")
@@ -64,12 +64,22 @@ def __init__(self, ean, writer=None, no_checksum=False):
6464
)
6565
else:
6666
self.ean = "{}{}".format(ean, self.calculate_checksum())
67+
68+
self.guardbar = guardbar
69+
if guardbar:
70+
self.EDGE = _ean.EDGE.replace("1", "G")
71+
self.MIDDLE = _ean.MIDDLE.replace("1", "G")
72+
else:
73+
self.EDGE = _ean.EDGE
74+
self.MIDDLE = _ean.MIDDLE
6775
self.writer = writer or Barcode.default_writer()
6876

6977
def __str__(self):
7078
return self.ean
7179

7280
def get_fullcode(self):
81+
if self.guardbar:
82+
return self.ean[0] + " " + self.ean[1:7] + " " + self.ean[7:] + " >"
7383
return self.ean
7484

7585
def calculate_checksum(self):
@@ -92,14 +102,14 @@ def build(self):
92102
:returns: The pattern as string
93103
:rtype: String
94104
"""
95-
code = _ean.EDGE[:]
105+
code = self.EDGE[:]
96106
pattern = _ean.LEFT_PATTERN[int(self.ean[0])]
97107
for i, number in enumerate(self.ean[1:7]):
98108
code += _ean.CODES[pattern[i]][int(number)]
99-
code += _ean.MIDDLE
109+
code += self.MIDDLE
100110
for number in self.ean[7:]:
101111
code += _ean.CODES["C"][int(number)]
102-
code += _ean.EDGE
112+
code += self.EDGE
103113
return [code]
104114

105115
def to_ascii(self):
@@ -109,7 +119,7 @@ def to_ascii(self):
109119
"""
110120
code = self.build()
111121
for i, line in enumerate(code):
112-
code[i] = line.replace("1", "|").replace("0", " ")
122+
code[i] = line.replace("G", "|").replace("1", "|").replace("0", " ")
113123
return "\n".join(code)
114124

115125
def render(self, writer_options=None, text=None):
@@ -118,6 +128,14 @@ def render(self, writer_options=None, text=None):
118128
return Barcode.render(self, options, text)
119129

120130

131+
class EuropeanArticleNumber13WithGuard(EuropeanArticleNumber13):
132+
133+
name = "EAN-13 with guards"
134+
135+
def __init__(self, *args, guardbar=True, **kwargs):
136+
return super().__init__(*args, guardbar=guardbar, **kwargs)
137+
138+
121139
class JapanArticleNumber(EuropeanArticleNumber13):
122140
"""Initializes JAN barcode.
123141
@@ -154,24 +172,37 @@ class EuropeanArticleNumber8(EuropeanArticleNumber13):
154172

155173
digits = 7
156174

157-
def __init__(self, ean, writer=None):
158-
EuropeanArticleNumber13.__init__(self, ean, writer)
175+
def __init__(self, ean, writer=None, guardbar=False):
176+
EuropeanArticleNumber13.__init__(self, ean, writer, guardbar=guardbar)
159177

160178
def build(self):
161179
"""Builds the barcode pattern from `self.ean`.
162180
163181
:returns: The pattern as string
164182
:rtype: String
165183
"""
166-
code = _ean.EDGE[:]
184+
code = self.EDGE[:]
167185
for number in self.ean[:4]:
168186
code += _ean.CODES["A"][int(number)]
169-
code += _ean.MIDDLE
187+
code += self.MIDDLE
170188
for number in self.ean[4:]:
171189
code += _ean.CODES["C"][int(number)]
172-
code += _ean.EDGE
190+
code += self.EDGE
173191
return [code]
174192

193+
def get_fullcode(self):
194+
if self.guardbar:
195+
return "< " + self.ean[:4] + " " + self.ean[4:] + " >"
196+
return self.ean
197+
198+
199+
class EuropeanArticleNumber8WithGuard(EuropeanArticleNumber8):
200+
201+
name = "EAN-8 with guards"
202+
203+
def __init__(self, *args, guardbar=True, **kwargs):
204+
return super().__init__(*args, guardbar=guardbar, **kwargs)
205+
175206

176207
class EuropeanArticleNumber14(EuropeanArticleNumber13):
177208
"""Represents an EAN-14 barcode. See EAN13's __init__ for details.
@@ -204,5 +235,7 @@ def sum_(x, y):
204235
# Shortcuts
205236
EAN14 = EuropeanArticleNumber14
206237
EAN13 = EuropeanArticleNumber13
238+
EAN13_GUARD = EuropeanArticleNumber13WithGuard
207239
EAN8 = EuropeanArticleNumber8
240+
EAN8_GUARD = EuropeanArticleNumber8WithGuard
208241
JAN = JapanArticleNumber

barcode/writer.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ def render(self, code):
175175
if self._callbacks["initialize"] is not None:
176176
self._callbacks["initialize"](code)
177177
ypos = 1.0
178+
base_height = self.module_height
178179
for cc, line in enumerate(code):
179180
"""
180181
Pack line to list give better gfx result, otherwise in can
@@ -189,23 +190,51 @@ def render(self, code):
189190
c += 1
190191
else:
191192
if line[i] == "1":
192-
mlist.append(c)
193+
mlist.append((c, 1))
194+
elif line[i] == "G":
195+
mlist.append((c, 1.1))
193196
else:
194-
mlist.append(-c)
197+
mlist.append((-c, 1.1))
195198
c = 1
196199
# Left quiet zone is x startposition
197200
xpos = self.quiet_zone
198201
bxs = xpos # x start of barcode
202+
text = {
203+
"start": [], # The x start of a guard
204+
"end": [], # The x end of a guard
205+
"xpos": [], # The x position where to write a text block
206+
"was_guard": False # Flag that indicates if the previous mod
207+
# was part of an guard block
208+
}
199209
for mod in mlist:
210+
height_factor = mod[1]
211+
mod = mod[0]
212+
200213
if mod < 1:
201214
color = self.background
202215
else:
203216
color = self.foreground
217+
218+
if text["was_guard"] and height_factor == 1:
219+
# The current guard ended, store its x position
220+
text["end"].append(xpos)
221+
text["was_guard"] = False
222+
elif not text["was_guard"] and height_factor != 1:
223+
# A guard started, store its x position
224+
text["start"].append(xpos)
225+
text["was_guard"] = True
226+
227+
self.module_height = base_height * height_factor
204228
# remove painting for background colored tiles?
205229
self._callbacks["paint_module"](
206230
xpos, ypos, self.module_width * abs(mod), color
207231
)
208232
xpos += self.module_width * abs(mod)
233+
else:
234+
if height_factor != 1:
235+
text["end"].append(xpos)
236+
self.module_height = base_height
237+
209238
bxe = xpos
210239
# Add right quiet zone to every line, except last line,
211240
# quiet zone already provided with background,
@@ -215,14 +244,41 @@ def render(self, code):
215244
xpos, ypos, self.quiet_zone, self.background
216245
)
217246
ypos += self.module_height
247+
218248
if self.text and self._callbacks["paint_text"] is not None:
219-
ypos += self.text_distance
220-
if self.center_text:
221-
# better center position for text
222-
xpos = bxs + ((bxe - bxs) / 2.0)
249+
if not text["start"]:
250+
# If we don't have any start value, print the entire ean
251+
ypos += self.text_distance
252+
if self.center_text:
253+
# better center position for text
254+
xpos = bxs + ((bxe - bxs) / 2.0)
255+
else:
256+
xpos = bxs
257+
self._callbacks["paint_text"](xpos, ypos)
223258
else:
224-
xpos = bxs
225-
self._callbacks["paint_text"](xpos, ypos)
259+
# Else, divide the ean into blocks and print each block
260+
# in the expected position.
261+
text["xpos"] = [bxs - 4 * self.module_width]
262+
263+
# Calculates the position of the text by getting the difference
264+
# between a guard end and the next start
265+
text["start"].pop(0)
266+
for (s, e) in zip(text["start"], text["end"]):
267+
text["xpos"].append(e + (s - e) / 2)
268+
269+
# The last text block is always put after the last guard end
270+
text["xpos"].append(text["end"][-1] + 4 * self.module_width)
271+
272+
# Split the ean into its blocks
273+
self.text = self.text.split(" ")
274+
275+
ypos += pt2mm(self.font_size)
276+
277+
blocks = self.text
278+
for (text, xpos) in zip(blocks, text["xpos"]):
279+
self.text = text
280+
self._callbacks["paint_text"](xpos, ypos)
281+
226282
return self._callbacks["finish"]()
227283

228284

tests/test_builds.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ def test_ean8_builds():
66
ean = get_barcode("ean8", "40267708")
77
bc = ean.build()
88
assert ref == bc[0]
9+
10+
def test_ean8_builds_with_longer_bars():
11+
ref = "G0G01000110001101001001101011110G0G01000100100010011100101001000G0G"
12+
ean = get_barcode("ean8", "40267708", options={"guardbar": True})
13+
bc = ean.build()
14+
assert ref == bc[0]
15+

tests/test_manually.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232

3333
TESTCODES = (
3434
("ean8", "40267708"),
35+
("ean8-guard", "40267708"),
3536
("ean13", "5901234123457"),
37+
("ean13-guard", "5901234123457"),
3638
("ean14", "12345678911230"),
3739
("upca", "36000291453"),
3840
("jan", "4901234567894"),

tests/test_writers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,11 @@ def test_saving_svg_to_byteio():
3636

3737
with open(f"{TESTPATH}/somefile.svg", "wb") as f:
3838
EAN13("100000011111", writer=SVGWriter()).write(f)
39+
40+
def test_saving_svg_to_byteio_with_guardbar():
41+
rv = BytesIO()
42+
EAN13(str(100000902922), writer=SVGWriter(), guardbar=True).write(rv)
43+
44+
with open(f"{TESTPATH}/somefile_guardbar.svg", "wb") as f:
45+
EAN13("100000011111", writer=SVGWriter(), guardbar=True).write(f)
46+

0 commit comments

Comments
 (0)