1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
|
# -*- coding: utf-8 -*-
# MIT Licensed, Copyright (c) 2015 Ari Koivula
# https://github.com/Venti-/pubcode
from __future__ import absolute_import, division, print_function, unicode_literals
from builtins import * # Use Python3-like builtins for Python2.
import base64
import io
try:
from PIL import Image
except ImportError:
# PIL is needed only for creating images of the barcode. Set Image to None to signify that PIL is missing.
Image = None
class Code128(object):
class Error(Exception):
pass
class CharsetError(Error):
pass
class CharsetLengthError(Error):
pass
class IncompatibleCharsetError(Error):
pass
class MissingDependencyError(Error):
pass
class UnknownFormatError(Error):
pass
# List of bar and space weights, indexed by symbol character values (0-105), and the STOP character (106).
# The first weights is a bar and then it alternates.
_val2bars = [
'212222', '222122', '222221', '121223', '121322', '131222', '122213', '122312', '132212', '221213',
'221312', '231212', '112232', '122132', '122231', '113222', '123122', '123221', '223211', '221132',
'221231', '213212', '223112', '312131', '311222', '321122', '321221', '312212', '322112', '322211',
'212123', '212321', '232121', '111323', '131123', '131321', '112313', '132113', '132311', '211313',
'231113', '231311', '112133', '112331', '132131', '113123', '113321', '133121', '313121', '211331',
'231131', '213113', '213311', '213131', '311123', '311321', '331121', '312113', '312311', '332111',
'314111', '221411', '431111', '111224', '111422', '121124', '121421', '141122', '141221', '112214',
'112412', '122114', '122411', '142112', '142211', '241211', '221114', '413111', '241112', '134111',
'111242', '121142', '121241', '114212', '124112', '124211', '411212', '421112', '421211', '212141',
'214121', '412121', '111143', '111341', '131141', '114113', '114311', '411113', '411311', '113141',
'114131', '311141', '411131', '211412', '211214', '211232', '2331112'
]
class Special(object):
"""These are special characters used by the Code128 encoding."""
START_A = '[Start Code A]'
START_B = '[Start Code B]'
START_C = '[Start Code C]'
CODE_A = '[Code A]'
CODE_B = '[Code B]'
CODE_C = '[Code C]'
SHIFT_A = '[Shift A]'
SHIFT_B = '[Shift B]'
FNC_1 = '[FNC 1]'
FNC_2 = '[FNC 2]'
FNC_3 = '[FNC 3]'
FNC_4 = '[FNC 4]'
STOP = '[Stop]'
_start_codes = {'A': Special.START_A, 'B': Special.START_B, 'C': Special.START_C}
_char_codes = {'A': Special.CODE_A, 'B': Special.CODE_B, 'C': Special.CODE_C}
# Lists mapping symbol values to characters in each character set. This defines the alphabet and Code128._sym2val
# is derived from this structure.
_val2sym = {
# Code Set A includes ordinals 0 through 95 and 7 special characters. The ordinals include digits,
# upper case characters, punctuation and control characters.
'A':
[chr(x) for x in range(32, 95 + 1)] +
[chr(x) for x in range(0, 31 + 1)] +
[
Special.FNC_3, Special.FNC_2, Special.SHIFT_B, Special.CODE_C,
Special.CODE_B, Special.FNC_4, Special.FNC_1,
Special.START_A, Special.START_B, Special.START_C, Special.STOP
],
# Code Set B includes ordinals 32 through 127 and 7 special characters. The ordinals include digits,
# upper and lover case characters and punctuation.
'B':
[chr(x) for x in range(32, 127 + 1)] +
[
Special.FNC_3, Special.FNC_2, Special.SHIFT_A, Special.CODE_C,
Special.FNC_4, Special.CODE_A, Special.FNC_1,
Special.START_A, Special.START_B, Special.START_C, Special.STOP
],
# Code Set C includes all pairs of 2 digits and 3 special characters.
'C':
['%02d' % (x,) for x in range(0, 99 + 1)] +
[
Special.CODE_B, Special.CODE_A, Special.FNC_1,
Special.START_A, Special.START_B, Special.START_C, Special.STOP
],
}
# Dicts mapping characters to symbol values in each character set.
_sym2val = {
'A': {char: val for val, char in enumerate(_val2sym['A'])},
'B': {char: val for val, char in enumerate(_val2sym['B'])},
'C': {char: val for val, char in enumerate(_val2sym['C'])},
}
# How large the quiet zone is on either side of the barcode, when quiet zone is used.
quiet_zone = 10
def __init__(self, data, charset=None):
"""Initialize a barcode with data as described by the character sets in charset.
:param data: The data to be encoded.
:param charset: A single character set (A, B or C), an iterable with a character set for each symbol or None.
- If a single character set is chosen, all characters will be encoded with that set, except for
incompatible characters which will be coded with one of the other character sets.
- If a sequence of character sets are given, incompatible characters will result in
Code128.IncompatibleCharsetError. Wrong size of the charset sequence in relation to data,
will result in Code128.CharsetLengthError.
- If None is given, the character set will be chosen as to minimize the length of the barcode.
"""
self._validate_charset(data, charset)
if charset in ('A', 'B'):
charset *= len(data)
elif charset in ('C',):
charset *= (len(data) // 2)
if len(data) % 2 == 1:
# If there are an odd number of characters for charset C, encode the last character with charset B.
charset += 'B'
self.data = data
self.symbol_values = self._encode(data, charset)
def width(self, add_quiet_zone=False):
"""Return the barcodes width in modules for a given data and character set combination.
:param add_quiet_zone: Whether quiet zone should be included in the width.
:return: Width of barcode in modules, which for images translates to pixels.
"""
quiet_zone = self.quiet_zone if add_quiet_zone else 0
return len(self.modules) + 2 * quiet_zone
@staticmethod
def _validate_charset(data, charset):
""""Validate that the charset is correct and throw an error if it isn't."""
if len(charset) > 1:
charset_data_length = 0
for symbol_charset in charset:
if symbol_charset not in ('A', 'B', 'C'):
raise Code128.CharsetError
charset_data_length += 2 if symbol_charset is 'C' else 1
if charset_data_length != len(data):
raise Code128.CharsetLengthError
elif len(charset) == 1:
if charset not in ('A', 'B', 'C'):
raise Code128.CharsetError
elif charset is not None:
raise Code128.CharsetError
@classmethod
def _encode(cls, data, charsets):
"""Encode the data using the character sets in charsets.
:param data: Data to be encoded.
:param charsets: Sequence of charsets that are used to encode the barcode.
Must be the exact amount of symbols needed to encode the data.
:return: List of the symbol values representing the barcode.
"""
result = []
charset = charsets[0]
start_symbol = cls._start_codes[charset]
result.append(cls._sym2val[charset][start_symbol])
cur = 0
prev_charset = charsets[0]
for symbol_num in range(len(charsets)):
charset = charsets[symbol_num]
if charset is not prev_charset:
# Handle a special case of there being a single A in middle of two B's or the other way around, where
# using a single shift character is more efficient than using two character set switches.
next_charset = charsets[symbol_num + 1] if symbol_num + 1 < len(charsets) else None
if charset == 'A' and prev_charset == next_charset == 'B':
result.append(cls._sym2val[prev_charset][cls.Special.SHIFT_A])
elif charset == 'B' and prev_charset == next_charset == 'A':
result.append(cls._sym2val[prev_charset][cls.Special.SHIFT_B])
else:
# This is the normal case.
charset_symbol = cls._char_codes[charset]
result.append(cls._sym2val[prev_charset][charset_symbol])
prev_charset = charset
nxt = cur + (2 if charset == 'C' else 1)
symbol = data[cur:nxt]
cur = nxt
result.append(cls._sym2val[charset][symbol])
result.append(cls._calc_checksum(result))
result.append(cls._sym2val[charset][cls.Special.STOP])
return result
@property
def symbols(self):
"""List of the coded symbols as strings, with special characters included."""
def _iter_symbols(symbol_values):
# The initial charset doesn't matter, as the start codes have the same symbol values in all charsets.
charset = 'A'
shift_charset = None
for symbol_value in symbol_values:
if shift_charset:
symbol = self._val2sym[shift_charset][symbol_value]
shift_charset = None
else:
symbol = self._val2sym[charset][symbol_value]
if symbol in (self.Special.START_A, self.Special.CODE_A):
charset = 'A'
elif symbol in (self.Special.START_B, self.Special.CODE_B):
charset = 'B'
elif symbol in (self.Special.START_C, self.Special.CODE_C):
charset = 'C'
elif symbol in (self.Special.SHIFT_A,):
shift_charset = 'A'
elif symbol in (self.Special.SHIFT_B,):
shift_charset = 'B'
yield symbol
return list(_iter_symbols(self.symbol_values))
@property
def bars(self):
"""A string of the bar and space weights of the barcode. Starting with a bar and alternating.
>>> barcode = Code128("Hello!", charset='B')
>>> barcode.bars
'2112142311131122142211142211141341112221221212412331112'
:rtype: string
"""
return ''.join(map((lambda val: self._val2bars[val]), self.symbol_values))
@property
def modules(self):
"""A list of the modules, with 0 representing a bar and 1 representing a space.
>>> barcode = Code128("Hello!", charset='B')
>>> barcode.modules # doctest: +ELLIPSIS
[0, 0, 1, 0, 1, 1, 0, 1, ..., 0, 0, 0, 1, 0, 1, 0, 0]
:rtype: list[int]
"""
def _iterate_modules(bars):
is_bar = True
for char in map(int, bars):
while char > 0:
char -= 1
yield 0 if is_bar else 1
is_bar = not is_bar
return list(_iterate_modules(self.bars))
@staticmethod
def _calc_checksum(values):
"""Calculate the symbol check character."""
checksum = values[0]
for index, value in enumerate(values):
checksum += index * value
return checksum % 103
def image(self, height=1, module_width=1, add_quiet_zone=True):
"""Get the barcode as PIL.Image.
By default the image is one pixel high and the number of modules pixels wide, with 10 empty modules added to
each side to act as the quiet zone. The size can be modified by setting height and module_width, but if used in
a web page it might be a good idea to do the scaling on client side.
:param height: Height of the image in number of pixels.
:param module_width: A multiplier for the width.
:param add_quiet_zone: Whether to add 10 empty modules to each side of the barcode.
:rtype: PIL.Image
:return: A monochromatic image containing the barcode as black bars on white background.
"""
if Image is None:
raise Code128.MissingDependencyError("PIL module is required to use image method.")
modules = list(self.modules)
if add_quiet_zone:
# Add ten space modules to each side of the barcode.
modules = [1] * self.quiet_zone + modules + [1] * self.quiet_zone
width = len(modules)
img = Image.new(mode='1', size=(width, 1))
img.putdata(modules)
if height == 1 and module_width == 1:
return img
else:
new_size = (width * module_width, height)
return img.resize(new_size, resample=Image.NEAREST)
def data_url(self, image_format='png', add_quiet_zone=True):
"""Get a data URL representing the barcode.
>>> barcode = Code128('Hello!', charset='B')
>>> barcode.data_url() # doctest: +ELLIPSIS
'data:image/png;base64,...'
:param image_format: Either 'png' or 'bmp'.
:param add_quiet_zone: Add a 10 white pixels on either side of the barcode.
:raises: Code128.UnknownFormatError
:raises: Code128.MissingDependencyError
:rtype: str
:returns: A data URL with the barcode as an image.
"""
memory_file = io.BytesIO()
pil_image = self.image(add_quiet_zone=add_quiet_zone)
# Using BMP can often result in smaller data URLs than PNG, but it isn't as widely supported by browsers as PNG.
# GIFs result in data URLs 10 times bigger than PNG or BMP, possibly due to lack of support for monochrome GIFs
# in Pillow, so they shouldn't be used.
if image_format == 'png':
# Unfortunately there is no way to avoid adding the zlib headers.
# Using compress_level=0 sometimes results in a slightly bigger data size (by a few bytes), but there
# doesn't appear to be a difference between levels 9 and 1, so let's just use 1.
pil_image.save(memory_file, format='png', compress_level=1)
elif image_format == 'bmp':
pil_image.save(memory_file, format='bmp')
else:
raise Code128.UnknownFormatError('Only png and bmp are supported.')
# Encode the data in the BytesIO object and convert the result into unicode.
base64_image = base64.b64encode(memory_file.getvalue()).decode('ascii')
data_url = 'data:image/{format};base64,{base64_data}'.format(
format=image_format,
base64_data=base64_image
)
return data_url
|