#/usr/bin/perl  -w
# GenerateBarcode.pl
# by ntheory
#
#   Generates barcodes and the CRC digit for CoinStar receipts.
#
# June 26th, 2003 - Started development.
# June 30th, 2003 - After a few day haitus I resumed work and finished the code.
#
# Notes: Put the barcodes in a text file and redirect the file into STDIN.
#        You should only have the 12-digits of the barcode on each line and
#        nothing else. Alternatively you can enter them by hand.

use Image::Magick;

# Set up some constants that we'll need in the binary string generation phase.
$StartEndSentinel         = "101";      # This goes at the beginning and end.
$CenterGuardPattern       = "01010";    #This goes in the middle.
$StartEndSentinelLength   = length($StartEndSentinel);
$CenterGuardPatternLength = length($CenterGuardPattern);

# These are just defined for readability purposes.
$OddParity              = 0;
$EvenParity             = 1;
$ManufacturerCodeLength = 5;
$WholeLeftSideLength    = $ManufacturerCodeLength + 1;
$RequiredInputLength    = 12;
$RightSideLength        = 5;
$WholeRightSideLength   = $RightSideLength + 1;
$BarcodeHeight          = 50;
$SingleDigitLength      = 7;

# Get the parity map, the left hand coding table, and the right hand coding table.
$ParityMap       = GetParityMap();
$LeftHandCoding  = GetLeftHandCodingTable();
$RightHandCoding = GetRightHandCodingTable();

$BarcodeCounter = 0;

