#!/usr/bin/python
#
#   scan13 - utility to read bar codes from EPS files.
#
#   Should be able to handle all EAN-13 variants including ISBN with UPC5
#   price code.
#
#   usage:
#   scan13 <filename.eps>
#
#   Copyright (C) 2007 Judah Milgram     
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation; either version 2
#   of the License, or (at your option) any later version.
#
#   Because this program copies a portion of itself into its output
#   file, its output files are also copyright the author and licensed
#   under the GPL.  Relevant provisions of the GPL notwithstanding,
#   the author licenses users to use and redistribute output files
#   generated by this program without restriction.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#     
#   You should have received a copy of the GNU General Public License along
#   with this program; if not, write to the Free Software Foundation, Inc.,
#   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#


import getopt
import os
import Image
import sys
import productcode
import re
import tempfile


YSIZE=2400
STRIPCOMMAND = "pstopnm -pbm -stdout -portrait -ysize %d -xborder 0 -yborder 0 %%s 2> /dev/null | pnmcut -top %d -bottom %d" % (YSIZE,YSIZE/2,YSIZE/2)

EPSFILE = re.compile(".*\.eps",re.I)

def eps2pixels(f):
    if not EPSFILE.match(f):
        raise Exception
    tempfil = tempfile.mktemp()
    s = os.popen("%s > %s" % (STRIPCOMMAND%f,tempfil)).readline()
    z = Image.open(tempfil)
    w,h = z.size
    pix = z.load()
    pixels = []
    for i in range(w):
        pixels.append(pix[i,0])
    os.unlink(tempfil)
    return pixels
    
def pixels2bars(pixels):
    bars = []
    p = 1
    for i in range(1,len(pixels)):
        if pixels[i]!= pixels[i-1]:
            bars.append(p)
            p = 1
        else:
            p+=1
    bars.append(p)
    # bars are in pixels
    # reduce to module widths
    i,a = ean13index(bars)
    bars = bars[i:]
    for j in range(len(bars)):
        bars[j] = int(round(bars[j]/float(a)))
    ean13bars = bars
    i,a = upc5index(bars)
    upc5bars = bars[i:]
    return (ean13bars,upc5bars)

def upc5index(bars):
    # Looking for 3 bars in ean13 right guard
    # then long space
    # then first two bars in upc5 left guard
    for i in range(len(bars)):
        candidates = bars[i:i+5]
        a = min(candidates[:3] + candidates[4:])
        b = max(candidates[:3] + candidates[4:])
        c = candidates[3]
        err = 0.20
        nlong=8
        # sorry for the hard-coded magic number (8) ...
        if abs(1-float(a)/b) < err and c > nlong*a:        
            return i+4,a
    return None
            
                
def ean13index(bars):
    # index of EAN13 guard bars and module size
    for i in range(len(bars)):
        candidates = bars[i:i+3]
        a = min(candidates)
        b = max(candidates)
        err = 0.20
        if abs(1-float(a)/b) < err:
            return i,a
    return None

def bars2bits(bars):
    val = 1
    bits=[]
    for bar in bars:
        bits.extend(bar*[val])
        # flip val:
        val = val ^ 1
    return bits

def upc5digit(bits):
    bits = "".join(map(str,bits))
    for i in range(len(productcode.UPC5BITS)):
        dict = productcode.UPC5BITS[i]
        keys = dict.keys()
        for key in keys:
            if dict[key]==bits:
                return i,key
    return None,None
            
def ean13digit(bits):
    bits = "".join(map(str,bits))
    for i in range(len(productcode.EAN13BITS)):
        dict = productcode.EAN13BITS[i]
        keys = dict.keys()
        for key in keys:
            if dict[key]==bits:
                return i,key


if __name__=="__main__":

    MYNAME="scan13"
    MYVERSION="0.1"
    COPYRIGHT="(C) 2007 J. Milgram"
    VERSIONDATE = "Feb 2007"
    MAINTAINER = "bookland-bugs@cgpp.com"
    WARNING = "This is free software and comes with NO WARRANTY"

    sys.stderr.write("%s\n" % WARNING)    

    filnam = sys.argv[1]

    pixels = eps2pixels(filnam)
    ean13bars,upc5bars = pixels2bars(pixels)
    ean13bits = bars2bits(ean13bars)
    upc5bits = bars2bits(upc5bars)

    NLEFT=3
    NCENTER=5
    ean13digits=[]
    ean13pattern=[]
    for i in range(6):
        i0 = i*7 + NLEFT
        i1 = i0+7
        digit,parity = ean13digit(ean13bits[i0:i1])
        ean13digits.append(digit)
        ean13pattern.append(parity)
    for i in range(6,12):
        i0 = i*7 + NLEFT + NCENTER
        i1 = (i+1)*7 + NLEFT + NCENTER
        digit,parity=ean13digit(ean13bits[i0:i1])
        ean13digits.append(digit)
        ean13pattern.append(parity)
    ean13pattern = "".join(ean13pattern)
    checkDigit = productcode.EAN13PARITY.index(ean13pattern)
    ean13digits.insert(0,checkDigit)
    ean13 = "".join(map(str,ean13digits))

    NLEFT = 4
    upc5digits=[]
    upc5pattern=[]
    for i in range(5):
        # 9 = 7 bits + 2 "delineators"
        i0=i*9 + NLEFT
        i1=i0+9
        digit,parity = upc5digit(upc5bits[i0:i0+7])
        upc5digits.append(digit)
        upc5pattern.append(parity)
    upc5 = "".join(map(str,upc5digits))


    print ean13,upc5

