1__all__=( 2 'Ean13BarcodeWidget','isEanString', 3 'Ean8BarcodeWidget', 'UPCA', 'Ean5BarcodeWidget', 'ISBNBarcodeWidget', 4 ) 5from reportlab.graphics.shapes import Group, String, Rect 6from reportlab.lib import colors 7from reportlab.pdfbase.pdfmetrics import stringWidth 8from reportlab.lib.validators import isNumber, isColor, isString, Validator, isBoolean, NoneOr 9from reportlab.lib.attrmap import * 10from reportlab.graphics.charts.areas import PlotArea 11from reportlab.lib.units import mm 12from reportlab.lib.utils import asNative 13 14#work out a list of manufacturer codes.... 15_eanNumberSystems = [ 16 ('00-13', 'USA & Canada'), 17 ('20-29', 'In-Store Functions'), 18 ('30-37', 'France'), 19 ('40-44', 'Germany'), 20 ('45', 'Japan (also 49)'), 21 ('46', 'Russian Federation'), 22 ('471', 'Taiwan'), 23 ('474', 'Estonia'), 24 ('475', 'Latvia'), 25 ('477', 'Lithuania'), 26 ('479', 'Sri Lanka'), 27 ('480', 'Philippines'), 28 ('482', 'Ukraine'), 29 ('484', 'Moldova'), 30 ('485', 'Armenia'), 31 ('486', 'Georgia'), 32 ('487', 'Kazakhstan'), 33 ('489', 'Hong Kong'), 34 ('49', 'Japan (JAN-13)'), 35 ('50', 'United Kingdom'), 36 ('520', 'Greece'), 37 ('528', 'Lebanon'), 38 ('529', 'Cyprus'), 39 ('531', 'Macedonia'), 40 ('535', 'Malta'), 41 ('539', 'Ireland'), 42 ('54', 'Belgium & Luxembourg'), 43 ('560', 'Portugal'), 44 ('569', 'Iceland'), 45 ('57', 'Denmark'), 46 ('590', 'Poland'), 47 ('594', 'Romania'), 48 ('599', 'Hungary'), 49 ('600-601', 'South Africa'), 50 ('609', 'Mauritius'), 51 ('611', 'Morocco'), 52 ('613', 'Algeria'), 53 ('619', 'Tunisia'), 54 ('622', 'Egypt'), 55 ('625', 'Jordan'), 56 ('626', 'Iran'), 57 ('64', 'Finland'), 58 ('690-692', 'China'), 59 ('70', 'Norway'), 60 ('729', 'Israel'), 61 ('73', 'Sweden'), 62 ('740', 'Guatemala'), 63 ('741', 'El Salvador'), 64 ('742', 'Honduras'), 65 ('743', 'Nicaragua'), 66 ('744', 'Costa Rica'), 67 ('746', 'Dominican Republic'), 68 ('750', 'Mexico'), 69 ('759', 'Venezuela'), 70 ('76', 'Switzerland'), 71 ('770', 'Colombia'), 72 ('773', 'Uruguay'), 73 ('775', 'Peru'), 74 ('777', 'Bolivia'), 75 ('779', 'Argentina'), 76 ('780', 'Chile'), 77 ('784', 'Paraguay'), 78 ('785', 'Peru'), 79 ('786', 'Ecuador'), 80 ('789', 'Brazil'), 81 ('80-83', 'Italy'), 82 ('84', 'Spain'), 83 ('850', 'Cuba'), 84 ('858', 'Slovakia'), 85 ('859', 'Czech Republic'), 86 ('860', 'Yugloslavia'), 87 ('869', 'Turkey'), 88 ('87', 'Netherlands'), 89 ('880', 'South Korea'), 90 ('885', 'Thailand'), 91 ('888', 'Singapore'), 92 ('890', 'India'), 93 ('893', 'Vietnam'), 94 ('899', 'Indonesia'), 95 ('90-91', 'Austria'), 96 ('93', 'Australia'), 97 ('94', 'New Zealand'), 98 ('955', 'Malaysia'), 99 ('977', 'International Standard Serial Number for Periodicals (ISSN)'), 100 ('978', 'International Standard Book Numbering (ISBN)'), 101 ('979', 'International Standard Music Number (ISMN)'), 102 ('980', 'Refund receipts'), 103 ('981-982', 'Common Currency Coupons'), 104 ('99', 'Coupons') 105 ] 106 107manufacturerCodes = {} 108for (k, v) in _eanNumberSystems: 109 words = k.split('-') 110 if len(words)==2: 111 fromCode = int(words[0]) 112 toCode = int(words[1]) 113 for code in range(fromCode, toCode+1): 114 manufacturerCodes[code] = v 115 else: 116 manufacturerCodes[int(k)] = v 117 118def nDigits(n): 119 class _ndigits(Validator): 120 def test(self,x): 121 return type(x) is str and len(x)<=n and len([c for c in x if c in "0123456789"])==n 122 return _ndigits() 123 124class Ean13BarcodeWidget(PlotArea): 125 codeName = "EAN13" 126 _attrMap = AttrMap(BASE=PlotArea, 127 value = AttrMapValue(nDigits(12), desc='the number'), 128 fontName = AttrMapValue(isString, desc='fontName'), 129 fontSize = AttrMapValue(isNumber, desc='font size'), 130 x = AttrMapValue(isNumber, desc='x-coord'), 131 y = AttrMapValue(isNumber, desc='y-coord'), 132 barFillColor = AttrMapValue(isColor, desc='bar color'), 133 barHeight = AttrMapValue(isNumber, desc='Height of bars.'), 134 barWidth = AttrMapValue(isNumber, desc='Width of bars.'), 135 barStrokeWidth = AttrMapValue(isNumber, desc='Width of bar borders.'), 136 barStrokeColor = AttrMapValue(isColor, desc='Color of bar borders.'), 137 textColor = AttrMapValue(isColor, desc='human readable text color'), 138 humanReadable = AttrMapValue(isBoolean, desc='if human readable'), 139 quiet = AttrMapValue(isBoolean, desc='if quiet zone to be used'), 140 lquiet = AttrMapValue(isBoolean, desc='left quiet zone length'), 141 rquiet = AttrMapValue(isBoolean, desc='right quiet zone length'), 142 ) 143 _digits=12 144 _start_right = 7 #for ean-13 left = [0:7] right=[7:13] 145 _nbars = 113 146 barHeight = 25.93*mm #millimeters 147 barWidth = (37.29/_nbars)*mm 148 humanReadable = 1 149 _0csw = 1 150 _1csw = 3 151 152 #Left Hand Digits. 153 _left = ( ("0001101", "0011001", "0010011", "0111101", 154 "0100011", "0110001", "0101111", "0111011", 155 "0110111", "0001011", 156 ), #odd left hand digits 157 ("0100111", "0110011", "0011011", "0100001", 158 "0011101", "0111001", "0000101", "0010001", 159 "0001001", "0010111"), #even left hand digits 160 ) 161 162 _right = ("1110010", "1100110", "1101100", "1000010", 163 "1011100", "1001110", "1010000", "1000100", 164 "1001000", "1110100") 165 166 quiet = 1 167 rquiet = lquiet = None 168 _tail = "101" 169 _sep = "01010" 170 171 _lhconvert={ 172 "0": (0,0,0,0,0,0), 173 "1": (0,0,1,0,1,1), 174 "2": (0,0,1,1,0,1), 175 "3": (0,0,1,1,1,0), 176 "4": (0,1,0,0,1,1), 177 "5": (0,1,1,0,0,1), 178 "6": (0,1,1,1,0,0), 179 "7": (0,1,0,1,0,1), 180 "8": (0,1,0,1,1,0), 181 "9": (0,1,1,0,1,0) 182 } 183 fontSize = 8 #millimeters 184 fontName = 'Helvetica' 185 textColor = barFillColor = colors.black 186 barStrokeColor = None 187 barStrokeWidth = 0 188 x = 0 189 y = 0 190 def __init__(self,value='123456789012',**kw): 191 value = str(value) if isinstance(value,int) else asNative(value) 192 self.value=max(self._digits-len(value),0)*'0'+value[:self._digits] 193 for k, v in kw.items(): 194 setattr(self, k, v) 195 196 width = property(lambda self: self.barWidth*(self._nbars-18+self._calc_quiet(self.lquiet)+self._calc_quiet(self.rquiet))) 197 198 def wrap(self,aW,aH): 199 return self.width,self.barHeight 200 201 def _encode_left(self,s,a): 202 cp = self._lhconvert[s[0]] #convert the left hand numbers 203 _left = self._left 204 z = ord('0') 205 for i,c in enumerate(s[1:self._start_right]): 206 a(_left[cp[i]][ord(c)-z]) 207 208 def _short_bar(self,i): 209 i += 9 - self._lquiet 210 return self.humanReadable and ((12<i<55) or (57<i<101)) 211 212 def _calc_quiet(self,v): 213 if self.quiet: 214 if v is None: 215 v = 9 216 else: 217 x = float(max(v,0))/self.barWidth 218 v = int(x) 219 if v-x>0: v += 1 220 else: 221 v = 0 222 return v 223 224 def draw(self): 225 g = Group() 226 gAdd = g.add 227 barWidth = self.barWidth 228 width = self.width 229 barHeight = self.barHeight 230 x = self.x 231 y = self.y 232 gAdd(Rect(x,y,width,barHeight,fillColor=None,strokeColor=None,strokeWidth=0)) 233 s = self.value+self._checkdigit(self.value) 234 self._lquiet = lquiet = self._calc_quiet(self.lquiet) 235 rquiet = self._calc_quiet(self.rquiet) 236 b = [lquiet*'0',self._tail] #the signal string 237 a = b.append 238 self._encode_left(s,a) 239 a(self._sep) 240 241 z = ord('0') 242 _right = self._right 243 for c in s[self._start_right:]: 244 a(_right[ord(c)-z]) 245 a(self._tail) 246 a(rquiet*'0') 247 248 fontSize = self.fontSize 249 barFillColor = self.barFillColor 250 barStrokeWidth = self.barStrokeWidth 251 barStrokeColor = self.barStrokeColor 252 253 fth = fontSize*1.2 254 b = ''.join(b) 255 256 lrect = None 257 for i,c in enumerate(b): 258 if c=="1": 259 dh = self._short_bar(i) and fth or 0 260 yh = y+dh 261 if lrect and lrect.y==yh: 262 lrect.width += barWidth 263 else: 264 lrect = Rect(x,yh,barWidth,barHeight-dh,fillColor=barFillColor,strokeWidth=barStrokeWidth,strokeColor=barStrokeColor) 265 gAdd(lrect) 266 else: 267 lrect = None 268 x += barWidth 269 270 if self.humanReadable: self._add_human_readable(s,gAdd) 271 return g 272 273 def _add_human_readable(self,s,gAdd): 274 barWidth = self.barWidth 275 fontSize = self.fontSize 276 textColor = self.textColor 277 fontName = self.fontName 278 fth = fontSize*1.2 279 # draw the num below the line. 280 c = s[0] 281 w = stringWidth(c,fontName,fontSize) 282 x = self.x+barWidth*(self._lquiet-8) 283 y = self.y + 0.2*fth 284 285 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor)) 286 x = self.x + (33-9+self._lquiet)*barWidth 287 288 c = s[1:7] 289 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle')) 290 291 x += 47*barWidth 292 c = s[7:] 293 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle')) 294 295 def _checkdigit(cls,num): 296 z = ord('0') 297 iSum = cls._0csw*sum([(ord(x)-z) for x in num[::2]]) \ 298 + cls._1csw*sum([(ord(x)-z) for x in num[1::2]]) 299 return chr(z+((10-(iSum%10))%10)) 300 _checkdigit=classmethod(_checkdigit) 301 302class Ean8BarcodeWidget(Ean13BarcodeWidget): 303 codeName = "EAN8" 304 _attrMap = AttrMap(BASE=Ean13BarcodeWidget, 305 value = AttrMapValue(nDigits(7), desc='the number'), 306 ) 307 _start_right = 4 #for ean-13 left = [0:7] right=[7:13] 308 _nbars = 85 309 _digits=7 310 _0csw = 3 311 _1csw = 1 312 313 def _encode_left(self,s,a): 314 cp = self._lhconvert[s[0]] #convert the left hand numbers 315 _left = self._left[0] 316 z = ord('0') 317 for i,c in enumerate(s[0:self._start_right]): 318 a(_left[ord(c)-z]) 319 320 def _short_bar(self,i): 321 i += 9 - self._lquiet 322 return self.humanReadable and ((12<i<41) or (43<i<73)) 323 324 def _add_human_readable(self,s,gAdd): 325 barWidth = self.barWidth 326 fontSize = self.fontSize 327 textColor = self.textColor 328 fontName = self.fontName 329 fth = fontSize*1.2 330 # draw the num below the line. 331 y = self.y + 0.2*fth 332 333 x = (26.5-9+self._lquiet)*barWidth 334 335 c = s[0:4] 336 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle')) 337 338 x = (59.5-9+self._lquiet)*barWidth 339 c = s[4:] 340 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle')) 341 342class UPCA(Ean13BarcodeWidget): 343 codeName = "UPCA" 344 _attrMap = AttrMap(BASE=Ean13BarcodeWidget, 345 value = AttrMapValue(nDigits(11), desc='the number'), 346 ) 347 _start_right = 6 348 _digits = 11 349 _0csw = 3 350 _1csw = 1 351 _nbars = 1+7*11+2*3+5 352 353 #these methods contributed by Kyle Macfarlane 354 #https://bitbucket.org/kylemacfarlane/ 355 def _encode_left(self,s,a): 356 cp = self._lhconvert[s[0]] #convert the left hand numbers 357 _left = self._left[0] 358 z = ord('0') 359 for i,c in enumerate(s[0:self._start_right]): 360 a(_left[ord(c)-z]) 361 362 def _short_bar(self,i): 363 i += 9 - self._lquiet 364 return self.humanReadable and ((18<i<55) or (57<i<93)) 365 366 def _add_human_readable(self,s,gAdd): 367 barWidth = self.barWidth 368 fontSize = self.fontSize 369 textColor = self.textColor 370 fontName = self.fontName 371 fth = fontSize*1.2 372 # draw the num below the line. 373 c = s[0] 374 w = stringWidth(c,fontName,fontSize) 375 x = self.x+barWidth*(self._lquiet-8) 376 y = self.y + 0.2*fth 377 378 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor)) 379 x = self.x + (38-9+self._lquiet)*barWidth 380 381 c = s[1:6] 382 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle')) 383 384 x += 36*barWidth 385 c = s[6:11] 386 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor,textAnchor='middle')) 387 388 x += 32*barWidth 389 c = s[11] 390 gAdd(String(x,y,c,fontName=fontName,fontSize=fontSize,fillColor=textColor)) 391 392class Ean5BarcodeWidget(Ean13BarcodeWidget): 393 """ 394 EAN-5 barcodes can print the human readable price, set: 395 price=True 396 """ 397 codeName = "EAN5" 398 _attrMap = AttrMap(BASE=Ean13BarcodeWidget, 399 price=AttrMapValue(isBoolean, 400 desc='whether to display the price or not'), 401 value=AttrMapValue(nDigits(5), desc='the number'), 402 ) 403 _nbars = 48 404 _digits = 5 405 _sep = '01' 406 _tail = '01011' 407 _0csw = 3 408 _1csw = 9 409 410 _lhconvert = { 411 "0": (1, 1, 0, 0, 0), 412 "1": (1, 0, 1, 0, 0), 413 "2": (1, 0, 0, 1, 0), 414 "3": (1, 0, 0, 0, 1), 415 "4": (0, 1, 1, 0, 0), 416 "5": (0, 0, 1, 1, 0), 417 "6": (0, 0, 0, 1, 1), 418 "7": (0, 1, 0, 1, 0), 419 "8": (0, 1, 0, 0, 1), 420 "9": (0, 0, 1, 0, 1) 421 } 422 423 def _checkdigit(cls, num): 424 z = ord('0') 425 iSum = cls._0csw * sum([(ord(x) - z) for x in num[::2]]) \ 426 + cls._1csw * sum([(ord(x) - z) for x in num[1::2]]) 427 return chr(z + iSum % 10) 428 429 def _encode_left(self, s, a): 430 check = self._checkdigit(s) 431 cp = self._lhconvert[check] 432 _left = self._left 433 _sep = self._sep 434 z = ord('0') 435 full_code = [] 436 for i, c in enumerate(s): 437 full_code.append(_left[cp[i]][ord(c) - z]) 438 a(_sep.join(full_code)) 439 440 def _short_bar(self, i): 441 i += 9 - self._lquiet 442 return self.humanReadable and ((12 < i < 41) or (43 < i < 73)) 443 444 def _add_human_readable(self, s, gAdd): 445 barWidth = self.barWidth 446 fontSize = self.fontSize 447 textColor = self.textColor 448 fontName = self.fontName 449 fth = fontSize * 1.2 450 # draw the num below the line. 451 y = self.y + 0.2 * fth 452 453 x = self.x + (self._nbars + self._lquiet * 2) * barWidth / 2 454 455 gAdd(String(x, y, s, fontName=fontName, fontSize=fontSize, 456 fillColor=textColor, textAnchor='middle')) 457 458 price = getattr(self,'price',None) 459 if price: 460 price = None 461 if s[0] in '3456': 462 price = '$' 463 elif s[0] in '01': 464 price = asNative(b'\xc2\xa3') 465 466 if price is None: 467 return 468 469 price += s[1:3] + '.' + s[3:5] 470 y += self.barHeight 471 gAdd(String(x, y, price, fontName=fontName, fontSize=fontSize, 472 fillColor=textColor, textAnchor='middle')) 473 474 def draw(self): 475 g = Group() 476 gAdd = g.add 477 barWidth = self.barWidth 478 width = self.width 479 barHeight = self.barHeight 480 x = self.x 481 y = self.y 482 gAdd(Rect(x, y, width, barHeight, fillColor=None, strokeColor=None, 483 strokeWidth=0)) 484 s = self.value 485 self._lquiet = lquiet = self._calc_quiet(self.lquiet) 486 rquiet = self._calc_quiet(self.rquiet) 487 b = [lquiet * '0' + self._tail] # the signal string 488 a = b.append 489 self._encode_left(s, a) 490 491 a(rquiet * '0') 492 493 fontSize = self.fontSize 494 barFillColor = self.barFillColor 495 barStrokeWidth = self.barStrokeWidth 496 barStrokeColor = self.barStrokeColor 497 498 fth = fontSize * 1.2 499 b = ''.join(b) 500 501 lrect = None 502 for i, c in enumerate(b): 503 if c == "1": 504 dh = fth 505 yh = y + dh 506 if lrect and lrect.y == yh: 507 lrect.width += barWidth 508 else: 509 lrect = Rect(x, yh, barWidth, barHeight - dh, 510 fillColor=barFillColor, 511 strokeWidth=barStrokeWidth, 512 strokeColor=barStrokeColor) 513 gAdd(lrect) 514 else: 515 lrect = None 516 x += barWidth 517 518 if self.humanReadable: 519 self._add_human_readable(s, gAdd) 520 return g 521 522class ISBNBarcodeWidget(Ean13BarcodeWidget): 523 """ 524 ISBN Barcodes optionally print the EAN-5 supplemental price 525 barcode (with the price in dollars or pounds). Set price to a string 526 that follows the EAN-5 for ISBN spec: 527 528 leading digit 0, 1 = GBP 529 3 = AUD 530 4 = NZD 531 5 = USD 532 6 = CAD 533 next 4 digits = price between 00.00 and 99.98, i.e.: 534 535 price='52499' # $24.99 USD 536 """ 537 codeName = 'ISBN' 538 _attrMap = AttrMap(BASE=Ean13BarcodeWidget, 539 price=AttrMapValue( 540 NoneOr(nDigits(5)), 541 desc='None or the price to display'), 542 ) 543 def draw(self): 544 g = Ean13BarcodeWidget.draw(self) 545 546 price = getattr(self,'price',None) 547 if not price: 548 return g 549 550 bounds = g.getBounds() 551 x = bounds[2] 552 pricecode = Ean5BarcodeWidget(x=x, value=price, price=True, 553 humanReadable=True, 554 barHeight=self.barHeight, quiet=self.quiet) 555 g.add(pricecode) 556 return g 557 558 def _add_human_readable(self, s, gAdd): 559 Ean13BarcodeWidget._add_human_readable(self,s, gAdd) 560 barWidth = self.barWidth 561 barHeight = self.barHeight 562 fontSize = self.fontSize 563 textColor = self.textColor 564 fontName = self.fontName 565 fth = fontSize * 1.2 566 y = self.y + 0.2 * fth + barHeight 567 x = self._lquiet * barWidth 568 569 isbn = 'ISBN ' 570 segments = [s[0:3], s[3:4], s[4:9], s[9:12], s[12]] 571 isbn += '-'.join(segments) 572 573 gAdd(String(x, y, isbn, fontName=fontName, fontSize=fontSize, 574 fillColor=textColor)) 575