/* * 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 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() { 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() { 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() { 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() { 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() { mode = ausMode.AUSREDIRECT; } /** {@inheritDoc} */ @Override public boolean encode() { String formatControlCode = "00"; String deliveryPointId; String barStateValues; String zeroPaddedInput = ""; int i; switch(mode) { case AUSPOST: switch(content.length()) { case 8: formatControlCode = "11"; break; case 13: formatControlCode = "59"; break; case 16: formatControlCode = "59"; if (!(content.matches("[0-9]+"))) { error_msg = "Invalid characters in data"; return false; } break; case 18: formatControlCode = "62"; break; case 23: formatControlCode = "62"; if (!(content.matches("[0-9]+"))) { error_msg = "Invalid characters in data"; return false; } break; default: error_msg = "Auspost input is wrong length"; return false; } break; case AUSREPLY: if (content.length() > 8) { error_msg = "Auspost input is too long"; return false; } else { formatControlCode = "45"; } break; case AUSROUTE: if (content.length() > 8) { error_msg = "Auspost input is too long"; return false; } else { formatControlCode = "87"; } break; case AUSREDIRECT: if (content.length() > 8) { error_msg = "Auspost input is too long"; return false; } else { formatControlCode = "92"; } break; } encodeInfo += "FCC: " + formatControlCode + '\n'; if(mode != ausMode.AUSPOST) { for (i = content.length(); i < 8; i++) { zeroPaddedInput += "0"; } } zeroPaddedInput += content; if (!(content.matches("[0-9A-Za-z #]+"))) { error_msg = "Invalid characters in data"; return false; } /* Verify that the first 8 characters are numbers */ deliveryPointId = zeroPaddedInput.substring(0, 8); if (!(deliveryPointId.matches("[0-9]+"))) { error_msg = "Invalid characters in DPID"; return false; } encodeInfo += "DPID: " + deliveryPointId + '\n'; /* 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"; encodeInfo += "Total length: " + barStateValues.length() + '\n'; encodeInfo += "Encoding: "; for (i = 0; i < barStateValues.length(); i++) { switch (barStateValues.charAt(i)) { case '1': encodeInfo += "A"; break; case '2': encodeInfo += "D"; break; case '0': encodeInfo += "F"; break; case '3': encodeInfo += "T"; break; } } encodeInfo += "\n"; readable = ""; pattern = new String[1]; pattern[0] = barStateValues; row_count = 1; row_height = new int[1]; row_height[0] = -1; plotSymbol(); return true; } private String calcReedSolomon(String oldBarStateValues) { ReedSolomon rs = new ReedSolomon(); String newBarStateValues = ""; /* Adds Reed-Solomon error correction to auspost */ int barStateCount; int tripleValueCount = 0; 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 (char oldBarStateValues, int shift) { return (oldBarStateValues - '0') << shift; } /** {@inheritDoc} */ @Override protected void plotSymbol() { int xBlock; int x, y, w, h; rectangles.clear(); x = 0; w = 1; y = 0; h = 0; for(xBlock = 0; xBlock < pattern[0].length(); xBlock++) { switch(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; } Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h); rectangles.add(rect); x += 2; } symbol_width = pattern[0].length() * 3; symbol_height = 8; } }