OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Copyright 2014 Robin Stuart
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package uk.org.okapibarcode.backend;

import static uk.org.okapibarcode.util.Arrays.positionOf;

import java.awt.geom.Rectangle2D;

/**
 * Implements the <a href=
 * "http://auspost.com.au/media/documents/a-guide-to-printing-the-4state-barcode-v31-mar2012.pdf">Australia
 * Post 4-State barcode</a>.
 *
 * @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
 */
public class AustraliaPost extends Symbol {

    private static final char[] CHARACTER_SET = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ', '#' };

    private static final String[] N_ENCODING_TABLE = { "00", "01", "02", "10", "11", "12", "20", "21", "22", "30" };

    private static final String[] C_ENCODING_TABLE = { "222", "300", "301", "302", "310", "311", "312", "320", "321", "322", "000", "001", "002", "010", "011", "012", "020", "021", "022", "100",
            "101", "102", "110", "111", "112", "120", "121", "122", "200", "201", "202", "210", "211", "212", "220", "221", "023", "030", "031", "032", "033", "103", "113", "123", "130", "131", "132",
            "133", "203", "213", "223", "230", "231", "232", "233", "303", "313", "323", "330", "331", "332", "333", "003", "013" };

    private static final String[] BAR_VALUE_TABLE = { "000", "001", "002", "003", "010", "011", "012", "013", "020", "021", "022", "023", "030", "031", "032", "033", "100", "101", "102", "103", "110",
            "111", "112", "113", "120", "121", "122", "123", "130", "131", "132", "133", "200", "201", "202", "203", "210", "211", "212", "213", "220", "221", "222", "223", "230", "231", "232", "233",
            "300", "301", "302", "303", "310", "311", "312", "313", "320", "321", "322", "323", "330", "331", "332", "333" };

    private enum ausMode {
        AUSPOST, AUSREPLY, AUSROUTE, AUSREDIRECT
    }

    private ausMode mode;

    public AustraliaPost() {
        this.mode = ausMode.AUSPOST;
    }

    /**
     * Specify encoding of Australia Post Standard Customer Barcode, Customer Barcode 2 or Customer
     * Barcode 3 (37-bar, 52-bar and 67-bar symbols) depending on input data length. Valid data
     * characters are 0-9, A-Z, a-z, space and hash (#). A Format Control Code (FCC) is added and
     * should not be included in the input data.
     * <p>
     * Input data should include a 8-digit Deliver Point ID (DPID) optionally followed by customer
     * information as shown below.
     * <table summary="Permitted Australia Post input data">
     * <tbody>
     * <tr>
     * <th>
     * <p>
     * Input Length
     * </p>
     * </th>
     * <th>
     * <p>
     * Required Input Format
     * </p>
     * </th>
     * <th>
     * <p>
     * Symbol Length
     * </p>
     * </th>
     * <th>
     * <p>
     * FCC
     * </p>
     * </th>
     * <th>
     * <p>
     * Encoding Table
     * </p>
     * </th>
     * </tr>
     * <tr>
     * <td>
     * <p>
     * 8
     * </p>
     * </td>
     * <td>
     * <p>
     * 99999999
     * </p>
     * </td>
     * <td>
     * <p>
     * 37-bar
     * </p>
     * </td>
     * <td>
     * <p>
     * 11
     * </p>
     * </td>
     * <td>
     * <p>
     * None
     * </p>
     * </td>
     * </tr>
     * <tr>
     * <td>
     * <p>
     * 13
     * </p>
     * </td>
     * <td>
     * <p>
     * 99999999AAAAA
     * </p>
     * </td>
     * <td>
     * <p>
     * 52-bar
     * </p>
     * </td>
     * <td>
     * <p>
     * 59
     * </p>
     * </td>
     * <td>
     * <p>
     * C
     * </p>
     * </td>
     * </tr>
     * <tr>
     * <td>
     * <p>
     * 16
     * </p>
     * </td>
     * <td>
     * <p>
     * 9999999999999999
     * </p>
     * </td>
     * <td>
     * <p>
     * 52-bar
     * </p>
     * </td>
     * <td>
     * <p>
     * 59
     * </p>
     * </td>
     * <td>
     * <p>
     * N
     * </p>
     * </td>
     * </tr>
     * <tr>
     * <td>
     * <p>
     * 18
     * </p>
     * </td>
     * <td>
     * <p>
     * 99999999AAAAAAAAAA
     * </p>
     * </td>
     * <td>
     * <p>
     * 67-bar
     * </p>
     * </td>
     * <td>
     * <p>
     * 62
     * </p>
     * </td>
     * <td>
     * <p>
     * C
     * </p>
     * </td>
     * </tr>
     * <tr>
     * <td>
     * <p>
     * 23
     * </p>
     * </td>
     * <td>
     * <p>
     * 99999999999999999999999
     * </p>
     * </td>
     * <td>
     * <p>
     * 67-bar
     * </p>
     * </td>
     * <td>
     * <p>
     * 62
     * </p>
     * </td>
     * <td>
     * <p>
     * N
     * </p>
     * </td>
     * </tr>
     * </tbody>
     * </table>
     */
    public void setPostMode() {
        this.mode = ausMode.AUSPOST;
    }

