]> git.sur5r.net Git - glabels/blob - barcode-0.98/bookland/bookland.py
Imported Debian patch 2.2.8-3
[glabels] / barcode-0.98 / bookland / bookland.py
1 #!/usr/local/bin/python
2
3 MYNAME="bookland.py"
4 MYVERSION="0.92"
5 COPYRIGHT="(C) 1999-2001 J. Milgram"
6 DATE = "Jan. 2002"
7 MAINTAINER = "bookland-bugs@cgpp.com"
8
9 #   Copyright (C) 1999,2000 Judah Milgram     
10 #
11 #   bookland.py - generate Bookland EAN symbol for ISBN encoding
12 #
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.
17 #
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.
22 #     
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.
26 #
27 # ==============================================================================
28 #
29 #  usage: bookland.py [ISBN] [price-code] > output.eps
30 #
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.
36 #
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.
46 #
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.
56 #
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.
62 #
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
66 #
67 #  INSTALLATION:
68 #
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.
72 #
73 #  ABOUT THE BOOKLAND EAN
74 #
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:
78 #
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
88 #
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.
95 #
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.
101 #
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
107 #  UPC-E.
108 #
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.
113 #
114 #  SEE ALSO:
115 #
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.
120 #
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!
126 #
127 #  HYPHENATION
128 #
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
134 #  the check digit.
135 #
136 #  ABOUT THE ISMN:
137 #
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
141 #
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).
150 #
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.
153 #
154 #  BAR WIDTH REDUCTIONS
155 #
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).
160 #
161 #  ABOUT PYTHON
162 #
163 #  See http://www.python.org
164 #
165 #  TO DO:
166 #
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
169 #    Bookland.
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.
174 #  - GUI?
175 #
176 #  HISTORY:
177 #
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 # ================================================================================
206
207 #
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,
210 #                         and barcode bits
211 #
212
213 import re       # we should get rid of regsub and regex in favor of re
214                 # hope there's no conflict.
215 import string
216 import sys
217 import regsub
218 from regex_syntax import *
219 import regex
220 regex.set_syntax(RE_SYNTAX_AWK)
221 from types import *
222
223 BooklandError = "Something wrong"
224
225
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.
249 UPC5BITS = UPCEBITS
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"])
265
266 PSFORMAT = "%.6f"
267 # Default fonts.
268 # Fonts might have a different name on your system.
269 # Edit if required.
270 ISBNFONT = "OCRB"      #  Doesn't have to be OCR-B
271 EAN13FONT = "OCRB"
272 UPC5FONT = "OCRB"
273
274 class psfile:
275     
276     def __init__(self):
277         self.x0 = 100; self.y0 = 100
278         self.lines=[]
279         self.bb=[self.x0,self.y0,self.x0,self.y0]
280
281     def orbb(self,arg):
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])
286
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)
291         
292     def out(self,file=None):
293         if file:
294             outfid=open(file,"w")
295         else:
296             outfid=sys.stdout
297         for line in self.lines:
298             outfid.write("%s\n"%line)
299         outfid.close()
300
301     def do(self,arg):
302         self.lines = self.lines + arg
303
304     def setbb(self):
305         for i in range(len(self.lines)):
306             if self.lines[i]=="%%BoundingBox: TBD":
307                 self.lines[i]= "%%BoundingBox:" + \
308                                " %d"%self.bb[0] + \
309                                " %d"%self.bb[1] + \
310                                " %d"%self.bb[2] + \
311                                " %d"%self.bb[3]
312                 return
313
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,
320                  "%%Title: " + title,
321                  "%%BoundingBox: TBD",
322                  "%%EndComments" ] +\
323                  comments + \
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",
336                  "3 1 roll % f w s",
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",
340                  "} def",
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",
360                  "0 0 moveto" ]
361
362     def trailer(self):
363         return ["stroke","% showpage supposedly OK in EPS",
364                 "showpage","\n% Good luck!"]
365
366         
367 class UPC:
368     
369     # Includes UPC-A, UPC-E, EAN-13 (sorry), UPC-5 et al.
370
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
376         #   trailing hyphen.
377         # If checksum is included, it will be verified.        
378         # N.B. "integer" representation is still a string! Just has no hyphens.
379
380         self.s=arg
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
388         else:
389             raise BooklandError, "UPC: wrong number of digits in \"" + self.s + "\""
390
391     def setbits(self,arg):                       # UPC (all)
392         self.bits=""
393         parityPattern=self.parityPattern()
394         bitchar=self.bitchar()
395         for p in range(len(arg)):
396             digit=int(arg[p])
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
401
402     def verifyChars(self,s):                     # UPC (all)
403         # Trailing hyphen allowed.
404         nevergood = "--|^-|[^0-9-]"
405         ierr=regex.search(nevergood,s)
406         if ierr != -1:
407             raise BooklandError, \
408                   "UPCA: in %s: illegal characters beginning with: %s" % (s,s[ierr])
409
410     def verifyCheckDigit(self):               # UPC (all)
411         # first verify correct number of digits.
412         soll=self.checkDigit(self.n)
413         ist=self.s[-1:]
414         if ist != soll:
415             raise BooklandError, "For %s checksum %s is wrong, should be %s" % \
416                              (self.s,ist,soll)
417
418     def xstring(self,p):                      # UPC (all)
419         return "%d" % p
420
421     def tackonCheckDigit(self):
422         self.n = self.n + self.x              # UPC (all)
423         self.s = self.s + self.x
424
425 class UPCA(UPC):
426
427     def __init__(self,arg):
428         UPC.__init__(self,arg)
429         self.setbits(self.n[1:])                 # skip first digit
430         
431     def parityPattern(self):
432         return UPCAPARITY[int(self.x)]
433     def bitchar(self):
434         return UPCABITS
435     
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)
444
445
446 class ISBN:
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):
454             # ISMN
455             self.name="ISMN"
456             self.n=re.sub("^M","3",self.n)
457             self.weight=[3,1,3,1,3,1,3,1,3]
458             self.magic=10
459         elif re.match("^\d-?\d(-?\d){7,7}-?(\d|X)?$",self.s):
460             # ISBN
461             self.name="ISBN"
462             self.weight=[10,9,8,7,6,5,4,3,2]
463             self.magic=11
464         else:
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
471         else:
472             raise BooklandError, "%s failed. Please report as bug" % self.s
473
474
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)
478         sum = 0
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)
486
487     def xstring(self,p):
488         if p == 10:
489             return "X"
490         else:
491             return "%d" % p
492
493     def tackonCheckDigit(self):
494         if self.s[-1:] == "-":
495             # Already have a trailing hyphen
496             self.s = self.s + self.x
497         else:
498             self.s = self.s + "-" + self.x
499
500     def verifyCheckDigit(self):               # UPC A; EAN13
501         # first verify correct number of digits.
502         soll=self.x
503         ist=self.s[-1:]
504         if ist != soll: raise BooklandError, \
505                   "For %s checksum %s is wrong, should be %s\n" % (self.s,ist,soll)
506
507 class Bar:
508     # a run of adjacent modules of identical value.
509     def __init__(self,val):
510         self.val=val
511         if not self.val in "L01":
512             raise BooklandError, "bar bit: %s, pls report as a bug" % self.val
513         self.width=1
514         if self.val=="1":
515             self.color="Black"
516         elif self.val=="0":
517             self.color="White"
518         elif self.val=="L":
519             self.color="Long Black"
520     def __cmp__(self,other):
521         if self.val==other or (self.val=="L" and other=="1"):
522             return 0
523         else:
524             return 1
525     def inc(self):
526         self.width=self.width+1
527     def pslines(self):
528         if self.val=="L":
529             rval = [ "%d L " % self.width ]
530         elif self.val=="1":
531             rval = [ "%d B " % self.width ]
532         else:
533             rval = [ "%d W " % self.width ]
534         return rval
535     def __repr__(self):
536         return "%s bar of width %d" % (self.color,self.width)
537
538 class barCodeSymbol:
539
540     def __init__(self):
541         self.patternWidth = len(self.bits)*self.moduleWidth
542         # Anything else?
543
544     def bitsComment(self):
545         return [ "%% Bits:\n%% %s" % self.bits ]
546         
547     def psbars(self):
548         # new version, try to prevent all hairlines between adjacent modules.
549         bars = []
550         bar=Bar(self.bits[0])
551         for bit in self.bits[1:]:
552             if bit==bar:
553                 bar.inc()
554             else:
555                 bars.append(bar)
556                 bar=Bar(bit)
557         bars.append(bar)
558         rval = ["0 0 moveto"]
559         for bar in bars:
560             rval = rval + bar.pslines()
561         rval = rval + [ "stroke" ]
562         return rval
563
564     def psbarsold(self):
565         psbits=regsub.gsub("1","I ",self.bits)
566         psbits=regsub.gsub("0","O ",psbits)
567         psbits=regsub.gsub("L","L ",psbits)
568         linewidth=50
569         p=0; j=linewidth; m=len(psbits); psbarlines=[]; blanks="^ | $"
570         while p <= m:
571             j = min(linewidth,m-p)
572             psbarlines = psbarlines + [ regsub.gsub(blanks,"",psbits[p:p+j]) ]
573             p=p+linewidth
574         return [ "0 0 moveto" ] + psbarlines + [ "stroke" ]
575         
576     def psSetBarHeight(self):
577         return [ "/barHeight { " + PSFORMAT % self.moduleHeight + " 72 mul } def" ]
578
579     def psSetBarWidthReduction(self):
580         return [ "/barWidthReduction { " + \
581                  PSFORMAT % self.barWidthReduction + " 72 mul } def" ]
582
583     def psSetModuleWidth(self):
584         rval = [ "/moduleWidth { " + PSFORMAT % self.moduleWidth + " 72 mul } def" ]
585         return rval
586
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" ]    
593
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" ]
599
600     def psFittedText(self,width,text,font):
601         return [ PSFORMAT % width + " (" + text + ") " + font + " fitstring" ]
602
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 ]
608
609 class EAN13Symbol(barCodeSymbol):
610
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)
620         self.font=font
621
622     def bb(self):
623         return  [ -12, -10, self.patternWidth*72+10, self.moduleHeight*72+12 ]
624
625     def pslines(self):
626         return self.bitsComment() + \
627                self.psSetModuleWidth() + \
628                self.psSetBarWidthReduction() + \
629                self.psSetBarHeight() + \
630                self.psbars() + \
631                self.psLRDigitLines()
632               
633     def psLRDigitLines(self):
634         # 24 = 3+6*7/2
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" ]
650
651 class EAN13(UPCA):
652
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:]
658         leftGuard="L0L"
659         rightGuard="L0L"
660         center="0L0L0"
661         self.bits = leftGuard + leftBits + center + rightBits + rightGuard
662         self.leftDigits = self.n[1:7]
663         self.rightDigits = self.n[7:13]
664
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])]
669     def bitchar(self):
670         return EAN13BITS
671
672 class UPC5Symbol(barCodeSymbol):
673
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)
683
684     def pslines(self):
685         return self.bitsComment() + \
686                self.psSetModuleWidth() + \
687                self.psSetBarHeight() + \
688                self.psbars()
689
690     def bb(self):
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 ]
693
694 UPC5Error = "Something wrong with 5-digit price code add-on."
695 class UPC5(UPC):
696
697     def __init__(self,arg):
698         self.ndigits=5            #  Includes check digit!
699         p=re.search("[^0-9]",arg)
700         if p:
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:
704           raise UPC5Error, \
705             "\"%s\" is wrong. Price code add-on should have exactly %d digits." % (arg,self.ndigits)
706         UPC.__init__(self,arg)
707         self.setbits(self.n)
708         leftGuard="1011"
709         # no right guard for UPC 5-digit add-on
710         # Have to insert pesky delineators:
711         delineator = "01"
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 + \
717                     self.bits[28:35]
718
719     def checkDigit(self,arg):     # UPC5
720           weight=[3,9,3,9,3]; sum = 0
721           for i in range(5):
722               sum = sum + int(arg[i]) * weight[i]
723           return self.xstring(sum % 10)
724
725     def verifyCheckDigit(self):               # UPC2/5 checksum not in number
726         return
727
728     def parityPattern(self):
729         return UPC5PARITY[int(self.x)]
730     def bitchar(self):
731         return UPC5BITS
732
733 class bookland(barCodeSymbol):
734
735     def __init__(self,isbn,price="",*rest):
736
737
738         # Some defaults:
739         ean13font=EAN13FONT
740         isbnfont=ISBNFONT
741         upc5font = UPC5FONT
742         zone=None
743         heightMultiplier=1.0
744         commandLine=""
745         barWidthReduction=0
746
747         # Maybe different fonts:
748         if len(rest)>0:
749             font=rest[0]
750             if font:
751                 ean13font=font
752                 isbnfont=font
753                 upc5font=font
754         if len(rest)>1:
755             zone=rest[1]
756         if len(rest)>2:
757             heightMultiplier=float(rest[2])
758         if len(rest) > 3:
759             commandLine=rest[3]
760         if len(rest) > 4:
761             barWidthReduction=float(rest[4])
762
763         # Initial setup:
764         
765         self.ps = psfile()
766         self.isbn = ISBN(isbn)
767
768         # Header, EAN13 bars, EAN13 number, and ISBN:
769
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)
774         else:
775             raise BooklandError, "Internal error doing %s, please report as bug" % isbn
776
777         self.ps.orbb(self.ean13Symbol.bb())
778         comments = ["",
779                     "     This is free software and comes with NO WARRANTY WHATSOVER",
780                     "     Think twice before going to press with this bar code!",
781                     "",
782                     "Command line: %s" % commandLine,
783                     "" ]
784         self.ps.lines = self.ps.header(self.isbn.s,comments,ean13font,isbnfont,upc5font) + \
785                         [ "ean13font" ] + \
786                         self.ean13Symbol.pslines() +\
787                         [ "isbnfont" ] + \
788                         self.ean13Symbol.psTopCenterText("%s %s" % (self.isbn.name,self.isbn.s),isbnfont)
789
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 + \
798                             translate + \
799                             self.upc5Symbol.pslines() + \
800                             [ "upc5font" ] +\
801                             self.upc5Symbol.psBottomRightText(price,upc5font)
802             if zone: self.ps.lines=self.ps.lines + self.upc5Symbol.psGreaterThan(upc5font)
803         else:
804             self.ps.lines.append("%% Skipping UPC-5 price code symbol per request")
805
806         self.ps.lines=self.ps.lines + self.ps.trailer()
807
808         # Can now set bounding box.
809
810         self.ps.setbb()
811     
812 # Here we go ...
813         
814 if __name__ == '__main__':
815
816     def printUsage():
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
819
820     def printVersion():
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")
824
825     import getopt
826     try:
827         opts,args = getopt.getopt(sys.argv[1:],
828                                   "xr:s:uvf:hzo:",
829                                   ["reduction=","outfile=","height=","noupc",
830                                    "check","version","help","font=","quietzone"])
831     except:
832         printUsage()
833         sys.exit(0)
834
835     # some initial defaults:
836     isbn = "1-56592-197-6" # Mark Lutz, "Programming Python",
837                            # O'Reilly, Sebastopol CA, 1996
838     price = "90000"
839     font=None
840     zone=None
841     checkonly=None
842     outfile=None
843     heightMultiplier=1
844     commandLine = string.join(sys.argv)
845     barWidthReduction = 0
846
847     # parse command line:
848     for opt,val in opts:
849         if opt in ("-v","--version"):
850             printVersion()
851             sys.exit(0)
852         elif opt in ("-h","--help"):
853             printUsage()
854             sys.exit(0)
855         elif opt in ("-f","--font"):
856             font=val
857         elif opt in ("-z","--quietzone"):
858             zone=1
859         elif opt in ("-x","--check"):
860             checkonly=1
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"):
866             outfile=val
867     if len(args)==1:
868         isbn=args[0]
869     elif len(args)==2:
870         isbn=args[0]
871         price=args[1]
872
873     # Do stuff.
874
875     printVersion()
876     try:
877         b = bookland(isbn,price,font,zone,heightMultiplier,
878                      commandLine,barWidthReduction)
879         if not checkonly: b.ps.out(outfile)
880         if outfile:
881             sys.stderr.write("Output written to %s\n" % outfile)
882     except BooklandError, message:
883         sys.stderr.write(BooklandError + ": " + message + "\n")
884         sys.exit(1)