1 #!/usr/local/bin/python
5 COPYRIGHT="(C) 1999-2001 J. Milgram"
7 MAINTAINER = "bookland-bugs@cgpp.com"
9 # Copyright (C) 1999,2000 Judah Milgram
11 # bookland.py - generate Bookland EAN symbol for ISBN encoding
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; either version 2
16 # of the License, or (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License along
24 # with this program; if not, write to the Free Software Foundation, Inc.,
25 # 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
27 # ==============================================================================
29 # usage: bookland.py [ISBN] [price-code] > output.eps
31 # ISBN - the ISBN, with or without check digit, with or without hyphens.
32 # default: 1-56592-197-6 ("Programming Python"). If the check digit
33 # is provided on the command line, it is verified. If not, it is
34 # calculated. It's up to you to get the hyphenation right - it's
35 # important, and something the program can't calculate for you.
37 # price - the five digit add-on code. Usually used to indicate the price,
38 # in which case the first digit indicates the currency (4=$CAN,
39 # 5=$US, etc.). The remaining digits indicate the price, with
40 # decimal point assumed to be between the digit 3 and 4.
41 # For example: $US 6.95 = 50695, $CAN 35.00 = 43500. Instead of a
42 # price code, a 5 digit add-on ranging from 90000-98999 can be
43 # used for internal purposes. BISG recommends just using 90000 if
44 # you don't want to specify a price. Add-ons ranging from 99000 to
45 # 99999 have been reserved for special use.
47 # An Encapsulated Postscript file (eps) is sent to standard out. This may in turn
48 # be converted to other formats using the pbmplus package. You may have trouble
49 # getting the OCRB to map correctly. If you already have the font, you can look in
50 # the Fontmap file to see what your system calls it, and edit the fontnames accordingly
51 # (see below). If you don't have it, you might find it on your DOS system. You
52 # need a .pfa/.pfb (Type 1) or .ttf (TrueType). Your Postscript interpreter might
53 # or might not be able to deal with TrueType. In any event, in an emergency, you
54 # might get away with Helvetica. Note that as of 1990 BISG no longer requires the
55 # ISBN to be printed in OCR-A.
57 # Take the "no-warranty" disclaimer seriously. Going to print with a faulty bar
58 # can cost you a bundle, and you'll be on your own. It's up to you to verify that
59 # the symbol is valid. If you need "corporate accountability", try the Book
60 # Industry Study Group at (212) 929-1393 or the US ISBN Agency at (908) 665-6770
61 # and ask for a list of commercial vendors. Outside the US, don't know.
63 # Feedback welcome. If you discover a case where the program generates a faulty
64 # symbol, I definitely want to hear about it - write me at milgram@cgpp.com or
65 # P.O. Box 8376, Langley Park, MD 20787, USA
69 # If you have a Python interpreter on your system, you're done. Just put this file
70 # somewhere in your path and give it execute permission. If you haven't installed
71 # Python, see http://www.python.org. It has been ported to Macs, DOS, and MS-Windows.
73 # ABOUT THE BOOKLAND EAN
75 # The most difficult part of this project was finding the documents that define
76 # the Bookland EAN. There appears to be no single, authoritative source that
77 # provides all the information required. Some good sources:
79 # [1] "Machine-Readable Coding Guidelines for the U.S. Book Industry", Book
80 # Industry Study Group, New York, Jan., 1992. (212) 929-1393
81 # [2] "UPC Symbol Specification Manual", Uniform Code Council Inc.,
82 # Dayton, Ohio, January 1986 (May 1995 Reprint). (937) 435-3870; I found it
83 # at http://www.uc-council.org/d36-t.htm
84 # [3] "EAN Identification for Retail/Trade Items", EAN International. I found it
85 # in Feb. 1999 at http://www.ean.be/html/Numbering.html
86 # [4] "Hyphenation Instructions", web page at:
87 # http://www.isbn.org/standards/home/isbn/international/hyphenation-instructions.asp
89 # The starting point of the exercise is the ISBN, assigned by the national ISBN
90 # Agency. This is a 10 digit number, the last being a check digit. The ISBN is
91 # converted to a 13 digit EAN number. The first three digits of the EAN-13 indicate
92 # the country or region. A prefix of 978 has been assigned to books, regardless
93 # of country of origin (hence, "Bookland") [3]. The remaining ten digits are the
94 # first 9 digits of the ISBN followed by the EAN-13 check digit.
96 # It seems the EAN-13 check digit can be calculated using the same algorithm as the
97 # UPC Version A number. Note that the EAN-13 check digit is always between 0 and 9,
98 # compare with ISBN check digit which can range to 10 ("X"). See Reference [2],
99 # Section 2 and Appendix G for details of creation of the EAN-13 symbol. Table 2 of
100 # Appendix G provides a good comparison of the UPC-A and EAN-13 symbols.
102 # The 5 digit add-on (here called, "UPC5") is defined in Ref. [2] Appendix D.
103 # The ">" to the right of the five digit code serves to enforce the "quiet zone" to
104 # the right of the bar pattern. Can't remember where I read that. It's probably
105 # optional. According to [1], in the UK, three horizontal bars appear over price
106 # add-ons. Haven't implemented that here. The UPC5 encoding is based on UPC-A and
109 # According to [2], Section 3, the EAN-13 numbers and 5-digit add-ons are supposed
110 # to be printed in OCR-B. The ISBN itself is printed above the EAN-13 symbol. At
111 # one time it was to be printed in OCR-A, but as of 1990 this requirement has been
112 # dropped [1], and I assume this means you can use any font you like.
116 # "TinyHelp 5 - Making ISBN Barcodes", D. Byram-Wigfield. Another approach to making
117 # the ISBN barcode symbol. I saw it at
118 # http://www.cappella.demon.co.uk/index.html/
119 # but haven't tried it.
121 # "XBarcode" - nice open-source X-Windows program for generating all sorts of bar codes.
122 # It does much more than this program, but didn't seem to do the UPC
123 # 5-digit add-on or do the ISBN->EAN13 calculation (as of v. 2.11). Might
124 # have made more sense to add this capability, but I needed a Python project.
125 # In any event, their license forbids distribution in modified form!
129 # bookland.py includes automatic hyphenation for ISBN's in group 0 and 1
130 # (English-language). This is based on my reading of Ref [4]. If in doubt,
131 # users can use the "-y" option to force the program to accept the hyphenation
132 # as input. For other ISBN groups and for ISMN's, no hyphenation is performed
133 # other than to ensure a hyphen is placed after the group identifier and before
138 # See the ISMN Users' Manual, 3rd Edition, 1998, ISBN 3-88053-070-X, published by
139 # the International ISMN Agency, Staatsbibliothek Preussischer Kulturbesitz, Berlin.
140 # I found my copy at http://www.ismn.spk-berlin.de/download/ISMNmanual.pdf
142 # An ISMN is just like an ISBN, except:
143 # - first character is an "M"
144 # - the "M" counts as a "3" for computing the ISMN check digit (last digit)
145 # - the checksum weights are 3,1,3,1,3,1,3,1,3, sum to be divisible by "10". This
146 # means the last character is always a numerical digit, never an "X".
147 # - the EAN number is "979" plus the *entire* ten character ISMN, except the
148 # "M" is replaced by "0". Note this means the ISMN checksum is identical to the
149 # corresponding EAN-13 checksum (excercise left to the reader).
151 # When bookland.py detects an "M" in the first position of the ISBN, it interprets
152 # it as an ISMN and proceeds accordingly. The 5-digit price code symbol is suppressed.
154 # BAR WIDTH REDUCTIONS
156 # Starting Version 0.92, the widths of the individual bars can be reduced using the
157 # "-r" option (units are inches). This is to compensate for bleed during printing.
158 # I don't know when it's a good idea to actually use this; in any event consult with
159 # your printer first. If not input, it defaults to zero (no reduction).
163 # See http://www.python.org
167 # - Generalize to more bar codes, starting with UPC-A and UPC-E. "Plain" EAN13 is
168 # already built in, could add command line argument to generate that instead of
170 # - Make font sizes and placement easier to configure - not sure I have it right.
171 # Does human-readable 5-digit code take wider font spacing?
172 # - Clean up bounding box stuff.
173 # - Bells and whistles.
178 # 1/2002 - v 0.92 add ISMN support (thanks to Lars-Henrik Nysten for this suggestion)
179 # re-wrote bar generation to preclude possibility of white hairlines
180 # between adjacent black modules. Thanks to Tero Lindfors for
181 # reporting this bug.
182 # new -o option to write eps to file rather than stdout
183 # new -x option for "check only" (verifies check digit)
184 # new -r option for bar width reduction (compensate for print bleed)
185 # new -s option to scale module (bar) height (Lars-Henrik Nysten again)
186 # can suppress UPC-5 price code by entering empty string. (thanks to
187 # Jacques Du Pasquier for this suggestion)
188 # re-wrote ISBN/ISMN sanity checks
189 # lowercase alphas ("x" for ISBN and "m" for ISMN) now ok on input
190 # fix "long" command line options.
191 # 10/2001 - v 0.91 add -z option for quiet zone ">"
192 # add -f option for fonts
193 # re-write command line parsing to use getopt
194 # 1/2000 - v 0.09 eliminate use of eval
195 # 9/99 - v 0.08 accomodate different versions of OCRB by fitting
196 # all strings to prescribed width. Thanks to Toby Gadd
197 # for reporting this problem and Peter Deutsch for
198 # help finding the fix.
199 # 7/99 -v0.05-0.07 improve error handling.
200 # 3/27/99 - v0.04 add "--help" and "--version".
201 # 3/13/99 - v0.03, do a showpage at end (it's allowed)
202 # fixed checksum calculations for certain cases
203 # 2/7/99 - v0.02, fixed LH parity pattern for EAN13. It's not the check digit!
204 # 2/7/99 - initial release
205 # ================================================================================
208 # barCodeSymbol - the whole printed symbol, including bar code(s) and product code(s).
209 # UPC, UPCA, UPC5, EAN13 - the number itself, with check digit, string representation,
213 import re # we should get rid of regsub and regex in favor of re
214 # hope there's no conflict.
218 from regex_syntax import *
220 regex.set_syntax(RE_SYNTAX_AWK)
223 BooklandError = "Something wrong"
226 A="A";B="B";C="C";O="O";E="E"
227 UPCABITS = [{O:"0001101",E:"1110010"},
228 {O:"0011001",E:"1100110"},
229 {O:"0010011",E:"1101100"},
230 {O:"0111101",E:"1000010"},
231 {O:"0100011",E:"1011100"},
232 {O:"0110001",E:"1001110"},
233 {O:"0101111",E:"1010000"},
234 {O:"0111011",E:"1000100"},
235 {O:"0110111",E:"1001000"},
236 {O:"0001011",E:"1110100"}]
237 UPCAPARITY = [ "OOOOOOEEEEEE" ] * 10
238 UPCEBITS = [{O:"0001101",E:"0100111"},
239 {O:"0011001",E:"0110011"},
240 {O:"0010011",E:"0011011"},
241 {O:"0111101",E:"0100001"},
242 {O:"0100011",E:"0011101"},
243 {O:"0110001",E:"0111001"},
244 {O:"0101111",E:"0000101"},
245 {O:"0111011",E:"0010001"},
246 {O:"0110111",E:"0001001"},
247 {O:"0001011",E:"0010111"}]
248 # what about UPCEPARITY? Don't need for isbn.
250 UPC5PARITY = ["EEOOO","EOEOO","EOOEO","EOOOE","OEEOO",
251 "OOEEO","OOOEE","OEOEO","OEOOE","OOEOE"]
252 EAN13BITS = [{A:"0001101", B:"0100111", C:"1110010"},
253 {A:"0011001", B:"0110011", C:"1100110"},
254 {A:"0010011", B:"0011011", C:"1101100"},
255 {A:"0111101", B:"0100001", C:"1000010"},
256 {A:"0100011", B:"0011101", C:"1011100"},
257 {A:"0110001", B:"0111001", C:"1001110"},
258 {A:"0101111", B:"0000101", C:"1010000"},
259 {A:"0111011", B:"0010001", C:"1000100"},
260 {A:"0110111", B:"0001001", C:"1001000"},
261 {A:"0001011", B:"0010111", C:"1110100"}]
262 EAN13PARITY = map(lambda x: x+"CCCCCC",
263 ["AAAAAA","AABABB","AABBAB","AABBBA","ABAABB",
264 "ABBAAB","ABBBAA","ABABAB","ABABBA","ABBABA"])
268 # Fonts might have a different name on your system.
270 ISBNFONT = "OCRB" # Doesn't have to be OCR-B
277 self.x0 = 100; self.y0 = 100
279 self.bb=[self.x0,self.y0,self.x0,self.y0]
282 self.bb[0] = min(self.bb[0],self.x0+arg[0])
283 self.bb[1] = min(self.bb[1],self.y0+arg[1])
284 self.bb[2] = max(self.bb[2],self.x0+arg[2])
285 self.bb[3] = max(self.bb[3],self.y0+arg[3])
287 def translate(self,dx,dy):
288 self.x0 = self.x0 + dx
289 self.y0 = self.y0 + dy
290 return "%d %d translate 0 0 moveto" % (dx,dy)
292 def out(self,file=None):
294 outfid=open(file,"w")
297 for line in self.lines:
298 outfid.write("%s\n"%line)
302 self.lines = self.lines + arg
305 for i in range(len(self.lines)):
306 if self.lines[i]=="%%BoundingBox: TBD":
307 self.lines[i]= "%%BoundingBox:" + \
314 def header(self,title,comments,ean13font,isbnfont,upc5font):
315 for i in range(len(comments)):
316 comments[i] = regsub.gsub("^","% ",comments[i])
317 # There's a more elegant way to do the bounding box line:
318 return [ "%!PS-Adobe-2.0 EPSF-1.2",
319 "%%Creator: " + MYNAME + " " + MYVERSION + " " + DATE,
321 "%%BoundingBox: TBD",
324 [ "\n% These font names might be different on your system:",
325 "/ean13font { /" + ean13font + " findfont 10 scalefont setfont } def",
326 "/isbnfont { /" + isbnfont + " findfont 8 scalefont setfont } def",
327 "/upc5font { /" + upc5font +" findfont 14 scalefont setfont } def\n",
328 "/nextModule { moduleWidth 0 rmoveto } def",
329 "% The following shenanigans is to deal with different implementations",
330 "% of same font having different char sizes and spacing.",
331 "% function fitstring:",
332 "% usage: width string font fitstring",
333 "% set font, scaled so that string exactly fits desired width",
334 "% leave string on stack",
335 "/fitstring { dup findfont 1 scalefont setfont % w s f",
337 "dup stringwidth pop % f w s sw",
338 "3 2 roll exch div % f s x",
339 "3 2 roll findfont exch scalefont setfont",
341 "/barHeight { 72 } def",
342 "/nextModule { moduleWidth 0 rmoveto } def",
343 "/topcentershow {dup stringwidth pop neg 2 div -9 rmoveto show} def",
344 "/toprightshow {dup stringwidth pop neg -9 rmoveto show} def",
345 "/bottomcentershow {dup stringwidth pop neg 2 div 0 rmoveto show} def",
346 "/bottomrightshow {dup stringwidth pop neg 0 rmoveto show} def",
347 "/W { moduleWidth mul 0 rmoveto } def",
348 "/B { dup moduleWidth mul 2 div 0 rmoveto",
349 "dup moduleWidth mul barWidthReduction sub setlinewidth",
350 "0 barHeight rlineto 0 barHeight neg rmoveto",
351 "currentpoint stroke moveto",
352 "moduleWidth mul 2 div 0 rmoveto } def",
353 "/L { dup moduleWidth mul 2 div 0 rmoveto",
354 "dup moduleWidth mul barWidthReduction sub setlinewidth",
355 "0 -5 rmoveto 0 5 rlineto",
356 "0 barHeight rlineto 0 barHeight neg rmoveto",
357 "currentpoint stroke moveto",
358 "moduleWidth mul 2 div 0 rmoveto } def",
359 self.x0,self.y0,"translate",
363 return ["stroke","% showpage supposedly OK in EPS",
364 "showpage","\n% Good luck!"]
369 # Includes UPC-A, UPC-E, EAN-13 (sorry), UPC-5 et al.
371 def __init__(self,arg):
372 # arg is a string, either:
373 # - product code including checksum
374 # - same, with hyphens (hyphens not verified)
375 # - same, but with last digit (checksum) dropped, possibly leaving a
377 # If checksum is included, it will be verified.
378 # N.B. "integer" representation is still a string! Just has no hyphens.
381 self.verifyChars(self.s)
382 self.n = regsub.gsub("-","",self.s) # create "integer" representation
383 self.x = self.checkDigit(self.n) # always calculate check digit
384 if len(self.n) == self.ndigits:
385 self.verifyCheckDigit() # if check digit given, verify it
386 elif len(self.n) == self.ndigits-1:
387 self.tackonCheckDigit() # tack on check digit
389 raise BooklandError, "UPC: wrong number of digits in \"" + self.s + "\""
391 def setbits(self,arg): # UPC (all)
393 parityPattern=self.parityPattern()
394 bitchar=self.bitchar()
395 for p in range(len(arg)):
397 # maybe better to define parityPattern with a leading blank?
398 parity=parityPattern[p]
399 bit=bitchar[digit][parity]
400 self.bits=self.bits + bit
402 def verifyChars(self,s): # UPC (all)
403 # Trailing hyphen allowed.
404 nevergood = "--|^-|[^0-9-]"
405 ierr=regex.search(nevergood,s)
407 raise BooklandError, \
408 "UPCA: in %s: illegal characters beginning with: %s" % (s,s[ierr])
410 def verifyCheckDigit(self): # UPC (all)
411 # first verify correct number of digits.
412 soll=self.checkDigit(self.n)
415 raise BooklandError, "For %s checksum %s is wrong, should be %s" % \
418 def xstring(self,p): # UPC (all)
421 def tackonCheckDigit(self):
422 self.n = self.n + self.x # UPC (all)
423 self.s = self.s + self.x
427 def __init__(self,arg):
428 UPC.__init__(self,arg)
429 self.setbits(self.n[1:]) # skip first digit
431 def parityPattern(self):
432 return UPCAPARITY[int(self.x)]
436 def checkDigit(self,arg): # UPCA/EAN13
437 weight=[1,3]*6; magic=10; sum = 0
438 for i in range(12): # checksum based on first 12 digits.
439 sum = sum + int(arg[i]) * weight[i]
440 z = ( magic - (sum % magic) ) % magic
441 if z < 0 or z >= magic:
442 raise BooklandError, "UPC checkDigit: something wrong."
443 return self.xstring(z)
447 # Includes ISMN, if the plan falls together.
448 def __init__(self,arg):
449 self.ndigits=10 # Includes check digit!
450 self.s=string.upper(arg)
451 self.n=re.sub("[ -]","",self.s) # "integer" representation
452 # In ISMN, I allow spaces in place of hyphens. See ISMN User's manual.
453 if re.match("^M( |-)?\d(( |-)?\d){7,7}(-| )?\d?$",self.s):
456 self.n=re.sub("^M","3",self.n)
457 self.weight=[3,1,3,1,3,1,3,1,3]
459 elif re.match("^\d-?\d(-?\d){7,7}-?(\d|X)?$",self.s):
462 self.weight=[10,9,8,7,6,5,4,3,2]
465 raise BooklandError, "%s invalid (hyphenation, characters, or length)" % self.s
466 self.x = self.checkDigit()
467 if len(self.n) == self.ndigits:
468 self.verifyCheckDigit() # if check digit given, verify it
469 elif len(self.n) == self.ndigits-1:
470 self.tackonCheckDigit() # tack on check digit
472 raise BooklandError, "%s failed. Please report as bug" % self.s
475 def checkDigit(self): # ISBN and ISMN; UPCA/EAN13 similar but for weights etc.
476 # now that we're checking regex's in init, we don't have to check the
477 # argument at all. (used to check length and bad characters)
479 for i in range(9): # checksum based on first nine digits.
480 sum = sum + int(self.n[i]) * self.weight[i]
481 z = ( self.magic - (sum % self.magic) ) % self.magic
482 if z < 0 or z >= self.magic:
483 raise BooklandError, \
484 "%s: checksum %d is wrong - please report as bug" % (self.s,z)
485 return self.xstring(z)
493 def tackonCheckDigit(self):
494 if self.s[-1:] == "-":
495 # Already have a trailing hyphen
496 self.s = self.s + self.x
498 self.s = self.s + "-" + self.x
500 def verifyCheckDigit(self): # UPC A; EAN13
501 # first verify correct number of digits.
504 if ist != soll: raise BooklandError, \
505 "For %s checksum %s is wrong, should be %s\n" % (self.s,ist,soll)
508 # a run of adjacent modules of identical value.
509 def __init__(self,val):
511 if not self.val in "L01":
512 raise BooklandError, "bar bit: %s, pls report as a bug" % self.val
519 self.color="Long Black"
520 def __cmp__(self,other):
521 if self.val==other or (self.val=="L" and other=="1"):
526 self.width=self.width+1
529 rval = [ "%d L " % self.width ]
531 rval = [ "%d B " % self.width ]
533 rval = [ "%d W " % self.width ]
536 return "%s bar of width %d" % (self.color,self.width)
541 self.patternWidth = len(self.bits)*self.moduleWidth
544 def bitsComment(self):
545 return [ "%% Bits:\n%% %s" % self.bits ]
548 # new version, try to prevent all hairlines between adjacent modules.
550 bar=Bar(self.bits[0])
551 for bit in self.bits[1:]:
558 rval = ["0 0 moveto"]
560 rval = rval + bar.pslines()
561 rval = rval + [ "stroke" ]
565 psbits=regsub.gsub("1","I ",self.bits)
566 psbits=regsub.gsub("0","O ",psbits)
567 psbits=regsub.gsub("L","L ",psbits)
569 p=0; j=linewidth; m=len(psbits); psbarlines=[]; blanks="^ | $"
571 j = min(linewidth,m-p)
572 psbarlines = psbarlines + [ regsub.gsub(blanks,"",psbits[p:p+j]) ]
574 return [ "0 0 moveto" ] + psbarlines + [ "stroke" ]
576 def psSetBarHeight(self):
577 return [ "/barHeight { " + PSFORMAT % self.moduleHeight + " 72 mul } def" ]
579 def psSetBarWidthReduction(self):
580 return [ "/barWidthReduction { " + \
581 PSFORMAT % self.barWidthReduction + " 72 mul } def" ]
583 def psSetModuleWidth(self):
584 rval = [ "/moduleWidth { " + PSFORMAT % self.moduleWidth + " 72 mul } def" ]
587 def psBottomRightText(self,text,font):
588 # this is specifically for the upc5 price code.
589 # this is all starting to get messy.
590 return [ PSFORMAT % self.patternWidth + " 72 mul dup 2 div",
591 PSFORMAT % self.moduleHeight + " 72 mul 2 add moveto",
592 "(" + text + ") /" + font + " fitstring bottomcentershow" ]
594 def psTopCenterText(self,text,font):
595 # the text at the top center of the bar pattern (i.e. the ISBN)
596 return [ PSFORMAT % self.patternWidth + " 72 mul dup 2 div",
597 PSFORMAT % self.moduleHeight + " 72 mul 3 add moveto",
598 " (" + text + ") /" + font + " fitstring bottomcentershow" ]
600 def psFittedText(self,width,text,font):
601 return [ PSFORMAT % width + " (" + text + ") " + font + " fitstring" ]
603 # This is optional; serves to enforce quiet zone to right of UPC 5 add-on
604 def psGreaterThan(self,font):
605 return [ PSFORMAT % self.patternWidth + " 72 mul",
606 PSFORMAT % self.moduleHeight + " 72 mul 2 add moveto",
607 "/%s (>) show" % font ]
609 class EAN13Symbol(barCodeSymbol):
611 def __init__(self,arg,font=EAN13FONT,heightMultiplier=1,barWidthReduction=0):
612 # arg is a string with the EAN product code
613 self.barWidthReduction=barWidthReduction
614 self.ean13 = EAN13(arg)
615 self.moduleWidth = 0.0130
616 specModuleHeight = 1.00
617 self.moduleHeight = 1.00 * heightMultiplier
618 self.bits = self.ean13.bits
619 barCodeSymbol.__init__(self)
623 return [ -12, -10, self.patternWidth*72+10, self.moduleHeight*72+12 ]
626 return self.bitsComment() + \
627 self.psSetModuleWidth() + \
628 self.psSetBarWidthReduction() + \
629 self.psSetBarHeight() + \
631 self.psLRDigitLines()
633 def psLRDigitLines(self):
635 # 70 = 3+6*7+4+6*7/2 "4" so we center on the "L" bars (the rightmost of
636 # the center guard bars is an "O".
637 # "5" in check digit is the five-module spacing recommended by [2], section 3.
638 return [ "% We do the left digits first and leave the font scaled",
639 "% as is for the 9-digit and the right-digits.",
640 "% EAN13 Left Digits:",
641 "moduleWidth 24 mul 0 moveto",
642 "moduleWidth 40 mul (" + self.ean13.leftDigits + ") ",
643 "/" + self.font + " fitstring topcentershow",
644 "\n% EAN13 human-readable number",
645 "% The \"9\" digit (only when encoding ISBN's and ISMN's, I think):",
646 "-5 0 moveto (" + self.ean13.n[0] + ") toprightshow",
647 "% EAN13 Right Digits:",
648 "moduleWidth 70 mul 0 moveto",
649 "moduleWidth 40 mul (" + self.ean13.rightDigits + ") topcentershow" ]
653 def __init__(self,arg):
654 self.ndigits=13 # Includes check digit!
655 UPCA.__init__(self,arg)
656 leftBits = self.bits[0:42]
657 rightBits = self.bits[42:]
661 self.bits = leftGuard + leftBits + center + rightBits + rightGuard
662 self.leftDigits = self.n[1:7]
663 self.rightDigits = self.n[7:13]
665 def parityPattern(self):
666 # N.B. parity pattern based on leftmost digit, the UCC Spec calls this
667 # the "13th" digit. It's not the check digit!
668 return EAN13PARITY[int(self.n[0])]
672 class UPC5Symbol(barCodeSymbol):
674 def __init__(self,arg,heightMultiplier=1,barWidthReduction=0):
675 # arg is a string with the 5 digit add-on.
676 self.barWidthReduction=barWidthReduction
677 self.upc5 = UPC5(arg)
678 self.moduleWidth = 0.0130
679 specModuleHeight = 0.852
680 self.moduleHeight = 0.852 * heightMultiplier
681 self.bits = self.upc5.bits
682 barCodeSymbol.__init__(self)
685 return self.bitsComment() + \
686 self.psSetModuleWidth() + \
687 self.psSetBarHeight() + \
691 # Note quiet zone is there even if we don't print the ">".
692 return [ 0, 0, self.patternWidth*72+10, self.moduleHeight*72+10 ]
694 UPC5Error = "Something wrong with 5-digit price code add-on."
697 def __init__(self,arg):
698 self.ndigits=5 # Includes check digit!
699 p=re.search("[^0-9]",arg)
701 badchar=arg[p.start()]
702 raise UPC5Error, "\"%s\" is wrong. The character \"%s\" is not allowed. Price code add-on should contain %d digits and nothing else. Or leave blank to suppress the UPC-5 code." % (arg,badchar,self.ndigits)
703 elif len(arg) != self.ndigits:
705 "\"%s\" is wrong. Price code add-on should have exactly %d digits." % (arg,self.ndigits)
706 UPC.__init__(self,arg)
709 # no right guard for UPC 5-digit add-on
710 # Have to insert pesky delineators:
712 self.bits = leftGuard + \
713 self.bits[0:7] + delineator + \
714 self.bits[7:14] + delineator + \
715 self.bits[14:21] + delineator + \
716 self.bits[21:28] + delineator + \
719 def checkDigit(self,arg): # UPC5
720 weight=[3,9,3,9,3]; sum = 0
722 sum = sum + int(arg[i]) * weight[i]
723 return self.xstring(sum % 10)
725 def verifyCheckDigit(self): # UPC2/5 checksum not in number
728 def parityPattern(self):
729 return UPC5PARITY[int(self.x)]
733 class bookland(barCodeSymbol):
735 def __init__(self,isbn,price="",*rest):
747 # Maybe different fonts:
757 heightMultiplier=float(rest[2])
761 barWidthReduction=float(rest[4])
766 self.isbn = ISBN(isbn)
768 # Header, EAN13 bars, EAN13 number, and ISBN:
770 if self.isbn.name=="ISMN":
771 self.ean13Symbol = EAN13Symbol("9790"+self.isbn.n[1:9],ean13font,heightMultiplier,barWidthReduction)
772 elif self.isbn.name=="ISBN":
773 self.ean13Symbol = EAN13Symbol("978"+self.isbn.n[:9],ean13font,heightMultiplier,barWidthReduction)
775 raise BooklandError, "Internal error doing %s, please report as bug" % isbn
777 self.ps.orbb(self.ean13Symbol.bb())
779 " This is free software and comes with NO WARRANTY WHATSOVER",
780 " Think twice before going to press with this bar code!",
782 "Command line: %s" % commandLine,
784 self.ps.lines = self.ps.header(self.isbn.s,comments,ean13font,isbnfont,upc5font) + \
786 self.ean13Symbol.pslines() +\
788 self.ean13Symbol.psTopCenterText("%s %s" % (self.isbn.name,self.isbn.s),isbnfont)
790 # 5-digit add-on: (optional for ISBN only)
791 BLANK=re.compile("^ *$")
792 if self.isbn.name=="ISBN" and not BLANK.match(price):
793 # 105 = 95 + 10; 10 = separation (min is 9)
794 translate=[ self.ps.translate( self.ean13Symbol.moduleWidth * 72 * 105, 0 ) ]
795 self.upc5Symbol = UPC5Symbol(price,heightMultiplier,barWidthReduction)
796 self.ps.orbb(self.upc5Symbol.bb())
797 self.ps.lines = self.ps.lines + \
799 self.upc5Symbol.pslines() + \
801 self.upc5Symbol.psBottomRightText(price,upc5font)
802 if zone: self.ps.lines=self.ps.lines + self.upc5Symbol.psGreaterThan(upc5font)
804 self.ps.lines.append("%% Skipping UPC-5 price code symbol per request")
806 self.ps.lines=self.ps.lines + self.ps.trailer()
808 # Can now set bounding box.
814 if __name__ == '__main__':
817 print "Usage: bookland [-h|--help] [-v|--version] [-x|--check] [-f|--font=<font>] [-s|--height=<height scale>] [-r|--reduction=<reduction factor>] [-o|outfile=<filename>] [-z|--quietzone] [<isbn>|<isbn> <price>]"
818 print "Report bugs to " + MAINTAINER
821 sys.stderr.write("%s version %s %s.\n" % (MYNAME,MYVERSION,COPYRIGHT))
822 sys.stderr.write("Bugs to %s\n" % MAINTAINER)
823 sys.stderr.write("This is free software and comes with NO WARRANTY\n")
827 opts,args = getopt.getopt(sys.argv[1:],
829 ["reduction=","outfile=","height=","noupc",
830 "check","version","help","font=","quietzone"])
835 # some initial defaults:
836 isbn = "1-56592-197-6" # Mark Lutz, "Programming Python",
837 # O'Reilly, Sebastopol CA, 1996
844 commandLine = string.join(sys.argv)
845 barWidthReduction = 0
847 # parse command line:
849 if opt in ("-v","--version"):
852 elif opt in ("-h","--help"):
855 elif opt in ("-f","--font"):
857 elif opt in ("-z","--quietzone"):
859 elif opt in ("-x","--check"):
861 elif opt in ("-s","--height"):
862 heightMultiplier = float(val)
863 elif opt in ("-r","--reduction"):
864 barWidthReduction = val
865 elif opt in ("-o","--outfile"):
877 b = bookland(isbn,price,font,zone,heightMultiplier,
878 commandLine,barWidthReduction)
879 if not checkonly: b.ps.out(outfile)
881 sys.stderr.write("Output written to %s\n" % outfile)
882 except BooklandError, message:
883 sys.stderr.write(BooklandError + ": " + message + "\n")