# We'll do this in a loop so you can generate many barcodes in a row.
while (<>) {

    # Get the unprocessed data and remove the trail ing newl i ne .
    $Unprocessed = $_;
    chomp($Unprocessed);

    # Check to make sure that it makes sense.
    if ( LooksValid($Unprocessed) ) {

        # Now convert it to a string of binary digits using some EAN-13 magic.
        # (http://www.barcodeisland.com/ean13.phtml)
        $Processed = $StartEndSentinel;

        # Get the first digit (determines the parity of the manufacturer code).
        $FirstDigit = int( substr( $Unprocessed, 0, 1 ) );

        # Encode the second digit (always odd parity).
        $Offset       = 1;
        $CurrentDigit = int( substr( $Unprocessed, $Offset, 1 ) );
        $Processed .= $LeftHandCoding[$CurrentDigit][$OddParity];

        # Next encode the manufacturer code.
        for ( $Loop = 0 ; $Loop < $ManufacturerCodeLength ; $Loop++ ) {

            # Move to the next character .
            $Offset++;

            # Get the parity.
            $CurrentParity =
              substr( $ParityMap[$FirstDigit], $Loop, 1 ) eq "0"
              ? $OddParity
              : $EvenParity;
            $CurrentDigit = int( substr( $Unprocessed, $Offset, 1 ) );
            $Processed .= $LeftHandCoding[$CurrentDigit][$CurrentParity];
        }

        # Slap the center guard pattern in there.
        $Processed .= $CenterGuardPattern;

        # Encode the right hand side.
        for ( $Loop = 0 ; $Loop < $RightSideLength ; $Loop++ ) {

            # Move to the next character.
            $Offset++;
            $CurrentDigit = int( substr( $Unprocessed, $Offset, 1 ) );
            $Processed .= $RightHandCoding[$CurrentDigit];
        }

# Finally encode the check digit and slap the end sentinel in there. We also tack
# the check digit onto the unprocessed string so we can draw it onto the image below in a loop.
        $CheckDigit = CalculateCheckDigit($Unprocessed);
        $Unprocessed .= $CheckDigit;
        $Processed   .= $RightHandCoding[$CheckDigit];
        $Processed   .= $StartEndSentinel;

        # Now generate the image
        $XOffset         = 100;
        $YOffset         = 100;
        $BarcodeLength   = length($Processed);
        $BarcodeImage    = new Image::Magick;
        $BarcodeGeometry = ( $BarcodeLength + $XOffset * 2 ) . "x"
          . ( $BarcodeHeight + $YOffset * 2 );

        $BarcodeImage->set( size => $BarcodeGeometry );
        $BarcodeImage->Read("gradient:white-white");

        # Don't ask me why I always do this.

        #Draw the barcode.
        $YMin = $YOffset;
        $YMax = $YOffset + $BarcodeHeight;

        for ( $Loop = 0 ; $Loop < $BarcodeLength ; $Loop++ ) {
            $X            = $Loop + $XOffset;
            $PointsString = "$X,$YMin,$X,$YMax";
            $StrokeColor =
              substr( $Processed, $Loop, 1 ) eq "0" ? "White" : "Black";
            $BarcodeImage->Draw(
                primitive => "Line",
                points    => "$PointsString",
                stoke     => "$StrokeColor"
            );
        }

        # Add in the digits below the barcode.
        # First clear out some space for the left digits.
        $BoxUpperLeftX = $StartEndSentinelLength + $XOffset;
        $BoxUpperLeftY = $BarcodeHeight - 10 + $YOffset;
        $BoxLowerRightX =
          $BoxUpperLeftX + $SingleDigitLength * $WholeLeftSideLength;
        $BoxLowerRightY = $BarcodeHeight + $YOffset;

        $PointsString =
          "$BoxUpperLeftX,$BoxUpperLeftY,$BoxLowerRightX,$BoxLowerRightY";
        $BarcodeImage->Draw(
            primitive => "Rectangle",
            points    => "$PointsString",
            fill      => "White",
            stroke    => "White"
        );

        # Draw the left side digits
        $Y = $YOffset + $BarcodeHeight;

        for ( $Loop = 1 ; $Loop < ( $WholeLeftSideLength + 1 ) ; $Loop++ ) {
            $X = $BoxUpperLeftX + 1 + ( ( $Loop - 1 ) * $SingleDigitLength );
            $Character = substr( $Unprocessed, $Loop, 1 );
            $BarcodeImage->Annotate(
                text => $Character,
                pointsize->11,
                antialias => "true",
                x         => $X,
                y         => $Y,
                fill      => "Black"
            );
        }

        # Then clear out some space for the right digits.
        $BoxUpperLeftX = $BoxLowerRightX + $CenterGuardPatternLength;
        $BoxLowerRightX =
          $BoxUpperLeftX + ( $WholeRightSideLength * $SingleDigitLength ) - 1;

        $PointsString =
          "$BoxUpperLeftX,$BoxUpperLeftY,$BoxLowerRightX,$BoxLowerRightY";
        $BarcodeImage->Draw(
            primitive => "Rectangle",
            points    => "$PointsString",
            fill      => "White",
            stroke    => "White"
        );

        # Draw the right side digits.
        for ( $Loop = 1 ; $Loop < ( $WholeRightSideLength + 1 ) ; $Loop++ ) {
            $X = $BoxUpperLeftX + ( ( $Loop - 1 ) * $SingleDigitlength );
            $Character =
              substr( $Unprocessed, $Loop + $WholeRightSideLength, 1 );
            $BarcodeImage->Annotate(
                text      => $Character,
                pointsize => 11,
                antialias => "true",
                x         => $X,
                y         => $Y,
                fill      => "Black"
            );
        }

        # Draw the first very first digit.
        $X         = $XOffset - 12;
        $Character = substr( $Unprocessed, 0, 1 );
        $BarcodeImage->Annotate(
            text      => $Character,
            pointsize => 11,
            antialias => "true",
            x         => $X,
            y         => $Y,
            fill      => "Black"
        );

        $BarcodeCounterString = sprintf( "%03d", $BarcodeCounter );
        $BarcodeImageName     = "CoinStar-$BarcodeCounterString.png";
        $BarcodeImage->Write($BarcodeImageName);
        $BarcodeCounter++;

        # Let the user know that something happened.
        $TransactionID = substr( $Unprocessed, 3, 4 );
        $Amount        = sprintf( "\$%3d.%02d",
            int( substr( $Unprocessed, 7,  3 ) ),
            int( substr( $Unprocessed, 10, 2 ) ) );

        print
"Barcode generation for transaction $Transaction ID ($Amount) was successful.\n";
        print
"The image was stored as $BarcodeImageName. The CoinStar check digit was $CheckDigit.\n";

    }
}