    /**
     * Specify encoding of a Reply Paid version of the Australia Post 4-State Barcode (FCC 45) which
     * requires an 8-digit DPID input.
     */
    public void setReplyMode() {
        this.mode = ausMode.AUSREPLY;
    }

    /**
     * Specify encoding of a Routing version of the Australia Post 4-State Barcode (FCC 87) which
     * requires an 8-digit DPID input.
     */
    public void setRouteMode() {
        this.mode = ausMode.AUSROUTE;
    }

    /**
     * Specify encoding of a Redirection version of the Australia Post 4-State Barcode (FCC 92)
     * which requires an 8-digit DPID input.
     */
    public void setRedirectMode() {
        this.mode = ausMode.AUSREDIRECT;
    }

    /** {@inheritDoc} */
    @Override
    protected void encode() {
        String formatControlCode = "00";
        String deliveryPointId;
        String barStateValues;
        String zeroPaddedInput = "";
        int i;

        switch (this.mode) {
        case AUSPOST:
            switch (this.content.length()) {
            case 8:
                formatControlCode = "11";
                break;
            case 13:
                formatControlCode = "59";
                break;
            case 16:
                formatControlCode = "59";
                if (!this.content.matches("[0-9]+")) {
                    throw new OkapiException("Invalid characters in data");
                }
                break;
            case 18:
                formatControlCode = "62";
                break;
            case 23:
                formatControlCode = "62";
                if (!this.content.matches("[0-9]+")) {
                    throw new OkapiException("Invalid characters in data");
                }
                break;
            default:
                throw new OkapiException("Auspost input is wrong length");
            }
            break;
        case AUSREPLY:
            if (this.content.length() > 8) {
                throw new OkapiException("Auspost input is too long");
            } else {
                formatControlCode = "45";
            }
            break;
        case AUSROUTE:
            if (this.content.length() > 8) {
                throw new OkapiException("Auspost input is too long");
            } else {
                formatControlCode = "87";
            }
            break;
        case AUSREDIRECT:
            if (this.content.length() > 8) {
                throw new OkapiException("Auspost input is too long");
            } else {
                formatControlCode = "92";
            }
            break;
        }

        infoLine("FCC: " + formatControlCode);

        if (this.mode != ausMode.AUSPOST) {
            for (i = this.content.length(); i < 8; i++) {
                zeroPaddedInput += "0";
            }
        }
        zeroPaddedInput += this.content;

        if (!this.content.matches("[0-9A-Za-z #]+")) {
            throw new OkapiException("Invalid characters in data");
        }

        /* Verify that the first 8 characters are numbers */
        deliveryPointId = zeroPaddedInput.substring(0, 8);

        if (!deliveryPointId.matches("[0-9]+")) {
            throw new OkapiException("Invalid characters in DPID");
        }

        infoLine("DPID: " + deliveryPointId);

        /* Start */
        barStateValues = "13";

        /* Encode the FCC */
        for (i = 0; i < 2; i++) {
            barStateValues += N_ENCODING_TABLE[formatControlCode.charAt(i) - '0'];
        }

        /* Delivery Point Identifier (DPID) */
        for (i = 0; i < 8; i++) {
            barStateValues += N_ENCODING_TABLE[deliveryPointId.charAt(i) - '0'];
        }

        /* Customer Information */
        switch (zeroPaddedInput.length()) {
        case 13:
        case 18:
            for (i = 8; i < zeroPaddedInput.length(); i++) {
                barStateValues += C_ENCODING_TABLE[positionOf(zeroPaddedInput.charAt(i), CHARACTER_SET)];
            }
            break;
        case 16:
        case 23:
            for (i = 8; i < zeroPaddedInput.length(); i++) {
                barStateValues += N_ENCODING_TABLE[positionOf(zeroPaddedInput.charAt(i), CHARACTER_SET)];
            }
            break;
        }

        /* Filler bar */
        switch (barStateValues.length()) {
        case 22:
        case 37:
        case 52:
            barStateValues += "3";
            break;
        }

        /* Reed Solomon error correction */
        barStateValues += calcReedSolomon(barStateValues);

        /* Stop character */
        barStateValues += "13";

        infoLine("Total Length: " + barStateValues.length());
        info("Encoding: ");
        for (i = 0; i < barStateValues.length(); i++) {
            switch (barStateValues.charAt(i)) {
            case '1':
                info("A");
                break;
            case '2':
                info("D");
                break;
            case '0':
                info("F");
                break;
            case '3':
                info("T");
                break;
            }
        }
        infoLine();

        this.readable = "";
        this.pattern = new String[1];
        this.pattern[0] = barStateValues;
        this.row_count = 1;
        this.row_height = new int[1];
        this.row_height[0] = -1;
    }