sub LooksValid {
    $Data        = $_[0];
    $ReturnValue = 0;
    if ( length($Data) != $RequiredInputlength ) {
        print
"Wrong number of characters. $RequiredlnputLength characters are needed to generate a barcode.\n";
    }
    elsif ( $Data =~ m/[^0-9]/ ) {
        print
"There was a non-numeric character in your data. Only numeric data is accepted.\n";
    }
    else {
        $ReturnValue = 1;
    }

    return $ReturnValue;
}

sub CalculateCheckDigit {
    $Data = $_[0];

    # CoinStar really exhausted their technician's with this one. Below the code
    # may look very similar to a typical EAN-13 checksum calculation but it has
    # a surprise ending.

    $Sum = 0;

    # Do the weighted sum.
    for ( $Loop = 0 ; $Loop < $RequiredlnputLength ; $Loop++ ) {
        $CurrentDigit = int( substr( $Data, $Loop, 1 ) );

     # Even digits are added to the sum normally while odd digits are multiplied
     # by three before they're added.
        if ( $Loop % 2 == 0 ) {
            $Sum += $CurrentDigit;
        }
        else {
            $Sum += ( $CurrentDigit * 3 );
        }
    }

    # Mod the sum by 10.
    $Sum = $Sum % 10;

    # Normally here we'd subtract the sum from 10 but CoinStar had to be
    # different. CoinStar has a Spinal Tap fetish ("We've got 11").
    $CheckDigit = 11 - $Sum;

    # Make sure the check digit is less than 10.
    $CheckDigit = $CheckDigit % 10;

    return $CheckDigit;
}

sub GetParityMap {

    # This table tells us how to code the manufacturer's code.
    my $ParityMap;
    $ParityMap[0] = "00000";
    $ParityMap[1] = "0E0EE";
    $ParityMap[2] = "0EE0E";
    $ParityMap[3] = "0EEE0";
    $ParityMap[4] = "E00EE";
    $ParityMap[5] = "EE00E";
    $ParityMap[6] = "EEE00";
    $ParityMap[7] = "E0E0E";
    $ParityMap[8] = "E0EE0";
    $ParityMap[9] = "EE0E0";

    return $ParityMap;
}

sub GetLeftHandCodingTable {

    # This table gives us the binary representation of the left hand digits.
    my $LeftHandCodingTable;

    $LeftHandCoding[0][$OddParity] = "0001101";
    $LeftHandCoding[1][$OddParity] = "0011001";
    $LeftHandCoding[2][$OddParity] = "0010011";
    $LeftHandCoding[3][$OddParity] = "0111101";
    $LeftHandCoding[4][$OddParity] = "0100011";
    $LeftHandCoding[5][$OddParity] = "0110001";
    $LeftHandCoding[6][$OddParity] = "0101111";
    $LeftHandCoding[7][$OddParity] = "0111011";
    $LeftHandCoding[8][$OddParity] = "0110111";
    $LeftHandCoding[9][$OddParity] = "0001011";

    $LeftHandCoding[0][$EvenParity] = "0100111";
    $LeftHandCoding[1][$EvenParity] = "0110011";
    $LeftHandCoding[2][$EvenParity] = "0011011";
    $LeftHandCoding[3][$EvenParity] = "0100001";
    $LeftHandCoding[4][$EvenParity] = "0011101";
    $LeftHandCoding[5][$EvenParity] = "0111001";
    $LeftHandCoding[6][$EvenParity] = "0000101";
    $LeftHandCoding[7][$EvenParity] = "0010001";
    $LeftHandCoding[8][$EvenParity] = "0001001";
    $LeftHandCoding[9][$EvenParity] = "0010111";

    return $LeftHandCoding;
}

sub GetRightHandCodingTable {

    # This table gives us the binary representation of the right hand digits.
    my $RightHandCoding;

    $RightHandCoding[0] = "1110010";
    $RightHandCoding[1] = "1100110";
    $RightHandCoding[2] = "1101100";
    $RightHandCoding[3] = "1000010";
    $RightHandCoding[4] = "1011100";
    $RightHandCoding[5] = "1001110";
    $RightHandCoding[6] = "1010000";
    $RightHandCoding[7] = "1000100";
    $RightHandCoding[8] = "1001000";
    $RightHandCoding[9] = "1110100";

    return $RightHandCoding;
}


syntax highlighted by Code2HTML, v. 0.9.1