    private String calcReedSolomon(final String oldBarStateValues) {
        final ReedSolomon rs = new ReedSolomon();
        String newBarStateValues = "";

        /* Adds Reed-Solomon error correction to auspost */

        int barStateCount;
        int tripleValueCount = 0;
        final int[] tripleValue = new int[31];

        for (barStateCount = 2; barStateCount < oldBarStateValues.length(); barStateCount += 3, tripleValueCount++) {
            tripleValue[tripleValueCount] = barStateToDecimal(oldBarStateValues.charAt(barStateCount), 4) + barStateToDecimal(oldBarStateValues.charAt(barStateCount + 1), 2)
                    + barStateToDecimal(oldBarStateValues.charAt(barStateCount + 2), 0);
        }

        rs.init_gf(0x43);
        rs.init_code(4, 1);
        rs.encode(tripleValueCount, tripleValue);

        for (barStateCount = 4; barStateCount > 0; barStateCount--) {
            newBarStateValues += BAR_VALUE_TABLE[rs.getResult(barStateCount - 1)];
        }

        return newBarStateValues;
    }

    private int barStateToDecimal(final char oldBarStateValues, final int shift) {
        return oldBarStateValues - '0' << shift;
    }

    /** {@inheritDoc} */
    @Override
    protected void plotSymbol() {
        int xBlock;
        int x, y, w, h;

        this.rectangles.clear();
        x = 0;
        w = 1;
        y = 0;
        h = 0;
        for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
            switch (this.pattern[0].charAt(xBlock)) {
            case '1':
                y = 0;
                h = 5;
                break;
            case '2':
                y = 3;
                h = 5;
                break;
            case '0':
                y = 0;
                h = 8;
                break;
            case '3':
                y = 3;
                h = 2;
                break;
            }

            final Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
            this.rectangles.add(rect);

            x += 2;
        }

        this.symbol_width = (this.pattern[0].length() - 1) * 2 + 1; // no whitespace needed after
                                                                    // the final bar
        this.symbol_height = 8;
    }
}